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