Skip to content

Fix Gemma4 weight mapping for 26b MoE and 31b alternative attention - #239

Merged
justinchuby merged 5 commits into
mainfrom
gemma4-weight-fixes
May 4, 2026
Merged

Fix Gemma4 weight mapping for 26b MoE and 31b alternative attention#239
justinchuby merged 5 commits into
mainfrom
gemma4-weight-fixes

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Summary

Two weight mapping fixes for Gemma4 models that use attention_k_eq_v=True (26b-a4b, 31b):

Fix 1: Alternative attention (V=K) for full_attention layers

When attention_k_eq_v=True, full-attention layers derive V from K (no separate v_proj), matching HF Gemma4TextAttention. Changes:

  • Skip v_proj creation for alternative-attention layers
  • In forward: V = raw k_proj output (before k_norm/RoPE), then v_norm applied
  • Per-layer KV head count: full_attention uses num_global_key_value_heads
  • Update task KV cache inputs with per-layer head counts

Fix 2: MoE expert weight mapping in Gemma4Model

Gemma4Model (multimodal) had its own preprocess_weights that was missing:

  • Expert weight rename (experts.gate_up_projfc1_experts_weights)
  • Router scale folding (hidden_size^-0.5)

These were only in Gemma4CausalLMModel.preprocess_weights.

Testing

  • 12/12 Gemma4 L1 tests pass
  • 2662/2662 full suite pass
  • Verified: decoder exports successfully for both 26b-a4b and 31b models

Models affected

Model Issue Fixed
gemma-4-26b-a4b[-it] MoE expert weights + V=K
gemma-4-31b[-it] V=K alternative attention
gemma-4-e2b[-it] No issue (no k_eq_v) ✅ Unaffected
gemma-4-e4b[-it] No issue (no k_eq_v) ✅ Unaffected

justinchuby and others added 5 commits May 4, 2026 06:06
When attention_k_eq_v=True (26b-a4b, 31b models), full_attention layers
derive V from K (no separate v_proj), matching HF Gemma4TextAttention.

Changes:
- Add attention_k_eq_v and num_global_key_value_heads to Gemma4Config
- Skip v_proj creation for alternative-attention layers
- In forward: V = raw k_proj output (before k_norm/RoPE), then v_norm
- Per-layer KV head count: full_attention uses num_global_key_value_heads
- Update task KV cache to use per-layer head counts

Tests: 2680 passed (12 gemma4), lint clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Gemma4Model (multimodal) has its own preprocess_weights that was missing
the expert weight rename (gate_up_proj → fc1_experts_weights) and router
scale folding. These were only in Gemma4CausalLMModel.preprocess_weights.

Added both transformations to the multimodal model's preprocess_weights,
fixing 26b-a4b and 26b-a4b-it exports.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
The need_fallback check only considered KV-shared layers but not layers
where head_dim exceeds CUDA GQA's max (256). Full-attention layers with
global_head_dim=512 fell through to GQA fallback with an empty bias dict,
causing KeyError: 'full_attention'.

Fix: include head_dim > 256 in the need_fallback condition.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Latest ORT supports head_dim=512 in GroupQueryAttention. Remove the
fallback that routed full-attention layers (global_head_dim=512) to
standard Attention instead of GQA. All non-shared layers now use GQA
on CUDA EP regardless of head_dim.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Decouple two independent Gemma4 features:
- num_global_key_value_heads: controls KV head count for full-attention
  layers. Now used whenever set (not None), regardless of attention_k_eq_v.
- attention_k_eq_v: controls V=K sharing (no v_proj). Gated separately.

Previously both were coupled: num_global_key_value_heads only took effect
when attention_k_eq_v was True. This was incorrect — a model could have
different KV head counts for global/local layers without sharing V=K.

Changes:
- gemma4.py: Use num_global_key_value_heads for full-attention layers
  whenever it's not None
- _gemma4.py: Same decoupling in _make_gemma4_kv_cache_inputs
- build_graph_test.py: Add test with k_eq_v=True and
  num_global_key_value_heads != num_key_value_heads, verifying no v_proj
  and correct KV cache shapes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby
justinchuby requested review from a team and Copilot May 4, 2026 17:52
@justinchuby
justinchuby merged commit 353dcdf into main May 4, 2026
34 of 53 checks passed
@justinchuby
justinchuby deleted the gemma4-weight-fixes branch May 4, 2026 17:53
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 474f5b1698365f

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 60 60 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 66 66 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 107 107 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 53 53 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 61 61 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 58 58 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 98 98 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 59 59 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 56 56 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 61 61 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 58 58 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 275 275 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 129 129 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 408 408 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 166 166 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 474f5b1698365f

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision_encoder 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Gemma4-specific export issues in the model/config/task stack: it adds support for attention_k_eq_v full-attention layers and ports missing MoE weight remapping into the multimodal Gemma4 entry point.

Changes:

  • Added attention_k_eq_v to Gemma4Config and used it in Gemma4 attention construction/forward to omit v_proj and derive V from K on full-attention layers.
  • Updated Gemma4 KV-cache shape generation so full-attention layers can use num_global_key_value_heads.
  • Added multimodal Gemma4 MoE weight remapping/router-scale folding and a new Gemma4 build-graph regression test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
tests/build_graph_test.py Adds a Gemma4 graph test for attention_k_eq_v initializer layout and per-layer KV-cache head counts.
src/mobius/tasks/_gemma4.py Updates Gemma4 KV-cache input construction to use per-layer global KV head counts for full-attention layers.
src/mobius/models/gemma4.py Implements alternative attention (V=K) handling, changes GQA selection logic, and adds multimodal MoE weight remapping.
src/mobius/_configs.py Extends Gemma4Config and HF config extraction with the new attention_k_eq_v field.

Comment on lines 1552 to +1555
# Per-layer decision: use GQA for non-shared layers when
# available, fall back to standard Attention for KV-shared layers
# and layers where head_dim exceeds CUDA GQA MAX_HEAD_SIZE (256).
# available, fall back to standard Attention for KV-shared layers.
is_shared = layer.self_attn.is_kv_shared_layer
gqa_head_dim_ok = layer.self_attn.head_dim <= 256
if use_gqa and not is_shared and gqa_head_dim_ok:
if use_gqa and not is_shared:
Comment thread src/mobius/_configs.py
Comment on lines 1849 to +1850
enable_moe_block=getattr(config, "enable_moe_block", False),
attention_k_eq_v=getattr(config, "attention_k_eq_v", False),
Comment on lines +2153 to +2169
# Map HF expert weight names to our 3D stacked parameter names.
# HF: decoder.model.layers.N.experts.gate_up_proj [E, 2*inter, H]
# Us: decoder.model.layers.N.fc1_experts_weights [E, 2*inter, H]
for key in list(renamed.keys()):
if ".experts.gate_up_proj" in key:
new_key = key.replace(".experts.gate_up_proj", ".fc1_experts_weights")
renamed[new_key] = renamed.pop(key)
elif ".experts.down_proj" in key:
new_key = key.replace(".experts.down_proj", ".fc2_experts_weights")
renamed[new_key] = renamed.pop(key)

# Fold hidden_size^-0.5 into router.scale
if self.config.enable_moe_block:
scale_factor = float(self.config.hidden_size**-0.5)
for key in list(renamed.keys()):
if ".router.scale" in key and ".per_expert_scale" not in key:
renamed[key] = renamed[key] * scale_factor
Comment on lines +2153 to +2169
# Map HF expert weight names to our 3D stacked parameter names.
# HF: decoder.model.layers.N.experts.gate_up_proj [E, 2*inter, H]
# Us: decoder.model.layers.N.fc1_experts_weights [E, 2*inter, H]
for key in list(renamed.keys()):
if ".experts.gate_up_proj" in key:
new_key = key.replace(".experts.gate_up_proj", ".fc1_experts_weights")
renamed[new_key] = renamed.pop(key)
elif ".experts.down_proj" in key:
new_key = key.replace(".experts.down_proj", ".fc2_experts_weights")
renamed[new_key] = renamed.pop(key)

# Fold hidden_size^-0.5 into router.scale
if self.config.enable_moe_block:
scale_factor = float(self.config.hidden_size**-0.5)
for key in list(renamed.keys()):
if ".router.scale" in key and ".per_expert_scale" not in key:
renamed[key] = renamed[key] * scale_factor
justinchuby added a commit that referenced this pull request May 4, 2026
… tests (#240)

Addresses 4 review comments from PR #239:

### 1. GQA rewrite rule head_dim limit (#1)
Updated `_MAX_GQA_HEAD_DIM` from 256 to 512 in the Attention→GQA rewrite
rule to match latest ORT support for head_dim=512.

### 2. GGUF path missing `attention_k_eq_v` (#2)
Added `attention_k_eq_v=True` to Gemma4 GGUF postprocessor when
`num_global_key_value_heads` is detected. Fixes `build_from_gguf()` for
26b-a4b/31b checkpoints.

### 3. DRY expert weight rename (#3)
Extracted `_remap_moe_expert_weights()` shared helper used by both
`Gemma4CausalLMModel.preprocess_weights()` and
`Gemma4Model.preprocess_weights()`. Single source of truth for expert
rename + router scale folding.

### 4. Missing multimodal preprocess_weights test (#4)
Added `gemma4_test.py` with 5 targeted tests covering both
`Gemma4CausalLMModel` and `Gemma4Model` weight preprocessing: expert
rename, router scale folding, and per_expert_scale passthrough.

### Testing
- 5/5 new preprocess_weights tests pass
- 2668/2668 full suite pass

---------

Signed-off-by: Justin Chu <justinchu@microsoft.com>
Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request May 4, 2026
- Rebase onto latest main, dropping Gemma4 commits (already in PR #239)
- Document batch=1 limitation in FunASREmbeddingModel
- Document audio_token_id=0 collision workaround
- Add explicit shape-unknown comment in SkipLayerNorm rank guard

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
justinchuby added a commit that referenced this pull request May 4, 2026
- Rebase onto latest main, dropping Gemma4 commits (already in PR #239)
- Document batch=1 limitation in FunASREmbeddingModel
- Document audio_token_id=0 collision workaround
- Add explicit shape-unknown comment in SkipLayerNorm rank guard

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
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.

2 participants