Prhub

#35813 [diffusion] stop the mapped-weight store from holding the parameter itself

原始 PR 作者 mickqian 合并时间 2026-08-21 21:02 文件变更 2 提交数 1 评论 1 代码增减 +40 / -1

执行摘要

修复映射权重存储被占位符覆盖的 bug

该PR修复了由#35701引入的bug:映射权重存储保存了参数对象本身,随后weight.data = ...原地替换存储,导致gpu_tensor.copy_(cpu_tensor)广播一个单元素占位符,静默地用错误权重重建层。PR body明确说明“Mapped layer weights were silently replaced by a broadcast placeholder.” 以及“Nothing raises: the reload does gpu_tensor.copy_(cpu_tensor), a one-element source broadcasts.”

值得精读,因为揭示了PyTorch中Tensor.data原地替换存储的陷阱以及如何通过独立视图规避。设计决策:使用detach().view_as而非clone以保持零拷贝。建议关注此bug对其他offload路径的影响。

讨论亮点

无review评论,无评论线程。PR作者在body中详细说明了bug根因、测试设计和性能影响,但没有外部讨论。

实现拆解

  1. 修改核心逻辑:在_initialize_layer_weights中,当权重映射到checkpoint存储时,存储一个独立于参数对象的tensor视图,即local_weight.detach().view_as(local_weight),而不是直接存储local_weight(它可能是参数对象本身)。这样后续对weight.data的修改不会影响存储的tensor。
  2. 补充测试:在test_layerwise_offload.py中新增test_the_mapped_store_survives_the_placeholder,断言存储的tensor不是参数对象,且numel大于1,并验证_mapped_bytes与存储描述一致。测试使用identity断言以精确捕获别名缺陷。
  3. 文档与配置:无需额外配置变更。该修复不引入新的配置项,仅修改内部逻辑。
文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/managers/memory_managers/layerwise_offload.py 层卸载 modified 5.86
python/sglang/multimodal_gen/test/unit/test_layerwise_offload.py 层卸载 modified 5.44

关键符号

_initialize_layer_weights test_the_mapped_store_survives_the_placeholder

关键源码片段

python/sglang/multimodal_gen/runtime/managers/memory_managers/layerwise_offload.py core-logic

核心修复文件,修改了映射权重存储的逻辑,避免了参数对象被占位符覆盖。

# python/sglang/multimodal_gen/runtime/managers/memory_managers/layerwise_offload.py
# 关键修复段:_initialize_layer_weights 内,当权重保留在映射存储上时
if keep_mapping and self._mapped_regions.holds(local_weight):
    # 之前直接存储 local_weight;但 _to_local_tensor 对非 DTensor 参数返回参数对象本身,
    # 随后 weight.data = ... 会原地替换该对象的存储,导致存储被 (1,) 占位符覆盖。
    # 现在存储一个基于同一底层存储的独立 tensor 视图,避免与参数对象共享身份。
    self._mapped_cpu_weights[layer_idx][name] = local_weight.detach().view_as(local_weight)
    self._weight_metadata[layer_idx][name] = {
        "dtype": local_weight.dtype,
        "shape": tuple(local_weight.shape),
        "stride": local_weight.stride(),
        "preserve_strides": False,
        "mapped": True,
    }
    self._mapped_bytes += local_weight.untyped_storage().nbytes()
    # 用占位符替换参数的存储,释放显存
    weight.data = self._get_shared_empty_tensor_for_target(weight, local_weight.dtype)
python/sglang/multimodal_gen/test/unit/test_layerwise_offload.py test-coverage

新增测试,验证存储的 tensor 不是参数对象且 numel 大于 1,防止回归。

# python/sglang/multimodal_gen/test/unit/test_layerwise_offload.py
# 新增测试:验证 mapped store 不会被占位符覆盖
def test_the_mapped_store_survives_the_placeholder(tmp_path, monkeypatch):
    """The store must not hold the parameter it is about to have overwritten."""
    if not pathlib.Path("/proc/self/maps").exists():
        pytest.skip("needs /proc to tell a mapping from anonymous memory")
    manager = _mapped_manager(tmp_path, monkeypatch, available_gib=0.001)
    stored = manager._mapped_cpu_weights[0]
    assert stored, "expected the weight to stay mapped"
​
    parameters = dict(manager.model.named_parameters())
    for name, tensor in stored.items():
        # 占位符会导致 numel == 1,而真实权重应远大于此
        assert tensor.numel() > 1, (
            f"{name} holds {tensor.numel()} element(s): the store is holding the "
            "placeholder that was assigned to the parameter, not the weight"
        )
        # 存储的 tensor 不能是参数对象本身,否则 .data 赋值会覆盖它
        assert tensor is not parameters[name], (
            f"{name} in the store is the parameter object itself, so assigning "
            "to the parameter's .data will overwrite the store"
        )
    # 字节计数器应与存储内容一致,确保没有隐藏的占位符
    assert manager._mapped_bytes == sum(
        t.numel() * t.element_size() for t in stored.values()
    ), "the byte counter and the store must describe the same weights"

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 性能影响:修复后,在受限主机(如32 GiB)下,映射权重会真正转移到GPU,导致denoise时间从142秒增至330.74秒。这可能影响使用layerwise offload的低内存场景的用户体验。
  2. 正确性回归:修复改变了存储的tensor来源,虽然存储数据底层相同,但可能影响依赖tensor对象身份的代码。测试已覆盖但需警惕。
  3. 测试覆盖:新增测试断言了存储tensor与参数对象不同,但未覆盖所有可能的别名情况,如_to_local_tensor返回其他视图的场景。

影响范围:layerwise offload的内存管理模块,主要涉及diffusion模型的权重加载。影响程度:修复一个静默数据损坏bug,但会暴露真实的性能开销。用户可能需要在受限主机上权衡性能与准确度。团队需要了解之前基于该路径测量的性能数据已不可靠。

核心路径变更 性能回退风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论