blob: 455bd873af55d5ccb84628d994676c61078e716b [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>
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01008#include <armnn/utility/Assert.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +01009#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 },
Finn Williams7ee5d2c2020-03-27 11:11:50 +0000355 { "Clip", &OnnxParser::ParseClip },
telsoa01c577f2c2018-08-31 09:22:23 +0100356 { "Constant", &OnnxParser::ParseConstant },
357 { "MaxPool", &OnnxParser::ParseMaxPool },
358 { "Reshape", &OnnxParser::ParseReshape },
Tee Jung7ff9a602019-11-01 07:04:42 +0000359 { "Sigmoid", &OnnxParser::ParseSigmoid },
360 { "Tanh", &OnnxParser::ParseTanh },
telsoa01c577f2c2018-08-31 09:22:23 +0100361 { "Relu", &OnnxParser::ParseRelu },
Tee Jung7ff9a602019-11-01 07:04:42 +0000362 { "LeakyRelu", &OnnxParser::ParseLeakyRelu },
telsoa01c577f2c2018-08-31 09:22:23 +0100363 { "Conv", &OnnxParser::ParseConv },
364 { "Add", &OnnxParser::ParseAdd },
365};
366
367template<typename TypePair, typename Location>
368void OnnxParser::ValidateInputs(const onnx::NodeProto& node,
369 TypePair validInputs,
370 const Location& location)
371{
372 for(auto input : node.input())
373 {
374 CheckValidDataType(validInputs.second,
375 m_TensorsInfo[input].m_dtype,
376 validInputs.first,
377 node.name(),
378 input,
379 location);
380 }
381}
382
383#define VALID_INPUTS(NODE, VALID_INPUTS) \
384 OnnxParser::ValidateInputs(NODE, \
385 VALID_INPUTS, \
386 CHECK_LOCATION())
387
388std::vector<TensorInfo> OnnxParser::ComputeOutputInfo(std::vector<std::string> outNames,
389 const IConnectableLayer* layer,
390 std::vector<TensorShape> inputShapes)
391{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100392 ARMNN_ASSERT(! outNames.empty());
telsoa01c577f2c2018-08-31 09:22:23 +0100393 bool needCompute = std::any_of(outNames.begin(),
394 outNames.end(),
395 [this](std::string name)
396 {
397 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr);
398 });
399 std::vector<TensorInfo> outInfo;
400 //if the output info(s) are not here, we need to compute them
401 std::vector<TensorShape> inferredShapes;
402 if(needCompute)
403 {
404 inferredShapes = layer->InferOutputShapes(inputShapes);
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100405 ARMNN_ASSERT(inferredShapes.size() == outNames.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100406 }
407 for (uint i = 0; i < outNames.size(); ++i)
408 {
409 if(needCompute)
410 {
411 m_TensorsInfo[outNames[i]] = OnnxTensor();
412 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
413 TensorInfo(inferredShapes[i], DataType::Float32));
414 }
415 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
416 }
417 return outInfo;
418}
419
420IOnnxParser* IOnnxParser::CreateRaw()
421{
422 return new OnnxParser();
423}
424
425IOnnxParserPtr IOnnxParser::Create()
426{
427 return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
428}
429
430void IOnnxParser::Destroy(IOnnxParser* parser)
431{
432 delete parser;
433}
434
435OnnxParser::OnnxParser()
436 : m_Network(nullptr, nullptr)
437{
438}
439
440void OnnxParser::ResetParser()
441{
442 m_Network = armnn::INetworkPtr(nullptr, nullptr);
443 m_Graph = nullptr;
444}
445
446void OnnxParser::Cleanup()
447{
448 m_TensorConnections.clear();
449 m_TensorsInfo.clear();
450 m_OutputsMap.clear();
451 m_OutputsFusedAndUsed.clear();
452}
453
454std::pair<ConstTensor, std::unique_ptr<float[]>> OnnxParser::CreateConstTensor(const std::string name)
455{
456 const TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
457 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
458
459 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100460 std::unique_ptr<float[]> tensorData(new float[tensorInfo.GetNumElements()]);
461 const size_t tensorSizeInBytes = tensorInfo.GetNumBytes();
462 // Copy the value list entries into the destination
463 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100464 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100465 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
466 {
467 throw ParseException(boost::str(
468 boost::format("The number of data provided (%1%) does not match the tensor '%2%' number of elements"
telsoa01c577f2c2018-08-31 09:22:23 +0100469 " (%3%) %4%")
470 % onnxTensor.float_data_size()
471 % name
472 % tensorInfo.GetNumElements()
473 % CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100474 }
475 ::memcpy(tensorData.get(), srcData, tensorSizeInBytes);
telsoa01c577f2c2018-08-31 09:22:23 +0100476 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100477 else
478 {
479 ::memcpy(tensorData.get(), onnxTensor.raw_data().c_str(), tensorSizeInBytes);
480 }
telsoa01c577f2c2018-08-31 09:22:23 +0100481
482 // Const tensors requires at least a list of values
483 if (tensorInfo.GetNumElements() == 0)
484 {
485 throw ParseException(boost::str(
486 boost::format("No tensor data found for Const tensor '%1%' %2%")
487 % name
488 % CHECK_LOCATION().AsString()));
489 }
490 return std::make_pair(ConstTensor(tensorInfo, tensorData.get()), std::move(tensorData));
491}
492
493ModelPtr OnnxParser::LoadModelFromTextFile(const char* graphFile)
494{
495 FILE* fd = fopen(graphFile, "r");
496
497 if (fd == nullptr)
498 {
499 throw FileNotFoundException(boost::str(
500 boost::format("Invalid (null) filename %1%") % CHECK_LOCATION().AsString()));
501 }
502
503 // Parse the file into a message
504 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
505 using google::protobuf::io::FileInputStream;
506 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
507 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
508 fclose(fd);
509
510 if (!success)
511 {
512 std::stringstream error;
513 error << "Failed to parse graph file";
514 throw ParseException(boost::str(
515 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
516 }
517 return modelProto;
518}
519
520INetworkPtr OnnxParser::CreateNetworkFromTextFile(const char* graphFile)
521{
522 ResetParser();
523 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
524 return CreateNetworkFromModel(*modelProto);
525}
526
527
528ModelPtr OnnxParser::LoadModelFromBinaryFile(const char* graphFile)
529{
530 FILE* fd = fopen(graphFile, "rb");
531
532 if (fd == nullptr)
533 {
534 throw FileNotFoundException(boost::str(
535 boost::format("Invalid (null) filename %1%") % CHECK_LOCATION().AsString()));
536 }
537
538 // Parse the file into a message
539 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
540
541 google::protobuf::io::FileInputStream inStream(fileno(fd));
542 google::protobuf::io::CodedInputStream codedStream(&inStream);
543 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
544 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
545 fclose(fd);
546
547 if (!success)
548 {
549 std::stringstream error;
550 error << "Failed to parse graph file";
551 throw ParseException(boost::str(
552 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
553 }
554 return modelProto;
555
556}
557
558INetworkPtr OnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
559{
560 ResetParser();
561 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
562 return CreateNetworkFromModel(*modelProto);
563}
564
565ModelPtr OnnxParser::LoadModelFromString(const std::string& protoText)
566{
567 if (protoText == "")
568 {
569 throw InvalidArgumentException(boost::str(
570 boost::format("Invalid (empty) string for model parameter %1%") % CHECK_LOCATION().AsString()));
571 }
572 // Parse the string into a message
573 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
574 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
575 if (!success)
576 {
577 std::stringstream error;
578 error << "Failed to parse graph file";
579 throw ParseException(boost::str(
580 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
581 }
582 return modelProto;
583}
584
585INetworkPtr OnnxParser::CreateNetworkFromString(const std::string& protoText)
586{
587 ResetParser();
588 ModelPtr modelProto = LoadModelFromString(protoText);
589 return CreateNetworkFromModel(*modelProto);
590}
591
592INetworkPtr OnnxParser::CreateNetworkFromModel(onnx::ModelProto& model)
593{
594 m_Network = INetwork::Create();
595 try
596 {
597 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
598 LoadGraph();
599 }
600 catch (const ParseException& e)
601 {
602 Cleanup();
603 throw e;
604 }
605 Cleanup();
606 return std::move(m_Network);
607}
608
609void OnnxParser::LoadGraph()
610{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100611 ARMNN_ASSERT(m_Graph.get() != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100612
613 //Fill m_TensorsInfo with the shapes and value of every tensor
614 SetupInfo(m_Graph->mutable_output());
615 SetupInfo(m_Graph->mutable_input());
616 SetupInfo(m_Graph->mutable_value_info());
617
618 for (auto tensor : m_Graph->initializer())
619 {
620 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000621 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
622 m_TensorsInfo[tensor.name()].m_dtype =
623 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100624 }
625
626 SetupInputLayers();
627 SetupOutputLayers();
628
629 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
630 DetectFullyConnected();
631
632 //Parsing the graph
633 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
634 {
635 auto node = m_Graph->node(static_cast<int>(nodeIndex));
636 const std::string& operation = node.op_type();
637
638 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000639 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100640 {
641 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
642 {
643 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
644 AddFullyConnected(node);
645 }
646 }
647 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
648 {
649 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
650 AddFullyConnected(m_Graph->node(matmulIndex), &node);
651 }
652 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
653 {
654 auto it = m_ParserFunctions.find(operation);
655 if (it != m_ParserFunctions.end())
656 {
657 auto func = it->second;
658 (this->*func)(node);
659 }
660 else
661 {
662 throw ParseException(boost::str(
663 boost::format("Unsupported operation %1% for node '%2%' %3%")
664 % operation
665 % node.name()
666 % CHECK_LOCATION().AsString()));
667 }
668 }
669 }
670
671 //Making the connections between outputs and inputs of each layers
672 for (const auto& tensorCon : m_TensorConnections)
673 {
674 if (tensorCon.second.outputSlot != nullptr)
675 {
676 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
677 {
678 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
679 }
680 }
681 }
682}
683
684void OnnxParser::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
685{
686 for (auto tensor : *list)
687 {
688 m_TensorsInfo[tensor.name()] = OnnxTensor();
689 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000690 m_TensorsInfo[tensor.name()].m_dtype =
691 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100692 }
693}
694
695void OnnxParser::DetectFullyConnected()
696{
697 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
698 auto matmulAndConstant = [&](const std::string& constInput,
699 const std::string& matmulInput,
700 int& nodeIndex)
701 {
702 auto matmulIt = m_OutputsMap.find(matmulInput);
703 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
704 && m_TensorsInfo[constInput].isConstant())
705 {
706 nodeIndex = matmulIt->second.second;
707 return true;
708 }
709 return false;
710 };
711
712 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
713 {
714 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
715 for (const std::string& output : node->output())
716 {
717 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
718 }
719
720 for (const std::string& input : node->input()) //count how many time a node is used as input
721 {
722 auto matmulIt = m_OutputsMap.find(input);
723 if(matmulIt != m_OutputsMap.end()){
724 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
725 }
726 }
727
728 if (node->op_type() == "Add")
729 {
730 int matmulIndex = 0;
731 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
732 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
733 {
734 //matmul and add were fused
735 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
736 .push_back(static_cast<size_t>(nodeIndex));
737
738 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
739 .push_back(static_cast<size_t>(matmulIndex));
740 }
741 }
742 }
743
744 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
745 auto matmulIt = m_OutputsMap.find(output.name());
746 if(matmulIt != m_OutputsMap.end()){
747 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
748 }
749 }
750}
751
752template<typename Location>
753void OnnxParser::GetInputAndParam(const onnx::NodeProto& node,
754 std::string* inputName,
755 std::string* constName,
756 const Location& location)
757{
758 int cstIndex;
759 if (m_TensorsInfo[node.input(0)].isConstant())
760 {
761 cstIndex = 0;
762 }
763 else if (m_TensorsInfo[node.input(1)].isConstant())
764 {
765 cstIndex = 1;
766 }
767 else
768 {
769 throw ParseException(boost::str(
770 boost::format("One of the input tensors ('%1%' or '%2%') should be constant in node '%3%' %4%")
771 % node.input(0)
772 % node.input(1)
773 % node.name()
774 % location.AsString()));
775 }
776 if(constName)
777 {
778 *constName = node.input(cstIndex);
779 }
780 if(inputName)
781 {
782 *inputName = node.input(!cstIndex);
783 }
784}
785
786template<typename Location>
787void OnnxParser::To1DTensor(const std::string& name, const Location& location)
788{
789 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
790 std::vector<uint32_t> newShape;
791 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
792 {
793 if(shape[i] != 1)
794 {
795 throw ParseException(boost::str(
796 boost::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and %1% %2%")
797 % TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype)
798 % location.AsString()));
799 }
800 }
801 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
802
803 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
804}
805
806void OnnxParser::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
807{
808
809 // find matmul inputs
810 std::string weightName;
811 std::string inputName;
812 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
813 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
814 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
815
816 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
817
818 FullyConnectedDescriptor desc;
819 desc.m_BiasEnabled = addNode != nullptr;
820
821 IConnectableLayer* layer = nullptr;
822 if(desc.m_BiasEnabled)
823 {
824 // find bias const
825 std::string biasName;
826 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
827 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
828 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
829
830 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
831
832 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
833 To1DTensor(biasName, CHECK_LOCATION());
834 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
835 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
836
837 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
838 {
839 throw ParseException(boost::str(
840 boost::format("Shape of weights '%1%' and bias of following Add node '%2%' do not match : %3%"
841 " and %4% ( /!\\ bias should be a 1D tensor) %5%")
842 % weightName
843 % addNode->name()
844 % TensorInfoAsString(*m_TensorsInfo[weightName].m_info,
845 weightName,
846 m_TensorsInfo[weightName].m_dtype)
847 % TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
848 m_TensorsInfo[biasName].m_dtype )
849 % CHECK_LOCATION().AsString()));
850 }
851 layer = m_Network->AddFullyConnectedLayer(desc,
852 CreateConstTensor(weightName).first,
Matteo Martincighfc598e12019-05-14 10:36:13 +0100853 Optional<ConstTensor>(CreateConstTensor(biasName).first),
telsoa01c577f2c2018-08-31 09:22:23 +0100854 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100855 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100856
857 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
858 {m_TensorsInfo[inputName].m_info->GetShape(),
859 m_TensorsInfo[weightName].m_info->GetShape()});
860
861 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
862
863 RegisterInputSlots(layer, {inputName});
864 RegisterOutputSlots(layer, {addNode->output(0)});
865 }
866 else
867 {
Matteo Martincighfc598e12019-05-14 10:36:13 +0100868 layer = m_Network->AddFullyConnectedLayer(desc,
869 CreateConstTensor(weightName).first,
870 EmptyOptional(),
871 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100872 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100873
874 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
875 {m_TensorsInfo[inputName].m_info->GetShape(),
876 m_TensorsInfo[weightName].m_info->GetShape()});
877 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
878
879 RegisterInputSlots(layer, {inputName});
880 RegisterOutputSlots(layer, {matmulNode.output(0)});
881 }
882}
883
884void OnnxParser::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
885{
886 auto armnnTensor = CreateConstTensor(tensorName);
887
888 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
889 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
890 RegisterOutputSlots(layer, {tensorName});
891}
892
893void OnnxParser::ParseConstant(const onnx::NodeProto& node)
894{
895 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +0100896 if (!node.attribute(0).has_t())
897 {
898 throw ParseException(boost::str(
899 boost::format("Value not found for Constant node '%1%' %2%")
900 % node.name()
901 % CHECK_LOCATION().AsString()));
902 }
903 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
904
905 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
Matteo Martincighe355dc22018-12-10 13:45:27 +0000906 CHECK_VALID_DATATYPE(node.name(), onnxTensor.name(),
907 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
telsoa01c577f2c2018-08-31 09:22:23 +0100908
909 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
910 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
Ryan OShea337c17f2020-02-21 12:33:17 +0000911 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
912 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100913
914 CreateConstantLayer(node.output(0), node.name());
telsoa01c577f2c2018-08-31 09:22:23 +0100915}
916
917void OnnxParser::ParseMaxPool(const onnx::NodeProto& node)
918{
919 Pooling2dDescriptor desc;
920 desc.m_PoolType = PoolingAlgorithm::Max;
921 desc.m_PaddingMethod = PaddingMethod::Exclude;
922 AddPoolingLayer(node, desc);
923}
924
925void OnnxParser::ParseGlobalAveragePool(const onnx::NodeProto& node)
926{
927 Pooling2dDescriptor desc = Pooling2dDescriptor();
928 desc.m_PoolType = PoolingAlgorithm::Average;
929
930 //kernel size is the same as input
931 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
932 desc.m_PoolWidth = inputShape[3];
933 desc.m_PoolHeight = inputShape[2];
934
935 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100936 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100937
938 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
939 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
940
941 // register the input connection slots for the layer, connections are made after all layers have been created
942 // only the tensors for the inputs are relevant, exclude the const tensors
943 RegisterInputSlots(layer, {node.input(0)});
944
945 // register the output connection slots for the layer, connections are made after all layers have been created
946 RegisterOutputSlots(layer, {node.output(0)});
947}
948
949void OnnxParser::ParseAveragePool(const onnx::NodeProto& node)
950{
951 Pooling2dDescriptor desc;
952 desc.m_PoolType = PoolingAlgorithm::Average;
953
954 uint32_t count_include_pad = 0;
955 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
956 if(count_include_pad) {
957 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
958 }
959 AddPoolingLayer(node, desc);
960}
961
962void OnnxParser::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
963{
964
965 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
966 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
967
968 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
969
970 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
971 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
972 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
973
974 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
975 desc.m_PoolWidth = kernel_shape[1];
976 desc.m_PoolHeight = kernel_shape[0];
977
978 if(strides.empty())
979 {
980 desc.m_StrideX = 1;
981 desc.m_StrideY = 1;
982 }
983 else
984 {
985 desc.m_StrideX = strides[1];
986 desc.m_StrideY = strides[0];
987 }
988
989 //Check new padding version first
990 if(pads.empty())
991 {
992 //Check deprecated version
993 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
994 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
995 {
996 bool isUpper;
997 if( paddingString == "SAME_LOWER")
998 {
999 isUpper = false;
1000 }
1001 else if (paddingString == "SAME_UPPER")
1002 {
1003 isUpper = true;
1004 }
1005 else
1006 {
1007 throw ParseException(boost::str(
1008 boost::format("Invalid auto_pad attribute for node %1%. "
1009 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
1010 % node.name()
1011 % paddingString
1012 % CHECK_LOCATION().AsString()));
1013 }
1014 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1015 uint32_t inputHeight = inputInfo.GetShape()[2];
1016 uint32_t inputWidth = inputInfo.GetShape()[3];
1017 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1018 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1019 }
1020 }
1021 else
1022 {
1023 desc.m_PadTop = pads[0];
1024 desc.m_PadLeft = pads[1];
1025 desc.m_PadBottom = pads[2];
1026 desc.m_PadRight = pads[3];
1027 }
1028
1029 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001030 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001031
1032 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1033 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1034
1035 // register the input connection slots for the layer, connections are made after all layers have been created
1036 // only the tensors for the inputs are relevant, exclude the const tensors
1037 RegisterInputSlots(layer, {node.input(0)});
1038
1039 // register the output connection slots for the layer, connections are made after all layers have been created
1040 RegisterOutputSlots(layer, {node.output(0)});
1041}
1042
1043void OnnxParser::CreateReshapeLayer(const std::string& inputName,
1044 const std::string& outputName,
1045 const std::string& layerName)
1046{
1047 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1048 ReshapeDescriptor reshapeDesc;
1049 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1050
1051 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001052 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001053 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1054
1055 // register the input connection slots for the layer, connections are made after all layers have been created
1056 // only the tensors for the inputs are relevant, exclude the const tensors
1057 RegisterInputSlots(layer, {inputName});
1058
1059 // register the output connection slots for the layer, connections are made after all layers have been created
1060 RegisterOutputSlots(layer, {outputName});
1061}
1062
1063void OnnxParser::ParseReshape(const onnx::NodeProto& node)
1064{
1065 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1066 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1067
1068 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1069 m_TensorsInfo[node.input(0)].m_dtype,
1070 onnx::TensorProto::FLOAT); //input
1071 CHECK_VALID_DATATYPE(node.name(), node.input(1),
1072 m_TensorsInfo[node.input(1)].m_dtype,
1073 onnx::TensorProto::INT64); //shape
1074
1075 if(!m_TensorsInfo[node.input(1)].isConstant())
1076 {
1077 throw ParseException(boost::str(
1078 boost::format("Shape '%1%' should be constant in Reshape layer '%2%' %3%")
1079 % node.input(1)
1080 % node.name()
1081 % CHECK_LOCATION().AsString()));
1082 }
1083
1084 if(m_TensorsInfo[node.input(0)].isConstant())
1085 {
1086 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
1087 if(m_TensorsInfo.count(node.output(0)) == 0)
1088 {
1089 m_TensorsInfo[node.output(0)] = OnnxTensor();
1090 }
1091 m_TensorsInfo[node.output(0)].m_tensor =
1092 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1093 }
1094 else
1095 {
1096 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1097
1098 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
1099 {
1100 auto outInfo = ComputeReshapeInfo(*m_TensorsInfo[node.input(1)].m_tensor, inputShape, node.output(0));
1101 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1102 }
1103
1104 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1105 }
1106}
1107
Tee Jung7ff9a602019-11-01 07:04:42 +00001108void OnnxParser::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001109{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001110 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001111 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1112
1113 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1114
1115 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001116 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001117
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001118 if (func == ActivationFunction::BoundedReLu)
1119 {
1120 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1121 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1122 }
1123
telsoa01c577f2c2018-08-31 09:22:23 +01001124 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001125 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001126
1127 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1128 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1129
1130 // register the input connection slots for the layer, connections are made after all layers have been created
1131 // only the tensors for the inputs are relevant, exclude the const tensors
1132 RegisterInputSlots(layer, {node.input(0)});
1133
1134 // register the output connection slots for the layer, connections are made after all layers have been created
1135 RegisterOutputSlots(layer, {node.output(0)});
1136}
1137
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001138void OnnxParser::ParseClip(const onnx::NodeProto& node)
1139{
1140 ParseActivation(node, ActivationFunction::BoundedReLu);
1141}
1142
Tee Jung7ff9a602019-11-01 07:04:42 +00001143void OnnxParser::ParseSigmoid(const onnx::NodeProto& node)
1144{
1145 ParseActivation(node, ActivationFunction::Sigmoid);
1146}
1147
1148void OnnxParser::ParseTanh(const onnx::NodeProto& node)
1149{
1150 ParseActivation(node, ActivationFunction::TanH);
1151}
1152
1153void OnnxParser::ParseRelu(const onnx::NodeProto& node)
1154{
1155 ParseActivation(node, ActivationFunction::ReLu);
1156}
1157
1158void OnnxParser::ParseLeakyRelu(const onnx::NodeProto& node)
1159{
1160 ParseActivation(node, ActivationFunction::LeakyReLu);
1161}
telsoa01c577f2c2018-08-31 09:22:23 +01001162
1163void OnnxParser::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
1164{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001165 ARMNN_ASSERT(node.op_type() == "Conv");
telsoa01c577f2c2018-08-31 09:22:23 +01001166
1167 DepthwiseConvolution2dDescriptor desc;
1168 desc.m_PadLeft = convDesc.m_PadLeft;
1169 desc.m_PadRight = convDesc.m_PadRight;
1170 desc.m_PadTop = convDesc.m_PadTop;
1171 desc.m_PadBottom = convDesc.m_PadBottom;
1172 desc.m_StrideX = convDesc.m_StrideX;
1173 desc.m_StrideY = convDesc.m_StrideY;
1174 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
1175
1176 armnn::IConnectableLayer* layer;
1177 auto weightTensor = CreateConstTensor(node.input(1));
1178 TensorShape& weightShape = weightTensor.first.GetShape();
1179 weightShape[1] = weightShape[0];
1180 weightShape[0] = 1;
1181 m_TensorsInfo[node.input(1)].m_info->SetShape(weightShape);
1182
1183 if (node.input_size() == 3)
1184 {
1185 if(!m_TensorsInfo[node.input(2)].isConstant())
1186 {
1187 throw ParseException(boost::str(
1188 boost::format("Bias '%1%' should be constant in Conv layer '%2%' %3%")
1189 % node.input(2)
1190 % node.name()
1191 % CHECK_LOCATION().AsString()));
1192 }
1193 desc.m_BiasEnabled = true;
1194 auto biasTensor = CreateConstTensor(node.input(2));
1195 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1196 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001197 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001198 node.name().c_str());
1199 }
1200 else
1201 {
1202 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1203 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001204 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001205 node.name().c_str());
1206 }
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001207 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001208
1209 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1210 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1211 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1212
1213 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1214
1215 // register the input connection slots for the layer, connections are made after all layers have been created
1216 // only the tensors for the inputs are relevant, exclude the const tensors
1217 RegisterInputSlots(layer, {node.input(0)});
1218
1219 // register the output connection slots for the layer, connections are made after all layers have been created
1220 RegisterOutputSlots(layer, {node.output(0)});
1221}
1222
1223void OnnxParser::ParseConv(const onnx::NodeProto& node)
1224{
1225 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1226 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1227
1228 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1229
1230 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1231 {
1232 throw ParseException(boost::str(
1233 boost::format("ArmNN only supports 2D convolution and Conv layer '%1%' input %2% %3%")
1234 % node.name()
1235 % TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1236 m_TensorsInfo[node.input(0)].m_dtype)
1237 % CHECK_LOCATION().AsString()));
1238 }
1239
1240 if(!m_TensorsInfo[node.input(1)].isConstant())
1241 {
1242 throw ParseException(boost::str(
1243 boost::format("Weights '%1%' should be constant in Conv layer '%2%' %3%")
1244 % node.input(1)
1245 % node.name()
1246 % CHECK_LOCATION().AsString()));
1247 }
1248
1249 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1250
1251 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1252 if (!dilations.empty())
1253 {
1254 std::stringstream ss;
1255 ss << "[ ";
1256 for (auto dilation : dilations)
1257 {
1258 ss << dilation << ", ";
1259 if (dilation != 1u)
1260 {
1261 ss << "... ]";
1262 throw ParseException(boost::str(
1263 boost::format("ArmNN only supports Convolution layers with dilations [1,1], and node '%1%' "
1264 "has dilatation %2% %3%")
1265 % node.name()
1266 % ss.str()
1267 % CHECK_LOCATION().AsString()));
1268 }
1269 }
1270 }
1271
1272 Convolution2dDescriptor desc;
1273 desc.m_BiasEnabled = false;
1274
1275 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1276 if(strides.empty())
1277 {
1278 desc.m_StrideX = 1;
1279 desc.m_StrideY = 1;
1280 }
1281 else
1282 {
1283 desc.m_StrideX = strides[1];
1284 desc.m_StrideY = strides[0];
1285 }
1286
1287 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1288 //Check new padding version first
1289 if(pads.empty())
1290 {
1291 //Check deprecated version
1292 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1293 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1294 {
1295 bool isUpper;
1296 if( paddingString == "SAME_LOWER")
1297 {
1298 isUpper = false;
1299 }
1300 else if (paddingString == "SAME_UPPER")
1301 {
1302 isUpper = true;
1303 }
1304 else
1305 {
1306 throw ParseException(boost::str(
1307 boost::format("Invalid auto_pad attribute for node %1%. "
1308 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
1309 % node.name()
1310 % paddingString
1311 % CHECK_LOCATION().AsString()));
1312 }
1313 uint32_t inputHeight = inputInfo.GetShape()[2];
1314 uint32_t inputWidth = inputInfo.GetShape()[3];
1315
1316 uint32_t weightHeight;
1317 uint32_t weightWidth;
1318 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1319 if (kernel_shape.empty())
1320 {
1321 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1322 weightHeight = weightTensorInfo.GetShape()[2];
1323 weightWidth = weightTensorInfo.GetShape()[3];
1324 }
1325 else
1326 {
1327 weightHeight = kernel_shape[0];
1328 weightWidth = kernel_shape[1];
1329 }
1330 CalcPadding(inputHeight, weightHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1331 CalcPadding(inputWidth, weightWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1332 }
1333 }
1334 else
1335 {
1336 desc.m_PadTop = pads[0];
1337 desc.m_PadLeft = pads[1];
1338 desc.m_PadBottom = pads[2];
1339 desc.m_PadRight = pads[3];
1340 }
1341
1342 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1343 if(group > 1)
1344 {
1345 if (group > inputInfo.GetShape()[1])
1346 {
1347 throw ParseException(
1348 boost::str(
1349 boost::format(
1350 "Error parsing Convolution node: %1%. "
1351 "The 'group'=%2% parameter cannot be larger than the "
1352 "channel of the input shape=%3% (in NCHW format). %4%") %
1353 node.name() %
1354 group %
1355 inputInfo.GetShape()[1] %
1356 CHECK_LOCATION().AsString()));
1357 }
1358 else if (group == inputInfo.GetShape()[1])
1359 {
1360 // we use a depthwise convolution here, because the number of groups equals to the
1361 // input channels
1362 AddConvLayerWithDepthwiseConv(node, desc);
1363 return;
1364 }
1365 else
1366 {
1367 // TODO: split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +01001368 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +01001369 throw ParseException(boost::str(
1370 boost::format("Error parsing Convolution node: %1%. "
1371 "The 'group'=%2% parameter should be 1 or be equal to the "
1372 "channel of the input shape=%3% (in NCHW format). %4%") %
1373 node.name() %
1374 group %
1375 inputInfo.GetShape()[1] %
1376 CHECK_LOCATION().AsString()));
1377 }
1378 }
1379
1380 armnn::IConnectableLayer* layer;
1381 auto weightTensor = CreateConstTensor(node.input(1));
1382
1383 if (node.input_size() == 3)
1384 {
1385 if(!m_TensorsInfo[node.input(2)].isConstant())
1386 {
1387 throw ParseException(boost::str(
1388 boost::format("Bias '%1%' should be constant in Conv layer '%2%' %3%")
1389 % node.input(2)
1390 % node.name()
1391 % CHECK_LOCATION().AsString()));
1392 }
1393 desc.m_BiasEnabled = true;
1394 auto biasTensor = CreateConstTensor(node.input(2));
1395 layer = m_Network->AddConvolution2dLayer(desc,
1396 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001397 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001398 node.name().c_str());
1399 }
1400 else
1401 {
1402 layer = m_Network->AddConvolution2dLayer(desc,
1403 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001404 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001405 node.name().c_str());
1406 }
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001407 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001408
1409 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1410 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1411 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1412 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1413
1414 // register the input connection slots for the layer, connections are made after all layers have been created
1415 // only the tensors for the inputs are relevant, exclude the const tensors
1416 RegisterInputSlots(layer, {node.input(0)});
1417
1418 // register the output connection slots for the layer, connections are made after all layers have been created
1419 RegisterOutputSlots(layer, {node.output(0)});
1420}
1421
1422void OnnxParser::PrependForBroadcast(const std::string& outputName,
1423 const std::string& input0,
1424 const std::string& input1)
1425{
1426 //input0 should be reshaped to have same number of dim as input1
1427 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
1428
1429 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1430 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1431
1432 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
1433 std::vector<uint32_t> newShape;
1434 while(diff > 0)
1435 {
1436 newShape.push_back(1);
1437 diff--;
1438 }
1439 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
1440 {
1441 newShape.push_back(input0Shape[dim]);
1442 }
1443 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1444
1445 //add the new tensor to m_TensorsInfo
1446 m_TensorsInfo[outputName] = OnnxTensor();
1447 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1448
1449 //add reshape layer if the parent was not constant...
1450 if( ! m_TensorsInfo[input0].isConstant())
1451 {
1452 CreateReshapeLayer(input0, outputName, boost::str(boost::format("Add:reshapeOf%1%") % input0));
1453 }
1454 else //make it constant and it will be create in Add
1455 {
1456 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1457
1458 }
1459}
1460
1461std::pair<std::string, std::string> OnnxParser::AddPrepareBroadcast(const std::string& input0,
1462 const std::string& input1)
1463{
1464 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1465
1466 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1467 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1468
1469 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1470 {
1471 auto outputName = boost::str(boost::format("reshape_output_%1%") % input1);
1472 PrependForBroadcast(outputName, input1, input0);
1473 inputs.second = outputName;
1474 }
1475 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1476 {
1477 auto outputName = boost::str(boost::format("reshape_output_%1%") % input0);
1478 PrependForBroadcast(outputName, input0, input1);
1479 inputs.first = outputName;
1480 }
1481 return inputs;
1482}
1483
1484void OnnxParser::ParseAdd(const onnx::NodeProto& node)
1485{
1486 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1487 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1488
1489 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1490
1491 // TODO: unify broadcast validation code across layers
1492 // tracked by: IVGCVSW-1576
1493
1494 // Checking broadcast compatibility : only scalar or 1D tensors
1495 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1496 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1497 auto input1 = *m_TensorsInfo[inputs.second].m_info;
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001498 ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
telsoa01c577f2c2018-08-31 09:22:23 +01001499
1500 unsigned int numDims = input0.GetNumDimensions();
1501 for (unsigned int i = 0; i < numDims; i++)
1502 {
1503 unsigned int dim0 = input0.GetShape()[i];
1504 unsigned int dim1 = input1.GetShape()[i];
1505 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
1506 {
1507 throw ParseException(boost::str(
1508 boost::format("Broadcast is only supported for scalar or 1D tensors in Add node '%1%'. "
1509 "Input dimensions should either match or one should be of size 1 and here, "
1510 "%2% and %3% %4%")
1511 % node.name()
1512 % TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1513 m_TensorsInfo[inputs.first].m_dtype)
1514 % TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1515 m_TensorsInfo[inputs.second].m_dtype)
1516 % CHECK_LOCATION().AsString()));
1517 }
1518 }
1519
1520
1521 IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001522 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001523
1524 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1525 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1526 m_TensorsInfo[inputs.second].m_info->GetShape() });
1527 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1528
1529 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1530 if(m_TensorsInfo[inputs.first].isConstant()) {
telsoa01c577f2c2018-08-31 09:22:23 +01001531 CreateConstantLayer(inputs.first, boost::str(boost::format("Add:constant_of_%1%") % node.input(0)));
1532 }
1533 if(m_TensorsInfo[inputs.second].isConstant()) {
telsoa01c577f2c2018-08-31 09:22:23 +01001534 CreateConstantLayer(inputs.second, boost::str(boost::format("Add:constant_of_%1%") % node.input(1)));
1535 }
1536 RegisterInputSlots(layer, {inputs.first, inputs.second});
1537
1538 // register the output connection
1539 RegisterOutputSlots(layer, {node.output(0)});
1540}
1541
1542void OnnxParser::ParseBatchNormalization(const onnx::NodeProto& node)
1543{
1544 //IGNORE momentum parameter and spatial parameters
1545
1546 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1547 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1548
1549 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1550 for(int ind = 1; ind < node.input_size(); ++ind)
1551 {
1552 auto tensor = node.input(ind);
1553 if(! m_TensorsInfo[tensor].isConstant())
1554 {
1555 throw ParseException(boost::str(
1556 boost::format("Input tensor '%1%' should be constant in BatchNormalization node '%2%' %3%")
1557 % tensor
1558 % node.name()
1559 % CHECK_LOCATION().AsString()));
1560 }
1561 }
1562
1563 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1564 BatchNormalizationDescriptor desc;
1565 desc.m_Eps = epsilon;
1566
1567 auto scaleTensor = CreateConstTensor(node.input(1));
1568 auto biasTensor = CreateConstTensor(node.input(2));
1569 auto meanTensor = CreateConstTensor(node.input(3));
1570 auto varTensor = CreateConstTensor(node.input(4));
1571
1572 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1573 meanTensor.first,
1574 varTensor.first,
1575 biasTensor.first,
1576 scaleTensor.first,
1577 node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001578 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001579
1580 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1581 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1582
1583 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1584
1585 // register the output connection
1586 RegisterOutputSlots(layer, {node.output(0)});
1587}
1588
1589void OnnxParser::SetupInputLayers()
1590{
1591 //Find user input and add their layers
1592 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1593 {
1594 auto input = m_Graph->input(inputIndex);
1595 if (! m_TensorsInfo[input.name()].isConstant())
1596 {
1597 IConnectableLayer* layer =
1598 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
1599 auto tensorInfo = ToTensorInfo(input);
1600 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1601
1602 RegisterOutputSlots(layer,{ input.name() });
1603 }
1604 }
1605}
1606
1607void OnnxParser::SetupOutputLayers()
1608{
1609 if(m_Graph->output_size() == 0)
1610 {
1611 throw ParseException(boost::str(boost::format("The given model does not have any outputs %1%")
1612 % CHECK_LOCATION().AsString()));
1613 }
1614
1615 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
1616 {
1617 IConnectableLayer* layer =
1618 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
1619 m_Graph->output(outputIndex).name().c_str());
1620
1621 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
1622 }
1623}
1624
1625void OnnxParser::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1626{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001627 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001628 if (tensorIds.size() != layer->GetNumInputSlots())
1629 {
1630 throw ParseException(
1631 boost::str(boost::format("The number of tensor inputs (%1%) does not match the number expected (%2%) %3%") %
1632 tensorIds.size() %
1633 layer->GetNumInputSlots() %
1634 CHECK_LOCATION().AsString()));
1635 }
1636 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
1637 {
1638 std::string tensorId = tensorIds[slotIndex];
1639 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
1640
1641 auto it = m_TensorConnections.find(tensorId);
1642
1643 if (it == m_TensorConnections.end())
1644 {
1645 //First time seing this tensor, we need to map it
1646 m_TensorConnections[tensorId] = TensorSlots();
1647 }
1648 m_TensorConnections[tensorId].inputSlots.push_back(slot);
1649 }
1650}
1651
1652void OnnxParser::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1653{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001654 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001655 if (tensorIds.size() != layer->GetNumOutputSlots())
1656 {
1657 throw ParseException(
1658 boost::str(boost::format("The number of tensor outputs (%1%) does not match the number expected (%2%) %3% ")
1659 % tensorIds.size()
1660 % layer->GetNumOutputSlots()
1661 % CHECK_LOCATION().AsString()));
1662 }
1663
1664 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
1665 {
1666 std::string tensorId = tensorIds[slotIndex];
1667 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
1668
1669 auto it = m_TensorConnections.find(tensorId);
1670
1671 if (it == m_TensorConnections.end())
1672 {
1673 //First time seing this tensor, we need to map it
1674 m_TensorConnections[tensorId] = TensorSlots();
1675 }
1676
Ryan OShea337c17f2020-02-21 12:33:17 +00001677 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01001678
1679 // assuming there is only one producer for that tensor
1680 if (tensorSlots.outputSlot != nullptr)
1681 {
1682 throw ParseException(boost::str(
1683 boost::format("Another layer has already registered itself as the producer of "
Ryan OShea337c17f2020-02-21 12:33:17 +00001684 "tensor:%1% %2%")
1685 % tensorId
1686 % CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001687 }
1688 tensorSlots.outputSlot = slot;
1689 }
1690}
1691
1692BindingPointInfo OnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
1693{
1694 for(int i = 0; i < m_Graph->input_size(); ++i)
1695 {
1696 auto input = m_Graph->input(i);
1697 if(input.name() == name)
1698 {
1699 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(input));
1700 }
1701 }
1702 throw InvalidArgumentException(boost::str(boost::format("The input layer '%1%' does not exist %2%")
1703 % name % CHECK_LOCATION().AsString()));
1704}
1705
1706BindingPointInfo OnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
1707{
1708 for(int i = 0; i < m_Graph->output_size(); ++i)
1709 {
1710 auto output = m_Graph->output(i);
1711 if(output.name() == name)
1712 {
1713 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(output));
1714 }
1715 }
1716 throw InvalidArgumentException(boost::str(boost::format("The output layer '%1%' does not exist %2%")
1717 % name % CHECK_LOCATION().AsString()));
1718}
1719
1720std::vector<std::string> OnnxParser::GetInputs(ModelPtr& model)
1721{
1722 if(model == nullptr) {
1723 throw InvalidArgumentException(boost::str(
1724 boost::format("The given model cannot be null %1%")
1725 % CHECK_LOCATION().AsString()));
1726 }
1727
1728 std::vector<std::string> inputNames;
1729 std::map<std::string, bool> isConstant;
1730 for(auto tensor : model->graph().initializer())
1731 {
1732 isConstant[tensor.name()] = true;
1733 }
1734 for(auto input : model->graph().input())
1735 {
1736 auto it = isConstant.find(input.name());
1737 if(it == isConstant.end())
1738 {
1739 inputNames.push_back(input.name());
1740 }
1741 }
1742 return inputNames;
1743}
1744
1745std::vector<std::string> OnnxParser::GetOutputs(ModelPtr& model)
1746{
1747 if(model == nullptr) {
1748 throw InvalidArgumentException(boost::str(
1749 boost::format("The given model cannot be null %1%")
1750 % CHECK_LOCATION().AsString()));
1751 }
1752
1753 std::vector<std::string> outputNames;
1754 for(auto output : model->graph().output())
1755 {
1756 outputNames.push_back(output.name());
1757 }
1758 return outputNames;
1759}
1760
1761} // namespace armnnOnnxParser