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