Repository navigation
fix(qwen4_exp): read weights under the checkpoint's quant config - #759
Merged
Merged
Conversation
- route each dense Linear tensor by its scheme's stored names, so per-channel fp8 scales (llm-compressor) load and fused parts concatenate per role - name the routed NVFP4 experts by the checkpoint's dialect, so compressed-tensors weight_packed / weight_global_scale load - accept a bf16 PLE table without a scale in the pinned and disk backends
ModelOpt exports store the same e8m0 codes as weight_scale_inv (MiniMax-M3) or weight_scale (Qwen3.8-Flash-Next-FP4-Local); Stored gains aliases and the scheme-driven readers match any of them.
KarrAcaRn
pushed a commit
to KarrAcaRn/FreeToken-ByAI
that referenced
this pull request
Oct 20, 2026
- hybrid fetch split: upstream per-layer-kind bench rows (FlashML-org#734) first, our startup split now measured per layer kind without a row (before the CPU executor), then cap 1; dropped the profile-driven auto->hybrid pick and its cache-share guard with upstream - qwen3_5_moe: upstream fp32 runtime norm +1 (FlashML-org#755) under our DFlash hidden-layer return and LinearOProj - qwen4_exp: upstream quant-config reader and MXFP8 scale aliases (FlashML-org#759) replace our .weight_scale rename; our validating scan_ple_table and NVFP4 PLE tables gain the bf16 table support - nvfp4_banks: upstream W4A4 input scales (FlashML-org#736) combined with our TP sharding and disk-tier skip - ftw: fp16 nvfp4 global refusal (FlashML-org#739) on top of our eager index validation - kernel/nvfp4_moe: drop the e2m1 LUT argument our bit-placement decode removed - benchbw: upstream layer-kind rows plus our THP bench banks, decode-shaped gather and custom profile hint - tests: disk-tier spec test skips the quant-config-driven qwen4_exp hook Assisted-by: Claude Opus 5.5
This was referenced Oct 24, 2026
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.
Fixes #738.
orcarouter/Qwen3.8-Flash-Next-Uncensored-NVFP4 is an llm-compressor (compressed-tensors) export. The model built its layers correctly from the quant config (192 per-channel fp8 Linears), but the
qwen4_expsafetensors reader could not feed them:.weight_scaleand only knew.weight/.weight_scale_inv, so the first fp8 layer failed withKeyError: ...linear_attn.in_proj_qkvz.weight_scale;weight/weight_scale/weight_scale_2), so the compressed-tensors namesweight_packed/weight_global_scalewould not match;This PR makes the reader follow the checkpoint's quant config instead of a list of suffixes, the way the
qwen3_5_moereader already does.Changes
models/qwen4_exp/weight.py:_DenseReaderreplaces_DenseFuser. Each quant-aware Linear tensor is routed byquant.scheme_for(module)and the dialect's stored names (quant.storage(scheme)): bf16, per-channel / per-tensor fp8, block-fp8, MXFP8 and NVFP4 dense layers in any mix. Fused parts (qkv_proj,in_proj_qkvz/in_proj_baorin_proj,gate_up_proj, the HCinput_mix_weight_down_block_injectwith its 16-row pad) are concatenated per role; per-channel and per-tensor fp8 scales become fp32[rows]. Each part is checked when it arrives (dtype, block shape), a module missing tensors is listed at the end, and a role stored twice is refused. Static KV-cache scales (k_scale/v_scale) are dropped, as inqwen3_5_moe.models/qwen4_exp/weight.py:nvfp4_expert_specbuilds the expert key pattern and kind map fromquant.stored_tensors(NVFP4), so ModelOpt and compressed-tensors experts both load (the compressed-tensors global scale is inverted, as inqwen3_5_moe).models/qwen4_exp/weight.py,ple_disk.py:load_ple_tableand the disk row source accept an fp8 table (scalarweight_scalerequired) or a bf16 table (no scale).PleRowSourcegainsrow_dtype.layers/quantization/configs/base.py,modelopt.py:Storedgainsaliases. The ModelOpt MXFP8 scale accepts bothweight_scale_inv(nvidia/MiniMax-M3) andweight_scale(nvidia/Qwen3.8-Flash-Next-FP4-Local, local-inference-lab/Qwen3.8-Flash-Next-NVFP4). Both names hold the same e8m0 codes; vLLM likewise mapsweight_scale_invontoweight_scaleat load. Theqwen4_expandqwen3_5_moereaders match any of the names.Stricter than before: a Linear the quant config declares unquantized but that carries a
.weight_scalenow raises instead of being dropped silently. None of the Qwen3.8-Flash-Next checkpoints checked below has one.Not covered: nvidia/Qwen3.8-Flash-Next-FP4-Local also needs its CSF expert scales, NVFP4 PLE table and W4A16 vision MLP (#708); only its MXFP8 dense part is handled here. A quantized vision tower is still not read (as on main).
Results
RTX PRO 6000, 249 GB host RAM.
Dense state dict (
iter_weightswith vision off vs the model the engine builds from the same config):On orcarouter the fused
in_proj_qkvzand shared-expertgate_up_projscales equal the concatenated checkpoint scales bitwise. On RadixArk and nvidia the expert spec is the same as main's fixed one.MXFP8 scale name: from nvidia/Qwen3.8-Flash-Next-FP4-Local, 13 MXFP8 modules of layer 0 (GDN, shared expert) and layer 3 (attention, indexer), read by range requests, fill all 16 model buffers with the right shapes and dtypes. Its
in_proj_qkvzdequantized asw * 2^(code - 127)is 2.65% from the Qwen/Qwen3.8-Flash-Next bf16 weight (relative Frobenius error), and local-inference-lab'sin_proj_qkvis 2.66% (dividing by the scale instead gives 6.6e7): the plain e4m3 rounding error.Serving orcarouter with
ft serve --moe-backend auto --max-running-requests 4 --memory-ratio 0.95(auto: offload, disk PLE): ready in under 3 minutes. Greedy answers (thinking off) are correct: "The capital of Australia is Canberra.",37 * 43->1591, a two-sentence Chinese explanation of photosynthesis, a Fibonacci function. Decode is about 55-60 tok/s. With--ple-backend pinnedthe 95.4 GiB table loads at about 2.2 GB/s and the four answers are identical.Tests
test_weight_ckpt.py: the dense pass runs with vision off, so the state-dict comparison matches the text model it builds; on main two of its tests failed on the vision keys. 45 / 45 pass on the RadixArk checkpoint.test_quant_config.py: the tensor oracle reads a U8weight_scaleon an fp8 weight as MXFP8.pytest tests/models/qwen4_exp tests/models/test_qwen3_5_moe_weight.py tests/models/test_quant_config.py tests/models/test_minimax_m3.py tests/kernels/test_mxfp8_linear.py tests/kernels/test_row_store.py: 346 passed, 143 skipped, 5 failed. The 5 also fail on main: fourtest_probed_layers_get_the_method_their_config_sayscases from the local HF cache andtest_nvfp4_experts_restricted_to_triton_for_swigluoai.