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