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