Prhub

#50242 K3 DSpark AR fusion

原始 PR 作者 jeejeelee 合并时间 2026-07-31 19:12 文件变更 5 提交数 5 评论 2 代码增减 +24 / -8

执行摘要

K3 DSpark 草稿模型启用 all-reduce 与 RMSNorm 融合

K3 DSpark 是 Kimi K3 的投机解码草稿模型,其 decoder layer 原本在每个注意力输出和 MLP 输出处分别做 all-reduce 后再做 RMSNorm,产生多次通信与 kernel 启动。deepseek_v32 已证明将 all-reduce 与后续 RMSNorm 融合可以降低开销(eager / breakable-cudagraph 路径下 torch.compile 的融合不生效,需要手动恢复)。本 PR 将该成熟模式复用到 K3 DSpark 草稿模型,并顺带将算子提升为公共组件。PR body 提供了 GSM8K 前后对比:main 分支 exact_match 0.9629(flexible-extract)/ 0.9621(strict-match),本 PR 为 0.9674 / 0.9666,精度未回退且略有提升。

值得精读 dspark_mla.py 的 forward 改造,它是‘延迟 all-reduce、融合进下一个 Norm’这一高性能模式的简洁示例;同时可关注 vllm/models/common/ops 目录的演进。建议在后续 PR 中补充该融合路径的数值对齐测试,减少对人工评估的依赖。

讨论亮点

Claude Code Review 确认未发现缺陷,认为该改动仅是内部张量并行通信与数值抖动处理,不涉及用户输入、认证或序列化面,无安全风险;唯一遗留问题是 mergify 标记的 merge conflict 需要人工 rebase。Reviewer Isotr0py 直接 approve。未发现针对性能收益量化的讨论,也没有对融合边界正确性(如首层 residual 为 None 分支)的质疑。

实现拆解

  1. 算子公共化:将 vllm/models/deepseek_v32/common/fused_ops.py 重命名为 vllm/models/common/ops/fused_allreduce_rms_norm.py,更新 docstring 说明其适用于 eager / breakable-cudagraph 路径的融合恢复,并在 vllm/models/common/ops/init.py 中导出 fused_allreduce_rms_norm。
  2. 更新既有引用:deepseek_v32 的 NVIDIA 与 AMD 模型实现(vllm/models/deepseek_v32/nvidia/model.py、vllm/models/deepseek_v32/amd/model.py)的 import 路径从 vllm.models.deepseek_v32.common.fused_ops 改为 vllm.models.common.ops,行为不变。
  3. K3 DSpark 草稿模型接入融合:在 vllm/models/kimi_k3/nvidia/dspark_mla.py 中,设置 self.self_attn.o_proj.reduce_results=False 与 self.mlp(..., reduce_results=False),使行并行输出保持未 reduce 状态;decoder layer 的 forward 中把 input_layernorm 与 post_attention_layernorm 调用替换为 fused_allreduce_rms_norm,将上一层的 all-reduce、残差加和与当前层 RMSNorm 合并;最后一层由 final_norm 通过 fused_allreduce_rms_norm 完成收尾 reduce。此模式与 kimi_k3/nvidia/model.py 和 deepseek_v32 中已存在的做法一致,首层 residual 为 None 时保持原有直接 RMSNorm 路径。
  4. 测试与配套:本 PR 未新增测试文件,GSM8K 评估结果作为精度回归依据写在 PR body 中。
文件 模块 状态 重要度
vllm/models/kimi_k3/nvidia/dspark_mla.py 模型实现 modified 6.92
vllm/models/deepseek_v32/nvidia/model.py 模型实现 modified 5.1
vllm/models/deepseek_v32/amd/model.py 模型实现 modified 5.1
vllm/models/common/ops/fused_allreduce_rms_norm.py 公共算子 renamed 4.9
vllm/models/common/ops/__init__.py 公共算子 modified 3.56

关键符号

K3DSparkDecoderLayer.forward K3DSparkModel.forward fused_allreduce_rms_norm

关键源码片段

vllm/models/kimi_k3/nvidia/dspark_mla.py core-logic

核心改动文件:K3 DSpark 草稿模型 decoder layer 改写为延迟 all-reduce、与 RMSNorm 融合的执行模式,是性能收益的直接来源。

# vllm/models/kimi_k3/nvidia/dspark_mla.py
# 关键改动:本层行并行输出保持未 reduce 状态,
# 由下一层的 fused_allreduce_rms_norm 一次性完成 all-reduce、残差加和与 RMSNorm。class K3DSparkDecoderLayer(nn.Module):
    def __init__(self, *, vllm_config, config, layer_idx, start_layer_id, prefix):
        super().__init__()
        quant_config = get_draft_quant_config(vllm_config)
        self.self_attn = MultiHeadLatentAttention(
            config=config,
            hidden_size=config.hidden_size,
            num_heads=config.num_attention_heads,
            qk_nope_head_dim=config.qk_nope_head_dim,
            qk_rope_head_dim=config.qk_rope_head_dim,
            v_head_dim=config.v_head_dim,
            q_lora_rank=config.q_lora_rank,
            kv_lora_rank=config.kv_lora_rank,
            cache_config=vllm_config.cache_config,
            quant_config=quant_config,
            prefix=maybe_prefix(prefix, f"layers.{start_layer_id + layer_idx}.self_attn"),
            use_rope=True,
            non_causal_multi_token_decode=True,
        )
        # 注意力输出的 all-reduce 推迟到下一个 RMSNorm 中融合执行
        self.self_attn.o_proj.reduce_results = False
        self.mlp = KimiMLP(
            hidden_size=config.hidden_size,
            intermediate_size=config.intermediate_size,
            hidden_act=config.hidden_act,
            quant_config=quant_config,
            reduce_results=False, # MLP 输出同样不立即 reduce
            prefix=maybe_prefix(prefix, f"layers.{start_layer_id + layer_idx}.mlp"),
        )
        self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.post_attention_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
​
    def forward(self, positions, hidden_states, residual):
        if residual is None:
            # 首层:hidden_states 是已 reduce 的 embedding,直接做 RMSNorm
            residual = hidden_states
            hidden_states = self.input_layernorm(hidden_states)
        else:
            # 融合上一层的 all-reduce、残差加和与当前输入的 RMSNorm
            hidden_states, residual = fused_allreduce_rms_norm(
                hidden_states, residual, self.input_layernorm
            )
​
        hidden_states = self.self_attn(positions=positions, hidden_states=hidden_states)
        # 注意力输出在进入后置 Norm 时统一 reduce
        hidden_states, residual = fused_allreduce_rms_norm(
            hidden_states, residual, self.post_attention_layernorm
        )
        # MLP 输出由下一层 input_layernorm(或模型 final_norm)负责 reduce
        hidden_states = self.mlp(hidden_states)
        return hidden_states, residual# K3DSparkModel.forward 中最后一层同样使用融合收尾:
# hidden_states, _ = fused_allreduce_rms_norm(hidden_states, residual, self.final_norm)
vllm/models/common/ops/fused_allreduce_rms_norm.py rename-or-move

算子从 deepseek_v32 私有目录搬迁为公共组件,是本次复用的基础,docstring 更新明确了适用场景。

# vllm/models/common/ops/fused_allreduce_rms_norm.py
# 公共融合算子:将张量并行 all-reduce、残差加和与 RMSNorm 合并为一次 kernel,
# 供 deepseek_v32、kimi_k3 等 eager 模型路径复用。"""Fused all-reduce + residual-add + RMSNorm for eager model paths.This recovers a fusion that vLLM's torch.compile passes would normally do but
that doesn't fire for models running eager (or under a breakable CUDA graph).
"""# 内部实现保持与 deepseek_v32 原有逻辑一致:
# 1. flashinfer 快速路径可用时直接调用融合 kernel;
# 2. 否则退化为显式 tensor_model_parallel_all_reduce 后再执行 RMSNorm。

评论区精华

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

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

风险与影响

主要风险集中在张量并行下的数值与通信正确性:dspark_mla.py 中 o_proj 与 mlp 的 reduce_results=False 后,所有 all-reduce 被推迟到下一个 RMSNorm 的 fused kernel,若存在绕过 fused_allreduce_rms_norm 的路径(如某种未覆盖的分支或未来新增层),会出现未 reduce 的中间张量流向下游。首层 residual is None 分支未走融合,需要确保首层输入是已 reduce 的 embedding。此外,算子搬迁涉及 deepseek_v32 两条后端路径(NVIDIA/AMD)的 import 更新,若存在其他直接引用旧路径的代码会造成导入错误;PR 未附带任何自动化测试,精度仅靠一次 GSM8K 评估背书。

影响范围限于 speculative decoding 场景下 K3 DSpark 草稿模型的推理路径:每层减少一次独立 all-reduce kernel 启动,对长序列或高并发解码有潜在延迟收益;算子公共化后,vllm/models/common/ops 成为模型间共享融合算子的新家,deepseek_v32 与 kimi_k3 之外的新模型可直接复用。对普通非投机解码用户无感知。团队后续可基于该公共算子进一步推广到其他 eager 模型。

缺少测试覆盖 核心路径变更 跨模块依赖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论