blob: 6bc5a32de0757a8decebd6a45e157ebf5c691545 [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
Tim Hall79d07d22020-04-27 18:20:16 +010046class Operation:
47 """Class representing a Neural Network operation. Has a name, a type,
48input and output tensors, as well as an attribute dictionary."""
49
Fredrik Svedberga0c36242020-06-03 15:43:31 +020050 __slots__ = (
51 "type",
52 "name",
53 "op_index",
54 "attrs",
55 "inputs",
56 "outputs",
57 "flops",
58 "scheduled_pass",
59 "run_on_npu",
60 "activation_lut",
61 )
Tim Hall79d07d22020-04-27 18:20:16 +010062
63 def __init__(self, op_type, name):
64 self.type = op_type
65 self.name = name
66 self.attrs = {}
67 self.inputs = []
68 self.outputs = []
69 self.flops = 0
70 self.run_on_npu = True
71 self.scheduled_pass = None
Tim Hallc8310b12020-06-17 14:53:11 +010072 self.op_index = None # input network operator index
Fredrik Svedberga0c36242020-06-03 15:43:31 +020073 self.activation_lut = None
Tim Hall79d07d22020-04-27 18:20:16 +010074
75 def clone(self, suffix="_clone"):
76 res = Operation(self.type, self.name + suffix)
77
78 res.attrs = dict(self.attrs)
79 res.inputs = list(self.inputs)
80 res.outputs = list(self.outputs)
81 res.flops = self.flops
82 res.scheduled_pass = self.scheduled_pass
Tim Hallc8310b12020-06-17 14:53:11 +010083 res.op_index = None # not relevant as not part of input network
Tim Hall79d07d22020-04-27 18:20:16 +010084
85 return res
86
87 def __str__(self):
88 return "<nng.Operation '%s' type=%s>" % (self.name, self.type)
89
90 __repr__ = __str__
91
92 def get_ifm_ifm2_weight_bias_ofm_indices(self):
93 ifm_idx = -1
94 ifm2_idx = -1
95 weight_idx = -1
96 bias_idx = -1
97 ofm_idx = -1
98 npu_block_type = self.attrs.get("npu_block_type", NpuBlockType.Default)
Jacob Bohlina41cd4d2020-08-26 18:21:28 +020099 if npu_block_type in (NpuBlockType.ConvolutionMxN, NpuBlockType.ConvolutionDepthWise):
Tim Hall79d07d22020-04-27 18:20:16 +0100100 ifm_idx = 0
101 weight_idx = 1
102 ofm_idx = 0
103
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200104 if self.type in ("Conv2DBiasAct", "DepthwiseConv2dBiasAct", "TransposeConvAct"):
Tim Hall79d07d22020-04-27 18:20:16 +0100105 if len(self.inputs) >= 3:
106 bias_idx = 2
107
Jacob Bohlincf7da102020-05-20 09:03:40 +0200108 elif self.type == "Conv2DBackpropInputSwitchedBias":
109 bias_idx = 3
110
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200111 elif npu_block_type in (NpuBlockType.Pooling, NpuBlockType.ReduceSum):
Tim Hall79d07d22020-04-27 18:20:16 +0100112 ifm_idx = 0
113 ofm_idx = 0
114 elif npu_block_type == NpuBlockType.VectorProduct:
115 ifm_idx = 0
116 weight_idx = 1
117 ofm_idx = 0
118
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200119 if self.type == "FullyConnectedAct":
Tim Hall79d07d22020-04-27 18:20:16 +0100120 if len(self.inputs) >= 3:
121 bias_idx = 2
122
123 if self.type == "BlockLSTM":
124 ifm_idx = 3
125 weight_idx = 4
126 ofm_idx = 6
127
128 elif npu_block_type == NpuBlockType.ElementWise:
129 ifm_idx = 0
130 ifm2_idx = 1
131 ofm_idx = 0
132
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200133 # LeakyRelu, Abs and CLZ have a single IFM
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200134 if self.type in ("LeakyRelu", "Abs", "CLZ"):
Tim Hall79d07d22020-04-27 18:20:16 +0100135 ifm2_idx = -1
136
137 elif self.type == "Conv2DBackpropInput":
138 ifm_idx = 2
139 weight_idx = 1
140 ofm_idx = 0
141
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200142 elif self.type in ("Squeeze", "Reshape", "QuantizedReshape", "ExpandDims"):
Tim Hall79d07d22020-04-27 18:20:16 +0100143 ifm_idx = 0
144 ofm_idx = 0
145
146 elif self.is_split_op():
147 ifm_idx = 0
148 ofm_idx = 0
149 if self.type == "Split":
150 ifm_idx = 1
151
152 elif self.is_concat_op():
153 ifms, _ = self.get_concat_inputs_axis()
154 ifm_idx = self.inputs.index(ifms[0])
155 if len(ifms) > 1:
156 ifm2_idx = self.inputs.index(ifms[1])
157 ofm_idx = 0
158
159 return ifm_idx, ifm2_idx, weight_idx, bias_idx, ofm_idx
160
161 def get_ifm_ifm2_weights_ofm(self):
162 ifm_tensor = None
163 ifm2_tensor = None
164 weight_tensor = None
165 ofm_tensor = None
166
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200167 ifm_idx, ifm2_idx, weight_idx, _, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
Tim Hall79d07d22020-04-27 18:20:16 +0100168 if ifm_idx != -1:
169 ifm_tensor = self.inputs[ifm_idx]
170 if ifm2_idx != -1:
171 ifm2_tensor = self.inputs[ifm2_idx]
172 if weight_idx != -1:
173 weight_tensor = self.inputs[weight_idx]
174 if ofm_idx != -1:
175 ofm_tensor = self.outputs[ofm_idx]
176
177 return ifm_tensor, ifm2_tensor, weight_tensor, ofm_tensor
178
179 def get_ifm_weights_biases_ofm(self):
180 ifm_tensor = None
181 weight_tensor = None
182 bias_tensor = None
183 ofm_tensor = None
184
185 ifm_idx, _, weight_idx, bias_idx, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
186 if ifm_idx != -1:
187 ifm_tensor = self.inputs[ifm_idx]
188 if weight_idx != -1:
189 weight_tensor = self.inputs[weight_idx]
190 if bias_idx != -1:
191 bias_tensor = self.inputs[bias_idx]
192 if ofm_idx != -1:
193 ofm_tensor = self.outputs[ofm_idx]
194
195 return ifm_tensor, weight_tensor, bias_tensor, ofm_tensor
196
Jacob Bohlin49d92122020-08-19 14:36:46 +0200197 def get_ifm_ifm2_weights_biases_ofm(self):
198 ifm_tensor = None
199 ifm2_tensor = None
200 weight_tensor = None
201 bias_tensor = None
202 ofm_tensor = None
203
204 ifm_idx, ifm2_idx, weight_idx, bias_idx, ofm_idx = self.get_ifm_ifm2_weight_bias_ofm_indices()
205 if ifm_idx != -1:
206 ifm_tensor = self.inputs[ifm_idx]
207 if ifm2_idx != -1:
208 ifm2_tensor = self.inputs[ifm2_idx]
209 if weight_idx != -1:
210 weight_tensor = self.inputs[weight_idx]
211 if bias_idx != -1:
212 bias_tensor = self.inputs[bias_idx]
213 if ofm_idx != -1:
214 ofm_tensor = self.outputs[ofm_idx]
215
216 return ifm_tensor, ifm2_tensor, weight_tensor, bias_tensor, ofm_tensor
217
Louis Verhaard98a34992020-09-01 10:39:04 +0200218 def get_ofm(self):
219 _, _, _, ofm = self.get_ifm_ifm2_weights_ofm()
220 return ofm
221
Tim Hall79d07d22020-04-27 18:20:16 +0100222 def is_concat_op(self):
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200223 return self.type in ("Concat", "ConcatV2", "QuantizedConcat", "ConcatTFLite", "PackReshaped")
Tim Hall79d07d22020-04-27 18:20:16 +0100224
225 def get_concat_inputs_axis(self):
226 assert self.is_concat_op()
227
228 if self.type == "ConcatV2":
229 axis_tensor = self.inputs[-1]
230 inputs = self.inputs[:-1]
231 elif self.type == "Concat":
232 axis_tensor = self.inputs[0]
233 inputs = self.inputs[1:]
234 elif self.type == "QuantizedConcat":
235 axis_tensor = self.inputs[0]
236 inputs = self.inputs[1:]
237 inputs = inputs[: len(inputs) // 3] # Skip min/max
238
239 if self.type == "ConcatTFLite":
240 inputs = self.inputs
241 axis = self.attrs["axis"]
242 elif self.type == "PackReshaped":
243 # Requires fixup_pack_input to be called before this point
244 inputs = self.inputs
245 axis = self.attrs["axis"]
246 assert len(self.inputs) == self.attrs["values_count"]
247 else:
248 assert len(axis_tensor.ops) == 1 and axis_tensor.ops[0].type == "Const"
249 axis = int(axis_tensor.values)
250
251 return inputs, axis
252
Louis Verhaardb2fb2122020-06-04 15:51:24 +0200253 def get_dilation_h_w(self):
254 _, dilation_h, dilation_w, _ = self.attrs.get("dilation", (1, 1, 1, 1))
255 return dilation_h, dilation_w
256
Tim Hall79d07d22020-04-27 18:20:16 +0100257 def is_split_op(self):
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200258 return self.type in ("Split", "SplitV", "StridedSlice", "Slice", "UnpackReshaped")
Tim Hall79d07d22020-04-27 18:20:16 +0100259
260 def get_split_inputs_axis(self):
261 assert self.is_split_op()
262
263 offset_start = None
264 offset_end = None
265 axis = None
266 if self.type == "Split":
Tim Hall79d07d22020-04-27 18:20:16 +0100267 num_splits = self.attrs.get("num_splits")
268 axis_tens = self.inputs[0]
269 assert len(axis_tens.ops) == 1 and axis_tens.ops[0].type == "Const"
270 axis = int(axis_tens.values)
271 input_tens = self.inputs[1]
272 outputs = self.outputs
273 assert num_splits == len(outputs)
274
Louis Verhaard9b8fa122020-05-15 13:41:13 +0200275 elif self.type == "SplitV":
Charles Xu53d47522020-05-04 11:32:05 +0200276 num_splits = self.attrs.get("num_splits")
277 input_tens = self.inputs[0]
278 size_tens = self.inputs[1]
279 assert len(size_tens.ops) == 1 and size_tens.ops[0].type == "Const"
280 sizes = size_tens.values
Patrik Gustavsson271ddc32020-09-01 09:15:27 +0200281
Charles Xu53d47522020-05-04 11:32:05 +0200282 axis_tens = self.inputs[2]
283 assert len(axis_tens.ops) == 1 and axis_tens.ops[0].type == "Const"
284 axis = int(axis_tens.values)
Patrik Gustavsson271ddc32020-09-01 09:15:27 +0200285
286 for idx, size in enumerate(sizes):
287 # One but only one size might be set to -1, indicating that size should be inferred
288 if size == -1:
289 sizes[idx] = input_tens.shape[axis] - (sum(sizes) + 1)
290 break
291
Charles Xu53d47522020-05-04 11:32:05 +0200292 outputs = self.outputs
293 assert num_splits == len(outputs)
294 assert sum(sizes) == input_tens.shape[axis]
295
Tim Hall79d07d22020-04-27 18:20:16 +0100296 elif self.type == "Slice":
297 input_tens, begin_tens, size_tens = self.inputs
298 outputs = self.outputs
299 offset_start = [0] * len(input_tens.shape)
300 offset_end = [0] * len(input_tens.shape)
301
302 for idx in range(len(begin_tens.values)):
303 # Check if the op should slice in dimension idx
304 if size_tens.values[idx] != input_tens.shape[idx]:
305 offset_start[idx] = begin_tens.values[idx]
306 offset_end[idx] = size_tens.values[idx] + offset_start[idx]
307
308 elif self.type == "StridedSlice":
309 input_tens, begin_tens, end_tens, strides_tens = self.inputs
310 outputs = self.outputs
311 out_tens = outputs[0]
312 offset_start = [0] * len(outputs[0].shape)
313 offset_end = [0] * len(outputs[0].shape)
314
315 # Extract masks
316 begin_mask = self.attrs["begin_mask"]
317 ellipsis_mask = self.attrs["ellipsis_mask"]
318 end_mask = self.attrs["end_mask"]
319 new_axis_mask = self.attrs["new_axis_mask"]
320 shrink_axis_mask = self.attrs["shrink_axis_mask"]
Patrik Gustavssoncf728902020-04-30 08:57:23 +0200321
322 # 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 +0100323 # may have the attribute modified and handled in the graph optimization phase.
Patrik Gustavssoncf728902020-04-30 08:57:23 +0200324 assert shrink_axis_mask == new_axis_mask == ellipsis_mask == 0
Tim Hall79d07d22020-04-27 18:20:16 +0100325 assert len(input_tens.shape) == len(out_tens.shape)
326
327 for idx in range(len(input_tens.shape)):
Patrik Gustavsson49134332020-04-29 14:10:32 +0200328 # Check if slicing is needed in this axis
329 if end_tens.values[idx] != input_tens.shape[idx] or (
330 end_tens.values[idx] == input_tens.shape[idx] and begin_tens.values[idx] != 0
331 ):
332 # If the i:th bit in begin_mask is set then the value on begin[i] should be ignored
333 if (begin_mask & (1 << idx)) == 0:
Tim Hall79d07d22020-04-27 18:20:16 +0100334 offset_start[idx] = begin_tens.values[idx]
Tim Hall79d07d22020-04-27 18:20:16 +0100335
Patrik Gustavsson49134332020-04-29 14:10:32 +0200336 # If the i:th bit in end_mask is set then the value on end[i] should be ignored
337 if (end_mask & (1 << idx)) == 0:
338 offset_end[idx] = end_tens.values[idx]
Tim Hall79d07d22020-04-27 18:20:16 +0100339
340 elif self.type == "UnpackReshaped":
341 # Requires fixup_unpack_output to be called before this point
342 input_tens = self.inputs[0]
343 outputs = self.outputs
344 axis = self.attrs["axis"]
345 num_splits = self.attrs["num"]
346 # Number of outputs have to equal the value of the dimension to unpack
347 assert num_splits == len(outputs) == input_tens.shape[axis]
348 else:
349 assert False
350
351 return input_tens, outputs, axis, offset_start, offset_end
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200352
353 def set_activation_lut(self, lut_tensor):
Fredrik Svedberga0c36242020-06-03 15:43:31 +0200354 self.attrs["fused_activation_function"] = "LUT"
355 self.activation_lut = lut_tensor
Michael McGeaghc5b549b2020-08-07 11:54:28 +0100356 self.add_input_tensor(lut_tensor)
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100357
358 def add_input_tensor(self, tens):
359 self.inputs.append(tens)
360 if self not in tens.consumer_list:
361 tens.consumer_list.append(self)
362
Jacob Bohlin67e0d8f2020-08-20 10:53:02 +0200363 def set_input_tensor(self, tens, idx):
364 tens_to_remove = self.inputs[idx]
365 if tens_to_remove in tens.consumer_list:
366 tens.consumer_list.remove(tens_to_remove)
367
368 self.inputs[idx] = tens
369 if self not in tens.consumer_list:
370 tens.consumer_list.append(self)
371
Michael McGeagh5778ffd2020-08-06 17:31:02 +0100372 def set_output_tensor(self, tens):
373 tens.ops = [self]
374 self.outputs = [tens]
Jacob Bohlina41cd4d2020-08-26 18:21:28 +0200375
376 def needs_bias(self):
377 return self.type in (
378 "Conv2DBiasAct",
379 "DepthwiseConv2dBiasAct",
380 "Conv2DBackpropInputSwitchedBias",
381 "FullyConnectedAct",
382 )
Louis Verhaard98a34992020-09-01 10:39:04 +0200383
384 def get_output_quantization(self):
385 return self.attrs.get("forced_output_quantization", self.get_ofm().quantization)