Skip to content

dsv4.1-amd: gfx950 sparse decode attention and sorted top-k - #41020

Merged
hnyls2002 merged 6 commits into
sgl-project:mainfrom
kevin-mii:dsv41-amd-3-hip-attn-backend
Sep 28, 2026
Merged

hnyls2002 merged 6 commits into
sgl-project:mainfrom
kevin-mii:dsv41-amd-3-hip-attn-backend

Conversation

@kevin-mii

@kevin-mii kevin-mii commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

This PR was "stack 3/4" of the DeepSeek-V4.1 AMD series (#41018 to #41021). It now follows the CUDA dsv4.1 layout (#39646, #39652, #39653, #39656, #39664, then #38798): kernel PRs by domain, each on main with no callers, then one integration PR. This is the third kernel PR; it does not depend on #41018 or #41019.

Summary

  • ops/attention/sparse_decode_reduce_hip.py: gfx_sparse_split_reduce combines the split-KV partials of aiter's gfx950 pa_decode_sparse(..., skip_reduce=True), bitwise equal to aiter's own reduce, with the attention sink and an optional inverse RoPE of each head's tail.
  • ops/attention/dsv4/compact_attention_hip.py: sparse decode attention that reads the compact V4.1 pages (528 B fp8 + ue8m0, 288 B e2m1 + e4m3) directly through aiter's gfx950 _qkpv_fp8, with main and extra slot ranges and 16-head groups.
  • ops/attention/dsv4/swapab_gluon_hip.py: the 16-head swap-AB Gluon kernel for HIP, argument-compatible with the SM100 kernel (CDNA4 MFMA, bf16 hi + lo compensated PV). It reuses the SM100 module's _load_v4, which is plain Gluon; swapab_attention gains inv_rope=None.
  • ops/attention/dsv4/attn_glue_hip.py: single-launch Triton replacements for the torch glue around the V4.1 sparse decode metadata, each bitwise equal to the torch expression in its docstring.
  • ROCm AOT top-k (deepseek_v4_topk.cu, sgl_kernel/top_k.py): sort_output sorts each row's picks ascending with a block-wide bitonic sort, -1 padding last; the selected set is unchanged; the sort is ROCm-only (under USE_ROCM). Strides below the wave width use lane shuffles and the rest LDS, so it is also right on 32-lane waves (gfx1250). benchmark/bench_dsv4_topk_transform.py times it: on MI350X the sort adds ~3.4 us per call, where a separate torch.sort costs ~17-20 us.
  • q_rope_store.py on HIP rounds like the batched HIP RoPE kernel.
  • ops/attention/dsv4/candidate_blocks_hip.py, beside main's candidate_blocks.py: the V4.1 two-level indexer's level-one block scores and top blocks, the top-k within the chosen blocks and the paged top-k sorted in the AOT epilogue, graph-safe.
  • Model call sites are in the integration PR of this stack.

Changes to existing kernels

  • decode_attention_sm100.py: swapab_attention takes an optional partial_gluon (swapab_gluon_hip.swapab_attention binds the gfx950 kernel), with no HIP branch in the SM100 file; _combine compiles the inverse RoPE out when inv_rope=None, so SM100 output is unchanged. decode_attention_sm100_gluon.py is untouched.
  • q_rope_store.py: the new branch is a HIP-only constexpr; CUDA arithmetic is unchanged.
  • The top-k epilogue, op schema and declaration are ROCm-only (setup_rocm.py); with sort_output=False the kernel is the same, plus a 4 KB static LDS array. The wrapper always passes sort_output, so sgl-kernel must be rebuilt from this PR.
  • Rollout: this PR is kernel-only, and no caller passes sort_output=True yet (the one existing caller, ops/attention/dsv4/topk.py, still uses the 6-argument form, which the default keeps working). Callers will only pass sort_output=True after sgl-kernel is released and the sglang-kernel pin is bumped to 0.4.8 or later, following How to update kernels in SGLang.

Verification

  • New tests: test_swapab_attention.py (SM100 and gfx950: fp64 reference over padded and poisoned pages, -1 / out-of-range slots, sinks, graph replay, and the combine's inverse RoPE bitwise to fused_rope_inplace); test_compact_attention_hip.py (both packed layouts, a second cache, -1 slots and a key-less row against a torch softmax with sink); test_topk.py::test_deepseek_v4_topk_transform_sorted_output; test_dsv41_decode_glue_hip.py (candidate blocks against main's select_candidate_block_ids with garbage past each row's reach, the sorted mapping, and the attn_glue_hip builders bitwise to their torch forms). test_dsv4_q_rope_store.py is now also registered for AMD.
  • MI350X (ROCm 10.0.0, torch 2.11, triton 3.8, aiter at the Dockerfile pin): these tests pass on this branch. The top-k epilogue built from this source matches the installed op over 120 shapes (sorted rows bitwise, unsorted rows the same set) and compiles for gfx1250.
  • With the integration PR's callers, DeepSeek-V4.1-Flash at TP4 / EP4 with DSpark serves and scores 0.900-0.905 on GSM8K (200 questions, two runs).
  • Pending on an SM100 runner: test_swapab_attention.py, test_dsv4_q_rope_store.py.

Stack

🤖 Generated with Claude Code


CI States

Latest PR Test (Base): ❌ Run #36477152106
Latest PR Test (Extra): ✅ Run #36477151594
Latest PR Test (AMD ROCm 10): ⏳ Run #36477151949

kevin-mii and others added 2 commits September 26, 2026 05:45
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
candidate_blocks_hip.py sits beside main's candidate_blocks.py: the level-one block scores
and top blocks, the top-k within the chosen blocks and the paged top-k sorted in the AOT
epilogue, all graph-safe. test_dsv41_decode_glue_hip.py pins them and this PR's
attn_glue_hip builders against the torch references.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@HaiShaw HaiShaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few comments to address.

import triton.language as tl

from sglang.srt.utils import is_hip

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider to move HIP out of _sm100.py, or rename file is possible.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7055e0c: no HIP code in _sm100.py now; the gfx950 entry is swapab_gluon_hip.swapab_attention.



@triton.jit
def _aiter_sparse_decode_reduce_kernel(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In-tree kernel no need to start with aiter when no origin relevant (in that case use _gfx_sparse_decode_recue_kernel), othereise we do import from it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: _gfx_sparse_decode_reduce_kernel; file renamed to sparse_decode_reduce_hip.py to match the other _hip.py kernels.

tl.store(out_ptr + t * out_stride_t + h * out_stride_h + d, out)


def aiter_sparse_split_reduce(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for method name

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: gfx_sparse_split_reduce.

Comment thread python/sglang/kernels/aot/csrc/elementwise/deepseek_v4_topk.cu
… in-tree split reduce

- decode_attention_sm100.py no longer branches on is_hip: swapab_attention takes an optional
  partial_gluon, and swapab_gluon_hip.py exports the gfx950 swapab_attention bound to its kernel.
- aiter_sparse_decode_reduce.py -> sparse_decode_reduce_hip.py (_gfx_sparse_decode_reduce_kernel,
  gfx_sparse_split_reduce): the kernel is in-tree, only its input layout comes from aiter.
- deepseek_v4_topk.cu: the wave-size, DPP/permlane and bitonic helpers and the sort_output epilogue
  are under USE_ROCM; a CUDA build rejects sort_output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kevin-mii

Copy link
Copy Markdown
Collaborator Author

Note on the AMD CI failure in run 36227768892 / stage-c-test-4-gpu-amd (rocm10, linux-mi300-4gpu-sglang, 0) (at 4421656): it is pre-existing on main, not from this PR.

  • Failing test: test/registered/lora/test_lora_gpt_oss_20b_logprob_diff.py, KL(sglang, trainer) 1.13-1.51 vs threshold 0.005 on every retry.
  • The same test fails the same way in main's scheduled PR Test (AMD) runs on the same job: 36241615319 (rocm10, KL up to 23.9), 36205773263 (rocm10, KL up to 15.4), 36168295472 (rocm720, KL up to 9.5).
  • This PR only touches DeepSeek-V4 kernels (ops/attention/dsv4/*, the split-KV reduce) and the ROCm DeepSeek-V4 top-k; gpt-oss LoRA does not reach any of them.

Review comments are addressed in 7055e0c.

@HaiShaw

HaiShaw commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator

bench_dsv4_topk_transform.py times deepseek_v4_topk_transform_512 unsorted, with sort_output,
and unsorted followed by torch.sort (MI350X, decode batch 1-128, 1k-32k keys: the epilogue sort
adds ~3.4 us, a separate torch.sort ~17-20 us).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kevin-mii

Copy link
Copy Markdown
Collaborator Author

https://github.com/sgl-project/sglang/actions/runs/36334848845 all PR base passed except for main failure for JIT test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amd bypass-fail-fast CI: a failing job no longer aborts its siblings (lint still gates) bypass-fastfail deepseek documentation Improvements or additions to documentation jit-kernel memory-pool quant LLM Quantization run-ci CI: run the baseline test suite on this PR run-ci-extra CI: also run the extra suite (requires run-ci) sgl-kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants