blob: cb208d7e804b92b964f149e7057bbbdea995db1d [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# Functions used to write to a TensorFlow Lite format file. Supports adding in file identifiers.
Tim Hall79d07d22020-04-27 18:20:16 +010018import flatbuffers
Diego Russoe8a10452020-04-21 17:39:10 +010019import flatbuffers.number_types as N
20import numpy as np
21from flatbuffers import encode
Diego Russoea6111a2020-04-14 18:41:58 +010022from flatbuffers.builder import UOffsetTFlags
23
Diego Russoe8a10452020-04-21 17:39:10 +010024from .nn_graph import PassPlacement
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020025from .tensor import MemType
Diego Russoe8a10452020-04-21 17:39:10 +010026from .tensor import TensorPurpose
Tim Hall79d07d22020-04-27 18:20:16 +010027from .tflite import Buffer
28from .tflite import Metadata
Diego Russoe8a10452020-04-21 17:39:10 +010029from .tflite import Model
30from .tflite import Operator
31from .tflite import OperatorCode
32from .tflite import QuantizationParameters
33from .tflite import SubGraph
34from .tflite import Tensor
35from .tflite_mapping import builtin_operator_inv_map
36from .tflite_mapping import BuiltinOperator
37from .tflite_mapping import custom_prefix
38from .tflite_mapping import datatype_inv_map
39
40# ugh, the python flatbuffer interface is missing a method to add in file identifier. patching it in here:
Tim Hall79d07d22020-04-27 18:20:16 +010041
42tflite_version = 3
43tflite_file_identifier = "TFL" + str(tflite_version)
44
45
Tim Hall79d07d22020-04-27 18:20:16 +010046def FinishWithFileIdentifier(self, rootTable, fid):
47 if fid is None or len(fid) != 4:
48 raise Exception("fid must be 4 chars")
49
50 flags = N.Uint8Flags
51 prepSize = 4
52 self.Prep(self.minalign, prepSize + len(fid))
53 for i in range(3, -1, -1):
54 self.head = self.head - flags.bytewidth
55 encode.Write(flags.packer_type, self.Bytes, self.Head(), ord(fid[i]))
56
57 return self.Finish(rootTable)
58
59
60flatbuffers.Builder.FinishWithFileIdentifier = FinishWithFileIdentifier
61
62
63def make_vector(v):
64 try:
65 len(v)
66 return v
67 except TypeError:
68 return [v]
69
70
71class TFLiteSerialiser:
72 def __init__(self, nng):
73 self.builder = flatbuffers.Builder(0)
74 self.nng = nng
75
76 self.scratch_buf_id = 0 # Always assign scratch to buffer 0
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020077 self.scratch_fast_buf_id = 1 # Always assign scratch_fast to buffer 1
Tim Hall79d07d22020-04-27 18:20:16 +010078 self.buffers_to_write = [] # have an empty array there
79
Tim Hall79d07d22020-04-27 18:20:16 +010080 self.ops_to_ignore = set(("Const", "Placeholder", "SubgraphInput"))
81
82 self.tensors_to_reshape = {}
83
84 self.subgraphs_to_write = [sg for sg in self.nng.subgraphs if sg.placement == PassPlacement.Cpu]
85
86 all_ops = []
87 for sg in self.subgraphs_to_write:
88 for ps in sg.passes:
89 for op in ps.ops:
90 if op.type not in self.ops_to_ignore:
91 all_ops.append(op)
92 if op.type.startswith("Conv2D") or op.type.startswith("DepthwiseConv2d"):
93 self.tensors_to_reshape[op.inputs[1]] = (3, 0, 1, 2)
94 if op.type.startswith("FullyConnected"):
95 self.tensors_to_reshape[op.inputs[1]] = (1, 0)
96
97 self.operator_codes = list(sorted(set(op.type for op in all_ops)))
98 self.operator_code_map = {}
99
100 def write_byte_vector(self, v, alignment=1):
101 builder = self.builder
102 builder.StartVector(1, len(v), alignment)
103 for e in v[::-1]:
104 builder.PrependByte(e)
105 return builder.EndVector(len(v))
106
107 def write_int_vector(self, v):
108 builder = self.builder
109 builder.StartVector(4, len(v), 4)
110 for e in v[::-1]:
111 builder.PrependInt32(e)
112 return builder.EndVector(len(v))
113
114 def write_long_vector(self, v):
115 builder = self.builder
116 builder.StartVector(8, len(v), 8)
117 for e in v[::-1]:
118 builder.PrependInt64(e)
119 return builder.EndVector(len(v))
120
121 def write_float_vector(self, v):
122 builder = self.builder
123 builder.StartVector(4, len(v), 4)
124 for e in v[::-1]:
125 builder.PrependFloat32(e)
126 return builder.EndVector(len(v))
127
128 def write_offset_vector(self, v):
129 builder = self.builder
130 builder.StartVector(4, len(v), 4)
131 for e in v[::-1]:
132 builder.PrependUOffsetTRelative(e)
133 return builder.EndVector(len(v))
134
Tim Hallc8310b12020-06-17 14:53:11 +0100135 def assign_buffers_to_tensors(self, tensors, scratch_tensor):
136 if scratch_tensor is not None:
137 scratch_tensor_mem_area = scratch_tensor.mem_area
Tim Hall25f605c2020-05-18 18:04:26 +0100138 else:
139 scratch_tensor_mem_area = None # all tensors are initialised to MemArea.Unknown
140
Tim Hall79d07d22020-04-27 18:20:16 +0100141 buffer_map = {}
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200142
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200143 buf_idx = 2
Tim Hall79d07d22020-04-27 18:20:16 +0100144
145 for tens in tensors:
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200146 # Set buffer ids depending on allocation
147 if tens.is_allocated_in_tensor_arena(scratch_tensor_mem_area):
Tim Hall79d07d22020-04-27 18:20:16 +0100148 buffer_map[tens] = self.scratch_buf_id
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200149 elif tens.mem_type == MemType.Scratch_fast:
150 # For Scratch_fast when not co-allocated with scratch in the TensorArena:
151 buffer_map[tens] = self.scratch_fast_buf_id
Tim Hall79d07d22020-04-27 18:20:16 +0100152 else:
153 buffer_map[tens] = buf_idx
154 buf_idx += 1
155
Tim Hallc8310b12020-06-17 14:53:11 +0100156 # Initialize buffers_to_write to a length equal to number of buffers so
Tim Hall79d07d22020-04-27 18:20:16 +0100157 # they can be appended at the correct index during tensor serialization
158 self.buffers_to_write = [None] * (buf_idx)
159
160 return buffer_map
161
162 def serialise_operator_code(self, idx, code):
163 builder = self.builder
164 custom_code_offset = None
165 if code.startswith(custom_prefix):
166 tf_code, opt_serializer = builtin_operator_inv_map[custom_prefix]
167 custom_code_offset = builder.CreateString(code[len(custom_prefix) :])
168 else:
Tim Halle9194df2020-08-04 20:37:01 +0100169 assert (
170 code in builtin_operator_inv_map
171 ), "Vela does not contain a mapping to serialise {} operator to a TensorFlow Lite operator".format(code)
172 tf_code, opt_serializer = builtin_operator_inv_map[code]
Tim Hall79d07d22020-04-27 18:20:16 +0100173
174 if tf_code == BuiltinOperator.CUSTOM:
Tim Halle9194df2020-08-04 20:37:01 +0100175 assert (
176 code == "NpuOp"
177 ), "Vela only supports serialising NpuOp operators as TensorFlow Lite Custom operators"
Tim Hall79d07d22020-04-27 18:20:16 +0100178 custom_code_offset = builder.CreateString("ethos-u")
179
Tim Hallc8310b12020-06-17 14:53:11 +0100180 self.operator_code_map[code] = (idx, tf_code, opt_serializer)
Tim Hall79d07d22020-04-27 18:20:16 +0100181
182 OperatorCode.OperatorCodeStart(builder)
183 OperatorCode.OperatorCodeAddBuiltinCode(builder, tf_code)
184 if custom_code_offset is not None:
185 OperatorCode.OperatorCodeAddCustomCode(builder, custom_code_offset)
186
187 return OperatorCode.OperatorCodeEnd(builder)
188
189 def serialise_quantization_parameters(self, quant):
190 builder = self.builder
191
192 min = None
193 max = None
194 scale = None
195 zero_point = None
196 if quant is not None:
197 if quant.min is not None:
198 min = self.write_float_vector(make_vector(quant.min))
199 if quant.max is not None:
200 max = self.write_float_vector(make_vector(quant.max))
201 if quant.scale_f32 is not None:
202 scale = self.write_float_vector(make_vector(quant.scale_f32))
203 if quant.zero_point is not None:
204 zero_point = self.write_long_vector(make_vector(quant.zero_point))
205
206 QuantizationParameters.QuantizationParametersStart(builder)
207 if min is not None:
208 QuantizationParameters.QuantizationParametersAddMin(builder, min)
209 if max is not None:
210 QuantizationParameters.QuantizationParametersAddMax(builder, max)
211 if scale is not None:
212 QuantizationParameters.QuantizationParametersAddScale(builder, scale)
213 if zero_point is not None:
214 QuantizationParameters.QuantizationParametersAddZeroPoint(builder, zero_point)
215 return QuantizationParameters.QuantizationParametersEnd(builder)
216
217 def serialise_tensor(self, tens):
218 builder = self.builder
219 tens_shape = tens.shape
220 values = tens.quant_values
221 if values is None:
222 values = tens.values
223
224 if values is None:
225 values = np.empty(shape=(0), dtype=np.uint8)
226
227 if tens in self.tensors_to_reshape:
228 reorder = self.tensors_to_reshape[tens]
229 tens_shape = [tens_shape[idx] for idx in reorder]
230 values = values.transpose(reorder)
231
232 if tens.purpose == TensorPurpose.Scratch:
233 tens_shape = [0]
Tim Hall79d07d22020-04-27 18:20:16 +0100234
235 buf_id = self.buffer_map[tens]
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200236 self.buffers_to_write[buf_id] = values.flatten().view(np.uint8)
Tim Hall79d07d22020-04-27 18:20:16 +0100237
238 shape = self.write_int_vector(tens_shape)
239
240 name = builder.CreateString(tens.name)
241 quant = self.serialise_quantization_parameters(tens.quantization)
242
243 Tensor.TensorStart(builder)
244 Tensor.TensorAddShape(builder, shape)
245 Tensor.TensorAddType(builder, datatype_inv_map[tens.dtype])
246 # All tensors must have a valid backing buffer, even if it is empty.
247 # Empty buffers should be kept unique for TensorFlow Lite Micro
248 Tensor.TensorAddBuffer(builder, buf_id)
249 Tensor.TensorAddName(builder, name)
250 Tensor.TensorAddQuantization(builder, quant)
251
252 res = Tensor.TensorEnd(builder)
253 return res
254
255 def serialise_operator(self, op):
256 builder = self.builder
257
Michael McGeaghbb1b09e2020-08-19 11:24:17 +0100258 inputs_offset = self.write_int_vector([self.tensor_map[tens] for tens in op.inputs if tens in self.tensor_map])
259 outputs_offset = self.write_int_vector(
260 [self.tensor_map[tens] for tens in op.outputs if tens in self.tensor_map]
261 )
Tim Hall79d07d22020-04-27 18:20:16 +0100262
263 op_idx, tflop, opt_serializer = self.operator_code_map[op.type]
264
265 builtin_opt_offset = None
266 custom_opt_offset = None
267 if opt_serializer is not None:
268 attrs = dict(op.attrs)
269 if "strides" in attrs:
270 attrs["stride_h"] = attrs["strides"][1]
271 attrs["stride_w"] = attrs["strides"][2]
272 if "ksize" in attrs:
273 attrs["filter_height"] = attrs["ksize"][1]
274 attrs["filter_width"] = attrs["ksize"][2]
275 if "dilation" in attrs:
276 attrs["dilation_h_factor"] = attrs["dilation"][1]
277 attrs["dilation_w_factor"] = attrs["dilation"][2]
278 if "channel_multiplier" in attrs:
279 attrs["depth_multiplier"] = attrs["channel_multiplier"]
280
281 builtin_opt_offset, custom_opt_offset = opt_serializer.serialize(builder, attrs)
282
283 mutating_variable_inputs_offset = self.write_byte_vector([])
284 Operator.OperatorStart(builder)
285 Operator.OperatorAddOpcodeIndex(builder, op_idx)
286 Operator.OperatorAddInputs(builder, inputs_offset)
287 Operator.OperatorAddOutputs(builder, outputs_offset)
288
289 if builtin_opt_offset is not None:
290 Operator.OperatorAddBuiltinOptionsType(builder, opt_serializer.builtin_opt_type)
291 Operator.OperatorAddBuiltinOptions(builder, builtin_opt_offset)
292 if custom_opt_offset is not None:
293 Operator.OperatorAddCustomOptions(builder, custom_opt_offset)
294 Operator.OperatorAddCustomOptionsFormat(builder, opt_serializer.custom_opt_format)
295
296 Operator.OperatorAddMutatingVariableInputs(builder, mutating_variable_inputs_offset)
297 return Operator.OperatorEnd(builder)
298
299 def serialise_subgraph(self, sg):
300 builder = self.builder
301 tensor_set = set()
Tim Hall79d07d22020-04-27 18:20:16 +0100302 all_ops = []
Michael McGeagh515c9562020-09-02 15:52:43 +0100303 placeholder_ops = []
304
Tim Hall79d07d22020-04-27 18:20:16 +0100305 for ps in sg.passes:
306 for op in ps.ops:
307 if op.type not in self.ops_to_ignore:
308 all_ops.append(op)
Michael McGeagh515c9562020-09-02 15:52:43 +0100309 elif op.type == "Placeholder":
310 placeholder_ops.append(op)
Tim Hall79d07d22020-04-27 18:20:16 +0100311
Michael McGeagh515c9562020-09-02 15:52:43 +0100312 # Add the tensors from all valid ops, as well as the tensors from placeholder ops
313 # This allows us to serialise tensors which arent attached to any specific ops,
314 # e.g. due to an empty graph containing no ops
315 for op in all_ops + placeholder_ops:
Tim Hall79d07d22020-04-27 18:20:16 +0100316 for tens in op.inputs + op.outputs:
317 tensor_set.add(tens)
318
319 all_tensors = [tens for nm, idx, tens in sorted((tens.name, idx, tens) for idx, tens in enumerate(tensor_set))]
320
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200321 scratch_tensors = [tens for tens in all_tensors if tens.name.endswith("scratch")]
322
Jacob Bohlin68a04b12020-07-13 11:39:36 +0200323 scratch_fast_tensor = None
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200324 for tens in all_tensors:
325 if tens.name.endswith("scratch_fast"):
326 scratch_fast_tensor = tens
Tim Hallc8310b12020-06-17 14:53:11 +0100327
328 if len(scratch_tensors) == 0:
329 scratch_tensor = None
330 else:
331 assert len(scratch_tensors) == 1, "Multiple scratch tensors"
332 scratch_tensor = scratch_tensors[0]
333
Tim Hall79d07d22020-04-27 18:20:16 +0100334 self.tensor_map = {tens: idx for idx, tens in enumerate(all_tensors)}
Tim Hallc8310b12020-06-17 14:53:11 +0100335 self.buffer_map = self.assign_buffers_to_tensors(all_tensors, scratch_tensor)
Tim Hall79d07d22020-04-27 18:20:16 +0100336
337 tensors_offset = self.write_offset_vector([self.serialise_tensor(tens) for tens in all_tensors])
338
Tim Hall79d07d22020-04-27 18:20:16 +0100339 # Make sure the input_tensors haven't been modified
340 assert all(inp in sg.original_inputs for inp in sg.input_tensors)
Michael McGeaghbb1b09e2020-08-19 11:24:17 +0100341 inputs = [self.tensor_map[tens] for tens in sg.original_inputs if tens in self.tensor_map]
Tim Hallc8310b12020-06-17 14:53:11 +0100342
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200343 # Add the Scratch Tensors as input to the NPU subgraph to get them allocated by TensorFlow Lite Micro
Tim Hallc8310b12020-06-17 14:53:11 +0100344 scratch_tensor_idx = self.tensor_map.get(scratch_tensor, None)
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200345 scratch_fast_tensor_idx = self.tensor_map.get(scratch_fast_tensor, None)
346
Tim Hallc8310b12020-06-17 14:53:11 +0100347 if scratch_tensor_idx is not None and scratch_tensor_idx not in inputs:
348 inputs.append(scratch_tensor_idx)
349
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200350 if scratch_fast_tensor_idx is not None and scratch_fast_tensor_idx not in inputs:
351 inputs.append(scratch_fast_tensor_idx)
352
Tim Hallc8310b12020-06-17 14:53:11 +0100353 inputs_offset = self.write_int_vector(inputs)
Michael McGeaghbb1b09e2020-08-19 11:24:17 +0100354 outputs_offset = self.write_int_vector(
355 [self.tensor_map[tens] for tens in sg.output_tensors if tens in self.tensor_map]
356 )
Tim Hall79d07d22020-04-27 18:20:16 +0100357
358 operators_offset = self.write_offset_vector([self.serialise_operator(op) for op in all_ops])
359
360 SubGraph.SubGraphStart(builder)
361 SubGraph.SubGraphAddTensors(builder, tensors_offset)
362 SubGraph.SubGraphAddInputs(builder, inputs_offset)
363 SubGraph.SubGraphAddOutputs(builder, outputs_offset)
364
365 SubGraph.SubGraphAddOperators(builder, operators_offset)
366
367 return SubGraph.SubGraphEnd(builder)
368
369 def write_aligned_bytes(self, buf):
370 builder = self.builder
371 builder.nested = True
372 data = bytes(buf)
373 length_bytes = UOffsetTFlags.py_type(len(data))
374 builder.Prep(16, length_bytes) # Reserve aligned storage
375 builder.head = UOffsetTFlags.py_type(builder.Head() - length_bytes) # Update FlatBuffer internal pointer
376 builder.Bytes[builder.Head() : builder.Head() + length_bytes] = data # Assign bytes to aligned area
377 return builder.EndVector(length_bytes)
378
379 def serialise_buffer(self, buf):
380 builder = self.builder
381 data = None
382 if buf is not None:
383 data = self.write_aligned_bytes(buf)
384 Buffer.BufferStart(builder)
385 if data is not None:
386 Buffer.BufferAddData(builder, data)
387 return Buffer.BufferEnd(builder)
388
389 def serialise_metadata(self, metadata):
390 builder = self.builder
391 name = builder.CreateString(metadata[0])
392
393 Metadata.MetadataStart(builder)
394 Metadata.MetadataAddName(builder, name)
395 Metadata.MetadataAddBuffer(builder, metadata[1])
396
397 return Metadata.MetadataEnd(builder)
398
399 def serialise_model(self):
400 builder = self.builder
401 operator_code_offset = self.write_offset_vector(
402 [self.serialise_operator_code(idx, code) for idx, code in enumerate(self.operator_codes)]
403 )
404
405 description = builder.CreateString("Vela Optimised")
406
407 subgraph_offset = self.write_offset_vector([self.serialise_subgraph(sg) for sg in self.subgraphs_to_write])
408
409 # Fill the metadata buffer
410 version = np.int32(0)
411 subgraph_idx = np.int32(len(self.subgraphs_to_write)) # Only 1 supported currently
412 nbr_tensors = np.int32(len(self.tensor_map))
413
414 # An offset of -1 indicates that the tensor will be allocated online by Tensorflow Lite Micro
415 offsets = [np.int32(-1)] * nbr_tensors
416
417 # Ensure that the order of the offsets match the order of the tensors
418 for tens, idx in self.tensor_map.items():
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200419 # Set offsets for tensor allocated in Tensor Arena or in the scratch_fast area
Charles Xu04ce34c2020-06-23 12:42:28 +0200420 if tens.mem_type in set((MemType.Scratch, MemType.Scratch_fast)) and tens.address is not None:
Tim Hall79d07d22020-04-27 18:20:16 +0100421 offsets[idx] = np.int32(tens.address)
422
Michael McGeagh22f74e12020-08-07 16:21:03 +0100423 self.nng.metadata.append(("OfflineMemoryAllocation", np.array([version, subgraph_idx, nbr_tensors] + offsets)))
424
425 metadata_list = []
426 for name, buffer in self.nng.metadata:
427 self.buffers_to_write.append(buffer)
428 metadata_list.append((name, len(self.buffers_to_write) - 1))
Tim Hall79d07d22020-04-27 18:20:16 +0100429
430 buffers_offset = self.write_offset_vector([self.serialise_buffer(buf) for buf in self.buffers_to_write])
Tim Hall79d07d22020-04-27 18:20:16 +0100431 metadata_offset = self.write_offset_vector([self.serialise_metadata(metadata) for metadata in metadata_list])
432
433 Model.ModelStart(builder)
434 Model.ModelAddVersion(builder, tflite_version)
435 Model.ModelAddOperatorCodes(builder, operator_code_offset)
436 Model.ModelAddSubgraphs(builder, subgraph_offset)
437 Model.ModelAddDescription(builder, description)
438 Model.ModelAddBuffers(builder, buffers_offset)
439 Model.ModelAddMetadata(builder, metadata_offset)
440 return Model.ModelEnd(builder)
441
442 def serialise(self):
443
444 model = self.serialise_model()
445
446 self.builder.FinishWithFileIdentifier(model, tflite_file_identifier)
447
448 return self.builder.Output()
449
450 def write(self, filename):
451 with open(self.filename, "wb") as f:
452 f.write(self.serialised_buf)
453
454
455def write_tflite(nng, filename):
456 writer = TFLiteSerialiser(nng)
457 buf = writer.serialise()
458
459 with open(filename, "wb") as f:
460 f.write(buf)