# # SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import copy import ml_dtypes import numpy as np import onnx import pytest from onnx_graphsurgeon.ir.graph import Graph from onnx_graphsurgeon.ir.node import Node from onnx_graphsurgeon.ir.tensor import Constant, LazyValues, Variable from onnx_graphsurgeon.logger import G_LOGGER from onnx_graphsurgeon.util.misc import SynchronizedList G_LOGGER.severity = G_LOGGER.ULTRA_VERBOSE class TensorBaseTests(object): def test_can_convert_in_place_to_constant(self): tensor = self.tensor.to_constant( values=np.ones((1, 3, 5, 5), dtype=ml_dtypes.bfloat16) ) assert tensor is self.tensor assert isinstance(tensor, Constant) assert isinstance(self.input_node.outputs[0], Constant) assert isinstance(self.output_node.inputs[0], Constant) assert tensor.shape == (1, 3, 5, 5) assert tensor.dtype == ml_dtypes.bfloat16 assert np.all(self.input_node.outputs[0].values == tensor.values) assert np.all(self.output_node.inputs[0].values == tensor.values) def test_can_convert_in_place_to_variable(self): tensor = self.tensor.to_variable(dtype=np.float32, shape=(1, 3, 224, 224)) assert tensor is self.tensor assert isinstance(tensor, Variable) assert isinstance(self.input_node.outputs[0], Variable) assert tensor.dtype == np.float32 assert tensor.shape == (1, 3, 224, 224) assert self.input_node.outputs[0].dtype == tensor.dtype assert self.input_node.outputs[0].shape == tensor.shape def test_equals(self): assert self.tensor == self.tensor def test_set_inputs_updates_old_inputs(self): dummy = Node(op="dummy") self.tensor.inputs = [dummy] assert len(self.input_node.outputs) == 0 assert dummy.outputs[0] == self.tensor def test_set_outputs_updates_old_outputs(self): dummy = Node(op="dummy") self.tensor.outputs = [dummy] assert len(self.output_node.inputs) == 0 assert dummy.inputs[0] == self.tensor def test_can_copy_inputs_from_other_node(self): tensor = Variable(name="other_test_tensor") tensor.inputs = self.tensor.inputs assert tensor.inputs == self.tensor.inputs # Contents should be the same, but it should not just be a reference to the existing SynchronizedList assert tensor.inputs is not self.tensor.inputs def test_can_copy_outputs_from_other_node(self): tensor = Variable(name="other_test_tensor") tensor.outputs = self.tensor.outputs assert tensor.outputs == self.tensor.outputs assert tensor.outputs is not self.tensor.outputs # copy.copy/deepcopy should yield a regular list instead of a synchronized list @pytest.mark.parametrize("copy_func", [copy.copy, copy.deepcopy]) def test_copy_makes_normal_list(self, copy_func): assert isinstance(self.tensor.inputs, SynchronizedList) inputs = copy_func(self.tensor.inputs) assert not isinstance(inputs, SynchronizedList) assert isinstance(inputs, list) def test_i(self): x = Variable(name="x") y = Variable(name="y") node = Node(op="Add", name="Input", inputs=[x], outputs=[y]) assert y.i() == x def test_i_multiple_inputs(self): x = Variable(name="x") x2 = Variable(name="x2") y = Variable(name="y") node = Node(op="Add", name="Input", inputs=[x, x2], outputs=[y]) assert y.i() == x assert y.i(1) == x2 def test_o(self): x = Variable(name="x") y = Variable(name="y") node = Node(op="Add", name="Input", inputs=[x], outputs=[y]) assert x.o() == y def test_o_multiple_outputs(self): x = Variable(name="x") y = Variable(name="y") y2 = Variable(name="y2") node = Node(op="Add", name="Input", inputs=[x], outputs=[y]) node2 = Node(op="Add", name="Input", inputs=[x], outputs=[y2]) assert x.o() == y assert x.o(1) == y2 class TestVariable(TensorBaseTests): def setup_method(self): self.tensor = Variable( name="test_tensor", dtype=np.float32, shape=(1, 3, 224, 224) ) self.input_node = Node(op="Add", outputs=[self.tensor]) self.output_node = Node(op="Add", inputs=[self.tensor]) def test_equals_name_mismatch(self): tensor = Variable(name="test_tensor0", dtype=np.float32, shape=(1, 3, 224, 224)) assert not self.tensor == tensor class TestConstant(TensorBaseTests): def setup_method(self): self.tensor = Constant( name="test_tensor", values=np.ones((1, 3, 5, 5), dtype=np.float64), ) self.input_node = Node( op="Add", outputs=[self.tensor] ) # Doesn't make sense for Constants, but needed to make base tests happy. self.output_node = Node(op="Add", inputs=[self.tensor]) def test_can_get_shape(self): assert self.tensor.shape == (1, 3, 5, 5) def test_can_get_dtype(self): assert self.tensor.dtype == np.float64 @pytest.fixture def node_with_nested_subgraphs(): inner_subgraph = Graph(name="inner_subgraph") outer_subgraph_1 = Graph(name="subgraph1") outer_subgraph_2 = Graph( name="subgraph2", nodes=[Node("Add", attrs={"x": inner_subgraph})] ) node = Node(op="Add", attrs={"x": outer_subgraph_1, "y": outer_subgraph_2, "z": 5}) return node class TestNode(object): def setup_method(self): self.input_tensor = Variable(name="x") self.output_tensor = Variable(name="y") self.node = Node( op="Add", name="Test", inputs=[self.input_tensor], outputs=[self.output_tensor], ) def test_equals(self): assert self.node == self.node def test_equals_name_mismatch(self): node = Node(op="Add", name="OtherTest") assert not self.node == node def test_equals_op_mismatch(self): node = Node(op="Subtract", name="Test") assert not self.node == node def test_equals_num_inputs_mismatch(self): node = Node(op="Subtract", name="Test") assert not self.node == node def test_equals(self): assert self.node == self.node def test_equals_inputs_mismatch(self): tensor = Variable(name="other_tensor") assert not self.input_tensor == tensor node = Node(op="Add", name="Test", inputs=[tensor]) assert not self.node == node def test_set_inputs_updates_old_inputs(self): dummy = Variable(name="dummy") self.node.inputs = [dummy] assert len(self.input_tensor.outputs) == 0 assert dummy.outputs[0] == self.node def test_set_outputs_updates_old_outputs(self): dummy = Variable(name="dummy") self.node.outputs = [dummy] assert len(self.output_tensor.inputs) == 0 assert dummy.inputs[0] == self.node def test_can_copy_inputs_from_other_node(self): node = Node(op="Subtract") node.inputs = self.node.inputs assert node.inputs == self.node.inputs # Contents should be the same, but it should not just be a reference to the existing SynchronizedList assert node.inputs is not self.node.inputs def test_can_copy_outputs_from_other_node(self): node = Node(op="Subtract") node.outputs = self.node.outputs assert node.outputs == self.node.outputs assert node.outputs is not self.node.outputs def test_i(self): intermediate_tensor = Variable(name="intermediate") input_node = Node( op="Add", name="Input", inputs=[self.input_tensor], outputs=[intermediate_tensor], ) output_node = Node( op="Add", name="Out", inputs=[intermediate_tensor], outputs=[self.output_tensor], ) assert output_node.i() == input_node def test_i_multiple_inputs(self): intermediate_tensor = Variable(name="intermediate") intermediate_tensor2 = Variable(name="intermediate2") input_node = Node( op="Add", name="Input", inputs=[self.input_tensor], outputs=[intermediate_tensor], ) input_node2 = Node( op="Add", name="Input2", inputs=[self.input_tensor], outputs=[intermediate_tensor2], ) output_node = Node( op="Add", name="Out", inputs=[intermediate_tensor, intermediate_tensor2], outputs=[self.output_tensor], ) assert output_node.i() == input_node assert output_node.i(1) == input_node2 def test_o(self): intermediate_tensor = Variable(name="intermediate") input_node = Node( op="Add", name="Input", inputs=[self.input_tensor], outputs=[intermediate_tensor], ) output_node = Node( op="Add", name="Out", inputs=[intermediate_tensor], outputs=[self.output_tensor], ) assert input_node.o() == output_node def test_o_multiple_outputs(self): intermediate_tensor = Variable(name="intermediate") intermediate_tensor2 = Variable(name="intermediate2") input_node = Node( op="Add", name="Input", inputs=[self.input_tensor], outputs=[intermediate_tensor], ) output_node = Node( op="Add", name="Out", inputs=[intermediate_tensor], outputs=[self.output_tensor], ) output_node2 = Node( op="Add", name="Input2", inputs=[intermediate_tensor], outputs=[intermediate_tensor2], ) assert input_node.o() == output_node assert input_node.o(1) == output_node2 def test_domain(self): node = Node(op="Add", domain="test") assert node.domain == "test" def test_subgraphs_not_recursive(self, node_with_nested_subgraphs): unrelated_graph = Graph(name="unrelated") subgraph_names = { subgraph.name for subgraph in node_with_nested_subgraphs.subgraphs() } assert subgraph_names == {"subgraph1", "subgraph2"} def test_subgraphs_recursive(self, node_with_nested_subgraphs): unrelated_graph = Graph(name="unrelated") subgraph_names = { subgraph.name for subgraph in node_with_nested_subgraphs.subgraphs(recursive=True) } assert subgraph_names == {"subgraph1", "subgraph2", "inner_subgraph"} class TestNodeIO(object): def setup_method(self, field_names): self.tensors = [ Variable( name="test_tensor_{:}".format(i), dtype=np.float32, shape=(1, 3, 224, 224), ) for i in range(10) ] self.node = Node(op="Dummy") def get_lists(self, field_names): return getattr(self.node, field_names[0]), field_names[1] @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_append(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.append(self.tensors[0]) assert nlist[0] == self.tensors[0] assert getattr(self.tensors[0], tensor_field)[0] == self.node @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_extend(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.extend(self.tensors) for tensor in self.tensors: assert tensor in nlist assert getattr(tensor, tensor_field)[0] == self.node @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_insert(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.append(self.tensors[1]) nlist.insert(0, self.tensors[0]) assert nlist[0] == self.tensors[0] assert getattr(self.tensors[0], tensor_field)[0] == self.node @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_remove(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.append(self.tensors[0]) nlist.remove(self.tensors[0]) assert len(nlist) == 0 assert len(getattr(self.tensors[0], tensor_field)) == 0 @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_pop(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.append(self.tensors[0]) tensor = nlist.pop() assert len(nlist) == 0 assert len(getattr(tensor, tensor_field)) == 0 @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_pop_index(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.extend(self.tensors) tensor = nlist.pop(1) assert self.tensors[1] not in nlist assert len(getattr(tensor, tensor_field)) == 0 @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_del_index(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.extend(self.tensors) tensor = nlist[1] del nlist[1] assert self.tensors[1] not in nlist assert len(getattr(tensor, tensor_field)) == 0 @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_clear(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.extend(self.tensors) nlist.clear() assert len(nlist) == 0 assert all([len(getattr(tensor, tensor_field)) == 0 for tensor in self.tensors]) @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_add(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist = nlist + self.tensors for tensor in self.tensors: assert tensor in nlist @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_iadd(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist += self.tensors for tensor in self.tensors: assert tensor in nlist assert getattr(tensor, tensor_field)[0] == self.node @pytest.mark.parametrize( "field_names", [("inputs", "outputs"), ("outputs", "inputs")] ) def test_setitem(self, field_names): nlist, tensor_field = self.get_lists(field_names) nlist.append(self.tensors[0]) new_tensor = Variable("new_tensor") nlist[0] = new_tensor assert nlist[0] == new_tensor assert len(getattr(self.tensors[0], tensor_field)) == 0 assert getattr(new_tensor, tensor_field)[0] == self.node def test_iadd_on_node_directly(self): t0 = Variable("t0") n0 = Node("", inputs=[]) n0.inputs += [t0] assert len(n0.inputs) == 1 assert n0.inputs[0] == t0 class TestTensorIO(object): def test_iadd_on_tensor_directly(self): n0 = Node("") t0 = Variable("t0") t0.inputs += [n0] assert len(t0.inputs) == 1 assert t0.inputs[0] == n0 class TestLazyValues(object): def test_basic(self): shape = (1, 5, 5) onnx_tensor = onnx.helper.make_tensor_value_info( "test", onnx.TensorProto.FLOAT, shape ) values = LazyValues(onnx_tensor) assert values.dtype == np.float32 assert tuple(values.shape) == shape assert values.nbytes == 100