Rickard Bolin | bc6ee58 | 2022-11-04 08:24:29 +0000 | [diff] [blame] | 1 | # SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates <open-source-office@arm.com> |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 2 | # |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the License); you may |
| 6 | # not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an AS IS BASIS, WITHOUT |
| 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
Rickard Bolin | bc6ee58 | 2022-11-04 08:24:29 +0000 | [diff] [blame] | 16 | # |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 17 | # Description: |
| 18 | # The TosaSupportedOperators class which is a collection of all supported operators and parameter checks. |
| 19 | from collections import defaultdict |
| 20 | |
| 21 | from .data_type import DataType |
| 22 | from .operation import Op |
| 23 | from .supported_operators_util import docstring_format_args |
| 24 | from .supported_operators_util import list_formatter |
| 25 | from .tosa_mapping import optype_to_tosa_op_type |
| 26 | |
| 27 | |
| 28 | class TosaSupportedOperators: |
| 29 | # TODO currently sparsely populated |
| 30 | # Categorised lists of supported operators |
| 31 | convolution_ops = set((Op.Conv2DBias,)) |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 32 | depthwise_convolution_ops = set((Op.DepthwiseConv2DBias,)) |
| 33 | convolution_like_ops = convolution_ops | depthwise_convolution_ops |
| 34 | |
| 35 | # TODO depending on what will be committed |
Patrik Gustavsson | c74682c | 2021-08-17 14:26:38 +0200 | [diff] [blame] | 36 | max_pooling_ops = Op.op_set(Op.is_maxpool_op) |
| 37 | avg_pooling_ops = Op.op_set(Op.is_avgpool_op) |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 38 | pooling_ops = max_pooling_ops | avg_pooling_ops |
| 39 | fc_vector_products = set((Op.FullyConnected,)) |
Patrik Gustavsson | c74682c | 2021-08-17 14:26:38 +0200 | [diff] [blame] | 40 | |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 41 | mac_main_ops = convolution_like_ops | pooling_ops | fc_vector_products |
Jonas Ohlsson | d857507 | 2022-03-30 10:30:25 +0200 | [diff] [blame] | 42 | memory_only_ops = set( |
| 43 | ( |
| 44 | Op.Reshape, |
| 45 | Op.Transpose, |
| 46 | Op.Concat, |
| 47 | Op.SplitSliceRead, |
| 48 | ) |
| 49 | ) |
| 50 | binary_elem_wise_add_mul_sub = set( |
| 51 | ( |
| 52 | Op.Add, |
| 53 | Op.Mul, |
Jonas Ohlsson | d857507 | 2022-03-30 10:30:25 +0200 | [diff] [blame] | 54 | Op.Sub, |
| 55 | ) |
| 56 | ) |
Patrik Gustavsson | 46408a8 | 2021-09-20 10:47:47 +0200 | [diff] [blame] | 57 | elem_wise_ops = binary_elem_wise_add_mul_sub |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 58 | type_conversion_ops = set((Op.Rescale,)) |
Jonas Ohlsson | d857507 | 2022-03-30 10:30:25 +0200 | [diff] [blame] | 59 | relu_ops = set( |
| 60 | ( |
| 61 | Op.Clamp, |
| 62 | Op.ReluN, |
| 63 | ) |
| 64 | ) |
Patrik Gustavsson | f436ada | 2021-09-14 14:56:48 +0200 | [diff] [blame] | 65 | activation_ops = relu_ops | set((Op.Table,)) |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 66 | pad_ops = set((Op.Pad,)) |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 67 | |
Patrik Gustavsson | b4936ad | 2021-10-05 13:53:34 +0200 | [diff] [blame] | 68 | rank_unlimited_ops = set((Op.Concat, Op.Reshape, Op.Identity, Op.Pad)) |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 69 | rank6_limited_ops = elem_wise_ops |
Patrik Gustavsson | 008cd10 | 2021-09-24 13:46:42 +0200 | [diff] [blame] | 70 | batch_enabled_ops = rank6_limited_ops | rank_unlimited_ops |
| 71 | large_tens_dims_enabled_ops = batch_enabled_ops | set((Op.SplitSliceRead,)) |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 72 | npu_post_ops = activation_ops |
Patrik Gustavsson | 46408a8 | 2021-09-20 10:47:47 +0200 | [diff] [blame] | 73 | |
Patrik Gustavsson | ef3ebdd | 2021-10-01 11:10:25 +0200 | [diff] [blame] | 74 | supported_operators = ( |
| 75 | mac_main_ops |
| 76 | | type_conversion_ops |
| 77 | | npu_post_ops |
| 78 | | memory_only_ops |
| 79 | | elem_wise_ops |
| 80 | | pad_ops |
| 81 | | set((Op.Identity,)) |
| 82 | ) |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 83 | |
| 84 | # Supported data types |
| 85 | # TODO will differ compared to TensorFlow Lite, currently set to the same |
Patrik Gustavsson | f1580f0 | 2021-09-01 12:43:02 +0200 | [diff] [blame] | 86 | supported_op_dtypes = set((DataType.uint8, DataType.int8, DataType.int16, DataType.int32)) # TODO add bool |
Patrik Gustavsson | f366fb1 | 2021-09-07 13:30:29 +0200 | [diff] [blame] | 87 | tens_dim_range = (1, 65535) # TODO HW limitation, that is to be resolved in SW |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 88 | |
| 89 | def __init__(self): |
| 90 | # Setup the generic constraints. Note: the order matters |
| 91 | self.generic_constraints = [] |
| 92 | self.generic_constraints.append(TosaSupportedOperators.constraint_tens_dtype) |
Patrik Gustavsson | 008cd10 | 2021-09-24 13:46:42 +0200 | [diff] [blame] | 93 | self.generic_constraints.append(TosaSupportedOperators.constraint_tens_dimension) # TODO not supported yet |
| 94 | self.generic_constraints.append(TosaSupportedOperators.constraint_rank) # TODO not supported for all ops yet |
| 95 | self.generic_constraints.append(TosaSupportedOperators.constraint_batch) # TODO not supported for all ops yet |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 96 | |
Fredrik Svedberg | 88d5b12 | 2022-09-16 16:24:55 +0200 | [diff] [blame] | 97 | # Setup generic constraint exceptions |
| 98 | self.generic_constraints_exceptions = defaultdict(list) |
| 99 | |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 100 | # Setup specific constraints. Note: the order matters |
| 101 | self.specific_constraints = defaultdict(list) |
| 102 | |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 103 | self.specific_constraints[Op.Transpose].append(TosaSupportedOperators.constraint_ifm_producer) |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 104 | self.specific_constraints[Op.Pad].append(TosaSupportedOperators.constraint_padding_producer) |
Patrik Gustavsson | f436ada | 2021-09-14 14:56:48 +0200 | [diff] [blame] | 105 | self.specific_constraints[Op.Table].append(TosaSupportedOperators.constraint_table_dtype) |
| 106 | self.specific_constraints[Op.Table].append(TosaSupportedOperators.constraint_table_producer) |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 107 | |
| 108 | # Depthwise Conv specific checks: |
| 109 | for op_type in TosaSupportedOperators.depthwise_convolution_ops: |
| 110 | self.specific_constraints[op_type].append(TosaSupportedOperators.constraint_depth_multiplier) |
| 111 | |
Patrik Gustavsson | f366fb1 | 2021-09-07 13:30:29 +0200 | [diff] [blame] | 112 | # Avgpool specific checks |
| 113 | for op_type in TosaSupportedOperators.avg_pooling_ops: |
| 114 | self.specific_constraints[op_type].append(TosaSupportedOperators.constraint_padding) |
| 115 | |
Patrik Gustavsson | 8f1f9aa | 2021-06-28 07:41:58 +0200 | [diff] [blame] | 116 | def is_operator_supported(self, op): |
| 117 | ext_type = optype_to_tosa_op_type(op.type) |
| 118 | if op.type not in TosaSupportedOperators.supported_operators: |
| 119 | if op.type not in (Op.Placeholder, Op.SubgraphInput, Op.Const): |
| 120 | print(f"Info: {ext_type} '{op.name}' is not a NPU op") |
| 121 | return False |
| 122 | |
| 123 | for constraint in self.generic_constraints + self.specific_constraints[op.type]: |
| 124 | valid, extra = constraint(op) |
| 125 | if not valid: |
| 126 | print(f"Warning: {ext_type} '{op.name}' is not supported on the NPU") |
| 127 | print(f" - {constraint.__doc__}") |
| 128 | if extra: |
| 129 | print(f" {extra}") |
| 130 | return False |
| 131 | |
| 132 | return True |
| 133 | |
| 134 | # TODO this function is the same for TensorFlow Lite, but input might differ |
| 135 | @classmethod |
| 136 | @docstring_format_args([list_formatter(supported_op_dtypes)]) |
| 137 | def constraint_tens_dtype(cls, op): |
| 138 | "Tensors must be of type: {}" |
| 139 | valid = True |
| 140 | extra = [] |
| 141 | tensors = [tens for tens in op.get_ifm_ifm2_weights_ofm() if tens] |
| 142 | if not tensors: |
| 143 | tensors = [tens for tens in op.inputs if tens] |
| 144 | for tens in tensors: |
| 145 | if tens.dtype not in cls.supported_op_dtypes: |
| 146 | valid = False |
| 147 | extra.append(f"Tensor '{tens.name}' has data type: {tens.dtype}") |
| 148 | return valid, ", ".join(extra) |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 149 | |
Patrik Gustavsson | f366fb1 | 2021-09-07 13:30:29 +0200 | [diff] [blame] | 150 | # TODO Duplicates check present for TFLite. But it is only temporarily added |
| 151 | # This is for a HW limitation, that is to be resolved in SW later on |
| 152 | @classmethod |
| 153 | @docstring_format_args(tens_dim_range) |
Patrik Gustavsson | 3f22ec2 | 2021-09-21 14:18:44 +0200 | [diff] [blame] | 154 | def constraint_tens_dimension(self, op): |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 155 | "Tensor dimensions must be in the range [{}, {}]" |
Patrik Gustavsson | 3f22ec2 | 2021-09-21 14:18:44 +0200 | [diff] [blame] | 156 | tens_min, tens_max = self.tens_dim_range |
Patrik Gustavsson | f366fb1 | 2021-09-07 13:30:29 +0200 | [diff] [blame] | 157 | valid = True |
| 158 | extra = [] |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 159 | if op.type not in self.large_tens_dims_enabled_ops: |
Patrik Gustavsson | 3f22ec2 | 2021-09-21 14:18:44 +0200 | [diff] [blame] | 160 | tensors = [tens for tens in op.get_ifm_ifm2_weights_ofm() if tens] |
| 161 | if not tensors: |
| 162 | tensors = [tens for tens in op.inputs if tens] |
| 163 | for tens in tensors: |
| 164 | if not all(tens_min <= dim <= tens_max for dim in tens.shape): |
| 165 | valid = False |
| 166 | extra.append(f"Tensor '{tens.name}' has shape: {tens.shape}") |
Patrik Gustavsson | f366fb1 | 2021-09-07 13:30:29 +0200 | [diff] [blame] | 167 | return valid, ", ".join(extra) |
| 168 | |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 169 | # TODO This is for a HW limitation, that is to be resolved in SW later on |
Patrik Gustavsson | 46408a8 | 2021-09-20 10:47:47 +0200 | [diff] [blame] | 170 | @classmethod |
| 171 | def constraint_rank(self, op): |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 172 | "Tensor rank must be <= 6 or <= 4 depending on operator" |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 173 | valid = True |
| 174 | extra = [] |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 175 | if op.type not in self.rank_unlimited_ops: |
| 176 | if op.type in self.rank6_limited_ops: |
| 177 | rank_limit = 6 |
| 178 | else: |
| 179 | rank_limit = 4 |
Patrik Gustavsson | 46408a8 | 2021-09-20 10:47:47 +0200 | [diff] [blame] | 180 | tensors = [tens for tens in op.get_ifm_ifm2_weights_ofm() if tens] |
| 181 | if not tensors: |
| 182 | tensors = [tens for tens in op.inputs if tens] |
| 183 | for tens in tensors: |
| 184 | rank = len(tens.shape) |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 185 | if not rank <= rank_limit: |
Patrik Gustavsson | 46408a8 | 2021-09-20 10:47:47 +0200 | [diff] [blame] | 186 | valid = False |
Patrik Gustavsson | 008cd10 | 2021-09-24 13:46:42 +0200 | [diff] [blame] | 187 | extra.append( |
| 188 | f"Tensor '{tens.name}' has rank: {rank}, rank limit is currently {rank_limit}" |
| 189 | f" for op of type {op.type}" |
| 190 | ) |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 191 | return valid, ", ".join(extra) |
| 192 | |
| 193 | # TODO This is for a HW limitation, that is to be resolved in SW later on |
Patrik Gustavsson | 46408a8 | 2021-09-20 10:47:47 +0200 | [diff] [blame] | 194 | @classmethod |
| 195 | def constraint_batch(self, op): |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 196 | "If Tensor rank is 4 batch of ifms/ofm must be 1" |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 197 | valid = True |
| 198 | extra = [] |
Patrik Gustavsson | c2b129d | 2021-09-23 13:52:34 +0200 | [diff] [blame] | 199 | if op.type not in self.batch_enabled_ops: |
Patrik Gustavsson | 46408a8 | 2021-09-20 10:47:47 +0200 | [diff] [blame] | 200 | tensors = [tens for tens in op.get_ifm_ifm2_ofm() if tens] |
| 201 | if not tensors: |
| 202 | tensors = [tens for tens in op.inputs if tens] |
| 203 | for tens in tensors: |
| 204 | rank = len(tens.shape) |
| 205 | if rank == 4 and tens.shape[0] != 1: |
| 206 | valid = False |
| 207 | extra.append(f"Tensor '{tens.name}' has rank: 4 and N: {tens.shape[0]}") |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 208 | return valid, ", ".join(extra) |
| 209 | |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 210 | @staticmethod |
| 211 | def constraint_ifm_producer(cls, op): |
| 212 | "Input must be constant data" |
| 213 | valid = op.ifm.ops and op.ifm.ops[0].type == Op.Const |
| 214 | return valid, "Op has ifm with non-constant data" |
| 215 | |
Patrik Gustavsson | f366fb1 | 2021-09-07 13:30:29 +0200 | [diff] [blame] | 216 | @staticmethod |
| 217 | def constraint_padding(op): |
| 218 | # TODO Only support for when global scaling can be used. |
| 219 | # That is when there is padding no padding |
| 220 | "Avgpool only supported for no padding" |
| 221 | top, left, _, _ = op.attrs["explicit_padding"] |
| 222 | valid = top == 0 and left == 0 |
| 223 | |
| 224 | return valid, "Avgpool with pad_top {top} and pad_left {left}" |
| 225 | |
Patrik Gustavsson | e2bfa7e | 2021-09-08 15:04:11 +0200 | [diff] [blame] | 226 | # TODO limit padding to be const data for now. |
| 227 | # For TFLite it is assumed to be constant. |
| 228 | @staticmethod |
| 229 | def constraint_padding_producer(op): |
| 230 | "Input must be constant data" |
| 231 | valid = op.inputs[1].ops and op.inputs[1].ops[0].type == Op.Const |
| 232 | return valid, "PAD Op with non-constant data padding" |
| 233 | |
Patrik Gustavsson | f366fb1 | 2021-09-07 13:30:29 +0200 | [diff] [blame] | 234 | # TODO duplicates tflite_supported operators, but support for depth multiplier should be added at a later stage |
Patrik Gustavsson | df99510 | 2021-08-23 15:33:59 +0200 | [diff] [blame] | 235 | @staticmethod |
| 236 | def constraint_depth_multiplier(op): |
| 237 | "For depth multipliers > 1, IFM channels must be 1 and OFM channels must be equal to the depth multiplier" |
| 238 | depth_multiplier = op.attrs.get("depth_multiplier", 1) |
| 239 | if depth_multiplier > 1: |
| 240 | ifm_channels = op.ifm.shape[3] |
| 241 | ofm_channels = op.ofm.shape[3] |
| 242 | valid = (ifm_channels == 1) and (ofm_channels == depth_multiplier) |
| 243 | extra = ( |
| 244 | f"Op has ifm_channels={ifm_channels}, ofm_channels={ofm_channels}" |
| 245 | f" and depth_multiplier={depth_multiplier}" |
| 246 | ) |
| 247 | return valid, extra |
| 248 | return True, "Op has depth_multiplier=1" |
Patrik Gustavsson | f436ada | 2021-09-14 14:56:48 +0200 | [diff] [blame] | 249 | |
| 250 | # TODO Table operator support limited to int8 for now. |
| 251 | # For TFLite it is assumed to be constant. |
| 252 | @staticmethod |
| 253 | def constraint_table_dtype(op): |
| 254 | "Only supported is int8" |
| 255 | valid = True |
| 256 | tensors = [op.ifm, op.ofm, op.inputs[1]] |
| 257 | for tens in tensors: |
| 258 | if tens.dtype != DataType.int8: |
| 259 | valid = False |
| 260 | return valid, "Table operator with non int8 tensor" |
| 261 | |
| 262 | # TODO limit table to be constant data for now. |
| 263 | # Can it be non-constant? |
| 264 | @staticmethod |
| 265 | def constraint_table_producer(op): |
| 266 | "Input must be constant data" |
| 267 | valid = op.inputs[1].ops and op.inputs[1].ops[0].type == Op.Const |
| 268 | return valid, "Table Op with non-constant table input" |