blob: de0ee7420b95c13294ebf1d8f5b84ef2058b6e87 [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
Louis Verhaardaee5d752020-09-30 09:01:52 +020025from .operation import Op
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020026from .tensor import MemType
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
Diego Russoe8a10452020-04-21 17:39:10 +010037from .tflite_mapping import datatype_inv_map
38
39# 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 +010040
41tflite_version = 3
42tflite_file_identifier = "TFL" + str(tflite_version)
43
44
Tim Hall79d07d22020-04-27 18:20:16 +010045def FinishWithFileIdentifier(self, rootTable, fid):
46 if fid is None or len(fid) != 4:
47 raise Exception("fid must be 4 chars")
48
49 flags = N.Uint8Flags
50 prepSize = 4
51 self.Prep(self.minalign, prepSize + len(fid))
52 for i in range(3, -1, -1):
53 self.head = self.head - flags.bytewidth
54 encode.Write(flags.packer_type, self.Bytes, self.Head(), ord(fid[i]))
55
56 return self.Finish(rootTable)
57
58
59flatbuffers.Builder.FinishWithFileIdentifier = FinishWithFileIdentifier
60
61
62def make_vector(v):
63 try:
64 len(v)
65 return v
66 except TypeError:
67 return [v]
68
69
70class TFLiteSerialiser:
71 def __init__(self, nng):
72 self.builder = flatbuffers.Builder(0)
73 self.nng = nng
74
75 self.scratch_buf_id = 0 # Always assign scratch to buffer 0
Patrik Gustavssoneca2e952020-05-27 09:15:11 +020076 self.scratch_fast_buf_id = 1 # Always assign scratch_fast to buffer 1
Tim Hall79d07d22020-04-27 18:20:16 +010077 self.buffers_to_write = [] # have an empty array there
78
patrik.gustavsson10683622020-10-14 10:57:46 +000079 self.ops_to_ignore = set((Op.Const, Op.Placeholder, Op.SubgraphInput))
Tim Hall79d07d22020-04-27 18:20:16 +010080
81 self.tensors_to_reshape = {}
82
83 self.subgraphs_to_write = [sg for sg in self.nng.subgraphs if sg.placement == PassPlacement.Cpu]
84
85 all_ops = []
86 for sg in self.subgraphs_to_write:
87 for ps in sg.passes:
88 for op in ps.ops:
89 if op.type not in self.ops_to_ignore:
90 all_ops.append(op)
Louis Verhaardaee5d752020-09-30 09:01:52 +020091 if op.type.is_conv2d_op() or op.type.is_depthwise_conv2d_op():
Andreas Nevalainend8c032d2020-09-11 10:25:09 +020092 # If values are None op has non-constant weights
93 if op.inputs[1].values is not None:
94 self.tensors_to_reshape[op.inputs[1]] = (3, 0, 1, 2)
Louis Verhaardaee5d752020-09-30 09:01:52 +020095 if op.type == Op.FullyConnected:
Andreas Nevalainend8c032d2020-09-11 10:25:09 +020096 # If values are None op has non-constant weights
97 if op.inputs[1].values is not None:
98 self.tensors_to_reshape[op.inputs[1]] = (1, 0)
Tim Hall79d07d22020-04-27 18:20:16 +010099
Louis Verhaardaee5d752020-09-30 09:01:52 +0200100 # list of tuple(Op, string); the custom code is only used for 3rd party custom operators
101 self.operator_codes = sorted(set((op.type, op.attrs.get("custom_code", "")) for op in all_ops))
Tim Hall79d07d22020-04-27 18:20:16 +0100102 self.operator_code_map = {}
103
104 def write_byte_vector(self, v, alignment=1):
105 builder = self.builder
106 builder.StartVector(1, len(v), alignment)
107 for e in v[::-1]:
108 builder.PrependByte(e)
109 return builder.EndVector(len(v))
110
111 def write_int_vector(self, v):
112 builder = self.builder
113 builder.StartVector(4, len(v), 4)
114 for e in v[::-1]:
115 builder.PrependInt32(e)
116 return builder.EndVector(len(v))
117
118 def write_long_vector(self, v):
119 builder = self.builder
120 builder.StartVector(8, len(v), 8)
121 for e in v[::-1]:
122 builder.PrependInt64(e)
123 return builder.EndVector(len(v))
124
125 def write_float_vector(self, v):
126 builder = self.builder
127 builder.StartVector(4, len(v), 4)
128 for e in v[::-1]:
129 builder.PrependFloat32(e)
130 return builder.EndVector(len(v))
131
132 def write_offset_vector(self, v):
133 builder = self.builder
134 builder.StartVector(4, len(v), 4)
135 for e in v[::-1]:
136 builder.PrependUOffsetTRelative(e)
137 return builder.EndVector(len(v))
138
Tim Hallc8310b12020-06-17 14:53:11 +0100139 def assign_buffers_to_tensors(self, tensors, scratch_tensor):
140 if scratch_tensor is not None:
141 scratch_tensor_mem_area = scratch_tensor.mem_area
Tim Hall25f605c2020-05-18 18:04:26 +0100142 else:
143 scratch_tensor_mem_area = None # all tensors are initialised to MemArea.Unknown
144
Tim Hall79d07d22020-04-27 18:20:16 +0100145 buffer_map = {}
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200146
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200147 buf_idx = 2
Tim Hall79d07d22020-04-27 18:20:16 +0100148
149 for tens in tensors:
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200150 # Set buffer ids depending on allocation
151 if tens.is_allocated_in_tensor_arena(scratch_tensor_mem_area):
Tim Hall79d07d22020-04-27 18:20:16 +0100152 buffer_map[tens] = self.scratch_buf_id
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200153 elif tens.mem_type == MemType.Scratch_fast:
154 # For Scratch_fast when not co-allocated with scratch in the TensorArena:
155 buffer_map[tens] = self.scratch_fast_buf_id
Tim Hall79d07d22020-04-27 18:20:16 +0100156 else:
157 buffer_map[tens] = buf_idx
158 buf_idx += 1
159
Tim Hallc8310b12020-06-17 14:53:11 +0100160 # Initialize buffers_to_write to a length equal to number of buffers so
Tim Hall79d07d22020-04-27 18:20:16 +0100161 # they can be appended at the correct index during tensor serialization
162 self.buffers_to_write = [None] * (buf_idx)
163
164 return buffer_map
165
Louis Verhaardaee5d752020-09-30 09:01:52 +0200166 def serialise_operator_code(self, idx, op_type, custom_code):
Tim Hall79d07d22020-04-27 18:20:16 +0100167 builder = self.builder
168 custom_code_offset = None
Louis Verhaardaee5d752020-09-30 09:01:52 +0200169 if op_type == Op.Custom:
170 tf_code, opt_serializer = builtin_operator_inv_map[op_type]
171 custom_code_offset = builder.CreateString(custom_code)
Tim Hall79d07d22020-04-27 18:20:16 +0100172 else:
Tim Halle9194df2020-08-04 20:37:01 +0100173 assert (
Louis Verhaardaee5d752020-09-30 09:01:52 +0200174 op_type in builtin_operator_inv_map
175 ), "Vela does not contain a mapping to serialise {} operator to a TensorFlow Lite operator".format(op_type)
176 tf_code, opt_serializer = builtin_operator_inv_map[op_type]
Tim Hall79d07d22020-04-27 18:20:16 +0100177
178 if tf_code == BuiltinOperator.CUSTOM:
Tim Halle9194df2020-08-04 20:37:01 +0100179 assert (
Louis Verhaardaee5d752020-09-30 09:01:52 +0200180 op_type == Op.CustomNpuOp
Tim Halle9194df2020-08-04 20:37:01 +0100181 ), "Vela only supports serialising NpuOp operators as TensorFlow Lite Custom operators"
Tim Hall79d07d22020-04-27 18:20:16 +0100182 custom_code_offset = builder.CreateString("ethos-u")
183
Louis Verhaardaee5d752020-09-30 09:01:52 +0200184 self.operator_code_map[op_type] = (idx, tf_code, opt_serializer)
Tim Hall79d07d22020-04-27 18:20:16 +0100185
186 OperatorCode.OperatorCodeStart(builder)
187 OperatorCode.OperatorCodeAddBuiltinCode(builder, tf_code)
188 if custom_code_offset is not None:
189 OperatorCode.OperatorCodeAddCustomCode(builder, custom_code_offset)
190
191 return OperatorCode.OperatorCodeEnd(builder)
192
193 def serialise_quantization_parameters(self, quant):
194 builder = self.builder
195
196 min = None
197 max = None
198 scale = None
199 zero_point = None
200 if quant is not None:
201 if quant.min is not None:
202 min = self.write_float_vector(make_vector(quant.min))
203 if quant.max is not None:
204 max = self.write_float_vector(make_vector(quant.max))
205 if quant.scale_f32 is not None:
206 scale = self.write_float_vector(make_vector(quant.scale_f32))
207 if quant.zero_point is not None:
208 zero_point = self.write_long_vector(make_vector(quant.zero_point))
209
210 QuantizationParameters.QuantizationParametersStart(builder)
211 if min is not None:
212 QuantizationParameters.QuantizationParametersAddMin(builder, min)
213 if max is not None:
214 QuantizationParameters.QuantizationParametersAddMax(builder, max)
215 if scale is not None:
216 QuantizationParameters.QuantizationParametersAddScale(builder, scale)
217 if zero_point is not None:
218 QuantizationParameters.QuantizationParametersAddZeroPoint(builder, zero_point)
219 return QuantizationParameters.QuantizationParametersEnd(builder)
220
221 def serialise_tensor(self, tens):
222 builder = self.builder
223 tens_shape = tens.shape
224 values = tens.quant_values
225 if values is None:
226 values = tens.values
227
228 if values is None:
229 values = np.empty(shape=(0), dtype=np.uint8)
230
231 if tens in self.tensors_to_reshape:
232 reorder = self.tensors_to_reshape[tens]
233 tens_shape = [tens_shape[idx] for idx in reorder]
234 values = values.transpose(reorder)
235
Tim Hall79d07d22020-04-27 18:20:16 +0100236 buf_id = self.buffer_map[tens]
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200237 self.buffers_to_write[buf_id] = values.flatten().view(np.uint8)
Tim Hall79d07d22020-04-27 18:20:16 +0100238
239 shape = self.write_int_vector(tens_shape)
240
241 name = builder.CreateString(tens.name)
242 quant = self.serialise_quantization_parameters(tens.quantization)
243
244 Tensor.TensorStart(builder)
245 Tensor.TensorAddShape(builder, shape)
246 Tensor.TensorAddType(builder, datatype_inv_map[tens.dtype])
247 # All tensors must have a valid backing buffer, even if it is empty.
248 # Empty buffers should be kept unique for TensorFlow Lite Micro
249 Tensor.TensorAddBuffer(builder, buf_id)
250 Tensor.TensorAddName(builder, name)
251 Tensor.TensorAddQuantization(builder, quant)
252
253 res = Tensor.TensorEnd(builder)
254 return res
255
256 def serialise_operator(self, op):
257 builder = self.builder
258
Michael McGeaghbb1b09e2020-08-19 11:24:17 +0100259 inputs_offset = self.write_int_vector([self.tensor_map[tens] for tens in op.inputs if tens in self.tensor_map])
260 outputs_offset = self.write_int_vector(
261 [self.tensor_map[tens] for tens in op.outputs if tens in self.tensor_map]
262 )
Tim Hall79d07d22020-04-27 18:20:16 +0100263
264 op_idx, tflop, opt_serializer = self.operator_code_map[op.type]
265
266 builtin_opt_offset = None
267 custom_opt_offset = None
268 if opt_serializer is not None:
269 attrs = dict(op.attrs)
270 if "strides" in attrs:
271 attrs["stride_h"] = attrs["strides"][1]
272 attrs["stride_w"] = attrs["strides"][2]
273 if "ksize" in attrs:
274 attrs["filter_height"] = attrs["ksize"][1]
275 attrs["filter_width"] = attrs["ksize"][2]
276 if "dilation" in attrs:
277 attrs["dilation_h_factor"] = attrs["dilation"][1]
278 attrs["dilation_w_factor"] = attrs["dilation"][2]
279 if "channel_multiplier" in attrs:
280 attrs["depth_multiplier"] = attrs["channel_multiplier"]
Louis Verhaarde8a5a782020-11-02 18:04:27 +0100281 if op.activation is not None:
282 attrs["fused_activation_function"] = op.activation.op_type
Tim Hall79d07d22020-04-27 18:20:16 +0100283
284 builtin_opt_offset, custom_opt_offset = opt_serializer.serialize(builder, attrs)
285
286 mutating_variable_inputs_offset = self.write_byte_vector([])
287 Operator.OperatorStart(builder)
288 Operator.OperatorAddOpcodeIndex(builder, op_idx)
289 Operator.OperatorAddInputs(builder, inputs_offset)
290 Operator.OperatorAddOutputs(builder, outputs_offset)
291
292 if builtin_opt_offset is not None:
293 Operator.OperatorAddBuiltinOptionsType(builder, opt_serializer.builtin_opt_type)
294 Operator.OperatorAddBuiltinOptions(builder, builtin_opt_offset)
295 if custom_opt_offset is not None:
296 Operator.OperatorAddCustomOptions(builder, custom_opt_offset)
297 Operator.OperatorAddCustomOptionsFormat(builder, opt_serializer.custom_opt_format)
298
299 Operator.OperatorAddMutatingVariableInputs(builder, mutating_variable_inputs_offset)
300 return Operator.OperatorEnd(builder)
301
302 def serialise_subgraph(self, sg):
303 builder = self.builder
304 tensor_set = set()
Tim Hall79d07d22020-04-27 18:20:16 +0100305 all_ops = []
Michael McGeagh515c9562020-09-02 15:52:43 +0100306 placeholder_ops = []
307
Tim Hall79d07d22020-04-27 18:20:16 +0100308 for ps in sg.passes:
309 for op in ps.ops:
310 if op.type not in self.ops_to_ignore:
311 all_ops.append(op)
Louis Verhaardaee5d752020-09-30 09:01:52 +0200312 elif op.type == Op.Placeholder:
Michael McGeagh515c9562020-09-02 15:52:43 +0100313 placeholder_ops.append(op)
Tim Hall79d07d22020-04-27 18:20:16 +0100314
Michael McGeagh515c9562020-09-02 15:52:43 +0100315 # Add the tensors from all valid ops, as well as the tensors from placeholder ops
316 # This allows us to serialise tensors which arent attached to any specific ops,
317 # e.g. due to an empty graph containing no ops
318 for op in all_ops + placeholder_ops:
Tim Hall79d07d22020-04-27 18:20:16 +0100319 for tens in op.inputs + op.outputs:
Andreas Nevalainend8c032d2020-09-11 10:25:09 +0200320 if tens is not None:
321 tensor_set.add(tens)
Tim Hall79d07d22020-04-27 18:20:16 +0100322
323 all_tensors = [tens for nm, idx, tens in sorted((tens.name, idx, tens) for idx, tens in enumerate(tensor_set))]
324
Patrik Gustavsson3ab94522020-06-29 17:36:55 +0200325 scratch_tensors = [tens for tens in all_tensors if tens.name.endswith("scratch")]
326
Tim Hallc8310b12020-06-17 14:53:11 +0100327 if len(scratch_tensors) == 0:
328 scratch_tensor = None
329 else:
330 assert len(scratch_tensors) == 1, "Multiple scratch tensors"
331 scratch_tensor = scratch_tensors[0]
332
Tim Hall79d07d22020-04-27 18:20:16 +0100333 self.tensor_map = {tens: idx for idx, tens in enumerate(all_tensors)}
Tim Hallc8310b12020-06-17 14:53:11 +0100334 self.buffer_map = self.assign_buffers_to_tensors(all_tensors, scratch_tensor)
Tim Hall79d07d22020-04-27 18:20:16 +0100335
336 tensors_offset = self.write_offset_vector([self.serialise_tensor(tens) for tens in all_tensors])
337
Tim Hall79d07d22020-04-27 18:20:16 +0100338 # Make sure the input_tensors haven't been modified
339 assert all(inp in sg.original_inputs for inp in sg.input_tensors)
Michael McGeaghbb1b09e2020-08-19 11:24:17 +0100340 inputs = [self.tensor_map[tens] for tens in sg.original_inputs if tens in self.tensor_map]
Tim Hallc8310b12020-06-17 14:53:11 +0100341
Tim Hallc8310b12020-06-17 14:53:11 +0100342 inputs_offset = self.write_int_vector(inputs)
Michael McGeaghbb1b09e2020-08-19 11:24:17 +0100343 outputs_offset = self.write_int_vector(
344 [self.tensor_map[tens] for tens in sg.output_tensors if tens in self.tensor_map]
345 )
Tim Hall79d07d22020-04-27 18:20:16 +0100346
347 operators_offset = self.write_offset_vector([self.serialise_operator(op) for op in all_ops])
348
349 SubGraph.SubGraphStart(builder)
350 SubGraph.SubGraphAddTensors(builder, tensors_offset)
351 SubGraph.SubGraphAddInputs(builder, inputs_offset)
352 SubGraph.SubGraphAddOutputs(builder, outputs_offset)
353
354 SubGraph.SubGraphAddOperators(builder, operators_offset)
355
356 return SubGraph.SubGraphEnd(builder)
357
358 def write_aligned_bytes(self, buf):
359 builder = self.builder
360 builder.nested = True
361 data = bytes(buf)
362 length_bytes = UOffsetTFlags.py_type(len(data))
363 builder.Prep(16, length_bytes) # Reserve aligned storage
364 builder.head = UOffsetTFlags.py_type(builder.Head() - length_bytes) # Update FlatBuffer internal pointer
365 builder.Bytes[builder.Head() : builder.Head() + length_bytes] = data # Assign bytes to aligned area
366 return builder.EndVector(length_bytes)
367
368 def serialise_buffer(self, buf):
369 builder = self.builder
370 data = None
371 if buf is not None:
372 data = self.write_aligned_bytes(buf)
373 Buffer.BufferStart(builder)
374 if data is not None:
375 Buffer.BufferAddData(builder, data)
376 return Buffer.BufferEnd(builder)
377
378 def serialise_metadata(self, metadata):
379 builder = self.builder
380 name = builder.CreateString(metadata[0])
381
382 Metadata.MetadataStart(builder)
383 Metadata.MetadataAddName(builder, name)
384 Metadata.MetadataAddBuffer(builder, metadata[1])
385
386 return Metadata.MetadataEnd(builder)
387
388 def serialise_model(self):
389 builder = self.builder
390 operator_code_offset = self.write_offset_vector(
Louis Verhaardaee5d752020-09-30 09:01:52 +0200391 [self.serialise_operator_code(idx, optype, code) for idx, (optype, code) in enumerate(self.operator_codes)]
Tim Hall79d07d22020-04-27 18:20:16 +0100392 )
393
394 description = builder.CreateString("Vela Optimised")
395
396 subgraph_offset = self.write_offset_vector([self.serialise_subgraph(sg) for sg in self.subgraphs_to_write])
397
398 # Fill the metadata buffer
399 version = np.int32(0)
400 subgraph_idx = np.int32(len(self.subgraphs_to_write)) # Only 1 supported currently
401 nbr_tensors = np.int32(len(self.tensor_map))
402
403 # An offset of -1 indicates that the tensor will be allocated online by Tensorflow Lite Micro
404 offsets = [np.int32(-1)] * nbr_tensors
405
406 # Ensure that the order of the offsets match the order of the tensors
407 for tens, idx in self.tensor_map.items():
Patrik Gustavssoneca2e952020-05-27 09:15:11 +0200408 # Set offsets for tensor allocated in Tensor Arena or in the scratch_fast area
Jacob Bohlin268394d2020-08-13 13:24:59 +0200409 if tens.mem_type in set((MemType.Scratch, MemType.Scratch_fast)):
410 offsets[idx] = np.int32(tens.address) if tens.address is not None else np.int32(0)
Tim Hall79d07d22020-04-27 18:20:16 +0100411
Michael McGeagh22f74e12020-08-07 16:21:03 +0100412 self.nng.metadata.append(("OfflineMemoryAllocation", np.array([version, subgraph_idx, nbr_tensors] + offsets)))
413
414 metadata_list = []
415 for name, buffer in self.nng.metadata:
416 self.buffers_to_write.append(buffer)
417 metadata_list.append((name, len(self.buffers_to_write) - 1))
Tim Hall79d07d22020-04-27 18:20:16 +0100418
419 buffers_offset = self.write_offset_vector([self.serialise_buffer(buf) for buf in self.buffers_to_write])
Tim Hall79d07d22020-04-27 18:20:16 +0100420 metadata_offset = self.write_offset_vector([self.serialise_metadata(metadata) for metadata in metadata_list])
421
422 Model.ModelStart(builder)
423 Model.ModelAddVersion(builder, tflite_version)
424 Model.ModelAddOperatorCodes(builder, operator_code_offset)
425 Model.ModelAddSubgraphs(builder, subgraph_offset)
426 Model.ModelAddDescription(builder, description)
427 Model.ModelAddBuffers(builder, buffers_offset)
428 Model.ModelAddMetadata(builder, metadata_offset)
429 return Model.ModelEnd(builder)
430
431 def serialise(self):
432
433 model = self.serialise_model()
434
435 self.builder.FinishWithFileIdentifier(model, tflite_file_identifier)
436
437 return self.builder.Output()
438
439 def write(self, filename):
440 with open(self.filename, "wb") as f:
441 f.write(self.serialised_buf)
442
443
444def write_tflite(nng, filename):
445 writer = TFLiteSerialiser(nng)
446 buf = writer.serialise()
447
448 with open(filename, "wb") as f:
449 f.write(buf)