diff --git a/backends/nxp/backend/ir/converter/node_converters/ops_converters/convolution_converter.py b/backends/nxp/backend/ir/converter/node_converters/ops_converters/convolution_converter.py index bdd71c6da1a..51f5ca4aa1a 100644 --- a/backends/nxp/backend/ir/converter/node_converters/ops_converters/convolution_converter.py +++ b/backends/nxp/backend/ir/converter/node_converters/ops_converters/convolution_converter.py @@ -31,7 +31,6 @@ from executorch.backends.nxp.backend.ir.converter.node_converters.shared.conv_utils import ( ConvConversionResult, ConvParameters, - get_node_tensor_params, ) from executorch.backends.nxp.backend.ir.converter.quantization_utils import ( set_quantization_parameters_to_tensor, @@ -58,29 +57,15 @@ Stride = Padding = Dilation = OutPadding = list[int] Transposed = bool Groups = int -ConvolutionArgs = tuple[ - Node, Node, Node | None, Stride, Padding, Dilation, Transposed, OutPadding, Groups -] @requires_channels_first_format class ConvolutionConverter(NodeConverter): @staticmethod - def _is_supported_on_target_regular_conv( - node: Node, - parameters_mapping: dict[str, Parameter], + def _is_conv_quant_supported( + node: Node, parameters_mapping: dict[str, Parameter] ) -> bool: - ( - inp_node, - w_node, - b_node, - stride, - _, - dilation, - _, - _, - _, - ) = ConvolutionConverter._get_convolution_arguments(node) + conv_params = ConvolutionConverter._get_conv_params(node) # Input must be INT8/UINT8 # Output must be INT8/UINT8 @@ -98,7 +83,7 @@ def _is_supported_on_target_regular_conv( return False # Bias must be INT32 - if b_node is not None: + if conv_params.bias_node is not None: b_supported_types = [torch.int32] if not NodeConverter.uses_quantization_type_for_io( node, b_supported_types, [2], [] @@ -106,39 +91,67 @@ def _is_supported_on_target_regular_conv( return False # Weights must be constant - if not node_is_effectively_static_tensor(w_node, parameters_mapping): + if not node_is_effectively_static_tensor( + conv_params.weight_node, parameters_mapping + ): return False # Bias must be constant (if present) - if b_node is not None and not node_is_effectively_static_tensor( - b_node, parameters_mapping + if conv_params.bias_node is not None and not node_is_effectively_static_tensor( + conv_params.bias_node, parameters_mapping ): return False + return True + + @staticmethod + def _is_supported_on_target_regular_conv( + node: Node, + neutron_target_spec: NeutronTargetSpec, + parameters_mapping: dict[str, Parameter], + ) -> bool: + # Check the quantization of inputs is supported + if not ConvolutionConverter._is_conv_quant_supported(node, parameters_mapping): + return False + + conv_params = ConvolutionConverter._get_conv_params(node) + # kernelH <= 4096, kernelW <= 4096 # strideH <= 4096, strideW <= 4096 # dilationH <= 4096, dilationW <= 4096 - w_node_shape = w_node.meta["val"].shape + w_node_shape = conv_params.weight_node.meta["val"].shape kernel_h = w_node_shape[2] kernel_w = w_node_shape[3] - stride_h = stride[0] - stride_w = stride[1] - dilation_h = dilation[0] - dilation_w = dilation[1] + stride_h = conv_params.stride[0] + stride_w = conv_params.stride[1] + dilation_h = conv_params.dilation[0] + dilation_w = conv_params.dilation[1] dim_sizes = [kernel_h, kernel_w, stride_h, stride_w, dilation_h, dilation_w] if any(dim > 4096 for dim in dim_sizes): return False - # kernelH * kernelW * inpC <= 65535 - inp_node_shape = inp_node.meta["val"].shape + # padT < kernelH, padB < kernelH, padL < kernelW, padR < kernelW + padding_h = conv_params.padding[0] + padding_w = conv_params.padding[1] + if padding_h >= kernel_h or padding_w >= kernel_w: + return False + + # kernelH * kernelW * ROUND_CEIL(inpC, NUM_MACS) <= 65535 + inp_node_shape = conv_params.input_node.meta["val"].shape inp_channels = ( inp_node_shape[1] if len(inp_node_shape) == 4 else inp_node_shape[0] ) + num_macs = neutron_target_spec.get_num_macs() - if kernel_h * kernel_w * inp_channels > 65535: + if ( + kernel_h + * kernel_w + * ConvolutionConverter._round_ceil(inp_channels, num_macs) + > 65535 + ): return False return True @@ -149,48 +162,54 @@ def _is_supported_on_target_transp_conv( neutron_target_spec: NeutronTargetSpec, parameters_mapping: dict[str, Parameter], ) -> bool: - # TODO: EIEX-894 update the requirements of delegation for new Neutron flow - _, w_node, _, stride, padding, dilation, transposed, _, groups = ( - ConvolutionConverter._get_convolution_arguments(node) - ) - - num_macs = neutron_target_spec.get_num_macs() - node_t_params = get_node_tensor_params(node) - - if node_t_params["batch_size"] != 1: - # Only TransposeConv2d with batch size = 1 is supported on neutron. + # Check the quantization of inputs is supported + if not ConvolutionConverter._is_conv_quant_supported(node, parameters_mapping): return False + conv_params = ConvolutionConverter._get_conv_params(node) + # TransposeConv2d with groups > 1 is not supported # TODO: split into multiple convs with groups = 1 - if groups > 1: + if conv_params.groups > 1: return False - if not node_is_effectively_static_tensor(w_node, parameters_mapping): - # Only supported if the weights are static, because TFLite `TransposeConv` uses permuted - # weights. In case the weights are dynamic, a Transpose operator would have to be added, which - # is not supported on Neutron. + + # kernelH <= 4096, kernelW <= 4096 + w_node_shape = conv_params.weight_node.meta["val"].shape + kernel_h = w_node_shape[2] + kernel_w = w_node_shape[3] + + dim_sizes = [kernel_h, kernel_w] + if any(dim > 4096 for dim in dim_sizes): + return False + + # strideH <= kernelH, strideW <= kernelW + stride_h = conv_params.stride[0] + stride_w = conv_params.stride[1] + if stride_h > kernel_h or stride_w > kernel_w: + return False + + # strideH <= 2, strideW <= 2 + if stride_h > 2 or stride_w > 2: return False - # neutron-library/src/utils/NeutronLibraryInterrogation.cpp#876 TransposeConv2DKernelKind + + # padT < kernelH, padB < kernelH, padL < kernelW, padR < kernelW + padding_h = conv_params.padding[0] + padding_w = conv_params.padding[1] + if padding_h >= kernel_h or padding_w >= kernel_w: + return False + + # kernelH * kernelW * ceil(inpC, NUM_MACS) <= 65535 + inp_node_shape = conv_params.input_node.meta["val"].shape + inp_channels = ( + inp_node_shape[1] if len(inp_node_shape) == 4 else inp_node_shape[0] + ) + num_macs = neutron_target_spec.get_num_macs() + if ( - dilation != [1, 1] - or padding[0] != 0 - or padding[1] >= node_t_params["kernel_width"] - or ( - padding[1] != 0 and node_t_params["inp_height"] != 1 - ) # Slice added by explicit padding - or stride[0] != 1 - or ( - ( - stride[1] != node_t_params["kernel_width"] / 2 - or node_t_params["out_height"] != 1 - ) - and stride[1] != node_t_params["kernel_width"] - ) - or stride[1] % 2 != 0 - or node_t_params["inp_channels"] % num_macs != 0 - or node_t_params["out_channels"] % num_macs != 0 - or node_t_params["kernel_width"] % 2 != 0 - or node_t_params["kernel_height"] != 1 + kernel_h + * kernel_w + * ConvolutionConverter._round_ceil(inp_channels, num_macs) + > 65535 ): return False @@ -203,7 +222,7 @@ def _is_supported_on_target( parameters_mapping: dict[str, Parameter], custom_delegation_options: CustomDelegationOptions, ) -> bool: - is_transposed = (ConvolutionConverter._get_convolution_arguments(node))[6] + is_transposed = node.args[6] if is_transposed: return ConvolutionConverter._is_supported_on_target_transp_conv( @@ -212,7 +231,7 @@ def _is_supported_on_target( else: return ConvolutionConverter._is_supported_on_target_regular_conv( - node, parameters_mapping + node, neutron_target_spec, parameters_mapping ) @staticmethod @@ -244,6 +263,10 @@ def _is_supported_in_IR( return True + @staticmethod + def _round_ceil(x, n): + return ((x + n - 1) // n) * n + def _compute_slicing_params( self, output_shape, explicit_padding ) -> tuple[list[int], list[int]]: @@ -259,22 +282,15 @@ def _compute_slicing_params( return begins, sizes @staticmethod - def _get_convolution_arguments( + def _get_conv_params( conv_node: Node, - ) -> ConvolutionArgs: + ) -> ConvParameters: x, w, b, stride, padding, dilation, transposed, out_padding, groups = ( conv_node.args ) - return ( - x, - w, - b, - list(stride), - list(padding), - list(dilation), - transposed, - list(out_padding), - groups, + + return ConvParameters( + x, w, b, stride, padding, dilation, transposed, out_padding, groups ) # noinspection PyPep8Naming @@ -415,8 +431,10 @@ def _convert_transpose_conv( return conversion_result def _convert_2d_conv( - self, t_op: tflite_model.Operator, conv_params: ConvParameters + self, torch_node: Node, t_op: tflite_model.Operator ) -> list[tflite_model.Operator]: + conv_params = self._get_conv_params(torch_node) + if conv_params.transposed: t_op.builtin_options = transpose_conv_options.TransposeConv() if conv_utils.group_conv_convertible_into_multiple_convolutions( @@ -502,18 +520,11 @@ def _convert_2d_conv( def convert(self, node: Node): self.assert_convertible(node) - _, _, _, stride, padding, dilation, transposed, out_padding, groups = ( - self._get_convolution_arguments(node) - ) - t_op = self._create_tflite_op_with_io_tensors(node) - conv_params = ConvParameters( - stride, padding, dilation, transposed, out_padding, groups - ) rank = t_op.tmp_inputs[1].shape.len() if rank == 4: # Conv2D - ops_to_add = self._convert_2d_conv(t_op, conv_params) + ops_to_add = self._convert_2d_conv(node, t_op) else: raise NotImplementedError( f"{rank - 2}D convolution is not supported." diff --git a/backends/nxp/backend/ir/converter/node_converters/shared/conv_utils.py b/backends/nxp/backend/ir/converter/node_converters/shared/conv_utils.py index 2012ecc8640..8f2fda3e953 100755 --- a/backends/nxp/backend/ir/converter/node_converters/shared/conv_utils.py +++ b/backends/nxp/backend/ir/converter/node_converters/shared/conv_utils.py @@ -1,4 +1,4 @@ -# Copyright 2023-2025 NXP +# Copyright 2023-2026 NXP # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. @@ -13,11 +13,14 @@ @dataclass class ConvParameters: + input_node: Node + weight_node: Node + bias_node: Node | None stride: list[int] padding: list[int] dilation: list[int] transposed: bool - out_padding: list[int] + out_padding: list[int] | None # only meaningful for transposed conv groups: int diff --git a/backends/nxp/tests/ir/converter/node_converter/test_conv_converter.py b/backends/nxp/tests/ir/converter/node_converter/test_conv_converter.py index 1705dae85bb..605783230ea 100644 --- a/backends/nxp/tests/ir/converter/node_converter/test_conv_converter.py +++ b/backends/nxp/tests/ir/converter/node_converter/test_conv_converter.py @@ -8,19 +8,13 @@ import torch from executorch.backends.nxp.tests.dataset_creator import RandomDatasetCreator from executorch.backends.nxp.tests.executorch_pipeline import to_quantized_edge_program -from executorch.backends.nxp.tests.executors import ( - convert_run_compare, - EdgeProgramToIRConverter, - ExportedProgram, - graph_contains_any_of_ops, - ToChannelFirstPreprocess, - ToChannelLastPreprocess, -) +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.models import Conv2dModule +from executorch.backends.nxp.tests.models import Conv2dModule, Conv2dTransposedModule from executorch.backends.nxp.tests.nsys_testing import ( AllCloseOutputComparator, lower_run_compare, + ReferenceModel, ) from executorch.backends.nxp.tests.ops_aliases import ( Convolution, @@ -36,148 +30,530 @@ def reseed_model_per_test_run(): np.random.seed(23) -class TestTransposedConvFromLegacyFlow: +def assert_delegated_and_correct( + model, + input_shape, + mocker, + request, + use_qat, + et_ref_model=ReferenceModel.QUANTIZED_EXECUTORCH_CPP, +): + graph_verifier = DetailedGraphVerifier( + mocker, + expected_delegated_ops={Convolution: 1}, + expected_non_delegated_ops={}, + ) + dataset = RandomDatasetCreator(low=-1.0, high=1.0) + + # Use quantized dataset and allow single bit error. + remove_quant_io_ops = True + comparator = AllCloseOutputComparator(atol=1) + + lower_run_compare( + model, + input_shape, + graph_verifier, + request, + dataset, + comparator, + use_qat=use_qat, + remove_quant_io_ops=remove_quant_io_ops, + reference_model=et_ref_model, + ) + + +def assert_not_delegated(model, input_shape, use_qat): + delegated_ep = to_quantized_edge_program( + model, + input_shape, + use_qat=use_qat, + ).exported_program() + + # Make sure the `convolution` was NOT delegated. + assert not graph_contains_any_of_ops(delegated_ep.graph, [ExecutorchDelegateCall]) + assert graph_contains_any_of_ops(delegated_ep.graph, [Convolution]) + + +def _conv_id(ins, oc, ks=3, s=2, d=1, p=0, op=0, b=True, g=1): + return ( + f"ins={ins}, " + f"oc={oc}, " + f"ks={ks}, " + f"s={s}, " + f"d={d}, " + f"p={p}, " + f"op={op}, " + f"b={b}, " + f"g={g}" + ) + + +class TestTrConv: @pytest.mark.parametrize( - "model, input_shape", + "input_shape, out_channels", [ pytest.param( - torch.nn.ConvTranspose2d(8, 16, (1, 4), stride=(1, 2)), - (1, 8, 1, 16), - id="In ch 8, out ch 16, kernel (1, 4), stride (1, 2)", + ins := (1, 8, 16, 24), + oc := 8, + id=f"basic inference: {_conv_id(ins, oc)}", ), pytest.param( - torch.nn.ConvTranspose2d(64, 64, (1, 2), stride=(1, 2)), - (1, 64, 3, 12), - id="In ch 64, out ch 64, kernel (1, 2), stride (1, 2)", + ins := (8, 16, 8, 32), + oc := 16, + id=f"basic inference: {_conv_id(ins, oc)}", ), pytest.param( - torch.nn.ConvTranspose2d(16, 40, (1, 4), stride=(1, 2), padding=(0, 1)), - (1, 16, 1, 27), - id="In ch 16, out ch 40, kernel (1, 4), stride (1, 2), padding (0, 1)", + ins := (16, 8, 32, 64), + oc := 32, + id=f"basic inference: {_conv_id(ins, oc)}", + marks=pytest.mark.xfail(reason="AIR-14853", strict=True), ), pytest.param( - torch.nn.ConvTranspose2d(8, 16, (1, 4), stride=(1, 2), padding=(0, 1)), - (1, 8, 1, 16), - id="In ch 8, out ch 16, kernel (1, 4), stride (1, 2), padding (0, 1)", + ins := (1, 8, 32, 64), + oc := 16, + id=f"basic inference: {_conv_id(ins, oc)}", ), pytest.param( - torch.nn.ConvTranspose2d( - 8, 16, (1, 4), stride=(1, 2), output_padding=(0, 1) - ), - (1, 8, 1, 16), - id="In ch 8, out ch 16, kernel (1, 8), stride (1, 2), output_padding (0, 1)", + ins := (1, 32, 48, 8), + oc := 24, + id=f"basic inference: {_conv_id(ins, oc)}", ), + ], + ) + def test__tr_conv__basic(self, input_shape, out_channels, use_qat, request, mocker): + in_channels = input_shape[1] + model = Conv2dTransposedModule( + in_channels=in_channels, out_channels=out_channels + ) + + assert_delegated_and_correct(model, input_shape, mocker, request, use_qat) + + @pytest.mark.parametrize( + "input_shape, out_channels, use_et_ref_model", + [ pytest.param( - torch.nn.ConvTranspose2d(16, 16, (1, 4), stride=(1, 2)), - (1, 16, 1, 16), - id="In ch 16, out ch 16, kernel (1, 4), stride (1, 2)", + ins := (1, 3, 7, 14), + oc := 3, + use_et_ref_model := True, + id=f"ET reference model used: {use_et_ref_model}, unusual shape inference: " + + _conv_id(ins, oc), ), pytest.param( - torch.nn.ConvTranspose2d(8, 16, (1, 4), stride=(1, 2), bias=False), - (1, 8, 1, 16), - id="In ch 8, out ch 16, kernel (1, 4), stride (1, 2), no bias", + ins := (2, 3, 13, 27), + oc := 7, + use_et_ref_model := True, + id=f"ET reference model used: {use_et_ref_model}, unusual shape inference: " + + _conv_id(ins, oc), ), pytest.param( - torch.nn.ConvTranspose2d( - 8, 16, (1, 4), stride=(1, 2), padding=(0, 1), bias=False - ), - (1, 8, 1, 16), - id="In ch 8, out ch 16, kernel (1, 4), stride (1, 2)," - "padding (0, 1), no bias", + ins := (3, 7, 3, 14), + oc := 4, + use_et_ref_model := True, + id=f"ET reference model used: {use_et_ref_model}, unusual shape inference: " + + _conv_id(ins, oc), + ), + pytest.param( + ins := (1, 9, 9, 13), + oc := 1, + use_et_ref_model := False, + id=f"ET reference model used: {use_et_ref_model}, unusual shape inference: " + + _conv_id(ins, oc), + ), + pytest.param( + ins := (7, 7, 7, 7), + oc := 10, + use_et_ref_model := True, + id=f"ET reference model used: {use_et_ref_model}, unusual shape inference: " + + _conv_id(ins, oc), + ), + pytest.param( + ins := (4, 21, 13, 17), + oc := 27, + use_et_ref_model := True, + id=f"ET reference model used: {use_et_ref_model}, unusual shape inference: " + + _conv_id(ins, oc), ), ], ) - def test_conv_transpose2d_conversion__quantized( - self, mocker, model: torch.nn.Module, input_shape, use_qat + def test__tr_conv__unusual_shapes( + self, input_shape, out_channels, use_et_ref_model, use_qat, request, mocker ): - converter_spy = mocker.spy(EdgeProgramToIRConverter, "convert_program") + in_channels = input_shape[1] + model = Conv2dTransposedModule( + in_channels=in_channels, out_channels=out_channels + ) - edge_program = to_quantized_edge_program( - model, input_shape, use_qat=use_qat, use_neutron_for_format_conversion=False - ).exported_program() + # Running `conv_transpose2d` with `output_channels = 1` produces errors in Executorch. The issue has been reported: + # https://github.com/pytorch/executorch/issues/20804 + ref_model = ( + ReferenceModel.QUANTIZED_EXECUTORCH_CPP + if use_et_ref_model + else ReferenceModel.QUANTIZED_EDGE_PYTHON + ) - # Make sure the `TransposeConv` was delegated. - assert not graph_contains_any_of_ops( - graph=edge_program.graph, ops=[Convolution] + assert_delegated_and_correct( + model, input_shape, mocker, request, use_qat, ref_model ) - assert graph_contains_any_of_ops( - graph=edge_program.graph, ops=[ExecutorchDelegateCall] + + @pytest.mark.parametrize( + "input_shape, out_channels", + [ + pytest.param( + ins := (21, 4, 7), + oc := 45, + id=f"`conv2d_transpose` implicit batch: {_conv_id(ins, oc)}", + ), + ], + ) + def test__tr_conv__impl_batch( + self, input_shape, out_channels, use_qat, mocker, request + ): + in_channels = input_shape[0] + + model = Conv2dTransposedModule( + in_channels=in_channels, out_channels=out_channels ) - # Capture generated model - tflite_flatbuffers_model, *_ = converter_spy.spy_return + # `view_copy` is inserted to convert to explicit batch + graph_verifier = DetailedGraphVerifier( + mocker, + expected_delegated_ops={Convolution: 1, ViewCopy: 2}, + expected_non_delegated_ops={}, + ) + dataset = RandomDatasetCreator(low=-1.0, high=1.0) + remove_quant_io_ops = True - # Capture converted program - exported_program: ExportedProgram = converter_spy.call_args.args[1] + comparator = AllCloseOutputComparator(atol=1) - input_data = (np.random.random(input_shape).astype(np.float32) * 50).astype( - np.int8 + lower_run_compare( + model, + input_shape, + graph_verifier, + request, + dataset, + comparator, + use_qat=use_qat, + remove_quant_io_ops=remove_quant_io_ops, ) - convert_run_compare( - exported_program, - tflite_input_preprocess=ToChannelLastPreprocess(), - tfl_model=tflite_flatbuffers_model, - tflite_output_preprocess=ToChannelFirstPreprocess(), - input_data=input_data, - atol=1.0, + @pytest.mark.parametrize( + "input_shape, out_channels, kernel_size, stride, dilation, padding", + [ + pytest.param( + ins := (2, 3, 1, 8500), + oc := 7, + ks := (1, 4096), + s := 1, + d := 1, + p := 0, + id=f"bounds of kernel width: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + marks=pytest.mark.xfail(reason="AIR-14853", strict=True), + ), + pytest.param( + ins := (3, 3, 8500, 1), + oc := 9, + ks := (4096, 1), + s := 1, + d := 1, + p := 0, + id=f"bounds of kernel height: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + marks=pytest.mark.xfail(reason="AIR-14853", strict=True), + ), + pytest.param( + ins := (3, 3, 5, 7), + oc := 9, + ks := (2, 1), + s := (2, 1), + d := 1, + p := 0, + id=f"bounds of stride height - kernel height: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (3, 3, 5, 7), + oc := 9, + ks := (1, 2), + s := (1, 2), + d := 1, + p := 0, + id=f"bounds of stride width - kernel width: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (3, 3, 5, 7), + oc := 9, + ks := (3, 3), + s := 1, + d := 1, + p := (2, 1), + id=f"bounds of padding height - kernel height: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (3, 3, 5, 7), + oc := 9, + ks := (3, 3), + s := 1, + d := 1, + p := (1, 2), + id=f"bounds of padding width - kernel width: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (1, 20, 16, 24), + oc := 2, + ks := (16, 24), + s := 1, + d := 1, + p := 1, + id=f"(almost) bounds of kernel_h * kernel_w * round_ceil(input_channels, num_macs): {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + ], + ) + def test__tr_conv__big( + self, + input_shape, + out_channels, + kernel_size, + stride, + dilation, + padding, + use_qat, + request, + mocker, + ): + model = Conv2dTransposedModule( + in_channels=input_shape[1], + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding, + ) + + assert_delegated_and_correct(model, input_shape, mocker, request, use_qat) + + @pytest.mark.parametrize( + "input_shape, out_channels, kernel_size, stride, dilation, padding, output_padding, bias", + [ + pytest.param( + ins := (1, 8, 32, 32), + oc := 7, + ks := (5, 3), + s := (2, 1), + d := (1, 2), + p := (2, 1), + op := (0, 1), + b := True, + id=f"some params not default: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p, b=b, op=op)}", + marks=pytest.mark.xfail(reason="AIR-14852", strict=True), + ), + pytest.param( + ins := (2, 7, 31, 17), + oc := 9, + ks := (7, 7), + s := (2, 2), + d := (6, 5), + p := (5, 4), + op := (2, 1), + b := False, + id=f"some params not default: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p, b=b, op=op)}", + marks=pytest.mark.xfail(reason="AIR-14853", strict=True), + ), + pytest.param( + ins := (2, 12, 28, 28), + oc := 11, + ks := (3, 5), + s := (2, 2), + d := (2, 2), + p := (1, 2), + op := (1, 1), + b := True, + id=f"some params not default: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p, b=b, op=op)}", + marks=pytest.mark.xfail(reason="AIR-14852", strict=True), + ), + pytest.param( + ins := (3, 2, 40, 20), + oc := 13, + ks := (1, 5), + s := (1, 2), + d := (3, 1), + p := (0, 4), + op := (1, 1), + b := False, + id=f"some params not default: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p, b=b, op=op)}", + ), + pytest.param( + ins := (4, 6, 30, 30), + oc := 5, + ks := (3, 3), + s := (2, 2), + d := (3, 3), + p := (2, 2), + op := (2, 2), + b := True, + id=f"some params not default: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p, b=b, op=op)}", + marks=pytest.mark.xfail(reason="AIR-14852", strict=True), + ), + pytest.param( + ins := (3, 12, 7, 7), + oc := 7, + ks := (5, 5), + s := (1, 2), + d := (1, 3), + p := (2, 4), + op := (0, 2), + b := False, + id=f"some params not default: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p, b=b, op=op)}", + marks=pytest.mark.xfail(reason="AIR-14852", strict=True), + ), + pytest.param( + ins := (1, 4, 15, 15), + oc := 9, + ks := (2, 2), + s := (2, 2), + d := (2, 2), + p := (1, 1), + op := (1, 1), + b := True, + id=f"some params not default: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p, b=b, op=op)}", + marks=pytest.mark.xfail(reason="AIR-14852", strict=True), + ), + ], + ) + def test__tr_conv__various_args( + self, + input_shape, + out_channels, + kernel_size, + stride, + dilation, + padding, + output_padding, + bias, + use_qat, + request, + mocker, + ): + model = Conv2dTransposedModule( + in_channels=input_shape[1], + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + dilation=dilation, + padding=padding, + output_padding=output_padding, + bias=bias, ) + assert_delegated_and_correct(model, input_shape, mocker, request, use_qat) + @pytest.mark.parametrize( - "model, input_shape", + "input_shape, out_channels, kernel_size, stride, padding, groups", [ pytest.param( - torch.nn.ConvTranspose2d(8, 16, (1, 4), stride=(1, 2), dilation=(1, 2)), - (1, 8, 1, 16), - id="Dilation != (1, 1)", + ins := (3, 7, 5000, 11), + oc := 7, + ks := (4097, 1), + s := 1, + p := 0, + g := 1, + id=f"kernel height too big: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", ), pytest.param( - torch.nn.ConvTranspose2d(6, 16, (1, 4), stride=(1, 2)), - (1, 6, 1, 16), - id="In channels % num_macs != 0", + ins := (3, 7, 13, 5000), + oc := 9, + ks := (1, 4097), + s := 1, + p := 0, + g := 1, + id=f"kernel width too big: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", + ), + pytest.param( + ins := (3, 7, 9, 11), + oc := 11, + ks := 1, + s := (2, 1), + p := 0, + g := 1, + id=f"stride height > kernel height: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", ), pytest.param( - torch.nn.ConvTranspose2d(8, 16, (1, 4), stride=(1, 2)), - (1, 8, 4, 16), - id="Out height != 1, stride width != kernel width", + ins := (3, 7, 13, 11), + oc := 5, + ks := 1, + s := (1, 2), + p := 0, + g := 1, + id=f"stride width > kernel width: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", ), pytest.param( - torch.nn.ConvTranspose2d(8, 16, (2, 4), stride=(1, 2), padding=(0, 1)), - (1, 8, 1, 16), - id="Out height != 1, stride width != kernel width", + ins := (3, 7, 13, 11), + oc := 7, + ks := 3, + s := (3, 1), + p := 0, + g := 1, + id=f"stride height too big: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", ), pytest.param( - torch.nn.ConvTranspose2d(8, 16, (1, 5), stride=(1, 4)), - (1, 8, 1, 16), - id="Stride width != kernel width / 2, stride width != kernel width", + ins := (3, 7, 13, 11), + oc := 7, + ks := 3, + s := (1, 3), + p := 0, + g := 1, + id=f"stride width too big: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", ), pytest.param( - torch.nn.ConvTranspose2d(16, 12, (1, 4), stride=(3, 3)), - (1, 16, 1, 16), - id="Out channels % num_macs != 0", + ins := (3, 7, 9, 11), + oc := 7, + ks := 3, + s := 1, + p := (3, 1), + g := 1, + id=f"padding height >= kernel height: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", ), pytest.param( - torch.nn.ConvTranspose2d(64, 64, (1, 4), stride=(1, 2)), - (1, 64, 3, 12), - id="Out height != 1, stride width != kernel width", + ins := (3, 7, 9, 11), + oc := 7, + ks := 3, + s := 1, + p := (1, 3), + g := 1, + id=f"padding width >= kernel width: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", ), pytest.param( - torch.nn.ConvTranspose2d(16, 40, (1, 4), stride=(1, 4), padding=(0, 1)), - (1, 16, 4, 27), - id="Padding width != 1 and input height != 1", + ins := (3, 113, 123, 133), + oc := 11, + ks := (41, 15), + s := 1, + p := 0, + g := 1, + id=f"kernel_h * kernel_w * round_ceil(input_channels, num_macs) too big: {_conv_id(ins, oc, ks=ks, s=s, p=p)}", + ), + pytest.param( + ins := (3, 9, 11, 13), + oc := 3, + ks := 3, + s := 1, + p := 0, + g := 3, + id=f"groups > 1: {_conv_id(ins, oc, ks=ks, s=s, p=p, g=g)}", ), ], ) - def test_conv_transpose2d_non_delegated_conversion__quantized( - self, model: torch.nn.Module, input_shape, use_qat + def test__tr_conv_non_deleg( + self, input_shape, out_channels, kernel_size, stride, padding, groups, use_qat ): - edge_program = to_quantized_edge_program( - model, input_shape, use_qat=use_qat - ).exported_program() + in_channels = input_shape[1] - nodes = list(edge_program.graph.nodes) - assert len(nodes) == 15 - assert nodes[11].target == Convolution # TransposeConv not delegated. + model = Conv2dTransposedModule( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + ) + + assert_not_delegated(model, input_shape, use_qat) class TestConv: @@ -194,44 +570,6 @@ def _conv_id(ins, oc, ks=3, s=2, d=1, p=0, b=True, g=1): f"g={g}" ) - @staticmethod - def assert_delegated_and_correct(model, input_shape, mocker, request, use_qat): - graph_verifier = DetailedGraphVerifier( - mocker, - expected_delegated_ops={Convolution: 1}, - expected_non_delegated_ops={}, - ) - dataset = RandomDatasetCreator(low=-1, high=1) - - # Use quantized dataset and allow single bit error. - remove_quant_io_ops = True - comparator = AllCloseOutputComparator(atol=1) - - lower_run_compare( - model, - input_shape, - graph_verifier, - request, - dataset, - comparator, - use_qat=use_qat, - remove_quant_io_ops=remove_quant_io_ops, - ) - - @staticmethod - def assert_not_delegated(model, input_shape, use_qat): - delegated_ep = to_quantized_edge_program( - model, - input_shape, - use_qat=use_qat, - ).exported_program() - - # Make sure the `convolution` was NOT delegated. - assert not graph_contains_any_of_ops( - delegated_ep.graph, [ExecutorchDelegateCall] - ) - assert graph_contains_any_of_ops(delegated_ep.graph, [Convolution]) - @pytest.mark.parametrize( "input_shape, out_channels, is_qat", [ @@ -297,13 +635,13 @@ def assert_not_delegated(model, input_shape, use_qat): ), ], ) - def test__basic_nsys_inference( + def test__forward_conv__basic( self, input_shape, out_channels, is_qat, request, mocker ): in_channels = input_shape[1] model = Conv2dModule(in_channels=in_channels, out_channels=out_channels) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( "input_shape, is_qat", @@ -377,7 +715,7 @@ def test__depthwise(self, input_shape, is_qat, request, mocker): in_channels=input_shape[1], out_channels=out_channels, group=group ) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( "input_shape, out_channels, is_qat", @@ -459,7 +797,7 @@ def test__depthwise(self, input_shape, is_qat, request, mocker): def test__unusual_shapes(self, input_shape, out_channels, is_qat, request, mocker): model = Conv2dModule(in_channels=input_shape[1], out_channels=out_channels) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( "input_shape, is_qat", @@ -546,7 +884,7 @@ def test__depthwise__unusual_shapes(self, input_shape, is_qat, request, mocker): in_channels=input_shape[1], out_channels=out_channels, group=group ) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( "input_shape, out_channels, is_qat", @@ -576,7 +914,9 @@ def test__implicit_batch(self, input_shape, out_channels, is_qat, mocker, reques expected_delegated_ops={Convolution: 1, ViewCopy: 2}, expected_non_delegated_ops={}, ) - dataset = RandomDatasetCreator(low=-256, high=256) + dataset = RandomDatasetCreator(low=-1.0, high=1.0) + remove_quant_io_ops = True + comparator = AllCloseOutputComparator(atol=1) lower_run_compare( @@ -587,6 +927,7 @@ def test__implicit_batch(self, input_shape, out_channels, is_qat, mocker, reques dataset, comparator, use_qat=is_qat, + remove_quant_io_ops=remove_quant_io_ops, ) @pytest.mark.parametrize( @@ -707,7 +1048,7 @@ def test__implicit_batch(self, input_shape, out_channels, is_qat, mocker, reques s := 1, d := 1, qat := True, - id=f"qat={qat}, bounds of kernel_h * kernel_w * input_channels: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, bounds of kernel_h * kernel_w * round_ceil(input_channels, num_macs): {_conv_id(ins, oc, ks=ks, s=s, d=d)}", marks=pytest.mark.xfail( reason="AIR-14679", strict=True, @@ -720,7 +1061,7 @@ def test__implicit_batch(self, input_shape, out_channels, is_qat, mocker, reques s := 1, d := 1, qat := False, - id=f"qat={qat}, bounds of kernel_h * kernel_w * input_channels: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, bounds of kernel_h * kernel_w * round_ceil(input_channels, num_macs): {_conv_id(ins, oc, ks=ks, s=s, d=d)}", marks=pytest.mark.xfail( reason="AIR-14679", strict=True, @@ -747,7 +1088,7 @@ def test__big( dilation=dilation, ) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( "input_shape, kernel_size, stride, dilation, is_qat", @@ -854,7 +1195,7 @@ def test__big( s := 1, d := 1, qat := True, - id=f"qat={qat}, bounds of kernel_h * kernel_w * input_channels: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, bounds of kernel_h * kernel_w * round_ceil(input_channels, num_macs): {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", ), pytest.param( ins := (2, 80, 35, 34), @@ -862,7 +1203,7 @@ def test__big( s := 1, d := 1, qat := False, - id=f"qat={qat}, bounds of kernel_h * kernel_w * input_channels: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, bounds of kernel_h * kernel_w * round_ceil(input_channels, num_macs): {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", ), ], ) @@ -881,7 +1222,7 @@ def test__depthwise__big( group=group, ) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( "input_shape, out_channels, kernel_size, stride, dilation, padding, bias, is_qat", @@ -1065,7 +1406,7 @@ def test__non_default_params( bias=bias, ) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( "input_shape, kernel_size, stride, dilation, padding, bias, is_qat", @@ -1238,10 +1579,10 @@ def test__depthwise__non_default_params( group=group, ) - self.assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) + assert_delegated_and_correct(model, input_shape, mocker, request, is_qat) @pytest.mark.parametrize( - "input_shape, out_channels, kernel_size, stride, dilation, is_qat", + "input_shape, out_channels, kernel_size, stride, dilation, padding, is_qat", [ pytest.param( ins := (3, 7, 5000, 11), @@ -1249,8 +1590,9 @@ def test__depthwise__non_default_params( ks := (4097, 1), s := 1, d := 1, + p := 0, qat := True, - id=f"qat={qat}, kernel height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, kernel height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 5000, 11), @@ -1258,8 +1600,9 @@ def test__depthwise__non_default_params( ks := (4097, 1), s := 1, d := 1, + p := 0, qat := False, - id=f"qat={qat}, kernel height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, kernel height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), @@ -1267,8 +1610,9 @@ def test__depthwise__non_default_params( ks := (1, 4097), s := 1, d := 1, + p := 0, qat := True, - id=f"qat={qat}, kernel width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, kernel width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), @@ -1276,8 +1620,9 @@ def test__depthwise__non_default_params( ks := (1, 4097), s := 1, d := 1, + p := 0, qat := False, - id=f"qat={qat}, kernel width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, kernel width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 5000, 11), @@ -1285,8 +1630,9 @@ def test__depthwise__non_default_params( ks := 3, s := (4097, 1), d := 1, + p := 0, qat := True, - id=f"qat={qat}, stride height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, stride height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 5000, 11), @@ -1294,8 +1640,9 @@ def test__depthwise__non_default_params( ks := 3, s := (4097, 1), d := 1, + p := 0, qat := False, - id=f"qat={qat}, stride height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, stride height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), @@ -1303,8 +1650,9 @@ def test__depthwise__non_default_params( ks := 3, s := (1, 4097), d := 1, + p := 0, qat := True, - id=f"qat={qat}, stride width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, stride width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), @@ -1312,8 +1660,49 @@ def test__depthwise__non_default_params( ks := 3, s := (1, 4097), d := 1, + p := 0, qat := False, - id=f"qat={qat}, stride width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, stride width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + oc := 5, + ks := (3, 1), + s := 1, + d := 1, + p := (3, 1), + qat := True, + id=f"qat={qat}, padding height >= kernel height: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + oc := 5, + ks := (3, 1), + s := 1, + d := 1, + p := (3, 1), + qat := False, + id=f"qat={qat}, padding height >= kernel height: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + oc := 5, + ks := (1, 3), + s := 1, + d := 1, + p := (1, 3), + qat := True, + id=f"qat={qat}, padding width >= kernel width: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + oc := 5, + ks := (1, 3), + s := 1, + d := 1, + p := (1, 3), + qat := False, + id=f"qat={qat}, padding width >= kernel width: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 8500, 11), @@ -1321,8 +1710,9 @@ def test__depthwise__non_default_params( ks := 3, s := 1, d := (4097, 1), + p := 0, qat := True, - id=f"qat={qat}, dilation height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, dilation height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 8500, 11), @@ -1330,8 +1720,9 @@ def test__depthwise__non_default_params( ks := 3, s := 1, d := (4097, 1), + p := 0, qat := False, - id=f"qat={qat}, dilation height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, dilation height too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 13, 8500), @@ -1339,8 +1730,9 @@ def test__depthwise__non_default_params( ks := 3, s := 1, d := (1, 4097), + p := 0, qat := True, - id=f"qat={qat}, dilation width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, dilation width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 7, 13, 8500), @@ -1348,8 +1740,9 @@ def test__depthwise__non_default_params( ks := 3, s := 1, d := (1, 4097), + p := 0, qat := False, - id=f"qat={qat}, dilation width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, dilation width too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 113, 123, 133), @@ -1357,8 +1750,9 @@ def test__depthwise__non_default_params( ks := (41, 15), s := 1, d := 1, + p := 0, qat := True, - id=f"qat={qat}, kernel_h * kernel_w * input_channels too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, kernel_h * kernel_w * round_ceil(input_channels, num_macs) too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), pytest.param( ins := (3, 113, 123, 133), @@ -1366,13 +1760,14 @@ def test__depthwise__non_default_params( ks := (41, 15), s := 1, d := 1, + p := 0, qat := False, - id=f"qat={qat}, kernel_h * kernel_w * input_channels too big: {_conv_id(ins, oc, ks=ks, s=s, d=d)}", + id=f"qat={qat}, kernel_h * kernel_w * round_ceil(input_channels, num_macs) too big: {_conv_id(ins, oc, ks=ks, s=s, d=d, p=p)}", ), ], ) - def test__non_delegation( - self, input_shape, out_channels, kernel_size, stride, dilation, is_qat + def test__forward_conv_non_delegation( + self, input_shape, out_channels, kernel_size, stride, dilation, padding, is_qat ): in_channels = input_shape[1] @@ -1382,129 +1777,180 @@ def test__non_delegation( kernel_size=kernel_size, stride=stride, dilation=dilation, + padding=padding, ) - self.assert_not_delegated(model, input_shape, is_qat) + assert_not_delegated(model, input_shape, is_qat) @pytest.mark.parametrize( - "input_shape, kernel_size, stride, dilation, is_qat", + "input_shape, kernel_size, stride, dilation, padding, is_qat", [ pytest.param( ins := (3, 7, 5000, 11), ks := (4097, 1), s := 1, d := 1, + p := 0, qat := True, - id=f"qat={qat}, kernel height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, kernel height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 5000, 11), ks := (4097, 1), s := 1, d := 1, + p := 0, qat := False, - id=f"qat={qat}, kernel height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, kernel height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), ks := (1, 4097), s := 1, d := 1, + p := 0, qat := True, - id=f"qat={qat}, kernel width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, kernel width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), ks := (1, 4097), s := 1, d := 1, + p := 0, qat := False, - id=f"qat={qat}, kernel width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, kernel width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 5000, 11), ks := 3, s := (4097, 1), d := 1, + p := 0, qat := True, - id=f"qat={qat}, stride height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, stride height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 5000, 11), ks := 3, s := (4097, 1), d := 1, + p := 0, qat := False, - id=f"qat={qat}, stride height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, stride height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), ks := 3, s := (1, 4097), d := 1, + p := 0, qat := True, - id=f"qat={qat}, stride width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, stride width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 13, 5000), ks := 3, s := (1, 4097), d := 1, + p := 0, qat := False, - id=f"qat={qat}, stride width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, stride width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + ks := (3, 1), + s := 1, + d := 1, + p := (3, 1), + qat := True, + id=f"qat={qat}, padding height >= kernel height, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + ks := (3, 1), + s := 1, + d := 1, + p := (3, 1), + qat := False, + id=f"qat={qat}, padding height >= kernel height, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + ks := (1, 3), + s := 1, + d := 1, + p := (1, 3), + qat := True, + id=f"qat={qat}, padding width >= kernel width, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", + ), + pytest.param( + ins := (3, 7, 13, 11), + ks := (1, 3), + s := 1, + d := 1, + p := (1, 3), + qat := False, + id=f"qat={qat}, padding width >= kernel width, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 8500, 11), ks := 3, s := 1, d := (4097, 1), + p := 0, qat := True, - id=f"qat={qat}, dilation height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, dilation height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 8500, 11), ks := 3, s := 1, d := (4097, 1), + p := 0, qat := False, - id=f"qat={qat}, dilation height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, dilation height too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 13, 8500), ks := 3, s := 1, d := (1, 4097), + p := 0, qat := True, - id=f"qat={qat}, dilation width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, dilation width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 7, 13, 8500), ks := 3, s := 1, d := (1, 4097), + p := 0, qat := False, - id=f"qat={qat}, dilation width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, dilation width too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 113, 123, 133), ks := (41, 15), s := 1, d := 1, + p := 0, qat := True, - id=f"qat={qat}, kernel_h * kernel_w * input_channels too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, kernel_h * kernel_w * round_ceil(input_channels, num_macs) too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), pytest.param( ins := (3, 113, 123, 133), ks := (41, 15), s := 1, d := 1, + p := 0, qat := False, - id=f"qat={qat}, kernel_h * kernel_w * input_channels too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1])}", + id=f"qat={qat}, kernel_h * kernel_w * round_ceil(input_channels, num_macs) too big, depthwise: {_conv_id(ins, ins[1], ks=ks, s=s, d=d, g=ins[1], p=p)}", ), ], ) - def test__non_delegation_depthwise( - self, input_shape, kernel_size, stride, dilation, is_qat + def test__forward_conv_non_delegation_depthwise( + self, input_shape, kernel_size, stride, dilation, padding, is_qat ): out_channels = input_shape[1] group = input_shape[1] @@ -1516,6 +1962,7 @@ def test__non_delegation_depthwise( stride=stride, dilation=dilation, group=group, + padding=padding, ) - self.assert_not_delegated(model, input_shape, is_qat) + assert_not_delegated(model, input_shape, is_qat) diff --git a/backends/nxp/tests/models.py b/backends/nxp/tests/models.py index a5049a4c6cc..bf2292cc67d 100644 --- a/backends/nxp/tests/models.py +++ b/backends/nxp/tests/models.py @@ -100,6 +100,37 @@ def forward(self, x): return self.conv(x) +class Conv2dTransposedModule(torch.nn.Module): + def __init__( + self, + bias: bool = True, + dilation: Union[int, tuple[int, int]] = 1, + in_channels: int = 4, + kernel_size: Union[int, tuple[int, int]] = 3, + out_channels: int = 8, + padding: Union[str, int, Collection[int]] = 0, + output_padding: Union[int, tuple[int, int]] = 0, + stride: Union[int, tuple[int, int]] = 2, + groups: int = 1, + ): + super().__init__() + + self.conv_transp = torch.nn.ConvTranspose2d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + output_padding=output_padding, + dilation=dilation, + bias=bias, + groups=groups, + ) + + def forward(self, x): + return self.conv_transp(x) + + class Conv3dModule(torch.nn.Module): def __init__( self,