Repository navigation
perf(rocm): FA-RDNA2 decode kernel for spec-decode verify and mixed batches - #29
Merged
Merged
Conversation
…atches RDNA_ATTN sent every batch with max_query_len > 1 through a prefill kernel, including MTP verify rows (q_len = 1 + k) and the decode rows of mixed chunked-prefill batches. The prefill kernels run one CTA per (query block, head group) over the whole KV range: a k=2 verify request at 16k context (Qwen3.8-27B, TP=4) got 3 CTAs reading 16k keys each, where split-K decode runs 288 CTAs of ~1k keys. * fa_rdna2_decode_paged takes an optional cu_query_lens. Query token i of a q_len-token request attends to seq_len - (q_len - 1 - i) keys, so the causal mask becomes a per-token KV length; tokens past cu[-1] get zeros. Without cu_query_lens the kernels are unchanged (bitwise). * RdnaAttentionMetadataBuilder sets a reorder threshold of 1 (plus the speculative tokens) and build() splits decode-first batches with split_decodes_and_prefills. forward() runs the decode kernel on the leading requests and the prefill dispatch on the rest (query_start_loc rebased), both writing into the layer output. * VLLM_FA_RDNA2_SPLIT_DECODE=0 restores the old routing. VLLM_FA_RDNA2_VERIFY_FULL_GRAPH=1 reports UNIFORM_BATCH so uniform verify batches can be captured as full CUDA graphs (opt-in). Checked offline: CPU emulation of the kernels (per-token causal decode vs an fp64 reference with windows, padding and zero-length rows; decode plus each of the four prefill kernels on split batches; uniform decode bitwise equal to the previous kernels; shuffled schedules), the gfx1030 device build (register use unchanged), and the builder/forward dispatch on CPU with stub kernels. Not yet run on gfx1030 hardware; the PR lists the plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018dwtAVjZdFLPVRucCFXBzY
Times RdnaAttentionImpl.forward with the decode/prefill split off and on over MTP verify batches (B x (1 + k) tokens) and mixed batches (decodes next to a fresh prompt or a prefix chunk), and prints the max output difference between the two routings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018dwtAVjZdFLPVRucCFXBzY
BlivionIaG
changed the base branch from
claude/eloquent-albattani-1e61il
to
rdna_extras
September 28, 2026 07:50
2 of 4 tasks
2 of 4 tasks
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.
Purpose
This is follow-up 3 from the exploration plan in #28: mixed and MTP-verify batches.
rdna_extras.Problem
RdnaAttentionImpl.forwardonly used the decode kernel whenmax_query_len <= 1. Any other batch went entirely through a prefill kernel:ceil(total_tokens / BR) × heads × num_seqs, and most of those CTAs exit early. The decode rows inflate both factors.UNIFORM_SINGLE_TOKEN_DECODE. With MTP, vLLM logs "... is not supported with spec-decode for attention backend ... setting cudagraph_mode=PIECEWISE", so the whole model loses FULL graphs, GDN layers included.Change
fa_rdna2_decode_pagedtakes an optionalcu_query_lens([num_seqs + 1]int32).cu_query_lens.cu[-1](graph padding) and zero-length requests come out as zeros.cu_query_lensevery token reads its own row as before, and the output is bitwise identical to perf(rocm): FA-RDNA2 causal tile skipping, in-place output, parallel GQA softmax #28.nullptr...., Tensor(a!) out, Tensor? cu_query_lens=None) -> ().rdna_attn.py).__init__calls_init_reorder_batch_threshold(1, supports_spec_as_decode=True), which gives a threshold of 1 + num_speculative_tokens with MTP and 1 otherwise. Hybrid models already reorder with the same threshold (from the GDN builder). Under the V1 runner, dense models are now reordered decode-first; before this they were not reordered.build(). Callssplit_decodes_and_prefills(CPU only, no sync) and stores:num_decodesandnum_decode_tokens;decode_query_start_loc, a view that is safe to capture in a graph;prefill_query_start_loc, rebased asquery_start_loc[nd:] - nd_tok;max_prefill_seq_len, taken fromseq_lens_cpu_upper_bound, which is exact for prefill rows.forward(). The single-token branch is untouched, so [ROCm][RDNA] Load and call hippihx V1 from RDNA_ATTN decode (opt-in) #27's hippihx hook merges as is.[:num_decode_tokens]and the prefill dispatch takes the rest. Both write into slices of the layer output._forward_prefill. Its heuristics (short kernel below 4096 keys, split-K count,num_seqs <= 4) now see only the prefill rows.VLLM_FA_RDNA2_SPLIT_DECODE=0restores the old routing, for A/B runs and as a kill switch.VLLM_FA_RDNA2_VERIFY_FULL_GRAPH=1reportsUNIFORM_BATCH, so uniform verify batches replay as FULL graphs. It is opt-in until validated.benchmarks/kernels/benchmark_fa_rdna2_split_decode.pytimes the forward pass with the split off and on, for verify and mixed batches, and prints the max output difference between the two.Not in this PR:
Not a duplicate: the only open PR that touches FA-RDNA2 or
RDNA_ATTNis #28. This PR builds on it and implements its plan item 3.AI assistance: written with Claude Code. Per AGENTS.md, a human must review every changed line and run the GPU validation below before this leaves draft.
Test Plan
For the gfx1030 agent, after #28's build and kernel tests pass:
_rocm_C. The decode op's schema gainedTensor? cu_query_lens=None.test_decode_multi_token_queries[*]: verify rows, short extends, a zero-length request and padded tokens; window 0 and 50.test_forward_split_decode[*]:RdnaAttentionImpl.forwardend to end on a mixed batch and a verify-only batch, split on and off, D=128 and 256.test_builder_splits_decode_first: CPU-only builder logic.max|diff|should be around 1e-3 (fp16 noise).kv_splitsfor multi-token rows (fewer splits oncenum_decode_tokens × H_qfills the GPU).--no-graph.scripts/serve_gfx1030_flashnext_mtp.shwithATTN=fa MTP=2, andserve_gfx1030_full.sh.VLLM_FA_RDNA2_SPLIT_DECODE=1vs=0: identical, or diverging only after a near-tie.ATTN=triton.rdna_attn.pyrecords "online-softmax split-K drift" on MTP verify, and verify rows now use the split-K decode kernel.tests/evalsor lm_eval), split on vs off.ATTN=triton.VLLM_FA_RDNA2_VERIFY_FULL_GRAPH=1.VLLM_FA_RDNA2_GQA_DECODE=1also handlescu_query_lens. If perf(rocm): FA-RDNA2 causal tile skipping, in-place output, parallel GQA softmax #28's step 7 finds that kernel coherent, repeat step 4 with it.Test Result
Offline (done):
cu_query_lens): bitwise identical to perf(rocm): FA-RDNA2 causal tile skipping, in-place output, parallel GQA softmax #28 in all 10 cases. D=128/256, G=4/6, bs 16/784, dense and hybrid layouts, windows 0/1/50/500.forward()(decode kernel, then each of the 4 prefill kernels on the rebased remainder): 14 cases with windows 0 and 32, max_rel 1.2e-3.VLLM_FA_RDNA2_GQA_DECODE=1; launch trace confirms it runs for D=256): same 35 cases pass.test_builder_splits_decode_firstandtest_metadata_carries_causalpass. Also passing: verify-only, pure-decode, prefill-first, non-causal, and V1-style padded requests and tokens.RdnaAttentionImpl.forwardwith the kernel module swapped for torch reference kernels (same layouts and argument contracts), on 47 batches: mixed, verify-only, pure decode, prefill first, padded tokens, windows, D=128/256, G=2-4, thresholds 1/3/4/off. Outputs match the reference, kernel dispatch and arguments are as expected, and rows pastnum_actual_tokensstay untouched.tools/pre_commit/mypy.py: no new errors (the existing two inrdna_attn.pyand one in the test file remain).rdna_extras(perf(rocm): opt in to HIP MoE for concurrent MTP decode #26, [ROCm][RDNA] Load and call hippihx V1 from RDNA_ATTN decode (opt-in) #27).On gfx1030: pending, to be filled in from the plan above.
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.🤖 Generated with Claude Code
https://claude.ai/code/session_018dwtAVjZdFLPVRucCFXBzY
Generated by Claude Code