Skip to content

[ROCm] Encoder prefill attention on gfx1151: split head-dim block and a narrower KV tile - #1322

Closed
roberteg16 wants to merge 952 commits into
rogarcia.upstream-encoder-prefill-attn-basefrom
rogarcia.upstream-encoder-prefill-attn
Closed

roberteg16 wants to merge 952 commits into
rogarcia.upstream-encoder-prefill-attn-basefrom
rogarcia.upstream-encoder-prefill-attn

Conversation

@roberteg16

@roberteg16 roberteg16 commented Sep 23, 2026 •

Copy link
Copy Markdown

Summary

vllm/v1/attention/ops/triton_prefill_attention.py is the encoder attention kernel — ViT towers via the mm-encoder backend, the encoder branches of TRITON_ATTN and ROCM_ATTN, and mimo_v2_omni's visual attention. (The paged LLM prefill path imports a same-named context_attention_fwd from prefix_prefill.py; that is a different module and is untouched here.)

It pads the head-dim tile to next_power_of_2(head_dim), which wastes most of the matmul on the non-power-of-2 head dims vision towers use, and the KV tile vllm-project#58225 picks for RDNA is still wider than those shapes want. On gfx1151 this is worth 2.19x on the head_dim=72 kernel and takes Gemma3-4B's TTFT down 21%.

Everything is inside one if on_gfx1151() in context_attention_fwd that only overrides launch values. Every other part keeps today's behaviour exactly: the launch arguments are computed as they are now, and only reassigned inside that branch.

    BLOCK_DMODEL = triton.next_power_of_2(Lk)
    BLOCK_DMODEL_TAIL = 0
    HEAD_STRIDE_ALIGNED_8 = False

    if on_gfx1151() and q.dtype in (torch.bfloat16, torch.float16):
        main = BLOCK_DMODEL // 2
        tail = max(16, triton.next_power_of_2(Lk - main))
        if Lk != BLOCK_DMODEL and main + tail < BLOCK_DMODEL:
            BLOCK_DMODEL, BLOCK_DMODEL_TAIL = main, tail

        HEAD_STRIDE_ALIGNED_8 = ...   # from the real strides of Q/K/V/O

        if HEAD_STRIDE_ALIGNED_8 and (BLOCK_DMODEL, BLOCK_DMODEL_TAIL) == (64, 16):
            BLOCK_N, num_warps = 16, 4

Exactly one existing line in the wrapper changes (BLOCK_DMODEL= in the launch). Four more change in _fwd_kernel, hoisting the head-axis offsets into locals so the alignment hint has something to annotate. Everything else is additive, and the new path is constexpr dead code when BLOCK_DMODEL_TAIL == 0.

Commits

commit what it does
[ROCm] Split the head-dim block for non-power-of-2 head dims on gfx1151 Covers the head dim as a power-of-2 main block plus a power-of-2 tail (72 → 64+16 = 80) behind a new constexpr BLOCK_DMODEL_TAIL; a tail of 0 reproduces the single-block kernel exactly. Skipped where it would not narrow the extent — 112 would be 64+64, the same 128 lanes for 12% more time.
[ROCm] Narrow the KV tile for the 64 + 16 head dims on gfx1151 Two things, and only one is about the tile. The tl.multiple_of hint on the head-axis offsets keeps the D-contiguous loads vectorized at head_dim=72, where the head stride is 8 mod 16 and Triton will not infer the alignment; it is set from the real strides and is unconditional. The narrow tile (BLOCK_N=16, 4 warps) is then taken only where that hint is active.

Performance

Radeon 8060S (gfx1151), against upstream/main which already includes vllm-project#58225. Each pass checks out every point in turn and tables are the median of 3 such passes; spread is ±1% or better except where noted.

Kernel — context_attention_fwd, B=1, S=3520, H=16, bf16, non-causal, ms/call:

head_dim upstream +split +narrow tile & hint total
64 1.727 1.745 (0.99x) 1.741 (1.00x) 0.99x
72 4.842 2.524 (1.92x) 2.213 (1.14x) 2.19x
80 3.511 ±8% 2.938 (1.20x) 2.347 (1.25x) 1.50x
96 3.578 3.222 (1.11x) 3.230 (1.00x) 1.11x
128 3.892 3.898 (1.00x) 3.902 (1.00x) 1.00x
256 13.746 13.744 (1.00x) 13.748 (1.00x) 1.00x

The hint is unconditional; the tile is what depends on it. At every head dim whose stride is already 16-byte aligned the hint is a tautology the compiler had anyway, and it measures exactly that. Each head dim on the tile it is given, median of 4 interleaved passes, ms/call:

head_dim no hint with hint
64 1.814 1.813 1.00x
72 2.887 2.302 1.25x
80 2.322 2.325 1.00x
96 3.204 3.203 1.00x
128 3.871 3.885 1.00x
256 13.556 13.563 1.00x

Only head_dim=72, the one that is 8 mod 16, learns anything from it. The narrow tile is the part that needs it: with vectorized loads BLOCK_N=16 and 4 warps are worth 1.25x at both 72 (2.895 → 2.293) and 80 (2.924 → 2.331), but without them they are a regression — at head_dim=72 with scalar loads the narrow tile runs 2.676 ms against 2.449 for the tile vllm-project#58225 picks. Hence if HEAD_STRIDE_ALIGNED_8 and ...: a head_dim of 72 in a view whose head stride is padded keeps the wider tile.

Sweeping BLOCK_N x num_warps confirms vllm-project#58225's tile is right for everything else: 64 wants 32/4, and 96, 128 and 256 want 32/8, where BLOCK_N=16 is 22% to 45% slower even with the warp count it prefers.

End to end — TTFT, single 1920x1080 image, max_tokens=1, prefix caching and the multimodal processor cache disabled and a different image per repetition so every call really re-runs the vision tower. Median of 5, ms:

model ViT head_dim upstream +split +narrow tile & hint total
gemma-3-4b-it-quantized.w4a16 (SigLIP) 72 442.8 362.2 (−18.2%) 350.8 (−3.1%) −20.8%
Qwen2.5-VL-7B-Instruct 80 1946.7 1934.4 (−0.6%) 1907.1 (−1.4%) −2.0%

Gemma3 pads to a fixed 896x896 and pairs a 4B w4a16 language model with a 27-layer tower, so the vision encoder dominates its TTFT; Qwen2.5-VL-7B spends proportionally more time in the language model. Both runs were checked to really reach this kernel — 27 calls per request at (16, 72) and 32 at (16, 80), matching each tower's depth.

Why these values

Against vllm-project#58225's tile, median of 5 interleaved passes at S=3520:

head_dim split vllm-project#58225 tile BLOCK_N=16, 4 warps
64 (64, 0) 1.818 1.943 0.94x
72 (64, 16) 2.909 2.309 1.26x
80 (64, 16) 2.934 2.341 1.25x
88 (64, 32) 3.202 3.350 0.96x
96 (64, 32) 3.225 3.385 0.95x
112 (128, 0) 3.672 8.009 0.46x
128 (128, 0) 3.896 9.236 0.42x

Hence the narrower tile is taken exactly for the 64 + 16 dims. No waves_per_eu override helped anywhere, so none is set, and BLOCK_M keeps the generic selection.

benchmarks/kernels/benchmark_vit_prefill_attn.py is added so this is reproducible; it launches _fwd_kernel directly so --bm/--bn/--nw/--ns/--we are honoured.

python benchmarks/kernels/benchmark_vit_prefill_attn.py --seq 3520 --heads 16 --head-dim 72 \
    --backend TRITON_ATTN --bm 128 --bn 16 --nw 4

Testing

pytest tests/kernels/attention/test_triton_prefill_attention.py   128 passed
pre-commit run --files <changed files>                            clean (ruff, ruff-format, mypy, SPDX)

The existing correctness tests only covered head_dim 128; they now also run 64, 72, 80 and 96, which between them exercise both tail widths the kernel can emit (72 and 80 split as 64+16, 96 as 64+32). The launch configuration is asserted through the capture harness vllm-project#58225 added, extended with a head_dim and an on_gfx1151 knob — so the gfx1151 branch is covered on CI agents that are not gfx1151, and vllm-project#58225's own tests stop depending on the host part. A further test runs the split kernel for real against the SDPA reference with the predicate mocked on.

Each commit compiles and passes its own tests in isolation (115 and 128 passed). The split leaves every power-of-2 head dim bit-identical; the tile change then alters the order the softmax accumulates in, so bf16 output moves by up to one ULP.

Model evaluation

The change perturbs bf16 output by up to one ULP (the KV tile alters the order the softmax accumulates in), so it is worth showing what that does downstream.

Multimodal, which is what this kernel serves. Five synthetic images with a known answer through Qwen2.5-VL-7B-Instruct, greedy, 32 tokens, TRITON_ATTN mm-encoder backend — 32 encoder-kernel calls per request, confirmed by instrumenting context_attention_fwd. The generated text is identical between upstream/main and this branch on all five:

[PASS] red/circle: 'Red circle.'      [PASS] yellow/square: 'Yellow square.'
[PASS] green/circle: 'Green circle.'  [PASS] purple/circle: 'Purple circle.'
[PASS] blue/square: 'Blue square.'

GSM8K, as a regression check only. tests/evals/gsm8k/gsm8k_eval.py, 600 questions, 5-shot, temperature 0, same model. It is worth being explicit that this benchmark does not reach this kernel: a text-only prompt makes zero calls to context_attention_fwd, measured the same way, because there is no vision tower to run. Running upstream/main three times gives 0.1250 / 0.1350 / 0.1400, so the harness spreads 1.5 accuracy points on identical code; this branch scores 0.1417, inside that spread. (The absolute number is low because the harness's bare few-shot completion prompt does not make this instruct model stop — 230 of 256 tokens per question — not because of anything here.)

Not a duplicate

This builds on vllm-project#58225, which caps BLOCK_N at 32 for all of RDNA3/RDNA4 and already covers head dims 96 and up on gfx1151 — which is why nothing is claimed there. What is left is the 64 + 16 dims, where the tile wants to be narrower still and the head dim needs the split in the first place.

vllm-project#56232, vllm-project#56253 and vllm-project#56276 tune triton_unified_attention.py (decoder prefill/decode) — the same argument applied to the other attention kernel, no file overlap. Encoder attention reaches this kernel under both ROCM_ATTN and TRITON_ATTN, so vllm-project#56232's backend reordering does not change whether this applies.

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.

@roberteg16
roberteg16 force-pushed the rogarcia.upstream-encoder-prefill-attn branch 2 times, most recently from e6fc764 to 008581b Compare October 5, 2026 14:42
@roberteg16 roberteg16 changed the title [ROCm] Encoder prefill attention on gfx115x: split-D head dims and a narrower KV tile [ROCm] Encoder prefill attention on gfx1151: split head-dim block and a narrower KV tile Oct 5, 2026
@roberteg16
roberteg16 force-pushed the rogarcia.upstream-encoder-prefill-attn branch 3 times, most recently from 8b3b5f0 to 5c141d6 Compare October 6, 2026 17:28
@roberteg16
roberteg16 force-pushed the rogarcia.upstream-encoder-prefill-attn branch 2 times, most recently from b895308 to a04ce4d Compare October 13, 2026 17:18
DimensionSTP and others added 21 commits October 15, 2026 14:46
…ation rounds (vllm-project#61161)

Signed-off-by: DimensionSTP <65501090+DimensionSTP@users.noreply.github.com>
Co-authored-by: Kevin H. Luu <khluu000@gmail.com>
…nce steps automatically (vllm-project#61861)

Signed-off-by: Thang Nguyen <thangnguyenvn647@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: jpvillam <juan.villamizar@amd.com>
Signed-off-by: Jaden Mathias <jaden.mathias@amd.com>
Signed-off-by: Juan Villamizar <jpvillam@amd.com>
Signed-off-by: jpvillam <jpvillam@amd.com>
Signed-off-by: Omar Muhammad <omar.muhammad@amd.com>
Signed-off-by: Daniel <danichan@amd.com>
Signed-off-by: Juan Villamizar <100237675+jpvillam-amd@users.noreply.github.com>
Co-authored-by: Jaden Mathias <jaden.mathias@amd.com>
Co-authored-by: jpvillam <jpvillam@amd.com>
Co-authored-by: Omar Muhammad <omar.muhammad@amd.com>
Co-authored-by: Daniel <danichan@amd.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Chan <Daniel.Chan@amd.com>
…vllm-project#59554)

Signed-off-by: Shern Shiou Tan <shernshiou@gmail.com>
Signed-off-by: Simon Veitner <sveitner@redhat.com>
Signed-off-by: Raphael Rialland <raphael.rialland@mistral.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Simon Veitner <sveitner@redhat.com>
Co-authored-by: Raphael Rialland <raphael.rialland@mistral.ai>
Co-authored-by: Lucas Wilkinson <LucasWilkinson@users.noreply.github.com>
…he target head (vllm-project#59973)

Signed-off-by: Stefano Castagnetta <scastagnetta@nvidia.com>
Co-authored-by: Kevin H. Luu <khluu000@gmail.com>
…#61767)

Signed-off-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: hjjq <50634613+hjjq@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… decode (vllm-project#57392)

Signed-off-by: omuhamma <omar.muhammad@amd.com>
Signed-off-by: Nicholas Susanto <nicholas.susanto@amd.com>
Co-authored-by: Nicholas Susanto <nicholas.susanto@amd.com>
Co-authored-by: Simon Danielsson <70206058+simondanielsson@users.noreply.github.com>
…cutor (vllm-project#61713)

Signed-off-by: Woosuk Kwon <woosuk@inferact.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…project#61724)

Signed-off-by: Juntian Liu <Juntianl777@gmail.com>
Signed-off-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…vllm-project#61913)

Signed-off-by: Anerudhan Gopal <agopal@nvidia.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
…+ KV Cache runtime fusion for Qwen3-30B-A3B on ROCM_ATTN (vllm-project#42755)

Signed-off-by: Jack Hu <Jack.Hu@amd.com>
Signed-off-by: Jack Hu <jingxujaster@hotmail.com>
Signed-off-by: Jack Hu <jack.hu@amd.com>
Co-authored-by: Douglas Lehr <91553416+dllehr-amd@users.noreply.github.com>
…-project#61388)

Signed-off-by: Ronald1995 <ronaldautomobile@163.com>
Signed-off-by: Ao Shen <281812371+aoshen02@users.noreply.github.com>
Signed-off-by: Ao Shen <aoshen@inferact.ai>
Co-authored-by: Ao Shen <281812371+aoshen02@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Ao Shen <aoshen@inferact.ai>
…t#61924)

Signed-off-by: Tessa Pham <tepham@redhat.com>
Co-authored-by: Benjamin Chislett <bchislett@nvidia.com>
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
… parameters (vllm-project#61930)

Signed-off-by: khluu <khluu000@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…m-project#61471)

Signed-off-by: Andreas Karatzas <akaratza@amd.com>
Co-authored-by: Claude <noreply@anthropic.com>
…oject#61716)

Signed-off-by: Monishver Chandrasekaran <monishverchandrasekaran@gmail.com>
Co-authored-by: Kevin H. Luu <khluu000@gmail.com>
…roject#43202)

Signed-off-by: lqh-hh <liuqihang6@huawei.com>
Signed-off-by: Tyler Michael Smith <tlrmchlsmth@gmail.com>
Co-authored-by: lqh-hh <liuqihang6@huawei.com>
Co-authored-by: Tyler Michael Smith <tlrmchlsmth@gmail.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: 2017403603 <705328646@qq.com>
Co-authored-by: Julien Mancuso <jmancuso@nvidia.com>
Co-authored-by: Itay Alroy <75032521+itayalroy@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Signed-off-by: Sandeep Maddipatla <sandeep.maddipatla@intel.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Kevin H. Luu <khluu000@gmail.com>
…llm-project#61867)

Signed-off-by: Benjamin Chislett <bchislett@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: vllm-agent <claw@inferact.ai>
john-evan08 and others added 28 commits October 19, 2026 22:09
…ect#60373)

Signed-off-by: John-Evan Karcenty <jkarcenty@yokbvdijkarcenty01.nvidia.com>
Co-authored-by: John-Evan Karcenty <jkarcenty@yokbvdijkarcenty01.nvidia.com>
Co-authored-by: Wei Zhao <51183510+wzhao18@users.noreply.github.com>
…t and three more steps from fixed to automatic shards (vllm-project#62084)

Signed-off-by: Thang Nguyen <thangnguyenvn647@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e KV cache and Step5 (vllm-project#62593)

Signed-off-by: Isotr0py <Isotr0py@outlook.com>
Co-authored-by: vllm-agent <claw@inferact.ai>
…ault (vllm-project#59053)

Signed-off-by: Giulio De Pasquale <git@depasquale.giugl.io>
Co-authored-by: vllm-agent <claw@inferact.ai>
…llm-project#61601)

Signed-off-by: zjy0516 <riverclouds.zhu@qq.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…end (vllm-project#52505)

Signed-off-by: Fangchen Li <fangchen.li@outlook.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lector (vllm-project#62563)

Signed-off-by: Ankur Singh <ankusingh@nvidia.com>
Co-authored-by: Claude <noreply@anthropic.com>
… failed receives to avoid potential KV block write-after-free (vllm-project#62282)

Signed-off-by: Yuhan Zhou <zhouyuhan_@outlook.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: vllm-agent <claw@inferact.ai>
Co-authored-by: Isotr0py <Isotr0py@outlook.com>
…ect#62578)

Signed-off-by: Chaojun Zhang <chaojun.zhang@intel.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: vllm-agent <claw@inferact.ai>
…Docs] Kimi-K3 GB300 配方使用概率式 DSpark 采样 (vllm-project#62610)

Signed-off-by: Rohit Pujar Nagraj <rpujarnagraj@nvidia.com>
Co-authored-by: Wei Zhao <51183510+wzhao18@users.noreply.github.com>
vllm-project#62326)

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…-project#62358)

Signed-off-by: Hollow Man <hollowman@opensuse.org>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ed (vllm-project#62449)

Signed-off-by: Martin Hickey <martin.hickey@ie.ibm.com>
Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>
…m-project#62620)

Signed-off-by: Andreas Karatzas <akaratza@amd.com>
Co-authored-by: Claude <noreply@anthropic.com>
…vllm-project#62421)

Signed-off-by: Andreas Karatzas <akaratza@amd.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: vllm-agent <claw@inferact.ai>
…points (vllm-project#57916)

Signed-off-by: Sergei Shliapnikov <sergei.shliapnikov@amd.com>
Co-authored-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: vllm-agent <claw@inferact.ai>
…ath (vllm-project#61497)

Signed-off-by: Jhao-Ting Chen <jhaotingc@nvidia.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…automatically (vllm-project#61999)

Signed-off-by: Thang Nguyen <thangnguyenvn647@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… automatically (vllm-project#62562)

Signed-off-by: Thang Nguyen <thangnguyenvn647@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ically (vllm-project#62039)

Signed-off-by: Thang Nguyen <thangnguyenvn647@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: wenjinhust <wenjin.sh@huawei.com>
…-project#62670)

Signed-off-by: Juntian Liu <Juntianl777@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…project#61949)

Signed-off-by: Ao Shen <aoshen@inferact.ai>
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Bugen Zhao <i@bugenzhao.com>
…llm-project#62672)

Signed-off-by: khluu <khluu000@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…55485)

Signed-off-by: Satvik Shrivas <44926681+theSatvik@users.noreply.github.com>
Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
The encoder prefill attention pads the head-dim tile to
next_power_of_2(head_dim), so a vision tower with a non-power-of-2 head dim
runs the qk/pv dots over padding lanes: SigLIP and Qwen3-VL have head_dim=72,
which contracts over 128 lanes when only 5 K-passes of 16 are needed.

Cover the head dim as a power-of-2 main block plus a power-of-2 tail block
(72 -> 64 + 16 = 80) behind a new constexpr BLOCK_DMODEL_TAIL. A tail of 0 is
dead code and reproduces the single-block kernel exactly, so every other
architecture and every power-of-2 head dim is byte-identical.

The split is a strict reduction in dot extent and ought to carry elsewhere,
but the extra dot and loads it costs have only been shown to pay for
themselves on gfx1151, so that is where it is enabled. It is also skipped
where it would not narrow the extent: 112 would split into 64 + 64, covering
the same 128 lanes for 12% more time.

context_attention_fwd at B=1, S=3520, H=16, bf16, non-causal on gfx1151,
median of 3 interleaved passes:

    head_dim 72:  4.842 -> 2.524 ms/call  (1.92x)
    head_dim 80:  3.511 -> 2.938 ms/call  (1.20x)
    head_dim 96:  3.578 -> 3.222 ms/call  (1.11x)

head_dim 64, 128 and 256 are flat to within 1%, as they must be.

End to end with a 1920x1080 image, TTFT: Gemma3-4B-w4a16 (SigLIP, head_dim
72) 442.2 -> 358.2 ms, Qwen2.5-VL-7B (head_dim 80) 1976.7 -> 1948.5 ms.

The existing correctness tests only covered head_dim 128; they now also run
64, 72, 80 and 96, which between them exercise both tail widths the kernel
can emit. The launch configuration is asserted through the capture harness
vllm-project#58225 added, so the gfx1151 branch is covered on CI agents that are not
gfx1151.

AI assistance (Claude) was used for this change.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
vllm-project#58225 caps BLOCK_N at 32 on RDNA3/RDNA4. The head dims that split into a
64-wide main block and a 16-wide tail -- 72 for SigLIP and Qwen3-VL, 80 for
Qwen2.5-VL -- want a KV tile narrower still, 16 wide with 4 warps rather
than 8.

Two things are needed for that, and only one of them is about the tile.

First, the head-stride alignment hint. In the packed [seq, heads, dim] layout
the head stride equals head_dim, and Triton's integer-argument
auto-specialization only attaches tt.divisibility=8 when that is a multiple
of 16. At head_dim=72 it is 8 mod 16, so AxisInfo treats the D-contiguous
Q/K/V loads as 2-byte aligned and Coalesce emits scalar loads.
tl.multiple_of(off_h_*, 8) on the head-axis offsets restores them. The hint
is set from the real runtime strides of Q/K/V/O rather than from head_dim,
so it stays sound for non-contiguous views, and it is not tied to the tile:
at every head dim whose stride is already 16-byte aligned it is a tautology
and measures exactly neutral. context_attention_fwd at B=1, S=3520, H=16,
bf16, non-causal on gfx1151, each head dim on the tile it is given, median
of 4 interleaved passes:

    head_dim  64:   1.814 -> 1.813   1.00x
    head_dim  72:   2.887 -> 2.302   1.25x
    head_dim  80:   2.322 -> 2.325   1.00x
    head_dim  96:   3.204 -> 3.203   1.00x
    head_dim 128:   3.871 -> 3.885   1.00x
    head_dim 256:  13.556 -> 13.563  1.00x

Second, the tile itself, which does depend on the hint. With the loads
vectorized, BLOCK_N=16 and 4 warps are worth 1.25x at both 72 and 80:

    head_dim 72:  2.895 -> 2.293  (1.26x)
    head_dim 80:  2.924 -> 2.331  (1.25x)

Without it they are not: at head_dim=72 with scalar loads the narrow tile
runs 2.676 ms against 2.449 for the tile vllm-project#58225 picks, 9% worse. So the
narrow tile is taken only when the hint is active. A head dim of 72 in a
view whose head stride is padded hits exactly that case.

Head dims outside the 64 + 16 split keep vllm-project#58225's tile. Sweeping BLOCK_N x
num_warps confirms it is the right one for them: 64 wants 32/4, and 96, 128
and 256 want 32/8, where BLOCK_N=16 is 22% to 45% slower even with the warp
count it prefers.

End to end with a 1920x1080 image, TTFT: Gemma3-4B-w4a16 362.2 -> 350.8 ms,
Qwen2.5-VL-7B 1934.4 -> 1907.1 ms.

AI assistance (Claude) was used for this change.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
@roberteg16
roberteg16 force-pushed the rogarcia.upstream-encoder-prefill-attn branch from 6543d61 to 69ed334 Compare October 20, 2026 12:16
@roberteg16 roberteg16 closed this Oct 23, 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.