Repository navigation
Add Qwen3.5 Quark GPTQ export support for hybrid attention models - #2545
Vishal Jain (VishalX) wants to merge 3 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
The new MTP skip condition is overly broad (based on attribute presence rather than quant_method), risking unintended disabling of MTP export for other quantized flows.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Enables ONNX export for Qwen3.5 hybrid-attention models when using pre-quantized (Quark/GPTQ) checkpoints by fixing linear-attention graph wiring and extending the quantized weight loader to recognize linear_attn.* tensors.
Changes:
- Capture and use
make_matmul()’s returned node name for all Qwen3.5 linear-attention projections to prevent disconnectedoutput_0references forMatMulNBitsnodes. - Extend the quantized loader’s tensor-key dispatch to support
linear_attnprojections and associated non-quantized tensors, and include them in quantized-module iteration/repacking. - Skip building the MTP head when
quantization_configis present (intended to avoid missing-weight failures for certain pre-quantized checkpoints).
File summaries
| File | Description |
|---|---|
| src/python/py/models/loaders/base.py | Adds QuantizedLinearAttention, wires linear_attn.* keys into quantized tensor dispatch, and includes linear-attention modules in quantized iteration/repack passes. |
| src/python/py/models/builders/qwen.py | Fixes linear-attention matmul name propagation for quantized exports and introduces logic to skip MTP head for quantized configs. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- builders/qwen.py: capture make_matmul() return value at all five linear-attention call sites so MatMulNBits nodes are correctly wired; same fix for out_proj in make_linear_attention_output_proj; skip MTP head when checkpoint has a quantization_config (pre-quantized checkpoints do not include MTP weights) - loaders/base.py: add QuantizedLinearAttention class and linear_attn field on QuantizedDecoderLayer; add 20 regex handlers for model.layers.N.linear_attn.* weight keys; include linear_attn in quantized_tensor_modules() and repack_quantized_tensors() Co-Authored-By: Claude <noreply@anthropic.com>
- builders/qwen.py: gate MTP-head skip on quant_method == "quark" rather than mere presence of quantization_config, so other quantized flows (e.g. modelopt) are not affected - test_quantized_model.py: add four unit tests for the linear_attn tensor-key dispatch added in the previous commit; tests are quantizer- agnostic and cover quantized projections, plain tensors (A_log/dt_bias/ conv1d/norm), weight_scale alias (scales vs weight_scale), and key isolation from self_attn patterns Co-Authored-By: Claude <noreply@anthropic.com>
GatedDeltaNet decay/beta gate projections (in_proj_b, in_proj_a) must remain dense because their outputs feed exp() and softplus() in the recurrence; quantization error there would be amplified and degrade quality over long sequences. c6f7afc added require_dense_linear_attention_gate() to the builder to enforce this. For Quark GPTQ checkpoints that supply these weights as pre-quantized int4, add dequantize_gate_to_dense() to the base loader and call it for in_proj_b/in_proj_a in repack_quantized_tensors(), following the same pattern as ModeloptModel.make_dense_linear_module(). Co-Authored-By: Claude <noreply@anthropic.com>
6d8fd96 to
f56b3fe
Compare
|
/azp run macOS arm64 Build |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
|
||
| qkv_name = f"{basename}/qkv_proj/MatMul" | ||
| self.make_matmul(attention.in_proj_qkv, qkv_name, root_input) | ||
| qkv_name = self.make_matmul(attention.in_proj_qkv, f"{basename}/qkv_proj/MatMul", root_input) |
There was a problem hiding this comment.
Why do we need these changes to store the returned output from make_matmul?
There was a problem hiding this comment.
make_matmul returns the actual node name, and for quantized weights that name differs from the basename we pass in. make_matmul_nbits names the node f"{basename}NBits", so the output tensor is .../MatMulNBits/output_0, not .../MatMul/output_0.
The old linear-attention code discarded the return value and hardcoded f"{basename}/.../MatMul/output_0". That worked for dense MatMul, but for a pre-quantized (Quark/GPTQ) export the downstream Transpose/Split/Reshape nodes referenced a tensor that did not exist, leaving a disconnected output_0. Using the returned name fixes that and matches how the rest of this file consumes make_matmul (e.g. the shared-expert gate and the MTP fc).
|
kunal-vaishnavi let me know if this needs a rebase |
Summary
Enables end-to-end ONNX export of Qwen3.5 models when the source weights are pre-quantized with AMD Quark (GPTQ +
pack_method=reorder). Qwen3.5 uses a hybrid architecture that interleaves standard full-attention layers with GatedDeltaNet linear-attention layers.Two changes required for quantized hybrid checkpoints:
make_matmul()returns the actual emitted node name — for pre-quantized weights this is<basename>NBitsrather than<basename>— but all linear-attention call sites discarded the return value and used the original basename string to construct downstream output references. This produced a silently disconnected ONNX graph for quantized models.NotImplementedErroron everylinear_attn.*weight key.Additionally, the MTP head build is now skipped when a
quantization_configis present on the HuggingFace config, since pre-quantized checkpoints do not include MTP weights whilemtp_num_hidden_layersin the config would otherwise cause the builder to attempt loading them.Tested with Qwen3.5-4B and Qwen3.5-9B GPTQ INT4 checkpoints. Text-only and vision-language inference verified with latest ONNX Runtime.
Changes
src/python/py/models/builders/qwen.pyFix
make_matmul()return-value capture in linear-attention projectionsmake_matmul()emits aMatMulNBitsnode for pre-quantized weights whose name has anNBitssuffix (e.g./model/layers.0/linear_attn/qkv_proj/MatMulNBits). All five call sites in the Qwen3.5 linear-attention builder were discarding the return value, then using the original basename to formoutput_0references — pointing to a non-existent node and corrupting the graph. Fixed in:make_linear_attention_input_proj— 4 sites:in_proj_qkv,in_proj_z,in_proj_b,in_proj_amake_linear_attention_output_proj— 1 site:out_projSkip MTP head for pre-quantized checkpoints
Qwen3_5ForConditionalGenerationconfigs carrymtp_num_hidden_layers: 1, but Quark/GPTQ checkpoints do not include MTP weights. The builder now checks forquantization_configon the HuggingFace config and skips the MTP head build when present, avoiding aValueErrorat weight loading time.src/python/py/models/loaders/base.pyAdd
QuantizedLinearAttentionand wire it into the loader pipelineNew
QuantizedLinearAttentionclass — holds the five quantized projections of a GatedDeltaNet layer (in_proj_qkv,in_proj_z,in_proj_a,in_proj_b,out_projasQuantizedTensorModule) plus non-quantized fields (conv1d,normasTensorModule; plain tensorsA_log,dt_bias).linear_attnfield onQuantizedDecoderLayer— every decoder layer now holds aQuantizedLinearAttention()instance alongside the existingself_attnandmlp. It remains empty for full-attention layers and is populated for linear-attention layers.20 new regex handlers in
QuantizedModel.__init__— coveringqweight,scales,qzeros,g_idxfor each of the five quantized projections, plusA_log,dt_bias,conv1d.weight, andnorm.weight. These slot into the existing tensor-map dispatch mechanism with no changes required toQuarkModel.quantized_tensor_modules()andrepack_quantized_tensors()updated — both iteration passes now includelayer.linear_attnalongsidelayer.self_attnandlayer.mlp, so unpack/repack and property-setting cover linear-attention weights automatically.