ArmNN  NotReleased
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
#define CHECK_VALID_SIZE(ACTUAL,...)
std::unique_ptr< onnx::ModelProto > ModelPtr
Definition: OnnxParser.hpp:23
#define VALID_INPUTS(NODE, VALID_INPUTS)
Definition: OnnxParser.cpp:382
#define CHECKED_INT32(VALUE)
static ModelPtr LoadModelFromTextFile(const char *fileName)
Definition: OnnxParser.cpp:492
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:43
uint32_t m_PadRight
Padding right value in the width dimension.
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:199
static std::vector< std::string > GetOutputs(ModelPtr &model)
Retrieve outputs names.
uint32_t m_PoolHeight
Pooling height value.
uint32_t m_PadTop
Padding top value in the height dimension.
virtual armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile) override
Create the network from a protobuf text file on disk.
Definition: OnnxParser.cpp:519
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:20
ActivationFunction
Definition: Types.hpp:54
A BatchNormalizationDescriptor for the BatchNormalizationLayer.
uint32_t m_PadRight
Padding right value in the width dimension.
uint32_t m_PadBottom
Padding bottom value in the height dimension.
#define CHECK_LOCATION()
Definition: Exceptions.hpp:169
bool m_BiasEnabled
Enable/disable bias.
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
#define CHECKED_NON_NEGATIVE(VALUE)
virtual std::vector< TensorShape > InferOutputShapes(const std::vector< TensorShape > &inputShapes) const =0
A ReshapeDescriptor for the ReshapeLayer.
uint32_t m_PadTop
Padding top value in the height dimension.
TensorShape m_TargetShape
Target shape value.
uint32_t m_PadLeft
Padding left value in the width dimension.
uint32_t m_PoolWidth
Pooling width value.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
unsigned int GetNumBytes() const
Definition: Tensor.cpp:213
#define STR_LIST(...)
Definition: OnnxParser.cpp:52
virtual unsigned int GetNumOutputSlots() const =0
uint32_t m_PadLeft
Padding left value in the width dimension.
An input connection slot for a layer. The input slot can be connected to an output slot of the preced...
Definition: INetwork.hpp:24
unsigned int GetNumElements() const
Definition: Tensor.cpp:106
A FullyConnectedDescriptor for the FullyConnectedLayer.
float m_Eps
Value to add to the variance. Used to avoid dividing by zero.
void SetShape(const TensorShape &newShape)
Definition: Tensor.hpp:90
static ModelPtr LoadModelFromString(const std::string &inputString)
Definition: OnnxParser.cpp:564
void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t &outPadHead, uint32_t &outPadTail, bool samePadding)
Definition: TfParser.cpp:419
PaddingMethod m_PaddingMethod
The padding method to be used. (Exclude, IgnoreValue).
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 ...
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:85
bool m_BiasEnabled
Enable/disable bias.
static std::vector< std::string > GetInputs(ModelPtr &model)
Retrieve inputs names.
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
OutputShapeRounding m_OutputShapeRounding
The rounding method for the output shape. (Floor, Ceiling).
armnn::BindingPointInfo BindingPointInfo
Definition: IOnnxParser.hpp:17
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square).
Definition: Descriptors.hpp:35
An output connection slot for a layer. The output slot may be connected to 1 or more input slots of s...
Definition: INetwork.hpp:37
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL,...)
Definition: OnnxParser.cpp:48
static ModelPtr LoadModelFromBinaryFile(const char *fileName)
Definition: OnnxParser.cpp:527
DataType
Definition: Types.hpp:32
unsigned int GetNumElements() const
Definition: Tensor.hpp:93
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:61
A Pooling2dDescriptor for the Pooling2dLayer.
virtual unsigned int GetNumInputSlots() const =0
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...
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile) override
Create the network from a protobuf binary file on disk.
Definition: OnnxParser.cpp:557
A Convolution2dDescriptor for the Convolution2dLayer.
armnn::TensorInfo ToTensorInfo(Deserializer::TensorRawPtr tensorPtr)
uint32_t m_PadBottom
Padding bottom value in the height dimension.
const TensorShape & GetShape() const
Definition: Tensor.hpp:88
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
std::string AsString() const
Definition: Exceptions.hpp:29
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
std::unique_ptr< IOnnxParser, void(*)(IOnnxParser *parser)> IOnnxParserPtr
Definition: IOnnxParser.hpp:20