ONE - On-device Neural Engine
Loading...
Searching...
No Matches
ModelAnalyzer.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2018 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 "ModelAnalyzer.h"
18
19#include "mir/Shape.h"
20#include "mir/Graph.h"
21#include "mir/OpDefs.h"
22
23#include <stack>
24#include <map>
25
26using namespace std;
27
28namespace nnc
29{
30
31using namespace mir;
32using namespace sir;
33
34void ModelAnalyzer::appendOperationToInference(Operation *op, const string &function_name,
35 std::vector<size_t> aux_args)
36{
37
38 vector<size_t> node_output_tensors;
39
40 // process operation outputs
41 if (op->getType() == Operation::Type::input)
42 {
43 // register input tensor
44 const string &tensor_name = op->getOutput(0)->getName();
45 const auto tensor_id = declareInputTensor(tensor_name, op->getOutputShape(0));
46 node_output_tensors.push_back(tensor_id);
47 }
48 else if (op->getType() == Operation::Type::constant)
49 {
50 // register constant tensor
51 // it's data is deserialized to described tensor by O(1) at runtime
52 const auto tensor_id = declareTemporaryTensor();
53 node_output_tensors.push_back(tensor_id);
54 }
55 else if (op->getType() == Operation::Type::output)
56 {
57 assert(!op->getInput(0)->getName().empty());
58 }
59 else
60 {
61 for (const auto &output : op->getOutputs())
62 {
63 const auto &tensor_name = output.getName();
64 const auto tensor_id =
65 tensor_name.empty() ? declareTemporaryTensor() : declarePersistentTensor(tensor_name);
66 node_output_tensors.push_back(tensor_id);
67 }
68 }
69
70 // process operation inputs
71 vector<size_t> node_input_tensors;
72 for (const Operation::Output *input : op->getInputs())
73 {
74 size_t idx = input->getIndex();
75 const Operation *prev_op = input->getNode();
76 assert(_opToDescr.find(prev_op) != _opToDescr.end());
77 auto call = dynamic_cast<const CallFunction *>(_opToDescr[prev_op]);
78 assert(call);
79 const size_t &in_tensor_id = call->outputs[idx];
80 node_input_tensors.push_back(in_tensor_id);
81 }
82
83 std::copy(aux_args.begin(), aux_args.end(), std::back_inserter(node_input_tensors));
84 unique_ptr<Action> operation_call(new CallFunction(
85 op, function_name, std::move(node_input_tensors), std::move(node_output_tensors)));
86 _inferenceSequence.push_back(std::move(operation_call));
87 _opToDescr[op] = _inferenceSequence.back().get();
88}
89
90void ModelAnalyzer::updateMaxTemporarySize(const size_t size)
91{
92 _max_temp_size = std::max(_max_temp_size, size);
93}
94
95size_t ModelAnalyzer::declareInputTensor(const std::string &name, const mir::Shape &shape)
96{
97 assert(!name.empty() && "Input tensor must have name");
98 size_t id = _allocatedTensors++;
99 _tensors.push_back({id, TensorDescriptor::Type::input, name, shape});
100 _inputs.push_back(id);
101 return id;
102}
103
104size_t ModelAnalyzer::declarePersistentTensor(const std::string &name)
105{
106 assert(!name.empty());
107 size_t id = _allocatedTensors++;
108 _tensors.push_back({id, TensorDescriptor::Type::persistent, name, {}});
109 _persistent_tensors.push_back(id);
110 return id;
111}
112
113size_t ModelAnalyzer::declareTemporaryTensor()
114{
115 size_t id = _allocatedTensors++;
116 _tensors.push_back({id, TensorDescriptor::Type::temporary, "", {}});
117 return id;
118}
119
120void ModelAnalyzer::gatherDefUseInfo(const vector<unique_ptr<Action>> &post_order,
121 map<size_t, size_t> &first_def, map<size_t, size_t> &last_use)
122{
123
124 for (size_t pos = 0; pos < post_order.size(); ++pos)
125 {
126 const unique_ptr<Action> &action = post_order[pos];
127 const CallFunction *call = dynamic_cast<CallFunction *>(action.get());
128 assert(call);
129
130 // update def info
131 for (size_t output_tensor_id : call->outputs)
132 {
133 const TensorDescriptor &td = _tensors[output_tensor_id];
134 if (td.type != TensorDescriptor::Type::temporary)
135 continue;
136
137 if (!first_def.count(output_tensor_id))
138 first_def[output_tensor_id] = pos;
139 }
140
141 // update usage info
142 for (size_t input_tensor_id : call->inputs)
143 {
144 const TensorDescriptor &td = _tensors[input_tensor_id];
145 if (td.type != TensorDescriptor::Type::temporary)
146 continue;
147
148 last_use[input_tensor_id] = pos;
149 }
150 }
151}
152
153void ModelAnalyzer::constructInferenceSequence(const vector<Operation *> &post_order)
154{
155 // Run inference sequence construction over constructed list of operations
156 for (auto it = post_order.rbegin(); it != post_order.rend(); ++it)
157 {
158 Operation *node = *it;
159 node->accept(this);
160 }
161
162 // Insert temporary tensor constructors
163 // map temporary tensor id to index in original sequence where it was defined/used first/last time
164 map<size_t, size_t> first_def;
165 map<size_t, size_t> last_use;
166
167 // prepare use-def info
168 gatherDefUseInfo(_inferenceSequence, first_def, last_use);
169
170 // insert memory operations
171 // Every iteration of loop contains three steps:
172 // 1) insert constructors of temporary tensors used in current operations
173 // and not used in inference sequence before
174 // 2) insert operation call
175 // 3) insert destructors of temporary tensors unused after current operation
176 std::vector<unique_ptr<Action>> old_inference_seq;
177 old_inference_seq.swap(_inferenceSequence);
178 _inferenceSequence.reserve(old_inference_seq.size());
179
180 for (size_t pos = 0; pos < old_inference_seq.size(); ++pos)
181 {
182 unique_ptr<Action> &action = old_inference_seq[pos];
183 const CallFunction *call = dynamic_cast<CallFunction *>(action.get());
184 assert(call);
185
186 // construct required temporary tensors
187 for (size_t output_tensor_id : call->outputs)
188 {
189 const TensorDescriptor &td = _tensors[output_tensor_id];
190 assert(td.id == output_tensor_id);
191 if (td.type != TensorDescriptor::Type::temporary)
192 continue;
193
194 if (first_def[output_tensor_id] == pos)
195 {
196 unique_ptr<Action> tmp_constructor(new CreateTmp(output_tensor_id));
197 _inferenceSequence.push_back(std::move(tmp_constructor));
198 }
199 }
200
201 // Insert operation call
202 _inferenceSequence.push_back(std::move(action));
203
204 // destroy unused temporary tensors
205 for (size_t input_tensor_id : call->inputs)
206 {
207 const TensorDescriptor &td = _tensors[input_tensor_id];
208 assert(td.id == input_tensor_id);
209 if (td.type != TensorDescriptor::Type::temporary)
210 continue;
211
212 if (last_use[input_tensor_id] == pos)
213 {
214 unique_ptr<Action> tmp_destructor(new DestroyTmp(input_tensor_id));
215 _inferenceSequence.push_back(std::move(tmp_destructor));
216 }
217 }
218 }
219}
220
221void ModelAnalyzer::collectOutputs(const mir::Graph *g)
222{
223 for (ops::OutputOp *out_op : g->getOutputs())
224 {
225 auto op_call = dynamic_cast<const CallFunction *>(_opToDescr[out_op]);
226 assert(op_call->inputs.size() == 1);
227 _outputs.push_back(op_call->inputs[0]);
228 }
229}
230
232{
233 // Current path through graph
234 stack<pair<Operation *, size_t>> s;
235 // Nodes in Reverse Post Order stored by DFS
236 vector<Operation *> post_order;
237 // Set contains pointer to node if it is visited by DFS
238 set<Operation *> visited;
239
240 vector<Operation *> init_ops;
241 for (Operation *op : g->getNodes())
242 {
243 if (op->getNumInputs() == 0)
244 {
245 init_ops.emplace_back(op);
246 }
247 }
248
249 // Register temporary tensor for im2col buffer
250 _temp_tensor_id = declareTemporaryTensor();
251
252 // Walk all network inputs
253 for (Operation *in : init_ops)
254 {
255 if (!visited.count(in))
256 {
257 visited.insert(in);
258 s.push({in, 0});
259 }
260
261 // main DFS loop
262 while (!s.empty())
263 {
264 // top stores current node and current outgoing edge from it
265 auto &top = s.top();
266 Operation *node = top.first;
267 auto edge = top.second++;
268 // FIXME Refactor me.
269 std::vector<Operation *> next_nodes;
270 for (const auto &out : node->getOutputs())
271 {
272 const auto &uses = out.getUses();
273 std::transform(uses.begin(), uses.end(), std::back_inserter(next_nodes),
274 [](Operation::Use use) { return use.getNode(); });
275 }
276 if (edge == next_nodes.size())
277 {
278 // this node is fully analyzed, push it into RPO and pop from stack
279 post_order.push_back(node);
280 s.pop();
281 }
282 else
283 {
284 // Search current outgoing edge
285 Operation *successor = next_nodes[edge];
286 if (!visited.count(successor))
287 {
288 visited.insert(successor);
289 s.push({next_nodes[edge], 0});
290 }
291 }
292 }
293 }
294
295 constructInferenceSequence(post_order);
296
297 collectOutputs(g);
298}
299
300void ModelAnalyzer::visit(ops::ConcatOp &op) { appendOperationToInference(&op, "concat"); }
301
303{
304 assert(op.getNumGroups() == 1);
305 const auto &kernel_shape = op.getInputShape(1);
306 const auto &out_shape = op.getOutputShape(0);
307 const int32_t tmp_size = kernel_shape.dim(1) * kernel_shape.dim(2) * kernel_shape.dim(3) *
308 out_shape.dim(0) * out_shape.dim(1) * out_shape.dim(2);
309 updateMaxTemporarySize(static_cast<size_t>(tmp_size));
310 appendOperationToInference(&op, "conv2d", {_temp_tensor_id});
311}
312
314{
315 appendOperationToInference(&op, "depthwiseConv2d");
316}
317
318void ModelAnalyzer::visit(ops::SoftmaxOp &op) { appendOperationToInference(&op, "softmax"); }
319
320void ModelAnalyzer::visit(ops::AvgPool2DOp &op) { appendOperationToInference(&op, "avgPool"); }
321
322void ModelAnalyzer::visit(ops::MaxPool2DOp &op) { appendOperationToInference(&op, "maxPool"); }
323
325{
326 appendOperationToInference(&op, "fullConnect");
327}
328
329void ModelAnalyzer::visit(ops::BroadcastOp &op) { appendOperationToInference(&op, "broadcast"); }
330
331void ModelAnalyzer::visit(ops::CappedReluOp &op) { appendOperationToInference(&op, "cappedRelu"); }
332
334{
335 assert(op.getNumInputs() == 0);
336 appendOperationToInference(&op, "in");
337}
338
340{
341 assert(op.getNumInputs() == 0);
342
343 // FIXME This is to work around deserializeTensors not being able to deserialize tensors of type
344 // other than float32.
345 const auto *output = op.getOutput(0);
346 if (output->getUses().empty())
347 return;
348
349 appendOperationToInference(&op, "constant");
350}
351
352void ModelAnalyzer::visit(ops::ReluOp &op) { appendOperationToInference(&op, "relu"); }
353
354void ModelAnalyzer::visit(ops::ReshapeOp &op) { appendOperationToInference(&op, "reshape"); }
355
357{
358 const auto &in_shape = op.getInputShape(0);
359 const auto &out_shape = op.getOutputShape(0);
360
361 assert(in_shape.rank() == 4);
362 assert(in_shape.rank() == out_shape.rank());
363
364 if (in_shape.dim(0) != out_shape.dim(0) || in_shape.dim(3) != out_shape.dim(3))
365 throw std::runtime_error("Not supported Resize on other dims besides height and width!");
366
367 switch (op.getMode())
368 {
370 appendOperationToInference(&op, "resize");
371 break;
372 default:
373 assert(false && "Not Implemented!");
374 }
375}
376
377void ModelAnalyzer::visit(mir::ops::SliceOp &op) { appendOperationToInference(&op, "slice"); }
378
380{
381 appendOperationToInference(&op, "tanhActivation");
382}
383
384void ModelAnalyzer::visit(mir::ops::EluOp &op) { appendOperationToInference(&op, "elu"); }
385
387{
388 const auto &kernel_shape = op.getInputShape(1);
389 const auto &out_shape = op.getOutputShape(0);
390 const int32_t tmp_size = kernel_shape.dim(0) * kernel_shape.dim(1) * kernel_shape.dim(3) *
391 out_shape.dim(0) * out_shape.dim(1) * out_shape.dim(2);
392 updateMaxTemporarySize(static_cast<size_t>(tmp_size));
393 appendOperationToInference(&op, "convTransposed2d", {_temp_tensor_id});
394}
395
396void ModelAnalyzer::visit(ops::SqueezeOp &op) { appendOperationToInference(&op, "reshape"); }
397
398void ModelAnalyzer::visit(ops::SqrtOp &op) { appendOperationToInference(&op, "sqrtFN"); }
399
400void ModelAnalyzer::visit(mir::ops::PadOp &op) { appendOperationToInference(&op, "pad"); }
401
403{
404 appendOperationToInference(&op, "reduceMean");
405}
406
408{
409 appendOperationToInference(&op, "transpose");
410}
411
412void ModelAnalyzer::visit(mir::ops::GatherOp &op) { appendOperationToInference(&op, "gather"); }
413
414void ModelAnalyzer::visit(mir::ops::SigmoidOp &op) { appendOperationToInference(&op, "sigmoid"); }
415
417{
418 appendOperationToInference(&op, "leakyRelu");
419}
420
421void ModelAnalyzer::visit(mir::ops::OutputOp &op) { appendOperationToInference(&op, "out"); }
422
423void ModelAnalyzer::visit(mir::ops::AbsOp &op) { appendOperationToInference(&op, "absFN"); }
424
426{
427 appendOperationToInference(&op, "ElementWise<Add>");
428}
429
431{
432 appendOperationToInference(&op, "ElementWise<Div>");
433}
434
436{
437 appendOperationToInference(&op, "ElementWise<Max>");
438}
439
441{
442 appendOperationToInference(&op, "ElementWise<Mul>");
443}
444
446{
447 appendOperationToInference(&op, "ElementWise<Sub>");
448}
449
450void ModelAnalyzer::visit_fallback(mir::Operation &) { throw std::runtime_error("NYI operation"); }
451
452} // namespace nnc
std::size_t getNumInputs() const
Definition Operation.h:128
std::deque< Output > & getOutputs()
Definition Operation.h:134
Output * getOutput(std::size_t index)
Definition Operation.h:149
const Shape & getInputShape(std::size_t index) const
Definition Operation.h:161
const Shape & getOutputShape(std::size_t index) const
Definition Operation.h:163
int32_t & dim(int32_t axis) noexcept
Definition Shape.h:47
Description of tensor concatenation operation.
Definition ConcatOp.h:31
std::int32_t getNumGroups() const
Definition Conv2DOp.h:58
Gather operation as defined by ONNX spec. https://github.com/onnx/onnx/blob/master/docs/Operators....
Definition GatherOp.h:33
Resize operation scales are such that output = input * scale for each dimension and the number of dim...
Definition ResizeOp.h:35
ResizeMethod getMode() const
Definition ResizeOp.h:75
description of softmax operation.
Definition SoftmaxOp.h:31
Tensor transpose operation.
Definition TransposeOp.h:34
void visit(mir::ops::AbsOp &) override
const std::vector< size_t > & getOutputs() const
void visit_fallback(mir::Operation &op) override
const std::vector< size_t > & getInputs() const
void analyze(const mir::Graph *g)
contructs inference sequence
const char * tensor_name(const circle::Tensor *tensor)
int32_t size[5]
Definition Slice.cpp:35