Skip to content

Fix vision encoder for 26b/31b: respect use_clipped_linears config - #277

Merged
justinchuby merged 3 commits into
mainfrom
fix-clippable-vision
May 6, 2026
Merged

Fix vision encoder for 26b/31b: respect use_clipped_linears config#277
justinchuby merged 3 commits into
mainfrom
fix-clippable-vision

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Problem

Gemma4 26b-A4B and 31b models fail to export with 756 missing weight errors:

ValueError: Component 'vision_encoder' has 756 initializer(s) without weights:
'vision_encoder.encoder.layers.0.self_attn.q_proj.input_min', ...

Root Cause

The vision encoder always used ClippableLinear for all projections, which creates input_min/input_max/output_min/output_max parameters. These QAT (quantization-aware training) clipping bounds exist in the HF checkpoint for e2b/e4b models but not for 26b/31b.

The HF config has use_clipped_linears:

  • e2b-it: True (224 vision clipping weights in checkpoint)
  • 26b-a4b: False (0 clipping weights)
  • 31b: False (0 clipping weights)

Our VisionConfig already reads this field, but the model code ignored it.

Fix

Gemma4VisionSelfAttention and Gemma4VisionEncoderLayer now accept use_clipped_linears and select ClippableLinear vs Linear accordingly. The flag is passed from VisionConfig through the encoder core.

Testing

  • ✅ All 1204 tests pass
  • mobius build --model google/gemma-4-26b-a4b exports all 3 submodels successfully
  • ✅ e2b/e4b models unaffected (still use ClippableLinear when True)

Gemma4 26b-A4B and 31b models set use_clipped_linears=False in their
vision config, meaning they don't use quantization-aware clipping
bounds. Previously, the vision encoder always used ClippableLinear
(which creates input_min/max/output_min/max parameters), causing 756
missing weight errors when loading these models.

Now Gemma4VisionSelfAttention and Gemma4VisionEncoderLayer accept a
use_clipped_linears parameter that selects between ClippableLinear
(e2b/e4b models with QAT) and standard Linear (26b/31b without QAT).
The flag is read from VisionConfig.use_clipped_linears which is
already populated from the HF config.

Tested with google/gemma-4-26b-a4b: all 3 submodels (decoder,
vision_encoder, embedding) now export and save successfully.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing e5eb966fb26c23

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.

@codecov

codecov Bot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing e5eb966fb26c23

Model Sub-model Changes Status
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 52 🟡
gemma4_text model 0
gemma4 (gemma4) / vision_encoder — 52 change(s)

Op summary: 190 → 176 nodes

--- base
+++ head
@@ -33,18 +33,12 @@
 Where
 Unsqueeze
 RMSNormalization
-Clip
 Transpose
 MatMul
-Clip
-Clip
 Transpose
 MatMul
-Clip
-Clip
 Transpose
 MatMul
-Clip
 Reshape
 Reshape
 Reshape
@@ -112,27 +106,19 @@
 MatMul
 Transpose
 Reshape
-Clip
 Transpose
 MatMul
-Clip
 RMSNormalization
 Add
 RMSNormalization
-Clip
 Transpose
 MatMul
-Clip
 Gelu
-Clip
 Transpose
 MatMul
-Clip
 Mul
-Clip
 Transpose
 MatMul
-Clip
 RMSNormalization
 Add
 Unsqueeze

Removed nodes:

  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip
  • - Clip

Modified attributes:

  • node[59] Gather: axis: 2 → 0
  • node[144] Constant: value_int: 1 → None, value_ints: None → [1]
  • node[156] Constant: value_int: 0 → None, value_ints: None → [0, 1]
  • node[162] Constant: value_floats: None → [0.0, 0.1111111111111111], value_ints: [1] → None

Connectivity changes:

  • node[1] CastLike: input_ids [61, 0] → [33, 0]
  • node[3] CastLike: input_ids [63, 0] → [35, 0]
  • node[4] Mul: input_ids [0, 62] → [0, 34]
  • node[5] Sub: input_ids [65, 64] → [37, 36]
  • node[7] MatMul: input_ids [66, 67] → [38, 39]
  • node[9] Gather: input_ids [1, 69] → [1, 41]
  • node[11] Equal: input_ids [70, 71] → [42, 43]
  • node[13] Clip: input_ids [1, 73] → [1, 45]
  • node[15] Gather: input_ids [74, 75] → [46, 47]
  • node[17] Gather: input_ids [74, 77] → [46, 49]
  • node[19] Gather: input_ids [2, 79] → [2, 51]
  • node[21] Gather: input_ids [2, 81] → [2, 53]
  • node[22] Gather: input_ids [80, 76] → [52, 48]
  • node[23] Gather: input_ids [82, 78] → [54, 50]
  • node[24] Add: input_ids [83, 84] → [55, 56]
  • node[25] CastLike: input_ids [4, 85] → [4, 57]
  • node[26] Not: input_ids [72] → [44]
  • node[27] Unsqueeze: input_ids [87, 5] → [59, 5]
  • node[28] Where: input_ids [88, 85, 86] → [60, 57, 58]
  • node[29] Add: input_ids [68, 89] → [40, 61]
  • node[30] CastLike: input_ids [6, 90] → [6, 62]
  • node[31] CastLike: input_ids [4, 90] → [4, 62]
  • node[32] Where: input_ids [72, 91, 92] → [44, 63, 64]
  • node[33] Unsqueeze: input_ids [93, 7] → [65, 7]
  • node[34] RMSNormalization: input_ids [90, 8] → [62, 8]
  • node[59] Gather: input_ids [1, 119] → [10, 88]
  • node[75] Slice: input_ids [131, 34, 31, 32] → [98, 18, 22, 20]
  • node[79] Mul: input_ids [138, 128] → [98, 95]
  • node[90] Slice: input_ids [133, 34, 31, 32] → [100, 18, 22, 20]
  • node[94] Mul: input_ids [153, 128] → [100, 95]
  • node[106] Transpose: input_ids [114] → [138]
  • node[161] Add: input_ids [220, 221] → [191, 193]
  • node[169] Mul: input_ids [228, 229] → [199, 201]

Initializer changes:

  • count 59 → 31; dtype distribution: FLOAT: 50 → 22

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

Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
@justinchuby
justinchuby merged commit 7e7bc03 into main May 6, 2026
2 checks passed
@justinchuby
justinchuby deleted the fix-clippable-vision branch May 6, 2026 21:20
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.

1 participant