From 0fed00c04d065d9b10c97261ae066280b40f7914 Mon Sep 17 00:00:00 2001 From: Yufeng Li Date: Thu, 18 May 2023 13:43:52 -0700 Subject: [PATCH] fix topo sort in quantization tool (#16003) ### Description Should not set up dependent node list for empty('') input ### Motivation and Context --- .../python/tools/quantization/onnx_model.py | 2 + .../python/quantization/test_onnx_model.py | 208 +++++++++++------- 2 files changed, 132 insertions(+), 78 deletions(-) diff --git a/onnxruntime/python/tools/quantization/onnx_model.py b/onnxruntime/python/tools/quantization/onnx_model.py index 6fecfb7e96..cb7836ab28 100644 --- a/onnxruntime/python/tools/quantization/onnx_model.py +++ b/onnxruntime/python/tools/quantization/onnx_model.py @@ -414,6 +414,8 @@ class ONNXModel: continue for input_name in node.input: + if not input_name: + continue if input_name not in deps_to_nodes: deps_to_nodes[input_name] = [node_idx] else: diff --git a/onnxruntime/test/python/quantization/test_onnx_model.py b/onnxruntime/test/python/quantization/test_onnx_model.py index 59f408b1ee..bdffae87fa 100644 --- a/onnxruntime/test/python/quantization/test_onnx_model.py +++ b/onnxruntime/test/python/quantization/test_onnx_model.py @@ -5,14 +5,15 @@ # license information. # -------------------------------------------------------------------------- +import tempfile import unittest +from pathlib import Path import numpy as np import onnx from onnx import TensorProto, helper, numpy_helper -from op_test_utils import TestDataFeeds, check_model_correctness, check_op_type_count, check_op_type_order # noqa: F401 +from op_test_utils import check_op_type_order -import onnxruntime # noqa: F401 from onnxruntime.quantization.onnx_model import ONNXModel @@ -25,97 +26,148 @@ def generate_input_initializer(tensor_shape, tensor_dtype, input_name): return init +def construct_model_for_topo_sort(model_path): + # (input) + # | + # GRU + # / \ + # Conv(1) \ + # | \ + # Relu Conv(2) + # | | + # \ / + # Add + # | + # (output) + initializers = [] + input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [4, 8, 12]) + output = helper.make_tensor_value_info("output", TensorProto.FLOAT, [4, 2, 8, 8]) + + # make GRU + initializers.append(generate_input_initializer([2, 24, 12], np.float32, "W_GRU")) + initializers.append(generate_input_initializer([2, 24, 8], np.float32, "R_GRU")) + initializers.append(generate_input_initializer([2, 8, 8], np.float32, "H_GRU")) + gru_node = helper.make_node( + "GRU", + ["input", "W_GRU", "R_GRU", "", "", "H_GRU"], + ["GRU_O"], + hidden_size=8, + direction="bidirectional", + ) + + initializers.append(generate_input_initializer([2, 2, 1, 1], np.float32, "W1")) + initializers.append(generate_input_initializer([2, 2, 1, 1], np.float32, "W2")) + initializers.append(generate_input_initializer([2], np.float32, "B1")) + initializers.append(generate_input_initializer([2], np.float32, "B2")) + conv_node_1 = helper.make_node("Conv", ["GRU_O", "W1", "B1"], ["Conv1_O"], name="Conv1") + conv_node_2 = helper.make_node("Conv", ["GRU_O", "W2", "B2"], ["Conv2_O"], name="Conv2") + relu_node = helper.make_node("Relu", ["Conv1_O"], ["Relu_O"], name="Relu") + add_node = helper.make_node("Add", ["Relu_O", "Conv2_O"], ["output"], name="Add") + graph = helper.make_graph( + [conv_node_1, relu_node, conv_node_2, gru_node, add_node], + "onnx_model_test", + [input], + [output], + initializer=initializers, + ) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)]) + onnx.save(model, model_path) + + +def construct_model_for_topo_sort_constant(model_path): + # (input) Constant + # \ / + # \ / + # \ / + # \ / + # Add + # | + # (output) + + initializers = [] + input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [4, 8, 12]) + output = helper.make_tensor_value_info("output", TensorProto.FLOAT, [4, 8, 12]) + + # make nodes + constant_node = helper.make_node("Constant", [], ["const_output"], value_float=42.0) + add_node = helper.make_node("Add", ["input", "const_output"], ["output"], name="Add") + graph = helper.make_graph( + [add_node, constant_node], + "onnx_model_test", + [input], + [output], + initializer=initializers, + ) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)]) + onnx.save(model, model_path) + + +def construct_model_for_topo_sort_empty_input_output(model_path): + # (input1) (input2) + # | | + # Op1 Op1 + # \ / + # \ / + # \ / + # \ / + # Op2 + # | + # Op3 + # | + # (output) + + input1 = helper.make_tensor_value_info("input1", TensorProto.FLOAT, [4, 8, 12]) + input2 = helper.make_tensor_value_info("input2", TensorProto.FLOAT, [4, 8, 12]) + output = helper.make_tensor_value_info("output", TensorProto.FLOAT, [4, 8, 12]) + + # make nodes + op1_node_1 = helper.make_node("Op1", ["input1"], ["", "", "Op1_1_output"], name="op1_1", domain="Test") + op1_node_2 = helper.make_node("Op1", ["input2"], ["", "", "Op1_2_output"], name="op1_2", domain="Test") + op2_node = helper.make_node("Op2", ["Op1_1_output", "Op1_2_output"], ["op2_output"], name="op2", domain="Test") + op3_node = helper.make_node("Op3", ["", "op2_output"], ["output"], name="op3", domain="Test") + graph = helper.make_graph( + [op1_node_1, op1_node_2, op3_node, op2_node], + "onnx_model_topo_test", + [input1, input2], + [output], + ) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("Test", 1), helper.make_opsetid("", 13)]) + onnx.save(model, model_path) + + class TestONNXModel(unittest.TestCase): - def construct_model(self, model_path): - # (input) - # | - # GRU - # / \ - # Conv(1) \ - # | \ - # Relu Conv(2) - # | | - # \ / - # Add - # | - # (output) - initializers = [] - input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [4, 8, 12]) - output = helper.make_tensor_value_info("output", TensorProto.FLOAT, [4, 2, 8, 8]) + @classmethod + def setUpClass(cls): + cls._tmp_model_dir = tempfile.TemporaryDirectory(prefix="test_onnx_model.") - # make GRU - initializers.append(generate_input_initializer([2, 24, 12], np.float32, "W_GRU")) - initializers.append(generate_input_initializer([2, 24, 8], np.float32, "R_GRU")) - initializers.append(generate_input_initializer([2, 8, 8], np.float32, "H_GRU")) - gru_node = onnx.helper.make_node( - "GRU", - ["input", "W_GRU", "R_GRU", "", "", "H_GRU"], - ["GRU_O"], - hidden_size=8, - direction="bidirectional", - ) - - initializers.append(generate_input_initializer([2, 2, 1, 1], np.float32, "W1")) - initializers.append(generate_input_initializer([2, 2, 1, 1], np.float32, "W2")) - initializers.append(generate_input_initializer([2], np.float32, "B1")) - initializers.append(generate_input_initializer([2], np.float32, "B2")) - conv_node_1 = onnx.helper.make_node("Conv", ["GRU_O", "W1", "B1"], ["Conv1_O"], name="Conv1") - conv_node_2 = onnx.helper.make_node("Conv", ["GRU_O", "W2", "B2"], ["Conv2_O"], name="Conv2") - relu_node = onnx.helper.make_node("Relu", ["Conv1_O"], ["Relu_O"], name="Relu") - add_node = onnx.helper.make_node("Add", ["Relu_O", "Conv2_O"], ["output"], name="Add") - graph = helper.make_graph( - [conv_node_1, relu_node, conv_node_2, gru_node, add_node], - "onnx_model_test", - [input], - [output], - initializer=initializers, - ) - model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)]) - onnx.save(model, model_path) - - def construct_model_Constant(self, model_path): # noqa: N802 - # (input) Constant - # \ / - # \ / - # \ / - # \ / - # Add - # | - # (output) - - initializers = [] - input = helper.make_tensor_value_info("input", TensorProto.FLOAT, [4, 8, 12]) - output = helper.make_tensor_value_info("output", TensorProto.FLOAT, [4, 8, 12]) - - # make nodes - constant_node = onnx.helper.make_node("Constant", [], ["const_output"], value_float=42.0) - add_node = onnx.helper.make_node("Add", ["input", "const_output"], ["output"], name="Add") - graph = helper.make_graph( - [add_node, constant_node], - "onnx_model_test", - [input], - [output], - initializer=initializers, - ) - model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 13)]) - onnx.save(model, model_path) + @classmethod + def tearDownClass(cls): + cls._tmp_model_dir.cleanup() def test_topo_sort(self): - test_model_path = "onnx_model_topo_sort.onnx" - self.construct_model(test_model_path) + test_model_path = str(Path(self._tmp_model_dir.name) / "onnx_model_topo_sort.onnx") + construct_model_for_topo_sort(test_model_path) onnx_model = ONNXModel(onnx.load(test_model_path)) check_op_type_order(self, onnx_model.model, ["Conv", "Relu", "Conv", "GRU", "Add"]) onnx_model.topological_sort() check_op_type_order(self, onnx_model.model, ["GRU", "Conv", "Conv", "Relu", "Add"]) def test_topo_sort_constant(self): - test_model_path = "onnx_model_topo_sort_constant.onnx" - self.construct_model_Constant(test_model_path) + test_model_path = str(Path(self._tmp_model_dir.name) / "onnx_model_topo_sort_constant.onnx") + construct_model_for_topo_sort_constant(test_model_path) onnx_model = ONNXModel(onnx.load(test_model_path)) check_op_type_order(self, onnx_model.model, ["Add", "Constant"]) onnx_model.topological_sort() check_op_type_order(self, onnx_model.model, ["Constant", "Add"]) + def test_topo_sort_empty_input_output(self): + test_model_path = str(Path(self._tmp_model_dir.name) / "onnx_model_topo_empty_input_output.onnx") + construct_model_for_topo_sort_empty_input_output(test_model_path) + onnx_model = ONNXModel(onnx.load(test_model_path)) + check_op_type_order(self, onnx_model.model, ["Op1", "Op1", "Op3", "Op2"]) + onnx_model.topological_sort() + check_op_type_order(self, onnx_model.model, ["Op1", "Op1", "Op2", "Op3"]) + if __name__ == "__main__": unittest.main()