Prhub

#56011 [CI][Test][Spec Decode] Fix CI failure of Qwen3 Omni DSpark loader mock

原始 PR 作者 starkwj 合并时间 2026-09-09 14:06 文件变更 1 提交数 1 评论 2 代码增减 +2 / -1

执行摘要

修复 Qwen3 Omni DSpark 单测 mock 缺 parallel_config

PR body 明确指出:PR#55472 changed load_dspark_model to derive the draft parallel configuration from the target vllm_config.parallel_config,但 missed updating text fixture in unit test test_qwen3_omni.py。测试使用不完整的 SimpleNamespace mock,运行时报 AttributeError: 'types.SimpleNamespace' object has no attribute 'parallel_config'。本 PR 的目的就是补齐缺失字段、恢复 CI 绿灯。

不建议精读,变更微小且机械,无设计决策可借鉴。值得留意两点:一是测试 fixture 必须在源码重构时保持同步,本 PR 正是 PR#55472 落地后的配套补丁,这类"源码改了、测试没跟上"的 CI 失败在快速演进的大型仓库中很常见;二是可考虑后续将 DSpark 测试的手工 SimpleNamespace mock 迁移到更贴近真实 Config 的对象或增加字段契约校验,以降低长期维护成本,并关注 #56600 对同一 mock 的后续修复。

讨论亮点

本 PR 没有人工 review 评论(review_comments_count=0),技术核验主要来自自动审核与 CI 触发:

  • copilot-pull-request-reviewer[bot](Lite 模式)给出 🟢 Approval recommended,认定改动是 minimal、correct 的测试夹具更新,新增字段与 load_dspark_model 实际访问的配置字段一一对应,除修复失败的单元测试外无行为风险。
  • gau-nernst 在 Issue 评论触发 /ci run,github-actions[bot] 随即启动 Buildkite CI #87837 验证。
  • gau-nernst、Isotr0py、ywang96 三位维护者均直接批准,无争议点或未解决疑虑。

实现拆解

  1. 变更入口:唯一改动文件 tests/model_executor/test_qwen3_omni.py 中的测试函数 test_dspark_shares_target_embedding_with_smaller_draft_vocabulary。
  2. 字段补齐(两处,对应 load_dspark_model 的两个读取点):
    • 在 vllm_config mock 中新增 parallel_config=SimpleNamespace(tensor_parallel_size=1),对应 PR#55472 新增的从目标侧推导 draft 并行配置的逻辑;
    • 将 speculative_config.draft_parallel_config 从空的 SimpleNamespace() 改为 SimpleNamespace(tensor_parallel_size=1),对应 draft 侧 TP 规模读取。
  3. 验证配套:作者运行目标单测及关联的 32 个用例(含 test_eagle3_aux_hidden_states_pp.py、test_spec_decode_embed_sharing_pp.py、test_pp_utils.py、test_aux_attn_res_stream.py、test_eagle3.py 等)全部通过;gau-nernst 触发 Buildkite CI #87837 复核合入。
文件 模块 状态 重要度
tests/model_executor/test_qwen3_omni.py 模型单测 modified 3.73

关键符号

test_dspark_shares_target_embedding_with_smaller_draft_vocabulary

关键源码片段

tests/model_executor/test_qwen3_omni.py test-coverage

唯一变更文件:补齐 DSpark 加载单测中 vllm_config mock 缺失的 parallel_config 字段,并为 draft_parallel_config 补上 tensor_parallel_size,修复 PR#55472 引入的 AttributeError,恢复 CI 通过。

# tests/model_executor/test_qwen3_omni.py
# 测试:draft 词表(99)小于 target 词表(100)时,DSpark 应复用 target embedding
def test_dspark_shares_target_embedding_with_smaller_draft_vocabulary():
    from vllm.v1.worker.gpu.spec_decode.dspark import utils as dspark_utils
​
    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 mock,字段必须与
    # load_dspark_model 的真实读取路径保持一致,否则会抛 AttributeError
    vllm_config = SimpleNamespace(
        speculative_config=SimpleNamespace(
            draft_model_config=draft_model_config,
            # 修复点 1:PR#55472 后 load_dspark_model 会读取
            # draft_parallel_config.tensor_parallel_size 来对齐 draft 的 TP 规模
            draft_parallel_config=SimpleNamespace(tensor_parallel_size=1),
            attention_backend=None,
            kv_cache_dtype=None,
        ),
        # 修复点 2:PR#55472 改为从目标 vllm_config.parallel_config
        # 推导 draft 并行配置,缺少该字段正是本次 CI 失败的根因
        parallel_config=SimpleNamespace(tensor_parallel_size=1),
        attention_config=SimpleNamespace(backend=None),
        cache_config=SimpleNamespace(),
        model_config=SimpleNamespace(get_vocab_size=Mock(return_value=100)),
    )
​
    def fake_replace(config, **changes):
        # 模拟 dataclasses.replace 的语义,供 load_dspark_model 内部调用
        values = vars(config).copy()
        values.update(changes)
        return SimpleNamespace(**values)
​
    # with 块内其余 patch(get_model、dflash_has_any_non_causal、
    # get_draft_quant_config 等)与后续断言逻辑在此省略,本片段聚焦修复点
    with (
        patch.object(dspark_utils, "replace", side_effect=fake_replace),
        # ...
    ):
        ...

评论区精华

copilot 对测试夹具修复的自动核验 other

copilot-pull-request-reviewer[bot](Lite 模式)核验后给出 Approval recommended:改动是 minimal、正确的测试夹具更新,新增的 parallel_config 与 draft_parallel_config.tensor_parallel_size 正好对应 load_dspark_model 实际访问的字段,除修复失败的单元测试外无行为风险。

结论:建议批准,无待解决问题;三位维护者均直接批准。 · 已解决

风险与影响

风险极低,理由如下:

  • 改动范围严格限定在测试文件 tests/model_executor/test_qwen3_omni.py 的 mock 构造内,不触碰任何运行时、推理或性能路径。
  • 新增字段与 load_dspark_model 的两处读取点(vllm_config.parallel_config 与 speculative_config.draft_parallel_config.tensor_parallel_size)一一对应,copilot 核验亦确认无遗漏。
  • 潜在脆弱性:该测试用手工 SimpleNamespace 模拟整个 vllm_config,字段与实现强耦合;后续并行配置读取方式一旦再变,不同步更新 fixture 就会复发同类 AttributeError。历史 PR#56600 在同一文件再次修复 DSpark 测试配置(H200 MIG CI 失败),印证了这一耦合的脆弱性。

影响面被严格限定在 CI/测试域:

  • 用户与运行时:零影响,不改变任何推理行为、API 或性能。
  • 团队:恢复 Qwen3 Omni DSpark 单测及关联 PP 测试(共 8+32=40 个用例)的稳定性,消除主分支 CI 红灯。
  • 维护成本:改动本身极小,但暴露了 DSpark 测试夹具与实现之间的耦合关系,speculative decoding 功能线的后续改动需同步关注该 mock。
测试 mock 与实现强耦合 纯测试改动回归风险极低

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论