Skip to content

Add Gemma4 GGUF support with per-layer KV heads fix - #234

Merged
justinchuby merged 4 commits into
mainfrom
gguf-gemma4-support
May 4, 2026
Merged

Add Gemma4 GGUF support with per-layer KV heads fix#234
justinchuby merged 4 commits into
mainfrom
gguf-gemma4-support

Conversation

@justinchuby

Copy link
Copy Markdown
Member

Summary

Add GGUF support for Gemma4 (text-only) and fix a pre-existing bug where full-attention layers used wrong KV head counts.

Changes

1. GGUF model_type mapping fix (1-line)

  • gemma4gemma4_text in GGUF_ARCH_TO_MODEL_TYPE — GGUF only has text weights, not multimodal. Follows the gemma3 → gemma3_text pattern.

2. Gemma4 GGUF config extraction

  • Added _gemma4_postprocess() in _config_mapping.py — extracts dual head_dim, dual RoPE theta, layer_types, sliding_window, softcapping, KV sharing, and per-layer-input gating from GGUF metadata.
  • Handles non-standard unsloth SWA keys (key_length_swa, freq_base_swa) with warnings when missing.
  • Extracts num_global_key_value_heads from per-layer KV head arrays.

3. Per-layer KV heads bug fix

  • Bug: Gemma4 full-attention layers used num_key_value_heads=16 (sliding) instead of num_global_key_value_heads=4 (full-attention).
  • Fix: Updated Gemma4TextAttention, Gemma4Config, Gemma4TextCausalLMTask KV cache, and GGUF config extraction.

4. Tests

  • 15 new unit tests for Gemma4 GGUF config extraction (synthetic GGUF writer)
  • 1 e2e test for build_from_gguf() with synthetic GGUF
  • All existing tests pass (2678 passed)

Verified

  • Real GGUF build: unsloth/gemma-4-31B-it-UD-IQ2_XXS.gguf → 833 tensors, 1962 nodes, SUCCESS
  • build_from_gguf() correctly produces single text-only model (not multimodal)
  • Config extraction matches HF config exactly

justinchuby and others added 2 commits May 2, 2026 00:29
Fix the GGUF architecture mapping so gemma4 GGUF files build using the
text-only task (gemma4_text) instead of the multimodal task, matching
the gemma3 pattern. GGUF files only contain the text backbone weights.

Add Gemma4-specific config postprocessing to extract dual-regime
fields from GGUF metadata:
- Dual head_dim: key_length_swa (sliding) vs key_length (global)
- Dual RoPE theta: freq_base_swa (sliding) vs freq_base (global)
- Layer types from sliding_window_pattern bool array
- Sliding window, softcapping, KV sharing, per-layer input gating
- Per-layer KV head arrays collapsed to majority value

Returns Gemma4Config (not plain ArchitectureConfig) so the model
receives all architecture-specific fields.

Tests:
- 13 unit tests for Gemma4 config mapping (dual head_dim, dual RoPE,
  layer types, softcapping, KV heads, per-layer input, etc.)
- 1 end-to-end test: build_from_gguf with synthetic Gemma4 GGUF file

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Gemma4 31B uses different KV head counts per layer type: 16 for
sliding-window layers and 4 for full-attention layers (HF config:
num_global_key_value_heads=4, attention_k_eq_v=True).

The model was previously using a uniform num_key_value_heads=16 for
all layers, causing k_proj/v_proj shape mismatches on full-attention
layers: expected [8192, 5376] but weights are [2048, 5376].

Changes:
- Add num_global_key_value_heads field to Gemma4Config
- Extract from HF config (attention_k_eq_v + num_global_key_value_heads)
- Extract from GGUF per-layer KV head array (minority value)
- Use per-layer-type KV heads in Gemma4TextAttention and KV cache
- Verified end-to-end: build_from_gguf('unsloth/gemma-4-31B-it-GGUF')
  succeeds with 833 tensors loaded, 1962 graph nodes

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

When a GGUF file lacks the non-standard key_length_swa and
freq_base_swa keys (unsloth extensions), emit a warning so users
know the sliding-window head_dim and rope_theta may be incorrect.

Also add a comment citing the HF Gemma4Config source for the
hardcoded global_partial_rotary_factor = 0.25 default.

Signed-off-by: Justin Chu <justinchu@microsoft.com>
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 4e70d8d37c46b4

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.

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

Adds GGUF import support for Gemma4 text-only models and fixes Gemma4’s per-layer KV head handling so full-attention layers use the correct (global) KV head count.

Changes:

  • Remap GGUF gemma4 architecture to gemma4_text and add Gemma4-specific GGUF → config postprocessing.
  • Fix per-layer KV head selection for full-attention layers across Gemma4 attention modules and KV-cache task inputs.
  • Add synthetic GGUF-based unit/e2e tests for Gemma4 config extraction and build_from_gguf().

Reviewed changes

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

Show a summary per file
File Description
src/mobius/tasks/_gemma4.py Use num_global_key_value_heads for full-attention KV-cache input shapes.
src/mobius/models/gemma4.py Set per-layer num_key_value_heads based on layer type to match HF Gemma4 semantics.
src/mobius/integrations/gguf/_config_mapping.py Map gemma4 → gemma4_text, collapse per-layer KV head arrays, and add Gemma4 postprocessor to build Gemma4Config.
src/mobius/integrations/gguf/_reader_test.py Add synthetic Gemma4 GGUF writer + tests for Gemma4 config mapping and build_from_gguf().
src/mobius/_configs.py Add num_global_key_value_heads to Gemma4Config and populate it from HF configs when applicable.

Comment thread src/mobius/integrations/gguf/_config_mapping.py
Comment thread src/mobius/integrations/gguf/_config_mapping.py
Comment thread src/mobius/models/gemma4.py
@codecov

codecov Bot commented May 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.19608% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mobius/integrations/gguf/_config_mapping.py 73.58% 6 Missing and 8 partials ⚠️
src/mobius/models/gemma4.py 42.85% 2 Missing and 2 partials ⚠️
src/mobius/_configs.py 50.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 4e70d8d37c46b4

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)

Address PR review comments:
- Validate sliding_window_pattern length matches num_hidden_layers
- Validate head_count_kv array length matches sliding_window_pattern
- Add layer_types length check in Gemma4TextDecoderLayer.__init__

Clear ValueError messages on malformed GGUF metadata instead of
cryptic IndexError at runtime.

Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby
justinchuby merged commit 0fc75da into main May 4, 2026
20 of 23 checks passed
@justinchuby
justinchuby deleted the gguf-gemma4-support branch May 4, 2026 05:39
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