Add regression coverage for mixed-precision (per-layer) quantized checkpoint loading#395
Open
fdagostino wants to merge 1 commit into
Open
Conversation
The mlx-community Gemma 4 QAT checkpoints (gemma-4-12B-it-qat-4bit,
gemma-4-E4B-it-qat-4bit) quantize attention and embeddings at 4-bit and
every mlp.{gate,up,down}_proj at 8-bit, declared as per-module overrides
interleaved with the global settings in config.json's quantization dict.
Loading these checkpoints works today: QuantizationContainer decodes the
interleaved dict, PerLayerQuantization resolves each module path, and
loadWeights quantizes each module with its per-module bits (mirroring
Python mlx-lm's class_predicate). But nothing exercised that path end to
end — only the config-decode unit tests in BaseConfigurationTests — and
reports like ollama/ollama#16740 attribute empty/garbage generation to
loaders mis-decoding the 8-bit MLP payload as 4-bit, so it is behavior
worth pinning.
Add MixedPrecisionQuantLoadTests, which builds a tiny gemma4_unified
model, quantizes its own weights into a synthetic checkpoint with the QAT
layout (8-bit MLP / 4-bit rest), and verifies through loadWeights that:
- per-module overrides are honored (mlp projections come back as 8-bit
QuantizedLinear, everything else 4-bit) and the 8-bit payload
dequantizes round-trip, and
- a load that ignores the overrides (global 4-bit only) fails loudly on
the packed-shape mismatch instead of silently mis-decoding the payload.
Validated to bite: with the per-layer lookup in loadWeights replaced by
the global default, the first test fails with mismatchedSize on
layers.0.mlp.gate_proj ([128, 8] vs [128, 16]).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
What
Test-only PR: adds
MixedPrecisionQuantLoadTests, end-to-end regression coverage for loading checkpoints that mix quantization precisions via per-module overrides inconfig.json— the layout every mlx-community Gemma 4 QAT upload uses (gemma-4-12B-it-qat-4bit,gemma-4-E4B-it-qat-4bit: 4-bit attention/embeddings, 8-bitmlp.{gate,up,down}_proj, declared as 144/126 per-module entries interleaved with the global quantization dict).Why
Reports such as ollama/ollama#16740 attribute empty/garbage generation from these checkpoints to loaders reading the 8-bit MLP payload as 4-bit. While investigating exactly that symptom on device, I verified that mlx-swift-lm handles this correctly today:
QuantizationContainerdecodes the interleaved dict,PerLayerQuantizationresolves each module path, andloadWeightsapplies the per-module bits (the same semantics as Python mlx-lm'sclass_predicate). Verified on macOS against the realmlx-community/gemma-4-12B-it-qat-4bit: after load the module tree has exactly 144 ×QuantizedLinear(bits: 8)for the MLP projections, 4-bit everywhere else, and greedy generation is correct.However, nothing in
Tests/exercises this path end to end (only the config-decode unit tests inBaseConfigurationTests), so the behavior the whole Gemma 4 QAT family depends on was unpinned. This PR pins it.The tests
Built on a tiny text-only
gemma4_unifiedmodel whose own initial weights are quantized into a synthetic on-disk checkpoint with the QAT layout:quantizationdict (global 4-bit + per-module 8-bit MLP overrides), runsloadWeights, and asserts each MLP projection comes back asQuantizedLinear(bits: 8, groupSize: 64), everything else quantizable at 4-bit, with an 8-bit dequantization round-trip check.Validated to bite: replacing the per-layer lookup in
loadWeightswith the global default makes test 1 fail withmismatchedSizeonlayers.0.mlp.gate_proj([128, 8]vs[128, 16]).Related
Follows the Gemma 4 series #390 (KV-shared construction), #391 (video), #392 (audio encoder).
🤖 Generated with Claude Code