Prhub

#30964 [AMD] Support DeepSeek V4 DSpark on AMD HIP platform

原始 PR 作者 At1a8 合并时间 2026-08-09 06:22 文件变更 9 提交数 64 评论 22 代码增减 +586 / -52

执行摘要

DeepSeek V4 DSpark 支持 AMD HIP,打通 unified KV ring 注入

PR body 开篇明确说明动机:"Follow https://github.com/sgl-project/sglang/pull/30261, support dspark for deepseek v4 on AMD platform"。此前 HIP 后端(DeepseekV4HipRadixBackend)在 ragged verify 的 CUDA graph 路径直接抛 NotImplementedError,DSpark 在 ROCm 上完全不可用;同时 DSpark draft 的 KV 存储与 target-hidden 注入仅支持非 unified 的 fp8 swa_kv_pool,无法利用 unified_kv_triton 的共享 bf16 ring,吞吐与显存都受限制。本 PR 需要同时补齐 HIP ragged verify 图支持与 unified KV 写入通道,并为 ROCm 构建未注册的 sgl_kernel 算子提供 torch 回退。

值得精读。核心看点:_unified_inject_loc 的 ring 寻址与双重 skip 设计、HIP 后端 ragged verify 图 key 改造(token-tier graph key)、以及围绕 nightly-only 覆盖缺口的 CI 流程讨论。若要在 AMD 上部署 DSV4 DSpark,需同时跟进 #34147 保证夜间覆盖,并在后续改动中保持 target_verify_num_draft_tokens 与 server args 的 gamma 约定一致。

讨论亮点

核心 review 交锋集中在三处:

  1. kkHuang-amd 指出 __init__ 中原地修改 speculative_num_draft_tokens -= 1 是 action-at-a-distance,建议镜像 CUDA 后端的局部变量模式;head 版本已改为显式 is_dspark_draft + target_verify_num_draft_tokens,并在注释中说明 CUDA 约定 gamma+1,同时提醒核对 graph buffer sizing。
  2. 1am9trash 提醒新测试应复用 PR#28920 清理后的默认环境变量,避免硬编码导致未来默认值变更时逐文件同步;作者已按建议更新并附上 CI 结果。
  3. amd-bot 多次给出同一结论:本 PR 唯一专用测试是 nightly-only 且路径被 SGLANG_HACK_FLASHMLA_BACKEND=unified_kv_triton 门控,PR CI 完全不验证变更代码,合入前必须由作者在 MI35x 上跑通 nightly 套件;michaelzhang-ai 随后手动调度该套件通过(GSM8K accuracy 0.9515 > 0.92,avg_spec_accept_length 3.730 > 3.0),并指出套件未挂入任何 nightly workflow、已提交 #34147 补排程。

实现拆解

1. HIP 后端打通 ragged verify CUDA graph

涉及 python/sglang/srt/layers/attention/deepseek_v4_backend_hip_radix.py:

  • 新增类属性 supports_ragged_verify_graph = True,移除 TARGET_VERIFY bucket 中针对 ragged verify 的 NotImplementedError 守卫。
  • __init__ 新增 is_dspark_draft 标志与 target_verify_num_draft_tokens = speculative_num_draft_tokens - 1:DSpark draft worker 只验证 gamma 行,服务端参数沿用 CUDA 侧 "gamma + 1" 约定,避免原地改属性导致下游 5 处用法隐式消费 gamma(对应 review 意见)。
  • init_forward_metadata_target_verify_old 接受 ragged_layout:通过 ExpandPrefillCausally 在 GPU 上做 prefill 展开,新增 compress_gpu_plan / extend_start_loc 支持 GPU 侧压缩计划;eager 路径回退到 sum(verify_lens)
  • init_forward_metadata_out_graph 的 TARGET_VERIFY bucket 用 ragged_layout.padded_to_bucket() 后的 graph_num_tokens 作为 token-tier 图 key(graph_key = num_tokens_v);make_forward_metadata_from_raw_verify 同步改用 target_verify_num_draft_tokens

2. unified KV ring 写入通道

涉及 python/sglang/kernels/ops/attention/dsv4/unified_kv_kernels/runtime.py 与 python/sglang/srt/mem_cache/deepseek_v4_memory_pool.py:

  • 新增 Triton _scatter_loc_kernel 与包装函数 scatter_bf16_into_unified:逐行把已 norm + RoPE 的 bf16 K scatter 进 unified_kv[loc]loc < 0 的行跳过(即按行 commit 掩码)。
  • DeepSeekV4TokenToKVPool 新增 set_unified_key_buffer_radix_fused_norm_rope:先 fused_norm_rope_inplace(与主模型 _compute_kv_bf16 同一 freqs_cis 路径),再 scatter 进 get_unified_kv(layer_id),签名与非 unified 版本完全一致。

3. 模型与注入/验证路径路由

  • python/sglang/srt/models/deepseek_v4_dspark.py:_store_block_kvis_unified_kv_triton() 时改用 get_unified_swa_loc(逐位置实时重算,支持多步 draft decode)+ unified 写入;write_target_hidden_kv 选择 unified store 函数,swa_loc/positions 契约不变。
  • python/sglang/srt/speculative/dspark_components/dspark_kv_inject.py:新增 _unified_inject_loc,地址公式 loc = state_slot * ring + pos % ring,叠加两层 skip(SWA 窗口裁剪旧 token 防 ring 槽位竞争、commit 门控丢未验收 token);inject_ragged 分流到 build_unified_commit_inject_layout
  • python/sglang/srt/speculative/dspark_components/dspark_verify.py:commit_hidden 为 unified 模式组装逐 token state_slot(由 batch.req_pool_indices 展开);_commit_inject 分流到 unified 布局构建。
  • python/sglang/srt/speculative/dspark_components/dspark_worker_v2.py:_forward_prefillrepeat_interleave 为每个 prefill token 计算 state_slotfinal_pos,使注入器能裁剪窗口外 token——长 prefill chunk 下这些 token 会共享 ring 槽位造成覆盖。

4. ROCm 采样回退与测试配套

  • python/sglang/srt/speculative/dflash_utils.py:为 top-k / top-p 概率重归一化增加 HIP torch 回退,CUDA/MUSA 行为不变。
  • 新增 test/registered/amd/test_deepseek_v4_pro_fp4_dspark.py:两个 kernel 单测用参考实现逐元素比对(test_build_unified_commit_inject_layouttest_scatter_bf16_into_unified);8 卡 MI35x 全量 GSM8K(1319 题,5-shot)e2e 注册为 nightly 套件,门禁 accuracy > 0.92 且 avg_spec_accept_length > 3.0。
文件 模块 状态 重要度
test/registered/amd/test_deepseek_v4_pro_fp4_dspark.py 端到端测试 added 7.55
python/sglang/srt/speculative/dspark_components/dspark_kv_inject.py 投机注入 modified 7.54
python/sglang/srt/layers/attention/deepseek_v4_backend_hip_radix.py 注意力后端 modified 7.37
python/sglang/srt/mem_cache/deepseek_v4_memory_pool.py KV 缓存 modified 6.93
python/sglang/srt/models/deepseek_v4_dspark.py 模型层 modified 6.77
python/sglang/srt/speculative/dspark_components/dspark_verify.py 投机验证 modified 6.56
python/sglang/srt/speculative/dspark_components/dspark_worker_v2.py 投机工作器 modified 6.27
python/sglang/kernels/ops/attention/dsv4/unified_kv_kernels/runtime.py 内核层 modified 5.93
python/sglang/kernels/ops/speculative/dspark/dspark_verify_window.py 内核层 modified 4.97
python/sglang/srt/speculative/dflash_utils.py 投机采样 modified 4.0

关键符号

scatter_bf16_into_unified _scatter_loc_kernel set_unified_key_buffer_radix_fused_norm_rope _unified_inject_loc inject_target_hidden inject_ragged build_unified_commit_inject_layout init_forward_metadata_target_verify_old init_forward_metadata_out_graph make_forward_metadata_from_raw_verify commit_hidden _commit_inject _store_block_kv write_target_hidden_kv

关键源码片段

python/sglang/srt/speculative/dspark_components/dspark_kv_inject.py dependency-wiring

DSpark target-hidden 注入的核心入口:新增 _unified_inject_loc 实现 ring 寻址与 SWA/commit 双重 skip,inject_ragged 分流到 unified 布局构建,是 unified KV 数据契约落地的关键。

# dspark_kv_inject.py —— unified_kv 下 target-hidden 注入的 ring 行地址计算def _unified_inject_loc(
    self,
    *,
    pool,
    positions: torch.Tensor,
    cache_loc_2d: Optional[torch.Tensor],
    commit_lens: Optional[torch.Tensor],
    state_slot: Optional[torch.Tensor],
    final_pos: Optional[torch.Tensor],
) -> torch.Tensor:
    """unified_kv 模式下 target-hidden 注入的 ring 行地址计算。    地址公式为 loc = state_slot * ring + pos % ring,并带两层 skip(-1)规则:
      * SWA 窗口裁剪:每个请求只有最后 win 个 token 落在 ring 内;更早的 token
        会与窗口内 token 共享 ring 槽位(pos % ring 相同)造成互相覆盖,所以
        直接丢弃——长 prefill chunk 场景必须依赖此规则。
      * commit 门控:未通过验收的 verify token(col >= commit_len)丢弃。
    """
    if state_slot is None:
        raise RuntimeError(
            "unified_kv target-hidden injection requires state_slot "
            "(per-token draft req_pool_indices)."
        )
    ring = pool.unified_swa_ring_size
    win = pool.unified_swa_window
    pos = positions.to(torch.int64)
    loc = state_slot.to(torch.int64) * ring + pos % ring
    if final_pos is not None:
        # 只保留窗口内的最新 token,其余置为 -1 由 scatter 跳过
        keep = pos > (final_pos.to(torch.int64) - win)
        loc = torch.where(keep, loc, torch.full_like(loc, -1))
    if commit_lens is not None and cache_loc_2d is not None:
        # 按请求展开列索引,未 commit 的列整体置 -1
        bs, verify_len = cache_loc_2d.shape
        col = torch.arange(verify_len, device=positions.device).view(1, -1)
        committed = (col < commit_lens.to(torch.long).view(-1, 1)).reshape(-1)
        loc = torch.where(committed, loc, torch.full_like(loc, -1))
    return loc.to(torch.int32)
python/sglang/kernels/ops/attention/dsv4/unified_kv_kernels/runtime.py infrastructure

新增 Triton scatter 内核,是 unified ring 写入的底层算子,被 memory pool 与注入路径共同依赖。

# unified_kv_kernels/runtime.py —— bf16 K 的 ring scatter 内核与包装函数@triton.jit
def _scatter_loc_kernel(
    kv_ptr, # [T, D] bf16,已 norm + rope 的 draft K
    loc_ptr, # [T] int,unified ring 行号;< 0 表示跳过
    unified_ptr, # [pages, D] bf16,目标 unified KV buffer
    n_rows,
    D: tl.constexpr,
    BLOCK_D: tl.constexpr,
):
    row = tl.program_id(0)
    if row >= n_rows:
        return
    loc = tl.load(loc_ptr + row).to(tl.int64)
    if loc < 0:
        return # 未 commit 或被窗口裁剪的 token,跳过写入
    offs = tl.arange(0, BLOCK_D)
    mask = offs < D
    vals = tl.load(kv_ptr + row * D + offs, mask=mask, other=0.0)
    tl.store(unified_ptr + loc * D + offs, vals, mask=mask)
​
​
def scatter_bf16_into_unified(
    *,
    kv: torch.Tensor, # [T, head_dim] bf16,已做 norm + RoPE
    loc: torch.Tensor, # [T] int32/int64,ring 行号;< 0 跳过
    unified_kv: torch.Tensor, # [pages, head_dim] bf16
) -> None:
    """把已 norm + rope 的 bf16 K scatter 进 unified_kv[loc],loc < 0 跳过。    与 store_swa_into_unified 配套:本函数面向已持有预计算 ring 行号的调用方
    (DSpark draft 的 get_unified_swa_loc、或 commit-inject 布局),
    用 loc == -1 表达按行的 commit 掩码,避免单独传 mask。
    """
    n_rows, D = kv.shape
    if n_rows == 0:
        return
    assert kv.is_contiguous() and kv.dtype == unified_kv.dtype
    assert loc.is_contiguous()
    assert unified_kv.is_contiguous()
    _scatter_loc_kernel[(n_rows,)](
        kv,
        loc,
        unified_kv,
        n_rows,
        D=D,
        BLOCK_D=triton.next_power_of_2(D),
        num_warps=8,
    )

评论区精华

speculative_num_draft_tokens 原地减一(gamma off-by-one 风险) 设计

kkHuang-amd 指出在 __init__ 中原地修改属性会让 5 处下游用法(init_forward_metadata_target_verify_old、num_tokens 计算、图 num_tokens_v、make_forward_metadata_from_raw_verify 等)隐式消费 gamma = N-1,属于 action-at-a-distance;CUDA 后端保持属性不变、用局部 block_size = speculative_num_draft_tokens - 1。还提醒核对 graph runner 读取的 server_args.speculative_num_draft_tokens(未减一)是否与 gamma 填充对齐。

结论:head 版本已改为显式 is_dspark_draft 标志 + target_verify_num_draft_tokens 局部语义,与 CUDA 模式一致。 · 已解决

commit_hidden 中的死变量 style

kkHuang-amd 指出 dspark_verify.py::commit_hidden 中 pool = self.kv_injector.draft_model_runner.token_to_kv_pool 未被使用(minor)。

结论:已清理,reviewer 最终 APPROVED(LGTM)。 · 已解决

测试环境变量应默认化而非硬编码 设计

1am9trash 提醒仓库在 PR#28920 已把大部分环境变量并入默认设置并清理了其他测试文件的硬编码,建议新测试跟随该模式(参考 amd/test_deepseek_v4_pro_fp4_mtp.py),避免未来默认值变更时逐文件同步。

结论:作者已按建议更新环境变量并附上 CI 结果。 · 已解决

nightly-only 覆盖缺口与合入门禁 测试

amd-bot 多次报告:本 PR 的唯一专用测试注册为 nightly=True,PR CI 不执行;合入门禁要求作者在 MI35x + unified_kv_triton 上跑通并确认 GSM8K 精度与 DSpark 接受长度。HaiShaw 据此要求作者补齐验证。

结论:michaelzhang-ai 手动调度 nightly 套件(run 31291406552)通过:GSM8K accuracy 0.9515 > 0.92、avg_spec_accept_length 3.730 > 3.0,满足门禁。 · 已解决

nightly 套件未挂入排程(coverage dormant) other

michaelzhang-ai 指出 register_amd_ci(nightly=True) 本身不会让套件进入 .github/workflows/nightly-test-amd.yml 的排程,本 PR 的覆盖在 main 上实际处于休眠状态;他已提交 #34147 补充 nightly-8-gpu-mi35x-deepseek-v4-pro-dspark-rocm720 job。

结论:待 #34147 合入后闭环;本 PR 合入时该缺口仍存在。 · 待处理

风险与影响

  • 测试覆盖空白(高风险):PR CI 不执行任何本 PR 的代码路径;合入时 nightly 排程也缺失(#34147 未合入),覆盖实际处于休眠状态,回归只能靠夜间任务兜底。
  • 门控依赖环境变量:is_unified_kv_triton() 依赖 SGLANG_HACK_FLASHMLA_BACKEND,unified / 非 unified 双路径增加组合数,新布局代码只在 unified 模式激活,普通 CI 无法触达。
  • gamma 语义耦合:target_verify_num_draft_tokens 与 graph runner 读取的 server_args.speculative_num_draft_tokens(未减一)必须始终对齐,kkHuang 也提醒检查 graph buffer sizing。
  • 分支卫生:64 个 commit、约 20+ 次 main merge、多次 revert/re-apply(如 CUTLASS FP8 相关 revert),存在冲突解决引入无关变更的风险。
  • 性能:scatter 内核每行一个 program、num_warps = 8,极端 shape 下可能成为瓶颈;但实测吞吐提升明显,风险可控。
  • 对 AMD 用户:MI35x 等 ROCm 平台首次获得 DSV4 Pro DSpark 投机解码支持,TP8 单并发 TTT 最高 2.93x、TP8DP8 256 并发 unified 后端 1.37x 且 TTFT 改善 2.4x 以上;实现为 HIP-only,不影响 CUDA/MUSA 路径。
  • 对系统:改动横跨 attention backend、mem cache、模型层、speculative 三组件与两个 kernel 模块,"state_slot * ring + pos % ring" 成为 DSV4 DSpark 注入的新数据契约。
  • 对团队流程:暴露并推动了 nightly 套件排程补齐(#34147),也示范了 env-gated HIP 特性的合入门禁做法。
仅 nightly 覆盖,PR CI 不验证 nightly 排程缺口待 #34147 补齐 HIP 专属 + 环境变量门控路径 投机解码核心路径变更 64 commits 长分支,多次 main 合并

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论