Skip to content

[HIP] [JIT] Add a DSv4 paged K gather+dequant kernel - #5680

Merged
valarLip merged 1 commit into
ROCm:mainfrom
akii96:aakif/dsv4-paged-k-gather-dequant-hip
Sep 21, 2026
Merged

valarLip merged 1 commit into
ROCm:mainfrom
akii96:aakif/dsv4-paged-k-gather-dequant-hip

Conversation

@akii96

@akii96 akii96 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a HIP kernel for the DeepSeek V4 paged K gather+dequant. The existing Triton kernel does not coalesce and holds ~0.34 TB/s on a 4.2 TB/s device, and it runs twice per chunk in the prefill loop.

Suggested fix

The new HIP kernel maps one wavefront to one gathered token, with lane L owning output dims [L8, L8+8). The 64 lanes cover all 512 dims so the store is a single 1024 B line. Lanes 0-55 read the fp8 payload and dequantize, lanes 56-63 pass the bf16 RoPE tail through. The fp8->f32 decode is branchless and parameterized by the exponent bias, so one path serves both e4m3fnuz and OCP e4m3.

Perf

MI325X (gfx942), CUDA-graph replay, k_cache[*, 64, 584]:

out shape Triton new HIP kernel
[4, 4096, 512] 74.6 us 13.3 us 5.6x
[4, 16384, 512] 313.5 us 32.1 us 9.8x
[4, 65536, 576] 1899.8 us 126.7 us 15.0x

Triton sits at ~0.34 TB/s across all three; this reaches 3.3 TB/s at the larger shapes, against ~4.2 TB/s measured streaming rate on this arch.

Correctness

  • Swept all 256 fp8 codes x all 256 UE8M0 scales against the Triton reference, both encodings. OCP e4m3 matches bit for bit; e4m3fnuz matches except 0x3f and 0xbf at scale 2^-126, where the result is bf16-subnormal and flushes to zero — that scale means the whole 64-dim group is already negligible. NaN encodings aren't reachable, since the V4 encoder clamps to FP8_MAX.
  • Also checked ragged gather_lens, partial gathers, non-zero offset, and block_size 32 and 64.

Add `dsv4_dequantize_and_gather_k`, a HIP kernel that gathers the DeepSeek
V4 paged K record into a bf16 workspace, replacing a Triton gather that does
not coalesce. One wavefront handles one gathered token so the store is a
single coalesced line, and the fp8 decode is branchless bit arithmetic
parameterized by the exponent bias, serving both e4m3fnuz and OCP e4m3.

Matches the Triton reference bit for bit on all representable inputs, except
that a product landing in the bf16 subnormal range flushes to zero: fp8 0x3f
and 0xbf at scale 2^-126 under e4m3fnuz. NaN encodings are not represented,
since the V4 encoder clamps to FP8_MAX.

New op only: nothing existing changes and no caller is switched over.

Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
@akii96
akii96 requested a review from a team September 19, 2026 07:00
@github-actions

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:gfx1250-ffm-triton Run the five-shard gfx1250 FFM Triton test suite
ci:triton-300x Run an additional Triton test job on MI300X in PRs; main branch always runs both MI35X and MI300X
multigpu Aiter multi-GPU tests on the 8-GPU runner
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: GPT-OSS-120B, DeepSeek-R1-0528, Kimi-K2.5
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 5680 --add-label <label>

PR title tags & labels:
Component tags ([Triton/Gluon], [HIP], [CK], [ASM], ...) are added to the PR title and as PR labels automatically from the changed files and re-synced on every push — change-type tags like [fix]/[Perf], op tags like [MLA], and human labels (ci:*) are left untouched. Add the no-auto-title label to opt this PR out.

@github-actions github-actions Bot changed the title [HIP] Add a DSv4 paged K gather+dequant kernel [HIP] [JIT] Add a DSv4 paged K gather+dequant kernel Sep 19, 2026
@akii96

akii96 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Re-benchmarked against vllm-project/vllm#56720

vllm-project/vllm#56720 rewrites this Triton gather for ROCm (one pass, plus a max_gather_len-sized worker grid). It gates on is_rocm(), not on arch, so gfx942 will inherit it but regardless I re-measured against that rather than the old two-pass kernel.

MI325X (gfx942), CUDA-graph replay, fragmented block table, 5 repeats, median. Both kernels in one process on identical inputs.

shape one-pass Triton this gain
1 x 4096 11.2 us (0.59 TB/s) 10.2 us (0.64) 1.09x
4 x 16384 37.9 us (2.78) 34.2 us (3.08) 1.11x
1 x 32768 22.7 us (2.32) 20.3 us (2.59) 1.12x
4 x 32768 68.0 us (3.10) 59.9 us (3.52) 1.14x
8 x 32768 238.2 us (1.77) 131.8 us (3.20) 1.81x
2 x 65536 69.4 us (3.04) 62.4 us (3.38) 1.11x
4 x 65536 241.5 us (1.75) 132.8 us (3.18) 1.82x
1 x 131072 72.8 us (2.89) 59.7 us (3.53) 1.22x
2 x 131072 243.1 us (1.73) 134.1 us (3.14) 1.81x
4 x 131072 480.2 us (1.76) 252.3 us (3.34) 1.90x
1 x 262144 253.8 us (1.66) 132.0 us (3.19) 1.92x
2 x 262144 481.6 us (1.75) 253.5 us (3.33) 1.90x
4 x 262144 911.2 us (1.85) 508.9 us (3.31) 1.79x

This kernel holds 3.2-3.5 TB/s at every shape. The one-pass grid caps at _GATHER_MAX_WORKERS, sized against a 256-CU gfx950; on gfx942 that cap binds past ~256k gathered tokens, each program loops, and Triton drops to a flat ~1.7-1.8 TB/s. So the gap is 1.1x on short prefills and 1.8-1.9x from 128k up!

@valarLip
valarLip merged commit 314ab7d into ROCm:main Sep 21, 2026
49 checks passed
@akii96
akii96 deleted the aakif/dsv4-paged-k-gather-dequant-hip branch October 12, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants