# PR #54005 完整报告

- 仓库：`vllm-project/vllm`
- 标题：[Bugfix][Model] Fix K3 DSpark config for 96-head drafts
- 合并时间：2026-08-28 06:58
- 原文链接：http://prhub.com.cn/vllm-project/vllm/pull/54005

---

# 执行摘要

- 一句话：修复 K3 DSpark 96 头配置解析失败
- 推荐动作：值得精读，特别是对于处理 MLA 配置继承和校验逻辑的开发者。本 PR 演示了如何通过选择正确的基类来解决配置校验问题。

# 功能与动机

Issue #53998 报告：加载 Kimi-K3 MLA DSpark 草稿 checkpoint 时，由于 K3DSparkConfig 继承 DeepseekV2Config，其 validate_architecture 中的 MHA invariant（hidden_size % num_attention_heads == 0）错误地拒绝了 Block5 checkpoint（7168 / 96）。MLA 的头维度是独立声明的，该不变量不适用。本 PR 通过切换基类为 DeepseekV3Config 解决此问题。

# 实现拆解

1. **修改基类**：在 `vllm/transformers_utils/configs/k3_dspark.py` 中，将 `K3DSparkConfig` 的继承关系从 `DeepseekV2Config` 改为 `DeepseekV3Config`。这改变了配置校验逻辑，DeepseekV3Config 更符合 MLA 的几何结构，允许非整除的 hidden_size 与注意力头数。同时更新了注释中的 MoE 拓扑描述。
2. **更新测试 fixture**：在 `tests/transformers_utils/test_dspark_mla_config.py` 中，将默认的 `num_attention_heads` 和 `num_key_value_heads` 从 64 改为 96（对应 Block5）。
3. **参数化测试**：将 `test_dspark_mla_uses_latent_kv_geometry` 参数化，覆盖 64 头和 96 头两种配置，并验证 TP8 下的本地头数（8 和 12）。确保两种公开 checkpoint 均能正确加载。

关键文件：
- `vllm/transformers_utils/configs/k3_dspark.py`（模块 配置解析；类别 source；类型 core-logic；符号 K3DSparkConfig）: 核心修复文件，修改 K3DSparkConfig 的基类，解决配置校验问题。
- `tests/transformers_utils/test_dspark_mla_config.py`（模块 配置测试；类别 test；类型 test-coverage；符号 test_dspark_mla_uses_latent_kv_geometry）: 测试文件更新，覆盖 64 头和 96 头配置，验证修复。

关键符号：K3DSparkConfig.__init__, test_dspark_mla_uses_latent_kv_geometry

## 关键源码片段

### `vllm/transformers_utils/configs/k3_dspark.py`

核心修复文件，修改 K3DSparkConfig 的基类，解决配置校验问题。

```python
# vllm/transformers_utils/configs/k3_dspark.py
from transformers import DeepseekV3Config  # 改为 DeepseekV3Config，它适用于 MLA 几何

class K3DSparkConfig(DeepseekV3Config):
    """Configuration for a dense MLA DSpark draft model."""

    model_type = "k3_dspark"
    has_no_defaults_at_init = True

    def __init__(
        self,
        mla_use_nope: bool = False,
        mla_use_output_gate: bool = False,
        mla_use_qk_norm: bool = False,
        rope_theta: float = 50000.0,
        **kwargs,
    ) -> None:
        # DeepseekV3Config 同样默认 MoE 拓扑，这里清零相关字段以标识为 dense
        kwargs.setdefault("n_routed_experts", 0)
        kwargs.setdefault("n_shared_experts", 0)
        kwargs.setdefault("num_experts_per_tok", 0)

        # 处理 rope 参数，优先使用传入的 rope_parameters，否则设置默认值
        rope_parameters = kwargs.get("rope_parameters")
        if rope_parameters is None:
            kwargs["rope_parameters"] = {
                "rope_type": "default",
                "rope_theta": rope_theta,
            }
        else:
            rope_parameters = dict(rope_parameters)
            rope_parameters.setdefault("rope_type", "default")
            rope_parameters.setdefault("rope_theta", rope_theta)
            kwargs["rope_parameters"] = rope_parameters

        super().__init__(**kwargs)
        self.mla_use_nope = mla_use_nope
        self.mla_use_output_gate = mla_use_output_gate
        self.mla_use_qk_norm = mla_use_qk_norm

        # 检查是否有不支持的选项，如果设置则抛出错误
        unsupported = [
            name
            for name in (
                "mla_use_nope",
                "mla_use_output_gate",
                "mla_use_qk_norm",
                "dspark_bonus_anchor",
            )
            if getattr(self, name, False)
        ]

```

### `tests/transformers_utils/test_dspark_mla_config.py`

测试文件更新，覆盖 64 头和 96 头配置，验证修复。

```python
# tests/transformers_utils/test_dspark_mla_config.py
# 测试函数参数化，覆盖两种头部数量布局
@pytest.mark.parametrize(
    ("num_attention_heads", "expected_local_heads"),
    [(64, 8), (96, 12)],  # 64 头对应 released 版本，96 头对应 Block5
    ids=["released-64-head", "block5-96-head"],
)
def test_dspark_mla_uses_latent_kv_geometry(
    tmp_path, num_attention_heads, expected_local_heads
):
    draft_path = tmp_path / "draft"
    _write_dspark_config(
        draft_path,
        num_attention_heads=num_attention_heads,
        num_key_value_heads=num_attention_heads,
    )
    model_config = ModelConfig(
        model=str(draft_path),
        tokenizer_mode="skip",
        runner="draft",
        max_model_len=32768,
    )

    assert model_config.is_deepseek_mla
    assert model_config.use_mla
    assert model_config.get_head_size() == 576
    # 使用 external_launcher 跳过本地 GPU 数检查，便于在单 GPU 上测试 TP8
    parallel_config = ParallelConfig(
        tensor_parallel_size=8, distributed_executor_backend="external_launcher"
    )
    assert model_config.get_num_kv_heads(parallel_config) == 1
    assert model_config.get_num_attention_heads(parallel_config) == expected_local_heads
    assert model_config.get_num_experts() == 0

```

# 评论区精华

未发现实质性讨论。Claude bot 指出此 PR 来自 fork，自动审查被禁用，建议维护者手动请求审查。WoosukKwon 已批准。

- 暂无高价值评论线程

# 风险与影响

- 风险：基类切换可能影响其他依赖 DeepseekV2Config 默认值的逻辑，但 K3DSparkConfig 已显式覆盖 MoE 相关字段并设置 `has_no_defaults_at_init = True`，风险较低。测试覆盖了两种头部布局，降低了回归风险。
- 影响：影响范围限于 K3 DSpark 草稿模型的配置加载。此修复使公开的 96 头 checkpoint 可用，提升了对 Kimi-K3 DSpark 模型的支持。对现有 64 头用户的兼容性已通过测试验证。
- 风险标记：配置继承变更 , 回归风险低

# 关联脉络

- PR #53797 Add support for loading dflash2 model in speculators format: 相关功能：dflash 模型加载支持，本 PR 修复的是 k3_dspark 配置，属于同一 DSpark 草稿模型功能线。