Skip to content

[ROCm] Swap the unified-attention 2D grid order on gfx1151 - #1289

Closed
roberteg16 wants to merge 1 commit into
rocm-2d-launch-configfrom
rocm-swapped-grid
Closed

roberteg16 wants to merge 1 commit into
rocm-2d-launch-configfrom
rocm-swapped-grid

Conversation

@roberteg16

@roberteg16 roberteg16 commented Sep 10, 2026 •

Copy link
Copy Markdown

Summary

On gfx1151 the 2D unified-attention kernel schedules better when the KV head is the fastest varying grid axis rather than the query block. This launches the 2D grid as (kv_heads, q_blocks) there, worth up to 31% of attention kernel time on long prefill.

The kernel gains a USE_SWAPPED_GRID constexpr defaulting to False, so every other platform compiles and launches exactly what it does today. The whole change is 2 lines of existing code plus the new gating helper.

Base

This is stacked on rocm-2d-launch-config, so the diff here shows only the swap. It is textually independent of that series — they share no hunks — but the swap is only meaningful on top of it: without the 2D launch tuning gfx11 does not route to TRITON_ATTN at all, and the head_size >= 80 boundary is the one that series already established.

Output is unchanged, bit for bit

The swap only relabels which grid axis carries which index. Every program still computes the same (q_block, kv_head) pair and accumulates over KV in the same order, so there is no reassociation and no change in results — unlike the tile/block-size tuning in the base series, which does alter reduction blocking.

Verified rather than asserted: a 3968-token Qwen3-8B-shaped prefill run with the swap on and off produces outputs that are torch.equal over the full [3968, 32, 128] tensor (max abs diff 0.0, zero differing elements).

Results

These are kernel microbenchmarks. benchmarks/attention_benchmarks operates on synthetic tensors; the configs carry only head geometry (num_q_heads, num_kv_heads, head_dim, block_size) and load no weights. The model names are shorthand for that geometry — quantization labels in particular have no effect here.

geometry head_dim shape base swapped run 1 run 2
Qwen3-1.7B 128 q8k 14.80 ms 11.28 ms +31.1%** +40.9%
Llama-2-7B 128 q8k 107.54 ms 93.59 ms +14.9%** +15.4%
Qwen2.5-7B 128 q3968s4096 5.59 ms 4.99 ms +12.0% +11.0%
Qwen3-1.7B 128 q3968s4k 3.15 ms 2.96 ms +6.4% +4.0%
Qwen3-8B 128 q3968s4096 6.19 ms 5.90 ms +5.0% +4.2%
Qwen3-Omni-30B-A3B 256 q8k 36.33 ms 34.71 ms +4.7% +5.7%
Llama-2-7B 128 q1920s2k 2.20 ms 2.25 ms −2.3% −4.7%
Qwen3-4B 80 q4000s4128 5.09 ms 5.16 ms −1.4% −1.6%

Testing

pytest tests/kernels/attention/test_triton_unified_attention.py    1874 passed (gfx1151)
pre-commit run --files <the two changed files>                     clean

AI assistance

This change was prepared with AI assistance (Claude). Every line was reviewed by the submitter, and the measurements above were run and checked by hand.

gfx1151 schedules the 2D kernel better when the KV head is the fastest
varying grid axis rather than the query block. The kernel gains a
USE_SWAPPED_GRID constexpr, defaulting to False, so every other platform
compiles and launches exactly what it does today.

head_size 64 regresses under the swapped order, so _use_swapped_grid draws
the line at 80.

The swap only relabels which grid axis carries which index: every program
still computes the same (q_block, kv_head) pair and accumulates over KV in
the same order, so the result is unchanged bit for bit. Verified on a 3968
token Qwen3-8B-shaped prefill, torch.equal over the full output tensor.

Measured on gfx1151 (Radeon 8060S) with benchmarks/attention_benchmarks,
against the same tree with the swap disabled. This is a kernel-level harness
on synthetic tensors: the configs carry only head geometry, no weights are
loaded, and the model names below are shorthand for that geometry. Attention
kernel time, 20 repeats after 10 warm-up iterations:

  Qwen3-1.7B      q8k          14.80 ms -> 11.28 ms   +31.1%
  Llama-2-7B      q8k         107.54 ms -> 93.59 ms   +14.9%
  Qwen2.5-7B-AWQ  q3968s4096    5.59 ms ->  4.99 ms   +12.0%
  Qwen3-1.7B      q3968s4k      3.15 ms ->  2.96 ms    +6.4%
  Qwen3-8B        q3968s4096    6.19 ms ->  5.90 ms    +5.0%
  Qwen3-Omni-30B  q8k          36.33 ms -> 34.71 ms    +4.7%

A second independent A/B pass reproduced these within about a percentage
point, except Qwen3-1.7B q8k, which came out at +40.9% with a much wider
spread; the +31.1% above is the conservative reading. The Qwen3-8B geometry
is present in the matrix twice under two names, so it is really four
measurements spanning +3.8% to +5.0%.

Two shapes lose. Llama-2-7B at q1920s2k costs 2.3% (4.7% on the second
pass), while the same model at q8k gains 15%: the win needs a long enough
sequence to amortise the reordered scheduling. Qwen3-4B-AWQ at q4000s4128
is 1.4% down, inside the noise band but consistent across both passes, and
it sits exactly on the head_size 80 boundary.

Everything else is flat, decode included: the swap is gated on the 2D path.

Co-authored-by: Claude <noreply@anthropic.com>

Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
@roberteg16

Copy link
Copy Markdown
Author

Upstream PR vllm-project#56253

@roberteg16 roberteg16 closed this Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant