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