diff --git a/backends/nxp/backend/ir/converter/node_converters/ops_converters/view_copy_converter.py b/backends/nxp/backend/ir/converter/node_converters/ops_converters/view_copy_converter.py index 8b497de68f7..88fbccf7d5c 100644 --- a/backends/nxp/backend/ir/converter/node_converters/ops_converters/view_copy_converter.py +++ b/backends/nxp/backend/ir/converter/node_converters/ops_converters/view_copy_converter.py @@ -4,21 +4,14 @@ # LICENSE file in the root directory of this source tree. import numpy as np -from executorch.backends.nxp.backend.data_format import NXP_NODE_FORMAT from executorch.backends.nxp.backend.edge_helper import ( - get_non_qdq_users, input_tensor, output_tensor, tensor_rank, ) from executorch.backends.nxp.backend.ir.converter import quantization_utils from executorch.backends.nxp.backend.ir.converter.conversion.common import OpsList -from executorch.backends.nxp.backend.ir.converter.conversion.translator import ( - apply_permutation_to, - create_channels_first_to_channels_last_permutation, - create_channels_last_to_channels_first_permutation, -) from executorch.backends.nxp.backend.ir.converter.node_converter import ( CustomDelegationOptions, NodeConverter, @@ -29,13 +22,7 @@ from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options import ( reshape_options, ) -from executorch.backends.nxp.backend.neutron_operator_support import ( - transposition_is_supported_on_neutron, -) -from executorch.backends.nxp.backend.neutron_target_spec import NeutronTargetSpec -from executorch.exir.dialects._ops import ops as exir_ops from torch.fx import Node -from torch.fx.passes.infra.partitioner import Partition from torch.nn import Parameter @@ -58,92 +45,6 @@ def _is_supported_in_IR( return True - @classmethod - def supports_partitioning_result( - cls, - node: Node, - partition_list: list[Partition], - custom_delegation_options: CustomDelegationOptions, - neutron_target_spec: NeutronTargetSpec, - parameters_mapping: dict[str, Parameter], - ): - view_copy_partitions = [ - partition for partition in partition_list if node in partition.nodes - ] - assert len(view_copy_partitions) == 1 - - input_format = node.args[0].meta[NXP_NODE_FORMAT] - output_format = node.meta[NXP_NODE_FORMAT] - input_shape = list(node.args[0].meta["val"].shape) - output_shape = list(node.meta["val"].shape) - to_nchw_perm = create_channels_last_to_channels_first_permutation( - len(input_shape), True - ) - to_nhwc_perm = create_channels_first_to_channels_last_permutation( - len(output_shape), True - ) - channels_last_input_shape = apply_permutation_to( - input_shape, - create_channels_first_to_channels_last_permutation(len(input_shape), True), - ) - - if input_format.is_channels_first() and (not output_format.is_channels_first()): - # The `view_copy` removes node format. Conversion will require the addition of a `Transpose` operator. - # Make sure the `Transpose` will be supported. - - if not transposition_is_supported_on_neutron( - channels_last_input_shape, to_nchw_perm, neutron_target_spec - ): - # The `Transpose` would have to be removed by the `PermuteFullyConnectedWeightsAfterReshape` pass. - # Make sure it will be applied. - users = get_non_qdq_users(node) - if len(users) != 1 or (linear_node := users[0]).target not in [ - exir_ops.edge.aten.addmm.default, - exir_ops.edge.aten.mm.default, - ]: - return False - - if linear_node not in view_copy_partitions[0].nodes: - # The `mm` / `addmm` node will not be delegated within this partition. - return False - - # Make sure the specific requirements of the `PermuteFullyConnectedWeightsAfterReshape` are satisfied. - weights_index = ( - 2 if linear_node.target == exir_ops.edge.aten.addmm.default else 1 - ) - if not ( - input_shape[0] == output_shape[0] # Preserve batch. - and len(output_shape) == 2 - and output_shape[1] - == linear_node.args[weights_index].meta["val"].shape[0] - ): - return False - - elif ( - not input_format.is_channels_first() - ) and output_format.is_channels_first(): - # The `view_copy` introduces node format. Conversion will require the addition of a `Transpose` operator. - # Make sure the `Transpose` will be supported. - if not transposition_is_supported_on_neutron( - output_shape, to_nhwc_perm, neutron_target_spec - ): - return False - - elif input_format.is_channels_first() and output_format.is_channels_first(): - # The `view_copy` works with the channels first format, so both tensors will end up being transposed. - # Make sure these transpositions are supported. - if not ( - transposition_is_supported_on_neutron( - channels_last_input_shape, to_nchw_perm, neutron_target_spec - ) - and transposition_is_supported_on_neutron( - output_shape, to_nhwc_perm, neutron_target_spec - ) - ): - return False - - return True - @staticmethod def _safe_compute_flat_size(shape: list[int | str]) -> int: """Compute the flat size of a tensor with given shape. Strings and negative dimensions are treated as '1'. diff --git a/backends/nxp/tests/ir/converter/node_converter/test_view_copy_converter.py b/backends/nxp/tests/ir/converter/node_converter/test_view_copy_converter.py index d38fd918e15..2cb8ad05006 100644 --- a/backends/nxp/tests/ir/converter/node_converter/test_view_copy_converter.py +++ b/backends/nxp/tests/ir/converter/node_converter/test_view_copy_converter.py @@ -8,37 +8,28 @@ import numpy as np import pytest import torch -from executorch.backends.nxp.backend.edge_program_converter import ( - EdgeProgramToIRConverter, -) -from executorch.backends.nxp.backend.ir.converter.builder.model_builder import ( - ModelBuilder, -) -from executorch.backends.nxp.backend.ir.tflite_generator.builtin_options.reshape_options import ( - Reshape, -) from executorch.backends.nxp.tests.dataset_creator import RandomDatasetCreator -from executorch.backends.nxp.tests.executorch_pipeline import ( - to_edge_program, - to_quantized_edge_program, -) -from executorch.backends.nxp.tests.executors import ( - convert_run_compare, - graph_contains_any_of_ops, - ToChannelFirstPreprocess, - ToChannelLastPreprocess, -) +from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program +from executorch.backends.nxp.tests.executors import graph_contains_any_of_ops from executorch.backends.nxp.tests.graph_verifier import DetailedGraphVerifier from executorch.backends.nxp.tests.model_output_comparator import ( AllCloseOutputComparator, ) from executorch.backends.nxp.tests.nsys_testing import lower_run_compare -from executorch.backends.nxp.tests.ops_aliases import Convolution, ViewCopy +from executorch.backends.nxp.tests.ops_aliases import ( + AddMM, + AddTensor, + AvgPool2D, + Convolution, + ExecutorchDelegateCall, + MM, + PermuteCopy, + Relu, + ViewCopy, +) from torch import nn -from torch.export import ExportedProgram from executorch.backends.nxp.tests.use_qat import * # noqa F403 -from executorch.exir.dialects._ops import ops as exir_ops @pytest.fixture(autouse=True) @@ -47,28 +38,45 @@ def reseed_model_per_test_run(): np.random.seed(23) -# noinspection PyProtectedMember -ExecutorchDelegateCall = torch.ops.higher_order.executorch_call_delegate +class ReshapeConvModule(nn.Module): + def __init__(self, new_shape: Sequence[int]): + super().__init__() + self.new_shape = new_shape + self.conv = nn.Conv2d( + new_shape[1], new_shape[1], kernel_size=3, padding=1, bias=True + ) + def forward(self, x): + x = torch.reshape(x, self.new_shape) + x = self.conv(x) + return x -class FormatlessToChannelsFirstModule(nn.Module): - def __init__(self, channels: int, new_shape: Sequence[int]): + +class ConvViewConvModule(nn.Module): + def __init__(self, input_shape: Sequence[int], new_shape: Sequence[int]): super().__init__() - self.conv = nn.Conv2d(channels, channels, 2, bias=True) self.new_shape = new_shape + self.conv1 = nn.Conv2d( + input_shape[1], input_shape[1], kernel_size=3, padding=1, bias=True + ) + self.conv2 = nn.Conv2d( + new_shape[1], new_shape[1], kernel_size=3, padding=1, bias=True + ) def forward(self, x): + x = self.conv1(x) x = torch.reshape(x, self.new_shape) - x = self.conv(x) + x = self.conv2(x) return x -class FormatlessToFormatlessModule(nn.Module): +class AddReshapeModule(nn.Module): def __init__(self, new_shape: Sequence[int]): super().__init__() self.new_shape = new_shape def forward(self, x): + x = x + x x = torch.reshape(x, self.new_shape) return x @@ -76,7 +84,7 @@ def forward(self, x): class ConvReshapeModule(nn.Module): def __init__(self, channels: int, new_shape: Sequence[int]): super().__init__() - self.conv = nn.Conv2d(channels, channels, 2, bias=True) + self.conv = nn.Conv2d(channels, channels, kernel_size=3, padding=1, bias=True) self.new_shape = new_shape def forward(self, x): @@ -116,7 +124,7 @@ def forward(self, x): class ConvViewLinearModule(torch.nn.Module): - def __init__(self, view_new_shape: list[int], channels: int, bias: bool): + def __init__(self, view_new_shape: Sequence[int], channels: int, bias: bool): super().__init__() self.view_new_shape = view_new_shape self.conv = nn.Conv2d(channels, channels, 1, 1) @@ -129,23 +137,29 @@ def forward(self, x): return x -class ConvViewConvModule(torch.nn.Module): - def __init__(self, view_new_shape: list[int], channels: int): +class ViewViewModel(nn.Module): + def __init__(self, new_shape_1: Sequence[int], new_shape_2: Sequence[int]): super().__init__() - self.view_new_shape = view_new_shape - self.conv1 = nn.Conv2d(channels, channels, 1, 1) - self.conv2 = nn.Conv2d(channels, channels, 1, 1) + self.new_shape_1 = new_shape_1 + self.new_shape_2 = new_shape_2 def forward(self, x): - x = self.conv1(x) - x = x.view(self.view_new_shape) - x = self.conv2(x) - return x + x = x.view(self.new_shape_1) + return x.view(self.new_shape_2) + + +class ViewAddZeroModel(nn.Module): + def __init__(self, new_shape: Sequence[int]): + super().__init__() + self.new_shape = new_shape + + def forward(self, x): + x = x.view(self.new_shape) + zero = torch.zeros(self.new_shape) + return x + zero class TestViewCopyNewFlow: - # some of the old tests are reworked to utilize NSYS so they don't fail - # the rest will be done as part of adding support for `view_copy` using new Neutron flow (EIEX-882) @staticmethod def assert_delegated_and_correct( mocker, @@ -176,19 +190,36 @@ def assert_delegated_and_correct( use_qat=use_qat, ) + @staticmethod + def assert_not_delegated(model, input_shape): + delegated_ep = to_quantized_edge_program( + model, + input_shape, + ).exported_program() + + # Make sure the partition was NOT delegated. + assert not graph_contains_any_of_ops( + delegated_ep.graph, [ExecutorchDelegateCall] + ) + assert graph_contains_any_of_ops(delegated_ep.graph, [ViewCopy]) + @pytest.mark.parametrize( "input_shape, new_shape", [ - pytest.param((1, 4, 7, 9), (6, 32), id="channels_first to 2D"), - pytest.param((1, 8, 6, 8), (7, 4, 2, 5), id="channels_first to 4D"), + pytest.param((3, 7, 3, 2), (126,), id="1D view"), + pytest.param((1, 4, 7, 9), (6, 42), id="2D view"), + pytest.param((3, 3, 7, 7), (7, 7, 9), id="3D view"), + pytest.param((1, 8, 6, 8), (6, 4, 2, 8), id="4D view"), + pytest.param((2, 7, 5, 9), (3, 2, 3, 7, 5), id="5D view"), ], ) - def test__basic_nsys_inference__channels_first_input( + def test__view_copy__channels_first_to_formatless( self, mocker, input_shape, new_shape, request, + use_qat, ): model = ConvReshapeModule(channels=input_shape[1], new_shape=new_shape) @@ -199,15 +230,23 @@ def test__basic_nsys_inference__channels_first_input( request, exp_deleg_ops={Convolution: 1, ViewCopy: 1}, exp_non_deleg_ops={}, + use_qat=use_qat, ) - def test__basic_nsys_inference__formatless_to_channels_first(self, mocker, request): - input_shape = (12, 32) - new_shape = (1, 4, 12, 8) # Mix up the dimensions for a thorough test. - - model = FormatlessToChannelsFirstModule( - channels=new_shape[1], new_shape=new_shape - ) + @pytest.mark.parametrize( + "input_shape, new_shape", + [ + pytest.param((126,), (3, 7, 3, 2), id="1D view"), + pytest.param((6, 42), (1, 4, 7, 9), id="2D view"), + pytest.param((7, 7, 9), (3, 3, 7, 7), id="3D view"), + pytest.param((6, 4, 2, 8), (1, 8, 6, 8), id="4D view"), + pytest.param((3, 2, 3, 7, 5), (2, 7, 5, 9), id="5D view"), + ], + ) + def test__view_copy__formatless_to_channels_first( + self, input_shape, new_shape, mocker, request, use_qat + ): + model = ReshapeConvModule(new_shape=new_shape) self.assert_delegated_and_correct( mocker, @@ -216,299 +255,162 @@ def test__basic_nsys_inference__formatless_to_channels_first(self, mocker, reque request, exp_deleg_ops={Convolution: 1, ViewCopy: 1}, exp_non_deleg_ops={}, + use_qat=use_qat, ) - -def test__view_copy__formatless_to_formatless(mocker): - input_shape = (12, 32) - new_shape = (1, 4, 6, 16) - - torch_model = FormatlessToFormatlessModule(new_shape=new_shape) - edge_program = to_edge_program(torch_model, input_shape).exported_program() - - input_data = np.random.random(input_shape).astype(np.float32) - - converter_spy = mocker.spy(ModelBuilder, "finish") - - convert_run_compare(edge_program, input_data) - - tflite_model = converter_spy.spy_return - ops = tflite_model.sub_graphs[0].operators.vector - assert len(ops) == 1 # No extra Transpose ops. - assert isinstance(ops[0].builtin_options, Reshape) - - -@pytest.mark.parametrize( - "input_shape, new_shape", - [ - pytest.param((8, 64), (1, 16, 4, 4), id="2D"), - ], -) -def test_view_copy_w_linear_quant_conversion(mocker, input_shape, new_shape, use_qat): - converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") - - # Run conversion - _ = to_quantized_edge_program( - LinearReshapeModule(new_shape=new_shape), input_shape, use_qat=use_qat - ) - - # Capture generated model - tflite_flatbuffers_model, *_ = converter_spy.spy_return - - # Capture converted program - edge_program: ExportedProgram = converter_spy.call_args.args[1] - - input_data = (np.random.random(input_shape).astype(np.float32) * 50).astype(np.int8) - - convert_run_compare( - edge_program, input_data, tfl_model=tflite_flatbuffers_model, atol=1.0 - ) - - -@pytest.mark.parametrize( - "input_shape, channels_view_out", - [ - pytest.param((1, 4, 16, 16), 196, id="4D"), - ], -) -def test_view_w_conv_linear_quant_conversion( - mocker, input_shape, channels_view_out, use_qat -): - converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") - - # Run conversion - _ = to_quantized_edge_program( - ConvLinearViewModule( - channels=input_shape[1], channels_view_out=channels_view_out - ), - input_shape, - use_qat=use_qat, - use_neutron_for_format_conversion=False, - ) - - # Capture generated model - tflite_flatbuffers_model, *_ = converter_spy.spy_return - - # Capture converted program - edge_program: ExportedProgram = converter_spy.call_args.args[1] - - input_data = (np.random.random(input_shape).astype(np.float32) * 50).astype(np.int8) - - convert_run_compare( - edge_program, - input_data, - tflite_input_preprocess=ToChannelLastPreprocess(), - tfl_model=tflite_flatbuffers_model, - atol=1.0, - ) - - -@pytest.mark.parametrize( - "bias", - [True, False], -) -def test__view_copy__context_dependent__channels_first_to_formatless__transpose_fused( - bias, mocker -): - input_shape = (1, 2, 3, 4) - new_shape = [1, 2 * 3 * 4] - module = ConvViewLinearModule(new_shape, 2, bias) - - converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") - - ep = to_quantized_edge_program( - module, - input_shape, - use_neutron_for_format_conversion=False, - ).exported_program() - - # Make sure all 3 nodes were delegated - assert any(n.target == ExecutorchDelegateCall for n in ep.graph.nodes) - assert not graph_contains_any_of_ops( - ep.graph, - [ - exir_ops.edge.aten.convolution.default, - exir_ops.edge.aten.mm.default, - exir_ops.edge.aten.addmm.default, - exir_ops.edge.aten.view_copy.default, - ], - ) - - input_data = (np.random.random(input_shape).astype(np.float32) * 50).astype(np.int8) - - converted_edge_program = converter_spy.call_args.args[1] - neutron_ir_model = converter_spy.spy_return[0] - convert_run_compare( - converted_edge_program, - input_data, - tfl_model=neutron_ir_model, - tflite_input_preprocess=ToChannelLastPreprocess(), - ) - - -@pytest.mark.parametrize( - "bias", - [True, False], -) -def test__view_copy__context_dependent__channels_first_to_formatless__transpose_not_fusable( - bias, -): - input_shape = (1, 2, 3, 4) - new_shape = [ - 2, - 3 * 4, - ] # The batch size changes, which makes the optimization not applicable. - module = ConvViewLinearModule(new_shape, 2, bias) - - ep = to_quantized_edge_program( - module, - input_shape, - use_neutron_for_format_conversion=False, - ).exported_program() - - # Make sure all ops were delegated anyway. - assert any(n.target == ExecutorchDelegateCall for n in ep.graph.nodes) - assert not graph_contains_any_of_ops( - ep.graph, + @pytest.mark.parametrize( + "input_shape, new_shape", [ - exir_ops.edge.aten.convolution.default, - exir_ops.edge.aten.mm.default, - exir_ops.edge.aten.addmm.default, - exir_ops.edge.aten.view_copy.default, + pytest.param((3, 7, 3, 2), (126,), id="1D view"), + pytest.param((1, 4, 7, 9), (6, 42), id="2D view"), + pytest.param((3, 3, 7, 7), (7, 7, 9), id="3D view"), + pytest.param((1, 8, 6, 8), (6, 4, 2, 8), id="4D view"), + pytest.param((2, 7, 5, 9), (3, 2, 3, 7, 5), id="5D view"), ], ) + def test__view_copy__formatless_to_formatless( + self, input_shape, new_shape, mocker, request, use_qat + ): + model = AddReshapeModule(new_shape=new_shape) + self.assert_delegated_and_correct( + mocker, + model, + input_shape, + request, + exp_deleg_ops={AddTensor: 1, ViewCopy: 1}, + exp_non_deleg_ops={}, + use_qat=use_qat, + ) -def test__view_copy__formatless_to_channels_first__transpose_supported(mocker): - input_shape = (1, 8 * 3 * 8) - new_shape = [1, 8, 3, 8] - module = FormatlessToChannelsFirstModule(8, new_shape) - - converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") - - ep = to_quantized_edge_program( - module, - input_shape, - use_neutron_for_format_conversion=False, - ).exported_program() - - # Make sure both nodes were delegated - assert any(n.target == ExecutorchDelegateCall for n in ep.graph.nodes) - assert not graph_contains_any_of_ops( - ep.graph, + @pytest.mark.parametrize( + "input_shape, new_shape", [ - exir_ops.edge.aten.convolution.default, - exir_ops.edge.aten.view_copy.default, + pytest.param((6, 4, 2, 8), (1, 8, 6, 8), id="4D view"), ], ) + def test__view_copy__channels_first_to_channels_first( + self, input_shape, new_shape, mocker, request, use_qat + ): + model = ConvViewConvModule(input_shape, new_shape) - input_data = (np.random.random(input_shape).astype(np.float32) * 50).astype(np.int8) - - converted_edge_program = converter_spy.call_args.args[1] - neutron_ir_model = converter_spy.spy_return[0] - convert_run_compare( - converted_edge_program, - input_data, - tfl_model=neutron_ir_model, - tflite_output_preprocess=ToChannelFirstPreprocess(), - ) + self.assert_delegated_and_correct( + mocker, + model, + input_shape, + request, + exp_deleg_ops={Convolution: 2, ViewCopy: 1}, + exp_non_deleg_ops={}, + use_qat=use_qat, + ) + def test_view_copy_w_linear_quant_conversion(self, mocker, request, use_qat): + input_shape = (8, 64) + new_shape = (1, 16, 4, 4) -def test__view_copy__channels_first_to_channels_first__transpose_supported(mocker): - input_shape = (1, 8, 3, 8) - new_shape = [1, 8, 1, 24] - module = ConvViewConvModule(new_shape, 8) + model = LinearReshapeModule(new_shape=new_shape) - converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") + self.assert_delegated_and_correct( + mocker, + model, + input_shape, + request, + exp_deleg_ops={AddMM: 1, ViewCopy: 1, PermuteCopy: 1}, + exp_non_deleg_ops={}, + use_qat=use_qat, + ) - ep = to_quantized_edge_program( - module, - input_shape, - use_neutron_for_format_conversion=False, - ).exported_program() + def test_view_w_conv_linear_quant_conversion(self, request, mocker, use_qat): + input_shape = (1, 4, 16, 16) + channels_view_out = 196 - # Make sure all nodes were delegated - assert any(n.target == ExecutorchDelegateCall for n in ep.graph.nodes) - assert not graph_contains_any_of_ops( - ep.graph, - [ - exir_ops.edge.aten.convolution.default, - exir_ops.edge.aten.view_copy.default, - ], - ) + model = ConvLinearViewModule( + channels=input_shape[1], channels_view_out=channels_view_out + ) - input_data = (np.random.random(input_shape).astype(np.float32) * 50).astype(np.int8) + self.assert_delegated_and_correct( + mocker, + model, + input_shape, + request, + exp_deleg_ops={ + AddMM: 1, + ViewCopy: 1, + Convolution: 1, + AvgPool2D: 1, + Relu: 1, + PermuteCopy: 1, + }, + exp_non_deleg_ops={}, + use_qat=use_qat, + ) - converted_edge_program = converter_spy.call_args.args[1] - neutron_ir_model = converter_spy.spy_return[0] - convert_run_compare( - converted_edge_program, - input_data, - tfl_model=neutron_ir_model, - tflite_input_preprocess=ToChannelLastPreprocess(), - tflite_output_preprocess=ToChannelFirstPreprocess(), + @pytest.mark.parametrize( + "bias", + [True, False], ) + def test__view_copy__context_dependent__channels_first_to_formatless__transpose_fused( + self, bias, mocker, request + ): + input_shape = (1, 2, 3, 4) + new_shape = (1, 2 * 3 * 4) + model = ConvViewLinearModule(new_shape, 2, bias) + converted_lin_op = AddMM if bias else MM + self.assert_delegated_and_correct( + mocker, + model, + input_shape, + request, + exp_deleg_ops={ + converted_lin_op: 1, + ViewCopy: 1, + Convolution: 1, + PermuteCopy: 1, + }, + exp_non_deleg_ops={}, + ) -class ViewViewModel(nn.Module): - def __init__(self, new_shape_1: list[int], new_shape_2: list[int]): - super().__init__() - self.new_shape_1 = new_shape_1 - self.new_shape_2 = new_shape_2 - - def forward(self, x): - x = x.view(self.new_shape_1) - return x.view(self.new_shape_2) - - -class ViewAddZeroModel(nn.Module): - def __init__(self, new_shape: list[int]): - super().__init__() - self.new_shape = new_shape - - def forward(self, x): - x = x.view(self.new_shape) - zero = torch.zeros(self.new_shape) - return x + zero - - -def test__view_copy__noop_partitions__second_view(): - input_shape = (1, 2, 3, 4) - new_shape1 = [2, 12] - new_shape2 = [6, 4] - module = ViewViewModel(new_shape1, new_shape2) - - ep = to_quantized_edge_program( - module, - input_shape, - ).exported_program() - - # Make sure neither `view_copy` was delegated. - assert not any(n.target == ExecutorchDelegateCall for n in ep.graph.nodes) - assert graph_contains_any_of_ops( - ep.graph, - [ - exir_ops.edge.aten.view_copy.default, - ], + @pytest.mark.parametrize( + "bias", + [True, False], ) + def test__view_copy__context_dependent__channels_first_to_formatless__transpose_not_fusable( + self, bias, mocker, request + ): + input_shape = (1, 2, 3, 4) + new_shape = ( + 2, + 3 * 4, + ) # The batch size changes, which makes the optimization not applicable. + model = ConvViewLinearModule(new_shape, 2, bias) + + converted_lin_op = AddMM if bias else MM + self.assert_delegated_and_correct( + mocker, + model, + input_shape, + request, + exp_deleg_ops={ + converted_lin_op: 1, + ViewCopy: 1, + Convolution: 1, + PermuteCopy: 1, + }, + exp_non_deleg_ops={}, + ) + def test__view_copy__noop_partitions__second_view(self): + input_shape = (1, 2, 3, 4) + new_shape1 = (2, 12) + new_shape2 = (6, 4) + model = ViewViewModel(new_shape1, new_shape2) -def test__view_copy__noop_partitions__add_zeros(): - input_shape = (1, 2, 3, 4) - new_shape = [2, 12] - module = ViewAddZeroModel(new_shape) + self.assert_not_delegated(model, input_shape) - ep = to_quantized_edge_program( - module, - input_shape, - ).exported_program() + def test__view_copy__noop_partitions__add_zeros(self): + input_shape = (1, 2, 3, 4) + new_shape = (2, 12) + model = ViewAddZeroModel(new_shape) - # Make sure neither the `view` nor the `add` was delegated - assert not any(n.target == ExecutorchDelegateCall for n in ep.graph.nodes) - assert graph_contains_any_of_ops( - ep.graph, - [ - exir_ops.edge.aten.add.Tensor, - exir_ops.edge.aten.view_copy.default, - ], - ) + self.assert_not_delegated(model, input_shape)