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