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