Prhub

#35618 [diffusion] UX: report where a component's weights are

原始 PR 作者 mickqian 合并时间 2026-08-20 13:05 文件变更 4 提交数 3 评论 2 代码增减 +248 / -3

执行摘要

加载日志按内存类型报告组件权重驻留位置

PR body 指出:加载器报告的 consumed GPU mem 是加载前后空闲显存的差值,对任何 offloaded 组件该差值都是 0.00 GB,日志读起来像组件零成本(model size: 21.16 GB, consumed GPU mem: 0.00 GB)。而 21.16 GB went somewhere,运行期没有任何信息说明权重真正在哪里,导致在显存受限的卡上无法区分驻留权重与流式权重,也无法区分 pinned host 内存(内核不可回收)与普通 host 内存。

值得精读。虽然只是日志增强,但 component_residency_bytes() 的实现有几个可复用的设计:用 /proc/self/maps 区分 file-backed 与 anonymous host 内存、用 storage 指针去重避免扁平 buffer 重复计数、pinned 判断先于 file-backed 判断(因为 CUDA host allocator 背后是命名映射)。对任何需要诊断显存/内存占用的模块都有参考价值。

讨论亮点

本 PR 没有收到任何 review 评论;两条 issue 评论均为作者发出的 /tag-and-rerun-ci(对应 CI Extra 曾失败后重跑)。作者在 PR body 中自行阐述了两个必须算对的会计细节:一是 layerwise-offloaded 权重不在 parameters() / buffers() 中(模块只留 (1,) 占位符),必须额外遍历 offload 管理器的 iter_cpu_weights();二是大小必须取自 storage 并按 storage 去重,因为一块扁平 host buffer 支撑多个逻辑权重,逐 tensor 求和会重复计数。此外还说明了 host_mapped 统计的是映射大小而非驻留页,真实占用至多等于报告值。

实现拆解

  1. 新增地址空间归类工具python/sglang/multimodal_gen/runtime/loader/utils.py):新增 _read_process_mappings() 读取 /proc/self/maps,按地址排序返回 (start, end, is_file_backed) 三元组;新增 component_residency_bytes() 遍历模块的 parameters()buffers() 以及 layerwise_offload_managersiter_cpu_weights(),以 untyped_storage().data_ptr() 去重并归入 vramhost_pinnedhost_mappedhost 四个桶;新增 format_component_residency() 仅渲染非空桶,host 显示为 host pageablehost_mapped 显示为 host mapped

  2. 改造组件加载日志行python/sglang/multimodal_gen/runtime/loader/component_loaders/component_loader.py):load() 中的 logger.info 移除 consumed GPU mem: %.2f GB,改用 format_component_residency(component) 输出 resident in ... 形式,同时保留 avail GPU mem。这样加载时点(layerwise offload 配置之前约 20 s)的驻留位置能直接可见。

  3. 改造 layerwise offload 启用日志python/sglang/multimodal_gen/runtime/managers/memory_managers/layerwise_offload.py):configure_layerwise_offload_modules() 中原本只输出组件名列表,现在逐个组件附上 format_component_residency(modules[name]),报告卸载后的最终放置(例如 vram 4.63 GB, host pinned 17.25 GB),弥补加载行只能描述预卸载放置的缺口。

  4. 新增单元测试python/sglang/multimodal_gen/test/unit/test_component_residency_report.py,10 个用例):覆盖 host 权重计数、同一扁平 buffer 的多个切片只计一次、空占位符跳过、file-backed 与 anonymous 分离、pinned 优先于 file-backed、resident 参数入 vram、pinned host 分离、非 nn.Module 返回空、格式化只列非零桶、无权重组件输出 weights: none;其中 device/pinned 用例在无 CUDA 时跳过,mapping 用例在无 /proc 时跳过。

  5. 配套说明:无配置、部署或 schema 变更;改动全部集中在启动期日志路径,不触碰 serving 热路径。

文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/loader/utils.py 加载工具 modified 8.12
python/sglang/multimodal_gen/test/unit/test_component_residency_report.py 单元测试 added 7.21
python/sglang/multimodal_gen/runtime/managers/memory_managers/layerwise_offload.py 逐层卸载 modified 5.4
python/sglang/multimodal_gen/runtime/loader/component_loaders/component_loader.py 组件加载 modified 4.56

关键符号

_read_process_mappings component_residency_bytes is_file_backed add format_component_residency

关键源码片段

python/sglang/multimodal_gen/test/unit/test_component_residency_report.py test-coverage

新增 10 个单元测试,覆盖分桶、storage 去重、占位符跳过、file-backed 与 anonymous 分离、pinned 优先及格式化规则,是本次变更正确性的主要保障。

# python/sglang/multimodal_gen/test/unit/test_component_residency_report.pyclass _FakeOffloadManager:
    """Stands in for LayerwiseOffloadManager's host-side weight store."""
​
    def __init__(self, tensors):
        self._tensors = tensors
​
    def iter_cpu_weights(self):
        for index, tensor in enumerate(self._tensors):
            yield f"w{index}", tensor
​
​
class _Streamed(nn.Module):
    """A module whose real weights live in its managers, not its parameters."""
​
    def __init__(self, managers):
        super().__init__()
        # layerwise offload leaves (1,) placeholders behind
        self.placeholder = nn.Parameter(torch.empty(0), requires_grad=False)
        self.layerwise_offload_managers = managers
​
​
class TestComponentResidencyBytes:
    def test_slices_of_one_buffer_are_counted_once(self):
        # this is the layerwise layout: one flat buffer, many logical weights
        buffer = torch.empty(1024, dtype=torch.float32)
        views = [buffer[0:256], buffer[256:512], buffer[512:1024]]
        module = _Streamed([_FakeOffloadManager(views)])
        assert component_residency_bytes(module)["host"] == 4096
​
    def test_pinned_wins_over_the_file_backed_check(self):
        if not torch.cuda.is_available():
            pytest.skip("pinning needs CUDA")
        # CUDA's host allocator sits behind a named mapping, so the
        # file-backed check alone would call this one mapped
        pinned = torch.empty(1024, dtype=torch.float32, pin_memory=True)
        module = _Streamed([_FakeOffloadManager([pinned])])
        totals = component_residency_bytes(module)
        assert totals["host_pinned"] == 4096
        assert totals["host_mapped"] == 0
python/sglang/multimodal_gen/runtime/loader/component_loaders/component_loader.py core-logic

加载完成日志的消费入口:移除误导性的 `consumed GPU mem`,改为输出权重驻留位置。

# python/sglang/multimodal_gen/runtime/loader/component_loaders/component_loader.py
# 加载完成的日志行:不再输出“消耗了多少显存”,而是报告权重实际驻留位置。
logger.info(
    f"Loaded %s: %s ({source} version). model size: %s GB, %s. avail GPU mem: %.2f GB",
    component_name,
    component.__class__.__name__,
    model_size,
    format_component_residency(component), # 例如 "host pageable: 21.16 GB"
    current_gpu_mem,
)

评论区精华

CI Extra 失败与重跑 other

作者在 issue 评论中两次发出 `/tag-and-rerun-ci`,对应 CI 状态块中 Latest PR Test (Extra) 一度失败后重跑;无实质技术讨论。

结论:无 review 评论;变更由作者自查并附带完整精度与速度测试说明。 · 已解决

风险与影响

  1. 平台相关:/proc/self/maps 只存在于 Linux,非 Linux 下 _read_process_mappings() 返回 None,host 字节不再拆分 file-backed 与 anonymous,但仍能报告总量,功能降级而非失效。
  2. 计数口径:host_mapped 报告的是文件映射区间大小而非实际驻留页,映射的 safetensors 文件按需缺页加载时可能虚高,日志读者需要知道这是上界。
  3. 日志时机:加载行在 layerwise offload 配置前约 20 s 运行,只能描述预卸载放置;若用户只看第一行可能误判,必须结合 offload 启用行(现已同步输出 settled placement)。
  4. 稳定性:untyped_storage() / is_pinned() 均有异常捕获保护,非 nn.Module 直接返回空字典,不会让加载流程崩溃。
  5. 性能:每个组件启动期多跑两次 /proc 解析与指针二分查找,不在 serving 路径,实测 per-step 时间不变(1.03 s)。

对用户(部署者):显存受限场景下日志首次能回答"权重到底在哪",可区分驻留权重与流式权重、pinned 与 pageable,有助于判断是否该开 layerwise offload 或扩容。对系统:无运行时行为变化,仅日志文案变化,同一运行的输出 SHA-256 (78a2a963…) 与改动前一致。对团队:diffusion 子系统的可观测性提升,为后续 offload 策略调优提供诊断依据;测试覆盖了分桶、去重、平台降级等关键分支,但对 CUDA 与 /proc 的依赖意味着部分用例在无对应环境的 CI 上会自动跳过。

平台相关:依赖 /proc/self/maps host_mapped 统计映射大小可能虚高 日志只描述加载时点,offload 后需看第二行日志 测试依赖 CUDA 与 /proc 环境会跳过

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论