Conversation
…ensor map load_model's class_predicate assumed any module whose weights already carry `.scales` should be quantized at the top-level default group_size/bits. For tensors a model's sanitize() derives and re-quantizes from a source tensor with its own per-tensor override (e.g. the absorbed MLA projections embed_q/unembed_out, derived from kv_b_proj in deepseek_v3, deepseek_v32, glm4_moe_lite, kimi_linear, and longcat_flash), that path never appears in config["quantization"] because the converter never saw it - it's created inside sanitize(). Loading a mixed-bit checkpoint where kv_b_proj carries a per-tensor override then allocates the derived module at the global default bits while the saved weight is packed at the override bits, raising a shape mismatch on load_weights. Infer the actual group_size/bits from the packed weight and scales shapes instead, mirroring the inference each sanitize() already performs on the source tensor. This is a no-op for the common case (no override, inferred bits equal the default already used to produce the checkpoint). Fixes ml-explore#1451.
michalk8
approved these changes
Sep 18, 2026
michalk8
left a comment
Collaborator
There was a problem hiding this comment.
Thanks for spotting this, I've modified your code a bit, happy to merge now.
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
Loading a mixed-bit quantized checkpoint fails with a shape mismatch when the source tensor a model's
sanitize()derives another tensor from carries a per-tensor quantization override.Concretely:
deepseek_v3.py,deepseek_v32.py,glm4_moe_lite.py,kimi_linear.py, andlongcat_flash.pyall derive the absorbed MLA projectionsembed_q/unembed_outfromkv_b_projinsidesanitize(), dequantizing and re-quantizing them at whatever bits/group_sizekv_b_projitself used. Butembed_q/unembed_outnever appear inconfig["quantization"]— the converter never saw those paths, since they only come into existence insidesanitize(). Soload_model'sclass_predicate(mlx_lm/utils.py) falls through tof"{p}.scales" in weights, which isTrue(sanitize just created those scales), andnn.quantizeallocates the module at the global default bits/group_size instead of the bits it was actually packed at. On any layer where the source tensor's override differs from the default,load_weightsthen raises a shape mismatch.Repro (from #1451):
Fixes #1451.
Fix
When a path is missing from
config["quantization"]but its weight already carries.scales, infer the actualgroup_size/bitsfrom the packed weight and scales shapes (in_dims = m.weight.shape[-1], same formula every affectedsanitize()already uses on the source tensor) instead of assuming the top-level default. This is generic — it isn't specific to any one model file — and a no-op for the common uniform-bit case, since the inferred bits equal the default that was used to produce the checkpoint in the first place.Testing
test_load_model_with_mixed_bit_derived_mla_projectionintests/test_utils.py: builds a tinydeepseek_v3model, quantizes it uniformly, then re-packsembed_q/unembed_outat a different bit width (without adding their paths to the quantization map) to simulate akv_b_projoverride — confirms the old code raises the shape-mismatch error from Mixed-bit MLA checkpoints fail to load — sanitize() re-quantizes derived embed_q/unembed_out at kv_b_proj's bits, but the modules are built at the global default #1451, and the fix loads cleanly with the modules correctly reporting the overridden bits.python -m unittest tests.test_models— 76 passed.python -m unittest tests.test_utils tests.test_prompt_cache— all pass except two pre-existing failures intest_convert/test_load_model_with_custom_get_classescaused by an incomplete local HF cache with network disabled, unrelated to this change.pre-commit run --files mlx_lm/utils.py tests/test_utils.py(black, isort) — clean.