Enable GQA for KV-shared layers and fix dtype issues in vision and audio - #279
Conversation
…ix audio SkipNorm fusion
There was a problem hiding this comment.
Pull request overview
This PR updates Mobius’s Gemma4 multimodal export path to improve inference performance and correctness across text/vision/audio by expanding GroupQueryAttention usage, correcting vision input dtype handling, tightening a SkipNorm fusion rule, and changing ORT-GenAI config defaults.
Changes:
- Gemma4 KV-shared decoder layers can now use
com.microsoft.GroupQueryAttentionby wiring shared KV aspast_key/past_valueand passing empty K/V. - Gemma4 vision encoder now declares
pixel_valuesasFLOAT(fp32) and casts internally to the model dtype to avoid NaNs from host-side dtype reinterpretation. - SkipNorm fusion rule now rejects Add+broadcast patterns by checking Add input ranks; ORT-GenAI config default enables
past_present_share_buffer.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/mobius/tasks/_gemma4.py | Makes Gemma4 vision encoder input pixel_values fp32 and casts to model dtype inside the graph. |
| src/mobius/rewrite_rules/_skip_norm.py | Adds a rank-equality guard to prevent invalid SkipNorm fusion when Add uses broadcasting (e.g., 1D bias). |
| src/mobius/models/gemma4.py | Enables GQA path for KV-shared layers by passing empty K/V and using shared present KV as past; simplifies fallback bias/pos-emb logic. |
| src/mobius/integrations/ort_genai/genai_config.py | Changes genai_config generation to always set past_present_share_buffer true (and apply max_length capping logic). |
Co-authored-by: Copilot <copilot@github.com>
Performance Comparison
|
|
The author of this PR, apsonawane, is not an activated member of this organization on Codecov. |
🏗️ Architecture Diff
No architecture changes detected. ✅ Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed) |
PR #279 sets do_rotary=1 for KV-shared GQA layers so RoPE is applied to queries. The source key already has RoPE baked in. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <justinchu@microsoft.com>
[Gemma4] Enable GQA for KV-shared layers, fix vision encoder dtype, fix audio SkipNorm fusion
Summary
Four changes to enable optimized Gemma4 multimodal inference:
GroupQueryAttentioninstead of standardAttentionfor the 20 KV-shared decoder layerspixel_valuesand cast inside the model to prevent NaNAdd(MatMul, 1D_bias) + RMSNormpast_present_share_buffer— avoid per-step KV cache allocation1. GQA for KV-shared layers (
gemma4.py)KV-shared layers borrow K/V from a source layer's
present_key/present_value(already in BNSH format). Instead of Transpose+Reshape into BSNH for the standardAttentionop, we now pass:[B, 0, kv_hidden](kv_sequence_length=0)past_key/past_valueThis eliminates Transpose+Reshape ops from the graph for all 20 shared layers and uses the optimized GQA flash attention kernel. Requires corresponding ORT changes to support
kv_sequence_length=0(separate ORT PR).Decoder loop change: All layers (shared and non-shared) now receive GQA context when the EP supports it. The previous
is_sharedexclusion and fallback attention bias/position embedding generation for shared layers are removed.2. Vision encoder float32 input (
_gemma4.py)Problem: The image processor in ORT-GenAI outputs float32
pixel_values, but the vision encoder declared its input asconfig.dtype(float16). The ORT-GenAI trimming path did a rawmemcpyfrom float32 source into an fp16 tensor using fp16 strides — reinterpreting float32 bit patterns as fp16, producing NaN.Fix: Declare
pixel_valuesasir.DataType.FLOATand addop.Cast(pixel_values, to=config.dtype)inside the ONNX model when needed. This keeps the host-side pipeline in float32 and lets the model handle the precision conversion safely.3. Audio encoder SkipNorm fix (
_skip_norm.py)Problem: The
AddRMSNormToSkipNormrewrite rule fusedAdd(MatMul_result, 1D_bias) + RMSNormintoSkipSimplifiedLayerNormalization. The 1D bias[hidden]became theskipinput, but the op requires both inputs to have the same shape ([B, S, hidden]). This caused:skip is expected to have 3 or 2 dimensions, got 1input and skip shall have same shape when enable_skip_layer_norm_strict_mode is TrueFix: Add a rank equality check in the
check()method. Fusion is skipped when the two Add inputs have different ranks (e.g., 3D tensor + 1D bias via broadcasting).4. Enable
past_present_share_buffer(genai_config.py)Set
past_present_share_buffertotrueby default for all EPs. This pre-allocates KV cache buffers and aliases past/present pointers, avoiding per-step allocation and past→present copies.Testing