Fix MoE expert decompression for non-32-divisible bit widths#47315
Fix MoE expert decompression for non-32-divisible bit widths#47315KKothuri wants to merge 1 commit into
Conversation
`DecompressExperts.convert` rebuilt each expert's unpacked in-dim as `packed_cols * (32 // num_bits)`. compressed-tensors uses dense bit-packing, so this is only exact when `num_bits` divides 32. For 3/5/6/7-bit weights it under-counts: e.g. a 3-bit `down_proj` packed as (2048, 48) reconstructs an in-dim of `48 * 10 = 480` instead of the true 512, producing wrongly shaped expert weights and a `torch._grouped_mm` "contraction dimension of mat_a and mat_b must match" crash at load. 4-bit and 8-bit are unaffected. The checkpoint already stores an exact per-expert `weight_shape`, and it is routed into `convert` via the `.weight_shape$` source pattern. Prefer it, and fall back to the packed-tensor reconstruction only when it is missing/empty (e.g. left empty on most ranks under TP/EP sharding). Verified on a 3-bit (W3A16) AWQ GlmMoeDsa checkpoint: loading goes from a hard crash to perplexity 1.0002 vs. 1.0000 for the bf16 base on the same text. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Karthik Kothuri <karthikkothuri2009@gmail.com>
CI recapDashboard: View test results in Grafana |
|
Not sure how significant it is but could be real, cc @SunMarc for quants maybe? |
ErenAta16
left a comment
There was a problem hiding this comment.
The fallback ordering is right: prefer the checkpoint's stored weight_shape when present, since it's exact for any bit width, and only reconstruct from pack_factor when it's missing or empty, which is the TP/EP-sharded case where weight_shape isn't populated on most ranks. That's a real gap in the previous unconditional-reconstruction path: pack_factor = 32 // num_bits only recovers the true in-dim exactly when num_bits divides 32, so 3/5/6/7-bit packing was silently under-counting the unpacked dimension. The fix keeps the original reconstruction as a fallback rather than replacing it outright, so the sharded-checkpoint case that logic was originally written for still works.
What does this PR do?
DecompressExperts.convert(inintegrations/compressed_tensors.py) rebuilds each MoE expert's unpacked input dimension aspacked_cols * (32 // num_bits).compressed-tensorsuses dense bit-packing, so this reconstruction is only exact whennum_bitsdivides 32 (i.e. 4-bit and 8-bit). For 3/5/6/7-bit weights it under-counts.Concretely, a 3-bit
down_projstored asweight_packedof shape(2048, 48)reconstructs an input dim of48 * (32 // 3) = 48 * 10 = 480, whereas the true dim is512. The experts then decompress to the wrong shape and loading fails with:from
torch._grouped_mm. 4-bit and 8-bit checkpoints are unaffected because 8 and 4 divide 32 evenly.Fix
The checkpoint already stores an exact per-expert
weight_shape((out-dim, in-dim)), and it is already routed intoconvertvia the.weight_shape$source pattern inquantizer_compressed_tensors.py. This PR uses that stored shape, and falls back to the packed-tensor reconstruction only when it is missing/empty (e.g. left empty on most ranks under TP/EP sharding — the case the previous code was written for).Testing
Verified on a 3-bit (W3A16) AWQ-quantized MoE checkpoint (
GlmMoeDsaForCausalLM):_grouped_mmcontraction error above.4-bit and 8-bit checkpoints load identically before and after (the stored shape matches the old reconstruction for those bit widths).
AI assistance was used for this change; every line was reviewed and the tests above were run.
Before submitting
Who can review?
Quantization / compressed-tensors: @SunMarc @MekkCyber