Repository navigation
Conversation
…uantizeLinear A16 models keep 16-bit activation Q/DQ boundaries around every quantized op, including the MatMulNBits nodes fused in microsoft#32885. On CPU each pair runs as two kernels and materializes an int16/uint16 tensor that is immediately dequantized again. Add the com.microsoft.QuantizeDequantizeLinear op (float input, int16 or uint16 zero point) and a CPU kernel for it: - MlasQuantizeDequantizeLinear runs the per-element steps of the MLAS QuantizeLinear kernel and then (q - zp) * scale in one vector loop (SSE2, NEON, LSX). On other platforms, or when a non-default x64 quantize kernel is selected, it falls back to two passes through MlasQuantizeLinear. - Results are bitwise identical to the unfused QuantizeLinear -> DequantizeLinear pair. Add QuantizeDequantizeLinearFusion, a Level3 CPU transformer that replaces a 16-bit QuantizeLinear whose consumers are all DequantizeLinear nodes with the same constant scalar scale and zero point by one fused node. It also follows the quantized tensor through data-movement ops (Transpose, Reshape, Slice, Gather, Split, Unsqueeze, ...) to matching DQs and runs those ops on the float values. It runs after the Level2 Q/DQ node-unit fusions, so it never removes a Q/DQ they could still use, and session.disable_quant_qdq disables it. Tests: - QuantizeDequantizeLinearOpTest: int16 and uint16 round-half-to-even and saturation, a large tensor against a reference, empty input, and rejection of per-axis parameters. - MLAS QuantizeDequantizeLinearS16/U16: bitwise comparison with MlasQuantizeLinear followed by dequantization. - QuantizeDequantizeLinearFusionTests: interior pairs are bit-exact, fusion through data-movement ops, fan-out and graph-output DQs, A16 MatMulNBits boundaries, session.disable_quant_qdq, and the cases that must not fuse (8-bit, mismatched or non-constant or per-axis parameters, other uses of the quantized value, non-CPU EPs). Co-authored-by: Jovan Bubonja <jovanbubonja@microsoft.com>
mirounga
requested review from
Edward Chen (edgchen1) and
Tianlei Wu (tianleiwu)
and
a balanced review from Copilot
October 30, 2026 20:19
mirounga
marked this pull request as draft
October 30, 2026 20:20
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The fusion accepts opset-23+ QuantizeLinear nodes with FLOAT16 precision but computes in FLOAT, changing model outputs.
1 open issue
What changed in this PR
This PR adds CPU fusion for 16-bit QuantizeLinear → DequantizeLinear pairs, reducing intermediate tensor storage and kernel overhead in A16 models, including the MatMulNBits boundaries preserved by #32885.
Changes:
- Adds the
com.microsoft.QuantizeDequantizeLinearoperator, CPU kernels, and MLAS implementation. - Registers a Level3 fusion that follows compatible data-movement operations and respects
session.disable_quant_qdq. - Adds numerical and graph-transformation tests, plus operator documentation.
| File | Description |
|---|---|
| onnxruntime/test/optimizer/quantize_dequantize_linear_fusion_test.cc | Tests fusion patterns, exclusions, and A16 boundaries. |
| onnxruntime/test/mlas/unittest/test_quantizelinear.cpp | Adds bitwise comparisons against separate quantization and dequantization. |
| onnxruntime/test/contrib_ops/quantize_dequantize_linear_op_test.cc | Tests rounding, saturation, empty inputs, and parameter validation. |
| onnxruntime/core/optimizer/qdq_transformer/quantize_dequantize_linear_fusion.h | Declares the fusion transformer. |
| onnxruntime/core/optimizer/qdq_transformer/quantize_dequantize_linear_fusion.cc | Implements eligibility checks and graph rewriting. |
| onnxruntime/core/optimizer/graph_transformer_utils.cc | Registers the fusion at Level3. |
| onnxruntime/core/mlas/lib/quantize.cpp | Implements vectorized fusion and two-pass fallback. |
| onnxruntime/core/mlas/inc/mlas.h | Declares the fused MLAS API. |
| onnxruntime/core/graph/contrib_ops/quantization_defs.cc | Defines the operator schema and shape inference. |
| onnxruntime/core/graph/contrib_ops/ms_opset.h | Registers the schema in the Microsoft operator set. |
| onnxruntime/contrib_ops/cpu/quantization/quantize_dequantize_linear.cc | Implements the threaded CPU operator. |
| onnxruntime/contrib_ops/cpu/cpu_contrib_kernels.cc | Registers int16 and uint16 CPU kernels. |
| docs/OperatorKernels.md | Lists supported CPU kernel types. |
| docs/ContribOperators.md | Documents the new operator. |
Review effort: Balanced
…earFusion From opset 23 the CPU QuantizeLinear kernel honors precision=FLOAT16, but the fused kernel always computes in float, so fusing such a node changes the outputs. Only fuse when precision is 0 or FLOAT, and add opset-23 tests for both the rejected and the still-fused cases.
This branch has not been deployed
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.

…uantizeLinear
A16 models keep 16-bit activation Q/DQ boundaries around every quantized op, including the MatMulNBits nodes fused in #32885. On CPU each pair runs as two kernels and materializes an int16/uint16 tensor that is immediately dequantized again.
Add the com.microsoft.QuantizeDequantizeLinear op (float input, int16 or uint16 zero point) and a CPU kernel for it:
Add QuantizeDequantizeLinearFusion, a Level3 CPU transformer that replaces a 16-bit QuantizeLinear whose consumers are all DequantizeLinear nodes with the same constant scalar scale and zero point by one fused node. It also follows the quantized tensor through data-movement ops (Transpose, Reshape, Slice, Gather, Split, Unsqueeze, ...) to matching DQs and runs those ops on the float values. It runs after the Level2 Q/DQ node-unit fusions, so it never removes a Q/DQ they could still use, and session.disable_quant_qdq disables it.
Tests: