diff --git a/onnxruntime/python/tools/quantization/operators/pad.py b/onnxruntime/python/tools/quantization/operators/pad.py index ba8f6c9f9c..e7eeac2cec 100644 --- a/onnxruntime/python/tools/quantization/operators/pad.py +++ b/onnxruntime/python/tools/quantization/operators/pad.py @@ -70,8 +70,8 @@ class QPad(QuantOperatorBase): quantized_input_value.scale_name, quantized_input_value.zp_name, ) - self.quantizer.new_nodes += [pad_value_qnodes] - node.input[2] = pad_value_qnodes.output[0] + self.quantizer.new_nodes.extend(pad_value_qnodes) + node.input[2] = pad_value_qnodes[0].output[0] else: node.input.extend([quantized_input_value.zp_name]) # pad zero_point for original zero diff --git a/onnxruntime/test/python/quantization/test_op_pad.py b/onnxruntime/test/python/quantization/test_op_pad.py index df288826be..d3680ea9f2 100644 --- a/onnxruntime/test/python/quantization/test_op_pad.py +++ b/onnxruntime/test/python/quantization/test_op_pad.py @@ -187,22 +187,37 @@ class TestOpQuatizerPad(unittest.TestCase): atol=0.05, activation_type=QuantType.QUInt8, weight_type=QuantType.QUInt8, - extra_options={}, + extra_options=None, ): + if extra_options is None: + extra_options = {} np.random.seed(108) tag_pad_mode = pad_mode if pad_mode is not None else "none" tag_constant_value = "" if constant_value is None else "_value" model_fp32_path = "qop_pad_{}_fp32_{}{}.onnx".format(quantize_mode, tag_pad_mode, tag_constant_value) - data_reader = self.input_feeds(1, {"input": [1, 8, 33, 33]}) - self.construct_model_conv_pad( - model_fp32_path, - [1, 8, 33, 33], - [16, 8, 3, 3], - [1, 16, 31, 31], - pad_mode, - [0, 0, 1, 2, 0, 0, 3, 4], - constant_value=constant_value, - ) + edge_case = "dual_feed" in extra_options and extra_options["dual_feed"] and constant_value is not None + if edge_case: + data_reader = self.input_feeds(1, {"input": [1, 8, 33, 33], "padding_value": [1]}) + self.construct_edge_case_model( + model_fp32_path, + [1, 8, 33, 33], + [16, 8, 3, 3], + [1, 16, 31, 31], + pad_mode, + [0, 0, 1, 2, 0, 0, 3, 4], + constant_value=constant_value, + ) + else: + data_reader = self.input_feeds(1, {"input": [1, 8, 33, 33]}) + self.construct_model_conv_pad( + model_fp32_path, + [1, 8, 33, 33], + [16, 8, 3, 3], + [1, 16, 31, 31], + pad_mode, + [0, 0, 1, 2, 0, 0, 3, 4], + constant_value=constant_value, + ) activation_proto_qtype = TensorProto.UINT8 if activation_type == QuantType.QUInt8 else TensorProto.INT8 activation_type_str = "u8" if (activation_type == QuantType.QUInt8) else "s8" @@ -229,7 +244,8 @@ class TestOpQuatizerPad(unittest.TestCase): if quantize_mode != "static": kwargs = {"DynamicQuantizeLinear": 1} if activation_type == QuantType.QUInt8 else {"QuantizeLinear": 1} else: - kwargs = {"DequantizeLinear": 2, "QuantizeLinear": 1} + # edge case will have 2 graph inputs + kwargs = {"DequantizeLinear": 2, "QuantizeLinear": 2 if edge_case else 1} check_op_type_count(self, model_i8_path, **kwargs) # check node input/output type if such node exists in the graph qnode_io_qtypes = { @@ -335,6 +351,59 @@ class TestOpQuatizerPad(unittest.TestCase): # def test_dynamic_mode_constant_value_s8s8(self): # self.verify_quantize_with_pad_mode('constant', constant_value=3.75, quantize_mode='dynamic', activation_type=QuantType.QInt8, # weight_type=QuantType.QInt8, extra_options={'ActivationSymmetric': True}) + def construct_edge_case_model( + self, + output_model_path, + conv_input_shape, + conv_weight_shape, + pad_input_shape, + pad_mode, + pad_dims, + constant_value=None, + ): + # (input) + # \ + # Conv (padding_value) + # / \ / + # Identity Pad + # / \ + # (identity_out) (output) + rank = len(pad_input_shape) + self.assertEqual(rank * 2, len(pad_dims)) + + input_tensor = helper.make_tensor_value_info("input", TensorProto.FLOAT, conv_input_shape) + conv_weight_arr = np.random.randint(-1, 2, conv_weight_shape).astype(np.float32) + conv_weight_initializer = onnx.numpy_helper.from_array(conv_weight_arr, name="conv1_weight") + conv_node = onnx.helper.make_node("Conv", ["input", "conv1_weight"], ["conv_output"], name="conv_node") + + identity_out = helper.make_tensor_value_info("identity_out", TensorProto.FLOAT, pad_input_shape) + identity_node = helper.make_node("Identity", ["conv_output"], ["identity_out"], name="IdentityNode") + + pad_dims_initializer = helper.make_tensor("pad_dims", TensorProto.INT64, [2 * rank], pad_dims) + output_shape = [sum(e) for e in list(zip(pad_input_shape, pad_dims[:rank], pad_dims[rank:]))] + output_tensor = helper.make_tensor_value_info("output", TensorProto.FLOAT, output_shape) + pad_inputs = ["conv_output", "pad_dims"] + initializers = [conv_weight_initializer, pad_dims_initializer] + constant_value_tensor = helper.make_tensor_value_info("padding_value", TensorProto.FLOAT, [1]) + pad_inputs.extend(["padding_value"]) + kwargs = {"mode": pad_mode} if pad_mode is not None else {} + pad_node = helper.make_node("Pad", pad_inputs, ["output"], name="pad_node", **kwargs) + + graph = helper.make_graph( + [conv_node, identity_node, pad_node], + "TestOpQuantizerPad_test_model", + [input_tensor, constant_value_tensor], + [identity_out, output_tensor], + initializer=initializers, + ) + 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 test_static_mode_constant_value_edge_case(self): + self.verify_quantize_with_pad_mode( + "constant", constant_value=0.1, quantize_mode="static", extra_options={"dual_feed": True} + ) if __name__ == "__main__":