From d0e41cf4c20a0f5780a8dd072df29bdb3267d960 Mon Sep 17 00:00:00 2001 From: Tim Hall Date: Tue, 14 Feb 2023 14:54:18 +0000 Subject: MLBEDSW-7343: MLCE: Unsupported STRIDED_SLICE with negative index and shrinking an axis - The problem was that the end values of STRIDED_SLICE operators were not taking the shrink_axis_mask into account - The fix is simply to ignore the end value set on the operator and calculate one based upon shrinking the axis Change-Id: I2e5f2d3c9b08035dfd9b1629c775408f2356d1cf Signed-off-by: Tim Hall --- ethosu/vela/operation.py | 24 ++++++------------------ ethosu/vela/tflite_model_semantic.py | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 22 deletions(-) (limited to 'ethosu') diff --git a/ethosu/vela/operation.py b/ethosu/vela/operation.py index de35dcc7..f85cb4bb 100644 --- a/ethosu/vela/operation.py +++ b/ethosu/vela/operation.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2020-2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2020-2023 Arm Limited and/or its affiliates # # SPDX-License-Identifier: Apache-2.0 # @@ -458,19 +458,6 @@ def create_activation_function(op_type: Op, min=None, max=None) -> ActivationFun return act -def get_slice_offsets(input_shape: List[int], offset_tens: Tensor, offset_mask: int, is_begin: bool = True): - # For strided slice operator: get start or end offsets - offsets = len(input_shape) * [0] if is_begin else input_shape[:] - for idx in range(len(input_shape)): - # If the i:th bit in the mask is set then the value on offset_tens[i] should be ignored - if (offset_mask & (1 << idx)) == 0: - offsets[idx] = offset_tens.values[idx] - if offsets[idx] < 0: - # Convert offset to positive value - offsets[idx] += input_shape[idx] - return offsets - - class Operation: """Class representing a Neural Network operation. Has a name, a type, input and output tensors, as well as an attribute dictionary.""" @@ -775,17 +762,18 @@ class Operation: outputs = self.outputs # Extract masks - begin_mask = self.attrs["begin_mask"] ellipsis_mask = self.attrs["ellipsis_mask"] - end_mask = self.attrs["end_mask"] new_axis_mask = self.attrs["new_axis_mask"] shrink_axis_mask = self.attrs["shrink_axis_mask"] # shrink_axis_mask/new_axis_mask/ellipsis_mask is not supported by the Operation class but the operation # may have the attribute modified and handled in the graph optimization phase. assert shrink_axis_mask == new_axis_mask == ellipsis_mask == 0 - offset_start = get_slice_offsets(input_tens.shape, begin_tens, begin_mask, is_begin=True) - offset_end = get_slice_offsets(input_tens.shape, end_tens, end_mask, is_begin=False) + # use the begin and end values that were calculated in the model semantic check. this is because the end + # values can be affected (ignored) by the shrink_axis_mask and this mask may have been changed in the graph + # optimizer (see assert above) + offset_start = self.attrs["offset_begin"] + offset_end = self.attrs["offset_end"] elif self.type == Op.UnpackReshaped: # Requires fixup_unpack_output to be called before this point input_tens = self.inputs[0] diff --git a/ethosu/vela/tflite_model_semantic.py b/ethosu/vela/tflite_model_semantic.py index 2851ab16..0c2086c3 100644 --- a/ethosu/vela/tflite_model_semantic.py +++ b/ethosu/vela/tflite_model_semantic.py @@ -23,7 +23,6 @@ import numpy as np from .data_type import BaseType from .data_type import DataType from .numeric_util import is_integer -from .operation import get_slice_offsets from .operation import Op from .supported_operators_util import docstring_format_args from .supported_operators_util import list_formatter @@ -551,15 +550,42 @@ class TFLiteSemantic: valid = (new_axis == 0) or (shrink_axis == 0) return valid, f"Op has new_axis_mask={new_axis} and shrink_axis_mask={shrink_axis}" + def _get_slice_offsets(input_shape, offset_tens, offset_mask, is_begin=True): + # For strided slice operator: get start or end offsets + # input_shape: List[int], offset_tens: Tensor, offset_mask: int, is_begin: bool = True + offsets = len(input_shape) * [0] if is_begin else input_shape[:] + for idx in range(len(input_shape)): + # If the i:th bit in the mask is not set then the value in offset_tens[i] should be used, otherwise it + # should be ignored + if (offset_mask & (1 << idx)) == 0: + offsets[idx] = offset_tens.values[idx] + if offsets[idx] < 0: + # Convert negative indexing to positive ones + offsets[idx] += input_shape[idx] + return offsets + @staticmethod def constraint_slice_ranges(op): "Slice 'end' values must be greater than 'begin' values" ifm, begin, end, _ = op.inputs + shrink_axis_mask = op.attrs["shrink_axis_mask"] # Calculate offset begin/end - offset_begin = get_slice_offsets(ifm.shape, begin, op.attrs["begin_mask"], is_begin=True) - offset_end = get_slice_offsets(ifm.shape, end, op.attrs["end_mask"], is_begin=False) + offset_begin = TFLiteSemantic._get_slice_offsets(ifm.shape, begin, op.attrs["begin_mask"], is_begin=True) + offset_end = TFLiteSemantic._get_slice_offsets(ifm.shape, end, op.attrs["end_mask"], is_begin=False) # Check "end - begin" doesn't result in any zero or negative elements - valid = all((e - b) > 0 for b, e in zip(offset_begin, offset_end)) + valid = True + # if a shrink mask bit is set then the end position provided by the operation should be ignored, and instead a + # new end position should be calculated so that calculations in the graph optimiser, such as (end - start), + # result in the correct value. otherwise, we just need to check that the begin and end values are valid + for i in range(len(ifm.shape)): + if (shrink_axis_mask & (1 << i)) != 0: + offset_end[i] = offset_begin[i] + 1 + else: + if offset_end[i] <= offset_begin[i]: + valid = False + + op.attrs["offset_begin"] = offset_begin + op.attrs["offset_end"] = offset_end return valid, f"Op has begin_values={begin.values} and end_values={end.values}" @staticmethod -- cgit v1.2.1