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