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