Prhub

#56600 [CI] Fix Qwen3 Omni DSpark load test config

原始 PR 作者 khluu 合并时间 2026-09-13 02:29 文件变更 1 提交数 1 评论 3 代码增减 +5 / -0

执行摘要

补齐 DSpark 测试 mock 配置,修复 H200 MIG CI 失败。

PR body 明确指出:"PR #54416 made DSpark call get_pp_safe_draft_load_config(vllm_config.load_config). The existing Qwen3 Omni test mock still lacked load_config and only stubbed the pipeline group used by embedding sharing, so H200 MIG Model Executor fails deterministically in test_dspark_shares_target_embedding_with_smaller_draft_vocabulary." 也就是说,这不是测试逻辑本身出错,而是上游 loader 契约变化后测试 mock 未同步更新,属于 CI 回归修复。

值得快速阅读,不建议深入精读。核心看点是:一个上游 loader 契约变化(PR #54416)如何传导到测试 mock 的更新,以及 DSpark loader 同时依赖两个模块的 get_pp_group 这一内部调用结构。后续维护者遇到类似失败时,应优先检查 get_pp_safe_draft_load_config 与 pipeline group stub 是否齐全。

讨论亮点

该 PR 的 review 过程几乎没有技术交锋:claude[bot] 因 PR 来自 fork 仓库而自动跳过 review;chatgpt-codex-connector[bot] 在 draft 标记为 ready 后完成 Codex review,未报告任何 findings;维护者 ZJY0516 直接批准并触发 CI。唯一的人工指令是 /ci run。由于没有实质性 review 评论,本 PR 的设计权衡主要体现在 PR body 的说明中:作者明确表示这是为了让测试满足 loader 当前配置契约,而非增强测试覆盖。

实现拆解

本次变更只涉及 tests/model_executor/test_qwen3_omni.py 中的 test_dspark_shares_target_embedding_with_smaller_draft_vocabulary,共两步补丁:

  1. 补齐 load_config 字段:在构造 vllm_config 的 SimpleNamespace 中新增 load_config=SimpleNamespace()。DSpark 的 draft model loader 现在会调用 get_pp_safe_draft_load_config(vllm_config.load_config),缺少该字段会直接触发 AttributeError,这是 H200 MIG 下确定性失败的直接原因。

  2. 补齐第二个 pipeline group stub:原测试只 patch 了 vllm.v1.worker.gpu.spec_decode.eagle.utils.get_pp_group(embedding 共享路径使用),但当前 draft model loader 还会从 vllm.v1.worker.gpu.spec_decode.utils 读取 pipeline group,因此新增一个针对 spec_decode.utils.get_pp_group 的 patch,两者都必须存在才能走通 load_dspark_model 的加载阶段。

  3. 验证配套:作者运行 pytest tests/model_executor/test_qwen3_omni.py::test_dspark_shares_target_embedding_with_smaller_draft_vocabulary -q 确认 1 个用例通过,并跑通 pre-commit;Buildkite CI #88542 也已触发。测试断言完全未改,仍只校验 loaded_model.model.embed_tokens is target_embedding,说明意图是维持原有测试语义。

无源码、schema、配置或部署层面的配套改动。

文件 模块 状态 重要度
tests/model_executor/test_qwen3_omni.py 模型测试 modified 4.27

关键符号

test_dspark_shares_target_embedding_with_smaller_draft_vocabulary

关键源码片段

tests/model_executor/test_qwen3_omni.py test-coverage

唯一的变更文件,修复 DSpark embedding 共享测试在 H200 MIG 下的确定性失败:补齐 `load_config` 字段,并新增 `spec_decode.utils.get_pp_group` 的 stub。

@pytest.mark.skip_global_cleanup
def test_dspark_shares_target_embedding_with_smaller_draft_vocabulary():
    from vllm.v1.worker.gpu.spec_decode.dspark import utils as dspark_utils
​
    # target 与 draft 的 vocab 大小故意不同(100 vs 99),
    # 用来验证 `load_dspark_model` 会保留 target 的 embed_tokens。
    target_embedding = nn.Embedding(100, 8)
    draft_embedding = nn.Embedding(99, 8)
    target_model = SimpleNamespace(model=SimpleNamespace(embed_tokens=target_embedding))
    draft_model = SimpleNamespace(
        model=SimpleNamespace(embed_tokens=draft_embedding),
        has_own_embed_tokens=False,
    )
    draft_model_config = SimpleNamespace(
        hf_config=SimpleNamespace(model_type="qwen3"),
        get_vocab_size=Mock(return_value=99),
    )
    vllm_config = SimpleNamespace(
        speculative_config=SimpleNamespace(
            draft_model_config=draft_model_config,
            draft_parallel_config=SimpleNamespace(tensor_parallel_size=1),
            attention_backend=None,
            kv_cache_dtype=None,
        ),
        parallel_config=SimpleNamespace(tensor_parallel_size=1),
        attention_config=SimpleNamespace(backend=None),
        cache_config=SimpleNamespace(),
        # PR #54416 之后,DSpark loader 会调用
        # `get_pp_safe_draft_load_config(vllm_config.load_config)`,
        # 因此 mock 必须补齐 `load_config`,否则 H200 MIG 环境会确定性失败。
        load_config=SimpleNamespace(),
        model_config=SimpleNamespace(get_vocab_size=Mock(return_value=100)),
    )
​
    def fake_replace(config, **changes):
        values = vars(config).copy()
        values.update(changes)
        return SimpleNamespace(**values)
​
    with (
        patch.object(dspark_utils, "replace", side_effect=fake_replace),
        # embedding 共享路径从 eagle.utils 读取 pipeline group
        patch(
            "vllm.v1.worker.gpu.spec_decode.eagle.utils.get_pp_group",
            return_value=SimpleNamespace(world_size=1),
        ),
        # 当前 draft model loader 还会从 spec_decode.utils 读取 pipeline group,
        # 两个模块的 `get_pp_group` 必须同时 stub,加载阶段才不会失败。
        patch(
            "vllm.v1.worker.gpu.spec_decode.utils.get_pp_group",
            return_value=SimpleNamespace(world_size=1),
        ),
        patch("vllm.compilation.backends.set_model_tag", return_value=nullcontext()),
        patch("vllm.model_executor.model_loader.get_model", return_value=draft_model),
        patch(
            "vllm.model_executor.models.qwen3_dflash.dflash_has_any_non_causal",
            return_value=False,
        ),
        patch(
            "vllm.model_executor.models.utils.get_draft_quant_config",
            return_value=None,
        ),
    ):
        loaded_model = dspark_utils.load_dspark_model(target_model, vllm_config)
​
    assert loaded_model.model.embed_tokens is target_embedding

评论区精华

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

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

风险与影响

风险集中在测试 mock 与 loader 内部实现的耦合:

  1. load_config=SimpleNamespace() 是空对象,若未来 loader 继续深入读取 load_config 的具体字段(如 model_loader_extra_config、download_dir),该 mock 会再次失败,需要持续跟随契约演进。
  2. 双 get_pp_group stub 分别指向 eagle.utils 与 spec_decode.utils 两个模块,一旦 loader 内部调用路径合并或改名,其中一个 patch 会变成无效 stub(不报错但失去意义),存在静默漂移风险。
  3. 测试断言未增强,本次修复只是让既有测试恢复通过,没有新增对 get_pp_safe_draft_load_config 返回值的正向校验,覆盖深度有限。

由于全部改动局限在单测 mock 内,对用户与生产路径无任何风险。

影响范围很小:仅影响 tests/model_executor/test_qwen3_omni.py 这一个单测文件,直接受益方是 H200 MIG 等 GPU 环境下的 CI 稳定性,消除了确定性失败;对 vLLM 运行时、模型加载、推理性能均无影响。团队层面,这个 PR 是 spec decode 测试契约同步的小样本,表明 DSpark 测试需要持续跟踪 spec_decode.utils 与 eagle.utils 两处 pipeline group 的调用变化。

测试 mock 依赖 loader 内部契约 load_config 为空对象,未覆盖字段级断言 双 get_pp_group stub 存在静默漂移风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论