Prhub

#46076 [Attention][DSA] support dcp for FLASHINFER_MLA_SPARSE

原始 PR 作者 ZJY0516 合并时间 2026-07-01 12:32 文件变更 12 提交数 27 评论 61 代码增减 +1996 / -98

执行摘要

为 GLM5.2 添加 DCP 稀疏注意力索引器支持

PR 标题明确表示为 FLASHINFER_MLA_SPARSE 后端添加 DCP 支持。PR body 提供了详细的测试命令和结果,展示 DCP 4 相比 TP 4 在生成吞吐量上的提升(4071.3 vs 2508.7 token/s),同时指出预填充速度仅为 TP 的一半,端到端吞吐不理想。没有关联 Issue,但实现直接针对 GLM-5.2 模型;从评论看,这是为了支持更大的上下文并行解码。

值得精读,尤其是理解以下设计决策:

  1. CuTeDSL 与 Triton 如何协作实现高效的跨 rank top-K 合并;
  2. LSE 基数归一化在 DCP reduce 中的关键作用;
  3. Triton 内核中利用原子加法实现有效槽位压缩的性能考量。
    建议在合并后持续关注预填充 all_gather 优化和 interleave > 1 支持。
讨论亮点

CuTeDSL 替换 C++ 内核

  • LucasWilkinson:建议使用 CuTeDSL 替换 C++ 候选 top-K 内核,以便在不重新编译的情况下优化,并可针对 num_candidates 做特种优化。
  • ZJY0516:采纳并合并了 CuTeDSL 实现。

预填充 all_gather 性能担忧

  • LucasWilkinson:询问是否可以使用两阶段 top-K(每个 rank 先做局部 top-K,再全局合并)以避免 prefill 中的 all_gather。
  • GirasoleY:当前保留 all_gather 路径,后续可优化。

LSE 基数归一化

  • ChatGPT-Codex [P1]:FlashInfer MLA 返回的 LSE 是 base-2,但 MLA DCP reducer 期望 base-e,需要转换。
  • GirasoleY:添加 _normalize_lse 函数,将 FlashInfer 的 base-2 LSE 转换为 base-e。

紧凑有效槽位融合到转换内核

  • LucasWilkinson:建议将 _compact_valid_to_front 的 argsort+gather 逻辑融合到 _convert_req_index_to_global_index_kernel 中,通过原子计数器分配连续槽位。
  • GirasoleY:采纳并实现(commit 0333fb4)。

DCP 解码序列长度本地化顺序修复

  • ChatGPT-Codex [P1]:当 DCP 启用时,先本地化请求级长度再展开 per-token 会导致解码局部长度计算错误(例如世界大小 2,rank 1,全局最后三个 token 的局部长度应为 [4,4,5] 而非 [3,4,5])。
  • GirasoleY:修复为先展开全局长度再本地化每个 token(commit e884ebb)。

实现拆解

1. 实现 CuTeDSL DCP 候选打包和稳定 top-K 选择

  • 新增 vllm/model_executor/kernels/attention/dsa/dcp_indexer_cutedsl.py,包含 pack_dcp_topk_candidates_cutedsl(Triton 内核将局部候选 score 和 global_id 打包为 (score, global_id) 对)和 stable_topk_from_gathered_candidates_cutedsl(CuTeDSL 内核从所有收集的候选中选择全局 top-K,输出为 topk_indices)。

2. 在稀疏注意力索引器中添加 DCP 合并入口

  • vllm/model_executor/layers/sparse_attn_indexer.py 中新增 _merge_dcp_topk_global 函数,对每个 prefill/decode 步骤调用,从分布式张量通信组 all_gather 打包的候选,然后应用 CuTeDSL 选择器。同时添加 _assert_cutedsl_dcp_merge_supported 进行前置条件检查(要求 CuTeDSL 安装、index_topk 在 512/1024/2048 中)。

3. 适配注意力元数据构建器以支持 DCP 序列长度本地化

  • vllm/v1/attention/backends/mla/indexer.py 中,新增 _dcp_localize_decode_seq_lens_prepare_global_decode_seq_lens 等函数,将全局序列长度转换为每个 rank 的局部长度;修改 DeepseekV32IndexerPrefillChunkMetadata 增加 local_cu_seq_lens 等字段;构建预填充块元数据时考虑 DCP。

4. 增强 Triton 索引转换内核支持 DCP 反交错和有效槽位压缩

  • vllm/v1/attention/backends/mla/sparse_utils.py 中,改造 _convert_req_index_to_global_index_kernel,添加 DCP_SIZE/DCP_RANK/DCP_INTERLEAVE 参数实现全局 token ID 到局部物理槽位的反交错映射,并新增 COMPACT_TO_FRONT 分支,利用原子加法将有效槽位连续排列在前端(避免 -1 间隙),减少后续内核扫描范围。

5. 配置 FlashInfer 稀疏 MLA 后端以启用 DCP

  • vllm/v1/attention/backends/mla/flashinfer_mla_sparse.py 中,添加 cp_kv_cache_interleave_size 元数据传递;添加 can_return_lse_for_decode = Truelse_base_on_e = False 标记;实现 _normalize_lse 将 base-2 LSE 转换为 base-e,供 DCP reducer 正确合并。

6. 配套测试

  • 新增 tests/v1/attention/test_indexer_dcp_localize.py(951 行),使用 _FakeDCPGroup 模拟多 rank 环境,覆盖 DCP 候选打包、序列长度本地化、端到端注意力一致性等。
文件 模块 状态 重要度
vllm/model_executor/kernels/attention/dsa/dcp_indexer_cutedsl.py 内核 added 9.36
vllm/model_executor/layers/sparse_attn_indexer.py 索引器 modified 8.54
vllm/v1/attention/backends/mla/indexer.py 注意力元数据 modified 8.2
vllm/v1/attention/backends/mla/sparse_utils.py 稀疏工具 modified 7.84
vllm/v1/attention/backends/mla/flashinfer_mla_sparse.py 注意力后端 modified 7.72
tests/v1/attention/test_indexer_dcp_localize.py 测试 added 8.05
vllm/v1/attention/backends/utils.py 后端工具 modified 6.03

关键符号

stable_topk_from_gathered_candidates_cutedsl pack_dcp_topk_candidates_cutedsl _merge_dcp_topk_global _dcp_localize_decode_seq_lens triton_filter_and_convert_dcp_index _normalize_lse _assert_cutedsl_dcp_merge_supported

关键源码片段

vllm/model_executor/layers/sparse_attn_indexer.py data-contract

新增 _merge_dcp_topk_global 函数,作为 DCP 合并的入口点,集成新内核。

# vllm/model_executor/layers/sparse_attn_indexer.py (modified)def _assert_cutedsl_dcp_merge_supported(
    logits: torch.Tensor,
    topk_indices: torch.Tensor,
    k: int,
) -> None:
    # 验证 DCP 合并的前置条件:仅 CuTeDSL 路径,要求 FP32 logits、int32 indices
    # 以及 index_topk 必须为 512/1024/2048(CuTeDSL radix 选择器的表大小限制)
    if not has_cutedsl():
        raise RuntimeError(
            "DCP sparse-indexer merge requires CuteDSL; install it or disable DCP.")
    if logits.device.type != "cuda":
        raise RuntimeError("DCP sparse-indexer merge requires CUDA tensors.")
    if logits.dtype != torch.float32 or topk_indices.dtype != torch.int32:
        raise RuntimeError(
            "DCP sparse-indexer merge requires fp32 logits and int32 indices.")
    if k not in (512, 1024, 2048):
        raise RuntimeError(
            f"DCP sparse-indexer merge requires index_topk in (512, 1024, 2048); "
            f"got {k}.")
​
​
def _merge_dcp_topk_global(
    logits: torch.Tensor,
    topk_indices: torch.Tensor,
    topk_tokens: int,
    dcp_rank: int,
    dcp_world_size: int,
    cp_interleave: int,
    row_starts: torch.Tensor | None = None,
) -> None:
    """
    将每个 DCP rank 的局部 top-K 合并为全局 top-K。    topk_indices 是本 rank 局部 KV 分片中的 top-K 位置。
    通过 all_gather 收集所有 rank 的 (score, global_id) 候选,
    然后使用 CuTeDSL 稳定选择器确定全局 top-K。
    最终将 topk_indices 覆写为全局 token ID(-1 表示填充)。
    """
    if dcp_world_size <= 1:
        return
​
    _assert_cutedsl_dcp_merge_supported(logits, topk_indices, topk_tokens)
    from vllm.model_executor.kernels.attention.dsa.dcp_indexer_cutedsl import (
        pack_dcp_topk_candidates_cutedsl,
        stable_topk_from_gathered_candidates_cutedsl,
    )
​
    # 分配 packed 缓冲区:[batch, topk, 2] 分别存放 score 和 global_id
    packed = torch.empty(
        (*topk_indices.shape, 2),
        dtype=torch.float32,
        device=topk_indices.device,
    )
    pack_dcp_topk_candidates_cutedsl(
        logits, topk_indices, packed,
        dcp_rank, dcp_world_size, cp_interleave, row_starts,
    )
    # all_gather 沿 dim=1 拼接候选(每个 rank 贡献 topk 个)
    gathered = get_dcp_group().all_gather(packed, dim=1)
    # CuTeDSL 稳定选择器返回全局 top-K token ID
    stable_topk_from_gathered_candidates_cutedsl(
        gathered, topk_tokens, out=topk_indices)
vllm/v1/attention/backends/mla/indexer.py dependency-wiring

修改预填充和解码元数据构建以支持 DCP 序列长度本地化,是中间层的关键适配。

# vllm/v1/attention/backends/mla/indexer.py (modified)def _dcp_localize_decode_seq_lens(
    seq_lens: torch.Tensor,
    dcp_rank: int,
    dcp_world_size: int,
    interleave: int,
) -> torch.Tensor:
    # 将全局序列长度转换为每个 rank 的局部长度
    # 基于 interleave 分块所有权:每个 rank 拥有 (pos // interleave) % dcp_world_size == rank 的 token
    # 返回 32-bit int tensor
    return torch.tensor(
        [_local_count(int(s), dcp_rank, dcp_world_size, interleave) for s in seq_lens],
        dtype=torch.int32,
        device=seq_lens.device)
​
​
# DeepseekV32IndexerPrefillChunkMetadata 新增字段
@dataclass
class DeepseekV32IndexerPrefillChunkMetadata:
    block_table: torch.Tensor
    cu_seqlen_ks: torch.Tensor # DCP 启用时存储局部行边界
    cu_seqlen_ke: torch.Tensor
    cu_seq_lens: torch.Tensor
    token_to_seq: torch.Tensor
    total_seq_lens: int
    token_start: int
    token_end: int
    num_reqs: int
    skip_kv_gather: bool = False
    # 新增 DCP 字段
    local_cu_seq_lens: torch.Tensor | None = None
    local_total_seq_lens: int = 0
    max_local_total_seq_lens: int = 0

评论区精华

使用 CuTeDSL 替换 C++ 内核 性能

LucasWilkinson 建议使用 CuTeDSL 替换 C++ 候选 top-K 内核,以便在不重新编译的情况下优化,并可针对 num_candidates 做特种优化;ZJY0516 采纳并合并了相关 PR。

结论:接受,合并了 CuTeDSL 实现。 · 已解决

预填充 all_gather 性能担忧 性能

LucasWilkinson 询问是否可以使用两阶段 top-K(每个 rank 先做局部 top-K,再全局合并)以避免 prefill 中的 all_gather;GirasoleY 表示当前保留 all_gather 路径,后续可优化。

结论:暂不改变,后续优化。 · 待处理

LSE 基数归一化 正确性

ChatGPT-Codex 指出 FlashInfer MLA 返回的 LSE 是 base-2,但 MLA DCP reducer 期望 base-e,需要转换否则 batch 内多 rank 局部 KV 行会计算出错误权重。

结论:添加 _normalize_lse 函数,将 FlashInfer 的 base-2 LSE 转换为 base-e。 · 已解决

紧凑有效槽位融合到转换内核 性能

LucasWilkinson 建议将 _compact_valid_to_front 的 argsort+gather 逻辑融合到 _convert_req_index_to_global_index_kernel 中,通过原子计数器分配连续槽位,避免额外排序开销。

结论:采纳并实现 (commit 0333fb4)。 · 已解决

DCP 解码序列长度本地化顺序修复 正确性

ChatGPT-Codex 指出当 DCP 启用时,先本地化请求级长度再展开 per-token 会导致解码局部长度计算错误(例如 world=2,rank=1,全局最后三个 token 的局部长度应为 [4,4,5] 而非 [3,4,5])。

结论:修复为先展开全局长度再本地化每个 token (commit e884ebb)。 · 已解决

风险与影响

  • 仅 CuTeDSL 路径(无降级)_merge_dcp_topk_global 在 DCP world size > 1 时只支持 CuTeDSL 路径,若未安装 CuTeDSL 会直接报错,没有 PyTorch 回退。
  • index_topk 严格限制:要求 k 必须为 512、1024 或 2048,否则运行时报错,限制了其他 top-K 配置的使用。
  • interleave > 1 未实现:当 cp_kv_cache_interleave_size > 1 时,索引元数据构建会抛出 NotImplementedError,因此目前只能使用 interleave=1。
  • 预填充 all_gather 性能:预填充阶段通过 all_gather 收集所有 rank 的候选,通信量随世界大小和 top-K 线性增长,可能成为长上下文场景的瓶颈。
  • CUDA 专用测试:测试文件 test_indexer_dcp_localize.py 中的多数测试依赖 CUDA,在 CPU-only 环境下被跳过,降低了非 GPU 场景的交付信心。
  • 用户:启用 DCP 后,长上下文 MLA 解码吞吐可提升约 1.6 倍(GB200 上 DCP 4 达 4071 token/s),但预填充速度减半,端到端吞吐可能不理想。
  • 系统:新增对 CuTeDSL 的强依赖,需要预装对应库;仅支持 NVIDIA GPU。
  • 团队:引入新的内核实现模式(CuTeDSL + Triton 混合),为后续其他后端的 DCP 支持奠基。
仅 CuTeDSL 无降级路径 index_topk 严格限制 interleave > 1 未实现 预填充 all_gather 性能瓶颈 CUDA 专用测试跳过

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论