ONE - On-device Neural Engine
Loading...
Searching...
No Matches
CircleOperationExporter.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
18#include "CircleExporterUtils.h"
19#include "ShapeInference.h"
20
21#include "Dialect/IR/TFLNode.h"
22#include "Dialect/IR/TFLNodes.h"
24
28
29#include "Check.h"
30
34#include <locoex/COpCall.h>
35
36#include <oops/InternalExn.h>
37
39
40#include <cassert>
41
42using namespace flatbuffers;
43using namespace circle;
44
45namespace
46{
47
48using namespace exo;
49using namespace exo::circle_detail;
50
51class OperationExporter final : public locoex::TFLNodeMutableVisitor<void>,
54{
55public:
56 OperationExporter(FlatBufferBuilder &fbb, SerializedModelData &ctx) : builder{fbb}, gd{ctx}
57 {
58 // DO NOTHING
59 }
60
61public:
62 // FOR TFLNodes
63 void visit(locoex::TFLAdd *) final;
64 void visit(locoex::TFLAveragePool2D *) final;
65 void visit(locoex::TFLConcatenation *) final;
66 void visit(locoex::TFLConst *) final{/* skip, everything is done in exportOpDefinedTensors */};
67 void visit(locoex::TFLConv2D *) final;
69 void visit(locoex::TFLDiv *) final;
70 void visit(locoex::TFLFullyConnected *) final;
71 void visit(locoex::TFLMaximum *) final;
72 void visit(locoex::TFLMaxPool2D *) final;
73 void visit(locoex::TFLMean *) final;
74 void visit(locoex::TFLMul *) final;
75 void visit(locoex::TFLRelu *) final;
76 void visit(locoex::TFLRelu6 *) final;
77 // TODO TFLReshape
78 // NOTE adding visit() to make compiler happy with "-Werror=overloaded-virtual="
79 void visit(locoex::TFLReshape *) final { assert(false); };
80 void visit(locoex::TFLRsqrt *) final;
81 // TODO TFLSoftmax
82 void visit(locoex::TFLSqrt *) final;
84 void visit(locoex::TFLSub *) final;
85 // TODO TFLTanh
86 void visit(locoex::TFLTranspose *) final;
87 void visit(locoex::TFLTransposeConv *) final;
88
89 // FOR CircleNodes
91
92 // FOR canonical nodes. These will be removed later
93 void visit(loco::ReLU *) final;
94 void visit(loco::ReLU6 *) final;
95 void visit(loco::Tanh *) final;
96 void visit(loco::Push *) final
97 { /* DO NOTHING */
98 }
99 void visit(loco::Pull *) final
100 { /* DO NOTHING */
101 }
102 void visit(loco::FeatureEncode *) final;
103 void visit(loco::FeatureDecode *) final;
104 void visit(loco::FilterEncode *) final;
106 void visit(loco::ConstGen *) final
107 { /* skip, everything is done in exportOpDefinedTensors */
108 }
109 void visit(loco::MaxPool2D *) final;
110 void visit(loco::AvgPool2D *) final;
111 void visit(loco::Conv2D *) final;
112 void visit(loco::TransposedConv2D *) final;
113 void visit(loco::DepthwiseConv2D *) final;
114 void visit(loco::TensorConcat *) final;
115 void visit(loco::TensorReduce *) final;
116 void visit(loco::TensorSoftmax *) final;
117 void visit(loco::BiasEncode *) final;
118 void visit(loco::TensorBiasAdd *) final;
119 void visit(loco::FeatureBiasAdd *) final;
120 void visit(loco::EltwiseAdd *) final;
121 void visit(loco::EltwiseMax *) final;
122 void visit(loco::EltwiseMul *) final;
123 void visit(loco::EltwiseSub *) final;
124 void visit(loco::EltwiseDiv *) final;
125 void visit(loco::EltwiseSqrt *) final;
126 void visit(loco::FixedReshape *) final;
127 void visit(loco::TensorBroadcast *) final;
128 void visit(loco::TensorConstantPad *) final;
129
130 void visit(locoex::COpCall *);
131
132 // NOTE adding visit() to make compiler happy with "-Werror=overloaded-virtual="
133 void visit(loco::Node *) final { assert(false); }
134 void visit(loco::MatMul *) final { assert(false); }
135 void visit(loco::MatrixDecode *) final { assert(false); }
136 void visit(loco::MatrixEncode *) final { assert(false); }
137 void visit(loco::TensorTranspose *) final { assert(false); }
138 void visit(loco::Forward *) final { assert(false); }
139 void visit(loco::FilterDecode *) final { assert(false); }
140 void visit(loco::DepthwiseFilterDecode *) final { assert(false); }
141 void visit(loco::BiasDecode *) final { assert(false); }
142 void visit(locoex::TFLNode *) final { assert(false); }
143 void visit(locoex::CircleNode *) final { assert(false); }
144
145private:
151 template <class TFLPool2D>
152 void export_pool_2d(TFLPool2D *node, circle::BuiltinOperator builtin_op);
153
154private:
155 FlatBufferBuilder &builder;
157};
158
159void OperationExporter::visit(locoex::TFLAdd *node)
160{
161 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD);
162 std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
163 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
164 auto inputs = builder.CreateVector(inputs_vec);
165 auto outputs = builder.CreateVector(outputs_vec);
166 auto options = CreateAddOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
167 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
168 circle::BuiltinOptions_AddOptions, options.Union());
169 gd._operators.push_back(op_offset);
170}
171
172void OperationExporter::visit(locoex::TFLAveragePool2D *node)
173{
174 export_pool_2d<locoex::TFLAveragePool2D>(node, circle::BuiltinOperator_AVERAGE_POOL_2D);
175}
176
177void OperationExporter::visit(locoex::TFLConcatenation *node)
178{
179 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION);
180 std::vector<int32_t> inputs_vec;
181 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
182
183 for (uint32_t i = 0; i < node->numValues(); ++i)
184 inputs_vec.push_back(get_tensor_index(node->values(i)));
185
186 auto inputs = builder.CreateVector(inputs_vec);
187 auto outputs = builder.CreateVector(outputs_vec);
188 auto options = CreateConcatenationOptions(builder, node->axis(),
190 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
191 circle::BuiltinOptions_ConcatenationOptions, options.Union());
192 gd._operators.push_back(op_offset);
193}
194
195void OperationExporter::visit(locoex::TFLConv2D *node)
196{
197 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONV_2D);
198
199 // Make input, output and options for operator
200 std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()),
201 get_tensor_index(node->bias())};
202 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
203 auto inputs = builder.CreateVector(inputs_vec);
204 auto outputs = builder.CreateVector(outputs_vec);
205 circle::Padding padding = getOpPadding(node->padding());
206 auto options = CreateConv2DOptions(builder, padding, node->stride()->w(), node->stride()->h(),
208
209 // Make CONV_2D operator
210 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
211 circle::BuiltinOptions_Conv2DOptions, options.Union());
212 gd._operators.push_back(op_offset);
213}
214
215void OperationExporter::visit(locoex::TFLDepthwiseConv2D *node)
216{
217 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DEPTHWISE_CONV_2D);
218
219 // Make input, output and options for operator
220 std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()),
221 get_tensor_index(node->bias())};
222 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
223 auto inputs = builder.CreateVector(inputs_vec);
224 auto outputs = builder.CreateVector(outputs_vec);
225 circle::Padding padding = getOpPadding(node->padding());
226 auto options = CreateDepthwiseConv2DOptions(builder, padding, node->stride()->w(),
227 node->stride()->h(), node->depthMultiplier(),
229
230 // Make DEPTHWISE_CONV_2D operator
231 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
232 circle::BuiltinOptions_DepthwiseConv2DOptions, options.Union());
233 gd._operators.push_back(op_offset);
234}
235
236void OperationExporter::visit(locoex::TFLDiv *node)
237{
238 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DIV);
239 std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
240 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
241 auto inputs = builder.CreateVector(inputs_vec);
242 auto outputs = builder.CreateVector(outputs_vec);
243 auto options = CreateDivOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
244 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
245 circle::BuiltinOptions_DivOptions, options.Union());
246 gd._operators.push_back(op_offset);
247}
248
249void OperationExporter::visit(locoex::TFLFullyConnected *node)
250{
251 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_FULLY_CONNECTED);
252
253 // Make input, output and options for operator
254 std::vector<int32_t> inputs_vec{get_tensor_index(node->input()),
255 get_tensor_index(node->weights()),
256 get_tensor_index(node->bias())};
257 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
258 auto inputs = builder.CreateVector(inputs_vec);
259 auto outputs = builder.CreateVector(outputs_vec);
260 auto options =
261 CreateFullyConnectedOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
262
263 // Make FULLY_CONNECTED operator
264 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
265 circle::BuiltinOptions_FullyConnectedOptions, options.Union());
266 gd._operators.push_back(op_offset);
267}
268
269void OperationExporter::visit(locoex::TFLMaximum *node)
270{
271 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MAXIMUM);
272 std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
273 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
274 auto inputs = builder.CreateVector(inputs_vec);
275 auto outputs = builder.CreateVector(outputs_vec);
276 auto options = CreateMaximumMinimumOptions(builder);
277 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
278 circle::BuiltinOptions_MaximumMinimumOptions, options.Union());
279 gd._operators.push_back(op_offset);
280}
281
282void OperationExporter::visit(locoex::TFLMaxPool2D *node)
283{
284 export_pool_2d<locoex::TFLMaxPool2D>(node, circle::BuiltinOperator_MAX_POOL_2D);
285}
286
287void OperationExporter::visit(locoex::TFLMean *node)
288{
289 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MEAN);
290 std::vector<int32_t> inputs_vec{get_tensor_index(node->input()),
292 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
293 auto inputs = builder.CreateVector(inputs_vec);
294 auto outputs = builder.CreateVector(outputs_vec);
295 auto options = CreateReducerOptions(builder, node->keep_dims());
296 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
297 circle::BuiltinOptions_ReducerOptions, options.Union());
298 gd._operators.push_back(op_offset);
299}
300
301void OperationExporter::visit(locoex::TFLMul *node)
302{
303 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MUL);
304 std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
305 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
306 auto inputs = builder.CreateVector(inputs_vec);
307 auto outputs = builder.CreateVector(outputs_vec);
308 auto options = CreateMulOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
309 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
310 circle::BuiltinOptions_MulOptions, options.Union());
311 gd._operators.push_back(op_offset);
312}
313
314void OperationExporter::visit(locoex::TFLRelu *node)
315{
316 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU);
317 std::vector<int32_t> inputs_vec{get_tensor_index(node->features())};
318 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
319 auto inputs = builder.CreateVector(inputs_vec);
320 auto outputs = builder.CreateVector(outputs_vec);
321 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
322 gd._operators.push_back(op_offset);
323}
324
325void OperationExporter::visit(locoex::TFLRelu6 *node)
326{
327 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU6);
328 std::vector<int32_t> inputs_vec{get_tensor_index(node->features())};
329 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
330 auto inputs = builder.CreateVector(inputs_vec);
331 auto outputs = builder.CreateVector(outputs_vec);
332 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
333 gd._operators.push_back(op_offset);
334}
335
336// TODO TFLReshape
337
338void OperationExporter::visit(locoex::TFLRsqrt *node)
339{
340 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RSQRT);
341 std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
342 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
343 auto inputs = builder.CreateVector(inputs_vec);
344 auto outputs = builder.CreateVector(outputs_vec);
345 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
346 gd._operators.push_back(op_offset);
347}
348
349// TODO TFLSoftmax
350
351void OperationExporter::visit(locoex::TFLSqrt *node)
352{
353 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQRT);
354 std::vector<int32_t> inputs_vec{get_tensor_index(node->x())};
355 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
356 auto inputs = builder.CreateVector(inputs_vec);
357 auto outputs = builder.CreateVector(outputs_vec);
358 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
359 gd._operators.push_back(op_offset);
360}
361
362void OperationExporter::visit(locoex::TFLSquaredDifference *node)
363{
364 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQUARED_DIFFERENCE);
365 std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
366 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
367 auto inputs = builder.CreateVector(inputs_vec);
368 auto outputs = builder.CreateVector(outputs_vec);
369 auto options = CreateSquaredDifferenceOptions(builder);
370 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
371 circle::BuiltinOptions_SquaredDifferenceOptions, options.Union());
372 gd._operators.push_back(op_offset);
373}
374
375void OperationExporter::visit(locoex::TFLSub *node)
376{
377 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SUB);
378 std::vector<int32_t> inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())};
379 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
380 auto inputs = builder.CreateVector(inputs_vec);
381 auto outputs = builder.CreateVector(outputs_vec);
382 auto options = CreateSubOptions(builder, to_circle_actfunc(node->fusedActivationFunction()));
383 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
384 circle::BuiltinOptions_SubOptions, options.Union());
385 gd._operators.push_back(op_offset);
386}
387
388// TODO TFLTanh
389
390void OperationExporter::visit(locoex::TFLTranspose *node)
391{
392 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE);
393 std::vector<int32_t> inputs_vec{get_tensor_index(node->arg(0)), get_tensor_index(node->arg(1))};
394 std::vector<int32_t> outputs_vec{get_tensor_index(node)};
395
396 auto inputs = builder.CreateVector(inputs_vec);
397 auto outputs = builder.CreateVector(outputs_vec);
398 auto options = CreateTransposeOptions(builder);
399
400 auto op_offset =
401 CreateOperator(builder, op_idx, inputs, outputs,
402 circle::BuiltinOptions::BuiltinOptions_TransposeOptions, options.Union());
403 gd._operators.push_back(op_offset);
404}
405
406void OperationExporter::visit(locoex::TFLTransposeConv *node)
407{
408 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE_CONV);
409
410 // Make input, output and options for operator
411 std::vector<int32_t> inputs_vec{get_tensor_index(node->inputSizes()),
412 get_tensor_index(node->filter()),
414 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
415 auto inputs = builder.CreateVector(inputs_vec);
416 auto outputs = builder.CreateVector(outputs_vec);
417 circle::Padding padding = getOpPadding(node->padding());
418 auto options =
419 CreateTransposeConvOptions(builder, padding, node->stride()->w(), node->stride()->h());
420
421 // Make TRANSPOSE_CONV operator
422 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
423 circle::BuiltinOptions_TransposeConvOptions, options.Union());
424 gd._operators.push_back(op_offset);
425}
426
427template <class TFLPool2D>
428void OperationExporter::export_pool_2d(TFLPool2D *node, circle::BuiltinOperator builtin_op)
429{
430 EXO_ASSERT(builtin_op == circle::BuiltinOperator_MAX_POOL_2D ||
431 builtin_op == circle::BuiltinOperator_AVERAGE_POOL_2D,
432 "should be maxpool or avgpool");
433 EXO_ASSERT(node->padding() != locoex::Padding::UNDEFINED, "Padding is not set");
434
435 uint32_t op_idx = gd.registerBuiltinOpcode(builtin_op);
436 std::vector<int32_t> inputs_vec{get_tensor_index(node->value())};
437 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
438 auto inputs = builder.CreateVector(inputs_vec);
439 auto outputs = builder.CreateVector(outputs_vec);
440
441 circle::Padding padding = getOpPadding(node->padding());
442
443 auto options = CreatePool2DOptions(builder, padding, node->stride()->w(), node->stride()->h(),
444 node->filter()->w(), node->filter()->h(),
445 to_circle_actfunc(node->fusedActivationFunction()));
446 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
447 circle::BuiltinOptions_Pool2DOptions, options.Union());
448 gd._operators.push_back(op_offset);
449}
450
451void OperationExporter::visit(locoex::CircleInstanceNorm *node)
452{
453 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_INSTANCE_NORM);
454 std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->gamma()),
455 get_tensor_index(node->beta())};
456 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
457 auto inputs = builder.CreateVector(inputs_vec);
458 auto outputs = builder.CreateVector(outputs_vec);
459 auto options = CreateInstanceNormOptions(builder, node->epsilon(),
461 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
462 circle::BuiltinOptions_InstanceNormOptions, options.Union());
463 gd._operators.push_back(op_offset);
464}
465
466void OperationExporter::visit(loco::ReLU *node)
467{
468 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU);
469 std::vector<int32_t> inputs_vec{get_tensor_index(node->input())};
470 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
471 auto inputs = builder.CreateVector(inputs_vec);
472 auto outputs = builder.CreateVector(outputs_vec);
473 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
474 gd._operators.push_back(op_offset);
475}
476
477void OperationExporter::visit(loco::ReLU6 *node)
478{
479 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU6);
480 std::vector<int32_t> inputs_vec{get_tensor_index(node->input())};
481 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
482 auto inputs = builder.CreateVector(inputs_vec);
483 auto outputs = builder.CreateVector(outputs_vec);
484 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
485 gd._operators.push_back(op_offset);
486}
487
488void OperationExporter::visit(loco::Tanh *node)
489{
490 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TANH);
491 std::vector<int32_t> inputs_vec{get_tensor_index(node->input())};
492 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
493 auto inputs = builder.CreateVector(inputs_vec);
494 auto outputs = builder.CreateVector(outputs_vec);
495 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
496 gd._operators.push_back(op_offset);
497}
498
499void OperationExporter::visit(loco::MaxPool2D *node)
500{
501 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MAX_POOL_2D);
502 std::vector<int32_t> inputs_vec{get_tensor_index(node->ifm())};
503 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
504 auto inputs = builder.CreateVector(inputs_vec);
505 auto outputs = builder.CreateVector(outputs_vec);
506 circle::Padding padding = getOpPadding(
507 node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node));
508 auto options =
509 CreatePool2DOptions(builder, padding, node->stride()->horizontal(), node->stride()->vertical(),
510 node->window()->horizontal(), node->window()->vertical());
511 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
512 circle::BuiltinOptions_Pool2DOptions, options.Union());
513 gd._operators.push_back(op_offset);
514}
515
516void OperationExporter::visit(loco::AvgPool2D *node)
517{
518 // Circle only support Valid convention of average pooling
520
521 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_AVERAGE_POOL_2D);
522 std::vector<int32_t> inputs_vec{get_tensor_index(node->ifm())};
523 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
524 auto inputs = builder.CreateVector(inputs_vec);
525 auto outputs = builder.CreateVector(outputs_vec);
526 circle::Padding padding = getOpPadding(
527 node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node));
528 auto options =
529 CreatePool2DOptions(builder, padding, node->stride()->horizontal(), node->stride()->vertical(),
530 node->window()->horizontal(), node->window()->vertical());
531 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
532 circle::BuiltinOptions_Pool2DOptions, options.Union());
533 gd._operators.push_back(op_offset);
534}
535
536void OperationExporter::visit(loco::Conv2D *node)
537{
538 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONV_2D);
539
540 // Third input of CONV_2D of Circle should be bias. We will make (and register to gd) dummy zero
541 // bias. Bias would be rank 1, have size of output kernel count, and have all zero values, i.e.
542 // zero bias.
543 auto *ker = dynamic_cast<loco::FilterEncode *>(node->ker());
544 assert(ker);
545 int32_t bias_vec_size = ShapeInference::get(ker)._dims[0]; // output kernel count
546
547 auto bias_vec_shape_offset = builder.CreateVector(std::vector<int32_t>{bias_vec_size});
548 size_t raw_bias_vec_size = bias_vec_size * sizeof(int32_t);
549
550 std::vector<float> bias_vec_data(bias_vec_size); // initialized as zero vector
551
552 auto bias_vec_offset =
553 builder.CreateVector(reinterpret_cast<uint8_t *>(bias_vec_data.data()), raw_bias_vec_size);
554
555 auto bias_buffer_offset = CreateBuffer(builder, bias_vec_offset);
556
557 const auto bias_buffer_id = static_cast<uint32_t>(gd._buffers.size());
558
559 gd._buffers.push_back(bias_buffer_offset);
560
561 auto bias_tensor_id = static_cast<int32_t>(gd._tensors.size());
562 auto name_offset = builder.CreateString("t_" + std::to_string(bias_tensor_id));
563
564 auto bias_tensor_offset =
565 CreateTensor(builder, bias_vec_shape_offset, TensorType_FLOAT32, bias_buffer_id, name_offset);
566 gd._tensors.push_back(bias_tensor_offset);
567
568 // Make input, output and options for operator
569 std::vector<int32_t> inputs_vec{get_tensor_index(node->ifm()), get_tensor_index(node->ker()),
570 bias_tensor_id};
571 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
572 auto inputs = builder.CreateVector(inputs_vec);
573 auto outputs = builder.CreateVector(outputs_vec);
574 circle::Padding padding = getOpPadding(
575 node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node));
576 auto options =
577 CreateConv2DOptions(builder, padding, node->stride()->horizontal(), node->stride()->vertical());
578
579 // Make CONV_2D operator
580 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
581 circle::BuiltinOptions_Conv2DOptions, options.Union());
582 gd._operators.push_back(op_offset);
583}
584
585void OperationExporter::visit(loco::TransposedConv2D *node)
586{
587 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE_CONV);
588
589 // TRANSPOSE_CONV's first input is output shape array.
590 const int32_t outshape_vec_size = 4;
591 auto outshape_vec_shape_offset = builder.CreateVector(std::vector<int32_t>{outshape_vec_size});
592 size_t raw_outshape_vec_size = outshape_vec_size * sizeof(int32_t);
593
594 std::vector<int32_t> outshape_vec_data(outshape_vec_size);
595 {
596 // Copy inferred output shape of node
597 auto out_feature_shape = loco::shape_get(node).as<loco::FeatureShape>();
598
599 // Feature tensor in Circle is NHWC
600 outshape_vec_data.at(0) = out_feature_shape.count().value();
601 outshape_vec_data.at(1) = out_feature_shape.height().value();
602 outshape_vec_data.at(2) = out_feature_shape.width().value();
603 outshape_vec_data.at(3) = out_feature_shape.depth().value();
604 }
605
606 auto outshape_vec_offset = builder.CreateVector(
607 reinterpret_cast<uint8_t *>(outshape_vec_data.data()), raw_outshape_vec_size);
608
609 auto outshape_buffer_offset = CreateBuffer(builder, outshape_vec_offset);
610
611 const auto outshape_buffer_id = static_cast<uint32_t>(gd._buffers.size());
612
613 gd._buffers.push_back(outshape_buffer_offset);
614
615 auto outshape_tensor_id = static_cast<int32_t>(gd._tensors.size());
616 auto name_offset = builder.CreateString("t_" + std::to_string(outshape_tensor_id));
617
618 auto outshape_tensor_offset = CreateTensor(builder, outshape_vec_shape_offset, TensorType_INT32,
619 outshape_buffer_id, name_offset);
620 gd._tensors.push_back(outshape_tensor_offset);
621
622 // Make input, output and options for operator
623 std::vector<int32_t> inputs_vec{outshape_tensor_id, get_tensor_index(node->ker()),
624 get_tensor_index(node->ifm())};
625 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
626 auto inputs = builder.CreateVector(inputs_vec);
627 auto outputs = builder.CreateVector(outputs_vec);
628 // NOTE input and output is inversed to use this function
629 circle::Padding padding = getOpPadding(node->pad(), node->stride(), ShapeInference::get(node),
630 ShapeInference::get(node->ifm()));
631 auto options = CreateTransposeConvOptions(builder, padding, node->stride()->horizontal(),
632 node->stride()->vertical());
633
634 // Make TRANSPOSE_CONV operator
635 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
636 circle::BuiltinOptions_TransposeConvOptions, options.Union());
637 gd._operators.push_back(op_offset);
638}
639
640void OperationExporter::visit(loco::DepthwiseConv2D *node)
641{
642 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DEPTHWISE_CONV_2D);
643
644 // Third input of DEPTHWISE_CONV2D of Circle should be bias. We will make (and register to gd)
645 // dummy zero bias. Bias would be rank 1, have size of output kernel count, and have all zero
646 // values, i.e. zero bias.
647 auto *ker = dynamic_cast<loco::DepthwiseFilterEncode *>(node->ker());
648 assert(ker);
649
650 int32_t bias_vec_size = ShapeInference::get(ker)._dims[3]; // output_size(C*M)
651 auto bias_vec_shape_offset = builder.CreateVector(std::vector<int32_t>{bias_vec_size});
652
653 size_t raw_bias_vec_size = bias_vec_size * sizeof(int32_t);
654 std::vector<float> bias_vec_data(bias_vec_size);
655 auto bias_vec_offset =
656 builder.CreateVector(reinterpret_cast<uint8_t *>(bias_vec_data.data()), raw_bias_vec_size);
657
658 auto bias_buffer_offset = CreateBuffer(builder, bias_vec_offset);
659
660 const auto bias_buffer_id = static_cast<uint32_t>(gd._buffers.size());
661
662 gd._buffers.push_back(bias_buffer_offset);
663
664 auto bias_tensor_id = static_cast<int32_t>(gd._tensors.size());
665 auto name_offset = builder.CreateString("t_" + std::to_string(bias_tensor_id));
666
667 auto bias_tensor_offset =
668 CreateTensor(builder, bias_vec_shape_offset, TensorType_FLOAT32, bias_buffer_id, name_offset);
669 gd._tensors.push_back(bias_tensor_offset);
670
671 std::vector<int32_t> inputs_vec{get_tensor_index(node->ifm()), get_tensor_index(node->ker()),
672 bias_tensor_id};
673 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
674 auto inputs = builder.CreateVector(inputs_vec);
675 auto outputs = builder.CreateVector(outputs_vec);
676 circle::Padding padding = getOpPadding(
677 node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node));
678
679 int32_t ifm_channel_size = ShapeInference::get(node->ifm())._dims[3];
680 // multiplier = bias_vec_size(output_size)/ifm_channel_size
681 auto options =
682 CreateDepthwiseConv2DOptions(builder, padding, node->stride()->horizontal(),
683 node->stride()->vertical(), bias_vec_size / ifm_channel_size);
684
685 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
686 circle::BuiltinOptions_DepthwiseConv2DOptions, options.Union());
687 gd._operators.push_back(op_offset);
688}
689
690void OperationExporter::visit(loco::TensorReduce *node)
691{
692 uint32_t op_idx;
693
694 switch (node->func())
695 {
697 op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MEAN);
698 break;
699
700 // TODO Support more reduce type operation
701 default:
702 INTERNAL_EXN_V("Unsupported reduce type", oops::to_uint32(node->func()));
703 }
704
705 // Create a vector for axes data
706 std::vector<int32_t> axes_vec;
707 auto rank = ShapeInference::get(node->input())._dims.size();
708 for (uint32_t i = 0; i < rank; ++i)
709 if (node->axes()->defined(i))
710 axes_vec.push_back(i);
711
712 int32_t axes_vec_size = axes_vec.size();
713 auto axes_vec_shape_offset = builder.CreateVector(std::vector<int32_t>{axes_vec_size});
714
715 size_t raw_axes_vec_size = axes_vec_size * sizeof(int32_t);
716 auto axes_vec_offset =
717 builder.CreateVector(reinterpret_cast<uint8_t *>(axes_vec.data()), raw_axes_vec_size);
718
719 auto axes_buffer_offset = CreateBuffer(builder, axes_vec_offset);
720
721 const auto axes_buffer_id = static_cast<uint32_t>(gd._buffers.size());
722
723 gd._buffers.push_back(axes_buffer_offset);
724
725 auto axes_tensor_id = static_cast<int32_t>(gd._tensors.size());
726 auto name_offset = builder.CreateString("t_" + std::to_string(axes_tensor_id));
727
728 auto axes_tensor_offset =
729 CreateTensor(builder, axes_vec_shape_offset, TensorType_INT32, axes_buffer_id, name_offset);
730 gd._tensors.push_back(axes_tensor_offset);
731
732 std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), axes_tensor_id};
733 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
734 auto inputs = builder.CreateVector(inputs_vec);
735 auto outputs = builder.CreateVector(outputs_vec);
736 auto options = CreateReducerOptions(builder, true); // true is for keep_dims option
737 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
738 circle::BuiltinOptions_ReducerOptions, options.Union());
739 gd._operators.push_back(op_offset);
740}
741
742void OperationExporter::visit(loco::TensorSoftmax *node)
743{
744 // TODO Support when the input rank of TensorSoftmax is not 2
745 assert(ShapeInference::get(node->input())._dims.size() == 2);
746
747 // NOTE Circle only accepts axis when the value is last dimension
748 assert(node->axis() == ShapeInference::get(node->input())._dims.size() - 1);
749
750 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SOFTMAX);
751 std::vector<int32_t> inputs_vec{get_tensor_index(node->input())};
752 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
753 auto inputs = builder.CreateVector(inputs_vec);
754 auto outputs = builder.CreateVector(outputs_vec);
755 auto options = CreateSoftmaxOptions(builder, 1.0f);
756 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
757 circle::BuiltinOptions_SoftmaxOptions, options.Union());
758 gd._operators.push_back(op_offset);
759}
760
762template <typename NodeT>
763void exportIdentity(NodeT *node, FlatBufferBuilder &builder, SerializedModelData &gd)
764{
765 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION);
766 std::vector<int32_t> inputs_vec{get_tensor_index(node->arg(0))};
767 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
768 auto inputs = builder.CreateVector(inputs_vec);
769 auto outputs = builder.CreateVector(outputs_vec);
770 auto options = CreateConcatenationOptions(builder); // use dummy 0 axis and NONE activation
771 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
772 circle::BuiltinOptions_ConcatenationOptions, options.Union());
773
774 gd._operators.push_back(op_offset);
775}
776
778void exportAsTranspose(loco::Node *node, FlatBufferBuilder &builder,
779 std::vector<int32_t> &perm_vec_data, SerializedModelData &gd)
780{
781 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE);
782
783 auto options = CreateTransposeOptions(builder);
784
785 // Create constant tensor with perm vector
786 constexpr int perm_vec_size = 4;
787 assert(perm_vec_data.size() == perm_vec_size);
788 auto perm_vec_shape_offset = builder.CreateVector(std::vector<int32_t>{perm_vec_size});
789 constexpr size_t raw_perm_vec_size = perm_vec_size * sizeof(int32_t);
790
791 auto perm_vec_offset =
792 builder.CreateVector(reinterpret_cast<uint8_t *>(perm_vec_data.data()), raw_perm_vec_size);
793
794 auto perm_buffer_offset = CreateBuffer(builder, perm_vec_offset);
795
796 const auto perm_buffer_id = static_cast<uint32_t>(gd._buffers.size());
797
798 gd._buffers.push_back(perm_buffer_offset);
799
800 auto perm_tensor_id = static_cast<int32_t>(gd._tensors.size());
801 auto name_offset = builder.CreateString("t_" + std::to_string(perm_tensor_id));
802
803 auto perm_tensor_offset =
804 CreateTensor(builder, perm_vec_shape_offset, TensorType_INT32, perm_buffer_id, name_offset);
805 gd._tensors.push_back(perm_tensor_offset);
806
807 // Create permutation node
808
809 std::vector<int32_t> inputs_vec{get_tensor_index(node->arg(0)), perm_tensor_id};
810 std::vector<int32_t> outputs_vec{get_tensor_index(node)};
811
812 auto inputs = builder.CreateVector(inputs_vec);
813 auto outputs = builder.CreateVector(outputs_vec);
814
815 constexpr auto options_type = circle::BuiltinOptions::BuiltinOptions_TransposeOptions;
816
817 auto transpose_offset =
818 CreateOperator(builder, op_idx, inputs, outputs, options_type, options.Union());
819 gd._operators.push_back(transpose_offset);
820}
821
822void OperationExporter::visit(loco::FeatureEncode *node)
823{
824 auto encoder = dynamic_cast<loco::PermutingEncoder<loco::Domain::Feature> *>(node->encoder());
825 auto perm = encoder->perm();
826
827 if (isNHWC(perm))
828 {
829 // Note that Circle represents feature as NHWC
830 exportIdentity(node, builder, gd);
831 }
832 else
833 {
834 std::vector<int32_t> perm_vec_data(4);
835 perm_vec_data[0] = perm->axis(loco::FeatureAxis::Count);
836 perm_vec_data[1] = perm->axis(loco::FeatureAxis::Height);
837 perm_vec_data[2] = perm->axis(loco::FeatureAxis::Width);
838 perm_vec_data[3] = perm->axis(loco::FeatureAxis::Depth);
839
840 exportAsTranspose(node, builder, perm_vec_data, gd);
841 }
842}
843
844void OperationExporter::visit(loco::FeatureDecode *node)
845{
846 auto decoder = dynamic_cast<loco::PermutingDecoder<loco::Domain::Feature> *>(node->decoder());
847 auto perm = decoder->perm();
848
849 if (isNHWC(perm))
850 {
851 // Note that Circle represents feature as NHWC
852 exportIdentity(node, builder, gd);
853 }
854 else
855 {
856 std::vector<int32_t> perm_vec_data(4);
857 perm_vec_data[perm->axis(loco::FeatureAxis::Count)] = 0;
858 perm_vec_data[perm->axis(loco::FeatureAxis::Height)] = 1;
859 perm_vec_data[perm->axis(loco::FeatureAxis::Width)] = 2;
860 perm_vec_data[perm->axis(loco::FeatureAxis::Depth)] = 3;
861
862 exportAsTranspose(node, builder, perm_vec_data, gd);
863 }
864}
865
866void OperationExporter::visit(loco::FilterEncode *node)
867{
868 auto encoder = dynamic_cast<loco::PermutingEncoder<loco::Domain::Filter> *>(node->encoder());
869 auto perm = encoder->perm();
870
871 if (isNHWC(perm))
872 {
873 // Note that Circle represents filter as NHWC
874 exportIdentity(node, builder, gd);
875 }
876 else
877 {
878 std::vector<int32_t> perm_vec_data(4);
879 // NOTE In Circle, all tensors means NHWC, so 0 = N, 1 = H, 2 = W, 3 = C
880 perm_vec_data[0] = perm->axis(loco::FilterAxis::Count);
881 perm_vec_data[1] = perm->axis(loco::FilterAxis::Height);
882 perm_vec_data[2] = perm->axis(loco::FilterAxis::Width);
883 perm_vec_data[3] = perm->axis(loco::FilterAxis::Depth);
884
885 exportAsTranspose(node, builder, perm_vec_data, gd);
886 }
887}
888
889void exportAsReshape(loco::Node *node, FlatBufferBuilder &builder,
890 std::vector<int32_t> &new_shape_vec, SerializedModelData &gd)
891{
892 // NOTE Circle currently follows TFLite for this.
893 // NOTE TFLite has two ways to get new shape paramter,
894 // one is by attribute 'new_shape' and the other is by input 'shape'.
895 // Therefore TFLite interpreter calculates Reshape operation correctly
896 // if one of them is valid.
897 // However, since NN runtime usually get new shape parameter by input 'shape',
898 // passing new shape only by attribute can cause some problems.
899 // Of course, the opposite situation can be occurred in the future.
900 // To prevent those problems, we pass new shape parameter not only by attribute
901 // but also by input.
902
903 auto input_shape_shape_vec_offset =
904 builder.CreateVector(std::vector<int32_t>{(int32_t)new_shape_vec.size()});
905
906 size_t input_shape_vec_size = new_shape_vec.size() * sizeof(int32_t);
907 auto input_shape_input_vec_offset =
908 builder.CreateVector(reinterpret_cast<uint8_t *>(new_shape_vec.data()), input_shape_vec_size);
909 auto input_shape_buffer_offset = CreateBuffer(builder, input_shape_input_vec_offset);
910
911 const auto input_shape_buffer_id = static_cast<uint32_t>(gd._buffers.size());
912 gd._buffers.push_back(input_shape_buffer_offset);
913
914 auto input_shape_tensor_id = static_cast<int32_t>(gd._tensors.size());
915 auto name_offset = builder.CreateString("t_" + std::to_string(input_shape_tensor_id));
916 auto input_shape_tensor_offset = CreateTensor(
917 builder, input_shape_shape_vec_offset, TensorType_INT32, input_shape_buffer_id, name_offset);
918 gd._tensors.push_back(input_shape_tensor_offset);
919
920 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RESHAPE);
921
922 std::vector<int32_t> inputs_vec{get_tensor_index(node->arg(0)), input_shape_tensor_id};
923 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
924 auto inputs = builder.CreateVector(inputs_vec);
925 auto outputs = builder.CreateVector(outputs_vec);
926
927 auto new_shape_vec_offset = builder.CreateVector(new_shape_vec);
928 auto options = CreateReshapeOptions(builder, new_shape_vec_offset);
929
930 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
931 circle::BuiltinOptions_ReshapeOptions, options.Union());
932
933 gd._operators.push_back(op_offset);
934}
935
936void OperationExporter::visit(loco::DepthwiseFilterEncode *node)
937{
938 auto ker = node->input(); // [H, W, C, M]
939
940 // Circle represents filter as [1, H, W, C*M] where M is multiplier.
941 std::vector<int32_t> new_shape_vec(4);
942 new_shape_vec[0] = 1;
943 new_shape_vec[1] = ShapeInference::get(ker)._dims[0];
944 new_shape_vec[2] = ShapeInference::get(ker)._dims[1];
945 new_shape_vec[3] = ShapeInference::get(ker)._dims[2] * ShapeInference::get(ker)._dims[3];
946
947 exportAsReshape(node, builder, new_shape_vec, gd);
948}
949
950void OperationExporter::visit(loco::BiasAdd<loco::Domain::Tensor> *node)
951{
952 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD);
953 std::vector<int32_t> inputs_vec{get_tensor_index(node->value()), get_tensor_index(node->bias())};
954 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
955 auto inputs = builder.CreateVector(inputs_vec);
956 auto outputs = builder.CreateVector(outputs_vec);
957 auto options = CreateAddOptions(builder); // dummy option
958 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
959 circle::BuiltinOptions_AddOptions, options.Union());
960 gd._operators.push_back(op_offset);
961}
962
963void OperationExporter::visit(loco::FeatureBiasAdd *node)
964{
965 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD);
966 std::vector<int32_t> inputs_vec{get_tensor_index(node->value()), get_tensor_index(node->bias())};
967 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
968 auto inputs = builder.CreateVector(inputs_vec);
969 auto outputs = builder.CreateVector(outputs_vec);
970 auto options = CreateAddOptions(builder); // dummy option
971 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
972 circle::BuiltinOptions_AddOptions, options.Union());
973 gd._operators.push_back(op_offset);
974}
975
977void OperationExporter::visit(loco::TensorConcat *node)
978{
979 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION);
980 std::vector<int32_t> inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())};
981 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
982 auto inputs = builder.CreateVector(inputs_vec);
983 auto outputs = builder.CreateVector(outputs_vec);
984 auto options = CreateConcatenationOptions(builder, node->axis());
985 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
986 circle::BuiltinOptions_ConcatenationOptions, options.Union());
987
988 gd._operators.push_back(op_offset);
989}
990
991void OperationExporter::visit(loco::BiasEncode *encode) { exportIdentity(encode, builder, gd); }
992
993void OperationExporter::visit(loco::EltwiseAdd *node)
994{
995 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD);
996 std::vector<int32_t> inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())};
997 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
998 auto inputs = builder.CreateVector(inputs_vec);
999 auto outputs = builder.CreateVector(outputs_vec);
1000 auto options = CreateAddOptions(builder); // dummy option
1001 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
1002 circle::BuiltinOptions_AddOptions, options.Union());
1003 gd._operators.push_back(op_offset);
1004}
1005
1006void OperationExporter::visit(loco::EltwiseMax *node)
1007{
1008 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MAXIMUM);
1009 std::vector<int32_t> inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())};
1010 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
1011 auto inputs = builder.CreateVector(inputs_vec);
1012 auto outputs = builder.CreateVector(outputs_vec);
1013 auto options = CreateMaximumMinimumOptions(builder); // dummy option
1014 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
1015 circle::BuiltinOptions_MaximumMinimumOptions, options.Union());
1016 gd._operators.push_back(op_offset);
1017}
1018
1019void OperationExporter::visit(loco::EltwiseMul *node)
1020{
1021 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MUL);
1022 std::vector<int32_t> inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())};
1023 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
1024 auto inputs = builder.CreateVector(inputs_vec);
1025 auto outputs = builder.CreateVector(outputs_vec);
1026 auto options = CreateMulOptions(builder); // dummy option
1027 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
1028 circle::BuiltinOptions_MulOptions, options.Union());
1029 gd._operators.push_back(op_offset);
1030}
1031
1032void OperationExporter::visit(loco::EltwiseSub *node)
1033{
1034 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SUB);
1035 std::vector<int32_t> inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())};
1036 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
1037 auto inputs = builder.CreateVector(inputs_vec);
1038 auto outputs = builder.CreateVector(outputs_vec);
1039 auto options = CreateSubOptions(builder); // dummy option
1040 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
1041 circle::BuiltinOptions_SubOptions, options.Union());
1042 gd._operators.push_back(op_offset);
1043}
1044
1045void OperationExporter::visit(loco::EltwiseDiv *node)
1046{
1047 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DIV);
1048 std::vector<int32_t> inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())};
1049 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
1050 auto inputs = builder.CreateVector(inputs_vec);
1051 auto outputs = builder.CreateVector(outputs_vec);
1052 auto options = CreateDivOptions(builder); // dummy option
1053 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
1054 circle::BuiltinOptions_DivOptions, options.Union());
1055 gd._operators.push_back(op_offset);
1056}
1057
1058void OperationExporter::visit(loco::EltwiseSqrt *node)
1059{
1060 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQRT);
1061 std::vector<int32_t> inputs_vec{get_tensor_index(node->input())};
1062 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
1063 auto inputs = builder.CreateVector(inputs_vec);
1064 auto outputs = builder.CreateVector(outputs_vec);
1065 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
1066 gd._operators.push_back(op_offset);
1067}
1068
1069void OperationExporter::visit(loco::FixedReshape *node)
1070{
1071 std::vector<int32_t> new_shape_vec;
1072 for (uint32_t axis = 0; axis < node->rank(); ++axis)
1073 {
1074 assert(node->dim(axis).known());
1075 new_shape_vec.push_back(node->dim(axis).value());
1076 }
1077
1078 exportAsReshape(node, builder, new_shape_vec, gd);
1079}
1080
1081void OperationExporter::visit(loco::TensorBroadcast *)
1082{
1083 INTERNAL_EXN("loco graph has loco::TensorBroadcast, which should not exist in the graph");
1084}
1085
1086void OperationExporter::visit(loco::TensorConstantPad *node)
1087{
1088 uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_PAD);
1089
1090 // make padding attribute an input
1091 auto padding = node->padding();
1092 // get padding vector size
1093 int32_t padding_vec_size = padding->rank();
1094 // get byte size of vector
1095 size_t padding_vec_byte_size = padding_vec_size * sizeof(int32_t) * 2; // [rank, 2]
1096 // create vector for data
1097 std::vector<int32_t> padding_vec_data(padding_vec_size * 2);
1098 // set data
1099 for (int32_t i = 0; i < padding_vec_size; i++)
1100 {
1101 padding_vec_data.at(i * 2) = padding->front(i);
1102 padding_vec_data.at(i * 2 + 1) = padding->back(i);
1103 }
1104 // create FlatBuffer vector
1105 auto padding_vec_ptr = builder.CreateVector(reinterpret_cast<uint8_t *>(padding_vec_data.data()),
1106 padding_vec_byte_size);
1107
1108 // create buffer
1109 auto padding_buffer_ptr = CreateBuffer(builder, padding_vec_ptr);
1110 // get buffer id
1111 const auto padding_buffer_id = static_cast<uint32_t>(gd._buffers.size());
1112
1113 gd._buffers.push_back(padding_buffer_ptr);
1114
1115 // create padding shape vector
1116 auto padding_shape_vec_ptr = builder.CreateVector(std::vector<int32_t>{padding_vec_size, 2});
1117 // create tensor
1118 auto padding_tensor_ptr =
1119 CreateTensor(builder, padding_shape_vec_ptr, TensorType_INT32, padding_buffer_id);
1120 // get tensor id
1121 const auto padding_tensor_id = static_cast<int32_t>(gd._tensors.size());
1122
1123 gd._tensors.push_back(padding_tensor_ptr);
1124
1125 std::vector<int32_t> inputs_vec{get_tensor_index(node->input()), padding_tensor_id};
1126 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(node))};
1127 auto inputs = builder.CreateVector(inputs_vec);
1128 auto outputs = builder.CreateVector(outputs_vec);
1129 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs);
1130 gd._operators.push_back(op_offset);
1131}
1132
1134CreateCOpCallOptions(flatbuffers::FlatBufferBuilder &fbb, locoex::COpCall *copCall)
1135{
1136 // read attrs in FlexBuffer format and pass them to FlatBuffer builder
1137 flexbuffers::Builder flexbuf;
1138 {
1139 size_t map_start = flexbuf.StartMap();
1140
1141 // Note: among attrs of COpCall, 'op' and 'name' won't be included into tflite file
1142 auto names = copCall->attr_names();
1143 for (auto name : names)
1144 {
1145 if (auto int_val = copCall->attr<locoex::COpAttrType::Int>(name))
1146 flexbuf.Int(name.c_str(), int_val->val());
1147 else if (auto float_val = copCall->attr<locoex::COpAttrType::Float>(name))
1148 flexbuf.Float(name.c_str(), float_val->val());
1149 else
1150 // TODO Support more attribute types
1151 INTERNAL_EXN_V("Unsupported dtype while writing flexbuffer for customop attr", name);
1152 }
1153
1154 flexbuf.EndMap(map_start);
1155 flexbuf.Finish();
1156 }
1157
1158 auto offset = fbb.CreateVector(flexbuf.GetBuffer());
1159
1160 return offset;
1161}
1162
1163void OperationExporter::visit(locoex::COpCall *call)
1164{
1165 // Registering this custom op name into tflite Operator Codes table
1166 uint32_t op_idx = gd.registerCustomOpcode(call->op());
1167
1168 std::vector<int32_t> inputs_vec;
1169 {
1170 inputs_vec.resize(call->arity());
1171 for (uint32_t i = 0; i < call->arity(); i++)
1172 inputs_vec[i] = get_tensor_index(call->arg(i));
1173 }
1174
1175 std::vector<int32_t> outputs_vec{get_tensor_index(static_cast<loco::Node *>(call))};
1176
1177 auto inputs = builder.CreateVector(inputs_vec);
1178 auto outputs = builder.CreateVector(outputs_vec);
1179
1180 auto custom_options = CreateCOpCallOptions(builder, call);
1181 auto op_offset = CreateOperator(builder, op_idx, inputs, outputs,
1182 circle::BuiltinOptions_NONE, // builtin_options_type
1183 0, // built-in option
1184 custom_options, // custom options
1185 circle::CustomOptionsFormat_FLEXBUFFERS);
1186
1187 gd._operators.push_back(op_offset);
1188}
1189
1190void exportNode(loco::Node *node, flatbuffers::FlatBufferBuilder &builder,
1192{
1193 // TODO Use explicit tagging to prevent possible mistake
1194 auto isNoOp = [](loco::Node *node) {
1195 if (node->arity() == 1)
1196 {
1197 assert(node->arg(0) != nullptr);
1198 return get_tensor_index(node) == get_tensor_index(node->arg(0));
1199 }
1200 return false;
1201 };
1202
1203 if (isNoOp(node))
1204 {
1205 // Skip if a given node is marked as NoOp (op with no effect) before
1206 return;
1207 }
1208
1209 if (auto canonical_node = dynamic_cast<loco::CanonicalNode *>(node))
1210 { // TODO Consider removing this later
1211 OperationExporter exporter{builder, data};
1212 canonical_node->accept(&exporter);
1213 }
1214 else if (auto tfl_node = dynamic_cast<locoex::TFLNode *>(node))
1215 {
1216 OperationExporter exporter{builder, data};
1217 tfl_node->accept(&exporter);
1218 }
1219 else if (auto circle_node = dynamic_cast<locoex::CircleNode *>(node))
1220 {
1221 OperationExporter exporter{builder, data};
1222 circle_node->accept(&exporter);
1223 }
1224 else if (dynamic_cast<locoex::COpNode *>(node))
1225 {
1226 OperationExporter exporter{builder, data};
1227 exporter.visit(dynamic_cast<locoex::COpCall *>(node));
1228 }
1229 else
1230 {
1231 INTERNAL_EXN("Node with unsupported dialect found");
1232 }
1233}
1234
1235} // namespace
1236
1237namespace exo
1238{
1239namespace circle_detail
1240{
1241
1242void exportNodes(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &gd)
1243{
1244 for (auto node : loco::postorder_traversal(loco::output_nodes(g)))
1245 {
1246 exportNode(node, builder, gd);
1247 }
1248}
1249
1250} // namespace circle_detail
1251} // namespace exo
#define INTERNAL_EXN(msg)
@ brief throw internal exception with message
Definition InternalExn.h:25
#define INTERNAL_EXN_V(msg, val)
@ brief throw internal exception with message and value
Definition InternalExn.h:28
Helper class to hold data needed in creation of a FlatBuffer. To serialize data, you typically call o...
Offset< String > CreateString(const char *str, size_t len)
Store a string in the buffer, which can contain any binary data.
Offset< Vector< T > > CreateVector(const T *v, size_t len)
Serialize an array into a FlatBuffer vector.
2D Average Pooling
Definition Nodes.h:337
Convention convention(void) const
Definition Nodes.h:353
Node * ifm(void) const
Definition Nodes.h:349
const Stride< 2 > * stride(void) const
Definition Nodes.h:365
const Padding2D * pad(void) const
Definition Nodes.h:357
const Window< 2 > * window(void) const
Definition Nodes.h:361
Add Feature and Bias along "depth" axis.
Definition Nodes.h:817
Node * value(void) const
Definition Nodes.h:822
Node * bias(void) const
Definition Nodes.h:825
Add Tensor and Bias.
Definition Nodes.h:781
Produce a value of domain D from an input value (of domain D) and a bias.
Definition Nodes.h:770
Create a "Tensor" from a "Bias".
Definition Nodes.h:743
Create a "Bias" from a "Tensor".
Definition Nodes.h:758
Create a value from constant byte array.
Definition Nodes.h:218
2D Spatial Convolution
Definition Nodes.h:554
const Stride< 2 > * stride(void) const
Definition Nodes.h:567
Node * ker(void) const
Definition Nodes.h:559
const Padding2D * pad(void) const
Definition Nodes.h:563
Node * ifm(void) const
Definition Nodes.h:556
Depthwise 2D Convolution.
Definition Nodes.h:582
Node * ifm(void) const
Definition Nodes.h:584
const Stride< 2 > * stride(void) const
Definition Nodes.h:595
const Padding2D * pad(void) const
Definition Nodes.h:591
Node * ker(void) const
Definition Nodes.h:587
Create a tensor from a depthwise filter.
Definition Nodes.h:475
Create a depthwise filter from a tensor.
Definition Nodes.h:456
Node * input(void) const
Definition Nodes.h:458
uint32_t value(void) const
Return the value.
Definition Dimension.h:51
Elementwise Add lhs and rhs.
Definition Nodes.h:872
Node * rhs(void) const
Definition Nodes.h:880
Node * lhs(void) const
Definition Nodes.h:877
Elementwise Div lhs and rhs.
Definition Nodes.h:938
Node * rhs(void) const
Definition Nodes.h:946
Node * lhs(void) const
Definition Nodes.h:943
Elementwise Maximum of lhs and rhs.
Definition Nodes.h:890
Node * rhs(void) const
Definition Nodes.h:898
Node * lhs(void) const
Definition Nodes.h:895
Elementwise Mul lhs and rhs.
Definition Nodes.h:906
Node * rhs(void) const
Definition Nodes.h:914
Node * lhs(void) const
Definition Nodes.h:911
Elementwise Sqrt of input.
Definition Nodes.h:955
Node * input(void) const
Definition Nodes.h:960
Elementwise Sub lhs and rhs.
Definition Nodes.h:922
Node * rhs(void) const
Definition Nodes.h:930
Node * lhs(void) const
Definition Nodes.h:927
Create a tensor from a feature map.
Definition Nodes.h:399
FeatureDecoder * decoder(void) const
Definition Nodes.h:405
Create a feature map from a tensor.
Definition Nodes.h:380
FeatureEncoder * encoder(void) const
Definition Nodes.h:386
Feature Map Shape.
const Dimension & count(void) const
Create a tensor from a filter.
Definition Nodes.h:437
Create a filter from a tensor.
Definition Nodes.h:418
FilterEncoder * encoder(void) const
Definition Nodes.h:424
Create a new value identical to its input.
Definition Nodes.h:146
A neural network graph.
Definition Graph.h:161
Matrix Multiplication lhs and rhs.
Definition Nodes.h:1065
Create Tensor from Matrix.
Definition Nodes.h:1042
Create Matrix from Tensor.
Definition Nodes.h:1018
2D Max Pooling
Definition Nodes.h:305
const Padding2D * pad(void) const
Definition Nodes.h:311
const Stride< 2 > * stride(void) const
Definition Nodes.h:319
const Window< 2 > * window(void) const
Definition Nodes.h:315
Node * ifm(void) const
Definition Nodes.h:307
Logical unit of computation.
Definition Node.h:54
virtual Node * arg(uint32_t N) const =0
Access N-th argument node.
virtual uint32_t arity(void) const =0
Return the number of arguments.
ShapeType as(void) const
uint32_t rank(void) const
Definition PaddingND.h:42
Create a value from user data.
Definition Nodes.h:96
Make a value visible to user.
Definition Nodes.h:53
Create a new value that rectifies its input capping the units at 6.
Definition Nodes.h:172
Node * input(void) const
Definition Nodes.h:177
Create a new value that rectifies its input.
Definition Nodes.h:159
Node * input(void) const
Definition Nodes.h:164
Reshape a tensor to another tensor whose shape is known at compile time.
Definition Nodes.h:517
Computes softmax activations for Tensor domain.
Definition Nodes.h:722
Node * input(void) const
Definition Nodes.h:727
uint32_t axis(void) const
Definition Nodes.h:730
uint32_t horizontal(void) const
Definition Stride.h:40
uint32_t vertical(void) const
Definition Stride.h:36
Create a new value that rectifies its input by tanh.
Definition Nodes.h:185
Node * input(void) const
Definition Nodes.h:190
bool defined(const TensorAxis &axis) const
Duplicate elements along specified axes.
Definition Nodes.h:980
Concatenate two tensors.
Definition Nodes.h:533
uint32_t axis(void) const
Definition Nodes.h:542
Node * rhs(void) const
Definition Nodes.h:538
Node * lhs(void) const
Definition Nodes.h:535
Pads a tensor with constant value.
Definition Nodes.h:852
Node * input(void) const
Definition Nodes.h:854
const PaddingND * padding(void) const
Definition Nodes.h:861
Computes ReduceFunc operations for Tensor domain.
Definition Nodes.h:620
Node * input(void) const
Definition Nodes.h:622
ReduceFunc func(void) const
Definition Nodes.h:630
const TensorAxisSet * axes(void) const
Definition Nodes.h:626
Permute an input.
Definition Nodes.h:1090
2D Transposed Convolution
Definition Nodes.h:688
Node * ifm(void) const
Definition Nodes.h:690
Node * ker(void) const
Definition Nodes.h:693
const Stride< 2 > * stride(void) const
Definition Nodes.h:701
const Padding2D * pad(void) const
Definition Nodes.h:697
uint32_t horizontal(void) const
Definition Window.h:42
uint32_t vertical(void) const
Definition Window.h:38
Class to calls custom operation.
Definition COpCall.h:38
std::vector< std::string > attr_names() const
get all the names of attr
Definition COpCall.cpp:46
void op(const std::string &op)
Definition COpCall.h:43
void attr(const std::string &attr_name, std::unique_ptr< COpAttrData > &&attr_data)
Store [attr_name, attr_data].
Definition COpCall.cpp:38
INSTANCE_NORM in circle.
Definition CircleNodes.h:58
loco::Node * input(void) const
Definition CircleNodes.h:61
loco::Node * gamma(void) const
Definition CircleNodes.h:64
loco::Node * beta(void) const
Definition CircleNodes.h:67
loco::Node * arg(uint32_t n) const final
Definition NodeMixins.h:46
int32_t w() const
Definition TFLNodes.h:65
int32_t h() const
Definition TFLNodes.h:68
ADD in TensorFlow Lite.
Definition TFLNodes.h:116
loco::Node * y(void) const
Definition TFLNodes.h:121
loco::Node * x(void) const
Definition TFLNodes.h:118
AVERAGE_POOL_2D in TensorFlow Lite.
Definition TFLNodes.h:130
CONCATENATION in TensorFlow Lite.
Definition TFLNodes.h:160
uint32_t axis(void) const
Definition TFLNodes.h:184
Node * values(uint32_t index) const
Definition TFLNodes.h:172
uint32_t numValues(void) const
Definition TFLNodes.h:169
Class to build tensor data.
Definition TFLNodes.h:198
CONV_2D in TensorFlow Lite.
Definition TFLNodes.h:218
loco::Node * filter(void) const
Definition TFLNodes.h:223
Padding padding() const
Definition TFLNodes.h:230
const Stride * stride(void) const
Definition TFLNodes.h:233
loco::Node * bias(void) const override
Definition TFLNodes.h:226
loco::Node * input(void) const
Definition TFLNodes.h:220
DEPTHWISE_CONV_2D in TensorFlow Lite.
Definition TFLNodes.h:248
loco::Node * filter(void) const
Definition TFLNodes.h:253
loco::Node * input(void) const
Definition TFLNodes.h:250
Padding padding() const
Definition TFLNodes.h:260
int32_t depthMultiplier(void) const
Definition TFLNodes.h:266
const Stride * stride(void) const
Definition TFLNodes.h:263
loco::Node * bias(void) const override
Definition TFLNodes.h:256
DIV in TensorFlow Lite.
Definition TFLNodes.h:280
loco::Node * x(void) const
Definition TFLNodes.h:285
loco::Node * y(void) const
Definition TFLNodes.h:288
FULLY_CONNECTED in TensorFlow Lite.
Definition TFLNodes.h:298
loco::Node * bias(void) const override
Definition TFLNodes.h:306
loco::Node * weights(void) const
Definition TFLNodes.h:303
loco::Node * input(void) const
Definition TFLNodes.h:300
MAX_POOL_2D in TensorFlow Lite.
Definition TFLNodes.h:328
MAXIMUM in TensorFlow Lite.
Definition TFLNodes.h:314
loco::Node * x(void) const
Definition TFLNodes.h:316
loco::Node * y(void) const
Definition TFLNodes.h:319
loco::Node * input(void) const
Definition TFLNodes.h:356
bool keep_dims(void) const
Definition TFLNodes.h:363
loco::Node * reduction_indices(void) const
Definition TFLNodes.h:359
MUL in TensorFlow Lite.
Definition TFLNodes.h:375
loco::Node * y(void) const
Definition TFLNodes.h:380
loco::Node * x(void) const
Definition TFLNodes.h:377
loco::Node * features(void) const
Definition TFLNodes.h:400
loco::Node * features(void) const
Definition TFLNodes.h:390
loco::Node * x(void) const
Definition TFLNodes.h:453
loco::Node * x(void) const
Definition TFLNodes.h:465
loco::Node * x(void) const
Definition TFLNodes.h:476
loco::Node * y(void) const
Definition TFLNodes.h:479
SUB in TensorFlow Lite.
Definition TFLNodes.h:488
loco::Node * y(void) const
Definition TFLNodes.h:496
loco::Node * x(void) const
Definition TFLNodes.h:493
TRANSPOSE_CONV in TensorFlow Lite.
Definition TFLNodes.h:528
const Stride * stride(void) const
Definition TFLNodes.h:543
const Padding & padding(void) const
Definition TFLNodes.h:540
loco::Node * filter(void) const
Definition TFLNodes.h:533
loco::Node * inputSizes(void) const
Definition TFLNodes.h:530
loco::Node * outBackprop(void) const
Definition TFLNodes.h:536
TRANSPOSE in TensorFlow Lite.
Definition TFLNodes.h:506
loco::Node * arg(uint32_t n) const final
uint32_t arity(void) const final
#define EXO_ASSERT(condition, msg)
Definition Check.h:28
__global uchar * offset(const Image *img, int x, int y)
Definition helpers.h:540
circle::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride< 2 > *stride, const ShapeDescription &ifm, const ShapeDescription &ofm)
TFLTensorIndex get_tensor_index(loco::Node *node)
circle::ActivationFunctionType to_circle_actfunc(locoex::FusedActFunc func)
bool isNHWC(Permutation *perm)
const T * data(const std::vector< T, Alloc > &v)
std::vector< loco::Node * > postorder_traversal(const std::vector< loco::Node * > &roots)
Generate postorder traversal sequence starting from "roots".
Definition Algorithm.cpp:53
std::vector< Node * > output_nodes(Graph *)
Definition Graph.cpp:101
NodeShape shape_get(const Node *node)
uint32_t to_uint32(T a)
Definition InternalExn.h:33
name
Definition setup.py:158
std::vector< int32_t > _dims
static ShapeDescription get(loco::Node *node)
uint32_t registerBuiltinOpcode(circle::BuiltinOperator builtin_code)
if opcode is not registered in table of opcodes add it
std::vector< flatbuffers::Offset< circle::Tensor > > _tensors
std::vector< flatbuffers::Offset< circle::Operator > > _operators
std::vector< flatbuffers::Offset< circle::Buffer > > _buffers
uint32_t registerCustomOpcode(const std::string &custom_op)
virtual T visit(TFLNode *)
Default fallback.