[Kernel] RPA: do the q token/kv-head swap on the 4D view to avoid a VMEM round trip - #3393
Open
shungcp wants to merge 1 commit into
Open
[Kernel] RPA: do the q token/kv-head swap on the 4D view to avoid a VMEM round trip#3393shungcp wants to merge 1 commit into
shungcp wants to merge 1 commit into
Conversation
Signed-off-by: Wang, Shun <shunwang@google.com>
shungcp
requested review from
a1yssan13,
bythew3i,
jrplatin and
kyuyeunk
as code owners
August 14, 2026 08:24
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.
What
prepare_inputsinragged_paged_attention/v3/kernel.pyreshapes q to 5D andthen swaps tokens with kv-heads. Doing the swap one step earlier — while q is
still 4D — produces a byte-identical operand for far less work.
One function; the kernel body,
q_hbm_ref,input_output_aliasesandprepare_outputsare untouched.Why it is faster
Splitting the q-head axis first fixes the two minor dims at
(q_packing, head_dim) = (2, 128). XLA cannot reach that layout from atranspose directly, so it stages the whole tensor through VMEM: copy in,
bitcast, copy back out to HBM. On the 4D view the minor dims stay
(num_q_heads_per_kv_head, head_dim), which one straight HBM→HBM copy reaches,and the trailing reshape is then a pure bitcast onto exactly the operand the
kernel already asks for.
The retile itself was never the cost:
bf16[T,N,H]{T(8,128)}andbf16[T,K,G/p,p,H]{T(2,128)}are byte-identical. What cost 130.8 µs/layer wasthe permutation being forced through VMEM.
Numbers
One 2048-token prefill step on v6e-1, Qwen3-4B, total device self time:
The attention-path layout ops drop from 5.91 ms to 4.03 ms per int8 step. bf16
is not regressed.
End to end on a real server (Qwix online int8, ISL 2300 / OSL 10, prefix caching
off, fixed seed, concurrency 64):
All five reported metrics move together in every row (e.g. Llama-3.1-8B at TP=1:
throughput +2.9%, TTFT −2.8%, P99 TTFT −2.8%, TPOT −2.8%). The spread tracks how
attention-heavy the model is and how many KV heads land on each chip. It is a
prefill-path change, so decode-heavy traffic sees proportionally less.
Who it helps
The gain needs
G = num_q_heads // num_kv_headsto be a power of two and>= 4(
>= 8when q itself is 8-bit, since q_packing goes 2→4) andhead_dim % 128 == 0. Both are per device — a K=8 model at TP=8 sees one KVhead per chip and the swap degenerates. Anything outside the set is an exact
no-op, measured inside a 0.3% noise floor:
num_q_heads_per_kv_headisalign_to(..., q_packing), so the 4D minor dims are never smaller than the 5Dones and a miss cannot regress.
Qualifying families:
Magistral, Devstral (all G=4)
235B-A22B (G=16) — plus Qwen2.5-72B, phi-4 and Granite-3.3-8B
kernel_hd64.pyhas the same construct but ishead_dim=64by definition, soit never meets the criteria; I left it alone rather than churn it for no gain.
Risk and testing
token-for-token on 5 prompts, including one crossing the chunked-prefill
boundary and one tile-unaligned.
tests/kernels/ragged_paged_attention_kernel_v3_test.py: 67 passed.no configuration.
Follow-up, not in this PR
Pinning the producer's layout with
with_layout_constraint(q, Layout(major_to_minor=(0,1,2,3)))is worth a further+4.8 points on Qwen3-4B when activations are int8: the fused
rope+activation-quant emits
[K][G][T][H], so even the 4D copy interleaves Gthrough T, and pinning
[T][K][G][H]makes it contiguous.Excluded here because it only pays when there is such a producer to pin. End to
end on Qwen3-4B it is +4.5% with int8 activations, −1.2% on bf16 and −1.3% on
weight-only int8 — two independent lines agreeing, and matching the +449 µs
per step the bf16 single-step profile predicted. Where XLA has already found a
good global layout, overriding it just relocates copies. So it needs a
quantization-aware condition, which seemed worth separating from a change that
is unconditionally free.