Repository navigation
[ROCm][gfx1151] ROCM_RDNA_ATTN for multimodal encoders (non-causal) - #1400
Draft
roberteg16 wants to merge 7 commits into
Draft
roberteg16 wants to merge 7 commits into
roberteg16 wants to merge 7 commits into
Conversation
roberteg16
force-pushed
the
rogarcia.rdna-attn-non-causal-mm-encoder
branch
from
October 27, 2026 09:30
e9f385c to
98caa67
Compare
roberteg16
changed the base branch from
rogarcia.gfx11-decode-extra-shapes
to
gfx11
October 27, 2026 09:31
roberteg16
force-pushed
the
rogarcia.rdna-attn-non-causal-mm-encoder
branch
from
October 27, 2026 11:58
98caa67 to
e5ffe5f
Compare
…capture The profiler label's block-diagonal segment lengths (#1372) read cu_seqlens on the host whenever there is more than one segment, which fails encoder CUDA graph capture. They are skipped while capturing. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
Decode: - D=32: a lane's K and V slice is one dword (kLaneDims 2); the dot path keeps two dims per lane. - D=80 and D=48: a lane holds kLaneA consecutive dims and, when D / 16 is odd, one more: they run as 64 + 16 and 32 + 16, every store in natural dim order. The dot path takes the fewest dot2 pairs whose lanes per key divide the wave. - D=72 runs padded to 80: the odd lane slice has 8 real dims; the other lanes load a clamped dim and zero it, and its stores skip them. Prefill: load units of 4 elements where a sub-tile of 8-element units is not whole waves; an odd lane slice requires V_T. D=72: Q and the K tile hold zeros past D, outputs past D are skipped. No table entries: the encoder shapes' non-causal rows follow. tools/floor.py: the stream floor and per-context ceiling of a shape. tools/tune.py: the grid skips builds the kernel asserts refuse; lane slices and chunks of a padded head dim. gfx12: the odd lane slice and the padded chunk follow acc_row and its operand layout (half-wave 1 of D=72's last Q chunk is zeros); v_t 2 needs whole 16-dim chunks of 8-element units, so not D=48 or D=72. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
Prefill builds with causal=0: every query token sees every key of its sequence (an encoder's segments, L = S). Each is a template build of its own, so causal builds generate the same code (verified ISA, symbol names aside). - rdna_encoder_attn: Q, K and V read in place, (tokens, heads, D) with any token stride; one sequence, or a batch through plan_prefill with query_start_loc as each segment's start. No KV cache, no block table. - K and V through buffer loads of 16-byte units of the D real dims, one descriptor per step ending at the last key (later keys load as zeros). - row_tiles=2 (non-causal): two 16-row tiles per wave share every K and V operand read from LDS. - PrefillConfig gains causal and row_tiles (entries leave them out when 1); the batch cells match rows by causal. - tune.py --prefill [--prefill-batch] --non-causal, with its own row steps and roofline (utils.roofline_noncausal). gfx12 builds the non-causal path with v_t 0 or 1 (v_t 2 reads V from the paged cache); row_tiles 2 is gfx11 only for now. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
D=32: 4/4, 8/8, 16/16, 32/32; D=48: 32/32; D=64: 4/4, 6/6, 8/8, 12/12, 14/14, 16/16, 20/20, 24/24, 64/64; D=72 and D=80: 16/16; D=128: 4/4, 8/8, 12/12, 16/16, 24/24. tune.py --prefill --non-causal and --prefill-batch --non-causal, bf16 (rows written for fp16 too), gfx1151's tables only: the mm-encoder backend runs on gfx1151. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
--mm-encoder-attn-backend ROCM_RDNA_ATTN on gfx1151 (opt-in; the default does not change). rocm_rdna_encoder runs all of an encoder's segments in one rdna_encoder_attn launch on Q, K and V in place: - one segment: the single-sequence non-causal row for its length; - several: the batch cell for the lengths and a plan, cached per list; - the lengths come from the host arrays maybe_recompute_cu_seqlens is given, else are read once per cu_seqlens tensor; - calls the kernels cannot serve (dtype, head size, GQA, missing builds) run on TRITON_ATTN. The launches follow the host's segment lengths, so cudagraph_mm_encoder is refused at startup with this backend. shapes.csv marks each shape decoder and/or encoder (the 21 encoder shapes added), and matrix.py times the encoder shapes with --non-causal. Qwen2.5-VL and Qwen3-VL accept the backend. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
Gemma 4's towers come from transformers, which call ALL_ATTENTION_FUNCTIONS, so --mm-encoder-attn-backend did not reach the vision tower: it ran SDPA with the padding mask. Each of its attention modules now gets an MMEncoderAttention, and a registered transformers attention function runs the encoder calls on it with each image's patches, then its padding (trailing, as the processor pads), as segments: cu_seqlens made on the device from the mask, in eager mode and in the encoder CUDA graph. Without segments, SDPA as before. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
MiniCPM-V/o pass the tower a mask when slices differ in patch count, which took MMEncoderAttention off its backend onto SDPA with an additive mask. Their masks keep each slice's patches first, so the tower now gives the backend each slice's patches, then its padding, as segments: cu_seqlens made on the device, which the encoder CUDA graph keeps too. TORCH_SDPA and FLASHINFER keep the mask. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
roberteg16
force-pushed
the
rogarcia.rdna-attn-non-causal-mm-encoder
branch
from
October 28, 2026 19:03
e5ffe5f to
8514d7c
Compare
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.
ROCM_RDNA_ATTNfor the multimodal encoders on gfx1151: a non-causal build of the prefill kernel behind--mm-encoder-attn-backend ROCM_RDNA_ATTN(opt-in; the default does not change). Gemma 4's and Idefics2's vision towers now reach the selected mm-encoder backend instead of SDPA with a mask, which also helpsTRITON_ATTN.Changes
TRITON_ATTN mm-encoder wrapper: no host read under CUDA graph capture. The profiler label's segment lengths ([ROCm] Emit block-diagonal segment lengths in attention profiler scope #1372) read
cu_seqlenson the host and broke encoder CUDA graph capture with more than one segment: todaycudagraph_mm_encoderfails on Qwen3-VL and Gemma 3, and commits 6 and 7 would extend it to Gemma 4 and MiniCPM-V.Head dims 32, 48, 72 and 80 in the decode and prefill kernels (D=72 padded to 80). They compile for gfx1151 and gfx1201; on gfx12,
v_t2 does not take D=48 or D=72. No causal rows.Non-causal prefill builds:
PrefillConfig.causal = 0. Each token sees every key of its segment. Q, K and V are read in place through token strides (a fused qkv needs no copy); K/V buffer loads end at the segment's last key.row_tiles = 2lets a wave run two 16-row tiles sharing each K/V operand read (non-causal, gfx11 only). New oprdna_encoder_attn.Non-causal rows and batch cells for 21 encoder shapes, gfx1151's tables only (the encoder backend is offered on gfx1151):
MMEncoderAttention: all of a call's segments in one launch, planned on the host. Calls the kernels cannot serve (dtype, head size, GQA, missing builds) run onTRITON_ATTN. The launches follow the host's segment lengths, socudagraph_mm_encoderwith this backend is refused at startup. Qwen2.5-VL and Qwen3-VL accept the backend.Gemma 4's vision tower on the mm-encoder backend: its towers come from transformers, so
--mm-encoder-attn-backendnever reached them. Each attention module gets anMMEncoderAttentionbehind a registered transformers attention function, given each image's patches, then its padding, as segments.Idefics2's vision tower (MiniCPM-V/o): padded slices become segments instead of an additive mask that sent attention to SDPA.
TORCH_SDPAandFLASHINFERkeep the mask.Results
TTFT of one-image requests on gfx1151 (Strix Halo), median of 12, a new random image per request, encoder without CUDA graphs. "Today" is what runs on
gfx11withTRITON_ATTN, the default on gfx1151 withoutflash_attn: SDPA with a mask for Gemma 4 and for MiniCPM-V-4 at 1024x800 (padded slices),TRITON_ATTNfor the rest.ROCM_RDNA_ATTN(ms)With
TRITON_ATTN, commits 6 and 7 alone take MiniCPM-V-4 at 1024x800 from 468.2 to 403.3 ms and Gemma 4 from 217.7 to 211.8 ms. At D=64TRITON_ATTNis close to these kernels (Qwen3-VL-4B's tower: 39.5 vs 37.3 ms of attention), so Qwen3-VL-4B does not move.Kernel, sustained back-to-back: 16/16/{64,72,80} reach 77–94 % of the roofline at L ≥ 1200; the 18 other shapes 78–101 % at L ≥ 2048 (
do_bench, which reads ~10–13 % above sustained). Shorter sequences are below 80 %.Accuracy
MMStar, first 300 questions,
TRITON_ATTNvsROCM_RDNA_ATTN, greedy, no fallbacks (measured on an earlier revision of the integration, same kernels):On this revision, the first 20 MMStar questions give the same letter as today's backend in 19–20 of 20 for each model in the results table. Qwen3-Omni's audio encoder (20/20/64) transcribes librispeech samples correctly.
Not covered
ROCM_RDNA_ATTN(refused at startup).MMEncoderAttention.Tests
pytest tests/kernels/attention/test_rocm_rdna_hip.py tests/kernels/attention/test_mha_attn.py tests/config/test_multimodal_config.pyon gfx1151: 704 passed, 16 skipped.No open PR covers encoder attention on gfx1151. AI-assisted (Claude Code); the submitter reviews every line.
🤖 Generated with Claude Code