Register Flatten as Direct8Bit op in Python QDQ static quantizer - #28340
Conversation
The Python-layer QDQ quantizer never registered Flatten as a passthrough (Direct8Bit) op, so quantize_static / quantize_dynamic kept redundant DequantizeLinear -> Flatten -> QuantizeLinear pairs in the output model even though the C++ runtime optimizer drops them (PR microsoft#21376). Flatten is a layout-only op with no arithmetic, identical in quantization semantics to Reshape, Squeeze, and Unsqueeze. Register it in QLinearOpsRegistry (Direct8BitOp) and QDQRegistry (QDQDirect8BitOp) so the Python tooling no longer emits the extra Q/DQ pair around Flatten in either QOperator or QDQ format. Adds test_op_flatten.py mirroring test_op_reshape.py and covering both u8/u8 and s8/s8 quantization paths. Fixes microsoft#21375
There was a problem hiding this comment.
Pull request overview
This PR updates the Python quantization toolchain so Flatten is treated as a layout-only “direct 8-bit” op (similar to Reshape/Squeeze/Unsqueeze), avoiding redundant DequantizeLinear -> Flatten -> QuantizeLinear patterns in quantized models. It also adds a new Python unit test to validate Flatten behavior in both QOperator and QDQ quantization formats.
Changes:
- Register
FlattenasDirect8BitOpinQLinearOpsRegistryand asQDQDirect8BitOpinQDQRegistry. - Add
test_op_flatten.pyto validate node patterns and numerical correctness for u8/u8 and s8/s8 in both QOperator and QDQ modes.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| onnxruntime/python/tools/quantization/registry.py | Adds Flatten to the direct-8bit registries for QOperator and QDQ quantization flows. |
| onnxruntime/test/python/quantization/test_op_flatten.py | Adds tests intended to ensure Flatten does not get redundant Q/DQ pairs and that quantized models are numerically correct. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
A few notes on the Copilot review pass to pre-empt likely questions: Scope (registry.py): The PR description was slightly loose — the change only aligns Test shape (test_op_flatten.py): The model uses a 2D MatMul output with CodeQL duplicate import: trivial — will consolidate to a single |
Remove the redundant 'from onnx import TensorProto, helper' and reference all symbols through the existing 'import onnx' module. Resolves CodeQL py/import-and-import-from finding (alert #34949) on PR microsoft#28340.
|
Addressed the CodeQL Dropped Commit: 5d4947b |
tianleiwu
left a comment
There was a problem hiding this comment.
Summary
The core change is correct and well-targeted. Flatten is a layout-only op with no arithmetic, so its quantization semantics are identical to Reshape/Squeeze/Unsqueeze, and routing it through Direct8BitOp/QDQDirect8BitOp (which reuse input[0]'s scale/zero-point for the output) is the right behavior for any axis. Registering it in QLinearOpsRegistry and QDQRegistry correctly removes the redundant DequantizeLinear -> Flatten -> QuantizeLinear pair, aligning the Python QDQ tooling with the runtime optimizer from #21376. The new test closely mirrors test_op_reshape.py, and the check_op_nodes assertion (Flatten input rewired to the quantized tensor) plus the QDQ count assertions (QuantizeLinear: 3, DequantizeLinear: 4, matching the Reshape baseline) do verify that no extra Q/DQ is inserted.
Verdict: COMMENT — approve-worthy once the two minor items below are considered.
Suggestions (non-blocking)
-
Test exercises a shape-preserving Flatten.
construct_model_matmul_flatten([3, 7], [7, 7], [3, 7])withaxis=1produces output shape[3, 7]identical to the MatMul output, soFlattenis effectively an identity here. The redundant-Q/DQ-removal is still validated, but the test would be more representative of real exported models if it used a higher-rank MatMul output (e.g. a 3D batched MatMul) soFlatten(axis=1)actually collapses dimensions. (This matches the existing automated reviewer note on theconstruct_model_matmul_flattencall.) -
PR description overclaims dynamic-quantization coverage. The description states the change aligns both
quantize_staticandquantize_dynamic, but onlyQLinearOpsRegistry/QDQRegistryare updated.quantize_dynamicusesQuantizationMode.IntegerOps(IntegerOpsRegistry), which is untouched, so dynamic quantization ofFlattenis unchanged. Please either narrow the description to static/QDQ, or registerFlattenfor IntegerOps if dynamic coverage is intended.
For reference, the earlier CodeQL note about onnx being imported via both import and import from is already resolved on the current head (commit consolidated the onnx imports).
Previously construct_model_matmul_flatten used a square weight [7,7] so the MatMul output and Flatten(axis=1) output were both [3,7], making Flatten a no-op identity. Use weight [7,5] and Flatten(axis=0) so the Flatten now collapses a [3,5] tensor to [1,15], exercising a genuine reshape through the Direct8Bit registration.
Summary
FlatteninQLinearOpsRegistry(Direct8BitOp) andQDQRegistry(QDQDirect8BitOp) so the Python QDQ quantizer no longer emits a redundantDequantizeLinear -> Flatten -> QuantizeLinearpair around layout-onlyFlattennodes.test_op_flatten.pycovering bothQOperatorandQDQformats in u8/u8 and s8/s8.Motivation
Fixes #21375. The C++ runtime optimizer was updated in #21376 to drop redundant Q/DQ pairs around
Flatten, mirroring its existingReshapehandling. The Python-layer quantizer was never updated, soquantize_static/quantize_dynamicstill emitted the redundant Q/DQ pair aroundFlattenin QDQ format. This PR aligns the Python tooling with the runtime behavior.Flattenis a layout-only op with no arithmetic — its quantization semantics are identical toReshape,Squeeze, andUnsqueeze, all of which are already registered asDirect8BitOp/QDQDirect8BitOp.Changes
onnxruntime/python/tools/quantization/registry.py: add"Flatten": Direct8BitOptoQLinearOpsRegistryand"Flatten": QDQDirect8BitOptoQDQRegistry.onnxruntime/test/python/quantization/test_op_flatten.py: new test file modeled ontest_op_reshape.pycovering u8/u8 and s8/s8 in both QOperator and QDQ formats; asserts no extraQuantizeLinear/DequantizeLinearis inserted aroundFlatten, and verifies numerical correctness viacheck_model_correctness.Test Plan
python -m pytest onnxruntime/test/python/quantization/test_op_flatten.py -v— 2 tests pass.python -m pytest onnxruntime/test/python/quantization/test_op_reshape.py -v— regression: 2 tests pass.python -m pytest onnxruntime/test/python/quantization/test_op_squeeze_unsqueeze.py -v— regression: 2 tests pass.lintrunner -aclean on changed files.