ArmNN
 22.02
OnnxParser.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include "OnnxParser.hpp"
6 
8 
9 #include <armnn/Descriptors.hpp>
10 #include <armnn/utility/Assert.hpp>
12 #include <ParserHelper.hpp>
13 #include <VerificationHelpers.hpp>
14 
15 #include <fmt/format.h>
16 
17 #include <google/protobuf/text_format.h>
18 #include <google/protobuf/io/zero_copy_stream_impl.h>
19 
20 #include <iostream>
21 #include <numeric>
22 #include <armnnUtils/Permute.hpp>
23 
24 using namespace armnn;
25 
26 namespace armnnOnnxParser
27 {
28 
29 IOnnxParser::IOnnxParser() : pOnnxParserImpl(new OnnxParserImpl()) {}
30 
31 IOnnxParser::~IOnnxParser() = default;
32 
33 IOnnxParser* IOnnxParser::CreateRaw()
34 {
35  return new IOnnxParser();
36 }
37 
38 IOnnxParserPtr IOnnxParser::Create()
39 {
40  return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
41 }
42 
43 void IOnnxParser::Destroy(IOnnxParser* parser)
44 {
45  delete parser;
46 }
47 
48 armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
49 {
50  return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile);
51 }
52 
53 armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile)
54 {
55  return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile);
56 }
57 
58 armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText)
59 {
60  return pOnnxParserImpl->CreateNetworkFromString(protoText);
61 }
62 
63 armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinaryFile(
64  const char* graphFile,
65  const std::map<std::string, armnn::TensorShape>& inputShapes)
66 {
67  return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile, inputShapes);
68 }
69 
70 armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile,
71  const std::map<std::string, armnn::TensorShape>& inputShapes)
72 {
73  return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile, inputShapes);
74 }
75 
76 armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText,
77  const std::map<std::string, armnn::TensorShape>& inputShapes)
78 {
79  return pOnnxParserImpl->CreateNetworkFromString(protoText, inputShapes);
80 }
81 
82 BindingPointInfo IOnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
83 {
84  return pOnnxParserImpl->GetNetworkInputBindingInfo(name);
85 }
86 
87 BindingPointInfo IOnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
88 {
89  return pOnnxParserImpl->GetNetworkOutputBindingInfo(name);
90 }
91 
92 namespace
93 {
94 void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
95  const onnx::TensorProto::DataType actualValue,
96  const char* validExpr,
97  std::string nodeName,
98  std::string tensorName,
99  const armnn::CheckLocation& location)
100 {
101  bool isValid = std::any_of(validInputTypes.begin(),
102  validInputTypes.end(),
103  [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
104  if (!isValid)
105  {
106  throw ParseException(
107  fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
108  onnx::TensorProto::DataType_Name(actualValue),
109  tensorName,
110  nodeName,
111  validExpr,
112  location.AsString()));
113  }
114 }
115 
116 #define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
117 CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
118 
119 using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
120 #define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
121 
122 template <typename Callable>
123 void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
124  const std::string& attribName,
125  onnx::AttributeProto::AttributeType expectedType,
126  Callable callable)
127 {
128  auto attribs = node.attribute();
129  int attriNum = 0;
130  while (attriNum < node.attribute_size())
131  {
132  if (attribs.Get(attriNum).name() == attribName)
133  {
134  if (attribs.Get(attriNum).type() == expectedType)
135  {
136  callable(attribs.Get(attriNum));
137  }
138  else
139  {
140  throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
141  "onnx::AttributeProto::AttributeType, but found {} instead {}",
142  attribName,
143  node.name(),
144  onnx::AttributeProto::AttributeType_Name(expectedType),
145  onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
146  CHECK_LOCATION().AsString()));
147  }
148  break;
149  }
150  ++attriNum;
151  }
152  if (attriNum == node.attribute_size())
153  {
154  throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
155  attribName, node.name(), CHECK_LOCATION().AsString()));
156  }
157 }
158 
159 template <typename Callable>
160 void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
161  const std::string& attribName,
162  onnx::AttributeProto::AttributeType expectedType,
163  Callable callable)
164 {
165  auto attribs = node.attribute();
166  for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
167  {
168  if (attribs.Get(attriNum).name() == attribName)
169  {
170  if (attribs.Get(attriNum).type() == expectedType)
171  {
172  callable(attribs.Get(attriNum));
173  }
174  else
175  {
176  throw ParseException(
177  fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
178  "but found {} instead {}",
179  attribName,
180  node.name(),
181  onnx::AttributeProto::AttributeType_Name(expectedType),
182  onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
183  CHECK_LOCATION().AsString()));
184  }
185  }
186  }
187 }
188 
189 int ReadMandatoryNodeIntAttribute(const onnx::NodeProto& node,
190  const std::string& name)
191 {
192  int attribValue = 0;
193  ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
194  [&attribValue](const onnx::AttributeProto& attrValue)
195  {
196  attribValue = CHECKED_INT32(attrValue.i());
197  });
198  return attribValue;
199 }
200 
201 int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
202  const std::string& name,
203  const int64_t defaultValue = 0)
204 {
205  int64_t attribValue = defaultValue;
206  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
207  [&attribValue](const onnx::AttributeProto& attrValue)
208  {
209  attribValue = attrValue.i();
210  });
211  return attribValue;
212 }
213 
214 std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
215  const std::string& name)
216 {
217  std::vector<uint32_t> attriList;
218  ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
219  [&attriList](const onnx::AttributeProto& attrValue)
220  {
221  for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
222  {
223  attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
224  }
225  });
226  return attriList;
227 }
228 
229 uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
230  const std::string& name,
231  const uint32_t defaultVal = 0u)
232 {
233  uint32_t attribValue = defaultVal;
234  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
235  [&attribValue](const onnx::AttributeProto& attrValue)
236  {
237  attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
238  });
239  return attribValue;
240 }
241 
242 std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
243  const std::string& name)
244 {
245  std::vector<uint32_t> attriList;
246  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
247  [&attriList](const onnx::AttributeProto& attrValue)
248  {
249  for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
250  {
251  attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
252  }
253  });
254 
255  return attriList;
256 }
257 
258 float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
259  const std::string& name,
260  const float defaultValue = 0.0f)
261 {
262  float attribValue = defaultValue;
263  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
264  [&attribValue](const onnx::AttributeProto& attrValue)
265  {
266  attribValue = attrValue.f();
267  });
268  return attribValue;
269 }
270 
271 std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
272 {
273  std::string attribValue = "";
274  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
275  [&attribValue](const onnx::AttributeProto& attrValue)
276  {
277  attribValue = attrValue.s();
278  });
279  return attribValue;
280 }
281 
282 armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
283 {
284  DataType type;
285  switch(data_type)
286  {
287  case onnx::TensorProto::FLOAT:
288  {
289  type = DataType::Float32;
290  break;
291  }
292  case onnx::TensorProto::INT32:
293  case onnx::TensorProto::INT64:
294  {
295  type = DataType::Signed32;
296  break;
297  }
298  default:
299  {
300  throw ParseException(
301  fmt::format("'{}' is not a currently supported datatype for tensor {}."
302  " Supported dataTypes are FLOAT, INT32 and INT64. {}",
303  onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
304  name,
305  CHECK_LOCATION().AsString() ));
306  }
307  }
308 
309  // Scalar Tensor
310  if (shape.empty())
311  {
312  return TensorInfo(TensorShape(Dimensionality::Scalar), type);
313  }
314 
315  // Dynamic Tensor
316  if(std::find(shape.begin(), shape.end(), 0) != shape.end())
317  {
318  return TensorInfo(TensorShape(Dimensionality::NotSpecified), type);
319  }
320 
321  return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
322 }
323 
324 armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
325 {
326  const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
327  std::vector<unsigned int> shapeDims;
328  for (int i = 0; i < onnxShape.dim_size(); ++i)
329  {
330  shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
331  }
332 
333  return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
334 }
335 
336 armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
337 {
338  std::vector<unsigned int> shapeDims;
339 
340  for (auto dim: tensor.dims())
341  {
342  shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
343  }
344 
345  return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
346 }
347 
348 std::string TensorInfoAsString(const TensorInfo& info,
349  const std::string& name,
350  const onnx::TensorProto::DataType& type)
351 {
352  const TensorShape shape = info.GetShape();
353  std::stringstream ss;
354  ss << "tensor '" << name << "' contains "
355  << onnx::TensorProto::DataType_Name(type)
356  << " and has shape [";
357 
358  for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
359  {
360  ss << shape[i] << ", ";
361  }
362  ss << shape[shape.GetNumDimensions() - 1] << "]";
363  return ss.str();
364 }
365 
366 void CalcPadding(uint32_t inputSize,
367  uint32_t filterSize,
368  uint32_t stride,
369  uint32_t dilation,
370  uint32_t* paddingFront,
371  uint32_t* paddingBack,
372  bool isUpper)
373 {
374  uint32_t outputSize = (inputSize + stride - 1) / stride;
375  uint32_t dilatedSize = filterSize + (dilation - 1) * (filterSize - 1);
376  uint32_t temp = (outputSize - 1) * stride + dilatedSize;
377  *paddingFront = (temp - inputSize) / 2;
378  *paddingBack = *paddingFront;
379  if((temp - inputSize) % 2 == 1)
380  {
381  if (isUpper)
382  {
383  *paddingBack += 1;
384  }
385  else
386  {
387  *paddingFront += 1;
388  }
389  }
390 }
391 
392 TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
393  const TensorShape& inShape,
394  const std::string& outName,
395  DataType dataType = DataType::Float32)
396 {
397  std::vector<int> targetDims;
398  for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
399  {
400  int val = CHECKED_INT32(targetShapeTensor[i]);
401  if(val == 0)
402  {
403  targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
404  }
405  else
406  {
407  targetDims.push_back(val);
408  }
409  }
410 
411  std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
412  const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
413  if (stretchDim != targetDims.end())
414  {
415  if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
416  {
417  std::stringstream ss;
418  ss << "[ ";
419  for(uint i = 0; i < targetDims.size() - 1; ++i)
420  {
421  ss << targetDims[i] << ", ";
422  }
423  ss << targetDims[targetDims.size() - 1] << " ]";
424 
425  throw ParseException(
426  fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
427  " -1 and here, shape is {} {}",
428  outName,
429  ss.str(),
430  CHECK_LOCATION().AsString()));
431  }
432 
433  auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
434  -1, std::multiplies<int32_t>()));
435  auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
436  outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
437  }
438  TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
439  return TensorInfo(outShape, dataType);
440 }
441 
442 } //namespace
443 
444 const std::map<std::string, OnnxParserImpl::OperationParsingFunction> OnnxParserImpl::m_ParserFunctions = {
445  { "BatchNormalization", &OnnxParserImpl::ParseBatchNormalization},
446  { "GlobalAveragePool", &OnnxParserImpl::ParseGlobalAveragePool},
447  { "AveragePool", &OnnxParserImpl::ParseAveragePool },
448  { "Clip", &OnnxParserImpl::ParseClip },
449  { "Constant", &OnnxParserImpl::ParseConstant },
450  { "MaxPool", &OnnxParserImpl::ParseMaxPool },
451  { "Reshape", &OnnxParserImpl::ParseReshape },
452  { "Sigmoid", &OnnxParserImpl::ParseSigmoid },
453  { "Tanh", &OnnxParserImpl::ParseTanh },
454  { "Relu", &OnnxParserImpl::ParseRelu },
455  { "LeakyRelu", &OnnxParserImpl::ParseLeakyRelu },
456  { "Conv", &OnnxParserImpl::ParseConv },
457  { "Add", &OnnxParserImpl::ParseAdd },
458  { "Flatten", &OnnxParserImpl::ParseFlatten },
459  { "Shape", &OnnxParserImpl::ParseShape },
460  { "Gather", &OnnxParserImpl::ParseGather },
461  { "Unsqueeze", &OnnxParserImpl::ParseUnsqueeze },
462  { "Concat", &OnnxParserImpl::ParseConcat },
463  { "Gemm", &OnnxParserImpl::ParseGemm }
464 };
465 
466 template<typename TypePair, typename Location>
467 void OnnxParserImpl::ValidateInputs(const onnx::NodeProto& node,
468  TypePair validInputs,
469  const Location& location)
470 {
471  for(auto input : node.input())
472  {
473  CheckValidDataType(validInputs.second,
474  m_TensorsInfo[input].m_dtype,
475  validInputs.first,
476  node.name(),
477  input,
478  location);
479  }
480 }
481 
482 #define VALID_INPUTS(NODE, VALID_INPUTS) \
483  OnnxParserImpl::ValidateInputs(NODE, \
484  VALID_INPUTS, \
485  CHECK_LOCATION())
486 
487 std::vector<TensorInfo> OnnxParserImpl::ComputeOutputInfo(std::vector<std::string> outNames,
488  const IConnectableLayer* layer,
489  std::vector<TensorShape> inputShapes,
490  const onnx::TensorProto::DataType& dataType)
491 {
492  ARMNN_ASSERT(! outNames.empty());
493  bool needCompute = std::any_of(outNames.begin(),
494  outNames.end(),
495  [this](std::string name)
496  {
497  return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr
498  || m_TensorsInfo[name].m_info->GetShape().GetDimensionality() ==
499  Dimensionality::NotSpecified);
500  });
501  std::vector<TensorInfo> outInfo;
502  //if the output info(s) are not here, we need to compute them
503  std::vector<TensorShape> inferredShapes;
504  DataType armnnType = DataType::Float32;
505  if(needCompute) {
506  inferredShapes = layer->InferOutputShapes(inputShapes);
507  ARMNN_ASSERT(inferredShapes.size() == outNames.size());
508  switch (dataType) {
509  case onnx::TensorProto::FLOAT: {
510  armnnType = DataType::Float32;
511  break;
512  }
513  case onnx::TensorProto::INT32:
514  case onnx::TensorProto::INT64: {
515  armnnType = DataType::Signed32;
516  break;
517  }
518  default: {
519  throw ParseException(
520  fmt::format("'{}' is not a currently supported datatype for {}."
521  " Supported dataTypes are FLOAT, INT32 and INT64. {}",
522  onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(dataType)),
523  layer->GetName(),
524  CHECK_LOCATION().AsString()));
525  }
526  }
527  }
528  for (uint i = 0; i < outNames.size(); ++i)
529  {
530  if(needCompute)
531  {
532  m_TensorsInfo[outNames[i]] = OnnxTensor();
533  m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
534  TensorInfo(inferredShapes[i], armnnType));
535  m_TensorsInfo[outNames[i]].m_dtype = dataType;
536  }
537  outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
538  }
539  return outInfo;
540 }
541 
542 OnnxParserImpl::OnnxParserImpl()
543  : m_Network(nullptr, nullptr)
544 {
545 }
546 
547 void OnnxParserImpl::ResetParser()
548 {
549  m_Network = armnn::INetworkPtr(nullptr, nullptr);
550  m_Graph = nullptr;
551  m_InputInfos.clear();
552  m_OutputInfos.clear();
553 }
554 
555 void OnnxParserImpl::Cleanup()
556 {
557  m_TensorConnections.clear();
558  m_TensorsInfo.clear();
559  m_OutputsMap.clear();
560  m_OutputsFusedAndUsed.clear();
561  m_InputShapes.clear();
562 }
563 
564 template<typename T>
565 std::pair<armnn::ConstTensor, std::unique_ptr<T[]>>
566 CreateConstTensorImpl(const T* bufferPtr,
567  armnn::TensorInfo& tensorInfo,
568  const armnn::Optional<armnn::PermutationVector&> permutationVector)
569 {
570  ARMNN_ASSERT_MSG(bufferPtr != nullptr, fmt::format("Buffer for permutation is null").c_str());
571 
572  std::unique_ptr<T[]> data(new T[tensorInfo.GetNumElements()]);
573 
574  if (permutationVector.has_value() && permutationVector.value().GetSize() > 0)
575  {
576  tensorInfo = armnnUtils::Permuted(tensorInfo, permutationVector.value());
577  armnnUtils::Permute(tensorInfo.GetShape(), permutationVector.value(),
578  reinterpret_cast<const T*>(bufferPtr), data.get(), sizeof(T));
579  }
580  else
581  {
582  ::memcpy(data.get(), bufferPtr, tensorInfo.GetNumBytes());
583  }
584 
585  return std::make_pair(ConstTensor(tensorInfo, data.get()), std::move(data));
586 }
587 
588 std::pair<ConstTensor, std::unique_ptr<float[]>>
589 OnnxParserImpl::CreateConstTensor(const std::string name,
591 {
592  TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
593  onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
594 
595  //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
596  CHECK_VALID_DATATYPE(name, onnxTensor.name(),
597  static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
598 
599  // Makes sure IsConstant flag is set.
600  tensorInfo.SetConstant();
601 
602  // Const tensors requires at least a list of values
603  if (tensorInfo.GetNumElements() == 0)
604  {
605  throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
606  name,
607  CHECK_LOCATION().AsString()));
608  }
609 
610  auto srcData = onnxTensor.float_data().data();
611  // Copy the value list entries into the destination
612  if (!onnxTensor.has_raw_data())
613  {
614  if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
615  {
616  throw ParseException(
617  fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
618  "elements ({}) {}",
619  onnxTensor.float_data_size(),
620  name,
621  tensorInfo.GetNumElements(),
622  CHECK_LOCATION().AsString()));
623  }
624  return CreateConstTensorImpl<float>(srcData, tensorInfo, permutationVector);
625  }
626  else
627  {
628  return CreateConstTensorImpl<float>(reinterpret_cast<const float*>(onnxTensor.raw_data().c_str()),
629  tensorInfo,
630  permutationVector);
631  }
632 }
633 
634 std::pair<ConstTensor, std::unique_ptr<int32_t[]>>
635 OnnxParserImpl::CreateInt64ConstTensor(const std::string name,
637 {
638  TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
639  onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
640 
641  CHECK_VALID_DATATYPE(name, onnxTensor.name(),
642  static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::INT64);
643 
644  // Makes sure IsConstant flag is set.
645  tensorInfo.SetConstant();
646  uint numElements = tensorInfo.GetNumElements();
647 
648  // Const tensors requires at least a list of values
649  if (numElements == 0)
650  {
651  throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
652  name,
653  CHECK_LOCATION().AsString()));
654  }
655 
656  // Copy the value list entries into the destination
657  if (!onnxTensor.has_raw_data())
658  {
659  auto srcData = onnxTensor.int64_data().data();
660  if(numElements != static_cast<uint>(onnxTensor.int64_data_size()))
661  {
662  throw ParseException(
663  fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
664  "elements ({}) {}",
665  onnxTensor.int64_data_size(),
666  name,
667  tensorInfo.GetNumElements(),
668  CHECK_LOCATION().AsString()));
669  }
670 
671  std::vector<int32_t> int32Data;
672  for(uint i = 0; i < numElements; i++)
673  {
674  int32_t int32Value = CHECKED_INT32(srcData[i]);
675  int32Data.push_back(int32Value);
676  }
677 
678  return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
679  }
680  else
681  {
682  auto srcData = reinterpret_cast<const int64_t*>(onnxTensor.raw_data().c_str());
683  std::vector<int32_t> int32Data;
684  for(uint i = 0; i < numElements; i++)
685  {
686  int32_t int32Value = CHECKED_INT32(srcData[i]);
687  int32Data.push_back(int32Value);
688  }
689  return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
690  }
691 }
692 
694 {
695  FILE* fd = fopen(graphFile, "r");
696 
697  if (fd == nullptr)
698  {
699  throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
700  }
701 
702  // Parse the file into a message
703  ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
704  using google::protobuf::io::FileInputStream;
705  std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
706  bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
707  fclose(fd);
708 
709  if (!success)
710  {
711  std::stringstream error;
712  error << "Failed to parse graph file";
713  throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
714  }
715  return modelProto;
716 }
717 
719 {
720  ResetParser();
721  ModelPtr modelProto = LoadModelFromTextFile(graphFile);
722  return CreateNetworkFromModel(*modelProto);
723 }
724 
726  const std::map<std::string, armnn::TensorShape>& inputShapes)
727 {
728  ResetParser();
729  m_InputShapes = inputShapes;
730  ModelPtr modelProto = LoadModelFromTextFile(graphFile);
731  return CreateNetworkFromModel(*modelProto);
732 }
733 
735 {
736  FILE* fd = fopen(graphFile, "rb");
737 
738  if (fd == nullptr)
739  {
740  throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
741  }
742 
743  // Parse the file into a message
744  ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
745 
746  google::protobuf::io::FileInputStream inStream(fileno(fd));
747  google::protobuf::io::CodedInputStream codedStream(&inStream);
748  codedStream.SetTotalBytesLimit(INT_MAX);
749  bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
750  fclose(fd);
751 
752  if (!success)
753  {
754  std::stringstream error;
755  error << "Failed to parse graph file";
756  throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
757  }
758  return modelProto;
759 
760 }
761 
763 {
764  ResetParser();
765  ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
766  return CreateNetworkFromModel(*modelProto);
767 }
768 
770  const std::map<std::string, armnn::TensorShape>& inputShapes)
771 {
772  ResetParser();
773  m_InputShapes = inputShapes;
774  ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
775  return CreateNetworkFromModel(*modelProto);
776 }
777 
778 ModelPtr OnnxParserImpl::LoadModelFromString(const std::string& protoText)
779 {
780  if (protoText == "")
781  {
782  throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
783  CHECK_LOCATION().AsString()));
784  }
785  // Parse the string into a message
786  ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
787  bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
788  if (!success)
789  {
790  std::stringstream error;
791  error << "Failed to parse graph file";
792  throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
793  }
794  return modelProto;
795 }
796 
798 {
799  ResetParser();
800  ModelPtr modelProto = LoadModelFromString(protoText);
801  return CreateNetworkFromModel(*modelProto);
802 }
803 
804 INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText,
805  const std::map<std::string, armnn::TensorShape>& inputShapes)
806 {
807  ResetParser();
808  m_InputShapes = inputShapes;
809  ModelPtr modelProto = LoadModelFromString(protoText);
810  return CreateNetworkFromModel(*modelProto);
811 }
812 
813 INetworkPtr OnnxParserImpl::CreateNetworkFromModel(onnx::ModelProto& model)
814 {
815  m_Network = INetwork::Create();
816  try
817  {
818  m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
819  LoadGraph();
820  }
821  catch (const ParseException& e)
822  {
823  Cleanup();
824  throw e;
825  }
826  Cleanup();
827  return std::move(m_Network);
828 }
829 
830 void OnnxParserImpl::LoadGraph()
831 {
832  ARMNN_ASSERT(m_Graph.get() != nullptr);
833 
834  //Fill m_TensorsInfo with the shapes and value of every tensor
835  SetupInfo(m_Graph->mutable_output());
836  SetupInfo(m_Graph->mutable_input());
837  SetupInfo(m_Graph->mutable_value_info());
838 
839  for (auto tensor : m_Graph->initializer())
840  {
841  m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
842  m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
843  m_TensorsInfo[tensor.name()].m_dtype =
844  static_cast<onnx::TensorProto::DataType>(tensor.data_type());
845  }
846 
847  SetupInputLayers();
848  SetupOutputLayers();
849 
850  //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
851  DetectFullyConnected();
852 
853  //Parsing the graph
854  for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
855  {
856  auto node = m_Graph->node(static_cast<int>(nodeIndex));
857  const std::string& operation = node.op_type();
858 
859  // check which layers we handled already (add and matmul fused as FC)
860  if (operation == "MatMul" )
861  {
862  if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
863  {
864  //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
865  AddFullyConnected(node);
866  }
867  }
868  else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
869  {
870  int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
871  AddFullyConnected(m_Graph->node(matmulIndex), &node);
872  }
873  else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
874  {
875  auto it = m_ParserFunctions.find(operation);
876  if (it != m_ParserFunctions.end())
877  {
878  auto func = it->second;
879  (this->*func)(node);
880  }
881  else
882  {
883  throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
884  operation,
885  node.name(),
886  CHECK_LOCATION().AsString()));
887  }
888  }
889  }
890 
891  //Making the connections between outputs and inputs of each layers
892  for (const auto& tensorCon : m_TensorConnections)
893  {
894  if (tensorCon.second.outputSlot != nullptr)
895  {
896  for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
897  {
898  tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
899  }
900  }
901  }
902 
903  // Get output info.
904  for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
905  {
906  auto output = m_Graph->output(outputIndex);
907  m_OutputInfos[output.name()] = *m_TensorsInfo[output.name()].m_info;
908  }
909 }
910 
911 void OnnxParserImpl::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
912 {
913  for (auto tensor : *list)
914  {
915  m_TensorsInfo[tensor.name()] = OnnxTensor();
916  m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
917  m_TensorsInfo[tensor.name()].m_dtype =
918  static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
919  }
920 }
921 
922 void OnnxParserImpl::DetectFullyConnected()
923 {
924  m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
925  auto matmulAndConstant = [&](const std::string& constInput,
926  const std::string& matmulInput,
927  int& nodeIndex)
928  {
929  auto matmulIt = m_OutputsMap.find(matmulInput);
930  if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
931  && m_TensorsInfo[constInput].isConstant())
932  {
933  nodeIndex = matmulIt->second.second;
934  return true;
935  }
936  return false;
937  };
938 
939  for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
940  {
941  const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
942  for (const std::string& output : node->output())
943  {
944  m_OutputsMap[output] = std::make_pair(node, nodeIndex);
945  }
946 
947  for (const std::string& input : node->input()) //count how many time a node is used as input
948  {
949  auto matmulIt = m_OutputsMap.find(input);
950  if(matmulIt != m_OutputsMap.end()){
951  ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
952  }
953  }
954 
955  if (node->op_type() == "Add")
956  {
957  int matmulIndex = 0;
958  if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
959  matmulAndConstant(node->input(1), node->input(0), matmulIndex))
960  {
961  //matmul and add were fused
962  m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
963  .push_back(static_cast<size_t>(nodeIndex));
964 
965  m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
966  .push_back(static_cast<size_t>(matmulIndex));
967  }
968  }
969  }
970 
971  for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
972  auto matmulIt = m_OutputsMap.find(output.name());
973  if(matmulIt != m_OutputsMap.end()){
974  ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
975  }
976  }
977 }
978 
979 template<typename Location>
980 void OnnxParserImpl::GetInputAndParam(const onnx::NodeProto& node,
981  std::string* inputName,
982  std::string* constName,
983  const Location& location)
984 {
985  int cstIndex;
986  if (m_TensorsInfo[node.input(0)].isConstant())
987  {
988  cstIndex = 0;
989  }
990  else if (m_TensorsInfo[node.input(1)].isConstant())
991  {
992  cstIndex = 1;
993  }
994  else
995  {
996  throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
997  node.input(0),
998  node.input(1),
999  node.name(),
1000  location.AsString()));
1001  }
1002  if(constName)
1003  {
1004  *constName = node.input(cstIndex);
1005  }
1006  if(inputName)
1007  {
1008  *inputName = node.input(!cstIndex);
1009  }
1010 }
1011 
1012 template<typename Location>
1013 void OnnxParserImpl::To1DTensor(const std::string& name, const Location& location)
1014 {
1015  TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
1016  std::vector<uint32_t> newShape;
1017  for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
1018  {
1019  if(shape[i] != 1)
1020  {
1021  throw ParseException(
1022  fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
1023  TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
1024  location.AsString()));
1025  }
1026  }
1027  newShape.push_back(shape[shape.GetNumDimensions() - 1]);
1028 
1029  m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1030 }
1031 
1032 void OnnxParserImpl::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
1033 {
1034  ARMNN_ASSERT(node.op_type() == "Conv");
1035 
1037  desc.m_PadLeft = convDesc.m_PadLeft;
1038  desc.m_PadRight = convDesc.m_PadRight;
1039  desc.m_PadTop = convDesc.m_PadTop;
1040  desc.m_PadBottom = convDesc.m_PadBottom;
1041  desc.m_StrideX = convDesc.m_StrideX;
1042  desc.m_StrideY = convDesc.m_StrideY;
1043  desc.m_BiasEnabled = convDesc.m_BiasEnabled;
1044 
1045  armnn::IConnectableLayer* layer;
1046 
1047  // weights come in as [O,1,H,W] from ONNX and need to be converted to ArmNNs dephtwise weights layout [1,H,W,O]
1048  armnn::PermutationVector perVec {3,0,1,2};
1049  auto weightTensor = CreateConstTensor(node.input(1), perVec);
1050 
1051  if (node.input_size() == 3)
1052  {
1053  if(!m_TensorsInfo[node.input(2)].isConstant())
1054  {
1055  throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1056  node.input(2),
1057  node.name(),
1058  CHECK_LOCATION().AsString()));
1059  }
1060  desc.m_BiasEnabled = true;
1061  auto biasTensor = CreateConstTensor(node.input(2));
1062  layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1063  weightTensor.first,
1064  Optional<ConstTensor>(biasTensor.first),
1065  node.name().c_str());
1066  }
1067  else
1068  {
1069  layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1070  weightTensor.first,
1071  EmptyOptional(),
1072  node.name().c_str());
1073  }
1074  ARMNN_ASSERT(layer != nullptr);
1075 
1076  auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1077  { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1078  weightTensor.first.GetInfo().GetShape() });
1079 
1080  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1081 
1082  // register the input connection slots for the layer, connections are made after all layers have been created
1083  // only the tensors for the inputs are relevant, exclude the const tensors
1084  RegisterInputSlots(layer, {node.input(0)});
1085 
1086  // register the output connection slots for the layer, connections are made after all layers have been created
1087  RegisterOutputSlots(layer, {node.output(0)});
1088 }
1089 
1090 void OnnxParserImpl::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
1091 {
1092 
1093  // find matmul inputs
1094  std::string weightName;
1095  std::string inputName;
1096  CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
1097  CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
1098  VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
1099 
1100  GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
1101 
1103  desc.m_BiasEnabled = addNode != nullptr;
1104 
1105  IConnectableLayer* layer = nullptr;
1106  if(desc.m_BiasEnabled)
1107  {
1108  // find bias const
1109  std::string biasName;
1110  CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
1111  CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
1112  VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
1113 
1114  GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
1115 
1116  //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
1117  To1DTensor(biasName, CHECK_LOCATION());
1118  TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1119  TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
1120 
1121  if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
1122  {
1123  throw ParseException(
1124  fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
1125  " and {} ( /!\\ bias should be a 1D tensor) {}",
1126  weightName,
1127  addNode->name(),
1128  TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
1129  m_TensorsInfo[weightName].m_dtype),
1130  TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
1131  m_TensorsInfo[biasName].m_dtype ),
1132  CHECK_LOCATION().AsString()));
1133  }
1134 
1135  // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1136  layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
1137  ARMNN_ASSERT(layer != nullptr);
1138 
1139  auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
1140  {m_TensorsInfo[inputName].m_info->GetShape(),
1141  m_TensorsInfo[weightName].m_info->GetShape()});
1142  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1143 
1144  // Add constant layer to store weights/biases and connect to FullyConnected layer..
1145  if(m_TensorsInfo[weightName].isConstant())
1146  {
1147  IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1148 
1149  weightInfo.SetConstant();
1150  weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1151  weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1152  }
1153 
1154  if(m_TensorsInfo[biasName].isConstant())
1155  {
1156  IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(biasName).first);
1157 
1158  biasInfo.SetConstant();
1159  biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1160  biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1161  }
1162 
1163  RegisterInputSlots(layer, {inputName, weightName, biasName});
1164  RegisterOutputSlots(layer, {addNode->output(0)});
1165  }
1166  else
1167  {
1168  layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
1169  ARMNN_ASSERT(layer != nullptr);
1170 
1171  auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
1172  {m_TensorsInfo[inputName].m_info->GetShape(),
1173  m_TensorsInfo[weightName].m_info->GetShape()});
1174  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1175 
1176  // Add constant layer to store weights and connect to FullyConnected layer.
1177  if(m_TensorsInfo[weightName].isConstant())
1178  {
1179  TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1180  IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1181 
1182  weightInfo.SetConstant();
1183  weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1184  weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1185  }
1186 
1187  RegisterInputSlots(layer, {inputName, weightName});
1188  RegisterOutputSlots(layer, {matmulNode.output(0)});
1189  }
1190 }
1191 
1192 void OnnxParserImpl::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
1193 {
1194 
1195  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1196  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1197 
1198  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1199 
1200  std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
1201  std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1202  std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1203 
1204  desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
1205  desc.m_PoolWidth = kernel_shape[1];
1206  desc.m_PoolHeight = kernel_shape[0];
1207 
1208  if(strides.empty())
1209  {
1210  desc.m_StrideX = 1;
1211  desc.m_StrideY = 1;
1212  }
1213  else
1214  {
1215  desc.m_StrideX = strides[1];
1216  desc.m_StrideY = strides[0];
1217  }
1218 
1219  //Check new padding version first
1220  if(pads.empty())
1221  {
1222  //Check deprecated version
1223  std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1224  if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1225  {
1226  bool isUpper;
1227  if( paddingString == "SAME_LOWER")
1228  {
1229  isUpper = false;
1230  }
1231  else if (paddingString == "SAME_UPPER")
1232  {
1233  isUpper = true;
1234  }
1235  else
1236  {
1237  throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
1238  "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
1239  node.name(),
1240  paddingString,
1241  CHECK_LOCATION().AsString()));
1242  }
1243  auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1244  uint32_t inputHeight = inputInfo.GetShape()[2];
1245  uint32_t inputWidth = inputInfo.GetShape()[3];
1246  CalcPadding(inputHeight,
1247  desc.m_PoolHeight,
1248  desc.m_StrideY,
1249  1u,
1250  &desc.m_PadTop,
1251  &desc.m_PadBottom,
1252  isUpper);
1253  CalcPadding(inputWidth,
1254  desc.m_PoolWidth,
1255  desc.m_StrideX,
1256  1u,
1257  &desc.m_PadLeft,
1258  &desc.m_PadRight,
1259  isUpper);
1260  }
1261  }
1262  else
1263  {
1264  desc.m_PadTop = pads[0];
1265  desc.m_PadLeft = pads[1];
1266  desc.m_PadBottom = pads[2];
1267  desc.m_PadRight = pads[3];
1268  }
1269 
1270  IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1271  ARMNN_ASSERT(layer != nullptr);
1272 
1273  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1274  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1275 
1276  // register the input connection slots for the layer, connections are made after all layers have been created
1277  // only the tensors for the inputs are relevant, exclude the const tensors
1278  RegisterInputSlots(layer, {node.input(0)});
1279 
1280  // register the output connection slots for the layer, connections are made after all layers have been created
1281  RegisterOutputSlots(layer, {node.output(0)});
1282 }
1283 
1284 std::pair<std::string, std::string> OnnxParserImpl::AddPrepareBroadcast(const std::string& input0,
1285  const std::string& input1)
1286 {
1287  std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1288 
1289  TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1290  TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1291 
1292  if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1293  {
1294  auto outputName = fmt::format("reshape_output_{}", input1);
1295  PrependForBroadcast(outputName, input1, input0);
1296  inputs.second = outputName;
1297  }
1298  else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1299  {
1300  auto outputName = fmt::format("reshape_output_{}", input0);
1301  PrependForBroadcast(outputName, input0, input1);
1302  inputs.first = outputName;
1303  }
1304  return inputs;
1305 }
1306 
1307 void OnnxParserImpl::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
1308 {
1309  auto armnnTensor = CreateConstTensor(tensorName);
1310  IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1311  layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1312  RegisterOutputSlots(layer, {tensorName});
1313 }
1314 
1315 void OnnxParserImpl::CreateInt64ConstantLayer(const std::string& tensorName, const std::string& layerName)
1316 {
1317  auto armnnTensor = CreateInt64ConstTensor(tensorName);
1318  IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1319  layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1320  RegisterOutputSlots(layer, {tensorName});
1321 }
1322 
1323 void OnnxParserImpl::CreateReshapeLayer(const std::string& inputName,
1324  const std::string& outputName,
1325  const std::string& layerName)
1326 {
1327  const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1328  ReshapeDescriptor reshapeDesc;
1329  reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1330 
1331  IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
1332  ARMNN_ASSERT(layer != nullptr);
1333  layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1334 
1335  // register the input connection slots for the layer, connections are made after all layers have been created
1336  // only the tensors for the inputs are relevant, exclude the const tensors
1337  RegisterInputSlots(layer, {inputName});
1338 
1339  // register the output connection slots for the layer, connections are made after all layers have been created
1340  RegisterOutputSlots(layer, {outputName});
1341 }
1342 
1343 void OnnxParserImpl::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
1344 {
1345  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
1346  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1347 
1348  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1349 
1350  ActivationDescriptor desc;
1351  desc.m_Function = func;
1352 
1353  if (func == ActivationFunction::BoundedReLu)
1354  {
1355  if (node.input_size() == 1 && node.attribute_size() > 0)
1356  {
1357  desc.m_A = ReadOptionalNodeFloatAttribute(node, "max", std::numeric_limits<float>::max());
1358  desc.m_B = ReadOptionalNodeFloatAttribute(node, "min", std::numeric_limits<float>::lowest());
1359  }
1360  else
1361  {
1362  desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1363  desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1364  }
1365  }
1366 
1367  IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
1368  ARMNN_ASSERT(layer != nullptr);
1369 
1370  auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1371  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1372 
1373  // register the input connection slots for the layer, connections are made after all layers have been created
1374  // only the tensors for the inputs are relevant, exclude the const tensors
1375  RegisterInputSlots(layer, {node.input(0)});
1376 
1377  // register the output connection slots for the layer, connections are made after all layers have been created
1378  RegisterOutputSlots(layer, {node.output(0)});
1379 }
1380 
1381 void OnnxParserImpl::ParseClip(const onnx::NodeProto& node)
1382 {
1383  ParseActivation(node, ActivationFunction::BoundedReLu);
1384 }
1385 
1386 void OnnxParserImpl::ParseSigmoid(const onnx::NodeProto& node)
1387 {
1388  ParseActivation(node, ActivationFunction::Sigmoid);
1389 }
1390 
1391 void OnnxParserImpl::ParseTanh(const onnx::NodeProto& node)
1392 {
1393  ParseActivation(node, ActivationFunction::TanH);
1394 }
1395 
1396 void OnnxParserImpl::ParseRelu(const onnx::NodeProto& node)
1397 {
1398  ParseActivation(node, ActivationFunction::ReLu);
1399 }
1400 
1401 void OnnxParserImpl::ParseLeakyRelu(const onnx::NodeProto& node)
1402 {
1403  ParseActivation(node, ActivationFunction::LeakyReLu);
1404 }
1405 
1406 void OnnxParserImpl::ParseAdd(const onnx::NodeProto& node)
1407 {
1408  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1409  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1410 
1411  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1412 
1413  // TODO: unify broadcast validation code across layers
1414  // tracked by: IVGCVSW-1576
1415 
1416  // Checking broadcast compatibility : only scalar or 1D tensors
1417  auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1418  auto input0 = *m_TensorsInfo[inputs.first].m_info;
1419  auto input1 = *m_TensorsInfo[inputs.second].m_info;
1420  ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1421 
1422  unsigned int numDims = input0.GetNumDimensions();
1423  for (unsigned int i = 0; i < numDims; i++)
1424  {
1425  unsigned int dim0 = input0.GetShape()[i];
1426  unsigned int dim1 = input1.GetShape()[i];
1427  if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
1428  {
1429  throw ParseException(
1430  fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1431  "Input dimensions should either match or one should be of size 1 and here, "
1432  "{} and {} {}",
1433  node.name(),
1434  TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1435  m_TensorsInfo[inputs.first].m_dtype),
1436  TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1437  m_TensorsInfo[inputs.second].m_dtype),
1438  CHECK_LOCATION().AsString()));
1439  }
1440  }
1441 
1442 
1443  IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
1444  ARMNN_ASSERT(layer != nullptr);
1445 
1446  auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1447  { m_TensorsInfo[inputs.first].m_info->GetShape(),
1448  m_TensorsInfo[inputs.second].m_info->GetShape() });
1449  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1450 
1451  // register the input connection -> for constant inputs, we need to make a newDim constant layer
1452  if(m_TensorsInfo[inputs.first].isConstant()) {
1453  CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
1454  }
1455  if(m_TensorsInfo[inputs.second].isConstant()) {
1456  CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
1457  }
1458  RegisterInputSlots(layer, {inputs.first, inputs.second});
1459 
1460  // register the output connection
1461  RegisterOutputSlots(layer, {node.output(0)});
1462 }
1463 
1464 void OnnxParserImpl::ParseAveragePool(const onnx::NodeProto& node)
1465 {
1466  Pooling2dDescriptor desc;
1467  desc.m_PoolType = PoolingAlgorithm::Average;
1468 
1469  uint32_t count_include_pad = 0;
1470  count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1471  if(count_include_pad) {
1472  desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1473  }
1474  AddPoolingLayer(node, desc);
1475 }
1476 
1477 void OnnxParserImpl::ParseBatchNormalization(const onnx::NodeProto& node)
1478 {
1479  //IGNORE momentum parameter and spatial parameters
1480 
1481  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1482  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1483 
1484  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1485  for(int ind = 1; ind < node.input_size(); ++ind)
1486  {
1487  auto tensor = node.input(ind);
1488  if(! m_TensorsInfo[tensor].isConstant())
1489  {
1490  throw ParseException(
1491  fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1492  tensor,
1493  node.name(),
1494  CHECK_LOCATION().AsString()));
1495  }
1496  }
1497 
1498  float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1500  desc.m_Eps = epsilon;
1501 
1502  auto scaleTensor = CreateConstTensor(node.input(1));
1503  auto biasTensor = CreateConstTensor(node.input(2));
1504  auto meanTensor = CreateConstTensor(node.input(3));
1505  auto varTensor = CreateConstTensor(node.input(4));
1506 
1507  IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1508  meanTensor.first,
1509  varTensor.first,
1510  biasTensor.first,
1511  scaleTensor.first,
1512  node.name().c_str());
1513  ARMNN_ASSERT(layer != nullptr);
1514 
1515  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1516  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1517 
1518  RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1519 
1520  // register the output connection
1521  RegisterOutputSlots(layer, {node.output(0)});
1522 }
1523 
1524 void OnnxParserImpl::ParseConcat(const onnx::NodeProto& node)
1525 {
1526  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1527 
1528  uint32_t numConcatView = static_cast<uint32_t>(node.input_size());
1529  uint32_t inputRank = m_TensorsInfo[node.input(0)].m_info->GetNumDimensions();
1530 
1531  int axisInt = ReadMandatoryNodeIntAttribute(node, "axis");
1532 
1533  unsigned int concatDimInput = static_cast<unsigned int>(
1534  (static_cast<int>(inputRank) + axisInt) % static_cast<int>(inputRank));
1535 
1536  OriginsDescriptor concatDescriptor(numConcatView, inputRank);
1537  concatDescriptor.SetConcatAxis(concatDimInput);
1538 
1539  unsigned int mergeDimOrigin = 0;
1540 
1541  std::vector<TensorShape> inputShapes;
1542  std::vector<std::string> tensorIds;
1543 
1544  for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1545  {
1546  std::string nodeName = node.input(static_cast<int>(viewIndex));
1547  auto inputTensorInfo = *m_TensorsInfo[nodeName].m_info;
1548  inputShapes.push_back(inputTensorInfo.GetShape());
1549  tensorIds.push_back(nodeName);
1550 
1551  // Set up concatDescriptor view origin
1553  inputTensorInfo, concatDescriptor, concatDimInput, viewIndex, mergeDimOrigin);
1554  }
1555 
1556  IConnectableLayer* layer = m_Network->AddConcatLayer(concatDescriptor, node.name().c_str());
1557  ARMNN_ASSERT(layer != nullptr);
1558 
1559  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, inputShapes,
1560  m_TensorsInfo[node.input(0)].m_dtype);
1561 
1562  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1563 
1564  // register the input connection slots for the layer, connections are made after all layers have been created
1565  RegisterInputSlots(layer, tensorIds);
1566 
1567  // register the output connection slots for the layer, connections are made after all layers have been created
1568  RegisterOutputSlots(layer, { node.output(0) });
1569 }
1570 
1571 void OnnxParserImpl::ParseConstant(const onnx::NodeProto& node)
1572 {
1573  CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1574  if (!node.attribute(0).has_t())
1575  {
1576  throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1577  node.name(),
1578  CHECK_LOCATION().AsString()));
1579  }
1580  const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1581 
1582  //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1583  m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1584  m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1585  m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1586 
1587  if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_FLOAT)
1588  {
1589  CreateConstantLayer(node.output(0), node.name());
1590  }
1591  else if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_INT64)
1592  {
1593  CreateInt64ConstantLayer(node.output(0), node.name());
1594  }
1595  else
1596  {
1597  throw ParseException(fmt::format("Data type not support for Constant node '{}' {}",
1598  node.name(),
1599  CHECK_LOCATION().AsString()));
1600  }
1601 }
1602 
1603 void OnnxParserImpl::ParseConv(const onnx::NodeProto& node)
1604 {
1605  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1606  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1607 
1608  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1609 
1610  if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1611  {
1612  throw ParseException(
1613  fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1614  node.name(),
1615  TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1616  m_TensorsInfo[node.input(0)].m_dtype),
1617  CHECK_LOCATION().AsString()));
1618  }
1619 
1620  if(!m_TensorsInfo[node.input(1)].isConstant())
1621  {
1622  throw ParseException(
1623  fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1624  node.input(1),
1625  node.name(),
1626  CHECK_LOCATION().AsString()));
1627  }
1628 
1629  auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1630 
1632  desc.m_BiasEnabled = false;
1633 
1634  std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1635  if(strides.empty())
1636  {
1637  desc.m_StrideX = 1;
1638  desc.m_StrideY = 1;
1639  }
1640  else
1641  {
1642  desc.m_StrideX = strides[1];
1643  desc.m_StrideY = strides[0];
1644  }
1645 
1646  std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1647  if(!dilations.empty())
1648  {
1649  desc.m_DilationX = dilations[1];
1650  desc.m_DilationY = dilations[0];
1651  }
1652 
1653  std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1654  //Check new padding version first
1655  if(pads.empty())
1656  {
1657  //Check deprecated version
1658  std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1659  if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1660  {
1661  bool isUpper;
1662  if( paddingString == "SAME_LOWER")
1663  {
1664  isUpper = false;
1665  }
1666  else if (paddingString == "SAME_UPPER")
1667  {
1668  isUpper = true;
1669  }
1670  else
1671  {
1672  throw ParseException(
1673  fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1674  "supported and found {} {}",
1675  node.name(),
1676  paddingString,
1677  CHECK_LOCATION().AsString()));
1678  }
1679  uint32_t inputHeight = inputInfo.GetShape()[2];
1680  uint32_t inputWidth = inputInfo.GetShape()[3];
1681 
1682  uint32_t weightHeight;
1683  uint32_t weightWidth;
1684  std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1685  if (kernel_shape.empty())
1686  {
1687  const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1688  weightHeight = weightTensorInfo.GetShape()[2];
1689  weightWidth = weightTensorInfo.GetShape()[3];
1690  }
1691  else
1692  {
1693  weightHeight = kernel_shape[0];
1694  weightWidth = kernel_shape[1];
1695  }
1696  CalcPadding(inputHeight,
1697  weightHeight,
1698  desc.m_StrideY,
1699  desc.m_DilationY,
1700  &desc.m_PadTop,
1701  &desc.m_PadBottom,
1702  isUpper);
1703  CalcPadding(inputWidth,
1704  weightWidth,
1705  desc.m_StrideX,
1706  desc.m_DilationX,
1707  &desc.m_PadLeft,
1708  &desc.m_PadRight,
1709  isUpper);
1710  }
1711  }
1712  else
1713  {
1714  desc.m_PadTop = pads[0];
1715  desc.m_PadLeft = pads[1];
1716  desc.m_PadBottom = pads[2];
1717  desc.m_PadRight = pads[3];
1718  }
1719 
1720  uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1721  if(group > 1)
1722  {
1723  if (group > inputInfo.GetShape()[1])
1724  {
1725  throw ParseException(
1726  fmt::format("Error parsing Convolution node: {}. "
1727  "The 'group'={} parameter cannot be larger than the "
1728  "channel of the input shape={} (in NCHW format). {}",
1729  node.name(),
1730  group,
1731  inputInfo.GetShape()[1],
1732  CHECK_LOCATION().AsString()));
1733  }
1734  else if (group == inputInfo.GetShape()[1])
1735  {
1736  // we use a depthwise convolution here, because the number of groups equals to the
1737  // input channels
1738  AddConvLayerWithDepthwiseConv(node, desc);
1739  return;
1740  }
1741  else
1742  {
1743  // TODO: split the input by channels into channels/groups separate convolutions
1744  // and concatenate the results afterwards
1745  throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1746  "The 'group'={} parameter should be 1 or be equal to the "
1747  "channel of the input shape={} (in NCHW format). {}",
1748  node.name(),
1749  group,
1750  inputInfo.GetShape()[1],
1751  CHECK_LOCATION().AsString()));
1752  }
1753  }
1754 
1755  armnn::IConnectableLayer* layer;
1756  auto weightTensor = CreateConstTensor(node.input(1));
1757 
1758  if (node.input_size() == 3)
1759  {
1760  if(!m_TensorsInfo[node.input(2)].isConstant())
1761  {
1762  throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1763  node.input(2),
1764  node.name(),
1765  CHECK_LOCATION().AsString()));
1766  }
1767  desc.m_BiasEnabled = true;
1768  auto biasTensor = CreateConstTensor(node.input(2));
1769  layer = m_Network->AddConvolution2dLayer(desc,
1770  weightTensor.first,
1771  Optional<ConstTensor>(biasTensor.first),
1772  node.name().c_str());
1773  }
1774  else
1775  {
1776  layer = m_Network->AddConvolution2dLayer(desc,
1777  weightTensor.first,
1778  EmptyOptional(),
1779  node.name().c_str());
1780  }
1781  ARMNN_ASSERT(layer != nullptr);
1782 
1783  auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1784  { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1785  m_TensorsInfo[node.input(1)].m_info->GetShape() });
1786  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1787 
1788  // register the input connection slots for the layer, connections are made after all layers have been created
1789  // only the tensors for the inputs are relevant, exclude the const tensors
1790  RegisterInputSlots(layer, {node.input(0)});
1791 
1792  // register the output connection slots for the layer, connections are made after all layers have been created
1793  RegisterOutputSlots(layer, {node.output(0)});
1794 }
1795 
1796 void OnnxParserImpl::ParseFlatten(const onnx::NodeProto& node)
1797 {
1798  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1799  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1800 
1801  CHECK_VALID_DATATYPE(node.name(), node.input(0),
1802  m_TensorsInfo[node.input(0)].m_dtype,
1803  onnx::TensorProto::FLOAT);
1804 
1805  int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1806  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1807 
1808  /// Negative axis conversion
1809  if (axis < 0)
1810  {
1811  axis += inputShape.GetNumDimensions();
1812  }
1813 
1814  /// Check Axis is within dimensions
1815  if (axis < 0 || axis >= inputShape.GetNumDimensions())
1816  {
1817  throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
1818  axis, inputShape.GetNumDimensions(), node.name()));
1819  }
1820 
1821  /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1822  uint dimension1{1};
1823  uint dimension2{1};
1824  uint i{0};
1825 
1826  /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1827  for (i = 0; i < axis; i++){
1828  dimension1 *= inputShape[i];
1829  }
1830 
1831  /// dimension2 = (d_axis * d_(axis+1) ... d_n)
1832  for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
1833  dimension2 *= inputShape[i];
1834  }
1835 
1836  TensorShape outputShape{dimension1, dimension2};
1837 
1838  auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
1839  m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1840  CreateReshapeLayer(node.input(0), node.output(0), node.name());
1841 }
1842 
1843 void OnnxParserImpl::ParseGather(const onnx::NodeProto& node)
1844 {
1845  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1846  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1847 
1848  armnn::GatherDescriptor gatherDescriptor;
1849  gatherDescriptor.m_Axis = static_cast<int>(ReadOptionalNodeInt64Attribute(node, "axis", 0));
1850 
1851  IConnectableLayer* layer = m_Network->AddGatherLayer(gatherDescriptor, node.name().c_str());
1852  ARMNN_ASSERT(layer != nullptr);
1853 
1854  const TensorShape& inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1855  const TensorShape& indicesShape = m_TensorsInfo[node.input(1)].m_info->GetShape();
1856  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, { inputShape, indicesShape },
1857  m_TensorsInfo[node.input(0)].m_dtype);
1858  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1859 
1860  // register the input connection slots for the layer, connections are made after all layers have been created
1861  RegisterInputSlots(layer, { node.input(0), node.input(1) });
1862 
1863  // register the output connection slots for the layer, connections are made after all layers have been created
1864  RegisterOutputSlots(layer, { node.output(0) });
1865 }
1866 
1867 void OnnxParserImpl::ParseGemm(const onnx::NodeProto& node)
1868 {
1869  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3);
1870  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1871 
1872  int transA = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transA", 0));
1873  int transB = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transB", 0));
1874  float alpha = ReadOptionalNodeFloatAttribute(node, "alpha", 1.0);
1875  float beta = ReadOptionalNodeFloatAttribute(node, "beta", 1.0);
1876  bool biasEnabled = node.input_size() == 3;
1877 
1878  TensorShape input0Shape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1879  TensorShape input1Shape = m_TensorsInfo[node.input(1)].m_info->GetShape();
1880 
1881  // if transB != 0, add transpose to the input1 (tanspose weight matrix in FullyConnected)
1882  armnn::FullyConnectedDescriptor fullyConnectedDescriptor;
1883  fullyConnectedDescriptor.m_BiasEnabled = biasEnabled;
1884  fullyConnectedDescriptor.m_TransposeWeightMatrix = transB;
1885 
1886  IConnectableLayer* layer = nullptr;
1887 
1888  // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1889  layer = m_Network->AddFullyConnectedLayer(fullyConnectedDescriptor, node.name().c_str());
1890  ARMNN_ASSERT(layer != nullptr);
1891 
1892  // if transA != 0, add transpose to the input0
1893  if (transA != 0)
1894  {
1895  std::string transAName = "transpose_" + node.input(0);
1896  armnn::TransposeDescriptor transposeADescriptor;
1897  transposeADescriptor.m_DimMappings = { 1, 0 };
1898  IConnectableLayer* transALayer = m_Network->AddTransposeLayer(transposeADescriptor, transAName.c_str());
1899  ARMNN_ASSERT(transALayer != nullptr);
1900  auto transAInfo = ComputeOutputInfo({ transAName }, transALayer, { input0Shape });
1901  transALayer->GetOutputSlot(0).SetTensorInfo(transAInfo[0]);
1902  transALayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
1903  // register the input connection slots for the layer, connections are made after all layers have been created
1904  RegisterInputSlot(transALayer, node.input(0), 0);
1905  input0Shape = transAInfo[0].GetShape();
1906  }
1907  else
1908  {
1909  RegisterInputSlot(layer, node.input(0), 0);
1910  }
1911 
1912  // Add constant layer to store weights/biases and connect to FullyConnected layer.
1913  if(m_TensorsInfo[node.input(1)].isConstant())
1914  {
1915  IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(1)).first);
1916  TensorInfo weightInfo = *m_TensorsInfo[node.input(1)].m_info;
1917  weightInfo.SetConstant();
1918  weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1919 
1920  // if alpha != 1, multiply to the weight
1921  if (alpha != 1)
1922  {
1923  std::string activationName = "activation_" + node.input(1);
1924  armnn::ActivationDescriptor activationDescriptor;
1925  activationDescriptor.m_A = alpha;
1926  activationDescriptor.m_Function = ActivationFunction::Linear;
1927  IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
1928  ARMNN_ASSERT(actLayer != nullptr);
1929 
1930  auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { weightInfo.GetShape() });
1931  actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
1932  actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1933  weightsLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
1934  input1Shape = actInfo[0].GetShape();
1935  }
1936  else
1937  {
1938  weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1939  input1Shape = weightInfo.GetShape();
1940  }
1941  }
1942  else
1943  {
1944  // if alpha != 1, multiply to the weight
1945  if (alpha != 1)
1946  {
1947  std::string activationName = "activation_" + node.input(1);
1948  armnn::ActivationDescriptor activationDescriptor;
1949  activationDescriptor.m_A = alpha;
1950  activationDescriptor.m_Function = ActivationFunction::Linear;
1951  IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
1952  ARMNN_ASSERT(actLayer != nullptr);
1953 
1954  auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { input1Shape });
1955  actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
1956  actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1957  RegisterInputSlot(actLayer, node.input(1), 0);
1958  input1Shape = actInfo[0].GetShape();
1959  }
1960  else
1961  {
1962  RegisterInputSlot(layer, node.input(1), 1);
1963  }
1964  }
1965 
1966  if(biasEnabled && m_TensorsInfo[node.input(2)].isConstant())
1967  {
1968  To1DTensor(node.input(2), CHECK_LOCATION());
1969  IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(2)).first);
1970  TensorInfo biasInfo = *m_TensorsInfo[node.input(2)].m_info;
1971  biasInfo.SetConstant();
1972  biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1973 
1974  // if beta != 1, multiply to the bias
1975  if (beta != 1)
1976  {
1977  std::string activationName = "activation_" + node.input(2);
1978  armnn::ActivationDescriptor activationDescriptor;
1979  activationDescriptor.m_A = beta;
1980  activationDescriptor.m_Function = ActivationFunction::Linear;
1981  IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
1982  ARMNN_ASSERT(actLayer != nullptr);
1983 
1984  auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { biasInfo.GetShape() });
1985  actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
1986  actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1987  biasLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
1988  }
1989  else
1990  {
1991  biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1992  }
1993  }
1994  else if (biasEnabled)
1995  {
1996  // Currently we support non-constant tensor of input C (bias) of Gemm when the dimension is 1
1997  if (m_TensorsInfo[node.input(2)].m_info->GetNumDimensions() != 1)
1998  {
1999  throw ParseException(fmt::format("The parser supports constant or non-constant with 1 dimension for "
2000  "Input C of Gemm. Input '{}' in '{}' is not supported '{}'",
2001  node.input(2),
2002  node.name(),
2003  CHECK_LOCATION().AsString()));
2004  }
2005  // if beta != 1, multiply to the bias
2006  if (beta != 1)
2007  {
2008  std::string activationName = "activation_" + node.input(2);
2009  armnn::ActivationDescriptor activationDescriptor;
2010  activationDescriptor.m_A = beta;
2011  activationDescriptor.m_Function = ActivationFunction::Linear;
2012  IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
2013  ARMNN_ASSERT(actLayer != nullptr);
2014 
2015  auto actInfo = ComputeOutputInfo({ activationName },
2016  actLayer,
2017  { m_TensorsInfo[node.input(2)].m_info->GetShape() });
2018  actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2019  actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2020  RegisterInputSlot(actLayer, node.input(2), 0);
2021  }
2022  else
2023  {
2024  RegisterInputSlot(layer, node.input(2), 2);
2025  }
2026  }
2027 
2028  // Set final output of the FullyConnected layer
2029  auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
2030  { input0Shape, input1Shape });
2031  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2032 
2033  RegisterOutputSlots(layer, {node.output(0)});
2034 }
2035 
2036 void OnnxParserImpl::ParseGlobalAveragePool(const onnx::NodeProto& node)
2037 {
2039  desc.m_PoolType = PoolingAlgorithm::Average;
2040 
2041  //kernel size is the same as input
2042  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2043  desc.m_PoolWidth = inputShape[3];
2044  desc.m_PoolHeight = inputShape[2];
2045 
2046  IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
2047  ARMNN_ASSERT(layer != nullptr);
2048 
2049  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
2050  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2051 
2052  // register the input connection slots for the layer, connections are made after all layers have been created
2053  // only the tensors for the inputs are relevant, exclude the const tensors
2054  RegisterInputSlots(layer, {node.input(0)});
2055 
2056  // register the output connection slots for the layer, connections are made after all layers have been created
2057  RegisterOutputSlots(layer, {node.output(0)});
2058 }
2059 
2060 void OnnxParserImpl::ParseMaxPool(const onnx::NodeProto& node)
2061 {
2062  Pooling2dDescriptor desc;
2063  desc.m_PoolType = PoolingAlgorithm::Max;
2064  desc.m_PaddingMethod = PaddingMethod::Exclude;
2065  AddPoolingLayer(node, desc);
2066 }
2067 
2068 void OnnxParserImpl::ParseShape(const onnx::NodeProto& node)
2069 {
2070  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
2071  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2072 
2073  IConnectableLayer* layer = m_Network->AddShapeLayer(node.name().c_str());
2074  ARMNN_ASSERT(layer != nullptr);
2075 
2076  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2077  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape}, onnx::TensorProto::INT64);
2078  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2079 
2080  // register the input connection slots for the layer, connections are made after all layers have been created
2081  RegisterInputSlots(layer, {node.input(0)});
2082 
2083  // register the output connection slots for the layer, connections are made after all layers have been created
2084  RegisterOutputSlots(layer, {node.output(0)});
2085 }
2086 
2087 void OnnxParserImpl::ParseReshape(const onnx::NodeProto& node)
2088 {
2089  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2090  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2091 
2092  CHECK_VALID_DATATYPE(node.name(), node.input(0),
2093  m_TensorsInfo[node.input(0)].m_dtype,
2094  onnx::TensorProto::FLOAT); //input
2095  CHECK_VALID_DATATYPE(node.name(), node.input(1),
2096  m_TensorsInfo[node.input(1)].m_dtype,
2097  onnx::TensorProto::INT64); //shape
2098 
2099  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2100 
2101  std::vector<unsigned int> targetShape;
2102  if(m_TensorsInfo[node.input(1)].isConstant())
2103  {
2104  unsigned int dims = static_cast<unsigned int>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2105  targetShape.reserve(dims);
2106 
2107  for(uint i = 0; i < dims; i++)
2108  {
2109  int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
2110  targetShape[i]= static_cast<unsigned int>(val);
2111  }
2112  }
2113  else
2114  {
2115  // The parser only supports shape (batch, -1) or (-1) for non-constant shape input.
2116  unsigned int dims = m_TensorsInfo[node.input(1)].m_info->GetNumDimensions();
2117  TensorShape shapes = m_TensorsInfo[node.input(1)].m_info->GetShape();
2118  if (dims != 1 || shapes[0] > 2)
2119  {
2120  throw ParseException(fmt::format("Invalid input shape '{}' in Reshape layer '{}' {}",
2121  node.input(1),
2122  node.name(),
2123  CHECK_LOCATION().AsString()));
2124  }
2125 
2126  unsigned int numInputElements = m_TensorsInfo[node.input(0)].m_info->GetNumElements();
2127  if (shapes[0] == 1)
2128  {
2129  targetShape = { numInputElements };
2130  }
2131  else if (shapes[0] == 2)
2132  {
2133  targetShape = { inputShape[0] , numInputElements / inputShape[0] };
2134  }
2135  }
2136 
2137  if(m_TensorsInfo[node.input(0)].isConstant())
2138  {
2139  //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
2140  if(m_TensorsInfo.count(node.output(0)) == 0)
2141  {
2142  m_TensorsInfo[node.output(0)] = OnnxTensor();
2143  }
2144  m_TensorsInfo[node.output(0)].m_tensor =
2145  std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
2146  }
2147  else
2148  {
2149  if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
2150  {
2151  auto outInfo = ComputeReshapeInfo(
2152  TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2153  inputShape, node.output(0));
2154  m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2155  }
2156 
2157  CreateReshapeLayer(node.input(0), node.output(0), node.name());
2158  }
2159 }
2160 
2161 void OnnxParserImpl::ParseUnsqueeze(const onnx::NodeProto& node)
2162 {
2163  CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.input_size()), 1, 2);
2164  CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.output_size()), 1);
2165 
2166  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2167  std::vector<uint32_t> dims;
2168  if (node.input_size() == 1 && node.attribute_size() > 0)
2169  {
2170  dims = ReadMandatoryNodeUint32ListAttribute(node, "axes");
2171  }
2172  else
2173  {
2174  CHECK_VALID_DATATYPE(node.name(), node.input(1),
2175  m_TensorsInfo[node.input(1)].m_dtype,
2176  onnx::TensorProto::INT64); //axes
2177 
2178  auto int64Axes = m_TensorsInfo[node.input(1)].m_tensor->int64_data().data();
2179  uint numDim = armnn::numeric_cast<uint>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2180 
2181  for(uint i = 0; i < numDim; i++)
2182  {
2183  uint32_t uint32Value = CHECKED_NON_NEGATIVE(CHECKED_INT32(int64Axes[i]));
2184  dims.push_back(uint32Value);
2185  }
2186  }
2187 
2188  // Ensure that the axes are sorted
2189  std::sort(dims.begin(), dims.end());
2190 
2191  std::vector<unsigned int> targetShape;
2192 
2193  if (inputShape.GetDimensionality() != Dimensionality::Scalar)
2194  {
2195  for(uint i = 0; i < inputShape.GetNumDimensions(); i++)
2196  {
2197  targetShape.push_back(inputShape[i]);
2198  }
2199  }
2200 
2201  for(uint i = 0; i < dims.size(); i++)
2202  {
2203  targetShape.insert(targetShape.begin() + armnn::numeric_cast<int>(dims[i]), 1);
2204  }
2205 
2206  auto outInfo = ComputeReshapeInfo(TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2207  inputShape, node.output(0), m_TensorsInfo[node.input(0)].m_info->GetDataType());
2208  m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2209  m_TensorsInfo[node.output(0)].m_dtype = m_TensorsInfo[node.input(0)].m_dtype;
2210 
2211  CreateReshapeLayer(node.input(0), node.output(0), node.name());
2212 }
2213 
2214 void OnnxParserImpl::PrependForBroadcast(const std::string& outputName,
2215  const std::string& input0,
2216  const std::string& input1)
2217 {
2218  //input0 should be reshaped to have same number of dim as input1
2219  TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
2220 
2221  TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
2222  TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
2223 
2224  uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
2225  std::vector<uint32_t> newShape;
2226  while(diff > 0)
2227  {
2228  newShape.push_back(1);
2229  diff--;
2230  }
2231  for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
2232  {
2233  newShape.push_back(input0Shape[dim]);
2234  }
2235  outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
2236 
2237  //add the new tensor to m_TensorsInfo
2238  m_TensorsInfo[outputName] = OnnxTensor();
2239  m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
2240 
2241  //add reshape layer if the parent was not constant...
2242  if( ! m_TensorsInfo[input0].isConstant())
2243  {
2244  CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
2245  }
2246  else //make it constant and it will be create in Add
2247  {
2248  m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
2249 
2250  }
2251 }
2252 
2253 void OnnxParserImpl::SetupInputLayers()
2254 {
2255  //Find user input and add their layers
2256  for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
2257  {
2258  auto input = m_Graph->input(inputIndex);
2259  if (!m_TensorsInfo[input.name()].isConstant())
2260  {
2261  IConnectableLayer* layer =
2262  m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
2263  TensorInfo tensorInfo = *m_TensorsInfo[input.name()].m_info;
2264  if (tensorInfo.GetShape().GetDimensionality() == Dimensionality::NotSpecified)
2265  {
2266  if (m_InputShapes.find(input.name()) == m_InputShapes.end())
2267  {
2268  throw ParseException(fmt::format("The parser does not support dynamic tensor, "
2269  "please specify input shape for {}. {}",
2270  input.name(),
2271  CHECK_LOCATION().AsString()));
2272  }
2273  else
2274  {
2275  tensorInfo.SetShape(m_InputShapes[input.name()]);
2276  m_TensorsInfo[input.name()].m_info = std::make_unique<TensorInfo>(tensorInfo);
2277  }
2278 
2279  }
2280  layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2281 
2282  m_InputInfos[input.name()] = tensorInfo;
2283 
2284  RegisterOutputSlots(layer,{ input.name() });
2285  }
2286  }
2287 }
2288 
2289 void OnnxParserImpl::SetupOutputLayers()
2290 {
2291  if(m_Graph->output_size() == 0)
2292  {
2293  throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
2294  }
2295 
2296  for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
2297  {
2298  IConnectableLayer* layer =
2299  m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
2300  m_Graph->output(outputIndex).name().c_str());
2301 
2302  RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
2303  }
2304 }
2305 
2306 void OnnxParserImpl::RegisterInputSlot(IConnectableLayer* layer,
2307  const std::string& tensorId,
2308  unsigned int slotIndex)
2309 {
2310  armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2311 
2312  auto it = m_TensorConnections.find(tensorId);
2313 
2314  if (it == m_TensorConnections.end())
2315  {
2316  //First time seeing this tensor, we need to map it
2317  m_TensorConnections[tensorId] = TensorSlots();
2318  }
2319  m_TensorConnections[tensorId].inputSlots.push_back(slot);
2320 }
2321 
2322 void OnnxParserImpl::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
2323 {
2324  ARMNN_ASSERT(layer != nullptr);
2325  if (tensorIds.size() != layer->GetNumInputSlots())
2326  {
2327  throw ParseException(
2328  fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
2329  tensorIds.size(),
2330  layer->GetNumInputSlots(),
2331  CHECK_LOCATION().AsString()));
2332  }
2333 
2334  for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
2335  {
2336  std::string tensorId = tensorIds[slotIndex];
2337  armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2338 
2339  auto it = m_TensorConnections.find(tensorId);
2340 
2341  if (it == m_TensorConnections.end())
2342  {
2343  // First time seing this tensor, we need to map it
2344  m_TensorConnections[tensorId] = TensorSlots();
2345  }
2346  m_TensorConnections[tensorId].inputSlots.push_back(slot);
2347  }
2348 }
2349 
2350 void OnnxParserImpl::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
2351 {
2352  ARMNN_ASSERT(layer != nullptr);
2353  if (tensorIds.size() != layer->GetNumOutputSlots())
2354  {
2355  throw ParseException(
2356  fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
2357  tensorIds.size(),
2358  layer->GetNumOutputSlots(),
2359  CHECK_LOCATION().AsString()));
2360  }
2361 
2362  for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
2363  {
2364  std::string tensorId = tensorIds[slotIndex];
2365  armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
2366 
2367  auto it = m_TensorConnections.find(tensorId);
2368 
2369  if (it == m_TensorConnections.end())
2370  {
2371  //First time seing this tensor, we need to map it
2372  m_TensorConnections[tensorId] = TensorSlots();
2373  }
2374 
2375  TensorSlots& tensorSlots = m_TensorConnections[tensorId];
2376 
2377  // assuming there is only one producer for that tensor
2378  if (tensorSlots.outputSlot != nullptr)
2379  {
2380  throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
2381  "tensor:{} {}",
2382  tensorId,
2383  CHECK_LOCATION().AsString()));
2384  }
2385  tensorSlots.outputSlot = slot;
2386  }
2387 
2388 }
2389 
2391 {
2392  for(int i = 0; i < m_Graph->input_size(); ++i)
2393  {
2394  auto input = m_Graph->input(i);
2395  if(input.name() == name)
2396  {
2397  auto it = m_InputInfos.find(name);
2398 
2399  if (it != m_InputInfos.end())
2400  {
2401  return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2402  }
2403  }
2404  }
2405  throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
2406  name, CHECK_LOCATION().AsString()));
2407 }
2408 
2410 {
2411  for(int i = 0; i < m_Graph->output_size(); ++i)
2412  {
2413  auto output = m_Graph->output(i);
2414  if(output.name() == name)
2415  {
2416  auto it = m_OutputInfos.find(name);
2417 
2418  if (it != m_OutputInfos.end())
2419  {
2420  return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2421  }
2422  }
2423  }
2424  throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
2425  name, CHECK_LOCATION().AsString()));
2426 }
2427 
2428 std::vector<std::string> OnnxParserImpl::GetInputs(ModelPtr& model)
2429 {
2430  if(model == nullptr) {
2431  throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2432  CHECK_LOCATION().AsString()));
2433  }
2434 
2435  std::vector<std::string> inputNames;
2436  std::map<std::string, bool> isConstant;
2437  for(auto tensor : model->graph().initializer())
2438  {
2439  isConstant[tensor.name()] = true;
2440  }
2441  for(auto input : model->graph().input())
2442  {
2443  auto it = isConstant.find(input.name());
2444  if(it == isConstant.end())
2445  {
2446  inputNames.push_back(input.name());
2447  }
2448  }
2449  return inputNames;
2450 }
2451 
2452 std::vector<std::string> OnnxParserImpl::GetOutputs(ModelPtr& model)
2453 {
2454  if(model == nullptr) {
2455  throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2456  CHECK_LOCATION().AsString()));
2457  }
2458 
2459  std::vector<std::string> outputNames;
2460  for(auto output : model->graph().output())
2461  {
2462  outputNames.push_back(output.name());
2463  }
2464  return outputNames;
2465 }
2466 
2467 const std::string OnnxParserImpl::GetVersion()
2468 {
2469  return ONNX_PARSER_VERSION;
2470 }
2471 
2472 } // namespace armnnOnnxParser
unsigned int GetNumElements() const
Function that calculates the tensor elements by multiplying all dimension size which are Specified...
Definition: Tensor.cpp:181
uint32_t m_PadBottom
Padding bottom value in the height dimension.
bool m_BiasEnabled
Enable/disable bias.
virtual unsigned int GetNumOutputSlots() const =0
Returns the number of connectable output slots.
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:66
uint32_t m_PadBottom
Padding bottom value in the height dimension.
virtual unsigned int GetNumInputSlots() const =0
Returns the number of connectable input slots.
const TensorShape & GetShape() const
Definition: Tensor.hpp:191
uint32_t m_PadLeft
Padding left value in the width dimension.
std::string AsString() const
Definition: Exceptions.hpp:29
A ReshapeDescriptor for the ReshapeLayer.
BindingPointInfo GetNetworkInputBindingInfo(const std::string &name) const
Retrieve binding info (layer id and tensor info) for the network input identified by the given layer ...
bool m_TransposeWeightMatrix
Enable/disable transpose weight matrix.
Dimensionality GetDimensionality() const
Function that returns the tensor type.
Definition: Tensor.hpp:92
uint32_t m_PoolWidth
Pooling width value.
A Convolution2dDescriptor for the Convolution2dLayer.
uint32_t m_PadLeft
Padding left value in the width dimension.
unsigned int GetNumBytes() const
Definition: Tensor.cpp:429
float m_Eps
Value to add to the variance. Used to avoid dividing by zero.
PaddingMethod m_PaddingMethod
The padding method to be used. (Exclude, IgnoreValue).
BindingPointInfo GetNetworkOutputBindingInfo(const std::string &name) const
Retrieve binding info (layer id and tensor info) for the network output identified by the given layer...
uint32_t m_PadTop
Padding top value in the height dimension.
void ProcessConcatInputTensorInfo(armnn::TensorInfo &inputTensorInfo, armnn::OriginsDescriptor &concatDescriptor, const unsigned int &concatAxis, unsigned int inputIndex, unsigned int &mergeDimOrigin)
uint32_t m_PadRight
Padding right value in the width dimension.
#define VALID_INPUTS(NODE, VALID_INPUTS)
Definition: OnnxParser.cpp:482
Copyright (c) 2021 ARM Limited and Contributors.
SizeType GetSize() const
Definition: Types.hpp:325
uint32_t m_DilationY
Dilation along y axis.
const armnnSerializer::Pooling2dDescriptor * Pooling2dDescriptor
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile)
Create the network from a protobuf binary file on disk.
Definition: OnnxParser.cpp:48
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
static const std::string GetVersion()
Retrieve version in X.Y.Z form.
static std::vector< std::string > GetInputs(ModelPtr &model)
Retrieve inputs names.
void SetShape(const TensorShape &newShape)
Definition: Tensor.hpp:193
TensorShape m_TargetShape
Target shape value.
uint32_t m_PoolHeight
Pooling height value.
uint32_t m_PadTop
Padding top value in the height dimension.
void Permute(const armnn::TensorShape &dstShape, const armnn::PermutationVector &mappings, const void *src, void *dst, size_t dataTypeSize)
Definition: Permute.cpp:131
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
std::unique_ptr< onnx::ModelProto > ModelPtr
Definition: OnnxParser.hpp:23
armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile)
Create the network from a protobuf text file on disk.
Definition: OnnxParser.cpp:718
static std::vector< std::string > GetOutputs(ModelPtr &model)
Retrieve outputs names.
DataType
Definition: Types.hpp:35
uint32_t m_PadRight
Padding right value in the width dimension.
#define ARMNN_ASSERT_MSG(COND, MSG)
Definition: Assert.hpp:15
An output connection slot for a layer.
Definition: INetwork.hpp:40
An OriginsDescriptor for the ConcatLayer.
armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile)
Create the network from a protobuf binary file on disk.
Definition: OnnxParser.cpp:762
bool has_value() const noexcept
Definition: Optional.hpp:53
A FullyConnectedDescriptor for the FullyConnectedLayer.
bool m_BiasEnabled
Enable/disable bias.
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
A GatherDescriptor for the GatherLayer.
#define CHECK_VALID_SIZE(ACTUAL,...)
#define CHECKED_NON_NEGATIVE(VALUE)
static ModelPtr LoadModelFromString(const std::string &inputString)
Definition: OnnxParser.cpp:778
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:36
#define CHECK_LOCATION()
Definition: Exceptions.hpp:209
static ModelPtr LoadModelFromTextFile(const char *fileName)
Definition: OnnxParser.cpp:693
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
float m_A
Alpha upper bound value used by the activation functions. (BoundedReLu, Linear, TanH, Elu).
Definition: Descriptors.hpp:61
uint32_t m_DilationX
Dilation along x axis.
std::pair< armnn::ConstTensor, std::unique_ptr< T[]> > CreateConstTensorImpl(const T *bufferPtr, armnn::TensorInfo &tensorInfo, const armnn::Optional< armnn::PermutationVector &> permutationVector)
Definition: OnnxParser.cpp:566
EmptyOptional is used to initialize the Optional class in case we want to have default value for an O...
Definition: Optional.hpp:32
int32_t m_Axis
The axis in params to gather indices from.
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
#define STR_LIST(...)
Definition: OnnxParser.cpp:120
unsigned int GetNumDimensions() const
Function that returns the tensor rank.
Definition: Tensor.cpp:174
OutputShapeRounding m_OutputShapeRounding
The rounding method for the output shape. (Floor, Ceiling).
void SetConcatAxis(unsigned int concatAxis)
Set the concatenation axis value.
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
void SetConstant(const bool IsConstant=true)
Marks the data corresponding to this tensor info as constant.
Definition: Tensor.cpp:516
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
Definition: NumericCast.hpp:35
armnn::TensorInfo ToTensorInfo(TensorRawPtr tensorPtr)
A TransposeDescriptor for the TransposeLayer.
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
virtual const char * GetName() const =0
Returns the name of the layer.
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:241
virtual int Connect(IInputSlot &destination)=0
#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL,...)
Definition: OnnxParser.cpp:116
virtual std::vector< TensorShape > InferOutputShapes(const std::vector< TensorShape > &inputShapes) const =0
Infer the shape of the output(s) based on the provided input shape(s)
armnn::BindingPointInfo BindingPointInfo
Definition: IOnnxParser.hpp:17
armnn::INetworkPtr CreateNetworkFromString(const std::string &protoText)
Create the network directly from protobuf text in a string. Useful for debugging/testing.
Definition: OnnxParser.cpp:797
#define CHECKED_INT32(VALUE)
A Pooling2dDescriptor for the Pooling2dLayer.
#define ONNX_PARSER_VERSION
ONNX_PARSER_VERSION: "X.Y.Z" where: X = Major version number Y = Minor version number Z = Patch versi...
Definition: Version.hpp:25
std::unique_ptr< IOnnxParser, void(*)(IOnnxParser *parser)> IOnnxParserPtr
Definition: IOnnxParser.hpp:21
static ModelPtr LoadModelFromBinaryFile(const char *fileName)
Definition: OnnxParser.cpp:734
float m_B
Beta lower bound value used by the activation functions. (BoundedReLu, Linear, TanH).
Definition: Descriptors.hpp:63
armnn::TensorShape Permuted(const armnn::TensorShape &srcShape, const armnn::PermutationVector &mappings)
Definition: Permute.cpp:98
PermutationVector m_DimMappings
Indicates how to translate tensor elements from a given source into the target destination, when source and target potentially have different memory layouts e.g.
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square, Elu).
Definition: Descriptors.hpp:59
An input connection slot for a layer.
Definition: INetwork.hpp:26
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
A BatchNormalizationDescriptor for the BatchNormalizationLayer.
uint32_t m_PadLeft
Padding left value in the width dimension.
unsigned int GetNumElements() const
Definition: Tensor.hpp:196
ActivationFunction
Definition: Types.hpp:73