9
9
#
10
10
11
11
import logging
12
+ import struct
12
13
from typing import final , List , Optional
13
14
14
- from torch .export .exported_program import ExportedProgram
15
-
15
+ import numpy as np
16
16
import torch
17
+ from torch .export .exported_program import ExportedProgram
17
18
18
19
from executorch .backends .nxp .backend .edge_program_converter import EdgeProgramToIRConverter
19
20
from executorch .backends .nxp .backend .ir .tensor_formatting import TensorFormat
20
21
from executorch .backends .nxp .backend .neutron_converter_manager import NeutronConverterManager
21
- from executorch .backends .nxp .neutron_node_extraction import extract_artifacts_from_neutron_node
22
+ from executorch .backends .nxp .neutron_node_extraction import extract_artifacts_from_neutron_node , NeutronNodeArtifacts
22
23
from executorch .backends .xnnpack .passes import RemoveGetItemPass , XNNPACKPassManager
23
24
from executorch .exir .backend .backend_details import BackendDetails , PreprocessResult
24
25
from executorch .exir .backend .compile_spec_schema import CompileSpec
25
-
26
-
27
- from torch .export .exported_program import ExportedProgram
28
26
from executorch .exir .verification .verifier import EXIREdgeDialectVerifier
29
27
30
28
@@ -132,13 +130,6 @@ def preprocess(
132
130
133
131
# Convert the edge program to TFLite.
134
132
tflite_model , io_formats = EdgeProgramToIRConverter ().convert_program (edge_program )
135
- for tensor , tensor_format in io_formats .items ():
136
- if tensor_format == TensorFormat .CHANNELS_LAST :
137
- channel_last_format = b'1'
138
- else :
139
- channel_last_format = b'0'
140
-
141
- compile_spec .append (CompileSpec (tensor , channel_last_format ))
142
133
143
134
# Call the neutron converter with the TFLite model.
144
135
neutron_model = NeutronConverterManager ().convert (tflite_model )
@@ -153,11 +144,121 @@ def preprocess(
153
144
f .write (bytes (neutron_model ))
154
145
NeutronBackend .counter = NeutronBackend .counter + 1
155
146
156
- # Extract the Neutron microcode, weights and kernels from the Neutron Node in the `neutron_model`.
157
- payload = extract_artifacts_from_neutron_node (neutron_model )
158
- binary = payload .processed_bytes
147
+ binary = PayloadComposer ().get_binary_payload (io_formats , neutron_model )
159
148
160
149
else :
161
150
raise RuntimeError (f"Unknown format { output_format } " )
162
151
163
152
return PreprocessResult (processed_bytes = binary )
153
+
154
+
155
+ class PayloadComposer :
156
+ ALIGNMENT = 16
157
+
158
+ def _padding_format_string_for_array (self , array : np .ndarray ) -> str :
159
+ """ Create a padding format string for the given array, which will add 0s at the end for correct alignment.
160
+ E.g. the string '10x' represents adding 10 bytes of '0' padding.
161
+ """
162
+ assert array .dtype == np .dtype ('uint8' )
163
+
164
+ overflow = array .size % self .ALIGNMENT
165
+ if overflow == 0 :
166
+ return ''
167
+
168
+ # Overflow 1 means padding 15, so use `alignment - overflow` padding.
169
+ return f'{ self .ALIGNMENT - overflow } x'
170
+
171
+ def _format_string_for_array (self , array : np .ndarray ) -> str :
172
+ """ Create a format string which will represent the provided array. It also handles the necessary alignment.
173
+ E.g. for array [1,2,3] we get '3s13x', because '3s' means string of 3 bytes, and `13x` means adding 13 bytes
174
+ of '0' padding at the end (for 16B alignment).
175
+ """
176
+ assert array .dtype == np .dtype ('uint8' )
177
+
178
+ return f'{ array .size } s{ self ._padding_format_string_for_array (array )} '
179
+
180
+ def _create_payload_header (self , io_formats ) -> np .ndarray :
181
+ """
182
+ Create bytes header for returned payload. It contains information about
183
+ input and output tensor formats. Tensors are ordered based on graph signature
184
+ of ExportedProgram. Header schema:
185
+
186
+ +----------------------------------+------------------------+---------------------------+
187
+ | Input TensorFormats length (1B) | 1st tensor format (1B) | [nth* tensor format (1B)] |
188
+ +----------------------------------+------------------------+---------------------------+
189
+ | Output TensorFormats length (1B) | 1st tensor format (1B) | [nth* tensor format (1B)] |
190
+ +----------------------------------+------------------------+---------------------------+
191
+
192
+ :param io_formats: IO tensors formats.
193
+ :return: Bytes representation of payload header.
194
+ """
195
+ inputs = io_formats ["inputs" ]
196
+ outputs = io_formats ["outputs" ]
197
+
198
+ assert len (inputs ) < 256 , "Models with more than 255 inputs are not supported."
199
+ assert len (outputs ) < 256 , "Models with more than 255 outputs are not supported."
200
+
201
+ header_data = [len (inputs )]
202
+ for tensor , tensor_format in inputs .items ():
203
+ header_data .append (1 if tensor_format == TensorFormat .CHANNELS_LAST else 0 )
204
+
205
+ header_data .append (len (outputs ))
206
+ for tensor , tensor_format in outputs .items ():
207
+ header_data .append (1 if tensor_format == TensorFormat .CHANNELS_LAST else 0 )
208
+
209
+ # noinspection PyTypeChecker
210
+ return np .array (header_data , dtype = np .uint8 )
211
+
212
+ def _pack_with_alignment (self , header : np .ndarray , neutron_artifacts : NeutronNodeArtifacts ) -> bytes :
213
+ """
214
+ Packs provided data into serialized binary data of the following C struct:
215
+ struct NeutronBinary {
216
+ uint8[] header;
217
+ uint8[] microcode;
218
+ uint8[] weights;
219
+ uint8[] kernels;
220
+ }
221
+ The individual components must be aligned to 16 bytes.
222
+ """
223
+
224
+ return struct .pack (
225
+ self ._format_string_for_array (header ) +
226
+ self ._format_string_for_array (neutron_artifacts .microcode ) +
227
+ self ._format_string_for_array (neutron_artifacts .weights ) +
228
+ self ._format_string_for_array (neutron_artifacts .kernels ),
229
+ header .tobytes (),
230
+ neutron_artifacts .microcode .tobytes (),
231
+ neutron_artifacts .weights .tobytes (),
232
+ neutron_artifacts .kernels .tobytes ()
233
+ )
234
+
235
+ def get_binary_payload (self , io_formats , neutron_model ) -> bytes :
236
+ """
237
+ Get binary payload for provided input/output tensor formats and neutron_model. Returned data have
238
+ following structure:
239
+
240
+ +----------------------------------------------------------------------------------------------------------------+
241
+ | 16 bytes aligned blocks |
242
+ +===========================+===========================+============================+===========================+
243
+ | Input formats length (1B) | [nth* tensor format (1B)] | Output formats length (1B) | [nth* tensor format (1B)] |
244
+ +---------------------------+---------------------------+----------------------------+---------------------------+
245
+ | Neutron microcode |
246
+ +----------------------------------------------------------------------------------------------------------------+
247
+ | Neutron weights |
248
+ +----------------------------------------------------------------------------------------------------------------+
249
+ | Neutron kernels |
250
+ +----------------------------------------------------------------------------------------------------------------+
251
+
252
+ Tensor format definition: '0x1' == CHANNELS_LAST, '0x0' == FORMATLESS (no format).
253
+
254
+ :param io_formats: Dictionary with keys 'inputs' and 'outputs' that contains dictionaries
255
+ mapping tensor name to TensorFormat.
256
+ :param neutron_model: Neutron model with single NeutronGraph node.
257
+ :return: 16 bytes aligned binary payload.
258
+ """
259
+ header = self ._create_payload_header (io_formats )
260
+
261
+ # Extract the Neutron microcode, weights and kernels from the Neutron Node in the `neutron_model`.
262
+ neutron_artifacts = extract_artifacts_from_neutron_node (neutron_model )
263
+
264
+ return self ._pack_with_alignment (header , neutron_artifacts )
0 commit comments