ArmNN
 20.11
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 
7 #include <armnn/Descriptors.hpp>
10 #include <VerificationHelpers.hpp>
11 
12 #include <fmt/format.h>
13 
14 #include <google/protobuf/text_format.h>
15 #include <google/protobuf/io/zero_copy_stream_impl.h>
16 
17 #include <numeric>
18 
19 using namespace armnn;
20 
21 namespace armnnOnnxParser
22 {
23 namespace
24 {
25 void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
26  const onnx::TensorProto::DataType actualValue,
27  const char* validExpr,
28  std::string nodeName,
29  std::string tensorName,
30  const armnn::CheckLocation& location)
31 {
32  bool isValid = std::any_of(validInputTypes.begin(),
33  validInputTypes.end(),
34  [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
35  if (!isValid)
36  {
37  throw ParseException(
38  fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
39  onnx::TensorProto::DataType_Name(actualValue),
40  tensorName,
41  nodeName,
42  validExpr,
43  location.AsString()));
44  }
45 }
46 
47 #define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
48 CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
49 
50 using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
51 #define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
52 
53 template <typename Callable>
54 void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
55  const std::string& attribName,
56  onnx::AttributeProto::AttributeType expectedType,
57  Callable callable)
58 {
59  auto attribs = node.attribute();
60  int attriNum = 0;
61  while (attriNum < node.attribute_size())
62  {
63  if (attribs.Get(attriNum).name() == attribName)
64  {
65  if (attribs.Get(attriNum).type() == expectedType)
66  {
67  callable(attribs.Get(attriNum));
68  }
69  else
70  {
71  throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
72  "onnx::AttributeProto::AttributeType, but found {} instead {}",
73  attribName,
74  node.name(),
75  onnx::AttributeProto::AttributeType_Name(expectedType),
76  onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
77  CHECK_LOCATION().AsString()));
78  }
79  break;
80  }
81  ++attriNum;
82  }
83  if (attriNum == node.attribute_size())
84  {
85  throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
86  attribName, node.name(), CHECK_LOCATION().AsString()));
87  }
88 }
89 
90 template <typename Callable>
91 void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
92  const std::string& attribName,
93  onnx::AttributeProto::AttributeType expectedType,
94  Callable callable)
95 {
96  auto attribs = node.attribute();
97  for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
98  {
99  if (attribs.Get(attriNum).name() == attribName)
100  {
101  if (attribs.Get(attriNum).type() == expectedType)
102  {
103  callable(attribs.Get(attriNum));
104  }
105  else
106  {
107  throw ParseException(
108  fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
109  "but found {} instead {}",
110  attribName,
111  node.name(),
112  onnx::AttributeProto::AttributeType_Name(expectedType),
113  onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
114  CHECK_LOCATION().AsString()));
115  }
116  }
117  }
118 }
119 
120 int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
121  const std::string& name,
122  const int64_t defaultValue = 0)
123 {
124  int64_t attribValue = defaultValue;
125  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
126  [&attribValue](const onnx::AttributeProto& attrValue)
127  {
128  attribValue = attrValue.i();
129  });
130  return attribValue;
131 }
132 
133 std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
134  const std::string& name)
135 {
136  std::vector<uint32_t> attriList;
137  ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
138  [&attriList](const onnx::AttributeProto& attrValue)
139  {
140  for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
141  {
142  attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
143  }
144  });
145  return attriList;
146 }
147 
148 uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
149  const std::string& name,
150  const uint32_t defaultVal = 0u)
151 {
152  uint32_t attribValue = defaultVal;
153  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
154  [&attribValue](const onnx::AttributeProto& attrValue)
155  {
156  attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
157  });
158  return attribValue;
159 }
160 
161 std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
162  const std::string& name)
163 {
164  std::vector<uint32_t> attriList;
165  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
166  [&attriList](const onnx::AttributeProto& attrValue)
167  {
168  for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
169  {
170  attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
171  }
172  });
173 
174  return attriList;
175 }
176 
177 float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
178  const std::string& name,
179  const float defaultValue = 0.0f)
180 {
181  float attribValue = defaultValue;
182  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
183  [&attribValue](const onnx::AttributeProto& attrValue)
184  {
185  attribValue = attrValue.f();
186  });
187  return attribValue;
188 }
189 
190 std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
191 {
192  std::string attribValue = "";
193  ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
194  [&attribValue](const onnx::AttributeProto& attrValue)
195  {
196  attribValue = attrValue.s();
197  });
198  return attribValue;
199 }
200 
201 armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
202 {
203  DataType type;
204  switch(data_type)
205  {
206  case onnx::TensorProto::FLOAT:
207  {
208  type = DataType::Float32;
209  break;
210  }
211  case onnx::TensorProto::INT32:
212  case onnx::TensorProto::INT64:
213  {
214  type = DataType::Signed32;
215  break;
216  }
217  default:
218  {
219  throw ParseException(
220  fmt::format("'{}' is not a currently supported datatype for tensor {}."
221  " Supported dataTypes are FLOAT, INT32 and INT64. {}",
222  onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
223  name,
224  CHECK_LOCATION().AsString() ));
225  }
226  }
227 
228  // To avoid crashes by trivial tensors
229  if (shape.empty())
230  {
231  return TensorInfo(TensorShape(), type);
232  }
233 
234  return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
235 }
236 
237 armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
238 {
239  const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
240  std::vector<unsigned int> shapeDims;
241  for (int i = 0; i < onnxShape.dim_size(); ++i)
242  {
243  shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
244  }
245 
246  if (shapeDims.empty())
247  {
248  shapeDims.push_back(1);
249  }
250 
251  return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
252 }
253 
254 armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
255 {
256  std::vector<unsigned int> shapeDims;
257 
258  for (auto dim: tensor.dims())
259  {
260  shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
261  }
262 
263  if (shapeDims.empty())
264  {
265  shapeDims.push_back(1);
266  }
267 
268  return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
269 }
270 
271 std::string TensorInfoAsString(const TensorInfo& info,
272  const std::string& name,
273  const onnx::TensorProto::DataType& type)
274 {
275  const TensorShape shape = info.GetShape();
276  std::stringstream ss;
277  ss << "tensor '" << name << "' contains "
278  << onnx::TensorProto::DataType_Name(type)
279  << " and has shape [";
280 
281  for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
282  {
283  ss << shape[i] << ", ";
284  }
285  ss << shape[shape.GetNumDimensions() - 1] << "]";
286  return ss.str();
287 }
288 
289 void CalcPadding(uint32_t inputSize, uint32_t filterSize, uint32_t stride, uint32_t* paddingFront,
290  uint32_t* paddingBack, bool isUpper)
291 {
292  uint32_t outputSize = (inputSize + stride - 1) / stride;
293  uint32_t temp = (outputSize - 1) * stride + filterSize;
294  *paddingFront = (temp - inputSize) / 2;
295  *paddingBack = *paddingFront;
296  if((temp - inputSize) % 2 == 1)
297  {
298  if (isUpper)
299  {
300  *paddingBack += 1;
301  }
302  else
303  {
304  *paddingFront += 1;
305  }
306  }
307 }
308 
309 TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
310  const TensorShape& inShape,
311  const std::string& outName)
312 {
313  std::vector<int> targetDims;
314  for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
315  {
316  int val = CHECKED_INT32(targetShapeTensor[i]);
317  if(val == 0)
318  {
319  targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
320  }
321  else
322  {
323  targetDims.push_back(val);
324  }
325  }
326 
327  std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
328  const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
329  if (stretchDim != targetDims.end())
330  {
331  if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
332  {
333  std::stringstream ss;
334  ss << "[ ";
335  for(uint i = 0; i < targetDims.size() - 1; ++i)
336  {
337  ss << targetDims[i] << ", ";
338  }
339  ss << targetDims[targetDims.size() - 1] << " ]";
340 
341  throw ParseException(
342  fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
343  " -1 and here, shape is {} {}",
344  outName,
345  ss.str(),
346  CHECK_LOCATION().AsString()));
347  }
348 
349  auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
350  -1, std::multiplies<int32_t>()));
351  auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
352  outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
353  }
354  TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
355  return TensorInfo(outShape, DataType::Float32);
356 }
357 
358 } //namespace
359 
360 const std::map<std::string, OnnxParser::OperationParsingFunction> OnnxParser::m_ParserFunctions = {
361  { "BatchNormalization", &OnnxParser::ParseBatchNormalization},
362  { "GlobalAveragePool", &OnnxParser::ParseGlobalAveragePool},
363  { "AveragePool", &OnnxParser::ParseAveragePool },
364  { "Clip", &OnnxParser::ParseClip },
365  { "Constant", &OnnxParser::ParseConstant },
366  { "MaxPool", &OnnxParser::ParseMaxPool },
367  { "Reshape", &OnnxParser::ParseReshape },
368  { "Sigmoid", &OnnxParser::ParseSigmoid },
369  { "Tanh", &OnnxParser::ParseTanh },
370  { "Relu", &OnnxParser::ParseRelu },
371  { "LeakyRelu", &OnnxParser::ParseLeakyRelu },
372  { "Conv", &OnnxParser::ParseConv },
373  { "Add", &OnnxParser::ParseAdd },
374  { "Flatten", &OnnxParser::ParseFlatten},
375 };
376 
377 template<typename TypePair, typename Location>
378 void OnnxParser::ValidateInputs(const onnx::NodeProto& node,
379  TypePair validInputs,
380  const Location& location)
381 {
382  for(auto input : node.input())
383  {
384  CheckValidDataType(validInputs.second,
385  m_TensorsInfo[input].m_dtype,
386  validInputs.first,
387  node.name(),
388  input,
389  location);
390  }
391 }
392 
393 #define VALID_INPUTS(NODE, VALID_INPUTS) \
394  OnnxParser::ValidateInputs(NODE, \
395  VALID_INPUTS, \
396  CHECK_LOCATION())
397 
398 std::vector<TensorInfo> OnnxParser::ComputeOutputInfo(std::vector<std::string> outNames,
399  const IConnectableLayer* layer,
400  std::vector<TensorShape> inputShapes)
401 {
402  ARMNN_ASSERT(! outNames.empty());
403  bool needCompute = std::any_of(outNames.begin(),
404  outNames.end(),
405  [this](std::string name)
406  {
407  return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr);
408  });
409  std::vector<TensorInfo> outInfo;
410  //if the output info(s) are not here, we need to compute them
411  std::vector<TensorShape> inferredShapes;
412  if(needCompute)
413  {
414  inferredShapes = layer->InferOutputShapes(inputShapes);
415  ARMNN_ASSERT(inferredShapes.size() == outNames.size());
416  }
417  for (uint i = 0; i < outNames.size(); ++i)
418  {
419  if(needCompute)
420  {
421  m_TensorsInfo[outNames[i]] = OnnxTensor();
422  m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
423  TensorInfo(inferredShapes[i], DataType::Float32));
424  }
425  outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
426  }
427  return outInfo;
428 }
429 
430 IOnnxParser* IOnnxParser::CreateRaw()
431 {
432  return new OnnxParser();
433 }
434 
435 IOnnxParserPtr IOnnxParser::Create()
436 {
437  return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
438 }
439 
440 void IOnnxParser::Destroy(IOnnxParser* parser)
441 {
442  delete parser;
443 }
444 
445 OnnxParser::OnnxParser()
446  : m_Network(nullptr, nullptr)
447 {
448 }
449 
450 void OnnxParser::ResetParser()
451 {
452  m_Network = armnn::INetworkPtr(nullptr, nullptr);
453  m_Graph = nullptr;
454 }
455 
456 void OnnxParser::Cleanup()
457 {
458  m_TensorConnections.clear();
459  m_TensorsInfo.clear();
460  m_OutputsMap.clear();
461  m_OutputsFusedAndUsed.clear();
462 }
463 
464 std::pair<ConstTensor, std::unique_ptr<float[]>> OnnxParser::CreateConstTensor(const std::string name)
465 {
466  const TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
467  onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
468 
469  auto srcData = onnxTensor.float_data().data();
470  std::unique_ptr<float[]> tensorData(new float[tensorInfo.GetNumElements()]);
471  const size_t tensorSizeInBytes = tensorInfo.GetNumBytes();
472  // Copy the value list entries into the destination
473  if (!onnxTensor.has_raw_data())
474  {
475  if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
476  {
477  throw ParseException(
478  fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
479  "elements ({}) {}",
480  onnxTensor.float_data_size(),
481  name,
482  tensorInfo.GetNumElements(),
483  CHECK_LOCATION().AsString()));
484  }
485  ::memcpy(tensorData.get(), srcData, tensorSizeInBytes);
486  }
487  else
488  {
489  ::memcpy(tensorData.get(), onnxTensor.raw_data().c_str(), tensorSizeInBytes);
490  }
491 
492  // Const tensors requires at least a list of values
493  if (tensorInfo.GetNumElements() == 0)
494  {
495  throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
496  name,
497  CHECK_LOCATION().AsString()));
498  }
499  return std::make_pair(ConstTensor(tensorInfo, tensorData.get()), std::move(tensorData));
500 }
501 
503 {
504  FILE* fd = fopen(graphFile, "r");
505 
506  if (fd == nullptr)
507  {
508  throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
509  }
510 
511  // Parse the file into a message
512  ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
513  using google::protobuf::io::FileInputStream;
514  std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
515  bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
516  fclose(fd);
517 
518  if (!success)
519  {
520  std::stringstream error;
521  error << "Failed to parse graph file";
522  throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
523  }
524  return modelProto;
525 }
526 
528 {
529  ResetParser();
530  ModelPtr modelProto = LoadModelFromTextFile(graphFile);
531  return CreateNetworkFromModel(*modelProto);
532 }
533 
534 
536 {
537  FILE* fd = fopen(graphFile, "rb");
538 
539  if (fd == nullptr)
540  {
541  throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
542  }
543 
544  // Parse the file into a message
545  ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
546 
547  google::protobuf::io::FileInputStream inStream(fileno(fd));
548  google::protobuf::io::CodedInputStream codedStream(&inStream);
549  codedStream.SetTotalBytesLimit(INT_MAX);
550  bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
551  fclose(fd);
552 
553  if (!success)
554  {
555  std::stringstream error;
556  error << "Failed to parse graph file";
557  throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
558  }
559  return modelProto;
560 
561 }
562 
564 {
565  ResetParser();
566  ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
567  return CreateNetworkFromModel(*modelProto);
568 }
569 
570 ModelPtr OnnxParser::LoadModelFromString(const std::string& protoText)
571 {
572  if (protoText == "")
573  {
574  throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
575  CHECK_LOCATION().AsString()));
576  }
577  // Parse the string into a message
578  ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
579  bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
580  if (!success)
581  {
582  std::stringstream error;
583  error << "Failed to parse graph file";
584  throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
585  }
586  return modelProto;
587 }
588 
590 {
591  ResetParser();
592  ModelPtr modelProto = LoadModelFromString(protoText);
593  return CreateNetworkFromModel(*modelProto);
594 }
595 
596 INetworkPtr OnnxParser::CreateNetworkFromModel(onnx::ModelProto& model)
597 {
598  m_Network = INetwork::Create();
599  try
600  {
601  m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
602  LoadGraph();
603  }
604  catch (const ParseException& e)
605  {
606  Cleanup();
607  throw e;
608  }
609  Cleanup();
610  return std::move(m_Network);
611 }
612 
613 void OnnxParser::LoadGraph()
614 {
615  ARMNN_ASSERT(m_Graph.get() != nullptr);
616 
617  //Fill m_TensorsInfo with the shapes and value of every tensor
618  SetupInfo(m_Graph->mutable_output());
619  SetupInfo(m_Graph->mutable_input());
620  SetupInfo(m_Graph->mutable_value_info());
621 
622  for (auto tensor : m_Graph->initializer())
623  {
624  m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
625  m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
626  m_TensorsInfo[tensor.name()].m_dtype =
627  static_cast<onnx::TensorProto::DataType>(tensor.data_type());
628  }
629 
630  SetupInputLayers();
631  SetupOutputLayers();
632 
633  //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
634  DetectFullyConnected();
635 
636  //Parsing the graph
637  for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
638  {
639  auto node = m_Graph->node(static_cast<int>(nodeIndex));
640  const std::string& operation = node.op_type();
641 
642  // check which layers we handled already (add and matmul fused as FC)
643  if (operation == "MatMul" )
644  {
645  if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
646  {
647  //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
648  AddFullyConnected(node);
649  }
650  }
651  else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
652  {
653  int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
654  AddFullyConnected(m_Graph->node(matmulIndex), &node);
655  }
656  else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
657  {
658  auto it = m_ParserFunctions.find(operation);
659  if (it != m_ParserFunctions.end())
660  {
661  auto func = it->second;
662  (this->*func)(node);
663  }
664  else
665  {
666  throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
667  operation,
668  node.name(),
669  CHECK_LOCATION().AsString()));
670  }
671  }
672  }
673 
674  //Making the connections between outputs and inputs of each layers
675  for (const auto& tensorCon : m_TensorConnections)
676  {
677  if (tensorCon.second.outputSlot != nullptr)
678  {
679  for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
680  {
681  tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
682  }
683  }
684  }
685 }
686 
687 void OnnxParser::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
688 {
689  for (auto tensor : *list)
690  {
691  m_TensorsInfo[tensor.name()] = OnnxTensor();
692  m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
693  m_TensorsInfo[tensor.name()].m_dtype =
694  static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
695  }
696 }
697 
698 void OnnxParser::DetectFullyConnected()
699 {
700  m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
701  auto matmulAndConstant = [&](const std::string& constInput,
702  const std::string& matmulInput,
703  int& nodeIndex)
704  {
705  auto matmulIt = m_OutputsMap.find(matmulInput);
706  if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
707  && m_TensorsInfo[constInput].isConstant())
708  {
709  nodeIndex = matmulIt->second.second;
710  return true;
711  }
712  return false;
713  };
714 
715  for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
716  {
717  const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
718  for (const std::string& output : node->output())
719  {
720  m_OutputsMap[output] = std::make_pair(node, nodeIndex);
721  }
722 
723  for (const std::string& input : node->input()) //count how many time a node is used as input
724  {
725  auto matmulIt = m_OutputsMap.find(input);
726  if(matmulIt != m_OutputsMap.end()){
727  ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
728  }
729  }
730 
731  if (node->op_type() == "Add")
732  {
733  int matmulIndex = 0;
734  if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
735  matmulAndConstant(node->input(1), node->input(0), matmulIndex))
736  {
737  //matmul and add were fused
738  m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
739  .push_back(static_cast<size_t>(nodeIndex));
740 
741  m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
742  .push_back(static_cast<size_t>(matmulIndex));
743  }
744  }
745  }
746 
747  for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
748  auto matmulIt = m_OutputsMap.find(output.name());
749  if(matmulIt != m_OutputsMap.end()){
750  ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
751  }
752  }
753 }
754 
755 template<typename Location>
756 void OnnxParser::GetInputAndParam(const onnx::NodeProto& node,
757  std::string* inputName,
758  std::string* constName,
759  const Location& location)
760 {
761  int cstIndex;
762  if (m_TensorsInfo[node.input(0)].isConstant())
763  {
764  cstIndex = 0;
765  }
766  else if (m_TensorsInfo[node.input(1)].isConstant())
767  {
768  cstIndex = 1;
769  }
770  else
771  {
772  throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
773  node.input(0),
774  node.input(1),
775  node.name(),
776  location.AsString()));
777  }
778  if(constName)
779  {
780  *constName = node.input(cstIndex);
781  }
782  if(inputName)
783  {
784  *inputName = node.input(!cstIndex);
785  }
786 }
787 
788 template<typename Location>
789 void OnnxParser::To1DTensor(const std::string& name, const Location& location)
790 {
791  TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
792  std::vector<uint32_t> newShape;
793  for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
794  {
795  if(shape[i] != 1)
796  {
797  throw ParseException(
798  fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
799  TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
800  location.AsString()));
801  }
802  }
803  newShape.push_back(shape[shape.GetNumDimensions() - 1]);
804 
805  m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
806 }
807 
808 void OnnxParser::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
809 {
810  ARMNN_ASSERT(node.op_type() == "Conv");
811 
813  desc.m_PadLeft = convDesc.m_PadLeft;
814  desc.m_PadRight = convDesc.m_PadRight;
815  desc.m_PadTop = convDesc.m_PadTop;
816  desc.m_PadBottom = convDesc.m_PadBottom;
817  desc.m_StrideX = convDesc.m_StrideX;
818  desc.m_StrideY = convDesc.m_StrideY;
819  desc.m_BiasEnabled = convDesc.m_BiasEnabled;
820 
822  auto weightTensor = CreateConstTensor(node.input(1));
823  TensorShape& weightShape = weightTensor.first.GetShape();
824  weightShape[1] = weightShape[0];
825  weightShape[0] = 1;
826  m_TensorsInfo[node.input(1)].m_info->SetShape(weightShape);
827 
828  if (node.input_size() == 3)
829  {
830  if(!m_TensorsInfo[node.input(2)].isConstant())
831  {
832  throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
833  node.input(2),
834  node.name(),
835  CHECK_LOCATION().AsString()));
836  }
837  desc.m_BiasEnabled = true;
838  auto biasTensor = CreateConstTensor(node.input(2));
839  layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
840  weightTensor.first,
841  Optional<ConstTensor>(biasTensor.first),
842  node.name().c_str());
843  }
844  else
845  {
846  layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
847  weightTensor.first,
848  EmptyOptional(),
849  node.name().c_str());
850  }
851  ARMNN_ASSERT(layer != nullptr);
852 
853  auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
854  { m_TensorsInfo[node.input(0)].m_info->GetShape(),
855  m_TensorsInfo[node.input(1)].m_info->GetShape() });
856 
857  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
858 
859  // register the input connection slots for the layer, connections are made after all layers have been created
860  // only the tensors for the inputs are relevant, exclude the const tensors
861  RegisterInputSlots(layer, {node.input(0)});
862 
863  // register the output connection slots for the layer, connections are made after all layers have been created
864  RegisterOutputSlots(layer, {node.output(0)});
865 }
866 
867 void OnnxParser::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
868 {
869 
870  // find matmul inputs
871  std::string weightName;
872  std::string inputName;
873  CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
874  CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
875  VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
876 
877  GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
878 
880  desc.m_BiasEnabled = addNode != nullptr;
881 
882  IConnectableLayer* layer = nullptr;
883  if(desc.m_BiasEnabled)
884  {
885  // find bias const
886  std::string biasName;
887  CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
888  CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
889  VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
890 
891  GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
892 
893  //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
894  To1DTensor(biasName, CHECK_LOCATION());
895  TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
896  TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
897 
898  if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
899  {
900  throw ParseException(
901  fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
902  " and {} ( /!\\ bias should be a 1D tensor) {}",
903  weightName,
904  addNode->name(),
905  TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
906  m_TensorsInfo[weightName].m_dtype),
907  TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
908  m_TensorsInfo[biasName].m_dtype ),
909  CHECK_LOCATION().AsString()));
910  }
911  layer = m_Network->AddFullyConnectedLayer(desc,
912  CreateConstTensor(weightName).first,
913  Optional<ConstTensor>(CreateConstTensor(biasName).first),
914  matmulNode.name().c_str());
915  ARMNN_ASSERT(layer != nullptr);
916 
917  auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
918  {m_TensorsInfo[inputName].m_info->GetShape(),
919  m_TensorsInfo[weightName].m_info->GetShape()});
920 
921  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
922 
923  RegisterInputSlots(layer, {inputName});
924  RegisterOutputSlots(layer, {addNode->output(0)});
925  }
926  else
927  {
928  layer = m_Network->AddFullyConnectedLayer(desc,
929  CreateConstTensor(weightName).first,
930  EmptyOptional(),
931  matmulNode.name().c_str());
932  ARMNN_ASSERT(layer != nullptr);
933 
934  auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
935  {m_TensorsInfo[inputName].m_info->GetShape(),
936  m_TensorsInfo[weightName].m_info->GetShape()});
937  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
938 
939  RegisterInputSlots(layer, {inputName});
940  RegisterOutputSlots(layer, {matmulNode.output(0)});
941  }
942 }
943 
944 void OnnxParser::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
945 {
946 
947  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
948  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
949 
950  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
951 
952  std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
953  std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
954  std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
955 
956  desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
957  desc.m_PoolWidth = kernel_shape[1];
958  desc.m_PoolHeight = kernel_shape[0];
959 
960  if(strides.empty())
961  {
962  desc.m_StrideX = 1;
963  desc.m_StrideY = 1;
964  }
965  else
966  {
967  desc.m_StrideX = strides[1];
968  desc.m_StrideY = strides[0];
969  }
970 
971  //Check new padding version first
972  if(pads.empty())
973  {
974  //Check deprecated version
975  std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
976  if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
977  {
978  bool isUpper;
979  if( paddingString == "SAME_LOWER")
980  {
981  isUpper = false;
982  }
983  else if (paddingString == "SAME_UPPER")
984  {
985  isUpper = true;
986  }
987  else
988  {
989  throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
990  "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
991  node.name(),
992  paddingString,
993  CHECK_LOCATION().AsString()));
994  }
995  auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
996  uint32_t inputHeight = inputInfo.GetShape()[2];
997  uint32_t inputWidth = inputInfo.GetShape()[3];
998  CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
999  CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1000  }
1001  }
1002  else
1003  {
1004  desc.m_PadTop = pads[0];
1005  desc.m_PadLeft = pads[1];
1006  desc.m_PadBottom = pads[2];
1007  desc.m_PadRight = pads[3];
1008  }
1009 
1010  IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1011  ARMNN_ASSERT(layer != nullptr);
1012 
1013  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1014  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1015 
1016  // register the input connection slots for the layer, connections are made after all layers have been created
1017  // only the tensors for the inputs are relevant, exclude the const tensors
1018  RegisterInputSlots(layer, {node.input(0)});
1019 
1020  // register the output connection slots for the layer, connections are made after all layers have been created
1021  RegisterOutputSlots(layer, {node.output(0)});
1022 }
1023 
1024 std::pair<std::string, std::string> OnnxParser::AddPrepareBroadcast(const std::string& input0,
1025  const std::string& input1)
1026 {
1027  std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1028 
1029  TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1030  TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1031 
1032  if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1033  {
1034  auto outputName = fmt::format("reshape_output_{}", input1);
1035  PrependForBroadcast(outputName, input1, input0);
1036  inputs.second = outputName;
1037  }
1038  else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1039  {
1040  auto outputName = fmt::format("reshape_output_{}", input0);
1041  PrependForBroadcast(outputName, input0, input1);
1042  inputs.first = outputName;
1043  }
1044  return inputs;
1045 }
1046 
1047 void OnnxParser::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
1048 {
1049  auto armnnTensor = CreateConstTensor(tensorName);
1050 
1051  IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1052  layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1053  RegisterOutputSlots(layer, {tensorName});
1054 }
1055 
1056 void OnnxParser::CreateReshapeLayer(const std::string& inputName,
1057  const std::string& outputName,
1058  const std::string& layerName)
1059 {
1060  const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1061  ReshapeDescriptor reshapeDesc;
1062  reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1063 
1064  IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
1065  ARMNN_ASSERT(layer != nullptr);
1066  layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1067 
1068  // register the input connection slots for the layer, connections are made after all layers have been created
1069  // only the tensors for the inputs are relevant, exclude the const tensors
1070  RegisterInputSlots(layer, {inputName});
1071 
1072  // register the output connection slots for the layer, connections are made after all layers have been created
1073  RegisterOutputSlots(layer, {outputName});
1074 }
1075 
1076 void OnnxParser::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
1077 {
1078  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
1079  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1080 
1081  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1082 
1083  ActivationDescriptor desc;
1084  desc.m_Function = func;
1085 
1086  if (func == ActivationFunction::BoundedReLu)
1087  {
1088  desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1089  desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1090  }
1091 
1092  IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
1093  ARMNN_ASSERT(layer != nullptr);
1094 
1095  auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1096  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1097 
1098  // register the input connection slots for the layer, connections are made after all layers have been created
1099  // only the tensors for the inputs are relevant, exclude the const tensors
1100  RegisterInputSlots(layer, {node.input(0)});
1101 
1102  // register the output connection slots for the layer, connections are made after all layers have been created
1103  RegisterOutputSlots(layer, {node.output(0)});
1104 }
1105 
1106 void OnnxParser::ParseClip(const onnx::NodeProto& node)
1107 {
1108  ParseActivation(node, ActivationFunction::BoundedReLu);
1109 }
1110 
1111 void OnnxParser::ParseSigmoid(const onnx::NodeProto& node)
1112 {
1113  ParseActivation(node, ActivationFunction::Sigmoid);
1114 }
1115 
1116 void OnnxParser::ParseTanh(const onnx::NodeProto& node)
1117 {
1118  ParseActivation(node, ActivationFunction::TanH);
1119 }
1120 
1121 void OnnxParser::ParseRelu(const onnx::NodeProto& node)
1122 {
1123  ParseActivation(node, ActivationFunction::ReLu);
1124 }
1125 
1126 void OnnxParser::ParseLeakyRelu(const onnx::NodeProto& node)
1127 {
1128  ParseActivation(node, ActivationFunction::LeakyReLu);
1129 }
1130 
1131 void OnnxParser::ParseAdd(const onnx::NodeProto& node)
1132 {
1133  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1134  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1135 
1136  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1137 
1138  // TODO: unify broadcast validation code across layers
1139  // tracked by: IVGCVSW-1576
1140 
1141  // Checking broadcast compatibility : only scalar or 1D tensors
1142  auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1143  auto input0 = *m_TensorsInfo[inputs.first].m_info;
1144  auto input1 = *m_TensorsInfo[inputs.second].m_info;
1145  ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1146 
1147  unsigned int numDims = input0.GetNumDimensions();
1148  for (unsigned int i = 0; i < numDims; i++)
1149  {
1150  unsigned int dim0 = input0.GetShape()[i];
1151  unsigned int dim1 = input1.GetShape()[i];
1152  if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
1153  {
1154  throw ParseException(
1155  fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1156  "Input dimensions should either match or one should be of size 1 and here, "
1157  "{} and {} {}",
1158  node.name(),
1159  TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1160  m_TensorsInfo[inputs.first].m_dtype),
1161  TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1162  m_TensorsInfo[inputs.second].m_dtype),
1163  CHECK_LOCATION().AsString()));
1164  }
1165  }
1166 
1167 
1168  IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
1169  ARMNN_ASSERT(layer != nullptr);
1170 
1171  auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1172  { m_TensorsInfo[inputs.first].m_info->GetShape(),
1173  m_TensorsInfo[inputs.second].m_info->GetShape() });
1174  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1175 
1176  // register the input connection -> for constant inputs, we need to make a newDim constant layer
1177  if(m_TensorsInfo[inputs.first].isConstant()) {
1178  CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
1179  }
1180  if(m_TensorsInfo[inputs.second].isConstant()) {
1181  CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
1182  }
1183  RegisterInputSlots(layer, {inputs.first, inputs.second});
1184 
1185  // register the output connection
1186  RegisterOutputSlots(layer, {node.output(0)});
1187 }
1188 
1189 void OnnxParser::ParseAveragePool(const onnx::NodeProto& node)
1190 {
1191  Pooling2dDescriptor desc;
1192  desc.m_PoolType = PoolingAlgorithm::Average;
1193 
1194  uint32_t count_include_pad = 0;
1195  count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1196  if(count_include_pad) {
1197  desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1198  }
1199  AddPoolingLayer(node, desc);
1200 }
1201 
1202 void OnnxParser::ParseBatchNormalization(const onnx::NodeProto& node)
1203 {
1204  //IGNORE momentum parameter and spatial parameters
1205 
1206  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1207  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1208 
1209  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1210  for(int ind = 1; ind < node.input_size(); ++ind)
1211  {
1212  auto tensor = node.input(ind);
1213  if(! m_TensorsInfo[tensor].isConstant())
1214  {
1215  throw ParseException(
1216  fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1217  tensor,
1218  node.name(),
1219  CHECK_LOCATION().AsString()));
1220  }
1221  }
1222 
1223  float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1225  desc.m_Eps = epsilon;
1226 
1227  auto scaleTensor = CreateConstTensor(node.input(1));
1228  auto biasTensor = CreateConstTensor(node.input(2));
1229  auto meanTensor = CreateConstTensor(node.input(3));
1230  auto varTensor = CreateConstTensor(node.input(4));
1231 
1232  IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1233  meanTensor.first,
1234  varTensor.first,
1235  biasTensor.first,
1236  scaleTensor.first,
1237  node.name().c_str());
1238  ARMNN_ASSERT(layer != nullptr);
1239 
1240  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1241  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1242 
1243  RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1244 
1245  // register the output connection
1246  RegisterOutputSlots(layer, {node.output(0)});
1247 }
1248 
1249 void OnnxParser::ParseConstant(const onnx::NodeProto& node)
1250 {
1251  CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1252  if (!node.attribute(0).has_t())
1253  {
1254  throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1255  node.name(),
1256  CHECK_LOCATION().AsString()));
1257  }
1258  const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1259 
1260  //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
1261  CHECK_VALID_DATATYPE(node.name(), onnxTensor.name(),
1262  static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
1263 
1264  //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1265  m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1266  m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1267  m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1268 
1269  CreateConstantLayer(node.output(0), node.name());
1270 }
1271 
1272 void OnnxParser::ParseConv(const onnx::NodeProto& node)
1273 {
1274  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1275  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1276 
1277  VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1278 
1279  if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1280  {
1281  throw ParseException(
1282  fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1283  node.name(),
1284  TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1285  m_TensorsInfo[node.input(0)].m_dtype),
1286  CHECK_LOCATION().AsString()));
1287  }
1288 
1289  if(!m_TensorsInfo[node.input(1)].isConstant())
1290  {
1291  throw ParseException(
1292  fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1293  node.input(1),
1294  node.name(),
1295  CHECK_LOCATION().AsString()));
1296  }
1297 
1298  auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1299 
1300  std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1301  if (!dilations.empty())
1302  {
1303  std::stringstream ss;
1304  ss << "[ ";
1305  for (auto dilation : dilations)
1306  {
1307  ss << dilation << ", ";
1308  if (dilation != 1u)
1309  {
1310  ss << "... ]";
1311  throw ParseException(
1312  fmt::format("ArmNN only supports Convolution layers with dilations [1,1], and node '{}' "
1313  "has dilatation {} {}",
1314  node.name(), ss.str(), CHECK_LOCATION().AsString()));
1315  }
1316  }
1317  }
1318 
1320  desc.m_BiasEnabled = false;
1321 
1322  std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1323  if(strides.empty())
1324  {
1325  desc.m_StrideX = 1;
1326  desc.m_StrideY = 1;
1327  }
1328  else
1329  {
1330  desc.m_StrideX = strides[1];
1331  desc.m_StrideY = strides[0];
1332  }
1333 
1334  std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1335  //Check new padding version first
1336  if(pads.empty())
1337  {
1338  //Check deprecated version
1339  std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1340  if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1341  {
1342  bool isUpper;
1343  if( paddingString == "SAME_LOWER")
1344  {
1345  isUpper = false;
1346  }
1347  else if (paddingString == "SAME_UPPER")
1348  {
1349  isUpper = true;
1350  }
1351  else
1352  {
1353  throw ParseException(
1354  fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1355  "supported and found {} {}",
1356  node.name(),
1357  paddingString,
1358  CHECK_LOCATION().AsString()));
1359  }
1360  uint32_t inputHeight = inputInfo.GetShape()[2];
1361  uint32_t inputWidth = inputInfo.GetShape()[3];
1362 
1363  uint32_t weightHeight;
1364  uint32_t weightWidth;
1365  std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1366  if (kernel_shape.empty())
1367  {
1368  const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1369  weightHeight = weightTensorInfo.GetShape()[2];
1370  weightWidth = weightTensorInfo.GetShape()[3];
1371  }
1372  else
1373  {
1374  weightHeight = kernel_shape[0];
1375  weightWidth = kernel_shape[1];
1376  }
1377  CalcPadding(inputHeight, weightHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1378  CalcPadding(inputWidth, weightWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1379  }
1380  }
1381  else
1382  {
1383  desc.m_PadTop = pads[0];
1384  desc.m_PadLeft = pads[1];
1385  desc.m_PadBottom = pads[2];
1386  desc.m_PadRight = pads[3];
1387  }
1388 
1389  uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1390  if(group > 1)
1391  {
1392  if (group > inputInfo.GetShape()[1])
1393  {
1394  throw ParseException(
1395  fmt::format("Error parsing Convolution node: {}. "
1396  "The 'group'={} parameter cannot be larger than the "
1397  "channel of the input shape={} (in NCHW format). {}",
1398  node.name(),
1399  group,
1400  inputInfo.GetShape()[1],
1401  CHECK_LOCATION().AsString()));
1402  }
1403  else if (group == inputInfo.GetShape()[1])
1404  {
1405  // we use a depthwise convolution here, because the number of groups equals to the
1406  // input channels
1407  AddConvLayerWithDepthwiseConv(node, desc);
1408  return;
1409  }
1410  else
1411  {
1412  // TODO: split the input by channels into channels/groups separate convolutions
1413  // and concatenate the results afterwards
1414  throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1415  "The 'group'={} parameter should be 1 or be equal to the "
1416  "channel of the input shape={} (in NCHW format). {}",
1417  node.name(),
1418  group,
1419  inputInfo.GetShape()[1],
1420  CHECK_LOCATION().AsString()));
1421  }
1422  }
1423 
1424  armnn::IConnectableLayer* layer;
1425  auto weightTensor = CreateConstTensor(node.input(1));
1426 
1427  if (node.input_size() == 3)
1428  {
1429  if(!m_TensorsInfo[node.input(2)].isConstant())
1430  {
1431  throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1432  node.input(2),
1433  node.name(),
1434  CHECK_LOCATION().AsString()));
1435  }
1436  desc.m_BiasEnabled = true;
1437  auto biasTensor = CreateConstTensor(node.input(2));
1438  layer = m_Network->AddConvolution2dLayer(desc,
1439  weightTensor.first,
1440  Optional<ConstTensor>(biasTensor.first),
1441  node.name().c_str());
1442  }
1443  else
1444  {
1445  layer = m_Network->AddConvolution2dLayer(desc,
1446  weightTensor.first,
1447  EmptyOptional(),
1448  node.name().c_str());
1449  }
1450  ARMNN_ASSERT(layer != nullptr);
1451 
1452  auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1453  { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1454  m_TensorsInfo[node.input(1)].m_info->GetShape() });
1455  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1456 
1457  // register the input connection slots for the layer, connections are made after all layers have been created
1458  // only the tensors for the inputs are relevant, exclude the const tensors
1459  RegisterInputSlots(layer, {node.input(0)});
1460 
1461  // register the output connection slots for the layer, connections are made after all layers have been created
1462  RegisterOutputSlots(layer, {node.output(0)});
1463 }
1464 
1465 void OnnxParser::ParseFlatten(const onnx::NodeProto& node)
1466 {
1467  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1468  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1469 
1470  CHECK_VALID_DATATYPE(node.name(), node.input(0),
1471  m_TensorsInfo[node.input(0)].m_dtype,
1472  onnx::TensorProto::FLOAT);
1473 
1474  int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1475  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1476 
1477  /// Negative axis conversion
1478  if (axis < 0)
1479  {
1480  axis += inputShape.GetNumDimensions();
1481  }
1482 
1483  /// Check Axis is within dimensions
1484  if (axis < 0 || axis >= inputShape.GetNumDimensions())
1485  {
1486  throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
1487  axis, inputShape.GetNumDimensions(), node.name()));
1488  }
1489 
1490  /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1491  uint dimension1{1};
1492  uint dimension2{1};
1493  uint i{0};
1494 
1495  /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1496  for (i = 0; i < axis; i++){
1497  dimension1 *= inputShape[i];
1498  }
1499 
1500  /// dimension2 = (d_axis * d_(axis+1) ... d_n)
1501  for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
1502  dimension2 *= inputShape[i];
1503  }
1504 
1505  TensorShape outputShape{dimension1, dimension2};
1506 
1507  auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
1508  m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1509  CreateReshapeLayer(node.input(0), node.output(0), node.name());
1510 }
1511 
1512 void OnnxParser::ParseGlobalAveragePool(const onnx::NodeProto& node)
1513 {
1515  desc.m_PoolType = PoolingAlgorithm::Average;
1516 
1517  //kernel size is the same as input
1518  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1519  desc.m_PoolWidth = inputShape[3];
1520  desc.m_PoolHeight = inputShape[2];
1521 
1522  IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1523  ARMNN_ASSERT(layer != nullptr);
1524 
1525  auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
1526  layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1527 
1528  // register the input connection slots for the layer, connections are made after all layers have been created
1529  // only the tensors for the inputs are relevant, exclude the const tensors
1530  RegisterInputSlots(layer, {node.input(0)});
1531 
1532  // register the output connection slots for the layer, connections are made after all layers have been created
1533  RegisterOutputSlots(layer, {node.output(0)});
1534 }
1535 
1536 void OnnxParser::ParseMaxPool(const onnx::NodeProto& node)
1537 {
1538  Pooling2dDescriptor desc;
1539  desc.m_PoolType = PoolingAlgorithm::Max;
1540  desc.m_PaddingMethod = PaddingMethod::Exclude;
1541  AddPoolingLayer(node, desc);
1542 }
1543 
1544 void OnnxParser::ParseReshape(const onnx::NodeProto& node)
1545 {
1546  CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1547  CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1548 
1549  CHECK_VALID_DATATYPE(node.name(), node.input(0),
1550  m_TensorsInfo[node.input(0)].m_dtype,
1551  onnx::TensorProto::FLOAT); //input
1552  CHECK_VALID_DATATYPE(node.name(), node.input(1),
1553  m_TensorsInfo[node.input(1)].m_dtype,
1554  onnx::TensorProto::INT64); //shape
1555 
1556  if(!m_TensorsInfo[node.input(1)].isConstant())
1557  {
1558  throw ParseException(fmt::format("Shape '{}' should be constant in Reshape layer '{}' {}",
1559  node.input(1),
1560  node.name(),
1561  CHECK_LOCATION().AsString()));
1562  }
1563 
1564  if(m_TensorsInfo[node.input(0)].isConstant())
1565  {
1566  //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
1567  if(m_TensorsInfo.count(node.output(0)) == 0)
1568  {
1569  m_TensorsInfo[node.output(0)] = OnnxTensor();
1570  }
1571  m_TensorsInfo[node.output(0)].m_tensor =
1572  std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1573  }
1574  else
1575  {
1576  TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1577 
1578  if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
1579  {
1580  uint64_t dims = static_cast<uint64_t>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
1581  TensorShape targetShape{static_cast<unsigned int>(dims), 1};
1582 
1583  for(uint i = 0; i < dims; i++)
1584  {
1585  int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
1586  targetShape[i]= static_cast<unsigned int>(val);
1587  }
1588 
1589  auto outInfo = ComputeReshapeInfo(targetShape, inputShape, node.output(0));
1590  m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1591  }
1592 
1593  CreateReshapeLayer(node.input(0), node.output(0), node.name());
1594  }
1595 }
1596 
1597 void OnnxParser::PrependForBroadcast(const std::string& outputName,
1598  const std::string& input0,
1599  const std::string& input1)
1600 {
1601  //input0 should be reshaped to have same number of dim as input1
1602  TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
1603 
1604  TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1605  TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1606 
1607  uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
1608  std::vector<uint32_t> newShape;
1609  while(diff > 0)
1610  {
1611  newShape.push_back(1);
1612  diff--;
1613  }
1614  for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
1615  {
1616  newShape.push_back(input0Shape[dim]);
1617  }
1618  outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1619 
1620  //add the new tensor to m_TensorsInfo
1621  m_TensorsInfo[outputName] = OnnxTensor();
1622  m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1623 
1624  //add reshape layer if the parent was not constant...
1625  if( ! m_TensorsInfo[input0].isConstant())
1626  {
1627  CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
1628  }
1629  else //make it constant and it will be create in Add
1630  {
1631  m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1632 
1633  }
1634 }
1635 
1636 void OnnxParser::SetupInputLayers()
1637 {
1638  //Find user input and add their layers
1639  for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1640  {
1641  auto input = m_Graph->input(inputIndex);
1642  if (! m_TensorsInfo[input.name()].isConstant())
1643  {
1644  IConnectableLayer* layer =
1645  m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
1646  auto tensorInfo = ToTensorInfo(input);
1647  layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1648 
1649  RegisterOutputSlots(layer,{ input.name() });
1650  }
1651  }
1652 }
1653 
1654 void OnnxParser::SetupOutputLayers()
1655 {
1656  if(m_Graph->output_size() == 0)
1657  {
1658  throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
1659  }
1660 
1661  for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
1662  {
1663  IConnectableLayer* layer =
1664  m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
1665  m_Graph->output(outputIndex).name().c_str());
1666 
1667  RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
1668  }
1669 }
1670 
1671 void OnnxParser::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1672 {
1673  ARMNN_ASSERT(layer != nullptr);
1674  if (tensorIds.size() != layer->GetNumInputSlots())
1675  {
1676  throw ParseException(
1677  fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
1678  tensorIds.size(),
1679  layer->GetNumInputSlots(),
1680  CHECK_LOCATION().AsString()));
1681  }
1682  for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
1683  {
1684  std::string tensorId = tensorIds[slotIndex];
1685  armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
1686 
1687  auto it = m_TensorConnections.find(tensorId);
1688 
1689  if (it == m_TensorConnections.end())
1690  {
1691  //First time seing this tensor, we need to map it
1692  m_TensorConnections[tensorId] = TensorSlots();
1693  }
1694  m_TensorConnections[tensorId].inputSlots.push_back(slot);
1695  }
1696 }
1697 
1698 void OnnxParser::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1699 {
1700  ARMNN_ASSERT(layer != nullptr);
1701  if (tensorIds.size() != layer->GetNumOutputSlots())
1702  {
1703  throw ParseException(
1704  fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
1705  tensorIds.size(),
1706  layer->GetNumOutputSlots(),
1707  CHECK_LOCATION().AsString()));
1708  }
1709 
1710  for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
1711  {
1712  std::string tensorId = tensorIds[slotIndex];
1713  armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
1714 
1715  auto it = m_TensorConnections.find(tensorId);
1716 
1717  if (it == m_TensorConnections.end())
1718  {
1719  //First time seing this tensor, we need to map it
1720  m_TensorConnections[tensorId] = TensorSlots();
1721  }
1722 
1723  TensorSlots& tensorSlots = m_TensorConnections[tensorId];
1724 
1725  // assuming there is only one producer for that tensor
1726  if (tensorSlots.outputSlot != nullptr)
1727  {
1728  throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
1729  "tensor:{} {}",
1730  tensorId,
1731  CHECK_LOCATION().AsString()));
1732  }
1733  tensorSlots.outputSlot = slot;
1734  }
1735 }
1736 
1738 {
1739  for(int i = 0; i < m_Graph->input_size(); ++i)
1740  {
1741  auto input = m_Graph->input(i);
1742  if(input.name() == name)
1743  {
1744  return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(input));
1745  }
1746  }
1747  throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
1748  name, CHECK_LOCATION().AsString()));
1749 }
1750 
1752 {
1753  for(int i = 0; i < m_Graph->output_size(); ++i)
1754  {
1755  auto output = m_Graph->output(i);
1756  if(output.name() == name)
1757  {
1758  return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(output));
1759  }
1760  }
1761  throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
1762  name, CHECK_LOCATION().AsString()));
1763 }
1764 
1765 std::vector<std::string> OnnxParser::GetInputs(ModelPtr& model)
1766 {
1767  if(model == nullptr) {
1768  throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
1769  CHECK_LOCATION().AsString()));
1770  }
1771 
1772  std::vector<std::string> inputNames;
1773  std::map<std::string, bool> isConstant;
1774  for(auto tensor : model->graph().initializer())
1775  {
1776  isConstant[tensor.name()] = true;
1777  }
1778  for(auto input : model->graph().input())
1779  {
1780  auto it = isConstant.find(input.name());
1781  if(it == isConstant.end())
1782  {
1783  inputNames.push_back(input.name());
1784  }
1785  }
1786  return inputNames;
1787 }
1788 
1789 std::vector<std::string> OnnxParser::GetOutputs(ModelPtr& model)
1790 {
1791  if(model == nullptr) {
1792  throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
1793  CHECK_LOCATION().AsString()));
1794  }
1795 
1796  std::vector<std::string> outputNames;
1797  for(auto output : model->graph().output())
1798  {
1799  outputNames.push_back(output.name());
1800  }
1801  return outputNames;
1802 }
1803 
1804 } // 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:61
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:187
uint32_t m_PadLeft
Padding left value in the width dimension.
std::string AsString() const
Definition: Exceptions.hpp:29
A ReshapeDescriptor for the ReshapeLayer.
virtual BindingPointInfo GetNetworkOutputBindingInfo(const std::string &name) const override
Retrieve binding info (layer id and tensor info) for the network output identified by the given layer...
uint32_t m_PoolWidth
Pooling width value.
virtual armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile) override
Create the network from a protobuf text file on disk.
Definition: OnnxParser.cpp:527
A Convolution2dDescriptor for the Convolution2dLayer.
uint32_t m_PadLeft
Padding left value in the width dimension.
unsigned int GetNumBytes() const
Definition: Tensor.cpp:418
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).
uint32_t m_PadTop
Padding top value in the height dimension.
void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t &outPadHead, uint32_t &outPadTail, bool samePadding)
Definition: TfParser.cpp:406
uint32_t m_PadRight
Padding right value in the width dimension.
#define VALID_INPUTS(NODE, VALID_INPUTS)
Definition: OnnxParser.cpp:393
Copyright (c) 2020 ARM Limited.
static ModelPtr LoadModelFromBinaryFile(const char *fileName)
Definition: OnnxParser.cpp:535
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
void SetShape(const TensorShape &newShape)
Definition: Tensor.hpp:189
static ModelPtr LoadModelFromTextFile(const char *fileName)
Definition: OnnxParser.cpp:502
virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile) override
Create the network from a protobuf binary file on disk.
Definition: OnnxParser.cpp:563
TensorShape m_TargetShape
Target shape value.
static std::vector< std::string > GetInputs(ModelPtr &model)
Retrieve inputs names.
uint32_t m_PoolHeight
Pooling height value.
uint32_t m_PadTop
Padding top value in the height dimension.
static std::vector< std::string > GetOutputs(ModelPtr &model)
Retrieve outputs names.
armnn::TensorInfo ToTensorInfo(Deserializer::TensorRawPtr tensorPtr)
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
std::unique_ptr< onnx::ModelProto > ModelPtr
Definition: OnnxParser.hpp:23
virtual BindingPointInfo GetNetworkInputBindingInfo(const std::string &name) const override
Retrieve binding info (layer id and tensor info) for the network input identified by the given layer ...
DataType
Definition: Types.hpp:32
uint32_t m_PadRight
Padding right value in the width dimension.
An output connection slot for a layer.
Definition: INetwork.hpp:37
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:314
#define CHECK_VALID_SIZE(ACTUAL,...)
#define CHECKED_NON_NEGATIVE(VALUE)
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:20
#define CHECK_LOCATION()
Definition: Exceptions.hpp:197
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:45
EmptyOptional is used to initialize the Optional class in case we want to have default value for an O...
Definition: Optional.hpp:32
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
#define STR_LIST(...)
Definition: OnnxParser.cpp:51
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).
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
virtual armnn::INetworkPtr CreateNetworkFromString(const std::string &protoText) override
Create the network directly from protobuf text in a string. Useful for debugging/testing.
Definition: OnnxParser.cpp:589
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
Definition: NumericCast.hpp:35
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:101
static ModelPtr LoadModelFromString(const std::string &inputString)
Definition: OnnxParser.cpp:570
#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL,...)
Definition: OnnxParser.cpp:47
armnn::BindingPointInfo BindingPointInfo
Definition: IOnnxParser.hpp:17
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)
#define CHECKED_INT32(VALUE)
A Pooling2dDescriptor for the Pooling2dLayer.
std::unique_ptr< IOnnxParser, void(*)(IOnnxParser *parser)> IOnnxParserPtr
Definition: IOnnxParser.hpp:20
float m_B
Beta lower bound value used by the activation functions. (BoundedReLu, Linear, TanH).
Definition: Descriptors.hpp:47
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square, Elu).
Definition: Descriptors.hpp:43
An input connection slot for a layer.
Definition: INetwork.hpp:24
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:192
ActivationFunction
Definition: Types.hpp:56