Generalize Gemma 4 MoE Quark path to plain Quark/AWQ int4 experts - #2483
Open
Thiago Pereira Rocha (thpereir) wants to merge 6 commits into
Open
Generalize Gemma 4 MoE Quark path to plain Quark/AWQ int4 experts#2483Thiago Pereira Rocha (thpereir) wants to merge 6 commits into
Thiago Pereira Rocha (thpereir) wants to merge 6 commits into
Conversation
Adds Gemma4Model builder for the text component of Gemma4Unified (Gemma4UnifiedForConditionalGeneration). Verified against HuggingFace: full 48-layer fp32 parity gives logit correlation 1.0 and exact top-1 match. Handles the architecture's deviations from Gemma3: - Per-layer head dims (sliding 256 / full 512) and KV heads (8 / 1), emitted as concrete per-layer KV cache shapes so the runtime's DefaultKeyValueCache auto-detects the heterogeneous layout. - attention_k_eq_v: full-attention layers share the K projection as V (no v_proj), plus a scaleless value RMSNorm (v_norm) on all layers. - Proportional RoPE on full layers (partial rotary on the global head dim with a zero-padded NoPE tail), default RoPE on sliding layers. - Per-layer layer_scalar residual multiplier. - No-offset RMSNorm (weight used directly, unlike Gemma1/2/3). - Text-only weight loader that remaps the model.language_model.* prefix and avoids loading the vision/audio towers. Emits model_type "gemma4_text" to match the runtime's model type list. Co-Authored-By: Claude <noreply@anthropic.com>
Add Gemma4MoEModel subclassing the dense Gemma4Model. Every layer runs a parallel dense MLP and a fused-QMoE expert block, combined by Add. Uses the fused QMoE op path (gelu + swiglu_fusion=1 + normalize_routing_weights) with an explicit router pre-projection subgraph feeding raw logits, and folds per_expert_scale into the expert down_proj weights offline. Co-Authored-By: Claude <noreply@anthropic.com>
…nature) Co-Authored-By: Claude <noreply@anthropic.com>
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
…tub test - make_moe_init: set both moe_attrs["op_type"] and ["moe_op_type"] to the resolved op. The shared emitters read "op_type" while the gemma parallel-FFN path reads "moe_op_type"; setting only one left quantized experts on the float "MoE" op, which rejects uint8 weights. - Gemma4MoEModel.__init__: hide config.moe_intermediate_size across super().__init__ so the base sets self.intermediate_size to the dense size directly, instead of reassigning it afterward (CodeQL "overwriting attribute"). - test_qmoe_weights.py: add Gemma4Model/Gemma4MoEModel to the stubbed `builders` class list so the builder-CLI import test matches builder.py's imports. Co-Authored-By: Claude <noreply@anthropic.com>
Build the fused Quark uint2 gemma-4-26B-A4B-it checkpoint to ONNX with a pre-quantized expert path: consume group-wise uint2 weights/scales/float zero-points directly, fold the router per-expert scale into the down-proj scales, and emit the shared gate/up prescale+rotation once before QMoE. - builder.py: add int2 precision plumbing - quantized_model.py: extend the Quark loader/repack to uint2 (group_size 64, MSB-first packing) and to the split gemma4 experts with bundled dense LoRA - gemma.py: pre-quantized uint2 expert branch in make_moe; emit block_size=64; omit zero_points on CUDA int2 (op reconstructs the symmetric -1.5*scale bias); keep float zero-points on CPU and interleave fc1 gate/up rows for the CPU kernel's swiglu_fusion=1 layout
make_moe_quark was hardwired for the factored 2-bit LoRA-KD checkpoint: it unconditionally emitted the shared input prescale+rotation, re-interleaved fc1 rows from concat layout, and cast zero-points to the float io_dtype. Plain Quark/AWQ int4 checkpoints have no prescale/rotation, are already emitted interleaved by combine_and_repack_gate_up, and carry integer (uint8) zero-points. Guard all three on the checkpoint's actual shape: - emit prescale+rotation only when input_prescale is present - gate the concat->interleaved fc1 reorder on the 2-bit path - emit zero-points at native dtype unless they are float Verified end-to-end on amd/gemma-4-26B-A4B-it-uint4-awq (build + "Paris" sanity + PPL 35.13 on wikitext_gpt_oss_120b). Co-Authored-By: Claude <noreply@anthropic.com>
Thiago Pereira Rocha (thpereir)
force-pushed
the
gemma4-4bit-upstream-new
branch
from
August 27, 2026 21:08
ce33102 to
48d1449
Compare
Thiago Pereira Rocha (thpereir)
marked this pull request as ready for review
August 28, 2026 14:28
Thiago Pereira Rocha (thpereir)
requested a review
from a team
as a code owner
August 28, 2026 14:28
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Copilot started reviewing on behalf of
Thiago Pereira Rocha (thpereir)
August 28, 2026 14:29
View session
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the Gemma 4 text model builder to better support Gemma4 MoE and Quark pre-quantized expert checkpoints by (a) generalizing the make_moe_quark path to handle plain Quark/AWQ int4 experts in addition to the existing 2-bit path, and (b) adding builder/loader plumbing for int2/uint2 precision and related behaviors across the Python model-builder stack.
Changes:
- Generalize Gemma4 MoE pre-quantized Quark expert emission (input transform, fc1 reorder, zero-point dtype/omission) so the same path supports both uint2 (2-bit) and uint4/int4 (4-bit) expert layouts.
- Add int2/uint2 precision support across quant config, CLI precision selection, and Quark loader repack/unpack utilities (including split-expert uint2 handling and LoRA adapter baking for quantized loads).
- Add/extend unit tests for Gemma4 dense + MoE builder structure and for the new Quark int4 expert path expectations.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/python/models/test_gemma4moe_builder.py | Adds/extends Gemma4MoE builder structural tests plus new Quark int4 path assertions. |
| test/python/models/test_gemma4_builder.py | Adds dense Gemma4 builder tests (RoPE caches, per-layer KV geometry, attention scale/norm behaviors). |
| test/python/builder/test_qmoe_weights.py | Registers Gemma4Model/Gemma4MoEModel in the QMoE weight test’s builder CLI module loader. |
| src/python/py/models/README.md | Documents the new 2-bit (int2) pre-quantized MoE builder usage. |
| src/python/py/models/quantization/quant_config.py | Introduces int2/uint2 descriptors and maps --precision int2 to uint2 weight/MoE quant selection. |
| src/python/py/models/loaders/quark.py | Adds uint2 split-expert unpack/repack pipeline and generalizes packing/repack logic to 2-bit+4-bit. |
| src/python/py/models/loaders/base.py | Adds Quark factored-rotation metadata parsing, shared rotation loading, LoRA adapter attachment for quantized loads, and packed-dimension helpers. |
| src/python/py/models/builders/gemma.py | Adds Gemma4Model + Gemma4MoEModel builders and generalizes make_moe_quark for int4 experts. |
| src/python/py/models/builders/base.py | Threads precision through quant config init, aligns MoE op-type attrs, and adds factored-rotation + baked LoRA emission in matmul path. |
| src/python/py/models/builders/init.py | Exports Gemma4Model/Gemma4MoEModel from the builders package. |
| src/python/py/models/builder.py | Adds int2 CLI precision, threads true precision through extra_options, and wires Gemma4 architectures into create_model. |
Suppressed comments (1)
src/python/py/models/loaders/base.py:1073
_packed_out_factoris added as a@staticmethodwith a leading underscore in the model-builder code. Per the model-builder guidelines, avoid@staticmethodhelpers (and leading-underscore names); prefer an instance method (e.g.,packed_out_factor) or an existing shared utility so call sites remain easy to override/mock and follow the same conventions.
@staticmethod
def _packed_out_factor(tensor, bits):
"""Number of logical output channels stored per element along the packed
(column) axis: 8//bits for uint8 packing (Quark native uint2/uint4),
32//bits for int32 packing (AWQ/GPTQ style), and 1 for unpacked floating
tensors (per-group float scales / float zero-points)."""
if tensor.dtype == torch.uint8:
return 8 // bits
if tensor.dtype == torch.int32:
return 32 // bits
return 1
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+273
to
+274
| # Factored online rotation (Quark rotation algo): x_rot = (x / input_prescale) @ shared_input_rotation_<in>. | ||
| # `shared_input_rotations` maps in_features -> [in, in] rotation matrix (shared across all layers). |
Comment on lines
+211
to
+217
| @staticmethod | ||
| def _unpack_uint2_msb_first(packed): | ||
| """Unpack uint8 ``[rows, cols/4]`` (4x 2-bit codes per byte, MSB-first along | ||
| columns) into int codes ``[rows, cols]``.""" | ||
| shifts = torch.tensor([6, 4, 2, 0], dtype=torch.int32, device=packed.device) | ||
| codes = (packed.to(torch.int32).unsqueeze(-1) >> shifts.view(1, 1, -1)) & 0x3 | ||
| return codes.reshape(packed.shape[0], -1) |
Comment on lines
+830
to
+845
| def _load_lora_adapters(self): | ||
| """Attach PEFT LoRA adapters (baked additively into the graph) if present. | ||
|
|
||
| The adapter lives at ``<input_path>/lora_adapters/adapter_model.safetensors`` | ||
| with keys ``base_model.model.model.layers.{i}.{proj}.lora_A.weight`` [r, in] | ||
| and ``.lora_B.weight`` [out, r] for proj in {qkv_proj, o_proj, gate_up_proj, | ||
| down_proj}. ``lora_A`` is shared across split projections that share an input | ||
| (q/k/v share the qkv input, gate/up share the gate_up input); ``lora_B`` is | ||
| split along its output dimension. The builder emits the runtime delta | ||
| ``(lora_B @ lora_A @ x) * scaling`` added to the quantized projection output. | ||
| """ | ||
| adapter_dir = os.path.join(self.input_path, "lora_adapters") | ||
| adapter_path = os.path.join(adapter_dir, "adapter_model.safetensors") | ||
| if not os.path.exists(adapter_path): | ||
| return | ||
|
|
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.
Summary
Generalizes the Gemma 4 MoE
make_moe_quarkpath so it also builds plain Quark/AWQ int4 experts (e.g.amd/gemma-4-26B-A4B-it-uint4-awq), not just the factored 2-bit LoRA-KD checkpoint. The previous code was hardwired for the 2-bit layout; this guards each 2-bit-specific step on the checkpoint's actual shape.input_prescaleis present. Plain Quark/AWQ experts have none, so the MoE input feedsQMoEdirectly.expert_weight_bits==2). AWQ int4 experts are already emitted interleaved bycombine_and_repack_gate_up.Stacking
This PR is stacked on top of the 2-bit PR (#2482), which is itself stacked on the base Gemma 4 PR (#2473). Until those merge, the diff here shows the parent commits; the int4-specific change is the top commit. Will rebase onto
mainas parents land.Test plan
test/python/models/test_gemma4moe_builder.py- addedTestGemma4MoEQuarkInt4Path: QMoE emitsexpert_weight_bits=4, no prescale/rotation subgraph, integer uint8 zero-points emitted without float cast, zero-points kept on CUDA int4. (14 passed total)ruff checkclean on the test file.amd/gemma-4-26B-A4B-it-uint4-awq-> sanity generation -> wikitext PPL.Generated with Claude Code