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