blob: 0c1af03af4598460317c7a1e054ca8ff1db3d73e [file] [log] [blame]
telsoa01c577f2c2018-08-31 09:22:23 +01001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
David Beckecb56cd2018-09-05 12:52:57 +01003// SPDX-License-Identifier: MIT
telsoa01c577f2c2018-08-31 09:22:23 +01004//
5#include "OnnxParser.hpp"
6
Matthew Bentham39ef3e52020-01-20 10:09:09 +00007#include <armnn/Descriptors.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +01008#include <armnn/Utils.hpp>
9#include <VerificationHelpers.hpp>
10
Aron Virginas-Tard4f0fea2019-04-09 14:08:06 +010011#include <boost/format.hpp>
12#include <boost/numeric/conversion/cast.hpp>
13
telsoa01c577f2c2018-08-31 09:22:23 +010014#include <google/protobuf/text_format.h>
15#include <google/protobuf/io/zero_copy_stream_impl.h>
16
telsoa01c577f2c2018-08-31 09:22:23 +010017#include <numeric>
18
19using namespace armnn;
20
21namespace armnnOnnxParser
22{
23namespace
24{
25void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
26 const onnx::TensorProto::DataType actualValue,
27 const char* validExpr,
28 std::string nodeName,
29 std::string tensorName,
30 const armnn::CheckLocation& location)
31{
32 bool isValid = std::any_of(validInputTypes.begin(),
33 validInputTypes.end(),
34 [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
35 if (!isValid)
36 {
37 throw ParseException(
38 boost::str(
39 boost::format("Datatype %1% is not valid for tensor '%2%' of node '%3%', not in {%4%}. %5%") %
40 onnx::TensorProto::DataType_Name(actualValue) %
41 tensorName %
42 nodeName %
43 validExpr %
44 location.AsString()));
45 }
46}
47
48#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
49CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
50
51using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
52#define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
53
54template <typename Callable>
55void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
56 const std::string& attribName,
57 onnx::AttributeProto::AttributeType expectedType,
58 Callable callable)
59{
60 auto attribs = node.attribute();
61 int attriNum = 0;
62 while (attriNum < node.attribute_size())
63 {
64 if (attribs.Get(attriNum).name() == attribName)
65 {
66 if (attribs.Get(attriNum).type() == expectedType)
67 {
68 callable(attribs.Get(attriNum));
69 }
70 else
71 {
72 throw ParseException(boost::str(boost::format(
73 "Attribute %1% of node %2% expected to have %3% as onnx::AttributeProto::AttributeType, "
74 "but found %4% instead %5%")
75 % attribName
76 % node.name()
77 % onnx::AttributeProto::AttributeType_Name(expectedType)
78 % onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type())
79 % CHECK_LOCATION().AsString()));
80 }
81 break;
82 }
83 ++attriNum;
84 }
85 if (attriNum == node.attribute_size())
86 {
87 throw ParseException(boost::str(boost::format("Could not find required attribute %1% in node %2% %3%")
88 % attribName % node.name() % CHECK_LOCATION().AsString()));
89 }
90}
91
92template <typename Callable>
93void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
94 const std::string& attribName,
95 onnx::AttributeProto::AttributeType expectedType,
96 Callable callable)
97{
98 auto attribs = node.attribute();
99 for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
100 {
101 if (attribs.Get(attriNum).name() == attribName)
102 {
103 if (attribs.Get(attriNum).type() == expectedType)
104 {
105 callable(attribs.Get(attriNum));
106 }
107 else
108 {
109 throw ParseException(boost::str(boost::format(
110 "Attribute %1% of node %2% expected to have %3% as onnx::AttributeProto::AttributeType, "
111 "but found %4% instead %5%")
112 % attribName
113 % node.name()
114 % onnx::AttributeProto::AttributeType_Name(expectedType)
115 % onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type())
116 % CHECK_LOCATION().AsString()));
117 }
118 }
119 }
120}
121
122std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
123 const std::string& name)
124{
125 std::vector<uint32_t> attriList;
126 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
127 [&attriList](const onnx::AttributeProto& attrValue)
128 {
129 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
130 {
131 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
132 }
133 });
134 return attriList;
135}
136
137uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
138 const std::string& name,
139 const uint32_t defaultVal = 0u)
140{
141 uint32_t attribValue = defaultVal;
142 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
143 [&attribValue](const onnx::AttributeProto& attrValue)
144 {
145 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
146 });
147 return attribValue;
148}
149
150std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
151 const std::string& name)
152{
153 std::vector<uint32_t> attriList;
154 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
155 [&attriList](const onnx::AttributeProto& attrValue)
156 {
157 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
158 {
159 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
160 }
161 });
162
163 return attriList;
164}
165
166float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
167 const std::string& name,
168 const float defaultValue = 0.0f)
169{
170 float attribValue = defaultValue;
171 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
172 [&attribValue](const onnx::AttributeProto& attrValue)
173 {
174 attribValue = attrValue.f();
175 });
176 return attribValue;
177}
178
179std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
180{
181 std::string attribValue = "";
182 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
183 [&attribValue](const onnx::AttributeProto& attrValue)
184 {
185 attribValue = attrValue.s();
186 });
187 return attribValue;
188}
189
Tee Jungfcf6fd52019-11-01 05:27:28 +0000190armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100191{
telsoa01c577f2c2018-08-31 09:22:23 +0100192 DataType type;
Tee Jungfcf6fd52019-11-01 05:27:28 +0000193 switch(data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100194 {
195 case onnx::TensorProto::FLOAT:
196 {
197 type = DataType::Float32;
198 break;
199 }
200 case onnx::TensorProto::INT32:
201 case onnx::TensorProto::INT64:
202 {
203 type = DataType::Signed32;
204 break;
205 }
206 default:
207 {
208 throw ParseException(
209 boost::str(
210 boost::format("'%1%' is not a currently supported datatype for tensor %2%."
211 " Supported dataTypes are FLOAT, INT32 and INT64. %3%") %
Matteo Martincighe355dc22018-12-10 13:45:27 +0000212 onnx::TensorProto::DataType_Name(
Tee Jungfcf6fd52019-11-01 05:27:28 +0000213 static_cast<onnx::TensorProto::DataType>(data_type)) %
214 name %
telsoa01c577f2c2018-08-31 09:22:23 +0100215 CHECK_LOCATION().AsString() ));
216 }
telsoa01c577f2c2018-08-31 09:22:23 +0100217 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000218
219 // To avoid crashes by trivial tensors
220 if (shape.empty())
221 {
222 return TensorInfo(TensorShape(), type);
223 }
224
Tee Jungfcf6fd52019-11-01 05:27:28 +0000225 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
226}
227
228armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
229{
230 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
231 std::vector<unsigned int> shapeDims;
232 for (int i = 0; i < onnxShape.dim_size(); ++i)
233 {
234 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
235 }
236
Ryan OShea337c17f2020-02-21 12:33:17 +0000237 if (shapeDims.empty())
238 {
239 shapeDims.push_back(1);
240 }
241
Tee Jungfcf6fd52019-11-01 05:27:28 +0000242 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
243}
244
245armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
246{
247 std::vector<unsigned int> shapeDims;
Ryan OShea337c17f2020-02-21 12:33:17 +0000248
Tee Jungfcf6fd52019-11-01 05:27:28 +0000249 for (auto dim: tensor.dims())
250 {
251 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
252 }
253
Ryan OShea337c17f2020-02-21 12:33:17 +0000254 if (shapeDims.empty())
255 {
256 shapeDims.push_back(1);
257 }
258
Tee Jungfcf6fd52019-11-01 05:27:28 +0000259 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100260}
261
262std::string TensorInfoAsString(const TensorInfo& info,
263 const std::string& name,
264 const onnx::TensorProto::DataType& type)
265{
266 const TensorShape shape = info.GetShape();
267 std::stringstream ss;
268 ss << "tensor '" << name << "' contains "
269 << onnx::TensorProto::DataType_Name(type)
270 << " and has shape [";
271
272 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
273 {
274 ss << shape[i] << ", ";
275 }
276 ss << shape[shape.GetNumDimensions() - 1] << "]";
277 return ss.str();
278}
279
280void CalcPadding(uint32_t inputSize, uint32_t filterSize, uint32_t stride, uint32_t* paddingFront,
281 uint32_t* paddingBack, bool isUpper)
282{
283 uint32_t outputSize = (inputSize + stride - 1) / stride;
284 uint32_t temp = (outputSize - 1) * stride + filterSize;
285 *paddingFront = (temp - inputSize) / 2;
286 *paddingBack = *paddingFront;
287 if((temp - inputSize) % 2 == 1)
288 {
289 if (isUpper)
290 {
291 *paddingBack += 1;
292 }
293 else
294 {
295 *paddingFront += 1;
296 }
297 }
298}
299
300TensorInfo ComputeReshapeInfo(const onnx::TensorProto& targetShapeTensor,
301 const TensorShape& inShape,
302 const std::string& outName)
303{
304 std::vector<int> targetDims;
305 for(int i = 0; i < targetShapeTensor.int64_data_size(); ++i)
306 {
307 int val = CHECKED_INT32(targetShapeTensor.int64_data(i));
308 if(val == 0)
309 {
310 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
311 }
312 else
313 {
314 targetDims.push_back(val);
315 }
316 }
317
318 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
319 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
320 if (stretchDim != targetDims.end())
321 {
322 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
323 {
324 std::stringstream ss;
325 ss << "[ ";
326 for(uint i = 0; i < targetDims.size() - 1; ++i)
327 {
328 ss << targetDims[i] << ", ";
329 }
330 ss << targetDims[targetDims.size() - 1] << " ]";
331
332 throw ParseException(boost::str(
333 boost::format("Error during creation of reshaped tensor '%1%'. At most one component of shape can be "
334 " -1 and here, shape is %2% %3%")
335 % outName
336 % ss.str()
337 % CHECK_LOCATION().AsString()));
338 }
339
340 auto targetNumElements = boost::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
341 -1, std::multiplies<int32_t>()));
342 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
343 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
344 }
345 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
346 return TensorInfo(outShape, DataType::Float32);
347}
348
349} //namespace
350
351const std::map<std::string, OnnxParser::OperationParsingFunction> OnnxParser::m_ParserFunctions = {
352 { "BatchNormalization", &OnnxParser::ParseBatchNormalization},
353 { "GlobalAveragePool", &OnnxParser::ParseGlobalAveragePool},
354 { "AveragePool", &OnnxParser::ParseAveragePool },
355 { "Constant", &OnnxParser::ParseConstant },
356 { "MaxPool", &OnnxParser::ParseMaxPool },
357 { "Reshape", &OnnxParser::ParseReshape },
Tee Jung7ff9a602019-11-01 07:04:42 +0000358 { "Sigmoid", &OnnxParser::ParseSigmoid },
359 { "Tanh", &OnnxParser::ParseTanh },
telsoa01c577f2c2018-08-31 09:22:23 +0100360 { "Relu", &OnnxParser::ParseRelu },
Tee Jung7ff9a602019-11-01 07:04:42 +0000361 { "LeakyRelu", &OnnxParser::ParseLeakyRelu },
telsoa01c577f2c2018-08-31 09:22:23 +0100362 { "Conv", &OnnxParser::ParseConv },
363 { "Add", &OnnxParser::ParseAdd },
364};
365
366template<typename TypePair, typename Location>
367void OnnxParser::ValidateInputs(const onnx::NodeProto& node,
368 TypePair validInputs,
369 const Location& location)
370{
371 for(auto input : node.input())
372 {
373 CheckValidDataType(validInputs.second,
374 m_TensorsInfo[input].m_dtype,
375 validInputs.first,
376 node.name(),
377 input,
378 location);
379 }
380}
381
382#define VALID_INPUTS(NODE, VALID_INPUTS) \
383 OnnxParser::ValidateInputs(NODE, \
384 VALID_INPUTS, \
385 CHECK_LOCATION())
386
387std::vector<TensorInfo> OnnxParser::ComputeOutputInfo(std::vector<std::string> outNames,
388 const IConnectableLayer* layer,
389 std::vector<TensorShape> inputShapes)
390{
391 BOOST_ASSERT(! outNames.empty());
392 bool needCompute = std::any_of(outNames.begin(),
393 outNames.end(),
394 [this](std::string name)
395 {
396 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr);
397 });
398 std::vector<TensorInfo> outInfo;
399 //if the output info(s) are not here, we need to compute them
400 std::vector<TensorShape> inferredShapes;
401 if(needCompute)
402 {
403 inferredShapes = layer->InferOutputShapes(inputShapes);
404 BOOST_ASSERT(inferredShapes.size() == outNames.size());
405 }
406 for (uint i = 0; i < outNames.size(); ++i)
407 {
408 if(needCompute)
409 {
410 m_TensorsInfo[outNames[i]] = OnnxTensor();
411 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
412 TensorInfo(inferredShapes[i], DataType::Float32));
413 }
414 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
415 }
416 return outInfo;
417}
418
419IOnnxParser* IOnnxParser::CreateRaw()
420{
421 return new OnnxParser();
422}
423
424IOnnxParserPtr IOnnxParser::Create()
425{
426 return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
427}
428
429void IOnnxParser::Destroy(IOnnxParser* parser)
430{
431 delete parser;
432}
433
434OnnxParser::OnnxParser()
435 : m_Network(nullptr, nullptr)
436{
437}
438
439void OnnxParser::ResetParser()
440{
441 m_Network = armnn::INetworkPtr(nullptr, nullptr);
442 m_Graph = nullptr;
443}
444
445void OnnxParser::Cleanup()
446{
447 m_TensorConnections.clear();
448 m_TensorsInfo.clear();
449 m_OutputsMap.clear();
450 m_OutputsFusedAndUsed.clear();
451}
452
453std::pair<ConstTensor, std::unique_ptr<float[]>> OnnxParser::CreateConstTensor(const std::string name)
454{
455 const TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
456 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
457
458 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100459 std::unique_ptr<float[]> tensorData(new float[tensorInfo.GetNumElements()]);
460 const size_t tensorSizeInBytes = tensorInfo.GetNumBytes();
461 // Copy the value list entries into the destination
462 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100463 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100464 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
465 {
466 throw ParseException(boost::str(
467 boost::format("The number of data provided (%1%) does not match the tensor '%2%' number of elements"
telsoa01c577f2c2018-08-31 09:22:23 +0100468 " (%3%) %4%")
469 % onnxTensor.float_data_size()
470 % name
471 % tensorInfo.GetNumElements()
472 % CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100473 }
474 ::memcpy(tensorData.get(), srcData, tensorSizeInBytes);
telsoa01c577f2c2018-08-31 09:22:23 +0100475 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100476 else
477 {
478 ::memcpy(tensorData.get(), onnxTensor.raw_data().c_str(), tensorSizeInBytes);
479 }
telsoa01c577f2c2018-08-31 09:22:23 +0100480
481 // Const tensors requires at least a list of values
482 if (tensorInfo.GetNumElements() == 0)
483 {
484 throw ParseException(boost::str(
485 boost::format("No tensor data found for Const tensor '%1%' %2%")
486 % name
487 % CHECK_LOCATION().AsString()));
488 }
489 return std::make_pair(ConstTensor(tensorInfo, tensorData.get()), std::move(tensorData));
490}
491
492ModelPtr OnnxParser::LoadModelFromTextFile(const char* graphFile)
493{
494 FILE* fd = fopen(graphFile, "r");
495
496 if (fd == nullptr)
497 {
498 throw FileNotFoundException(boost::str(
499 boost::format("Invalid (null) filename %1%") % CHECK_LOCATION().AsString()));
500 }
501
502 // Parse the file into a message
503 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
504 using google::protobuf::io::FileInputStream;
505 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
506 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
507 fclose(fd);
508
509 if (!success)
510 {
511 std::stringstream error;
512 error << "Failed to parse graph file";
513 throw ParseException(boost::str(
514 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
515 }
516 return modelProto;
517}
518
519INetworkPtr OnnxParser::CreateNetworkFromTextFile(const char* graphFile)
520{
521 ResetParser();
522 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
523 return CreateNetworkFromModel(*modelProto);
524}
525
526
527ModelPtr OnnxParser::LoadModelFromBinaryFile(const char* graphFile)
528{
529 FILE* fd = fopen(graphFile, "rb");
530
531 if (fd == nullptr)
532 {
533 throw FileNotFoundException(boost::str(
534 boost::format("Invalid (null) filename %1%") % CHECK_LOCATION().AsString()));
535 }
536
537 // Parse the file into a message
538 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
539
540 google::protobuf::io::FileInputStream inStream(fileno(fd));
541 google::protobuf::io::CodedInputStream codedStream(&inStream);
542 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
543 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
544 fclose(fd);
545
546 if (!success)
547 {
548 std::stringstream error;
549 error << "Failed to parse graph file";
550 throw ParseException(boost::str(
551 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
552 }
553 return modelProto;
554
555}
556
557INetworkPtr OnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
558{
559 ResetParser();
560 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
561 return CreateNetworkFromModel(*modelProto);
562}
563
564ModelPtr OnnxParser::LoadModelFromString(const std::string& protoText)
565{
566 if (protoText == "")
567 {
568 throw InvalidArgumentException(boost::str(
569 boost::format("Invalid (empty) string for model parameter %1%") % CHECK_LOCATION().AsString()));
570 }
571 // Parse the string into a message
572 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
573 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
574 if (!success)
575 {
576 std::stringstream error;
577 error << "Failed to parse graph file";
578 throw ParseException(boost::str(
579 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
580 }
581 return modelProto;
582}
583
584INetworkPtr OnnxParser::CreateNetworkFromString(const std::string& protoText)
585{
586 ResetParser();
587 ModelPtr modelProto = LoadModelFromString(protoText);
588 return CreateNetworkFromModel(*modelProto);
589}
590
591INetworkPtr OnnxParser::CreateNetworkFromModel(onnx::ModelProto& model)
592{
593 m_Network = INetwork::Create();
594 try
595 {
596 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
597 LoadGraph();
598 }
599 catch (const ParseException& e)
600 {
601 Cleanup();
602 throw e;
603 }
604 Cleanup();
605 return std::move(m_Network);
606}
607
608void OnnxParser::LoadGraph()
609{
610 BOOST_ASSERT(m_Graph.get() != nullptr);
611
612 //Fill m_TensorsInfo with the shapes and value of every tensor
613 SetupInfo(m_Graph->mutable_output());
614 SetupInfo(m_Graph->mutable_input());
615 SetupInfo(m_Graph->mutable_value_info());
616
617 for (auto tensor : m_Graph->initializer())
618 {
619 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000620 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
621 m_TensorsInfo[tensor.name()].m_dtype =
622 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100623 }
624
625 SetupInputLayers();
626 SetupOutputLayers();
627
628 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
629 DetectFullyConnected();
630
631 //Parsing the graph
632 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
633 {
634 auto node = m_Graph->node(static_cast<int>(nodeIndex));
635 const std::string& operation = node.op_type();
636
637 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000638 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100639 {
640 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
641 {
642 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
643 AddFullyConnected(node);
644 }
645 }
646 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
647 {
648 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
649 AddFullyConnected(m_Graph->node(matmulIndex), &node);
650 }
651 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
652 {
653 auto it = m_ParserFunctions.find(operation);
654 if (it != m_ParserFunctions.end())
655 {
656 auto func = it->second;
657 (this->*func)(node);
658 }
659 else
660 {
661 throw ParseException(boost::str(
662 boost::format("Unsupported operation %1% for node '%2%' %3%")
663 % operation
664 % node.name()
665 % CHECK_LOCATION().AsString()));
666 }
667 }
668 }
669
670 //Making the connections between outputs and inputs of each layers
671 for (const auto& tensorCon : m_TensorConnections)
672 {
673 if (tensorCon.second.outputSlot != nullptr)
674 {
675 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
676 {
677 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
678 }
679 }
680 }
681}
682
683void OnnxParser::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
684{
685 for (auto tensor : *list)
686 {
687 m_TensorsInfo[tensor.name()] = OnnxTensor();
688 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000689 m_TensorsInfo[tensor.name()].m_dtype =
690 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100691 }
692}
693
694void OnnxParser::DetectFullyConnected()
695{
696 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
697 auto matmulAndConstant = [&](const std::string& constInput,
698 const std::string& matmulInput,
699 int& nodeIndex)
700 {
701 auto matmulIt = m_OutputsMap.find(matmulInput);
702 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
703 && m_TensorsInfo[constInput].isConstant())
704 {
705 nodeIndex = matmulIt->second.second;
706 return true;
707 }
708 return false;
709 };
710
711 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
712 {
713 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
714 for (const std::string& output : node->output())
715 {
716 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
717 }
718
719 for (const std::string& input : node->input()) //count how many time a node is used as input
720 {
721 auto matmulIt = m_OutputsMap.find(input);
722 if(matmulIt != m_OutputsMap.end()){
723 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
724 }
725 }
726
727 if (node->op_type() == "Add")
728 {
729 int matmulIndex = 0;
730 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
731 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
732 {
733 //matmul and add were fused
734 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
735 .push_back(static_cast<size_t>(nodeIndex));
736
737 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
738 .push_back(static_cast<size_t>(matmulIndex));
739 }
740 }
741 }
742
743 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
744 auto matmulIt = m_OutputsMap.find(output.name());
745 if(matmulIt != m_OutputsMap.end()){
746 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
747 }
748 }
749}
750
751template<typename Location>
752void OnnxParser::GetInputAndParam(const onnx::NodeProto& node,
753 std::string* inputName,
754 std::string* constName,
755 const Location& location)
756{
757 int cstIndex;
758 if (m_TensorsInfo[node.input(0)].isConstant())
759 {
760 cstIndex = 0;
761 }
762 else if (m_TensorsInfo[node.input(1)].isConstant())
763 {
764 cstIndex = 1;
765 }
766 else
767 {
768 throw ParseException(boost::str(
769 boost::format("One of the input tensors ('%1%' or '%2%') should be constant in node '%3%' %4%")
770 % node.input(0)
771 % node.input(1)
772 % node.name()
773 % location.AsString()));
774 }
775 if(constName)
776 {
777 *constName = node.input(cstIndex);
778 }
779 if(inputName)
780 {
781 *inputName = node.input(!cstIndex);
782 }
783}
784
785template<typename Location>
786void OnnxParser::To1DTensor(const std::string& name, const Location& location)
787{
788 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
789 std::vector<uint32_t> newShape;
790 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
791 {
792 if(shape[i] != 1)
793 {
794 throw ParseException(boost::str(
795 boost::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and %1% %2%")
796 % TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype)
797 % location.AsString()));
798 }
799 }
800 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
801
802 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
803}
804
805void OnnxParser::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
806{
807
808 // find matmul inputs
809 std::string weightName;
810 std::string inputName;
811 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
812 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
813 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
814
815 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
816
817 FullyConnectedDescriptor desc;
818 desc.m_BiasEnabled = addNode != nullptr;
819
820 IConnectableLayer* layer = nullptr;
821 if(desc.m_BiasEnabled)
822 {
823 // find bias const
824 std::string biasName;
825 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
826 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
827 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
828
829 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
830
831 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
832 To1DTensor(biasName, CHECK_LOCATION());
833 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
834 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
835
836 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
837 {
838 throw ParseException(boost::str(
839 boost::format("Shape of weights '%1%' and bias of following Add node '%2%' do not match : %3%"
840 " and %4% ( /!\\ bias should be a 1D tensor) %5%")
841 % weightName
842 % addNode->name()
843 % TensorInfoAsString(*m_TensorsInfo[weightName].m_info,
844 weightName,
845 m_TensorsInfo[weightName].m_dtype)
846 % TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
847 m_TensorsInfo[biasName].m_dtype )
848 % CHECK_LOCATION().AsString()));
849 }
850 layer = m_Network->AddFullyConnectedLayer(desc,
851 CreateConstTensor(weightName).first,
Matteo Martincighfc598e12019-05-14 10:36:13 +0100852 Optional<ConstTensor>(CreateConstTensor(biasName).first),
telsoa01c577f2c2018-08-31 09:22:23 +0100853 matmulNode.name().c_str());
854 BOOST_ASSERT(layer != nullptr);
855
856 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
857 {m_TensorsInfo[inputName].m_info->GetShape(),
858 m_TensorsInfo[weightName].m_info->GetShape()});
859
860 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
861
862 RegisterInputSlots(layer, {inputName});
863 RegisterOutputSlots(layer, {addNode->output(0)});
864 }
865 else
866 {
Matteo Martincighfc598e12019-05-14 10:36:13 +0100867 layer = m_Network->AddFullyConnectedLayer(desc,
868 CreateConstTensor(weightName).first,
869 EmptyOptional(),
870 matmulNode.name().c_str());
telsoa01c577f2c2018-08-31 09:22:23 +0100871 BOOST_ASSERT(layer != nullptr);
872
873 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
874 {m_TensorsInfo[inputName].m_info->GetShape(),
875 m_TensorsInfo[weightName].m_info->GetShape()});
876 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
877
878 RegisterInputSlots(layer, {inputName});
879 RegisterOutputSlots(layer, {matmulNode.output(0)});
880 }
881}
882
883void OnnxParser::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
884{
885 auto armnnTensor = CreateConstTensor(tensorName);
886
887 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
888 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
889 RegisterOutputSlots(layer, {tensorName});
890}
891
892void OnnxParser::ParseConstant(const onnx::NodeProto& node)
893{
894 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +0100895 if (!node.attribute(0).has_t())
896 {
897 throw ParseException(boost::str(
898 boost::format("Value not found for Constant node '%1%' %2%")
899 % node.name()
900 % CHECK_LOCATION().AsString()));
901 }
902 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
903
904 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
Matteo Martincighe355dc22018-12-10 13:45:27 +0000905 CHECK_VALID_DATATYPE(node.name(), onnxTensor.name(),
906 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
telsoa01c577f2c2018-08-31 09:22:23 +0100907
908 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
909 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
Ryan OShea337c17f2020-02-21 12:33:17 +0000910 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
911 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100912
913 CreateConstantLayer(node.output(0), node.name());
telsoa01c577f2c2018-08-31 09:22:23 +0100914}
915
916void OnnxParser::ParseMaxPool(const onnx::NodeProto& node)
917{
918 Pooling2dDescriptor desc;
919 desc.m_PoolType = PoolingAlgorithm::Max;
920 desc.m_PaddingMethod = PaddingMethod::Exclude;
921 AddPoolingLayer(node, desc);
922}
923
924void OnnxParser::ParseGlobalAveragePool(const onnx::NodeProto& node)
925{
926 Pooling2dDescriptor desc = Pooling2dDescriptor();
927 desc.m_PoolType = PoolingAlgorithm::Average;
928
929 //kernel size is the same as input
930 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
931 desc.m_PoolWidth = inputShape[3];
932 desc.m_PoolHeight = inputShape[2];
933
934 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
935 BOOST_ASSERT(layer != nullptr);
936
937 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
938 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
939
940 // register the input connection slots for the layer, connections are made after all layers have been created
941 // only the tensors for the inputs are relevant, exclude the const tensors
942 RegisterInputSlots(layer, {node.input(0)});
943
944 // register the output connection slots for the layer, connections are made after all layers have been created
945 RegisterOutputSlots(layer, {node.output(0)});
946}
947
948void OnnxParser::ParseAveragePool(const onnx::NodeProto& node)
949{
950 Pooling2dDescriptor desc;
951 desc.m_PoolType = PoolingAlgorithm::Average;
952
953 uint32_t count_include_pad = 0;
954 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
955 if(count_include_pad) {
956 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
957 }
958 AddPoolingLayer(node, desc);
959}
960
961void OnnxParser::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
962{
963
964 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
965 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
966
967 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
968
969 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
970 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
971 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
972
973 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
974 desc.m_PoolWidth = kernel_shape[1];
975 desc.m_PoolHeight = kernel_shape[0];
976
977 if(strides.empty())
978 {
979 desc.m_StrideX = 1;
980 desc.m_StrideY = 1;
981 }
982 else
983 {
984 desc.m_StrideX = strides[1];
985 desc.m_StrideY = strides[0];
986 }
987
988 //Check new padding version first
989 if(pads.empty())
990 {
991 //Check deprecated version
992 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
993 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
994 {
995 bool isUpper;
996 if( paddingString == "SAME_LOWER")
997 {
998 isUpper = false;
999 }
1000 else if (paddingString == "SAME_UPPER")
1001 {
1002 isUpper = true;
1003 }
1004 else
1005 {
1006 throw ParseException(boost::str(
1007 boost::format("Invalid auto_pad attribute for node %1%. "
1008 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
1009 % node.name()
1010 % paddingString
1011 % CHECK_LOCATION().AsString()));
1012 }
1013 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1014 uint32_t inputHeight = inputInfo.GetShape()[2];
1015 uint32_t inputWidth = inputInfo.GetShape()[3];
1016 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1017 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1018 }
1019 }
1020 else
1021 {
1022 desc.m_PadTop = pads[0];
1023 desc.m_PadLeft = pads[1];
1024 desc.m_PadBottom = pads[2];
1025 desc.m_PadRight = pads[3];
1026 }
1027
1028 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1029 BOOST_ASSERT(layer != nullptr);
1030
1031 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1032 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1033
1034 // register the input connection slots for the layer, connections are made after all layers have been created
1035 // only the tensors for the inputs are relevant, exclude the const tensors
1036 RegisterInputSlots(layer, {node.input(0)});
1037
1038 // register the output connection slots for the layer, connections are made after all layers have been created
1039 RegisterOutputSlots(layer, {node.output(0)});
1040}
1041
1042void OnnxParser::CreateReshapeLayer(const std::string& inputName,
1043 const std::string& outputName,
1044 const std::string& layerName)
1045{
1046 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1047 ReshapeDescriptor reshapeDesc;
1048 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1049
1050 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
1051 BOOST_ASSERT(layer != nullptr);
1052 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1053
1054 // register the input connection slots for the layer, connections are made after all layers have been created
1055 // only the tensors for the inputs are relevant, exclude the const tensors
1056 RegisterInputSlots(layer, {inputName});
1057
1058 // register the output connection slots for the layer, connections are made after all layers have been created
1059 RegisterOutputSlots(layer, {outputName});
1060}
1061
1062void OnnxParser::ParseReshape(const onnx::NodeProto& node)
1063{
1064 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1065 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1066
1067 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1068 m_TensorsInfo[node.input(0)].m_dtype,
1069 onnx::TensorProto::FLOAT); //input
1070 CHECK_VALID_DATATYPE(node.name(), node.input(1),
1071 m_TensorsInfo[node.input(1)].m_dtype,
1072 onnx::TensorProto::INT64); //shape
1073
1074 if(!m_TensorsInfo[node.input(1)].isConstant())
1075 {
1076 throw ParseException(boost::str(
1077 boost::format("Shape '%1%' should be constant in Reshape layer '%2%' %3%")
1078 % node.input(1)
1079 % node.name()
1080 % CHECK_LOCATION().AsString()));
1081 }
1082
1083 if(m_TensorsInfo[node.input(0)].isConstant())
1084 {
1085 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
1086 if(m_TensorsInfo.count(node.output(0)) == 0)
1087 {
1088 m_TensorsInfo[node.output(0)] = OnnxTensor();
1089 }
1090 m_TensorsInfo[node.output(0)].m_tensor =
1091 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1092 }
1093 else
1094 {
1095 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1096
1097 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
1098 {
1099 auto outInfo = ComputeReshapeInfo(*m_TensorsInfo[node.input(1)].m_tensor, inputShape, node.output(0));
1100 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1101 }
1102
1103 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1104 }
1105}
1106
Tee Jung7ff9a602019-11-01 07:04:42 +00001107void OnnxParser::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001108{
1109 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1110 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1111
1112 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1113
1114 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001115 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001116
1117 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
1118 BOOST_ASSERT(layer != nullptr);
1119
1120 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1121 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1122
1123 // register the input connection slots for the layer, connections are made after all layers have been created
1124 // only the tensors for the inputs are relevant, exclude the const tensors
1125 RegisterInputSlots(layer, {node.input(0)});
1126
1127 // register the output connection slots for the layer, connections are made after all layers have been created
1128 RegisterOutputSlots(layer, {node.output(0)});
1129}
1130
Tee Jung7ff9a602019-11-01 07:04:42 +00001131void OnnxParser::ParseSigmoid(const onnx::NodeProto& node)
1132{
1133 ParseActivation(node, ActivationFunction::Sigmoid);
1134}
1135
1136void OnnxParser::ParseTanh(const onnx::NodeProto& node)
1137{
1138 ParseActivation(node, ActivationFunction::TanH);
1139}
1140
1141void OnnxParser::ParseRelu(const onnx::NodeProto& node)
1142{
1143 ParseActivation(node, ActivationFunction::ReLu);
1144}
1145
1146void OnnxParser::ParseLeakyRelu(const onnx::NodeProto& node)
1147{
1148 ParseActivation(node, ActivationFunction::LeakyReLu);
1149}
telsoa01c577f2c2018-08-31 09:22:23 +01001150
1151void OnnxParser::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
1152{
1153 BOOST_ASSERT(node.op_type() == "Conv");
1154
1155 DepthwiseConvolution2dDescriptor desc;
1156 desc.m_PadLeft = convDesc.m_PadLeft;
1157 desc.m_PadRight = convDesc.m_PadRight;
1158 desc.m_PadTop = convDesc.m_PadTop;
1159 desc.m_PadBottom = convDesc.m_PadBottom;
1160 desc.m_StrideX = convDesc.m_StrideX;
1161 desc.m_StrideY = convDesc.m_StrideY;
1162 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
1163
1164 armnn::IConnectableLayer* layer;
1165 auto weightTensor = CreateConstTensor(node.input(1));
1166 TensorShape& weightShape = weightTensor.first.GetShape();
1167 weightShape[1] = weightShape[0];
1168 weightShape[0] = 1;
1169 m_TensorsInfo[node.input(1)].m_info->SetShape(weightShape);
1170
1171 if (node.input_size() == 3)
1172 {
1173 if(!m_TensorsInfo[node.input(2)].isConstant())
1174 {
1175 throw ParseException(boost::str(
1176 boost::format("Bias '%1%' should be constant in Conv layer '%2%' %3%")
1177 % node.input(2)
1178 % node.name()
1179 % CHECK_LOCATION().AsString()));
1180 }
1181 desc.m_BiasEnabled = true;
1182 auto biasTensor = CreateConstTensor(node.input(2));
1183 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1184 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001185 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001186 node.name().c_str());
1187 }
1188 else
1189 {
1190 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1191 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001192 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001193 node.name().c_str());
1194 }
1195 BOOST_ASSERT(layer != nullptr);
1196
1197 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1198 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1199 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1200
1201 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1202
1203 // register the input connection slots for the layer, connections are made after all layers have been created
1204 // only the tensors for the inputs are relevant, exclude the const tensors
1205 RegisterInputSlots(layer, {node.input(0)});
1206
1207 // register the output connection slots for the layer, connections are made after all layers have been created
1208 RegisterOutputSlots(layer, {node.output(0)});
1209}
1210
1211void OnnxParser::ParseConv(const onnx::NodeProto& node)
1212{
1213 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1214 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1215
1216 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1217
1218 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1219 {
1220 throw ParseException(boost::str(
1221 boost::format("ArmNN only supports 2D convolution and Conv layer '%1%' input %2% %3%")
1222 % node.name()
1223 % TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1224 m_TensorsInfo[node.input(0)].m_dtype)
1225 % CHECK_LOCATION().AsString()));
1226 }
1227
1228 if(!m_TensorsInfo[node.input(1)].isConstant())
1229 {
1230 throw ParseException(boost::str(
1231 boost::format("Weights '%1%' should be constant in Conv layer '%2%' %3%")
1232 % node.input(1)
1233 % node.name()
1234 % CHECK_LOCATION().AsString()));
1235 }
1236
1237 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1238
1239 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1240 if (!dilations.empty())
1241 {
1242 std::stringstream ss;
1243 ss << "[ ";
1244 for (auto dilation : dilations)
1245 {
1246 ss << dilation << ", ";
1247 if (dilation != 1u)
1248 {
1249 ss << "... ]";
1250 throw ParseException(boost::str(
1251 boost::format("ArmNN only supports Convolution layers with dilations [1,1], and node '%1%' "
1252 "has dilatation %2% %3%")
1253 % node.name()
1254 % ss.str()
1255 % CHECK_LOCATION().AsString()));
1256 }
1257 }
1258 }
1259
1260 Convolution2dDescriptor desc;
1261 desc.m_BiasEnabled = false;
1262
1263 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1264 if(strides.empty())
1265 {
1266 desc.m_StrideX = 1;
1267 desc.m_StrideY = 1;
1268 }
1269 else
1270 {
1271 desc.m_StrideX = strides[1];
1272 desc.m_StrideY = strides[0];
1273 }
1274
1275 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1276 //Check new padding version first
1277 if(pads.empty())
1278 {
1279 //Check deprecated version
1280 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1281 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1282 {
1283 bool isUpper;
1284 if( paddingString == "SAME_LOWER")
1285 {
1286 isUpper = false;
1287 }
1288 else if (paddingString == "SAME_UPPER")
1289 {
1290 isUpper = true;
1291 }
1292 else
1293 {
1294 throw ParseException(boost::str(
1295 boost::format("Invalid auto_pad attribute for node %1%. "
1296 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
1297 % node.name()
1298 % paddingString
1299 % CHECK_LOCATION().AsString()));
1300 }
1301 uint32_t inputHeight = inputInfo.GetShape()[2];
1302 uint32_t inputWidth = inputInfo.GetShape()[3];
1303
1304 uint32_t weightHeight;
1305 uint32_t weightWidth;
1306 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1307 if (kernel_shape.empty())
1308 {
1309 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1310 weightHeight = weightTensorInfo.GetShape()[2];
1311 weightWidth = weightTensorInfo.GetShape()[3];
1312 }
1313 else
1314 {
1315 weightHeight = kernel_shape[0];
1316 weightWidth = kernel_shape[1];
1317 }
1318 CalcPadding(inputHeight, weightHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1319 CalcPadding(inputWidth, weightWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1320 }
1321 }
1322 else
1323 {
1324 desc.m_PadTop = pads[0];
1325 desc.m_PadLeft = pads[1];
1326 desc.m_PadBottom = pads[2];
1327 desc.m_PadRight = pads[3];
1328 }
1329
1330 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1331 if(group > 1)
1332 {
1333 if (group > inputInfo.GetShape()[1])
1334 {
1335 throw ParseException(
1336 boost::str(
1337 boost::format(
1338 "Error parsing Convolution node: %1%. "
1339 "The 'group'=%2% parameter cannot be larger than the "
1340 "channel of the input shape=%3% (in NCHW format). %4%") %
1341 node.name() %
1342 group %
1343 inputInfo.GetShape()[1] %
1344 CHECK_LOCATION().AsString()));
1345 }
1346 else if (group == inputInfo.GetShape()[1])
1347 {
1348 // we use a depthwise convolution here, because the number of groups equals to the
1349 // input channels
1350 AddConvLayerWithDepthwiseConv(node, desc);
1351 return;
1352 }
1353 else
1354 {
1355 // TODO: split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +01001356 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +01001357 throw ParseException(boost::str(
1358 boost::format("Error parsing Convolution node: %1%. "
1359 "The 'group'=%2% parameter should be 1 or be equal to the "
1360 "channel of the input shape=%3% (in NCHW format). %4%") %
1361 node.name() %
1362 group %
1363 inputInfo.GetShape()[1] %
1364 CHECK_LOCATION().AsString()));
1365 }
1366 }
1367
1368 armnn::IConnectableLayer* layer;
1369 auto weightTensor = CreateConstTensor(node.input(1));
1370
1371 if (node.input_size() == 3)
1372 {
1373 if(!m_TensorsInfo[node.input(2)].isConstant())
1374 {
1375 throw ParseException(boost::str(
1376 boost::format("Bias '%1%' should be constant in Conv layer '%2%' %3%")
1377 % node.input(2)
1378 % node.name()
1379 % CHECK_LOCATION().AsString()));
1380 }
1381 desc.m_BiasEnabled = true;
1382 auto biasTensor = CreateConstTensor(node.input(2));
1383 layer = m_Network->AddConvolution2dLayer(desc,
1384 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001385 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001386 node.name().c_str());
1387 }
1388 else
1389 {
1390 layer = m_Network->AddConvolution2dLayer(desc,
1391 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001392 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001393 node.name().c_str());
1394 }
1395 BOOST_ASSERT(layer != nullptr);
1396
1397 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1398 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1399 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1400 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1401
1402 // register the input connection slots for the layer, connections are made after all layers have been created
1403 // only the tensors for the inputs are relevant, exclude the const tensors
1404 RegisterInputSlots(layer, {node.input(0)});
1405
1406 // register the output connection slots for the layer, connections are made after all layers have been created
1407 RegisterOutputSlots(layer, {node.output(0)});
1408}
1409
1410void OnnxParser::PrependForBroadcast(const std::string& outputName,
1411 const std::string& input0,
1412 const std::string& input1)
1413{
1414 //input0 should be reshaped to have same number of dim as input1
1415 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
1416
1417 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1418 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1419
1420 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
1421 std::vector<uint32_t> newShape;
1422 while(diff > 0)
1423 {
1424 newShape.push_back(1);
1425 diff--;
1426 }
1427 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
1428 {
1429 newShape.push_back(input0Shape[dim]);
1430 }
1431 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1432
1433 //add the new tensor to m_TensorsInfo
1434 m_TensorsInfo[outputName] = OnnxTensor();
1435 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1436
1437 //add reshape layer if the parent was not constant...
1438 if( ! m_TensorsInfo[input0].isConstant())
1439 {
1440 CreateReshapeLayer(input0, outputName, boost::str(boost::format("Add:reshapeOf%1%") % input0));
1441 }
1442 else //make it constant and it will be create in Add
1443 {
1444 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1445
1446 }
1447}
1448
1449std::pair<std::string, std::string> OnnxParser::AddPrepareBroadcast(const std::string& input0,
1450 const std::string& input1)
1451{
1452 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1453
1454 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1455 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1456
1457 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1458 {
1459 auto outputName = boost::str(boost::format("reshape_output_%1%") % input1);
1460 PrependForBroadcast(outputName, input1, input0);
1461 inputs.second = outputName;
1462 }
1463 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1464 {
1465 auto outputName = boost::str(boost::format("reshape_output_%1%") % input0);
1466 PrependForBroadcast(outputName, input0, input1);
1467 inputs.first = outputName;
1468 }
1469 return inputs;
1470}
1471
1472void OnnxParser::ParseAdd(const onnx::NodeProto& node)
1473{
1474 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1475 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1476
1477 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1478
1479 // TODO: unify broadcast validation code across layers
1480 // tracked by: IVGCVSW-1576
1481
1482 // Checking broadcast compatibility : only scalar or 1D tensors
1483 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1484 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1485 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1486 BOOST_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1487
1488 unsigned int numDims = input0.GetNumDimensions();
1489 for (unsigned int i = 0; i < numDims; i++)
1490 {
1491 unsigned int dim0 = input0.GetShape()[i];
1492 unsigned int dim1 = input1.GetShape()[i];
1493 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
1494 {
1495 throw ParseException(boost::str(
1496 boost::format("Broadcast is only supported for scalar or 1D tensors in Add node '%1%'. "
1497 "Input dimensions should either match or one should be of size 1 and here, "
1498 "%2% and %3% %4%")
1499 % node.name()
1500 % TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1501 m_TensorsInfo[inputs.first].m_dtype)
1502 % TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1503 m_TensorsInfo[inputs.second].m_dtype)
1504 % CHECK_LOCATION().AsString()));
1505 }
1506 }
1507
1508
1509 IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
1510 BOOST_ASSERT(layer != nullptr);
1511
1512 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1513 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1514 m_TensorsInfo[inputs.second].m_info->GetShape() });
1515 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1516
1517 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1518 if(m_TensorsInfo[inputs.first].isConstant()) {
telsoa01c577f2c2018-08-31 09:22:23 +01001519 CreateConstantLayer(inputs.first, boost::str(boost::format("Add:constant_of_%1%") % node.input(0)));
1520 }
1521 if(m_TensorsInfo[inputs.second].isConstant()) {
telsoa01c577f2c2018-08-31 09:22:23 +01001522 CreateConstantLayer(inputs.second, boost::str(boost::format("Add:constant_of_%1%") % node.input(1)));
1523 }
1524 RegisterInputSlots(layer, {inputs.first, inputs.second});
1525
1526 // register the output connection
1527 RegisterOutputSlots(layer, {node.output(0)});
1528}
1529
1530void OnnxParser::ParseBatchNormalization(const onnx::NodeProto& node)
1531{
1532 //IGNORE momentum parameter and spatial parameters
1533
1534 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1535 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1536
1537 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1538 for(int ind = 1; ind < node.input_size(); ++ind)
1539 {
1540 auto tensor = node.input(ind);
1541 if(! m_TensorsInfo[tensor].isConstant())
1542 {
1543 throw ParseException(boost::str(
1544 boost::format("Input tensor '%1%' should be constant in BatchNormalization node '%2%' %3%")
1545 % tensor
1546 % node.name()
1547 % CHECK_LOCATION().AsString()));
1548 }
1549 }
1550
1551 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1552 BatchNormalizationDescriptor desc;
1553 desc.m_Eps = epsilon;
1554
1555 auto scaleTensor = CreateConstTensor(node.input(1));
1556 auto biasTensor = CreateConstTensor(node.input(2));
1557 auto meanTensor = CreateConstTensor(node.input(3));
1558 auto varTensor = CreateConstTensor(node.input(4));
1559
1560 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1561 meanTensor.first,
1562 varTensor.first,
1563 biasTensor.first,
1564 scaleTensor.first,
1565 node.name().c_str());
1566 BOOST_ASSERT(layer != nullptr);
1567
1568 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1569 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1570
1571 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1572
1573 // register the output connection
1574 RegisterOutputSlots(layer, {node.output(0)});
1575}
1576
1577void OnnxParser::SetupInputLayers()
1578{
1579 //Find user input and add their layers
1580 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1581 {
1582 auto input = m_Graph->input(inputIndex);
1583 if (! m_TensorsInfo[input.name()].isConstant())
1584 {
1585 IConnectableLayer* layer =
1586 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
1587 auto tensorInfo = ToTensorInfo(input);
1588 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1589
1590 RegisterOutputSlots(layer,{ input.name() });
1591 }
1592 }
1593}
1594
1595void OnnxParser::SetupOutputLayers()
1596{
1597 if(m_Graph->output_size() == 0)
1598 {
1599 throw ParseException(boost::str(boost::format("The given model does not have any outputs %1%")
1600 % CHECK_LOCATION().AsString()));
1601 }
1602
1603 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
1604 {
1605 IConnectableLayer* layer =
1606 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
1607 m_Graph->output(outputIndex).name().c_str());
1608
1609 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
1610 }
1611}
1612
1613void OnnxParser::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1614{
1615 BOOST_ASSERT(layer != nullptr);
1616 if (tensorIds.size() != layer->GetNumInputSlots())
1617 {
1618 throw ParseException(
1619 boost::str(boost::format("The number of tensor inputs (%1%) does not match the number expected (%2%) %3%") %
1620 tensorIds.size() %
1621 layer->GetNumInputSlots() %
1622 CHECK_LOCATION().AsString()));
1623 }
1624 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
1625 {
1626 std::string tensorId = tensorIds[slotIndex];
1627 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
1628
1629 auto it = m_TensorConnections.find(tensorId);
1630
1631 if (it == m_TensorConnections.end())
1632 {
1633 //First time seing this tensor, we need to map it
1634 m_TensorConnections[tensorId] = TensorSlots();
1635 }
1636 m_TensorConnections[tensorId].inputSlots.push_back(slot);
1637 }
1638}
1639
1640void OnnxParser::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1641{
1642 BOOST_ASSERT(layer != nullptr);
1643 if (tensorIds.size() != layer->GetNumOutputSlots())
1644 {
1645 throw ParseException(
1646 boost::str(boost::format("The number of tensor outputs (%1%) does not match the number expected (%2%) %3% ")
1647 % tensorIds.size()
1648 % layer->GetNumOutputSlots()
1649 % CHECK_LOCATION().AsString()));
1650 }
1651
1652 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
1653 {
1654 std::string tensorId = tensorIds[slotIndex];
1655 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
1656
1657 auto it = m_TensorConnections.find(tensorId);
1658
1659 if (it == m_TensorConnections.end())
1660 {
1661 //First time seing this tensor, we need to map it
1662 m_TensorConnections[tensorId] = TensorSlots();
1663 }
1664
Ryan OShea337c17f2020-02-21 12:33:17 +00001665 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01001666
1667 // assuming there is only one producer for that tensor
1668 if (tensorSlots.outputSlot != nullptr)
1669 {
1670 throw ParseException(boost::str(
1671 boost::format("Another layer has already registered itself as the producer of "
Ryan OShea337c17f2020-02-21 12:33:17 +00001672 "tensor:%1% %2%")
1673 % tensorId
1674 % CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001675 }
1676 tensorSlots.outputSlot = slot;
1677 }
1678}
1679
1680BindingPointInfo OnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
1681{
1682 for(int i = 0; i < m_Graph->input_size(); ++i)
1683 {
1684 auto input = m_Graph->input(i);
1685 if(input.name() == name)
1686 {
1687 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(input));
1688 }
1689 }
1690 throw InvalidArgumentException(boost::str(boost::format("The input layer '%1%' does not exist %2%")
1691 % name % CHECK_LOCATION().AsString()));
1692}
1693
1694BindingPointInfo OnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
1695{
1696 for(int i = 0; i < m_Graph->output_size(); ++i)
1697 {
1698 auto output = m_Graph->output(i);
1699 if(output.name() == name)
1700 {
1701 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(output));
1702 }
1703 }
1704 throw InvalidArgumentException(boost::str(boost::format("The output layer '%1%' does not exist %2%")
1705 % name % CHECK_LOCATION().AsString()));
1706}
1707
1708std::vector<std::string> OnnxParser::GetInputs(ModelPtr& model)
1709{
1710 if(model == nullptr) {
1711 throw InvalidArgumentException(boost::str(
1712 boost::format("The given model cannot be null %1%")
1713 % CHECK_LOCATION().AsString()));
1714 }
1715
1716 std::vector<std::string> inputNames;
1717 std::map<std::string, bool> isConstant;
1718 for(auto tensor : model->graph().initializer())
1719 {
1720 isConstant[tensor.name()] = true;
1721 }
1722 for(auto input : model->graph().input())
1723 {
1724 auto it = isConstant.find(input.name());
1725 if(it == isConstant.end())
1726 {
1727 inputNames.push_back(input.name());
1728 }
1729 }
1730 return inputNames;
1731}
1732
1733std::vector<std::string> OnnxParser::GetOutputs(ModelPtr& model)
1734{
1735 if(model == nullptr) {
1736 throw InvalidArgumentException(boost::str(
1737 boost::format("The given model cannot be null %1%")
1738 % CHECK_LOCATION().AsString()));
1739 }
1740
1741 std::vector<std::string> outputNames;
1742 for(auto output : model->graph().output())
1743 {
1744 outputNames.push_back(output.name());
1745 }
1746 return outputNames;
1747}
1748
1749} // namespace armnnOnnxParser