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