Prhub

#50484 [Kimi-K3] DCP support

原始 PR 作者 GirasoleY 合并时间 2026-08-11 03:54 文件变更 19 提交数 9 评论 15 代码增减 +3529 / -82

执行摘要

Kimi-K3 支持 DCP 解码,直接对称内存 A2A 大幅提速

PR body 明确指出这是 Follow-up to #50000 adding decode context parallelism for Kimi-K3,目标是让 Kimi-K3 的融合 MLA 层支持 DCP,并通过直接对称内存通信替代通用的 NCCL A2A / AG-RS 路径。给出的性能对比显示 DCP8 在 c1 并发下 TPOT 由 13.806ms 降到 10.544ms、HBM 占用由 7.17% 峰值降到 1.10%,单卡 KV 容量提升约 10 倍,同时 GSM8K 准确率从 96.21% 提升到 96.97%,是推动合并的核心动机。Issue 评论中也有外部用户询问合并计划与 DSpark 投机模型的兼容性,说明该能力有明确的外部需求。

值得精读。建议重点关注三处设计:dcp_utils.py_DirectDCPWorkspace 的持久对称缓冲区 + epoch 奇偶槽位复用机制(CUDA-graph 安全的关键);dcp_direct_a2a_lse_reduce.cu 中空 shard 源端掩码与消费端零权重跳过的一致性设计;mla_attention.pyalign_mla_chunked_context_workspace_size 如何用 lcm 对齐同时满足 DCP 分块与 graph padding。对计划在自有模型上接入 DCP 的工程师,这是目前最完整的参考实现。

讨论亮点

Review 中有三个值得关注的讨论点:

  • mask_dcp_empty_shards_ 与自定义 CUDA 内核的关系:zyongye 询问 "I assume this will never be called if we use custom cuda kernel?"。其结论是:该函数服务于 NCCL / AG-RS 回退路径的防御性掩码,直接对称内存路径由 CUDA 内核在源端完成 LSE -inf 掩码,二者不会同时生效。

  • 开关配置形式之争:zyongye 建议为 VLLM_USE_DIRECT_DCP_A2A 提供默认值或做成 engine args;pavanimajety 指出已有 --dcp-comm-backend cuda-a2a。GirasoleY 回应:"the kernel version has some limitation. The intent here is... None = auto, 1 = force use, 0 = disable. Which I feel it's actually cleaner this way... but happy to change." 最终保留三态环境变量,auto 在 multicast 可用时默认开启。

  • 合并决策:WoosukKwon 最终批准并留言 "LGTM! Thanks for the offline discussion!",关键设计权衡主要在线下完成。

实现拆解

实现按 5 个步骤拆解:

  1. 移植直接对称内存 DCP 基础设施:新增 vllm/v1/attention/ops/dcp_utils.py(约 740 行),引入 MLADCPManager 统一管理 query gather、KV gather 与 A2A combine;_symm_mem_spans_group 通过 torch.distributed._symmetric_memory 探测 NVLS multicast 能力;_direct_dcp_enabled / _direct_dcp_multicast_enabled 实现门控;vllm/envs.py 新增 VLLM_USE_DIRECT_DCP_A2A / VLLM_USE_DIRECT_DCP_Q_GATHER / VLLM_USE_DIRECT_DCP_KV_GATHER 三态开关(auto / force / disable),默认 auto。

  2. Kimi-K3 融合 MLA 层接入 DCPvllm/models/kimi_k3/nvidia/mla.py 放开原来 decode_context_parallel_size <= 1 的断言,仅禁止 prefill CP 与 RoPE+DCP 组合;dcp_world_size > 1 时构建 MLADCPManager,decode 路径在 forward_mqa 前调用 query_gather、之后调用 combine,prefill 的 chunked-context 改走 _context_parallel_compute_prefill_context 并配合 KV gather。

  3. 通用 MLA 层路径统一vllm/model_executor/layers/attention/mla_attention.py 删除 cp_lse_ag_out_ar / cp_lse_ag_out_rs / dcp_a2a_lse_reduce 三处分散分支,统一收敛到 dcp_manager.combine;新增 align_mla_chunked_context_workspace_size,按 lcm(block_size, dcp_size * cp_kv_cache_interleave_size) 对齐 chunked-prefill workspace,保证 DCP 分块边界与 CUDA graph padding 兼容;build_mla_chunked_context_metadatasparse_mla_attention.py 同步透传 dcp_manager

  4. 新增 CUDA 内核csrc/libtorch_stable/attention/dcp_utils/ 下新增 dcp_direct_common.cuhdcp_direct_a2a_lse_reduce.cudcp_direct_q_gather.cudcp_direct_kv_gather.cu。A2A 内核将每 rank 的 head 切片与 LSE 直接写入 peer 的对称缓冲区,用 epoch 奇偶槽位配合信号量做跨 rank 同步;空 KV shard 在源端就把 LSE 掩码为 -inf,消费端加权时跳过零权重来源;query gather 与 KV gather 使用 NVLS multicast 直连,行几何不满足 16 字节对齐时回退 NCCL。CMakeLists.txt 挂载新编译单元。

  5. 测试配套:新增 tests/distributed/test_dcp_direct_a2a_lse_reduce.py(1077 行,覆盖开关门控、multicast 探测、q/kv gather 与参考实现逐位对比)、tests/distributed/test_kimi_linear_context_parallel.py(TP2 基线 vs DCP2 端到端,logprob 漂移 <= 1e-2)、tests/v1/attention/test_flashinfer_mla_dcp.pytests/distributed/test_dcp_a2a.py 补充空 shard NaN/-inf 掩码与 CUDA graph padding LSE 用例。

文件 模块 状态 重要度
vllm/v1/attention/ops/dcp_utils.py DCP 工具 added 7.75
vllm/models/kimi_k3/nvidia/mla.py K3 注意力 modified 7.56
vllm/model_executor/layers/attention/mla_attention.py MLA 层 modified 7.68
csrc/libtorch_stable/attention/dcp_utils/dcp_direct_a2a_lse_reduce.cu DCP 内核 added 6.31
tests/distributed/test_dcp_direct_a2a_lse_reduce.py DCP 测试 added 7.48
tests/distributed/test_kimi_linear_context_parallel.py K3 测试 added 7.02
tests/distributed/test_dcp_a2a.py A2A 测试 modified 6.86
vllm/v1/attention/ops/common.py CP 工具 modified 5.33
vllm/envs.py 环境变量 modified 5.03
vllm/model_executor/layers/attention/sparse_mla_attention.py 稀疏 MLA modified 6.19

关键符号

MLADCPManager DirectDCPA2AWorkspace.lse_reduce DirectDCPQGatherWorkspace DirectDCPKVGatherWorkspace get_direct_dcp_a2a_workspace get_direct_dcp_q_gather_workspace get_direct_dcp_kv_gather_workspace reserve_query_head_storage align_mla_chunked_context_workspace_size mask_dcp_empty_shards_ build_mla_chunked_context_metadata MultiHeadLatentAttention._attention MultiHeadLatentAttention.__init__ wait_lse_combine_kernel dispatch_output_lse_kernel signal_kernel

关键源码片段

vllm/v1/attention/ops/dcp_utils.py infrastructure

新增的直接 DCP 基础设施核心:MLADCPManager 统一管理 query gather、KV gather、A2A combine,以及三态开关门控与 NVLS multicast 探测,是整条 DCP 路径的调度中枢。

class _DirectDCPWorkspace:
    # 持久对称内存工作区:一次 rendezvous 建立跨 rank 视图,
    # 后续每个 ubatch 复用独立槽位,避免热路径重复分配。
    def __init__(self, group, device, num_ubatches):
        self.group = group
        self.world_size = group.size()
        self.rank = group.rank()
        self.device = torch.device(device)
        self.num_ubatches = num_ubatches
        # 每个 ubatch 一个 epoch 计数器,配合信号量实现发布 - 等待同步
        self.epoch = torch.zeros(num_ubatches, dtype=torch.int64, device=self.device)
        self._allocations = []
​
    def _allocate(self, shape, dtype):
        # 对称内存 + rendezvous,拿到本 rank 存储与各 peer 的远端视图
        storage = symm_mem.empty(shape, device=self.device, dtype=dtype)
        storage.zero_()
        torch.accelerator.synchronize()
        handle = symm_mem.rendezvous(storage, self.group.group_name)
        assert handle is not None, 'DCP symmetric memory rendezvous returned None'
        handle.barrier()
        views = [
            handle.get_buffer(peer, list(shape), dtype, 0)
            for peer in range(self.world_size)
        ]
        # 预计算 peer_ptrs:把「每 peer 每 ubatch 的 data_ptr」提前固化,
        # 内核侧只需查表即可写入远端缓冲区,无需在热路径反复查询
        peer_ptrs = torch.tensor(
            [[view[ubatch].data_ptr() for view in views]
             for ubatch in range(self.num_ubatches)],
            dtype=torch.int64,
            device=self.device,
        )
        self._allocations.append((storage, handle, views))
        return storage, peer_ptrs
vllm/models/kimi_k3/nvidia/mla.py core-logic

Kimi-K3 融合 MLA 层的 DCP 主接线:放开 decode CP 限制、构建 dcp_manager、在 decode 与 chunked-prefill 两处接入 gather 与 combine,是本 PR 功能落地的模型侧入口。

# ---- Decode: latent multi-query attention(DCP 接线)----
if num_mqa_tokens > 0:
    mqa_q_nope, mqa_q_pe = q[:num_mqa_tokens].split(
        [self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1
    )
    # BMM1: absorb q_nope into latent space. (N,B,P) x (N,P,L) -> (B,N,L)
    ql_nope = torch.bmm(mqa_q_nope.transpose(0, 1), self.W_UK_T).transpose(0, 1)
    # Fused: concat mqa_q = [ql_nope | q_pe] 并写入分页缓存(单次 launch)
    mqa_q = self._decode_concat_cache(
        ql_nope, mqa_q_pe, kv_c_normed[:num_mqa_tokens], k_pe[:num_mqa_tokens],
        rope_positions[:num_mqa_tokens] if rope_positions is not None else None,
        cos_sin_cache, slot_mapping[:num_mqa_tokens],
    )
    # DCP 开启时,先把本 rank 的 query 沿 head 维 gather 成全局 query
    if self.dcp_world_size > 1:
        assert self.dcp_manager is not None
        assert self.dcp_manager.query_gather is not None
        mqa_q = self.dcp_manager.query_gather(mqa_q)
    latent_out, lse = self.impl.forward_mqa(
        mqa_q, self._attn_read_kv_cache(), attn_metadata, self
    )
    # forward_mqa 返回后,用 LSE 做跨 rank 加权 combine,再执行 W_UV 上投影
    if self.dcp_world_size > 1:
        assert lse is not None
        assert self.dcp_manager is not None
        assert attn_metadata.decode is not None
        latent_out = self.dcp_manager.combine(
            latent_out,
            lse,
            seq_lens=attn_metadata.decode.seq_lens,
            query_start_loc=attn_metadata.query_start_loc[: attn_metadata.num_decodes + 1],
        )
    self._v_up_proj(latent_out, out=attn_out[:num_mqa_tokens])
vllm/model_executor/layers/attention/mla_attention.py core-logic

通用 MLA 层重构:将三套 DCP 输出融合逻辑统一到 MLADCPManager.combine,并新增 workspace 对齐函数,影响所有 MLA 模型,是风险面最大的源码改动。

def align_mla_chunked_context_workspace_size(
    vllm_config: VllmConfig,
    workspace_size: int,
) -> int:
    # chunked-prefill 的 workspace 需要同时满足两个对齐约束:
    # 1) KV cache block 边界(cache_config.block_size)
    # 2) DCP 分块边界:dcp_size * cp_kv_cache_interleave_size,
    # 取最小公倍数 lcm 可同时整除两者,避免跨 rank 分块错位
    parallel_config = vllm_config.parallel_config
    alignment = vllm_config.cache_config.block_size
    if parallel_config.decode_context_parallel_size > 1:
        alignment = lcm(
            alignment,
            parallel_config.decode_context_parallel_size
            * parallel_config.cp_kv_cache_interleave_size,
        )
    # 至少容纳 max_num_seqs 个请求所需的完整对齐行数
    workspace_size = max(
        workspace_size,
        vllm_config.scheduler_config.max_num_seqs * alignment,
    )
    return round_up(workspace_size, alignment)

评论区精华

mask_dcp_empty_shards_ 与自定义 CUDA 内核的职责边界 设计

zyongye 在 vllm/v1/attention/ops/common.py 上询问:如果走自定义 CUDA 内核,这个掩码函数是否永远不会被调用。

结论:该函数服务于 NCCL / AG-RS 回退路径,直接对称内存路径由 CUDA 内核在源端完成 LSE 的 -inf 掩码,二者按路径互斥。 · 已解决

直接 DCP 开关的配置形式之争 设计

zyongye 建议为 VLLM_USE_DIRECT_DCP_A2A 提供默认值或改造成 engine args;pavanimajety 指出已有 --dcp-comm-backend cuda-a2a 参数;GirasoleY 回应内核版本有局限,三态 env(auto/force/disable)语义更清晰:None 自动、1 强制、0 禁用。

结论:保留三态环境变量方案,auto 在 NVLS multicast 可用时默认开启;未改为 engine arg。 · 已解决

合并计划与 DSpark 兼容性询问 question

外部用户 prakhar-prakash-juspay 询问该 PR 是否计划合并,以及是否兼容 dspark 投机模型。

结论:PR 最终由 WoosukKwon 批准合并(LGTM! Thanks for the offline discussion!);DSpark 兼容性未在 review 中明确答复。 · 已解决

风险与影响

主要风险集中在以下四点:

  1. 通用 MLA 层核心路径变更mla_attention.py 是所有 MLA 模型(DeepSeek、Kimi 等)共享的路径,dcp_a2a_lse_reduce / cp_lse_ag_out_* 三分支统一到 dcp_manager.combine 后,DCP 关闭时仍走 AG-RS 回退,若 MLADCPManager 初始化条件与后端能力不一致,可能影响非 DCP 场景。

  2. 新 CUDA 内核的硬件依赖dcp_direct_*.cu 依赖 NVLS symmetric memory 与 multicast,仅 NVIDIA 且同节点环境可用;fp32 等不支持的 dtype 依赖门控回退,get_direct_dcp_a2a_workspace 在 force 模式下对不支持 dtype 会直接抛 ValueError,门控误判会导致启动或运行期失败。

  3. CUDA graph 交互:query 缓冲区复用依赖 post-attention DCP combine 建立的跨 rank 依赖;commit 76b2e9d45e 修复了空 shard LSE 掩码对 graph padding 行的容忍,但 PR body 自述全文件 GB300 探索性运行仍有 7 个 return-LSE dtype 断言失败(fp32 vs fp16/bf16),这些用例不执行新路径,风险敞口仍需关注。

  4. 空 shard 与陈旧数据:直接 A2A 内核中空 KV shard 的载荷可能是陈旧数据,源码通过源端 -inf 掩码 + 消费端零权重跳过双重保护;epoch 超时直接 trap 而非静默降级,行为正确但缺乏优雅恢复路径。

对用户:Kimi-K3 用户在 NVLS 环境下开启 DCP 后可获得约 24% 的 TPOT 下降(c1 13.8ms -> 10.5ms)和约 10 倍的每卡 KV 容量提升(1.9M -> 19.7M tokens),GSM8K 准确率持平略升;非 NVLS 环境自动回退 NCCL,功能不变。对系统:chunked-prefill workspace 对齐逻辑与 MLADCPManager 成为通用 MLA 基础设施,后续模型接入 DCP 的成本显著降低,sparse_mla_attention.py 与 FlashInfer MLA 后端同步适配了 gathered head 存储预分配。对团队:新增 3 个环境变量、4 个 CUDA 编译单元和 5 个测试文件,CI 需覆盖 DCP2/DCP8 多档配置。

核心注意力路径重构 新增 CUDA 内核 仅 NVIDIA NVLS 可用 CUDA graph 交互风险 环境变量默认 auto 门控 多后端回退依赖门控正确性

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论