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