Prhub

#35947 Publish gated DSV4 DFLASH-family target-prefill read completion

原始 PR 作者 weireweire 合并时间 2026-08-27 12:33 文件变更 8 提交数 3 评论 5 代码增减 +249 / -51

执行摘要

DSV4 目标预填充提前发布共享读完成事件以提升重叠调度

DSV4 sparse prefill 此前在第一个 attention 层才懒构建 request-to-token 与 Full-to-SWA 快照,PR body 指出“The overlap scheduler therefore could not safely release or mutate those shared inputs at the metadata boundary and had to retain them through a coarser forward boundary.” 由于 DFLASH/DSPARK 没有单独的 prefill draft-extend 读取器,对非 CP 目标 prefill,在发布 PRE_REPLAY 边界前物化快照可以避免与 sparse-prefill 读取器竞争,同时让后续调度工作与目标模型计算重叠。本 PR 是 #34816 的 target-prefill 对应物,从 #34515 的集成性能工作中提取,最终为移除所有阻塞后的 40% E2E 吞吐提升做出贡献。

值得精读。该 PR 是投机解码调度重叠的重要一环,展示了如何通过“提前快照共享读 + 声明 PRE_REPLAY 边界”来安全地放宽 WAR 屏障,对理解 DSV4 的 sparse prefill 数据流和 CUDA graph 元数据生命周期有直接帮助。建议结合 #34816 和 #34515 一起阅读,把握完整的调度重叠演进路径。但需关注合并后 CI 回归的修复情况,确认 token_to_kv_pool 问题已解决后再合入主线。

讨论亮点

审查中无 inline review 评论,但 Issue 评论中有三条关键讨论:

  • nvpohanh 报告 CI 回归:本 PR 合并后导致 test_q8kv8_sparse_prefill_backend.py 中四个测试失败,报错 AttributeError: 'DeepseekV4AttnBackend' object has no attribute 'token_to_kv_pool',并留言“This broke CI 😢 ... I am fixing it.”。这提示该 PR 或 main 合并带入了对 DeepseekV4AttnBackend 接口的破坏,需要后续修复。
  • YAMY1234 提供隔离验证:在 Qwen3.5 AgentX 工作负载上,Total TPS/GPU 为 116382.57 vs 117919.87 baseline(-1.304%),P90 TPS/User 为 108.631 vs 108.131(+0.462%),均在 3% 阈值内;但明确说明 Qwen3.5 使用 trtllm_mha,本 PR 修改的 DSV4 DFLASH 路径未被激活,因此该验证只确认隔离 cherry-pick 无回归,不验证 DSV4 主动路径性能。
  • hnyls2002 触发 rerun:请求 rerun test_deepseek_v4.pytest_prefill_shared_read_done.pytest_prefill_cuda_graph_padding.py,相应 CI 在 4-gpu-b200 与 1-gpu-h100 上全部通过。

实现拆解

按以下 4 步拆解实现:

  1. 后端扩展(deepseek_v4_backend.py):在 DSV4Metadata 中新增 prefill_shared_reads_snapshotted 布尔标志,并在 copy_ / refresh_for_breakable_cuda_graph_replay_ 中重置,避免 replay 残留污染;新增 prepare_prefill_shared_read_snapshot 方法,在满足环境变量开关、EXTEND 模式、spec_algorithm.is_dflash_family() 且非 CP-v2 时,若属 sparse prefill(num_qo_tokens > _LARGE_INDEXER_QUERY_THRESHOLD 或显式开启 SGLANG_OPT_FLASHMLA_SPARSE_PREFILL)则调用新增的 _build_sparse_prefill_chunk_cache 构建 chunk cache,并将 prefill_shared_reads_snapshotted 置为 Trueshared_read_endsEXTEND 且已快照时返回 SharedReadEnds.PRE_REPLAY
  2. Runner 接入(eager_runner.py、prefill_cuda_graph_runner.py):在 init_forward_metadata 之后立即调用 prepare_prefill_shared_read_snapshot,并随后调用 maybe_publish_prefill_shared_read_done;prefill CUDA graph runner 使用 padding 后的 token 数作为 num_qo_tokens,确保与 graph replay 的几何尺寸一致。
  3. 事件门控(shared_read_event.py、base_attn_backend.py)maybe_publish_prefill_shared_read_done 的投机算法判断从“仅无投机”放宽为“无投机或 DFLASH 家族”,其余投机算法(如 EAGLE/MTP)因存在后续 draft-extend 读取器而保持保守粗屏障;base_attn_backend.py 新增默认 no-op 的 prepare_prefill_shared_read_snapshot 钩子,保证未实现该语义的后端不会误声明 PRE_REPLAY
  4. 测试配套:在 test_deepseek_v4.py 新增 test_prefill_snapshot_declares_pre_replay_boundarytest_snapshot_builds_cache_only_for_sparse_prefilltest_sparse_prefill_snapshot_marks_success_only_after_build 三个单元测试,覆盖快照声明、dense/sparse 分支、构建成功后才标记;在 test_prefill_cuda_graph_padding.py 新增 test_replay_snapshot_uses_padded_token_count,验证 replay 使用 padding 后的 token 数调用快照;在 test_prefill_shared_read_done.py 新增 test_dflash_family_target_prefill_publishes,验证 DFLASH 家族事件发布路径。
文件 模块 状态 重要度
python/sglang/srt/layers/attention/deepseek_v4_backend.py 注意力后端 modified 7.96
python/sglang/srt/model_executor/runner/eager_runner.py 运行器 modified 6.18
python/sglang/srt/model_executor/runner_utils/shared_read_event.py 事件发布 modified 6.01
python/sglang/srt/layers/attention/base_attn_backend.py 注意基类 modified 5.97
python/sglang/srt/model_executor/runner/prefill_cuda_graph_runner.py 图谱运行器 modified 5.03
test/registered/attention/unittests/dsv4/test_deepseek_v4.py DSV4 测试 modified 7.01
test/registered/unit/model_executor/runner/test_prefill_cuda_graph_padding.py 图谱填充测试 modified 5.9
test/registered/unit/model_executor/runner/test_prefill_shared_read_done.py 共享读测试 modified 5.71

关键符号

prepare_prefill_shared_read_snapshot _build_sparse_prefill_chunk_cache maybe_publish_prefill_shared_read_done shared_read_ends test_prefill_snapshot_declares_pre_replay_boundary test_snapshot_builds_cache_only_for_sparse_prefill test_sparse_prefill_snapshot_marks_success_only_after_build test_replay_snapshot_uses_padded_token_count test_dflash_family_target_prefill_publishes

关键源码片段

python/sglang/srt/layers/attention/deepseek_v4_backend.py core-logic

核心实现文件:新增 prepare_prefill_shared_read_snapshot 与 _build_sparse_prefill_chunk_cache,扩展 DSV4Metadata 状态并修改 shared_read_ends 以声明 PRE_REPLAY 边界,是本次优化最关键的行为变更点。

def prepare_prefill_shared_read_snapshot(
    self, forward_batch: ForwardBatch, *, num_qo_tokens: int
) -> None:
    # 先重置快照标记,避免上次 replay 残留影响本次判定。
    metadata = self.forward_metadata
    if isinstance(metadata, DSV4Metadata):
        metadata.prefill_shared_reads_snapshotted = False
​
    # 仅当启用 WAR 优化、处于 EXTEND 模式、DFLASH 家族且非 CP-v2 时才快照。
    snapshot_shared_prefill_reads = (
        envs.SGLANG_ENABLE_PREFILL_WAR_READ_DONE.get()
        and forward_batch.forward_mode == ForwardMode.EXTEND
        and self.model_runner.spec_algorithm.is_dflash_family()
        and not is_cp_v2_active(forward_batch)
    )
    if not snapshot_shared_prefill_reads:
        return
​
    assert isinstance(metadata, DSV4Metadata)
    # 稀疏 prefill 需要额外构建 chunk cache,否则只声明已快照。
    use_sparse_prefill = not _is_sm120 and (
        num_qo_tokens > _LARGE_INDEXER_QUERY_THRESHOLD
        or envs.SGLANG_OPT_FLASHMLA_SPARSE_PREFILL.get()
    )
    if use_sparse_prefill:
        metadata.sparse_prefill_cache = self._build_sparse_prefill_chunk_cache(
            forward_batch, num_qo_tokens=num_qo_tokens
        )
    # 无论 dense 还是 sparse,都标记共享读已完成快照,从而允许
    # shared_read_ends 向调度器声明 PRE_REPLAY 边界。
    metadata.prefill_shared_reads_snapshotted = True
python/sglang/srt/model_executor/runner_utils/shared_read_event.py data-contract

事件发布门控逻辑的核心文件:将投机算法条件放宽到 DFLASH 家族,保留 EAGLE/MTP 的粗屏障,决定 PRE_REPLAY 事件是否最终发布。

def maybe_publish_prefill_shared_read_done(
    model_runner, forward_batch, device_module
) -> None:
    """在合规的元数据初始化后发布 prefill 读完成事件。"""
    if not envs.SGLANG_ENABLE_PREFILL_WAR_READ_DONE.get():
        return
    if forward_batch.forward_mode != ForwardMode.EXTEND:
        return
    # 除 DFLASH 家族外,其他投机算法可能还有后续 draft-extend 读取器,
    # 保持保守屏障,避免提前释放共享输入导致数据竞争。
    if (
        not model_runner.spec_algorithm.is_none()
        and not model_runner.spec_algorithm.is_dflash_family()
    ):
        return
    # 记录点位于 replay 准备之后,因此只支持 PRE_REPLAY 边界。
    declared = model_runner.attn_backend.shared_read_ends(
        forward_batch.forward_mode
    )
    if declared is not SharedReadEnds.PRE_REPLAY:
        return
    logger.info_once(
        "Prefill shared-read-done fastpath active (%s)",
        type(model_runner.attn_backend).__name__,
    )
    read_done = device_module.Event()
    read_done.record()
    model_runner.shared_read_done_event = read_done

评论区精华

合并后 CI 回归:DeepseekV4AttnBackend 缺失 token_to_kv_pool 正确性

nvpohanh 在 Issue 中报告:Four tests in test_q8kv8_sparse_prefill_backend.py fail with: AttributeError: 'DeepseekV4AttnBackend' object has no attribute 'token_to_kv_pool'。并表示正在修复。

结论:本 PR 或 main 合并引入了对 DeepseekV4AttnBackend 接口的破坏,需要额外修复;截至 PR 合并时仍未解决。 · unresolved

Qwen3.5 AgentX 无回归验证 性能

YAMY1234 提供验证结果:Total TPS/GPU 116382.57 vs 117919.87 baseline(-1.304%),P90 TPS/User 108.631 vs 108.131(+0.462%),均在 3% 阈值内;但说明 Qwen3.5 使用 trtllm_mha,DSV4 DFLASH 路径未激活,仅验证隔离 cherry-pick 兼容性。

结论:未观察到可测量的性能回归,但未验证 DSV4 主动路径性能。 · 已解决

测试 rerun 请求与结果 测试

hnyls2002 请求 rerun test_deepseek_v4.py、test_prefill_shared_read_done.py、test_prefill_cuda_graph_padding.py,结果显示 1-gpu-h100、4-gpu-b200 等全部通过。

结论:相关针对性测试通过,未发现本 PR 引入的测试失败。 · 已解决

风险与影响

主要风险如下:

  • 合并后 CI 回归:nvpohanh 明确指出 test_q8kv8_sparse_prefill_backend.py 四个测试失败,DeepseekV4AttnBackend 缺失 token_to_kv_pool 属性。该回归可能源于本 PR 与 main 合并时的接口变化,也可能暴露了 DeepseekV4AttnBackend 初始化路径的隐式依赖,需要优先修复,否则影响主线稳定性。
  • 行为开关默认关闭:快速路径由 SGLANG_ENABLE_PREFILL_WAR_READ_DONE 环境变量门控,默认不生效,因此默认部署风险可控;但一旦开启,所有依赖 PRE_REPLAY 边界安全性的路径都必须严格满足快照语义,否则可能出现共享输入被提前释放导致的数据竞争。
  • 投机解码边界放宽shared_read_event.py 将放行条件从“仅无投机”扩展至“DFLASH 家族”,若未来 DFLASH/DSPARK 引入新的按顺序读取器,可能破坏 WAR 保证;当前靠 is_dflash_family() 保守限定,但长期需补充更细粒度的边界声明。
  • 快照重建开销_build_sparse_prefill_chunk_cache 在元数据初始化阶段提前构建,会将该开销转移到 prefill 早期,若构建失败(如显存不足或异常),prefill_shared_reads_snapshotted 不会被置位,测试也覆盖了“成功后才标记”的语义,但实际失败恢复路径仍需观察。
  • CUDA graph replay 一致性:prefill graph runner 使用 padding 后的 token 数调用快照,测试已验证 num_qo_tokens=16 的 padding 场景,但其他 padding 倍数及多 bucket 场景的覆盖仍有限。

影响范围集中在 DSV4 模型 + DFLASH/DSPARK 投机解码 + 非 CP-v2 的 target prefill 路径,且默认关闭,因此对现有默认部署无行为变化。对启用该优化的用户,提前发布共享读完成事件可提升 prefill 与调度器的重叠度,为整体 40% E2E 吞吐改进贡献一部分;但对 Qwen3.5 等使用 trtllm_mha 的工作负载无影响(YAMY1234 验证)。对团队而言,新增的“快照钩子 + 事件发布”模式将成为后续其他后端实现同类优化的模板,同时需要维护 base_attn_backend.py 新增的 no-op 钩子和 shared_read_event.py 的投机算法门控,避免后续后端误用。

合并后 CI 回归 默认关闭(opt-in) 核心路径变更 投机算法门控放宽 需验证 DSV4 主动路径性能

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论