Repository navigation
[Model] Gemma 4: native packed vision tower on MMEncoderAttention - #1415
Draft
roberteg16 wants to merge 1 commit into
Draft
roberteg16 wants to merge 1 commit into
roberteg16 wants to merge 1 commit into
Conversation
roberteg16
force-pushed
the
rogarcia.gemma4-native-vision
branch
7 times, most recently
from
October 30, 2026 09:08
d94b1ad to
b5cd8b7
Compare
roberteg16
changed the base branch from
rogarcia.upstream-main-3c39dda
to
rogarcia.mm-encoder-graph-infra
October 30, 2026 09:29
roberteg16
force-pushed
the
rogarcia.gemma4-native-vision
branch
from
October 30, 2026 11:37
b5cd8b7 to
33db17f
Compare
roberteg16
force-pushed
the
rogarcia.mm-encoder-graph-infra
branch
2 times, most recently
from
November 1, 2026 10:02
3296c7e to
84ee5f6
Compare
roberteg16
force-pushed
the
rogarcia.gemma4-native-vision
branch
from
November 1, 2026 10:02
33db17f to
b37ab27
Compare
Replace the transformers vision tower (padded images, SDPA with a mask) with a native one built from vLLM layers that packs all images or video frames of a batch into one sequence: cu_seqlens instead of padding, MMEncoderAttention (any --mm-encoder-attn-backend), the axial 2D RoPE from get_rope applied with ApplyRotaryEmb, and moonvit's packed patch merger for pooling. Parameter names match transformers, so checkpoints load without a mapper. Encoder CUDA graphs move to token budgets (as in Qwen3-VL): eager, capture and replay share prepare_encoder_metadata and the tower forward. Sequence slots and max_seqlen are sized from what a budget can hold and the configured max_soft_tokens; other items are marked eager_only. With TORCH_SDPA, capture falls back to eager (needs the encoder CUDA graph eager fallback for packed TORCH_SDPA). DiffusionGemma and EmbeddingGemma2 reuse the same tower; Gemma 4 Unified moves the (now host-side) positions to the device and opts out of encoder graphs. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Robert Esclapez Garcia <robert.garcia@amd.com>
roberteg16
force-pushed
the
rogarcia.mm-encoder-graph-infra
branch
from
November 1, 2026 10:02
84ee5f6 to
291a2fd
Compare
roberteg16
force-pushed
the
rogarcia.gemma4-native-vision
branch
from
November 1, 2026 10:02
b37ab27 to
768f58c
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.
Gemma 4's vision tower becomes a native vLLM module that packs the images (or video frames) of a batch into one sequence, so it runs on any
--mm-encoder-attn-backendand its encoder CUDA graphs follow token budgets like Qwen3-VL. Draft for review before proposing it upstream. Depends on #1417 (encoder CUDA graphs: eager fallback for packedTORCH_SDPA), which is this PR's base branch, so the diff shows only the Gemma 4 change.Why
Today the tower is transformers'
Gemma4VisionModelwith vLLM linears (vllm-project#43798): every image is padded tomax_soft_tokens * 9patches, attention is SDPA with a padding mask (it ignores--mm-encoder-attn-backendand never reaches FlashAttention), the pooler builds aone_hotover the padded batch, and encoder CUDA graphs capture padded rows per row width (vllm-project#46837, vllm-project#61365). Earlier attempts (vllm-project#43440, vllm-project#45574, issue vllm-project#43178) stopped at the padding; the review asked forMMEncoderAttentionandApplyRotaryEmb, which need the packed layout.Changes
gemma4_vision.py(new): built from vLLM layers:MMEncoderAttentionwithcu_seqlens,ReplicatedLinear,RMSNorm,get_act_fn, the axial 2D RoPE fromget_rope(head_dim / 2 rotary dims per axis) applied withApplyRotaryEmbon each half of the head (tables gathered once per forward), and moonvit's_build_merge_gather_idx/patch_merger_packedfor the 3x3 pooling (averaged in fp32 like transformers). It validates that each image's patches form a row-major grid with sides that are multiples of 3, and refuses FlashInfer with GQA (one offset stride for q, k and v;maybe_recompute_cu_seqlensgains an explicitseparate_qkvflag for that layout, which FP8 already used). Gemma 4's clipped linears areReplicatedLinearplus the checkpoint's clamp bounds, keeping the.linearnesting. The MLP keeps transformers' tanh GELU with vLLM'sGELUTanh(get_act_fnswaps it for the exact one on ROCm, for torch.compile, which this tower does not use). Parameter names match transformers: checkpoints and quantized towers load without a mapper.pixel_position_idsstay on the CPU (keep_on_cpu=True).prepare_encoder_metadatabuilds packed positions,cu_seqlens,max_seqlen, FlashInfer'ssequence_lengthsand the pooling indices without reading the GPU; the eager path, capture and replay share it and the same tower forward.gemma4_mm.py: images and video frames are encoded in one packed pass and split per item. Gone: resolution buckets, the memory-based_encoder_chunk(SDPA now bounds its memory itself, [Multimodal] Encoder CUDA graphs: eager fallback for packed TORCH_SDPA #1417), the LoRA padding path and the row-width graph axis. Vision LoRA counts are9 x soft tokens, the patches the tower actually encodes, so the MM LoRA padding (and its per-image patch limit) is gone.budget * 9patches. The attention grid launches work for every sequence slot and up tomax_seqlen, so both are sized from what a replay can hold:budget // 35sequences (every image or frame has at least half of the smallestmax_soft_tokens; the manager does not bound a batch's frames) and the configuredmax_soft_tokens. Items outside those bounds (a per-requestmax_soft_tokensoverride) are markedeager_only([Multimodal] Encoder CUDA graphs: eager fallback for packed TORCH_SDPA #1417) and run eager. WithTORCH_SDPA(gfx1151's default), capture falls back to eager through [Multimodal] Encoder CUDA graphs: eager fallback for packed TORCH_SDPA #1417. Capture splits its dummy input into images of at most that size. Packed pixel values (eager and graph buffers) are in the model dtype.cudagraph_mm_encoderit used to fail at startup).Design notes
MergedColumnParallelLinear+GeluAndMul,QKVParallelLinear).RMSNorm: vLLM's casts to the model dtype before applying the weight, transformers' multiplies in fp32. The fp32 parity test cannot see that; the bf16 MMStar comparison below covers it.Results (gfx1151, Strix Halo)
TTFT, one image per request, median of 15 (ms, 336x336 / 1024x800). Main runs SDPA with a mask regardless of the backend flag; gfx1151's default ViT backend is
TORCH_SDPA, so "this PR, SDPA" is the like-for-like default.On gfx1151 the tower is compute bound (~200 ms per image on 26B), so padding a batch up to its budget costs more than the launches a graph saves; sizing the sequence slots and
max_seqlenas above took 26B's graph TTFT from 464.8 to 439.3 ms. Graphs are expected to pay on GPUs where the tower is fast and launch-bound (not measured here: no CUDA hardware).MMStar (the evaluation for this numerics change;
tests/evalsonly has text GSM8K for Gemma 4), first 300 questions, greedy,mainvs this PR (default backend, SDPA): same option letter in 293 (E2B), 296 (E4B), 293 (26B AWQ), 300 (31B AWQ) of 300; correct answers main / PR: E2B 68 / 67, E4B 159 / 160, 26B AWQ 54 / 55, 31B AWQ 106 / 106 (8-token answers). An earlier revision (exact GELU) also gave 300/300 on 31B AWQ and, with 64-token answers, E2B 112 / 117, E4B 166 / 164, 26B AWQ 177 / 177, 31B AWQ 189 / 190.Encoder graph replay vs eager on E2B (
TRITON_ATTN; mixed-size images, two videos, an image above the largest budget that falls back to eager): same shapes and per-item token counts, max difference 0.34 on values up to 6.4, the same as eager's own variance when an image is encoded alone or next to another (0.16).Tests
pytest tests/models/multimodal/test_gemma4_vision.py(new; tower vs transformers' padded tower, fp32, SDPA and Triton, with and without clipped linears, rtol/atol 1e-3): 4 passed.pytest tests/models/multimodal/processing/test_gemma4.py tests/models/multimodal/processing/test_gemma4_unified.py: passed (graph tests rewritten for token budgets;_encoder_chunktests removed with the function).pytest tests/models/multimodal/pooling/test_embedding_gemma2.py -k contract: passed (borrowed methods updated).pytest tests/models/multimodal/generation/test_common.py -k "gemma4 or qwen2_5_vl": 7 passed (Qwen2.5-VL runs SDPA with hostcu_seqlenson gfx1151).pytest tests/models/multimodal/generation/test_vit_cudagraph.py -k "gemma4 or qwen2_5_vl": 4 passed with gfx1151's default SDPA (capture falls back to eager with a warning); Gemma 4 also passed withTRITON_ATTN(graphs) on an earlier revision. Gemma 4 E4B with SDPA andcudagraph_mm_encoder: 196.3 / 204.6 ms TTFT.TRITON_ATTN(script above) on this revision: same shapes and token counts, an image above the configuredmax_soft_tokensruns eager, and a 12-frame video replays withencoder_cudagraph_max_frames_per_batch=8.pytest tests/lora/test_gemma4_tp.py::test_gemma4_lora: passed on main and on this PR (teardown error:raynot installed).Not covered / open points
maybe_recompute_cu_seqlens(fp8_padded_hidden_size=...), which today only distinguishes the fused-QKV and FP8 layouts.ApplyRotaryEmbcalls on gfx1151) could move tolayers/rotary_embedding/in a follow-up. This PR supersedes [Perf][Model] Gemma 4: remove trailing image padding to enable FlashAttention in vision SDPA vllm-project/vllm#60577 (strip trailing padding). Their authors and issue [Feature]: Port Gemma4 vision encoder to MMEncoderAttention with FlashAttention support vllm-project/vllm#43178 are to be contacted before going upstream.No open PR moves Gemma 4's vision tower to
MMEncoderAttention(checked vllm-project#43440, vllm-project#45574, vllm-project#60478, vllm-project#60577, vllm-project#61365). AI-assisted (Claude Code); the submitter reviews every line.🤖 Generated with Claude Code