Reuse QDQConv for ConvTranspose to generate the QDQ model (#15385)

### Description
Reuse QDQConv for ConvTranspose to generate the QDQ model

### Motivation and Context
Generate the correct QDQ model
This commit is contained in:
Hector Li 2023-04-06 15:07:44 -07:00 committed by GitHub
parent 2bd8e4a130
commit 276c0a00e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 1 deletions

View file

@ -238,7 +238,7 @@ class QDQConv(QDQOperatorBase):
def quantize(self):
node = self.node
assert node.op_type == "Conv"
assert node.op_type == "Conv" or node.op_type == "ConvTranspose"
self.quantizer.quantize_activation_tensor(node.input[0])
if not self.disable_qdq_for_node_output:

View file

@ -65,6 +65,7 @@ QLinearOpsRegistry.update(CommonOpsRegistry)
QDQRegistry = {
"Conv": QDQConv,
"ConvTranspose": QDQConv,
"Gemm": QDQGemm,
"Clip": QDQRemovableActivation,
"Relu": QDQRemovableActivation,

View file

@ -0,0 +1,168 @@
#!/usr/bin/env python
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
"""
Tests quantization of ConvTranspose operator.
"""
import unittest
import numpy as np
import onnx
from onnx import TensorProto, helper
from op_test_utils import TestDataFeeds, check_model_correctness, check_op_type_count, check_qtype_by_node_type
from onnxruntime.quantization import QuantFormat, QuantType, quantize_static
class TestOpConvTranspose(unittest.TestCase):
"""
Class with test_* methods that test quantization of the ConvTranspose operator.
"""
def input_feeds(self, num_test_inputs, name2shape):
"""
Returns a data reader of input test data.
:param num_test_inputs: The number of testing inputs to generate.
:param name2shape: A dictionary mapping the name of an input to its shape.
:return: A data reader.
"""
input_data_list = []
for _ in range(num_test_inputs):
inputs = {}
for name, shape in name2shape.items():
inputs.update({name: np.random.randint(-1, 2, shape).astype(np.float32)})
input_data_list.extend([inputs])
data_reader = TestDataFeeds(input_data_list)
return data_reader
def construct_model(self, output_model_path):
"""
Constructs an ONNX model containing a single ConvTranspose node, and saves
the model to the specified output path.
:param output_model_path: The output filepath in which to save the model.
"""
input_tensor = helper.make_tensor_value_info("input", TensorProto.FLOAT, [1, 1, 7, 7])
output_tensor = helper.make_tensor_value_info("output", TensorProto.FLOAT, [1, 1, 8, 8])
ini_w = helper.make_tensor("weight", TensorProto.FLOAT, [1, 1, 2, 2], [1.0, 1.0, 1.0, 1.0])
ini_b = helper.make_tensor("bias", TensorProto.FLOAT, [1], [0.17])
conv_tranpose_node = onnx.helper.make_node(
"ConvTranspose",
["input", "weight", "bias"],
["output"],
kernel_shape=[2, 2],
output_padding=[0, 0],
pads=[0, 0, 0, 0],
strides=[1, 1],
dilations=[1, 1],
group=1,
)
graph = helper.make_graph(
[conv_tranpose_node],
"conv_transpose_test",
[input_tensor],
[output_tensor],
initializer=[ini_w, ini_b],
)
model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)])
model.ir_version = 7 # use stable onnx ir version
onnx.save(model, output_model_path)
def static_quant_test_qdq(
self,
model_fp32_path,
data_reader,
activation_type,
weight_type,
extra_options=None,
):
"""
Quantizes an FP32 model and checks various properties about the generated quantized model.
:param model_fp32_path: The path to the FP32 ONNX model to quantize and test.
:param data_reader: Data reader that generates input data.
:param activation_type: The quantized type for activations. One of QuantType.QUInt8 or QuantType.QInt8.
:param weight_type: The quantized type for weights. One of QuantType.QUInt8 or QuantType.QInt8.
:param extra_options: Dictionary of extra options for the quantize_static() function.
"""
activation_proto_qtype = TensorProto.UINT8 if activation_type == QuantType.QUInt8 else TensorProto.INT8
activation_type_str = "u8" if (activation_type == QuantType.QUInt8) else "s8"
weight_type_str = "u8" if (weight_type == QuantType.QUInt8) else "s8"
model_int8_path = f"conv_transpose_fp32.quant_dqd_{activation_type_str}{weight_type_str}.onnx"
data_reader.rewind()
quantize_static(
model_fp32_path,
model_int8_path,
data_reader,
quant_format=QuantFormat.QDQ,
activation_type=activation_type,
weight_type=weight_type,
extra_options=extra_options,
)
# Check node counts in quantized model.
quant_node_counts = {"ConvTranspose": 1, "QuantizeLinear": 2, "DequantizeLinear": 4}
check_op_type_count(self, model_int8_path, **quant_node_counts)
# Check input/output types for QuantizeLinear nodes.
qnode_io_qtypes = {
"QuantizeLinear": [
["i", 2, activation_proto_qtype],
["o", 0, activation_proto_qtype],
]
}
check_qtype_by_node_type(self, model_int8_path, qnode_io_qtypes)
# Check model correctness.
data_reader.rewind()
check_model_correctness(self, model_fp32_path, model_int8_path, data_reader.get_next())
def test_quantize_conv_transpose_u8u8(self):
"""
Unit test that quantizes (uint8) an ONNX model containing an ConvTranspose operator.
"""
np.random.seed(1)
model_fp32_path = "conv_transpose_fp32.onnx"
self.construct_model(model_fp32_path)
data_reader = self.input_feeds(1, {"input": [1, 1, 7, 7]})
self.static_quant_test_qdq(
model_fp32_path,
data_reader,
activation_type=QuantType.QUInt8,
weight_type=QuantType.QUInt8,
)
def test_quantize_conv_transpose_s8s8(self):
"""
Unit test that quantizes (int8) an ONNX model containing an ConvTranspose operator.
"""
np.random.seed(1)
model_fp32_path = "conv_transpose_fp32.onnx"
self.construct_model(model_fp32_path)
data_reader = self.input_feeds(1, {"input": [1, 1, 7, 7]})
self.static_quant_test_qdq(
model_fp32_path,
data_reader,
activation_type=QuantType.QInt8,
weight_type=QuantType.QInt8,
extra_options={"ActivationSymmetric": True},
)
if __name__ == "__main__":
unittest.main()