From c0bb868fe375ff38eede8be363165794ca780978 Mon Sep 17 00:00:00 2001 From: Johan Alfven Date: Mon, 4 Sep 2023 17:18:33 +0200 Subject: MLBEDSW-7997: [MLCE] Extended stride support for TRANSPOSE CONV - Support for stride WxH 1x1 - Support for stride WxH 2x1 when IFM and KERNEL is 1D shape with height 1 - Added test to supported operators - Updated SUPPORTED_OPS.md Change-Id: Ic1abead8399a5e14a78d962f8aded0d3b3dbfcc4 Signed-off-by: Johan Alfven X --- SUPPORTED_OPS.md | 6 ++-- .../vela/test/test_tflite_supported_operators.py | 37 +++++++++++++++++++++- ethosu/vela/tflite_graph_optimiser.py | 29 +++++++++++------ ethosu/vela/tflite_supported_operators.py | 23 +++++++++++--- 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/SUPPORTED_OPS.md b/SUPPORTED_OPS.md index 81ed6d1..0fef738 100644 --- a/SUPPORTED_OPS.md +++ b/SUPPORTED_OPS.md @@ -19,7 +19,7 @@ limitations under the License. # Supported Ops This file was automatically generated by Vela using the `--supported-ops-report` parameter. -Vela version: `3.9.0` +Vela version: `3.9.1.dev2+gc02eaa3.d20230904` This file complies with [**Gitiles Markdown syntax**](https://github.com/google/gitiles/blob/master/Documentation/markdown.md) @@ -408,7 +408,9 @@ This is a list of constraints that the TRANSPOSE_CONV operator must satisfy in o - Optional Bias tensor must be of shape: 1D - Optional Bias tensor must be of type: int32, int64 - Optional Bias tensor values must fit within 40-bits -- Stride values for both width and height must be 2 +- Stride values for width and height must match one of the following criteria: + Stride values WxH must be 1x1 or 2x2 + Stride WxH 2x1 supported if ifm height and kernel height = 1 - SAME padding: OFM dimensions must equal IFM dimensions multiplied by stride - VALID padding: OFM dimensions must equal IFM dimensions multiplied by stride, minus difference between kernel size and stride diff --git a/ethosu/vela/test/test_tflite_supported_operators.py b/ethosu/vela/test/test_tflite_supported_operators.py index f54211f..e5cc280 100644 --- a/ethosu/vela/test/test_tflite_supported_operators.py +++ b/ethosu/vela/test/test_tflite_supported_operators.py @@ -218,12 +218,47 @@ def test_constraint_depth_multiplier(): def test_constraint_tconv_stride(): - # Strides must be 2 + # Valid 2x2 op = testutil.create_op_with_quant_tensors(Op.Conv2DBackpropInput, [0], [1, 2, 2, 1], weights_shape=[1, 1, 1, 1]) + op.attrs = {"stride_w": 2, "stride_h": 2, "padding": Padding.SAME} + ifm = Tensor([1, 1, 1, 1], DataType.uint8, "ifm") + ifm.quantization = testutil.default_quant_params() + op.add_input_tensor(ifm) + assert support.is_operator_supported(op) + # Valid 1x1 + op = testutil.create_op_with_quant_tensors(Op.Conv2DBackpropInput, [0], [1, 1, 1, 1], weights_shape=[1, 1, 1, 1]) op.attrs = {"stride_w": 1, "stride_h": 1, "padding": Padding.SAME} ifm = Tensor([1, 1, 1, 1], DataType.uint8, "ifm") ifm.quantization = testutil.default_quant_params() op.add_input_tensor(ifm) + assert support.is_operator_supported(op) + # Valid 2x1 (WxH) ifm h and kernel h = 1 + op = testutil.create_op_with_quant_tensors(Op.Conv2DBackpropInput, [0], [1, 1, 2, 1], weights_shape=[1, 1, 1, 1]) + op.attrs = {"stride_w": 2, "stride_h": 1, "padding": Padding.SAME} + ifm = Tensor([1, 1, 1, 1], DataType.uint8, "ifm") + ifm.quantization = testutil.default_quant_params() + op.add_input_tensor(ifm) + assert support.is_operator_supported(op) + # Invalid 2x1 (WxH) ifm h = 2 and kernel h = 1 + op = testutil.create_op_with_quant_tensors(Op.Conv2DBackpropInput, [0], [1, 1, 2, 1], weights_shape=[1, 1, 1, 1]) + op.attrs = {"stride_w": 2, "stride_h": 1, "padding": Padding.SAME} + ifm = Tensor([1, 2, 1, 1], DataType.uint8, "ifm") + ifm.quantization = testutil.default_quant_params() + op.add_input_tensor(ifm) + assert not support.is_operator_supported(op) + # Invalid 2x1 (WxH) ifm h = 1 and kernel h = 2 + op = testutil.create_op_with_quant_tensors(Op.Conv2DBackpropInput, [0], [1, 1, 1, 1], weights_shape=[1, 2, 1, 1]) + op.attrs = {"stride_w": 2, "stride_h": 1, "padding": Padding.SAME} + ifm = Tensor([1, 2, 1, 1], DataType.uint8, "ifm") + ifm.quantization = testutil.default_quant_params() + op.add_input_tensor(ifm) + assert not support.is_operator_supported(op) + # Invalid 1x2 (WxH) + op = testutil.create_op_with_quant_tensors(Op.Conv2DBackpropInput, [0], [1, 1, 1, 1], weights_shape=[1, 1, 1, 1]) + op.attrs = {"stride_w": 1, "stride_h": 2, "padding": Padding.SAME} + ifm = Tensor([1, 1, 1, 1], DataType.uint8, "ifm") + ifm.quantization = testutil.default_quant_params() + op.add_input_tensor(ifm) assert not support.is_operator_supported(op) diff --git a/ethosu/vela/tflite_graph_optimiser.py b/ethosu/vela/tflite_graph_optimiser.py index 9b98b8f..5107871 100644 --- a/ethosu/vela/tflite_graph_optimiser.py +++ b/ethosu/vela/tflite_graph_optimiser.py @@ -243,13 +243,15 @@ def calc_padding_and_skirt(padding_type, kernel, input_shape, explicit_padding): return padding, skirt -def calc_upscaled_padding_and_skirt(padding_type, kernel_size, stride, input_shape, upscaling_factor): +def calc_upscaled_padding_and_skirt( + padding_type, kernel_size, stride, input_shape, upscaling_factor_y, upscaling_factor_x +): kernel_height, kernel_width = kernel_size[0], kernel_size[1] if padding_type == Padding.SAME: - ypad = needed_total_padding(int(input_shape.height) * upscaling_factor, int(stride[1]), int(kernel_height)) - xpad = needed_total_padding(int(input_shape.width) * upscaling_factor, int(stride[2]), int(kernel_width)) - right_pad = max(((xpad + 1) // upscaling_factor) - 1, 0) - bottom_pad = max(((ypad + 1) // upscaling_factor) - 1, 0) + ypad = needed_total_padding(int(input_shape.height) * upscaling_factor_y, int(stride[1]), int(kernel_height)) + xpad = needed_total_padding(int(input_shape.width) * upscaling_factor_x, int(stride[2]), int(kernel_width)) + right_pad = max(((xpad + 1) // upscaling_factor_x) - 1, 0) + bottom_pad = max(((ypad + 1) // upscaling_factor_y) - 1, 0) left_pad = max(kernel_width - 1 - right_pad, 0) top_pad = max(kernel_height - 1 - bottom_pad, 0) elif padding_type == Padding.VALID: @@ -269,7 +271,11 @@ def fixup_conv2d_backprop(op: Operation, arch, nng) -> Operation: # flip the inputs op.inputs[0], op.inputs[2] = op.inputs[2], op.inputs[0] op.type = Op.Conv2DBackpropInputSwitchedBias - op.ifm_resampling_mode = resampling_mode.TRANSPOSE + stride_w = op.kernel.stride.x + stride_h = op.kernel.stride.y + if stride_w > 1 or stride_h > 1: + # Transpose conv2d with upscaling + op.ifm_resampling_mode = resampling_mode.TRANSPOSE # Update strides op.attrs.update({"stride_w": 1, "stride_h": 1, "strides": (1, 1, 1, 1)}) @@ -924,10 +930,15 @@ def add_padding_fields(op, arch, nng): else: raise UnsupportedFeatureError(f"Unknown operation that uses padding: {optype_to_builtintype(op.type)}") - if op.type == Op.Conv2DBackpropInputSwitchedBias: - upscaling_factor = output_shape.height // input_shape.height + if op.type == Op.Conv2DBackpropInputSwitchedBias and op.ifm_resampling_mode == resampling_mode.TRANSPOSE: + # Transpose with upscale padding, skirt = calc_upscaled_padding_and_skirt( - op.attrs["padding"], kernel_size, op.attrs["strides"], input_shape, upscaling_factor + op.attrs["padding"], + kernel_size, + op.attrs["strides"], + input_shape, + output_shape.height // input_shape.height, + output_shape.width // input_shape.width, ) else: padding, skirt = calc_padding_and_skirt( diff --git a/ethosu/vela/tflite_supported_operators.py b/ethosu/vela/tflite_supported_operators.py index 723c5f2..52b0485 100644 --- a/ethosu/vela/tflite_supported_operators.py +++ b/ethosu/vela/tflite_supported_operators.py @@ -590,11 +590,24 @@ class TFLiteSupportedOperators: @staticmethod def constraint_tconv_stride(op): - "Stride values for both width and height must be 2" - w = op.kernel.stride.x - h = op.kernel.stride.y - valid = (w == 2) and (h == 2) - return valid, f"Op has stride WxH as: {w}x{h}" + """Stride values for width and height must match one of the following criteria: + Stride values WxH must be 1x1 or 2x2 + Stride WxH 2x1 supported if ifm height and kernel height = 1""" + s_w = op.kernel.stride.x + s_h = op.kernel.stride.y + k_h = op.kernel.height + i_h = op.ifm.shape[1] + valid = False + if s_w == 1 and s_h == 1: + valid = True + + if s_w == 2 and s_h == 2: + valid = True + + if s_w == 2 and s_h == 1 and i_h == 1 and k_h == 1: + valid = True + + return valid, f"Op has ifm_height={i_h}, kernel_height={k_h} and stride WxH as {s_w}x{s_h}" @staticmethod def constraint_tconv_same(op): -- cgit v1.2.1