2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include "CircleTensorExporter.h"
18 #include "TypeBridge.h"
20 #include <luci/IR/CircleNodes.h>
21 #include <luci/IR/CircleNodeVisitor.h>
22 #include <luci/IR/CircleShapeSignature.h>
23 #include <luci/Service/CircleTypeInference.h>
24 #include <luci/Service/CircleShapeInference.h>
27 #include <loco/IR/Algorithm.h>
28 #include <loco/IR/CanonicalNode.h>
29 #include <loco/IR/CanonicalNodeVisitor.h>
30 #include <loco/IR/DataTypeTraits.h>
31 #include <oops/InternalExn.h>
33 using namespace circle;
34 using namespace flatbuffers;
44 CircleTensoInfo() = default;
47 void name(const std::string &name) { _name = name; }
48 const std::string &name(void) const { return _name; }
51 const circle::TensorType &dtype(void) const { return _dtype; }
52 void dtype(const circle::TensorType &dtype) { _dtype = dtype; }
54 const ShapeDescription &shape(void) const { return _shape; }
55 void shape(const ShapeDescription &shape) { _shape = shape; }
57 const ShapeSignature &shape_signature(void) const { return _shape_signature; }
58 void shape_signature(const ShapeSignature &ss) { _shape_signature = ss; }
60 luci::ShapeStatus shape_status(void) const { return _shape_status; }
61 void shape_status(luci::ShapeStatus ss) { _shape_status = ss; }
64 luci::CircleConst *content(void) const { return _content; }
65 void content(luci::CircleConst *c) { _content = c; }
67 luci::CircleQuantParam *quantparam(void) const { return _quantparam; }
68 void quantparam(luci::CircleQuantParam *qp) { _quantparam = qp; }
70 luci::SparsityParam *sparsityparam(void) const { return _sparsityparam; }
71 void sparsityparam(luci::SparsityParam *sp) { _sparsityparam = sp; }
76 circle::TensorType _dtype{circle::TensorType_FLOAT32};
77 ShapeDescription _shape{};
78 ShapeSignature _shape_signature;
79 luci::ShapeStatus _shape_status{luci::ShapeStatus::UNDEFINED};
81 luci::CircleConst *_content = nullptr;
82 luci::CircleQuantParam *_quantparam = nullptr;
83 luci::SparsityParam *_sparsityparam = nullptr;
86 using CircleTensorContext = std::vector<CircleTensoInfo>;
88 struct NoOpDetector final : public luci::CircleNodeMutableVisitor<bool>
90 // Input is Virtual but does produce a Tensor
91 // Output is Virtual that does not produce any Tensor
92 bool visit(luci::CircleOutput *) final { return true; }
93 bool visit(luci::CircleOutputExclude *) final { return true; }
95 // Return false by default
96 bool visit(luci::CircleNode *) final { return false; }
99 void allocateCircleTensorInfo(CircleNode *node, CircleTensorContext &ctx)
103 auto tensor_index = static_cast<CircleTensorIndex>(ctx.size());
104 // TODO Use Graph-level metadata for Input & Output
105 // auto tensor_name = "t_" + std::to_string(tensor_index);
106 std::string tensor_name = node->name();
107 if (tensor_name.empty())
108 tensor_name = "t_" + std::to_string(tensor_index);
109 INFO(l) << "[luci] Tensor for " << tensor_name << ": " << tensor_index << std::endl;
111 CircleTensoInfo tensor_info;
113 tensor_info.name(tensor_name);
114 tensor_info.dtype(to_circle_tensortype(luci::node_dtype(node)));
115 tensor_info.shape_signature(node->shape_signature());
116 if (node->shape_status() == ShapeStatus::VALID)
117 tensor_info.shape(to_shape_description(luci::node_shape(node)));
118 tensor_info.shape_status(node->shape_status());
120 tensor_info.content(dynamic_cast<luci::CircleConst *>(node));
121 tensor_info.quantparam(node->quantparam());
122 tensor_info.sparsityparam(node->sparsityparam());
124 set_tensor_index(node, tensor_index);
126 ctx.emplace_back(tensor_info);
129 class MultiOutputDetector final : public luci::CircleNodeMutableVisitor<bool>
132 MultiOutputDetector(CircleTensorContext &ctx) : _ctx(ctx) {}
135 void store_outputs(luci::CircleNode *node, uint32_t count)
137 auto outs = loco::succs(node);
138 assert(outs.size() == count);
139 (void)count; // for unused variable error in release build
140 for (auto out : outs)
142 auto circle_out = loco::must_cast<luci::CircleNode *>(out);
143 allocateCircleTensorInfo(circle_out, _ctx);
145 set_tensor_index(node, -1);
149 bool visit(luci::CircleIfOut *) final { return true; }
150 bool visit(luci::CircleSplitOut *) final { return true; }
151 bool visit(luci::CircleSplitVOut *) final { return true; }
152 bool visit(luci::CircleTopKV2Out *) final { return true; }
153 bool visit(luci::CircleUnpackOut *) final { return true; }
154 bool visit(luci::CircleWhileOut *) final { return true; }
156 bool visit(luci::CircleIf *node) final
158 store_outputs(node, node->output_count());
162 bool visit(luci::CircleSplit *node) final
164 store_outputs(node, uint32_t(node->num_split()));
168 bool visit(luci::CircleSplitV *node) final
170 store_outputs(node, uint32_t(node->num_split()));
174 bool visit(luci::CircleTopKV2 *node) final
176 store_outputs(node, 2);
180 bool visit(luci::CircleUnpack *node) final
182 store_outputs(node, node->num());
186 bool visit(luci::CircleWhile *node) final
188 store_outputs(node, node->output_count());
192 // Return false by default
193 bool visit(luci::CircleNode *) final { return false; }
196 CircleTensorContext &_ctx;
199 void allocateCircleTensor(CircleNode *node, CircleTensorContext &ctx)
202 throw std::runtime_error("allocateCIrcleTensor Failed : node is nullptr");
204 auto isNoOp = [](loco::Node *node) {
205 if (auto circle_node = dynamic_cast<luci::CircleNode *>(node))
208 return circle_node->accept(&d);
215 set_tensor_index(node, -1);
219 // TODO revise this when loco supports multiple outputs
220 // NOTE this will store all virtual output tensors and skip for the real node
221 if (auto circle_node = dynamic_cast<luci::CircleNode *>(node))
223 MultiOutputDetector d(ctx);
224 if (circle_node->accept(&d))
228 allocateCircleTensorInfo(node, ctx);
236 flatbuffers::Offset<Vector<int32_t>> encodeShape(FlatBufferBuilder &builder,
237 const ShapeDescription &shape)
239 assert(shape._rank_known && "unknown number of dimensions is not supported");
240 return builder.CreateVector(shape._dims);
243 flatbuffers::Offset<Vector<int32_t>> encodeShapeSignature(FlatBufferBuilder &builder,
244 const ShapeSignature &shape_signature)
246 return builder.CreateVector(shape_signature.as_vector());
249 flatbuffers::Offset<circle::Buffer> encodeOpBuffer(FlatBufferBuilder &builder)
251 return CreateBuffer(builder);
254 template <typename NodeT>
255 flatbuffers::Offset<circle::Buffer> encodeOpBuffer(FlatBufferBuilder &builder, NodeT *)
257 return CreateBuffer(builder);
260 template <loco::DataType DT>
261 flatbuffers::Offset<circle::Buffer> encodeOpBufferByDType(FlatBufferBuilder &builder,
262 luci::CircleConst *c)
264 using NativeType = typename loco::DataTypeImpl<DT>::Type;
266 std::vector<NativeType> raw_data;
267 const uint32_t size = c->size<DT>();
268 raw_data.reserve(size);
269 for (uint32_t i = 0; i < size; ++i)
271 raw_data.push_back(c->at<DT>(i));
273 const size_t raw_size = size * sizeof(NativeType);
274 auto array_offset = builder.CreateVector(reinterpret_cast<uint8_t *>(raw_data.data()), raw_size);
275 return CreateBuffer(builder, array_offset);
279 flatbuffers::Offset<circle::Buffer> encodeOpBuffer(FlatBufferBuilder &builder, luci::CircleConst *c)
283 case loco::DataType::FLOAT32:
284 return encodeOpBufferByDType<loco::DataType::FLOAT32>(builder, c);
285 case loco::DataType::S8:
286 return encodeOpBufferByDType<loco::DataType::S8>(builder, c);
287 case loco::DataType::S16:
288 return encodeOpBufferByDType<loco::DataType::S16>(builder, c);
289 case loco::DataType::S32:
290 return encodeOpBufferByDType<loco::DataType::S32>(builder, c);
291 case loco::DataType::S64:
292 return encodeOpBufferByDType<loco::DataType::S64>(builder, c);
293 case loco::DataType::U8:
294 return encodeOpBufferByDType<loco::DataType::U8>(builder, c);
295 case loco::DataType::BOOL:
296 return encodeOpBufferByDType<loco::DataType::BOOL>(builder, c);
301 INTERNAL_EXN_V("Unsupported datatype", oops::to_uint32(c->dtype()));
304 flatbuffers::Offset<circle::QuantizationParameters>
305 encodeQuantizationParameters(FlatBufferBuilder &builder, luci::CircleQuantParam *quantparam)
307 if (quantparam == nullptr)
310 flatbuffers::Offset<flatbuffers::Vector<float>> min;
311 flatbuffers::Offset<flatbuffers::Vector<float>> max;
312 flatbuffers::Offset<flatbuffers::Vector<float>> scale;
313 flatbuffers::Offset<flatbuffers::Vector<int64_t>> zero_point;
314 if (quantparam->min.size() && quantparam->max.size())
316 min = builder.CreateVector(quantparam->min);
317 max = builder.CreateVector(quantparam->max);
319 if (quantparam->scale.size() && quantparam->zerop.size())
321 scale = builder.CreateVector(quantparam->scale);
322 zero_point = builder.CreateVector(quantparam->zerop);
324 // Note: QuantizationDetails is not supported
325 return circle::CreateQuantizationParameters(builder, min, max, scale, zero_point,
326 circle::QuantizationDetails::QuantizationDetails_NONE,
327 0, quantparam->quantized_dimension);
330 flatbuffers::Offset<circle::SparsityParameters>
331 encodeSparsityParameters(FlatBufferBuilder &builder, luci::SparsityParam *sparsityparam)
333 if (sparsityparam == nullptr)
336 std::vector<flatbuffers::Offset<circle::DimensionMetadata>> dim_metadata_vec;
337 auto luci_dim_metadata = sparsityparam->dim_metadata;
338 for (auto it : luci_dim_metadata)
341 auto circle_array_segments = to_circle_sparse_index_vector(builder, it.array_segments());
342 auto circle_array_segments_type =
343 to_circle_sparse_index_vector_type(it.array_segments().type());
346 auto circle_array_indices = to_circle_sparse_index_vector(builder, it.array_indices());
347 auto circle_array_indices_type = to_circle_sparse_index_vector_type(it.array_indices().type());
348 auto dim_metadata = circle::CreateDimensionMetadata(
349 builder, to_circle_dimensiontype(it.format()), it.dense_size(), circle_array_segments_type,
350 circle_array_segments, circle_array_indices_type, circle_array_indices);
351 dim_metadata_vec.emplace_back(dim_metadata);
354 return circle::CreateSparsityParametersDirect(builder, &sparsityparam->traversal_order,
355 &sparsityparam->block_map, &dim_metadata_vec);
358 bool has_same_values(luci::CircleConst *lhs, luci::CircleConst *rhs)
360 if (lhs->dtype() != rhs->dtype())
363 if (lhs->rank() != rhs->rank())
366 for (uint32_t i = 0; i < lhs->rank(); ++i)
367 if (!(lhs->dim(i) == rhs->dim(i)))
370 switch (lhs->dtype())
372 case loco::DataType::FLOAT32:
373 for (uint32_t i = 0; i < lhs->size<loco::DataType::FLOAT32>(); ++i)
374 if (lhs->at<loco::DataType::FLOAT32>(i) != rhs->at<loco::DataType::FLOAT32>(i))
378 case loco::DataType::S32:
379 for (uint32_t i = 0; i < lhs->size<loco::DataType::S32>(); ++i)
380 if (lhs->at<loco::DataType::S32>(i) != rhs->at<loco::DataType::S32>(i))
384 case loco::DataType::S64:
385 for (uint32_t i = 0; i < lhs->size<loco::DataType::S64>(); ++i)
386 if (lhs->at<loco::DataType::S64>(i) != rhs->at<loco::DataType::S64>(i))
390 case loco::DataType::BOOL:
391 for (uint32_t i = 0; i < lhs->size<loco::DataType::BOOL>(); ++i)
392 if (lhs->at<loco::DataType::BOOL>(i) != rhs->at<loco::DataType::BOOL>(i))
403 uint32_t get_buffer_id(FlatBufferBuilder &builder, SerializedModelData &md, luci::CircleConst *node)
407 // When buffer with same values is found, use the buffer id.
408 for (auto key_value : md._cached_buffer_id)
410 if (has_same_values(key_value.first, node))
411 return key_value.second;
414 // When buffer with same values is not found, generate new buffer
415 auto buffer = encodeOpBuffer(builder, node);
417 auto buffer_id = static_cast<uint32_t>(md._buffers.size());
418 md._buffers.push_back(buffer);
420 // Cache the newly generated buffer id
421 md._cached_buffer_id.insert({node, buffer_id});
427 // When there is no CircleConst, the operation do not use buffer.
428 // So return buffer id as 0 which means empty buffer in circle schema.
433 void exportOpDefinedTensor(const CircleTensoInfo &info, FlatBufferBuilder &builder,
434 SerializedModelData &md, SerializedGraphData &gd)
436 // Create and register output tensor shape
437 flatbuffers::Offset<Vector<int32_t>> shape_offset;
438 if (info.shape_status() == ShapeStatus::VALID)
439 shape_offset = encodeShape(builder, info.shape());
441 auto quantparam = encodeQuantizationParameters(builder, info.quantparam());
443 auto sparsityparam = encodeSparsityParameters(builder, info.sparsityparam());
445 auto shape_signature_offset = encodeShapeSignature(builder, info.shape_signature());
447 auto buffer_id = get_buffer_id(builder, md, info.content());
449 auto name_offset = builder.CreateString(info.name());
451 CreateTensor(builder, shape_offset, info.dtype(), buffer_id, name_offset, quantparam,
452 /*is_variable*/ false, sparsityparam, shape_signature_offset);
453 gd._tensors.push_back(tensor_offset);
461 void prepareModelData(FlatBufferBuilder &builder, SerializedModelData &md)
463 // add one empty buffer
464 // note: this follows TFLite
465 // note: there's a comment in tflite fbs file
466 // - Note the 0th entry of this array must be an empty buffer (sentinel).
467 // - This is a convention so that tensors without a buffer can provide 0 as
469 auto buffer = encodeOpBuffer(builder);
470 md._buffers.push_back(buffer);
473 void exportOpDefinedTensors(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &md,
474 SerializedGraphData &gd)
476 CircleTensorContext tensor_ctx;
478 // NOTE There may exist dangle CircleInput that is not visited with postorder_traversal()
479 // All dangle CircleOutput should be visited by postorder_traversal()
480 auto nodes = g->nodes();
481 for (uint32_t n = 0; n < nodes->size(); ++n)
483 auto node = dynamic_cast<luci::CircleInput *>(nodes->at(n));
485 allocateCircleTensor(node, tensor_ctx);
488 for (auto node : loco::postorder_traversal(loco::output_nodes(g)))
490 CircleNode *circle_node = loco::must_cast<luci::CircleNode *>(node);
491 if (dynamic_cast<const luci::CircleInput *>(circle_node) != nullptr)
493 allocateCircleTensor(circle_node, tensor_ctx);
496 for (const auto &tensor_info : tensor_ctx)
498 exportOpDefinedTensor(tensor_info, builder, md, gd);