blob: 8dec379dd235e6c74e857a9cb6c27b44f1413c01 [file] [log] [blame]
Tim Hall79d07d22020-04-27 18:20:16 +01001# Copyright (C) 2020 Arm Limited or its affiliates. All rights reserved.
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.
Tim Hall79d07d22020-04-27 18:20:16 +010016# Description:
17# Internal representation of a Neural Network Operation.
Tim Hall79d07d22020-04-27 18:20:16 +010018import enum
19
20
21class NpuBlockType(enum.Enum):
22 Default = 0
23 ConvolutionMxN = 1
24 VectorProduct = 2
25 Pooling = 3
26 ConvolutionDepthWise = 4
27 ElementWise = 5
Fredrik Svedberga0c36242020-06-03 15:43:31 +020028 ReduceSum = 6
Tim Hall79d07d22020-04-27 18:20:16 +010029
30
31class Operation:
32 """Class representing a Neural Network operation. Has a name, a type,
33input and output tensors, as well as an attribute dictionary."""
34
Fredrik Svedberga0c36242020-06-03 15:43:31 +020035 __slots__ = (
36 "type",
37 "name",
38 "op_index",
39 "attrs",
40 "inputs",
41 "outputs",
42 "flops",
43 "scheduled_pass",
44 "run_on_npu",
45 "activation_lut",
46 )
Tim Hall79d07d22020-04-27 18:20:16 +010047
48 def __init__(self, op_type, name):
49 self.type = op_type
50 self.name = name
51 self.attrs = {}
52 self.inputs = []
53 self.outputs = []
54 self.flops = 0
55 self.run_on_npu = True
56 self.scheduled_pass = None
Tim Hallc8310b12020-06-17 14:53:11 +010057 self.op_index = None # input network operator index
Fredrik Svedberga0c36242020-06-03 15:43:31 +020058 self.activation_lut = None
Tim Hall79d07d22020-04-27 18:20:16 +010059
60 def clone(self, suffix="_clone"):
61 res = Operation(self.type, self.name + suffix)
62
63 res.attrs = dict(self.attrs)
64 res.inputs = list(self.inputs)
65 res.outputs = list(self.outputs)
66 res.flops = self.flops
67 res.scheduled_pass = self.scheduled_pass
Tim Hallc8310b12020-06-17 14:53:11 +010068 res.op_index = None # not relevant as not part of input network
Tim Hall79d07d22020-04-27 18:20:16 +010069
70 return res
71
72 def __str__(self):
73 return "<nng.Operation '%s' type=%s>" % (self.name, self.type)
74
75 __repr__ = __str__
76
77 def get_ifm_ifm2_weight_bias_ofm_indices(self):
78 ifm_idx = -1
79 ifm2_idx = -1
80 weight_idx = -1
81 bias_idx = -1
82 ofm_idx = -1
83 npu_block_type = self.attrs.get("npu_block_type", NpuBlockType.Default)
Jacob Bohlina41cd4d2020-08-26 18:21:28 +020084 if npu_block_type in (NpuBlockType.ConvolutionMxN, NpuBlockType.ConvolutionDepthWise):
Tim Hall79d07d22020-04-27 18:20:16 +010085 ifm_idx = 0
86 weight_idx = 1
87 ofm_idx = 0
88
Jacob Bohlina41cd4d2020-08-26 18:21:28 +020089 if self.type in ("Conv2DBiasAct", "DepthwiseConv2dBiasAct", "TransposeConvAct"):
Tim Hall79d07d22020-04-27 18:20:16 +010090 if len(self.inputs) >= 3:
91 bias_idx = 2
92
Jacob Bohlincf7da102020-05-20 09:03:40 +020093 elif self.type == "Conv2DBackpropInputSwitchedBias":
94 bias_idx = 3
95
Fredrik Svedberga0c36242020-06-03 15:43:31 +020096 elif npu_block_type in (NpuBlockType.Pooling, NpuBlockType.ReduceSum):
Tim Hall79d07d22020-04-27 18:20:16 +010097 ifm_idx = 0
98 ofm_idx = 0
99 elif npu_block_type == NpuBlockType.VectorProduct:
100 ifm_idx = 0
101 weight_idx = 1
102 ofm_idx = 0
103
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200104 if self.type == "FullyConnectedAct":
Tim Hall79d07d22020-04-27 18:20:16 +0100105 if len(self.inputs) >= 3:
106 bias_idx = 2
107
108 if self.type == "BlockLSTM":
109 ifm_idx = 3
110 weight_idx = 4
111 ofm_idx = 6
112
113 elif npu_block_type == NpuBlockType.ElementWise:
114 ifm_idx = 0
115 ifm2_idx = 1
116 ofm_idx = 0
117
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200118 # LeakyRelu, Abs and CLZ have a single IFM
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200119 if self.type in ("LeakyRelu", "Abs", "CLZ"):
Tim Hall79d07d22020-04-27 18:20:16 +0100120 ifm2_idx = -1
121
122 elif self.type == "Conv2DBackpropInput":
123 ifm_idx = 2
124 weight_idx = 1
125 ofm_idx = 0
126
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200127 elif self.type in ("Squeeze", "Reshape", "QuantizedReshape", "ExpandDims"):
Tim Hall79d07d22020-04-27 18:20:16 +0100128 ifm_idx = 0
129 ofm_idx = 0
130
131 elif self.is_split_op():
132 ifm_idx = 0
133 ofm_idx = 0
134 if self.type == "Split":
135 ifm_idx = 1
136
137 elif self.is_concat_op():
138 ifms, _ = self.get_concat_inputs_axis()
139 ifm_idx = self.inputs.index(ifms[0])
140 if len(ifms) > 1:
141 ifm2_idx = self.inputs.index(ifms[1])
142 ofm_idx = 0
143
144 return ifm_idx, ifm2_idx, weight_idx, bias_idx, ofm_idx
145
146 def get_ifm_ifm2_weights_ofm(self):
147 ifm_tensor = None
148 ifm2_tensor = None
149 weight_tensor = None
150 ofm_tensor = None
151
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200152 ifm_idx, ifm2_idx, weight_idx, _, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
Tim Hall79d07d22020-04-27 18:20:16 +0100153 if ifm_idx != -1:
154 ifm_tensor = self.inputs[ifm_idx]
155 if ifm2_idx != -1:
156 ifm2_tensor = self.inputs[ifm2_idx]
157 if weight_idx != -1:
158 weight_tensor = self.inputs[weight_idx]
159 if ofm_idx != -1:
160 ofm_tensor = self.outputs[ofm_idx]
161
162 return ifm_tensor, ifm2_tensor, weight_tensor, ofm_tensor
163
164 def get_ifm_weights_biases_ofm(self):
165 ifm_tensor = None
166 weight_tensor = None
167 bias_tensor = None
168 ofm_tensor = None
169
170 ifm_idx, _, weight_idx, bias_idx, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
171 if ifm_idx != -1:
172 ifm_tensor = self.inputs[ifm_idx]
173 if weight_idx != -1:
174 weight_tensor = self.inputs[weight_idx]
175 if bias_idx != -1:
176 bias_tensor = self.inputs[bias_idx]
177 if ofm_idx != -1:
178 ofm_tensor = self.outputs[ofm_idx]
179
180 return ifm_tensor, weight_tensor, bias_tensor, ofm_tensor
181
Jacob Bohlin49d92122020-08-19 14:36:46 +0200182 def get_ifm_ifm2_weights_biases_ofm(self):
183 ifm_tensor = None
184 ifm2_tensor = None
185 weight_tensor = None
186 bias_tensor = None
187 ofm_tensor = None
188
189 ifm_idx, ifm2_idx, weight_idx, bias_idx, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
190 if ifm_idx != -1:
191 ifm_tensor = self.inputs[ifm_idx]
192 if ifm2_idx != -1:
193 ifm2_tensor = self.inputs[ifm2_idx]
194 if weight_idx != -1:
195 weight_tensor = self.inputs[weight_idx]
196 if bias_idx != -1:
197 bias_tensor = self.inputs[bias_idx]
198 if ofm_idx != -1:
199 ofm_tensor = self.outputs[ofm_idx]
200
201 return ifm_tensor, ifm2_tensor, weight_tensor, bias_tensor, ofm_tensor
202
Tim Hall79d07d22020-04-27 18:20:16 +0100203 def is_concat_op(self):
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200204 return self.type in ("Concat", "ConcatV2", "QuantizedConcat", "ConcatTFLite", "PackReshaped")
Tim Hall79d07d22020-04-27 18:20:16 +0100205
206 def get_concat_inputs_axis(self):
207 assert self.is_concat_op()
208
209 if self.type == "ConcatV2":
210 axis_tensor = self.inputs[-1]
211 inputs = self.inputs[:-1]
212 elif self.type == "Concat":
213 axis_tensor = self.inputs[0]
214 inputs = self.inputs[1:]
215 elif self.type == "QuantizedConcat":
216 axis_tensor = self.inputs[0]
217 inputs = self.inputs[1:]
218 inputs = inputs[: len(inputs) // 3] # Skip min/max
219
220 if self.type == "ConcatTFLite":
221 inputs = self.inputs
222 axis = self.attrs["axis"]
223 elif self.type == "PackReshaped":
224 # Requires fixup_pack_input to be called before this point
225 inputs = self.inputs
226 axis = self.attrs["axis"]
227 assert len(self.inputs) == self.attrs["values_count"]
228 else:
229 assert len(axis_tensor.ops) == 1 and axis_tensor.ops[0].type == "Const"
230 axis = int(axis_tensor.values)
231
232 return inputs, axis
233
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200234 def get_dilation_h_w(self):
235 _, dilation_h, dilation_w, _ = self.attrs.get("dilation", (1, 1, 1, 1))
236 return dilation_h, dilation_w
237
Tim Hall79d07d22020-04-27 18:20:16 +0100238 def is_split_op(self):
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200239 return self.type in ("Split", "SplitV", "StridedSlice", "Slice", "UnpackReshaped")
Tim Hall79d07d22020-04-27 18:20:16 +0100240
241 def get_split_inputs_axis(self):
242 assert self.is_split_op()
243
244 offset_start = None
245 offset_end = None
246 axis = None
247 if self.type == "Split":
Tim Hall79d07d22020-04-27 18:20:16 +0100248 num_splits = self.attrs.get("num_splits")
249 axis_tens = self.inputs[0]
250 assert len(axis_tens.ops) == 1 and axis_tens.ops[0].type == "Const"
251 axis = int(axis_tens.values)
252 input_tens = self.inputs[1]
253 outputs = self.outputs
254 assert num_splits == len(outputs)
255
Louis Verhaard9b8fa122020-05-15 13:41:13 +0200256 elif self.type == "SplitV":
Charles Xu53d47522020-05-04 11:32:05 +0200257 num_splits = self.attrs.get("num_splits")
258 input_tens = self.inputs[0]
259 size_tens = self.inputs[1]
260 assert len(size_tens.ops) == 1 and size_tens.ops[0].type == "Const"
261 sizes = size_tens.values
262 axis_tens = self.inputs[2]
263 assert len(axis_tens.ops) == 1 and axis_tens.ops[0].type == "Const"
264 axis = int(axis_tens.values)
265 outputs = self.outputs
266 assert num_splits == len(outputs)
267 assert sum(sizes) == input_tens.shape[axis]
268
Tim Hall79d07d22020-04-27 18:20:16 +0100269 elif self.type == "Slice":
270 input_tens, begin_tens, size_tens = self.inputs
271 outputs = self.outputs
272 offset_start = [0] * len(input_tens.shape)
273 offset_end = [0] * len(input_tens.shape)
274
275 for idx in range(len(begin_tens.values)):
276 # Check if the op should slice in dimension idx
277 if size_tens.values[idx] != input_tens.shape[idx]:
278 offset_start[idx] = begin_tens.values[idx]
279 offset_end[idx] = size_tens.values[idx] + offset_start[idx]
280
281 elif self.type == "StridedSlice":
282 input_tens, begin_tens, end_tens, strides_tens = self.inputs
283 outputs = self.outputs
284 out_tens = outputs[0]
285 offset_start = [0] * len(outputs[0].shape)
286 offset_end = [0] * len(outputs[0].shape)
287
288 # Extract masks
289 begin_mask = self.attrs["begin_mask"]
290 ellipsis_mask = self.attrs["ellipsis_mask"]
291 end_mask = self.attrs["end_mask"]
292 new_axis_mask = self.attrs["new_axis_mask"]
293 shrink_axis_mask = self.attrs["shrink_axis_mask"]
Patrik Gustavssoncf728902020-04-30 08:57:23 +0200294
295 # shrink_axis_mask/new_axis_mask/ellipsis_mask is not supported by the Operation class but the operation
Tim Hall79d07d22020-04-27 18:20:16 +0100296 # may have the attribute modified and handled in the graph optimization phase.
Patrik Gustavssoncf728902020-04-30 08:57:23 +0200297 assert shrink_axis_mask == new_axis_mask == ellipsis_mask == 0
Tim Hall79d07d22020-04-27 18:20:16 +0100298 assert len(input_tens.shape) == len(out_tens.shape)
299
300 for idx in range(len(input_tens.shape)):
Patrik Gustavsson49134332020-04-29 14:10:32 +0200301 # Check if slicing is needed in this axis
302 if end_tens.values[idx] != input_tens.shape[idx] or (
303 end_tens.values[idx] == input_tens.shape[idx] and begin_tens.values[idx] != 0
304 ):
305 # If the i:th bit in begin_mask is set then the value on begin[i] should be ignored
306 if (begin_mask & (1 << idx)) == 0:
Tim Hall79d07d22020-04-27 18:20:16 +0100307 offset_start[idx] = begin_tens.values[idx]
Tim Hall79d07d22020-04-27 18:20:16 +0100308
Patrik Gustavsson49134332020-04-29 14:10:32 +0200309 # If the i:th bit in end_mask is set then the value on end[i] should be ignored
310 if (end_mask & (1 << idx)) == 0:
311 offset_end[idx] = end_tens.values[idx]
Tim Hall79d07d22020-04-27 18:20:16 +0100312
313 elif self.type == "UnpackReshaped":
314 # Requires fixup_unpack_output to be called before this point
315 input_tens = self.inputs[0]
316 outputs = self.outputs
317 axis = self.attrs["axis"]
318 num_splits = self.attrs["num"]
319 # Number of outputs have to equal the value of the dimension to unpack
320 assert num_splits == len(outputs) == input_tens.shape[axis]
321 else:
322 assert False
323
324 return input_tens, outputs, axis, offset_start, offset_end
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200325
326 def set_activation_lut(self, lut_tensor):
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200327 self.attrs["fused_activation_function"] = "LUT"
328 self.activation_lut = lut_tensor
Michael McGeaghc5b549b2020-08-07 11:54:28 +0100329 self.add_input_tensor(lut_tensor)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100330
331 def add_input_tensor(self, tens):
332 self.inputs.append(tens)
333 if self not in tens.consumer_list:
334 tens.consumer_list.append(self)
335
Jacob Bohlin67e0d8f2020-08-20 10:53:02 +0200336 def set_input_tensor(self, tens, idx):
337 tens_to_remove = self.inputs[idx]
338 if tens_to_remove in tens.consumer_list:
339 tens.consumer_list.remove(tens_to_remove)
340
341 self.inputs[idx] = tens
342 if self not in tens.consumer_list:
343 tens.consumer_list.append(self)
344
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100345 def set_output_tensor(self, tens):
346 tens.ops = [self]
347 self.outputs = [tens]
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200348
349 def needs_bias(self):
350 return self.type in (
351 "Conv2DBiasAct",
352 "DepthwiseConv2dBiasAct",
353 "Conv2DBackpropInputSwitchedBias",
354 "FullyConnectedAct",
355 )