ONE - On-device Neural Engine
Loading...
Searching...
No Matches
Importer.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2020 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
17#include "luci/Importer.h"
19#include "PostImport.h"
20
27
28#include <luci/IR/Module.h>
29#include <luci/IR/CircleNodes.h>
33#include <luci/Log.h>
34#include <luci/LogHelper.h>
35
36#include <oops/InternalExn.h>
37#include <oops/UserExn.h>
38
39#include <memory>
40
41namespace
42{
43
44void convert_graph(const luci::GraphBuilderSource &source, luci::CircleReader &reader,
45 loco::Graph *graph, bool &ext_buffer)
46{
47 LOGGER(l);
48
49 auto nodefinder = std::make_unique<luci::IndexNodeFinder>();
50 auto tensoroutputs = std::make_unique<luci::IndexTensorOutputs>();
51
52 luci::GraphBuilderContext gb_context(graph, &reader, nodefinder.get(), tensoroutputs.get());
53
54 const auto operators = reader.operators();
55 const auto tensors = reader.tensors();
56 assert(!tensors.null());
57 auto circle_metadata = std::make_unique<luci::CircleImportMetadata>(reader);
58
59 // build a cache to identify if a tensor is output of an operator
60 // if this is set, we should not create a CircleConst for this tensor
61 for (uint32_t i = 0; i < operators.size(); ++i)
62 {
63 const auto op = operators[i];
64 assert(op != nullptr);
65 const auto outputs = luci::wrap(op->outputs());
66
67 for (uint32_t j = 0; j < outputs.size(); ++j)
68 {
69 auto tidx = outputs[j];
70 tensoroutputs->enroll(tidx);
71 }
72 }
73
74 // graph inputs; there are no input nodes in TFlite but just Tensors
75 // creating virtual input nodes will make possible to connect nodes that uses them
76 // all attributes of tensor should be copied to CircleInput node
77 for (const auto input : reader.inputs())
78 {
79 auto input_node = graph->nodes()->create<luci::CircleInput>();
80 assert(input_node != nullptr);
81 const auto tensor = tensors[input];
82 assert(tensor != nullptr);
83
84 luci::copy_tensor_attributes(tensor, input_node);
85 if (tensor->shape() == nullptr)
87 else
89
90 INFO(l) << "[luci] NodeFinder INPUT(" << input << ") = " << input_node << std::endl;
91 nodefinder->enroll(input, input_node);
92
93 // input_node is also an output to a tensor
94 tensoroutputs->enroll(input);
95
96 // Name
97 auto graph_input = graph->inputs()->create();
98 graph_input->name(input_node->name());
99
100 // Set GraphInputOutputIndex for graph
101 input_node->index(graph_input->index());
102
103 // Data type
104 graph_input->dtype(input_node->dtype());
105
106 const auto tensor_shape_signature = luci::wrap(tensor->shape_signature());
107 const auto tensor_shape = luci::wrap(tensor->shape());
108 assert(tensor_shape_signature.size() == 0 ||
109 tensor_shape_signature.size() == tensor_shape.size());
110
111 // Shape of GraphInput
112 auto input_shape = std::make_unique<loco::TensorShape>();
113 const auto &input_dims = tensor_shape; // in NHWC
114 input_shape->rank(input_dims.size());
115 for (uint32_t r = 0; r < input_dims.size(); ++r)
116 {
117 if (tensor_shape_signature.size() > 0 && tensor_shape_signature.at(r) == -1)
118 input_shape->dim(r).unset();
119 else
120 input_shape->dim(r).set(input_dims[r]);
121 }
122 graph_input->shape(std::move(input_shape));
123 }
124
125 // Create CircleNodes for constant tensors.
126 // NOTE Origin is intentionally not provided for constants.
127 auto const_builder = source.lookup(luci::NodeBuilderType::BUFFER);
128 if (not const_builder)
129 throw oops::UserExn("Not supported", "tensor with buffer builder");
130
131 for (uint32_t i = 0; i < tensors.size(); ++i)
132 {
133 auto *const_node = const_builder->build(i, &gb_context);
134 if (const_node != nullptr)
135 nodefinder->enroll(i, const_node);
136 }
137
138 // Create CircleVariable nodes for variable tensors
139 // TODO Add Origin if needed, skip for now
140 for (uint32_t i = 0; i < tensors.size(); ++i)
141 {
142 luci::CircleVariable *variable_node = luci::create_circlevariable(&gb_context, i);
143 if (variable_node != nullptr)
144 nodefinder->enroll(i, variable_node);
145 }
146
147 // Import the operators.
148 // Note that operators in model are stored in execution order. This means that when importing
149 // an operator, its input operators have already been imported. We exploit this fact to set up
150 // node's inputs right after creating the node.
151 auto origin_table = circle_metadata->origin_table();
152 for (uint32_t i = 0; i < operators.size(); ++i)
153 {
154 const auto op = operators[i];
155 assert(op != nullptr);
156 circle::BuiltinOperator builtincode = reader.builtin_code(op);
157
158 if (const auto *builder = source.lookup(builtincode))
159 {
160 // create temporary unpack API obj
161 circle::OperatorT oper_t;
162 op->UnPackTo(&oper_t);
163
165 if (!builder->validate(args))
166 {
167 throw oops::UserExn("Invalid operator", reader.opcode_name(op));
168 }
169
170 auto built_op = builder->build(oper_t, &gb_context);
171 set_node_id(built_op, i);
172 if (origin_table.find(i) != origin_table.end())
173 add_origin(built_op, origin_table.at(i));
174 else
175 add_origin(built_op, luci::single_origin(i, built_op->name()));
176 }
177 else
178 {
179 throw oops::UserExn("Not supported", reader.opcode_name(op));
180 }
181 }
182
183 // graph outputs
184 for (auto output : reader.outputs())
185 {
186 const auto tensor = tensors[output];
187 assert(tensor != nullptr);
188
189 auto output_node = graph->nodes()->create<luci::CircleOutput>();
190 assert(output_node != nullptr);
191 auto output_from = nodefinder->node(output);
192 if (output_from != nullptr)
193 output_node->from(output_from);
194 else
195 {
196 // NOTE loco::Graph requires all input node(s) to a node should exist.
197 // Here, CircleOutput needs an input node.
198 // We add a dummy node to make it happy.
199 auto output_dummy = graph->nodes()->create<luci::CircleOutputDummy>();
200 assert(output_dummy != nullptr);
201 output_node->from(output_dummy);
202
203 luci::copy_tensor_attributes(tensor, output_dummy);
204 if (tensor->shape() == nullptr)
205 output_dummy->shape_status(luci::ShapeStatus::NOSHAPE);
206 else
207 output_dummy->shape_status(luci::ShapeStatus::VALID);
208 }
209
210 INFO(l) << "[luci] NodeFinder OUTPUT(" << output << ") = " << output_node << std::endl;
211
212 // set the graph output name and node object
213 auto graph_output = graph->outputs()->create();
214 std::string tname = luci::tensor_name(tensor);
215 assert(tname.length() > 0);
216 graph_output->name(tname);
217
218 luci::copy_tensor_attributes(tensor, output_node);
219
220 // Set GraphInputOutputIndex for graph
221 output_node->index(graph_output->index());
222
223 const auto tensor_shape_signature = luci::wrap(tensor->shape_signature());
224 const auto tensor_shape = luci::wrap(tensor->shape());
225 assert(tensor_shape_signature.size() == 0 ||
226 tensor_shape_signature.size() == tensor_shape.size());
227
228 // Shape of Output
229 auto output_shape = std::make_unique<loco::TensorShape>();
230 const auto &output_dims = tensor_shape; // in NHWC
231 output_shape->rank(output_dims.size());
232 for (uint32_t r = 0; r < output_dims.size(); ++r)
233 {
234 if (tensor_shape_signature.size() > 0 && tensor_shape_signature.at(r) == -1)
235 output_shape->dim(r).unset();
236 else
237 output_shape->dim(r).set(output_dims[r]);
238 }
239 graph_output->shape(std::move(output_shape));
240
241 // Data type
242 auto dtype = luci::luci_datatype(tensor->type());
243 graph_output->dtype(dtype);
244 }
245
246 ext_buffer = gb_context.ext_buffer();
247}
248
249class ValidateCollector final : public loco::ErrorListener
250{
251public:
253 {
254 LOGGER(l);
255 INFO(l) << "[luci] GraphValidate error " << d.node() << "(" << d.index() << ")" << std::endl;
256 }
257};
258
259} // namespace
260
261namespace luci
262{
263
265{
266 // DO NOTHING
267}
268
269std::unique_ptr<Module> Importer::importModule(const circle::Model *model) const
270{
271 assert(model);
272 assert(_file_data);
273 assert(_file_size);
274
275 auto module = make_module();
276
277 const GraphBuilderSource *source_ptr = &GraphBuilderRegistry::get();
278
279 if (_source != nullptr)
280 {
281 // Use user-defined GraphBuilderSource
282 source_ptr = _source;
283 }
284
285 CircleReader reader;
286 if (!reader.parse(model, _file_data, _file_size))
287 return nullptr;
288
289 for (uint32_t g = 0; g < reader.num_subgraph(); ++g)
290 {
291 auto graph = loco::make_graph();
292
293 if (!reader.select_subgraph(g))
294 return nullptr;
295
296 graph->name(reader.name());
297
298 // Convert circle::Model to loco::Graph
299 bool graph_ext_buffer = false;
300 convert_graph(*source_ptr, reader, graph.get(), graph_ext_buffer);
301
302 LOGGER(l);
303 VERBOSE(l, 3) << "--- graph dump begin -------------------------------------------";
304 VERBOSE(l, 3) << "Name: " << graph->name();
305 VERBOSE(l, 3) << fmt(graph.get());
306 VERBOSE(l, 3) << "--- graph dump end ---------------------------------------------";
307
308 assert(loco::valid(graph.get(), std::make_unique<ValidateCollector>()));
309
310 module->add(std::move(graph));
311
312 if (graph_ext_buffer)
313 module->ext_buffer(true);
314 }
315
316 post_import_graph(module.get(), reader);
317
318 // Initialize 'source_table'
319 auto circle_metadata = std::make_unique<luci::CircleImportMetadata>(reader);
320 if (circle_metadata->source_table().size() > 0)
321 {
322 // If there is 'source_table' metadata in circle model, copy the table.
323 module->source_table(circle_metadata->source_table());
324 }
325 else
326 {
327 // If there is no 'source_table' metadata in circle model,
328 // create new table with circle nodes.
329 std::map<uint32_t, std::string> table;
330
331 // NOTE Only first subgraph is considered
332 for (auto node : loco::all_nodes(module->graph(0)))
333 {
334 auto circle_node = loco::must_cast<luci::CircleNode *>(node);
335
336 // Virtual nodes may not have id
337 if (!has_node_id(circle_node))
338 continue;
339
340 assert(table.find(get_node_id(circle_node)) == table.end());
341 table.insert({get_node_id(circle_node), circle_node->name()});
342 }
343
344 module->source_table(table);
345 }
346
347 // Add execution_plan annotations
348 if (circle_metadata->execution_plan_table().size() > 0)
349 {
350 auto execution_plan_table = circle_metadata->execution_plan_table();
351 auto node_position = 0;
352 for (auto node : loco::postorder_traversal(loco::output_nodes(module->graph())))
353 {
354 if (auto circle_node = dynamic_cast<luci::CircleNode *>(node))
355 {
356 if (execution_plan_table.count(node_position) == 0)
357 continue;
358
359 auto node_plan = execution_plan_table[node_position];
360 assert(node_plan.size() > 0);
361
363 circle_node,
365 node_plan[0], std::vector<uint32_t>(node_plan.begin() + 1, node_plan.end())));
366 }
367 node_position++;
368 }
369 }
370
371 return module;
372}
373
374std::unique_ptr<Module> Importer::importModule(const uint8_t *data, size_t size)
375{
376 if (data == nullptr || size == 0)
377 return nullptr;
378
379 _file_data = data;
380 _file_size = size;
381
382 const circle::Model *circle_model = circle::GetModel(_file_data);
383 if (circle_model == nullptr)
384 return nullptr;
385
386 return importModule(circle_model);
387}
388
389} // namespace luci
#define LOGGER(name)
Definition Log.h:65
#define INFO(name)
Definition Log.h:68
The details of each error.
Definition Verifier.h:43
A neural network graph.
Definition Graph.h:161
uint32_t rank(void) const
Definition TensorShape.h:35
CircleNode used for Input of the Graph.
Definition CircleInput.h:36
void index(const loco::GraphInputIndex &index)
Temporary DummyNode used with dangle CircleNode.
CircleNode for Output of the Graph.
loco::Node * from(void) const
void index(const loco::GraphOutputIndex &index)
Loads Circle file and provides helpers to access attributes.
CircleOperators operators() const
CircleTensors tensors() const
circle::BuiltinOperator builtin_code(const circle::Operator *op) const
std::string opcode_name(const circle::Operator *op) const
Virtual CircleVariable in Circle for 'variable' Tensor.
Class to store context to build loco graph IR from TensorFlow.
static GraphBuilderRegistry & get()
Exception to user.
Definition UserExn.h:42
const luci_interpreter::RuntimeShape output_shape
#define VERBOSE(name, lv)
Definition Log.h:71
const T * data(const std::vector< T, Alloc > &v)
args
Definition infer.py:21
std::vector< loco::Node * > postorder_traversal(const std::vector< loco::Node * > &roots)
Generate postorder traversal sequence starting from "roots".
Definition Algorithm.cpp:53
std::set< Node * > all_nodes(Graph *)
Enumerate all the nodes in a given graph.
Definition Graph.cpp:59
bool valid(Graph *g, std::unique_ptr< ErrorListener > &&l=nullptr)
Validate a loco graph.
Definition Verifier.cpp:100
std::vector< Node * > output_nodes(Graph *)
Definition Graph.cpp:101
std::unique_ptr< Graph > make_graph(void)
Definition Graph.cpp:131
CircleNodeID get_node_id(const luci::CircleNode *circle_node)
loco::DataType luci_datatype(circle::TensorType type)
FormattedGraph fmt(loco::Graph *g)
Definition LogHelper.cpp:23
const char * tensor_name(const circle::Tensor *tensor)
void copy_tensor_attributes(const circle::Tensor *tensor, CircleNode *node)
Copy common tensor attributes such as name, type, etc. to node.
CircleVariable * create_circlevariable(GraphBuilderContext *context, int32_t tensor_index)
VectorWrapper< T > wrap(const flatbuffers::Vector< T > *vec)
void set_node_id(luci::CircleNode *circle_node, CircleNodeID id)
bool has_node_id(const luci::CircleNode *circle_node)
std::shared_ptr< CircleNodeOrigin > single_origin(uint32_t id, const std::string &name)
CircleOutput * output_node(loco::Graph *g, const loco::GraphOutputIndex &index)
Find a CircleOutput node with a given output index.
CircleInput * input_node(loco::Graph *g, const loco::GraphInputIndex &index)
Find a Pull node with a given input index.
void add_execution_plan(luci::CircleNode *circle_node, const luci::CircleNodeExecutionPlan &execution_plan)
int32_t size[5]
Definition Slice.cpp:35
Error listener (with default implementation)
Definition Verifier.h:83
void notify(const ErrorDetail< ErrorCategory::MissingArgument > &) override
Definition Verifier.h:86
NodeName name(void) const
ShapeStatus shape_status(void) const
virtual const GraphBuilderBase * lookup(const circle::BuiltinOperator &op) const =0
Returns registered GraphBuilder pointer for operator (nullptr if not present)