Prhub

#38783 [Diffusion] Quiet request-path cache diagnostics

原始 PR 作者 mickqian 合并时间 2026-09-11 22:45 文件变更 3 提交数 2 评论 1 代码增减 +5 / -5

执行摘要

把 diffusion 请求路径的 5 处缓存诊断日志由 info 降为 debug

PR body 明确给出三点动机:一是把 layerwise residency 的页回收诊断从 info 移到 debug;二是把 MiniMax-H3 AdaLN 缓存的主机内存压力与缓存命中诊断从 info 移到 debug;三是把 Helios 请求级质量融合挂载诊断从 info 移到 debug。归结起来就是 body 中的那句 “preserve startup, warning, and error visibility while avoiding per-request log noise” —— 这些日志都挂在单次请求的执行路径上(每次缓存跳过、每次 rebuild、每次 host 命中、每次阶段换出),在 diffusion 在线服务下会随请求量线性增长,属于典型的日志噪声而非故障信号。

值得快速浏览,但不值得精读。三点建议:(1)合并前确认 diffusion 侧已有 metrics 或看板覆盖 built_plans / host_hit_plans / host_pressure_skips,否则这次降级等于把唯一的信号源关掉;(2)在运行手册中登记“需要开启 DEBUG 才能看到 AdaLN host tier / layerwise paged-out 诊断”,避免排障时误判功能缺失;(3)后续若有独立 PR 为这三个计数器补充遥测导出(可参考 PR#38782 把 telemetry 暴露到 nightly 看板的做法),可以把观测能力补齐,届时本 PR 的降级才真正无代价。

讨论亮点

本 PR 没有任何 review 评论(review_comments_count 为 0,Review 审核列表为空),唯一的交互是作者自己在 Issue 评论里发出的 /tag-and-rerun-ci。有效信息大多来自 PR body 中的 CI 状态块:Latest PR Test (Base) 通过,而 Latest PR Test (Extra) 与 Latest PR Test (AMD ROCm 10) 均为失败标记,最终由作者本人(merged_by: mickqian)合并。结合第二个 commit 的 “Merge main to pick up IPC JIT recovery for diffusion CI”,可以判断失败的两条流水线更可能是 diffusion JIT 构建相关的基础设施问题,而非本 PR 的日志改动所致,但 PR 内没有留下明确的失败归因讨论。

实现拆解

  1. MiniMax-H3 AdaLN 主机缓存的 3 处请求级日志降级(python/sglang/multimodal_gen/runtime/models/dits/minimax_h3_adaln_cache.py)。分别位于 _skip_for_pressure()(内存压力下跳过某组 plan 的缓存)、build() 尾部(重建 plan 后打印 rebuild 次数与常驻比例)、_swap_in_from_host() 尾部(从主机固定页换入 plan)。三处都保留前置的 self.stats 计数(host_pressure_skips、built_plans、host_hit_plans),因此降级的只是文本输出,缓存命中、重建与压力跳过的量化观测能力不变。这样改的原因是这三条路径在稳态下每次请求都可能触发,属于“正常但频繁”,不是异常。

  2. Helios 质量融合挂载日志降级(python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/helios_denoising.py)。改动点只在 HeliosChunkedDenoisingStage._maybe_toggle_quality_fusions() 中 mount_helios_gated_residual() 成功分支的 logger.debug。注意这里的 want == self._quality_fusions_mounted 提前 return 保证了挂载/卸载只在质量档位真正切换时发生,日志本身频率并不高,但仍然被归入“请求路径诊断”而一起降级;unmount 分支本来就没有日志,因此挂载与卸载的日志对称性没有变化。

  3. Layerwise 逐层换出的页回收诊断降级(python/sglang/multimodal_gen/runtime/managers/memory_managers/component_residency_strategies.py)。位于 finish_use() 中 CUDA 共享主机内存池分支:在 empty_cache() 之后,遍历 module.layerwise_offload_managers 调用 advise_mapped_pages_cold(room_bytes=...) 统计 paged_out,只有 paged_out 非零时才打印。降为 debug 后,这条“本组件页被主动标冷”的信息默认不可见,但 SHARED_POOL_NEXT_STAGE_HEADROOM_BYTES 参与的 room_bytes 计算逻辑与冷页建议调用本身完全未动。

  4. 测试、配置与部署配套:无。本 PR 未新增或修改任何测试文件、CI 配置或部署脚本,也没有 schema / 接口契约变化(change_kind 中标注的 data-contract 仅对应日志文案参数,不涉及数据格式)。

  5. 分支与合并动作:head 分支为 codex/diffusion-cache-log-hygiene,共 2 个 commit —— 主体提交 chore(diffusion): quiet request-path cache diagnostics 和一次 Merge main to pick up IPC JIT recovery for diffusion CI,后者说明作者在 CI 失败后选择同步 main 而不是修改本 PR 的代码。

文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/models/dits/minimax_h3_adaln_cache.py 缓存诊断 modified 4.63
python/sglang/multimodal_gen/runtime/managers/memory_managers/component_residency_strategies.py 常驻策略 modified 3.92
python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/helios_denoising.py 去噪阶段 modified 3.93

关键符号

_skip_for_pressure build _swap_in_from_host _maybe_toggle_quality_fusions finish_use

关键源码片段

python/sglang/multimodal_gen/runtime/models/dits/minimax_h3_adaln_cache.py logging

本 PR 改动最集中的文件,涉及请求路径上最多的日志降级(3 处),也是 AdaLN 主机缓存可观测性的关键位置。

class _MiniMaxH3AdaLNHostTier:
    # ... 省略 host slab 分配与 group 管理部分 ...
​
    def _skip_for_pressure(self, group_key) -> None:
        """内存压力下放弃把某组 plan 写入主机缓存时调用。
​
        这条日志原本是 info 级,而它在稳态下会随请求频率反复触发,
        因此本 PR 改为 debug:默认安静,需要时再开 DEBUG 观察。
        注意 self.stats.host_pressure_skips 仍然照常累加,
        量化观测能力没有随日志级别一起消失。
        """
        if self.stats is not None:
            self.stats.host_pressure_skips += 1
        logger.debug(
            "MiniMax H3 AdaLN host tier: group of %d plan(s) skipped under "
            "memory pressure; it will recompute on its next occurrence",
            len(group_key),
        )
​
​
class _MiniMaxH3AdaLNCache:
    # ... 省略 slot 分配、checkpoint 投影与 host tier 交互部分 ...
​
    def build(self, *, wanted, missing, group_key) -> None:
        """按需重建缺失的 plan,并把整组 plan 写回主机缓存。"""
        pending_slots = self._allocate_slots(wanted=wanted, missing=missing)
        # ... 省略 adaln 输入准备、ColumnParallelLinear 分片投影与异常回滚 ...
​
        # Commit host metadata only after every layer has been written.
        self._slots.update(pending_slots)
        self.rebuilds += 1
        self.stats.built_plans += len(missing)
        # rebuild 属于缓存淘汰后的正常重算路径,不是异常;
        # 降为 debug 以免在请求路径上每请求刷一行,
        # 真实重建次数仍由 stats.built_plans / self.rebuilds 记录。
        logger.debug(
            "MiniMax H3 AdaLN: rebuilt %d plan(s), %d/%d resident, pass #%d",
            len(missing),
            len(self._slots),
            self.max_plans,
            self.rebuilds,
        )
​
    def _swap_in_from_host(self, *, wanted, missing) -> None:
        """从固定主机页换入缺失的 plan,不需要再读权重。"""
        assert self._host_tier is not None
        assignments = self._allocate_slots(wanted=wanted, missing=missing)
        # ... 省略 H2D 拷贝、fence_gpu_reads 与异常时归还 slot ...
​
        self._slots.update(assignments)
        self.stats.host_hit_plans += len(missing)
        # host 命中同样是逐请求事件,降为 debug;
        # 命中量可从 stats.host_hit_plans 读出。
        logger.debug(
            "MiniMax H3 AdaLN: %d plan(s) from the host cache, %d/%d resident",
            len(missing),
            len(self._slots),
            self.max_plans,
        )
python/sglang/multimodal_gen/runtime/managers/memory_managers/component_residency_strategies.py core-logic

layerwise 逐层换出策略中唯一一处请求路径诊断日志被降级,涉及 CUDA 共享主机内存池的冷页建议路径。

def finish_use(
    self,
    module: nn.Module,
    use: ComponentUse,
    state: ResidencyState,
) -> None:
    """组件本阶段使用结束,释放逐层窗口并把本组件的页标为冷页。"""
    # ... 省略 release_all / park_non_layer_weights 与 MPS 分支 ...
​
    elif current_platform.is_cuda() and current_platform.device_shares_host_memory():
        # 阶段内流式的逐层窗口已经释放,但仍被 caching allocator 持有;
        # 在共享池上这份占用就是下一阶段映射所需的 page cache,先交还。
        empty_cache = getattr(torch.get_device_module(), "empty_cache", None)
        if empty_cache is not None:
            empty_cache()
​
        # 下一阶段到来前,本组件的页是缓存里价值最低的一批;
        # 可留给它的空间 = 当前可用空间 - 中间阶段需要的余量。
        room_bytes = max(
            0, shared_pool_available_bytes() - SHARED_POOL_NEXT_STAGE_HEADROOM_BYTES
        )
        paged_out = 0
        for manager in module.layerwise_offload_managers:
            advise_cold = getattr(manager, "advise_mapped_pages_cold", None)
            if advise_cold is not None:
                paged_out += int(advise_cold(room_bytes=room_bytes) or 0)
​
        if paged_out:
            # 该分支在每次阶段切换时都可能走到,属于稳态路径而非异常;
            # 本 PR 把这条诊断降到 debug,避免请求路径噪声,
            # 但 advise_mapped_pages_cold 的调用与 room_bytes 计算完全未改。
            logger.debug(
                "Layerwise offload: paged out the first %.1f GiB of %s so the "
                "next request's stream fits the %.1f GiB the cache can give it.",
                paged_out / 1024**3,
                use.component_name,
                room_bytes / 1024**3,
            )
python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/helios_denoising.py logging

Helios chunked denoising 阶段按请求质量档位挂载 / 卸载 gated residual 的日志被降级,是本次请求路径日志治理的一部分。

class HeliosChunkedDenoisingStage(PipelineStage):
    """Helios 分块去噪阶段,按请求质量档位切换 gated-residual 快路径。"""
​
    def __init__(self, transformer, scheduler):
        super().__init__()
        self.transformer = transformer
        self.scheduler = scheduler
        self._quality_fusions_mounted = False
​
    def _maybe_toggle_quality_fusions(self, batch: Req) -> None:
        # 仅当 quality 为 "extra-high"/"high" 时挂载请求级的 Helios
        # gated-residual 快路径;"lossless" 保持参考实现的 FP32 乘法,
        # 以保证逐位一致。
        quality = getattr(batch.sampling_params, "quality", "lossless")
        want = quality_allows_kernel_fusions(quality)
        # 档位未变化时提前返回,避免每个请求都重复挂载 / 卸载。
        if want == self._quality_fusions_mounted:
            return
        self._quality_fusions_mounted = want
        if self.transformer is None:
            return
        if want:
            if mount_helios_gated_residual(self.transformer):
                # 挂载成功原本走 info,本 PR 降为 debug:
                # 该信息只在质量档位切换时出现,属于诊断而非故障,
                # 默认保持安静;mount 失败本身仍无日志(行为未变)。
                logger.debug(
                    "Mounted Helios per-token gated residual for quality=%s", quality
                )
        else:
            # 卸载分支本来就不打日志,因此挂载 / 卸载的日志行为依旧不对称。
            unmount_helios_gated_residual(self.transformer)

评论区精华

CI 失败与 /tag-and-rerun-ci:日志改动是否需要重跑验证 测试

PR body 的 CI 状态块显示 Latest PR Test (Base) 通过,而 Latest PR Test (Extra) 与 Latest PR Test (AMD ROCm 10) 为失败;作者随后在 Issue 评论中发出 `/tag-and-rerun-ci`,并追加 commit “Merge main to pick up IPC JIT recovery for diffusion CI”。

结论:没有留下失败归因的正式讨论。就改动内容看,5 行日志级别调整不可能影响内核构建或 ROCm 路径,作者通过同步 main 带入 JIT 修复后直接合并,可视为将失败归因于基础设施问题。 · 已解决

缺少 review 意见的日志级别变更 other

review_comments_count 为 0,Review 审核列表为空,唯一评论是作者的 CI 重跑指令,没有对“哪些日志该降级、降级后靠什么观测”进行任何评审交锋。

结论:变更由作者自行合并(merged_by 与 author 均为 mickqian),属于典型的低风险自治合并;观测替代方案(stats 计数器)未在 PR 中被显式确认。 · 已解决

风险与影响

  1. 可观测性回退(主要风险):minimax_h3_adaln_cache.py 的 build() 与 _swap_in_from_host() 两条日志原本是排查“plan 反复重建 / host 缓存是否生效”的第一手信号。降为 debug 后,运维如果仍然依赖 grep stdout 观察主机缓存行为,将直接失去这三个维度;必须改用 stats.built_plans / host_hit_plans / host_pressure_skips 三个计数器(这些字段在 diff 中保留)或显式把日志级别开到 DEBUG。

  2. 性能回归被掩盖的窗口:rebuilds 与 rebuild pass # 是判断缓存是否退化为“每请求全量重建”的关键线索。降级之后,即使出现 rebuild 频率异常升高,也不会再有 info 级提示,可能延迟发现主机缓存失效类问题。同理,component_residency_strategies.finish_use() 中 paged_out 长期为零(冷页建议失效)也不会再被日志暴露。

  3. 日志级别不一致的隐患:_skip_for_pressure() 与同文件 _evict_oldest_group() 属于同一语义层级,后者只累加统计不打日志,前者现在也降为 debug,一致性反而更好;但同一仓库其它模块(如 HiCache 相关路径)仍可能保留 info 级缓存诊断,跨模块排障时级别语义不完全统一。

  4. 缺少测试覆盖:无测试变更,日志级别本身难以被单测锁定,未来若有人误改回 info 或误删 stats 计数,CI 不会报警。

  5. 兼容性风险极低:无 API、参数、序列化格式或内核变更,改动范围仅 5 行日志调用,回滚成本接近于零。

对使用者(部署 diffusion 服务的工程师):默认 stdout 变得干净,不再被逐请求的 AdaLN 主机缓存与 layerwise 换出信息刷屏,长跑与压测场景下日志体积和 I/O 开销小幅下降;代价是查看这些诊断需要主动设置 DEBUG 日志级别或接入 metrics。对系统:请求路径上每次缓存跳过/命中/重建的字符串格式化开销被省掉,属于顺带的微小性能收益,不改变任何缓存、内存或调度语义。对团队:本 PR 与同期的 diffusion 可观测性工作(把指标搬到看板)方向一致,意味着团队对 diffusion 运行时的观测入口正在从“读日志”转向“读指标/看板”,需要同步更新排障手册中涉及这三条日志的说明。

可观测性回退 无测试覆盖 CI Extra/ROCm 失败未归因 缓存退化信号被静音

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论