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