fix(models): better support for mixed-precision compressed-tensors NVFP4 - #390
Open
Sam-Izdat wants to merge 1 commit into
Open
fix(models): better support for mixed-precision compressed-tensors NVFP4 #390Sam-Izdat wants to merge 1 commit into
Sam-Izdat wants to merge 1 commit into
Conversation
The compressed-tensors iter crashed on mixed-precision NVFP4 checkpoints (FP8 attention/shared-expert + NVFP4 routed experts) and loaded the per-row global scale without reciprocating, producing garbled output. Root cause: the iter and bank builder assumed modelopt's per-expert naming and dequant-side-divisor convention; llm-compressor and the same family use a different naming (`weight_packed` / `weight_global_scale`) and store the quant-side scale instead. Fixes: - Probe the safetensors index for `weight_packed` vs `weight_scale_2` to pick the right reciprocal convention per checkpoint (data, not config claim). - Route compressed-tensors MoE to the modelopt iter when the shared_expert is in the FP8 group, avoiding the FP8+bf16 cat crash. - Extend the NVFP4 expert key regex and bank kind dispatch to cover llm-compressor naming (`weight_packed` / `weight_global_scale` / `input_global_scale`). - `_SharedExpert` refactored to use the `quant_linear` factory, with a new `attn_quant='fp8_pertensor'` dispatch for the mixed-precision path.
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
Adds support for compressed-tensors checkpoints that mix per-tensor
FP8 attention/shared-expert projections with NVFP4 routed
experts - a layout produced by
llm-compressorand found in the wild.Without this, such checkpoints either crash on load (FP8 attn gets
torch.cat'd with bf16 weights) or produce garbled output (the dequantkernel multiplies by the per-row global scale, but
llm-compressorstores the QUANT-side scale rather than the DEQUANT-side divisor that
modeloptstores).The fix is in five files, +236/-63 lines. The key idea is to use the
on-disk tensor naming as the ground truth for which convention is
in use (
weight_packed-> llm-compressor,weight_scale_2-> modelopt)rather than relying on the config-side
format: nvfp4-pack-quantizedstring (which both exporters set).
Test models
nvidia/Qwen3.6-35B-A3B-NVFP4primitive-ai/Ornith-1.5-35B-A3B-agentic-NVFP4-FP8Tested with
--moe-backend offload --expert-load parallelon RTX 306012 GB (
nvfp4_backend='triton'). Ornith decoding at ~30-40+ tok/s with minimal tuning.Fixed
Crash on mixed-precision attn+shared-expert — the
compressed-tensors iter doesn't know about FP8. Routing
compressed-tensors MoE to the modelopt iter (which handles FP8
attn) when the shared-expert is in the FP8 group avoids the
cat-of-fp8-and-bf16 crash.
Missing global-reciprocal for llm-compressor NVFP4 experts —
the dequant kernel multiplies by the per-row global;
modeloptstores the DEQUANT-side divisor directly, but
llm-compressorstores the QUANT-side scale that must be reciprocated. A
per-checkpoint flag on the NVFP4 source spec picks the right
convention.
Unknown NVFP4 kind
input_global_scale(parallel build) —the bank dispatch only knew
weight_scale_2(modelopt). Aliasweight_global_scale -> weight_scale_2viaspec.kind_map;skip
input_scale/input_global_scale(activation scales,not bank tensors).
Per-expert naming regex too narrow for parallel build — the
expert key pattern only matched
weight | weight_scale | weight_scale_2. Extended to cover the fullllm-compressorper-expert naming set so the parallel bank builder's
weight_infopopulates correctly.Iter dispatch routed mixed-precision MoE to the
compressed-tensors iter, which holds more GPU state per-shard
than the default iter (caused OOM on 12 GB). Reverted to the
default iter for that case.
The on-disk-naming heuristic for reciprocal was too coarse.
format: nvfp4-pack-quantizedis set by BOTHllm-compressorandmodeloptre-exports. Now probes the safetensors index forweight_packed(llm-compressor signature) vsweight_scale_2(modelopt signature) and picks the convention from the data, not
the config claim.
Files changed (5, +236/-63)
python/freetoken/models/config.py—_nvfp4_global_reciprocalheuristic probing the safetensors index;
ModelConfig.nvfp4_global_reciprocalfieldpython/freetoken/models/nvfp4_banks.py— bank kind dispatchhandles
weight_global_scale(viaspec.kind_map) and skipsactivation scales; serial and parallel paths updated in lockstep
python/freetoken/models/qwen3_5_moe/config.py—_has_moe_expertshelper;
_attn_quantand_expert_quantextended forcompressed-tensors;
dense_quantprobe for FP8 shared-expert;parse_config wiring
python/freetoken/models/qwen3_5_moe/moe.py—_SharedExpertrefactored to use the
quant_linearfactory, withattn_quant='fp8_pertensor'dispatch for the mixed-precisionshared-expert path
python/freetoken/models/qwen3_5_moe/weight.py— per-checkpoint_spec_forwithkind_map;_load_maybe_quantizedand_nvfp4_partsaccept llm-compressor naming;_PT_FP8_FUSEand_CT_NVFP4_FUSEextended for shared-expert gate|up fuse; expanded_NVFP4_EXPERT_KEY_REregexBackwards compatibility
Qwen3.6-35B-A3B-NVFP4(modelopt) is regression-clean.No NVFP4 reciprocal applied (the data shows
weight_scale_2only,which signals the modelopt convention).
exports use) is unchanged in behavior.
Notes
The heuristic that disambiguates
llm-compressorfrommodeloptprobes the safetensors index for a routed-expert tensor's sibling
suffixes. This is a one-time read at model-load time; no per-tensor
overhead. If both
weight_packedandweight_scale_2are absent(e.g. some other compressed-tensors flavor), the heuristic falls
back to the safe default of "no reciprocal" same as
global_reciprocal=Falseupstream.