blob: 252f03b73eedf4b4ceede34ecd4852dc493d15fb [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
Michael McGeagh8dbf8cf2020-09-08 11:09:48 +010031def create_avgpool_nop(name):
32 op = Operation("AvgPool", name)
33 op.attrs["padding"] = b"VALID"
34 op.attrs["npu_block_type"] = NpuBlockType.Pooling
35 op.attrs["stride_w"] = 1
36 op.attrs["stride_h"] = 1
37 op.attrs["filter_width"] = 1
38 op.attrs["filter_height"] = 1
39 op.attrs["strides"] = [1, 1, 1, 1]
40 op.attrs["ksize"] = [1, 1, 1, 1]
41 op.attrs["skirt"] = [0, 0, 0, 0]
42 op.attrs["explicit_padding"] = [0, 0, 0, 0]
43 return op
44
45
Louis Verhaardfa2f92a2020-09-21 11:56:18 +020046def get_slice_offsets(input_shape, offset_tens, offset_mask, is_begin=True):
47 # For strided slice operator: get start or end offsets
48 offsets = len(input_shape) * [0] if is_begin else input_shape[:]
49 for idx in range(len(input_shape)):
50 # If the i:th bit in the mask is set then the value on offset_tens[i] should be ignored
51 if (offset_mask & (1 << idx)) == 0:
52 offsets[idx] = offset_tens.values[idx]
53 if offsets[idx] < 0:
54 # Convert offset to positive value
55 offsets[idx] += input_shape[idx]
56 return offsets
57
58
Tim Hall79d07d22020-04-27 18:20:16 +010059class Operation:
60 """Class representing a Neural Network operation. Has a name, a type,
61input and output tensors, as well as an attribute dictionary."""
62
Fredrik Svedberga0c36242020-06-03 15:43:31 +020063 __slots__ = (
64 "type",
65 "name",
66 "op_index",
67 "attrs",
68 "inputs",
69 "outputs",
70 "flops",
71 "scheduled_pass",
72 "run_on_npu",
73 "activation_lut",
74 )
Tim Hall79d07d22020-04-27 18:20:16 +010075
76 def __init__(self, op_type, name):
77 self.type = op_type
78 self.name = name
79 self.attrs = {}
80 self.inputs = []
81 self.outputs = []
82 self.flops = 0
83 self.run_on_npu = True
84 self.scheduled_pass = None
Tim Hallc8310b12020-06-17 14:53:11 +010085 self.op_index = None # input network operator index
Fredrik Svedberga0c36242020-06-03 15:43:31 +020086 self.activation_lut = None
Tim Hall79d07d22020-04-27 18:20:16 +010087
88 def clone(self, suffix="_clone"):
89 res = Operation(self.type, self.name + suffix)
90
91 res.attrs = dict(self.attrs)
92 res.inputs = list(self.inputs)
93 res.outputs = list(self.outputs)
94 res.flops = self.flops
95 res.scheduled_pass = self.scheduled_pass
Tim Hallc8310b12020-06-17 14:53:11 +010096 res.op_index = None # not relevant as not part of input network
Tim Hall79d07d22020-04-27 18:20:16 +010097
98 return res
99
100 def __str__(self):
101 return "<nng.Operation '%s' type=%s>" % (self.name, self.type)
102
103 __repr__ = __str__
104
105 def get_ifm_ifm2_weight_bias_ofm_indices(self):
106 ifm_idx = -1
107 ifm2_idx = -1
108 weight_idx = -1
109 bias_idx = -1
110 ofm_idx = -1
111 npu_block_type = self.attrs.get("npu_block_type", NpuBlockType.Default)
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200112 if npu_block_type in (NpuBlockType.ConvolutionMxN, NpuBlockType.ConvolutionDepthWise):
Tim Hall79d07d22020-04-27 18:20:16 +0100113 ifm_idx = 0
114 weight_idx = 1
115 ofm_idx = 0
116
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200117 if self.type in ("Conv2DBiasAct", "DepthwiseConv2dBiasAct", "TransposeConvAct"):
Tim Hall79d07d22020-04-27 18:20:16 +0100118 if len(self.inputs) >= 3:
119 bias_idx = 2
120
Jacob Bohlincf7da102020-05-20 09:03:40 +0200121 elif self.type == "Conv2DBackpropInputSwitchedBias":
122 bias_idx = 3
123
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200124 elif npu_block_type in (NpuBlockType.Pooling, NpuBlockType.ReduceSum):
Tim Hall79d07d22020-04-27 18:20:16 +0100125 ifm_idx = 0
126 ofm_idx = 0
127 elif npu_block_type == NpuBlockType.VectorProduct:
128 ifm_idx = 0
129 weight_idx = 1
130 ofm_idx = 0
131
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200132 if self.type == "FullyConnectedAct":
Tim Hall79d07d22020-04-27 18:20:16 +0100133 if len(self.inputs) >= 3:
134 bias_idx = 2
135
136 if self.type == "BlockLSTM":
137 ifm_idx = 3
138 weight_idx = 4
139 ofm_idx = 6
140
141 elif npu_block_type == NpuBlockType.ElementWise:
142 ifm_idx = 0
143 ifm2_idx = 1
144 ofm_idx = 0
145
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200146 # LeakyRelu, Abs and CLZ have a single IFM
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200147 if self.type in ("LeakyRelu", "Abs", "CLZ"):
Tim Hall79d07d22020-04-27 18:20:16 +0100148 ifm2_idx = -1
149
150 elif self.type == "Conv2DBackpropInput":
151 ifm_idx = 2
152 weight_idx = 1
153 ofm_idx = 0
154
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200155 elif self.type in ("Squeeze", "Reshape", "QuantizedReshape", "ExpandDims"):
Tim Hall79d07d22020-04-27 18:20:16 +0100156 ifm_idx = 0
157 ofm_idx = 0
158
159 elif self.is_split_op():
160 ifm_idx = 0
161 ofm_idx = 0
162 if self.type == "Split":
163 ifm_idx = 1
164
165 elif self.is_concat_op():
166 ifms, _ = self.get_concat_inputs_axis()
167 ifm_idx = self.inputs.index(ifms[0])
168 if len(ifms) > 1:
169 ifm2_idx = self.inputs.index(ifms[1])
170 ofm_idx = 0
171
172 return ifm_idx, ifm2_idx, weight_idx, bias_idx, ofm_idx
173
174 def get_ifm_ifm2_weights_ofm(self):
175 ifm_tensor = None
176 ifm2_tensor = None
177 weight_tensor = None
178 ofm_tensor = None
179
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200180 ifm_idx, ifm2_idx, weight_idx, _, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
Tim Hall79d07d22020-04-27 18:20:16 +0100181 if ifm_idx != -1:
182 ifm_tensor = self.inputs[ifm_idx]
183 if ifm2_idx != -1:
184 ifm2_tensor = self.inputs[ifm2_idx]
185 if weight_idx != -1:
186 weight_tensor = self.inputs[weight_idx]
187 if ofm_idx != -1:
188 ofm_tensor = self.outputs[ofm_idx]
189
190 return ifm_tensor, ifm2_tensor, weight_tensor, ofm_tensor
191
192 def get_ifm_weights_biases_ofm(self):
193 ifm_tensor = None
194 weight_tensor = None
195 bias_tensor = None
196 ofm_tensor = None
197
198 ifm_idx, _, weight_idx, bias_idx, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
199 if ifm_idx != -1:
200 ifm_tensor = self.inputs[ifm_idx]
201 if weight_idx != -1:
202 weight_tensor = self.inputs[weight_idx]
203 if bias_idx != -1:
204 bias_tensor = self.inputs[bias_idx]
205 if ofm_idx != -1:
206 ofm_tensor = self.outputs[ofm_idx]
207
208 return ifm_tensor, weight_tensor, bias_tensor, ofm_tensor
209
Jacob Bohlin49d92122020-08-19 14:36:46 +0200210 def get_ifm_ifm2_weights_biases_ofm(self):
211 ifm_tensor = None
212 ifm2_tensor = None
213 weight_tensor = None
214 bias_tensor = None
215 ofm_tensor = None
216
217 ifm_idx, ifm2_idx, weight_idx, bias_idx, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
218 if ifm_idx != -1:
219 ifm_tensor = self.inputs[ifm_idx]
220 if ifm2_idx != -1:
221 ifm2_tensor = self.inputs[ifm2_idx]
222 if weight_idx != -1:
223 weight_tensor = self.inputs[weight_idx]
224 if bias_idx != -1:
225 bias_tensor = self.inputs[bias_idx]
226 if ofm_idx != -1:
227 ofm_tensor = self.outputs[ofm_idx]
228
229 return ifm_tensor, ifm2_tensor, weight_tensor, bias_tensor, ofm_tensor
230
Louis Verhaard98a34992020-09-01 10:39:04 +0200231 def get_ofm(self):
232 _, _, _, ofm = self.get_ifm_ifm2_weights_ofm()
233 return ofm
234
Tim Hall79d07d22020-04-27 18:20:16 +0100235 def is_concat_op(self):
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200236 return self.type in ("Concat", "ConcatV2", "QuantizedConcat", "ConcatTFLite", "PackReshaped")
Tim Hall79d07d22020-04-27 18:20:16 +0100237
238 def get_concat_inputs_axis(self):
239 assert self.is_concat_op()
240
241 if self.type == "ConcatV2":
242 axis_tensor = self.inputs[-1]
243 inputs = self.inputs[:-1]
244 elif self.type == "Concat":
245 axis_tensor = self.inputs[0]
246 inputs = self.inputs[1:]
247 elif self.type == "QuantizedConcat":
248 axis_tensor = self.inputs[0]
249 inputs = self.inputs[1:]
250 inputs = inputs[: len(inputs) // 3] # Skip min/max
251
252 if self.type == "ConcatTFLite":
253 inputs = self.inputs
254 axis = self.attrs["axis"]
255 elif self.type == "PackReshaped":
256 # Requires fixup_pack_input to be called before this point
257 inputs = self.inputs
258 axis = self.attrs["axis"]
259 assert len(self.inputs) == self.attrs["values_count"]
260 else:
261 assert len(axis_tensor.ops) == 1 and axis_tensor.ops[0].type == "Const"
262 axis = int(axis_tensor.values)
263
264 return inputs, axis
265
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200266 def get_dilation_h_w(self):
267 _, dilation_h, dilation_w, _ = self.attrs.get("dilation", (1, 1, 1, 1))
268 return dilation_h, dilation_w
269
Tim Hall79d07d22020-04-27 18:20:16 +0100270 def is_split_op(self):
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200271 return self.type in ("Split", "SplitV", "StridedSlice", "Slice", "UnpackReshaped")
Tim Hall79d07d22020-04-27 18:20:16 +0100272
273 def get_split_inputs_axis(self):
274 assert self.is_split_op()
275
276 offset_start = None
277 offset_end = None
278 axis = None
279 if self.type == "Split":
Tim Hall79d07d22020-04-27 18:20:16 +0100280 num_splits = self.attrs.get("num_splits")
281 axis_tens = self.inputs[0]
282 assert len(axis_tens.ops) == 1 and axis_tens.ops[0].type == "Const"
283 axis = int(axis_tens.values)
284 input_tens = self.inputs[1]
285 outputs = self.outputs
286 assert num_splits == len(outputs)
287
Louis Verhaard9b8fa122020-05-15 13:41:13 +0200288 elif self.type == "SplitV":
Charles Xu53d47522020-05-04 11:32:05 +0200289 num_splits = self.attrs.get("num_splits")
290 input_tens = self.inputs[0]
291 size_tens = self.inputs[1]
292 assert len(size_tens.ops) == 1 and size_tens.ops[0].type == "Const"
293 sizes = size_tens.values
Patrik Gustavsson271ddc32020-09-01 09:15:27 +0200294
Charles Xu53d47522020-05-04 11:32:05 +0200295 axis_tens = self.inputs[2]
296 assert len(axis_tens.ops) == 1 and axis_tens.ops[0].type == "Const"
297 axis = int(axis_tens.values)
Patrik Gustavsson271ddc32020-09-01 09:15:27 +0200298
299 for idx, size in enumerate(sizes):
300 # One but only one size might be set to -1, indicating that size should be inferred
301 if size == -1:
302 sizes[idx] = input_tens.shape[axis] - (sum(sizes) + 1)
303 break
304
Charles Xu53d47522020-05-04 11:32:05 +0200305 outputs = self.outputs
306 assert num_splits == len(outputs)
307 assert sum(sizes) == input_tens.shape[axis]
308
Tim Hall79d07d22020-04-27 18:20:16 +0100309 elif self.type == "Slice":
310 input_tens, begin_tens, size_tens = self.inputs
311 outputs = self.outputs
312 offset_start = [0] * len(input_tens.shape)
313 offset_end = [0] * len(input_tens.shape)
314
315 for idx in range(len(begin_tens.values)):
316 # Check if the op should slice in dimension idx
317 if size_tens.values[idx] != input_tens.shape[idx]:
318 offset_start[idx] = begin_tens.values[idx]
319 offset_end[idx] = size_tens.values[idx] + offset_start[idx]
320
321 elif self.type == "StridedSlice":
322 input_tens, begin_tens, end_tens, strides_tens = self.inputs
323 outputs = self.outputs
324 out_tens = outputs[0]
Tim Hall79d07d22020-04-27 18:20:16 +0100325
326 # Extract masks
327 begin_mask = self.attrs["begin_mask"]
328 ellipsis_mask = self.attrs["ellipsis_mask"]
329 end_mask = self.attrs["end_mask"]
330 new_axis_mask = self.attrs["new_axis_mask"]
331 shrink_axis_mask = self.attrs["shrink_axis_mask"]
Patrik Gustavssoncf728902020-04-30 08:57:23 +0200332
333 # 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 +0100334 # may have the attribute modified and handled in the graph optimization phase.
Patrik Gustavssoncf728902020-04-30 08:57:23 +0200335 assert shrink_axis_mask == new_axis_mask == ellipsis_mask == 0
Tim Hall79d07d22020-04-27 18:20:16 +0100336 assert len(input_tens.shape) == len(out_tens.shape)
Louis Verhaardfa2f92a2020-09-21 11:56:18 +0200337 offset_start = get_slice_offsets(input_tens.shape, begin_tens, begin_mask, is_begin=True)
338 offset_end = get_slice_offsets(input_tens.shape, end_tens, end_mask, is_begin=False)
Tim Hall79d07d22020-04-27 18:20:16 +0100339 elif self.type == "UnpackReshaped":
340 # Requires fixup_unpack_output to be called before this point
341 input_tens = self.inputs[0]
342 outputs = self.outputs
343 axis = self.attrs["axis"]
344 num_splits = self.attrs["num"]
345 # Number of outputs have to equal the value of the dimension to unpack
346 assert num_splits == len(outputs) == input_tens.shape[axis]
347 else:
348 assert False
349
350 return input_tens, outputs, axis, offset_start, offset_end
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200351
352 def set_activation_lut(self, lut_tensor):
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200353 self.attrs["fused_activation_function"] = "LUT"
354 self.activation_lut = lut_tensor
Michael McGeaghc5b549b2020-08-07 11:54:28 +0100355 self.add_input_tensor(lut_tensor)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100356
357 def add_input_tensor(self, tens):
358 self.inputs.append(tens)
359 if self not in tens.consumer_list:
360 tens.consumer_list.append(self)
361
Jacob Bohlin67e0d8f2020-08-20 10:53:02 +0200362 def set_input_tensor(self, tens, idx):
363 tens_to_remove = self.inputs[idx]
364 if tens_to_remove in tens.consumer_list:
365 tens.consumer_list.remove(tens_to_remove)
366
367 self.inputs[idx] = tens
368 if self not in tens.consumer_list:
369 tens.consumer_list.append(self)
370
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100371 def set_output_tensor(self, tens):
372 tens.ops = [self]
373 self.outputs = [tens]
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200374
375 def needs_bias(self):
376 return self.type in (
377 "Conv2DBiasAct",
378 "DepthwiseConv2dBiasAct",
379 "Conv2DBackpropInputSwitchedBias",
380 "FullyConnectedAct",
381 )
Louis Verhaard98a34992020-09-01 10:39:04 +0200382
383 def get_output_quantization(self):
384 return self.attrs.get("forced_output_quantization", self.get_ofm().quantization)