Prhub

#48407 [Attention] Skip sparse indexer scoring for dense short prefills

原始 PR 作者 qianlihuang 合并时间 2026-07-29 00:17 文件变更 7 提交数 9 评论 14 代码增减 +230 / -12

执行摘要

跳过短预填充时稀疏评分,提升 2.5% 吞吐

当预填充通过密集 MHA 处理时(即短序列,prefill_max_seq_len <= topk_tokens),top-k 索引不会被消费,因此索引器评分是多余计算。PR 旨在消除这部分死工作,提升短预填充性能。该优化对早期多轮对话和 PD 部署中的 prefill DP rank 尤其有益。

值得精读。该 PR 展示了一种优雅的跨组件短路由优化方式:通过 metadata 传递决策,在 indexer 中实现早期退出,避免 kernel 启动开销。同时提供了详尽的基准和测试,可视为 vllm 中稀疏 attention 优化的参考实现。

讨论亮点
  • MatthewBonanni 起初因类似 PR #49486 曾出现 decode 回归而撤回 auto-merge,并要求提供 decode 端基准。作者补充了 ISL=1、OSL=1024 的多组运行数据,确认 median TPOT 无回归后获批准。
  • 作者提到无合适 GPU 进行更多验证,但社区协助完成基准。
  • 一个 nit 修复:在 sparse_mla_attention.py 中移除多余条件(num_prefill_tokens > 0,已在分支内恒成立)。

实现拆解

  1. MLA metadata 增强:在 vllm/model_executor/layers/attention/mla_attention.pyMLACommonPrefillMetadata 中添加 use_dense_mha: bool 字段,指示当前预填充是否走密集 MHA 路径。
  2. 路由决策下沉到 prefill 构建:在 vllm/model_executor/layers/attention/sparse_mla_attention.pybuild() 中,计算 use_dense_mha 并传递到 metadata,取代之前在 forward_impl() 中基于 prefill_max_seq_len 的重复判断。
  3. 索引器早期退出机制:在 vllm/model_executor/layers/sparse_attn_indexer.pysparse_attn_indexer() 中新增 dense_mha_metadata_layer_name 参数。在非 FULL cudagraph 模式下,检查对应 MLA 层的 metadata:若 use_dense_mha=True、无 decode token 且不在 cuda graph capture 中,则跳过评分并直接返回 topk_indices_buffer
  4. MLA wrapper 配置传播:在 vllm/model_executor/layers/mla.pyMultiHeadLatentAttentionWrapper.__init__() 中新增 allow_short_prefill_indexer_scoring_skip 参数,满足条件时(非 skip_topk、非 PCP、CUDA 平台)绑定 indexer op 的 dense_mha_metadata_layer_name 到 MLA 注意力层名称,使索引器能访问 MLA metadata。
  5. 模型适配:在 vllm/models/deepseek_v32/nvidia/attention.py 中为融合路径传递 dense_mha_metadata_layer_name=""(该路径始终假)。在 vllm/model_executor/models/deepseek_v2.py 中传递 allow_short_prefill_indexer_scoring_skip=True 启用优化。
  6. 测试覆盖:新增 tests/model_executor/layers/test_mla_short_prefill_indexer.py,包含 6 种参数化场景(短、阈值不匹配、强制 MQA、MLA decode、CUDA capture、FULL cudagraph),验证短密集 MHA prefills 确实调用早期退出,而其他场景仍执行评分。
文件 模块 状态 重要度
tests/model_executor/layers/test_mla_short_prefill_indexer.py 测试用例 added 8.23
vllm/model_executor/layers/sparse_attn_indexer.py 索引器 modified 6.92
vllm/model_executor/layers/attention/mla_attention.py MLA 注意力 modified 6.3

关键符号

sparse_attn_indexer MultiHeadLatentAttentionWrapper.__init__ MultiHeadLatentAttentionWrapper.forward SparseAttentionLayer.build forward_impl sparse_attn_indexer_fake SparseAttnIndexerOp.__init__

关键源码片段

vllm/model_executor/layers/sparse_attn_indexer.py core-logic

核心变更文件,添加早期退出逻辑和 `dense_mha_metadata_layer_name` 参数。

# vllm/model_executor/layers/sparse_attn_indexer.py# ... 前置代码省略 ...
# 注意:新增参数 dense_mha_metadata_layer_name
@eager_break_during_capture
def sparse_attn_indexer(
    ...
    dense_mha_metadata_layer_name: LayerNameType, # 新增:对应当前 batch 使用的 MLA 层名
    ...
) -> torch.Tensor:
    forward_context = get_forward_context()
    attn_metadata = forward_context.attn_metadata
​
    # 在 profiling/dummy run 时,attn_metadata 不是 dict
    if not isinstance(attn_metadata, dict):
        # 预留 workspace 后返回 fake 结果
        ...
        return sparse_attn_indexer_fake(...)
​
    # 正常推理路径
    # ... 解析 metadata ...
​
    # 核心早期退出逻辑:当且仅当预填充走密集 MHA、无 decode tokens、
    # 且不在 CUDAGraph capture 中时,跳过评分直接返回 topk_indices_buffer。
    # 注意:只有非 FULL 模式才会检查,因为 FULL 模式下本函数不会被调用。
    if forward_context.cudagraph_runtime_mode != CUDAGraphMode.FULL:
        dense_mha_layer = _resolve_layer_name(dense_mha_metadata_layer_name)
        if dense_mha_layer:
            mla_metadata = attn_metadata.get(dense_mha_layer)
            prefill_metadata = getattr(mla_metadata, "prefill", None)
            if (getattr(prefill_metadata, "use_dense_mha", False)
                    and getattr(mla_metadata, "num_decode_tokens", -1) == 0
                    and not torch.cuda.is_current_stream_capturing()):
                # 缓冲区在之前已经填充过,无需再执行评分
                return topk_indices_buffer
​
    # 继续执行原有的评分逻辑(如果未达到早期退出条件)
    # ...
vllm/model_executor/layers/attention/mla_attention.py refactor

修改 `forward_impl()` 使用 metadata 的 `use_dense_mha` 标志替代重复计算,并在 `MLACommonPrefillMetadata` 中新增 `use_dense_mha` 字段。

# vllm/model_executor/layers/attention/mla_attention.py ( 关键片段 )
​
    def forward_impl(self, ...):
        # ...
        if self.impl.is_sparse and num_mha_tokens > 0:
            # 原逻辑:基于 prefill_max_seq_len 和 config 重新判断
            # 新逻辑:直接使用 metadata 中已缓存的路由决策
            prefill_metadata = getattr(attn_metadata, "prefill", None)
            if not getattr(prefill_metadata, "use_dense_mha", False):
                # 如果不是 dense MHA,则所有 token 走 MQA 路径
                num_mqa_tokens = q.size(0)
                num_mha_tokens = 0
        # ...# 在 MLACommonPrefillMetadata 数据类中新增字段(约第 1407 行):
@dataclass
class MLACommonPrefillMetadata:
    # ... 原有字段 ...
    # Whether the prefill suffix is routed through dense MHA.
    # Indexer scoring may be skipped only for a pure-prefill batch,
    # since decode tokens still consume top-k indices.
    use_dense_mha: bool = False

评论区精华

解码回归检查 性能

MatthewBonanni 由于类似 PR #49486 曾出现 decode 回归,暂停 auto-merge 并要求作者提供解码端基准。作者随后添加了 ISL=1、OSL=1024 的多个 concurrency 基准,结论是 median TPOT 无变化。

结论:通过基准确认无解码回归,MatthewBonanni 批准合并。 · 已解决

CI 成本和失败处理 other

MatthewBonanni 提醒作者减少分支更新频率以控制 CI 成本;对于 CI 失败,要求判断是否为主干已有故障,否则修复。

结论:作者确认唯一失败由 #50060 已修复引发,CI 检查实际通过。 · 已解决

代码清理(去除多余条件) style

MatthewBonanni 指出在 `sparse_mla_attention.py` 中 `use_dense_mha` 计算时 `num_prefill_tokens > 0` 条件多余(已在 `num_prefills > 0` 分支内)。

结论:作者移除该条件。 · 已解决

风险与影响

  • 解码回归:核心关切,但通过多组解码基准(concurrency 1/8/32)验证无回归。
  • CUDAGraph 兼容性:早期退出逻辑仅在 CUDAGraphMode.PIECEWISE 下生效,FULL 模式仍然执行原流程,不会破坏 capture。
  • PCP 排除:PCP 路径因索引器缓存/评分所有权跨 rank 不同,未应用此优化,需确保已有逻辑不依赖 dense_mha_metadata_layer_name
  • 测试覆盖:新增测试覆盖了短/阈值不匹配/强制 MQA 等边界,但未覆盖长预填充或混合 batch 场景,可能存在隐式行为差异。
  • 配置依赖:新参数 allow_short_prefill_indexer_scoring_skip 默认未启用,需显式在模型注册中开启(已在 deepseek_v2.py 开启)。
  • 用户影响:短预填充场景(如多轮对话早期、PD 部署)获得约 2.5% 吞吐提升和 TTFT 降低;解码端及其他场景无变化。
  • 系统影响:无侵入性,所有新增参数默认为空或 False,保持向后兼容。
  • 团队影响:引入了跨组件 metadata 通信模式(indexer 读取 MLA metadata),需在后续开发中维护此契约。
核心路径变更 解码回归校验 PCP 排除 CUDAGraph 模式影响

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论