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