Repository navigation
[ROCm][gfx1151] rdna35_rope_cache: fused decode q/k RMSNorm + RoPE + KV cache write - #1342
Draft
roberteg16 wants to merge 1 commit into
Draft
roberteg16 wants to merge 1 commit into
roberteg16 wants to merge 1 commit into
Conversation
…write One launch replaces what a decode step runs between the qkv projection and attention (ROCM_ATTN on gfx1151): the per-head q/k RMSNorm, NeoX RoPE (full or partial), Gemma 4's weightless V norm and the K/V write into the packed HND cache. _rocm_C op rdna35_rope_cache, built with the RDNA35 attention variants (gfx1151 only). 2.0-2.7x the separate kernels and 1.26-1.41x AITER's fused ones at T = nseq * M <= 32. ROCM_ATTN's gfx1151 impl implements the hooks of the fuse_rope_kvcache and fuse_qk_norm_rope_kvcache passes. The passes needed: the layer name as a LayerName pattern input, one qk-norm pattern sized per match (Gemma 4 has 256- and 512-dim layers), the fp8 query variant only when AITER's quant op exists, impl-declared head sizes, the V norm, and a RoPE variant that writes q and k to buffers of their own (the decode kernel reads a contiguous query; rotating the qkv view in place made Inductor copy qkv every layer). The impl also makes a strided query contiguous, for +rotary_embedding past the fusion's token range. On by default when the model's attention resolves to ROCM_ATTN on gfx1151, selected or auto-selected, its activations and KV cache are fp16 or bf16, and the build has the op (RocmPlatform.uses_rdna35_rope_cache): the optimization-level defaults turn the passes on from O2, and apply_config_platform_defaults sets what they need, +rotary_embedding and splitting at attention only. Settings the user gives are kept (fuse_*=False, -rotary_embedding, splitting_ops), and a model another backend serves keeps its graph. Guards: the RoPE pass skips layers whose V head size differs from K's; the hooks run the unfused ops, warning once with the reason, for GPT-J RoPE, mixed dtypes or norm weights of another type; the [3, T] positions contract (rows equal, only row 0 read) is the caller's, and the passes never route mrope there. Every other contract is a TORCH_CHECK in the op. End to end the gain is within noise (+0.12 % geomean decode speed over the 32 GB regression entries). Tools: benchmarks/kernels/gfx1151_decode_attn/tools/rope_cache.py, qknorm_rope_cache.py, cache_write.py. Tests: tests/kernels/attention/test_rdna35_rope_cache.py, up to 256 tokens, within 2 ulps of vLLM's native ops (which round after each RoPE multiply and add), and tests/compile/passes/test_rdna35_rope_kvcache_fusion.py, the passes on ROCM_ATTN and the defaults. AI assistance was used for this patch. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
This was referenced Oct 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #1330. The base is #1330's branch, so the diff shows only this commit; it will be retargeted to
gfx11once #1330 lands. It needs ROCM_ATTN's gfx1151 impl from #1330, whose hooks call this kernel.rdna35_rope_cache: fused decode q/k RMSNorm + RoPE + KV cache write.
fuse_rope_kvcacheandfuse_qk_norm_rope_kvcachepasses through the hooks of ROCM_ATTN's gfx1151 impl. The passes needed several fixes:fuse_*=False,-rotary_embedding,splitting_ops), and a model served by another backend keeps its graph.[3, T]positions contract (rows equal, only row 0 read) is the caller's, and the passes never route mrope there;TORCH_CHECKin the op.Performance
rdna35_rope_cache
Measured with
triton.testing.do_bench_cudagraphat T = nseq x M <= 32, contexts 128..1k (tools/rope_cache.py,tools/qknorm_rope_cache.py). It runs 2.0-2.7x faster than the kernels it replaces (Inductor's norm/RoPE and the Triton cache writer), and 1.26-1.41x faster than AITER's fused kernels in every cell.End to end the gain is within noise, so this commit is not about end-to-end speed. It saves 2-4 us a layer on steps of 9-35 ms.
rocm-scripts
regression_config.yaml(7f9f3aa), the out128 entries that fit a 32 GB board, fusion off vs on alternated per entry, median TPOT in ms:Geomean +0.12 % decode speed. The gain matters more where steps are short or T is large: small models, batches, speculative decode at M = 8.
Test plan
tests/kernels/attention/test_rdna35_rope_cache.py: 124 passed.tests/compile/passes/test_rdna35_rope_kvcache_fusion.py: 10 passed.-rotary_embeddingorfuse_*=False.--attention-backend TRITON_ATTNit is not enabled.pre-commit(ruff, mypy, clang-format) clean.Not duplicated
No other open PR adds a fused RoPE/KV-write kernel for gfx1151. AITER's
fused_qk_norm_rope_cache_pts_quant_shufflewrites only a block-contiguous NHD cache and has no D=512, so it cannot serve ROCM_ATTN's packed HND cache on gfx1151.AI assistance (Claude) was used for this change.