You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
qwen3_5_moe (engine), following the quantization refactor in #418 / #427. Verified against main @ fb7f732.
What problem does this solve
layers/quantization/ already describes the compressed-tensors checkpoints people keep filing issues about -- correctly -- but qwen3_5_moe doesn't consume it, so those checkpoints still don't load.
Probing QuantConfig.from_hf(...).scheme_for_name(...) on today's main:
All three resolve exactly right. But qwen3_5_moe/config.py still computes its own verdicts with hand-rolled detectors -- _expert_quant (L42), _lm_head_quant (L71), _attn_quant (L107), consumed at L169/172/180 -- and _iter_weights_attn_fp8 in weight.py still routes off those strings. Those detectors return attn_quant='none' for both compressed-tensors checkpoints above, so the reader never reaches the fp8 dense path even though the config layer describes it perfectly.
model.py is already migrated for the head -- it passes quant_config=config.quant, prefix="lm_head" to a single ParallelLMHead -- so the model half and the config half currently disagree about the same checkpoint.
This is the shared root cause under a cluster of open reports: #252 (per-channel FP8, targets: ["Linear"]), #381 (model.language_model prefix), #344, #410, and the routing discussion on #390 / #413.
Proposed solution
Port qwen3_5_moe to derive its verdicts from the QuantConfig, the way qwen4_exp/config.py already does:
Keep the verdict strings -- engine.py reads expert_quant for MoE-strategy and bench-format decisions (L1162, L1316, L1552) and attn_quant/dense_quant at L1397, and checkpoint/convert.py:44 hashes expert_quant -- and just stop hand-deriving them. Each module already takes its own scheme when it's built.
The reader is the harder half: qwen3_5_moe/weight.py only imports QuantKind and never receives a QuantConfig, so #427 didn't reach it. _iter_weights_attn_fp8's dispatch would need to become scheme-driven rather than keyed on attn_quant == "fp8_pertensor", and _per_row_scale becomes redundant once scales come from Fp8TensorLinearMethod.create_weights, which already allocates [out_features] fp32.
Scoped deliberately to fp8/NVFP4, with the legacy detectors retained as the fallback for AWQ and compressed-tensors int4. That is not tidiness -- a wholesale port would break three checkpoints today, because layers/quantization has no dialect for them:
checkpoint
quant_method
QuantConfig.from_hf
cyankiwi/Qwen3.6-35B-A3B-AWQ-4bit
awq
NotImplementedError: quantization method 'awq' is not supported
Avesed/Qwen3.6-35B-A3B-INT4-W4A16
compressed-tensors
NotImplementedError: weight scheme ...
cyankiwi/Qwen-AgentWorld-35B-A3B-AWQ-INT4
compressed-tensors
NotImplementedError
Adding AWQ and CT-int4 dialects to layers/quantization/configs/ is the natural follow-up, and it's the same ground as #396 -- I'd rather do that as a separate change than smuggle it in here.
Migrate everything including int4 in one PR. Bigger, unreviewable, and it would couple an int4 dialect to an fp8 fix.
Offering to do it
Happy to take this if nobody has claimed it. I can validate against every family involved -- unsloth/...-NVFP4-Fast (CT per-channel fp8 + NVFP4), primitive-ai/Ornith-1.5-...-mixed-NVFP4-FP8 (CT per-tensor fp8), nvidia/...-NVFP4 (modelopt, the regression case), plus the three int4 checkpoints above to prove the fallback still works -- on a 4080 SUPER with 62 GB host RAM and load / needle-recall / BFCL harnesses already wired up for this engine.
Before writing code I would want your call on the following.
Is retaining the legacy detectors as an AWQ/int4 fallback acceptable as an interim state, or would you rather the int4 dialects land in layers/quantization first and the migration be one clean cut?
Applies to
qwen3_5_moe(engine), following the quantization refactor in #418 / #427. Verified againstmain@fb7f732.What problem does this solve
layers/quantization/already describes the compressed-tensors checkpoints people keep filing issues about -- correctly -- butqwen3_5_moedoesn't consume it, so those checkpoints still don't load.Probing
QuantConfig.from_hf(...).scheme_for_name(...)on today'smain:All three resolve exactly right. But
qwen3_5_moe/config.pystill computes its own verdicts with hand-rolled detectors --_expert_quant(L42),_lm_head_quant(L71),_attn_quant(L107), consumed at L169/172/180 -- and_iter_weights_attn_fp8inweight.pystill routes off those strings. Those detectors returnattn_quant='none'for both compressed-tensors checkpoints above, so the reader never reaches the fp8 dense path even though the config layer describes it perfectly.model.pyis already migrated for the head -- it passesquant_config=config.quant, prefix="lm_head"to a singleParallelLMHead-- so the model half and the config half currently disagree about the same checkpoint.This is the shared root cause under a cluster of open reports: #252 (per-channel FP8,
targets: ["Linear"]), #381 (model.language_modelprefix), #344, #410, and the routing discussion on #390 / #413.Proposed solution
Port
qwen3_5_moeto derive its verdicts from theQuantConfig, the wayqwen4_exp/config.pyalready does:Keep the verdict strings --
engine.pyreadsexpert_quantfor MoE-strategy and bench-format decisions (L1162, L1316, L1552) andattn_quant/dense_quantat L1397, andcheckpoint/convert.py:44hashesexpert_quant-- and just stop hand-deriving them. Each module already takes its own scheme when it's built.The reader is the harder half:
qwen3_5_moe/weight.pyonly importsQuantKindand never receives aQuantConfig, so #427 didn't reach it._iter_weights_attn_fp8's dispatch would need to become scheme-driven rather than keyed onattn_quant == "fp8_pertensor", and_per_row_scalebecomes redundant once scales come fromFp8TensorLinearMethod.create_weights, which already allocates[out_features]fp32.Scoped deliberately to fp8/NVFP4, with the legacy detectors retained as the fallback for AWQ and compressed-tensors int4. That is not tidiness -- a wholesale port would break three checkpoints today, because
layers/quantizationhas no dialect for them:quant_methodQuantConfig.from_hfcyankiwi/Qwen3.6-35B-A3B-AWQ-4bitawqNotImplementedError: quantization method 'awq' is not supportedAvesed/Qwen3.6-35B-A3B-INT4-W4A16compressed-tensorsNotImplementedError: weight scheme ...cyankiwi/Qwen-AgentWorld-35B-A3B-AWQ-INT4compressed-tensorsNotImplementedErrorAdding AWQ and CT-int4 dialects to
layers/quantization/configs/is the natural follow-up, and it's the same ground as #396 -- I'd rather do that as a separate change than smuggle it in here.Alternatives considered
weight_scale) and feat(qwen3_5_moe): keep a natively-fp8 lm_head native instead of failing to load #416, plus astrategy: "channel"widening offered on fix(models): better support for mixed-precision compressed-tensors NVFP4 #390. I closed feat(qwen3_5_moe): keep a natively-fp8 lm_head native instead of failing to load #416 once refactor(quant): config, scheme and method layers for quantization #418/refactor(quant): hand the checkpoint QuantConfig to the weight readers #427 landed -- the new config layer covers it more completely than my patch, including class-nametargetsinignore, which is the actual blocker for [Feature Request] Support compressed-tensors per-channel FP8 checkpoints for Qwen3.5 MoE #252's checkpoint. Continuing down that road means maintaining two detection systems that disagree.QuantSchemeper module that routing may not need to exist. Worth settling before that work lands.Offering to do it
Happy to take this if nobody has claimed it. I can validate against every family involved --
unsloth/...-NVFP4-Fast(CT per-channel fp8 + NVFP4),primitive-ai/Ornith-1.5-...-mixed-NVFP4-FP8(CT per-tensor fp8),nvidia/...-NVFP4(modelopt, the regression case), plus the three int4 checkpoints above to prove the fallback still works -- on a 4080 SUPER with 62 GB host RAM and load / needle-recall / BFCL harnesses already wired up for this engine.Before writing code I would want your call on the following.
layers/quantizationfirst and the migration be one clean cut?weight.pyreceive theQuantConfigthe same way refactor(quant): hand the checkpoint QuantConfig to the weight readers #427 did it for other readers, or is there a preferred shape for MoE readers that split dense and expert passes?