Prhub

#29202 [AMD] Enable draft-extend CUDA graph and reduce bubble for MTP

原始 PR 作者 RolaoDenthu 合并时间 2026-08-13 15:50 文件变更 3 提交数 12 评论 17 代码增减 +38 / -11

执行摘要

DSV4 MTP 在 AMD 启用 draft-extend CUDA graph,TTT 提升可达 11.7%

PR body 列出三个独立问题:一是 HIP draft-extend CUDA-graph 门控不认识 DSV4 backend,导致可被 graph 捕获的 draft_extend 阶段退回 eager;二是 --profile 场景下对 _attach_unified_kv_prefill_meta 改动后,服务在垃圾回收期间可靠崩溃,阻塞了 MTP 路径的 profiling 验证;三是 repeat_interleave 在 unified-kv prefill 元数据构建中引发隐式 device→host 同步,拖慢整个 draft_extend 元数据构建。作者在评论中还补充:最初尝试在 target-extend 路径也消除该同步,但触发 GPU memory access fault 且未能定位是哪个 kernel 导致,因此作为 workaround 仅对 draft-extend 生效。

值得精读,尤其适合关注 ROCm/HIP 后端、投机解码(EAGLE/MTP)与 CUDA graph 捕获的工程师。三个值得借鉴的点:一是用 repeat_interleave(..., output_size=...) 消除隐式 D2H 同步的微优化技巧,以及面对未知 kernel 故障时“分路径降级、保留注释留档”的务实决策;二是 torch.profiler 引用循环与 gc.collect() 受控回收的处理模式,是 Python + pybind11 混合编程的典型坑;三是“按硬件后端局部 import、惰性加载”隔离依赖的写法。但需注意当前验证完全依赖 nightly 人工测试,合并前应保持对 target-extend 故障的敏感度。

讨论亮点

围绕 CI 覆盖缺口与 target-extend 故障的取舍是讨论核心:

  • amd-bot 多次强调 PR CI 无法验证本 PR 改动,因为 DSV4 MTP 相关测试全部注册为 nightly:"This PR's changed code is not exercised by any PR-CI test"。HaiShaw 据此要求作者补跑 nightly 套件:"can you fill the coverage gap by running extra tests?"。
  • 1am9trash 在 approve 时纠正了 amd-bot 的具体判断错误:"the amd-bot CI summary is mistaken here: the stage-c-test-large-8-gpu-amd suite it points to does not include the V4 MTP test. That test lives in nightly, so the queued stage-c jobs would not related to this PR anyway."。
  • RolaoDenthu 解释为何优化只落到 draft-extend:"The earlier D2H optimization caused a GPU memory access fault on the target-extend path with the current codebase, and I couldn't detect which kernel triggered it. As a workaround, the optimization is now applied to draft-extend only.",该取舍被接受并在源码注释中留档。
  • 1am9trash 提醒 lint 失败,作者以 fix lints 提交收尾。

实现拆解

变更入口是 eagle_worker_v2.py::_capture_cuda_graphs 的 HIP 门控扩展,整体按以下 4 步推进:

  1. 扩展 HIP draft-extend 图捕获门控python/sglang/srt/speculative/eagle_worker_v2.py):将 supports_hip_aiter_draft_extend_graph 重命名为 supports_hip_draft_extend_graph,在原有 AiterMultiStepDraftBackend 判断基础上,用 or isinstance(self.draft_extend_attn_backend, DeepseekV4HipRadixBackend) 增加 DSV4 HIP radix 后端支持;DeepseekV4HipRadixBackend 采用局部 import,避免非 HIP 环境拉入依赖。这样 draft_extend 阶段在 AMD 上可被 CUDA graph 捕获,不再退回 eager。

  2. 消除 repeat_interleave 的隐式 D2H 同步python/sglang/srt/layers/attention/deepseek_v4_backend_hip_radix.py):_attach_unified_kv_prefill_meta 新增 num_tokens: intneed_compress: bool = True 参数,调用方 init_forward_metadata_prefill 传入。当 need_compress=True 时保留原 repeat_interleave 用法;当 need_compress=False(即 draft-extend 路径)时传入 output_size=num_tokens,跳过读取 extend_seq_lens.sum() 回 host 的隐式同步。之所以分路径,是因为 target-extend 上同样改动会触发 GPU memory access fault。

  3. 修复 profiler 停止时的 GC 崩溃python/sglang/srt/managers/scheduler_components/profiler_manager.py):在 _stop_profile 末尾把原先无条件的 self.torch_profiler = None 改为先判空再置 None 并立即 gc.collect(),避免 torch.profiler 的 pybind11 对象引用循环被延迟的 cyclic GC 回收时发生 C++ 析构 double-free。该修复只在 --profile 启动时执行一次,不引入持续开销。

  4. 验证与配套:未新增自动化测试文件;作者手动运行 test/registered/amd/test_deepseek_v4_pro_fp4_mtp.py(nightly 套件)两轮,结果分别为 Accuracy 0.955(avg_spec_accept_length 2.914、speed 151.51 token/s)与 Accuracy 0.943(output_throughput 1353.042 token/s、acc_length 2.96、speed 124.83 token/s),gsm8k 准确率 0.95;另有一次 fix lints 提交修复 CI 的 lint 失败。

文件 模块 状态 重要度
python/sglang/srt/layers/attention/deepseek_v4_backend_hip_radix.py 注意力后端 modified 6.52
python/sglang/srt/speculative/eagle_worker_v2.py 投机解码 modified 6.04
python/sglang/srt/managers/scheduler_components/profiler_manager.py 性能剖析 modified 5.78

关键符号

_capture_cuda_graphs _attach_unified_kv_prefill_meta init_forward_metadata_prefill _stop_profile

关键源码片段

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

核心性能改动:为 unified-KV prefill 元数据构建消除隐式 D2H 同步,并按 need_compress 分路径传递 output_size;同时因 target-extend 触发 GPU memory access fault 而保留双分支,是本次设计权衡的关键点。

# python/sglang/srt/layers/attention/deepseek_v4_backend_hip_radix.pydef _attach_unified_kv_prefill_meta(
    self,
    core: DSV4AttnMetadata,
    req_pool_indices: torch.Tensor,
    seq_lens: torch.Tensor,
    extend_seq_lens: torch.Tensor,
    num_tokens: int,
    need_compress: bool = True,
) -> None:
    from sglang.kernels.ops.attention.dsv4.unified_kv_kernels.env_gate import (
        is_unified_kv_triton,
    )
​
    if not is_unified_kv_triton():
        return
    device = req_pool_indices.device
    bs = req_pool_indices.shape[0]
    seq_lens = seq_lens.to(torch.int64)
    extend_seq_lens = extend_seq_lens.to(torch.int64)
​
    # token -> req index(长度 L = sum(extend_seq_lens))。
    # 传 output_size 可跳过隐式的 sum() 返回 host 的同步,避免 draft-extend
    # 元数据构建被 D2H 卡住。但 target-extend 路径上同样的优化会触发 GPU
    # 内存访问错误,因此只对 draft-extend(need_compress=False)生效。
    if need_compress:
        bid = torch.repeat_interleave(
            torch.arange(bs, device=device, dtype=torch.int64),
            extend_seq_lens,
        )
    else:
        bid = torch.repeat_interleave(
            torch.arange(bs, device=device, dtype=torch.int64),
            extend_seq_lens,
            output_size=num_tokens,
        )
    if core.unified is None:
        core.unified = UnifiedKvMetadata()
    core.unified.pf_state_slot = req_pool_indices[bid]
    core.unified.pf_chunk_start = (seq_lens - extend_seq_lens)[bid]
    cu_q_per_req = torch.cumsum(extend_seq_lens, dim=0) - extend_seq_lens
    core.unified.pf_cu_q = cu_q_per_req[bid]
    core.unified.pf_final_pos = (seq_lens - 1)[bid]
python/sglang/srt/speculative/eagle_worker_v2.py dependency-wiring

draft-extend CUDA graph 门控的扩展入口:将 DeepseekV4HipRadixBackend 纳入 HIP 支持类型,使 DSV4 MTP 的 draft-extend 阶段从 eager 转为图捕获,这是性能收益的直接来源。

# python/sglang/srt/speculative/eagle_worker_v2.py(_capture_cuda_graphs 内)supports_hip_draft_extend_graph = False
if _is_hip:
    # 保持局部 import,避免非 HIP 环境引入 aiter / DSV4 HIP 后端依赖。
    # aiter 把 draft-extend 支持打包在 decode(multi-step)backend 中;
    # DSV4 则在 draft-extend backend 自身暴露该能力。
    from sglang.srt.layers.attention.aiter_backend import AiterMultiStepDraftBackend
    from sglang.srt.layers.attention.deepseek_v4_backend_hip_radix import (
        DeepseekV4HipRadixBackend,
    )
​
    supports_hip_draft_extend_graph = isinstance(
        self.draft_attn_backend, AiterMultiStepDraftBackend
    ) or isinstance(self.draft_extend_attn_backend, DeepseekV4HipRadixBackend)# 仅在启用且未被 SGLANG_DISABLE_DRAFT_EXTEND_CUDA_GRAPH 关闭时捕获 draft-extend graph。
if (
    self.draft_extend_attn_backend
    and not envs.SGLANG_DISABLE_DRAFT_EXTEND_CUDA_GRAPH.get()
    and (
        _is_npu
        or _is_xpu
        or supports_cuda_draft_extend_graph
        or supports_hip_draft_extend_graph
    )
):
    self.cuda_graph_runner_for_draft_extend = Device2ExtendCudaGraphRunner[
        self.target_worker.device
    ](self)
python/sglang/srt/managers/scheduler_components/profiler_manager.py bugfix

修复 --profile 下启动崩溃的独立 bugfix:在受控停止点显式释放 torch_profiler 引用并立即 gc.collect(),规避 pybind11 引用循环被延迟回收时的 double-free。

# python/sglang/srt/managers/scheduler_components/profiler_manager.py(_stop_profile 尾部)
​
        merge_message = self._merge_profile_traces()
        logger.info(
            "Profiling done. Traces are saved to: %s%s",
            self.torch_profiler_output_dir,
            merge_message,
        )
​
        # torch.profiler 的 pybind11 对象形成引用循环,若交给随机的 cyclic GC
        # 回收,C++ 析构可能 double-free 导致堆损坏崩溃。这里在受控停止点
        # 主动释放引用并立即触发 GC,保证析构发生在确定位置。
        if self.torch_profiler is not None:
            self.torch_profiler = None
            gc.collect()
​
        self.profile_in_progress = False
        self.profiler_start_forward_ct = None
​
        return ProfileReqOutput(success=True, message=f"Succeeded.{merge_message}")

评论区精华

PR CI 覆盖缺口:DSV4 MTP 测试仅 nightly 覆盖 测试

amd-bot 多次提示 PR CI 不验证本 PR:"This PR's changed code is not exercised by any PR-CI test",并列出唯一覆盖测试 `test_deepseek_v4_pro_fp4_mtp.py` 为 nightly 套件。HaiShaw 据此要求作者补跑 nightly 测试。

结论:作者手动运行 nightly 套件两轮并贴出结果(Accuracy 0.955 / 0.943),覆盖缺口被关闭;reviewer 接受该验证方式后合入。 · 已解决

target-extend 上 D2H 优化触发 GPU memory access fault,仅应用到 draft-extend 正确性

作者说明:"The earlier D2H optimization caused a GPU memory access fault on the target-extend path with the current codebase, and I couldn't detect which kernel triggered it. As a workaround, the optimization is now applied to draft-extend only."

结论:以 need_compress 双分支保留工作区,只在 draft-extend 使用 output_size;故障未 root-cause,但已在源码注释和 PR 讨论中留档,被 maintainer 接受。 · 已解决

amd-bot 对 stage-c 套件包含 V4 MTP 测试的判断有误 question

1am9trash 在 approve 时纠正:"the amd-bot CI summary is mistaken here: the stage-c-test-large-8-gpu-amd suite it points to does not include the V4 MTP test. That test lives in nightly, so the queued stage-c jobs would not related to this PR anyway."

结论:确认 V4 MTP 测试在 nightly;amd-bot 关于 stage-c queued 的警告不适用,后续 amd-bot 也改为直接指出 nightly 覆盖缺口。 · 已解决

lint 检查失败 style

1am9trash 提醒 "The lint check failed.",作者随后提交 `fix lints` commit。

结论:lint 已修复,无遗留问题。 · 已解决

风险与影响

主要风险集中在验证缺口与未 root-cause 的故障上:

  1. 无自动化测试覆盖:三处改动都只在 AMD + DSV4 + MTP + HIP 组合下生效,唯一相关测试 test_deepseek_v4_pro_fp4_mtp.py 是 nightly 套件,PR CI 不执行,回归只能靠人工跑 nightly 兜底。
  2. target-extend 故障未 root-causedeepseek_v4_backend_hip_radix.pyneed_compress 分支的存在意味着同一函数在 target-extend 与 draft-extend 下行为不同,未来重构若误删分支或把 output_size 推广到 target-extend,可能重现 GPU memory access fault。
  3. profiler GC 修复仅缓解症状profiler_manager.pygc.collect() 只在 --profile 停止时执行一次,开销可忽略,但 torch.profiler pybind11 引用循环的 double-free 是 torch 侧生命周期问题,其他平台或新版本 torch 仍可能暴露。
  4. CUDA graph 捕获的显存开销:draft-extend 进入 graph 捕获后,捕获阶段会有额外显存占用,显存受限的 AMD 部署可用 SGLANG_DISABLE_DRAFT_EXTEND_CUDA_GRAPH 关闭。
  5. 合并噪音:分支含 9 个 merge commit,eagle_worker_v2.py 多次冲突,虽经 review 确认最终 diff 干净,但合并过程本身有引入错误的窗口。

影响范围严格限定在 AMD(HIP/ROCm)上的 DeepSeek-V4 模型 + MTP/EAGLE 投机解码场景,NVIDIA 路径不受影响(reviewer 1am9trash 明确确认)。对 AMD DSV4 MTP 用户,draft-extend 从 eager 变为 CUDA graph 捕获、消除 D2H 同步后,TTT 提升 5.7%-11.7%、ITL 提升 9.7%-13.5%,属于直观的延迟与吞吐收益;本轮实测 accuracy 0.955/0.943 与 gsm8k 0.95 无精度回退。对团队而言,该 PR 确立了 HIP 上按 attention backend 扩展 draft-extend graph 门控的写法,后续新 backend 只需在 supports_hip_draft_extend_graph 中追加类型即可复用;同时留下一处待 root-cause 的隐患记录,提醒后续优化需谨慎。

缺少测试覆盖 未 root-cause 的已知故障 平台特定变更 CUDA graph 显存开销

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论