blob: 01ad12448f235195bd13ca0c44a186ce145b86f1 [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
Matthew Bentham39ef3e52020-01-20 10:09:09 +00007#include <armnn/Descriptors.hpp>
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01008#include <armnn/utility/Assert.hpp>
Matthew Sloyan589e3e82020-09-11 16:17:48 +01009#include <armnn/utility/NumericCast.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010010#include <VerificationHelpers.hpp>
11
Aron Virginas-Tard4f0fea2019-04-09 14:08:06 +010012#include <boost/format.hpp>
Aron Virginas-Tard4f0fea2019-04-09 14:08:06 +010013
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
Ryan OSheaed27ee72020-04-22 16:37:29 +0100122int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
123 const std::string& name,
124 const int64_t defaultValue = 0)
125{
126 int64_t attribValue = defaultValue;
127 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
128 [&attribValue](const onnx::AttributeProto& attrValue)
129 {
130 attribValue = attrValue.i();
131 });
132 return attribValue;
133}
134
telsoa01c577f2c2018-08-31 09:22:23 +0100135std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
136 const std::string& name)
137{
138 std::vector<uint32_t> attriList;
139 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
140 [&attriList](const onnx::AttributeProto& attrValue)
141 {
142 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
143 {
144 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
145 }
146 });
147 return attriList;
148}
149
150uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
151 const std::string& name,
152 const uint32_t defaultVal = 0u)
153{
154 uint32_t attribValue = defaultVal;
155 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
156 [&attribValue](const onnx::AttributeProto& attrValue)
157 {
158 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
159 });
160 return attribValue;
161}
162
163std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
164 const std::string& name)
165{
166 std::vector<uint32_t> attriList;
167 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
168 [&attriList](const onnx::AttributeProto& attrValue)
169 {
170 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
171 {
172 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
173 }
174 });
175
176 return attriList;
177}
178
179float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
180 const std::string& name,
181 const float defaultValue = 0.0f)
182{
183 float attribValue = defaultValue;
184 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
185 [&attribValue](const onnx::AttributeProto& attrValue)
186 {
187 attribValue = attrValue.f();
188 });
189 return attribValue;
190}
191
192std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
193{
194 std::string attribValue = "";
195 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
196 [&attribValue](const onnx::AttributeProto& attrValue)
197 {
198 attribValue = attrValue.s();
199 });
200 return attribValue;
201}
202
Tee Jungfcf6fd52019-11-01 05:27:28 +0000203armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100204{
telsoa01c577f2c2018-08-31 09:22:23 +0100205 DataType type;
Tee Jungfcf6fd52019-11-01 05:27:28 +0000206 switch(data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100207 {
208 case onnx::TensorProto::FLOAT:
209 {
210 type = DataType::Float32;
211 break;
212 }
213 case onnx::TensorProto::INT32:
214 case onnx::TensorProto::INT64:
215 {
216 type = DataType::Signed32;
217 break;
218 }
219 default:
220 {
221 throw ParseException(
222 boost::str(
223 boost::format("'%1%' is not a currently supported datatype for tensor %2%."
224 " Supported dataTypes are FLOAT, INT32 and INT64. %3%") %
Matteo Martincighe355dc22018-12-10 13:45:27 +0000225 onnx::TensorProto::DataType_Name(
Tee Jungfcf6fd52019-11-01 05:27:28 +0000226 static_cast<onnx::TensorProto::DataType>(data_type)) %
227 name %
telsoa01c577f2c2018-08-31 09:22:23 +0100228 CHECK_LOCATION().AsString() ));
229 }
telsoa01c577f2c2018-08-31 09:22:23 +0100230 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000231
232 // To avoid crashes by trivial tensors
233 if (shape.empty())
234 {
235 return TensorInfo(TensorShape(), type);
236 }
237
Tee Jungfcf6fd52019-11-01 05:27:28 +0000238 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
239}
240
241armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
242{
243 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
244 std::vector<unsigned int> shapeDims;
245 for (int i = 0; i < onnxShape.dim_size(); ++i)
246 {
247 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
248 }
249
Ryan OShea337c17f2020-02-21 12:33:17 +0000250 if (shapeDims.empty())
251 {
252 shapeDims.push_back(1);
253 }
254
Tee Jungfcf6fd52019-11-01 05:27:28 +0000255 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
256}
257
258armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
259{
260 std::vector<unsigned int> shapeDims;
Ryan OShea337c17f2020-02-21 12:33:17 +0000261
Tee Jungfcf6fd52019-11-01 05:27:28 +0000262 for (auto dim: tensor.dims())
263 {
264 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
265 }
266
Ryan OShea337c17f2020-02-21 12:33:17 +0000267 if (shapeDims.empty())
268 {
269 shapeDims.push_back(1);
270 }
271
Tee Jungfcf6fd52019-11-01 05:27:28 +0000272 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100273}
274
275std::string TensorInfoAsString(const TensorInfo& info,
276 const std::string& name,
277 const onnx::TensorProto::DataType& type)
278{
279 const TensorShape shape = info.GetShape();
280 std::stringstream ss;
281 ss << "tensor '" << name << "' contains "
282 << onnx::TensorProto::DataType_Name(type)
283 << " and has shape [";
284
285 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
286 {
287 ss << shape[i] << ", ";
288 }
289 ss << shape[shape.GetNumDimensions() - 1] << "]";
290 return ss.str();
291}
292
293void CalcPadding(uint32_t inputSize, uint32_t filterSize, uint32_t stride, uint32_t* paddingFront,
294 uint32_t* paddingBack, bool isUpper)
295{
296 uint32_t outputSize = (inputSize + stride - 1) / stride;
297 uint32_t temp = (outputSize - 1) * stride + filterSize;
298 *paddingFront = (temp - inputSize) / 2;
299 *paddingBack = *paddingFront;
300 if((temp - inputSize) % 2 == 1)
301 {
302 if (isUpper)
303 {
304 *paddingBack += 1;
305 }
306 else
307 {
308 *paddingFront += 1;
309 }
310 }
311}
312
Ryan OSheaed27ee72020-04-22 16:37:29 +0100313TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
telsoa01c577f2c2018-08-31 09:22:23 +0100314 const TensorShape& inShape,
315 const std::string& outName)
316{
317 std::vector<int> targetDims;
Ryan OSheaed27ee72020-04-22 16:37:29 +0100318 for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
telsoa01c577f2c2018-08-31 09:22:23 +0100319 {
Ryan OSheaed27ee72020-04-22 16:37:29 +0100320 int val = CHECKED_INT32(targetShapeTensor[i]);
telsoa01c577f2c2018-08-31 09:22:23 +0100321 if(val == 0)
322 {
323 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
324 }
325 else
326 {
327 targetDims.push_back(val);
328 }
329 }
330
331 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
332 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
333 if (stretchDim != targetDims.end())
334 {
335 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
336 {
337 std::stringstream ss;
338 ss << "[ ";
339 for(uint i = 0; i < targetDims.size() - 1; ++i)
340 {
341 ss << targetDims[i] << ", ";
342 }
343 ss << targetDims[targetDims.size() - 1] << " ]";
344
345 throw ParseException(boost::str(
346 boost::format("Error during creation of reshaped tensor '%1%'. At most one component of shape can be "
347 " -1 and here, shape is %2% %3%")
348 % outName
349 % ss.str()
350 % CHECK_LOCATION().AsString()));
351 }
352
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100353 auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
telsoa01c577f2c2018-08-31 09:22:23 +0100354 -1, std::multiplies<int32_t>()));
355 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
356 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
357 }
358 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
359 return TensorInfo(outShape, DataType::Float32);
360}
361
362} //namespace
363
364const std::map<std::string, OnnxParser::OperationParsingFunction> OnnxParser::m_ParserFunctions = {
365 { "BatchNormalization", &OnnxParser::ParseBatchNormalization},
366 { "GlobalAveragePool", &OnnxParser::ParseGlobalAveragePool},
367 { "AveragePool", &OnnxParser::ParseAveragePool },
Finn Williams7ee5d2c2020-03-27 11:11:50 +0000368 { "Clip", &OnnxParser::ParseClip },
telsoa01c577f2c2018-08-31 09:22:23 +0100369 { "Constant", &OnnxParser::ParseConstant },
370 { "MaxPool", &OnnxParser::ParseMaxPool },
371 { "Reshape", &OnnxParser::ParseReshape },
Tee Jung7ff9a602019-11-01 07:04:42 +0000372 { "Sigmoid", &OnnxParser::ParseSigmoid },
373 { "Tanh", &OnnxParser::ParseTanh },
telsoa01c577f2c2018-08-31 09:22:23 +0100374 { "Relu", &OnnxParser::ParseRelu },
Tee Jung7ff9a602019-11-01 07:04:42 +0000375 { "LeakyRelu", &OnnxParser::ParseLeakyRelu },
telsoa01c577f2c2018-08-31 09:22:23 +0100376 { "Conv", &OnnxParser::ParseConv },
377 { "Add", &OnnxParser::ParseAdd },
Ryan OSheaed27ee72020-04-22 16:37:29 +0100378 { "Flatten", &OnnxParser::ParseFlatten},
telsoa01c577f2c2018-08-31 09:22:23 +0100379};
380
381template<typename TypePair, typename Location>
382void OnnxParser::ValidateInputs(const onnx::NodeProto& node,
383 TypePair validInputs,
384 const Location& location)
385{
386 for(auto input : node.input())
387 {
388 CheckValidDataType(validInputs.second,
389 m_TensorsInfo[input].m_dtype,
390 validInputs.first,
391 node.name(),
392 input,
393 location);
394 }
395}
396
397#define VALID_INPUTS(NODE, VALID_INPUTS) \
398 OnnxParser::ValidateInputs(NODE, \
399 VALID_INPUTS, \
400 CHECK_LOCATION())
401
402std::vector<TensorInfo> OnnxParser::ComputeOutputInfo(std::vector<std::string> outNames,
403 const IConnectableLayer* layer,
404 std::vector<TensorShape> inputShapes)
405{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100406 ARMNN_ASSERT(! outNames.empty());
telsoa01c577f2c2018-08-31 09:22:23 +0100407 bool needCompute = std::any_of(outNames.begin(),
408 outNames.end(),
409 [this](std::string name)
410 {
411 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr);
412 });
413 std::vector<TensorInfo> outInfo;
414 //if the output info(s) are not here, we need to compute them
415 std::vector<TensorShape> inferredShapes;
416 if(needCompute)
417 {
418 inferredShapes = layer->InferOutputShapes(inputShapes);
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100419 ARMNN_ASSERT(inferredShapes.size() == outNames.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100420 }
421 for (uint i = 0; i < outNames.size(); ++i)
422 {
423 if(needCompute)
424 {
425 m_TensorsInfo[outNames[i]] = OnnxTensor();
426 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
427 TensorInfo(inferredShapes[i], DataType::Float32));
428 }
429 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
430 }
431 return outInfo;
432}
433
434IOnnxParser* IOnnxParser::CreateRaw()
435{
436 return new OnnxParser();
437}
438
439IOnnxParserPtr IOnnxParser::Create()
440{
441 return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
442}
443
444void IOnnxParser::Destroy(IOnnxParser* parser)
445{
446 delete parser;
447}
448
449OnnxParser::OnnxParser()
450 : m_Network(nullptr, nullptr)
451{
452}
453
454void OnnxParser::ResetParser()
455{
456 m_Network = armnn::INetworkPtr(nullptr, nullptr);
457 m_Graph = nullptr;
458}
459
460void OnnxParser::Cleanup()
461{
462 m_TensorConnections.clear();
463 m_TensorsInfo.clear();
464 m_OutputsMap.clear();
465 m_OutputsFusedAndUsed.clear();
466}
467
468std::pair<ConstTensor, std::unique_ptr<float[]>> OnnxParser::CreateConstTensor(const std::string name)
469{
470 const TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
471 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
472
473 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100474 std::unique_ptr<float[]> tensorData(new float[tensorInfo.GetNumElements()]);
475 const size_t tensorSizeInBytes = tensorInfo.GetNumBytes();
476 // Copy the value list entries into the destination
477 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100478 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100479 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
480 {
481 throw ParseException(boost::str(
482 boost::format("The number of data provided (%1%) does not match the tensor '%2%' number of elements"
telsoa01c577f2c2018-08-31 09:22:23 +0100483 " (%3%) %4%")
484 % onnxTensor.float_data_size()
485 % name
486 % tensorInfo.GetNumElements()
487 % CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100488 }
489 ::memcpy(tensorData.get(), srcData, tensorSizeInBytes);
telsoa01c577f2c2018-08-31 09:22:23 +0100490 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100491 else
492 {
493 ::memcpy(tensorData.get(), onnxTensor.raw_data().c_str(), tensorSizeInBytes);
494 }
telsoa01c577f2c2018-08-31 09:22:23 +0100495
496 // Const tensors requires at least a list of values
497 if (tensorInfo.GetNumElements() == 0)
498 {
499 throw ParseException(boost::str(
500 boost::format("No tensor data found for Const tensor '%1%' %2%")
501 % name
502 % CHECK_LOCATION().AsString()));
503 }
504 return std::make_pair(ConstTensor(tensorInfo, tensorData.get()), std::move(tensorData));
505}
506
507ModelPtr OnnxParser::LoadModelFromTextFile(const char* graphFile)
508{
509 FILE* fd = fopen(graphFile, "r");
510
511 if (fd == nullptr)
512 {
513 throw FileNotFoundException(boost::str(
514 boost::format("Invalid (null) filename %1%") % CHECK_LOCATION().AsString()));
515 }
516
517 // Parse the file into a message
518 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
519 using google::protobuf::io::FileInputStream;
520 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
521 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
522 fclose(fd);
523
524 if (!success)
525 {
526 std::stringstream error;
527 error << "Failed to parse graph file";
528 throw ParseException(boost::str(
529 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
530 }
531 return modelProto;
532}
533
534INetworkPtr OnnxParser::CreateNetworkFromTextFile(const char* graphFile)
535{
536 ResetParser();
537 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
538 return CreateNetworkFromModel(*modelProto);
539}
540
541
542ModelPtr OnnxParser::LoadModelFromBinaryFile(const char* graphFile)
543{
544 FILE* fd = fopen(graphFile, "rb");
545
546 if (fd == nullptr)
547 {
548 throw FileNotFoundException(boost::str(
549 boost::format("Invalid (null) filename %1%") % CHECK_LOCATION().AsString()));
550 }
551
552 // Parse the file into a message
553 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
554
555 google::protobuf::io::FileInputStream inStream(fileno(fd));
556 google::protobuf::io::CodedInputStream codedStream(&inStream);
557 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
558 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
559 fclose(fd);
560
561 if (!success)
562 {
563 std::stringstream error;
564 error << "Failed to parse graph file";
565 throw ParseException(boost::str(
566 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
567 }
568 return modelProto;
569
570}
571
572INetworkPtr OnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
573{
574 ResetParser();
575 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
576 return CreateNetworkFromModel(*modelProto);
577}
578
579ModelPtr OnnxParser::LoadModelFromString(const std::string& protoText)
580{
581 if (protoText == "")
582 {
583 throw InvalidArgumentException(boost::str(
584 boost::format("Invalid (empty) string for model parameter %1%") % CHECK_LOCATION().AsString()));
585 }
586 // Parse the string into a message
587 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
588 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
589 if (!success)
590 {
591 std::stringstream error;
592 error << "Failed to parse graph file";
593 throw ParseException(boost::str(
594 boost::format("%1% %2%") % error.str() % CHECK_LOCATION().AsString()));
595 }
596 return modelProto;
597}
598
599INetworkPtr OnnxParser::CreateNetworkFromString(const std::string& protoText)
600{
601 ResetParser();
602 ModelPtr modelProto = LoadModelFromString(protoText);
603 return CreateNetworkFromModel(*modelProto);
604}
605
606INetworkPtr OnnxParser::CreateNetworkFromModel(onnx::ModelProto& model)
607{
608 m_Network = INetwork::Create();
609 try
610 {
611 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
612 LoadGraph();
613 }
614 catch (const ParseException& e)
615 {
616 Cleanup();
617 throw e;
618 }
619 Cleanup();
620 return std::move(m_Network);
621}
622
623void OnnxParser::LoadGraph()
624{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100625 ARMNN_ASSERT(m_Graph.get() != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100626
627 //Fill m_TensorsInfo with the shapes and value of every tensor
628 SetupInfo(m_Graph->mutable_output());
629 SetupInfo(m_Graph->mutable_input());
630 SetupInfo(m_Graph->mutable_value_info());
631
632 for (auto tensor : m_Graph->initializer())
633 {
634 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000635 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
636 m_TensorsInfo[tensor.name()].m_dtype =
637 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100638 }
639
640 SetupInputLayers();
641 SetupOutputLayers();
642
643 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
644 DetectFullyConnected();
645
646 //Parsing the graph
647 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
648 {
649 auto node = m_Graph->node(static_cast<int>(nodeIndex));
650 const std::string& operation = node.op_type();
651
652 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000653 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100654 {
655 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
656 {
657 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
658 AddFullyConnected(node);
659 }
660 }
661 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
662 {
663 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
664 AddFullyConnected(m_Graph->node(matmulIndex), &node);
665 }
666 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
667 {
668 auto it = m_ParserFunctions.find(operation);
669 if (it != m_ParserFunctions.end())
670 {
671 auto func = it->second;
672 (this->*func)(node);
673 }
674 else
675 {
676 throw ParseException(boost::str(
677 boost::format("Unsupported operation %1% for node '%2%' %3%")
678 % operation
679 % node.name()
680 % CHECK_LOCATION().AsString()));
681 }
682 }
683 }
684
685 //Making the connections between outputs and inputs of each layers
686 for (const auto& tensorCon : m_TensorConnections)
687 {
688 if (tensorCon.second.outputSlot != nullptr)
689 {
690 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
691 {
692 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
693 }
694 }
695 }
696}
697
698void OnnxParser::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
699{
700 for (auto tensor : *list)
701 {
702 m_TensorsInfo[tensor.name()] = OnnxTensor();
703 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000704 m_TensorsInfo[tensor.name()].m_dtype =
705 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100706 }
707}
708
709void OnnxParser::DetectFullyConnected()
710{
711 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
712 auto matmulAndConstant = [&](const std::string& constInput,
713 const std::string& matmulInput,
714 int& nodeIndex)
715 {
716 auto matmulIt = m_OutputsMap.find(matmulInput);
717 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
718 && m_TensorsInfo[constInput].isConstant())
719 {
720 nodeIndex = matmulIt->second.second;
721 return true;
722 }
723 return false;
724 };
725
726 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
727 {
728 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
729 for (const std::string& output : node->output())
730 {
731 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
732 }
733
734 for (const std::string& input : node->input()) //count how many time a node is used as input
735 {
736 auto matmulIt = m_OutputsMap.find(input);
737 if(matmulIt != m_OutputsMap.end()){
738 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
739 }
740 }
741
742 if (node->op_type() == "Add")
743 {
744 int matmulIndex = 0;
745 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
746 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
747 {
748 //matmul and add were fused
749 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
750 .push_back(static_cast<size_t>(nodeIndex));
751
752 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
753 .push_back(static_cast<size_t>(matmulIndex));
754 }
755 }
756 }
757
758 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
759 auto matmulIt = m_OutputsMap.find(output.name());
760 if(matmulIt != m_OutputsMap.end()){
761 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
762 }
763 }
764}
765
766template<typename Location>
767void OnnxParser::GetInputAndParam(const onnx::NodeProto& node,
768 std::string* inputName,
769 std::string* constName,
770 const Location& location)
771{
772 int cstIndex;
773 if (m_TensorsInfo[node.input(0)].isConstant())
774 {
775 cstIndex = 0;
776 }
777 else if (m_TensorsInfo[node.input(1)].isConstant())
778 {
779 cstIndex = 1;
780 }
781 else
782 {
783 throw ParseException(boost::str(
784 boost::format("One of the input tensors ('%1%' or '%2%') should be constant in node '%3%' %4%")
785 % node.input(0)
786 % node.input(1)
787 % node.name()
788 % location.AsString()));
789 }
790 if(constName)
791 {
792 *constName = node.input(cstIndex);
793 }
794 if(inputName)
795 {
796 *inputName = node.input(!cstIndex);
797 }
798}
799
800template<typename Location>
801void OnnxParser::To1DTensor(const std::string& name, const Location& location)
802{
803 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
804 std::vector<uint32_t> newShape;
805 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
806 {
807 if(shape[i] != 1)
808 {
809 throw ParseException(boost::str(
810 boost::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and %1% %2%")
811 % TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype)
812 % location.AsString()));
813 }
814 }
815 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
816
817 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
818}
819
Ryan OSheaed27ee72020-04-22 16:37:29 +0100820void OnnxParser::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
821{
822 ARMNN_ASSERT(node.op_type() == "Conv");
823
824 DepthwiseConvolution2dDescriptor desc;
825 desc.m_PadLeft = convDesc.m_PadLeft;
826 desc.m_PadRight = convDesc.m_PadRight;
827 desc.m_PadTop = convDesc.m_PadTop;
828 desc.m_PadBottom = convDesc.m_PadBottom;
829 desc.m_StrideX = convDesc.m_StrideX;
830 desc.m_StrideY = convDesc.m_StrideY;
831 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
832
833 armnn::IConnectableLayer* layer;
834 auto weightTensor = CreateConstTensor(node.input(1));
835 TensorShape& weightShape = weightTensor.first.GetShape();
836 weightShape[1] = weightShape[0];
837 weightShape[0] = 1;
838 m_TensorsInfo[node.input(1)].m_info->SetShape(weightShape);
839
840 if (node.input_size() == 3)
841 {
842 if(!m_TensorsInfo[node.input(2)].isConstant())
843 {
844 throw ParseException(boost::str(
845 boost::format("Bias '%1%' should be constant in Conv layer '%2%' %3%")
846 % node.input(2)
847 % node.name()
848 % CHECK_LOCATION().AsString()));
849 }
850 desc.m_BiasEnabled = true;
851 auto biasTensor = CreateConstTensor(node.input(2));
852 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
853 weightTensor.first,
854 Optional<ConstTensor>(biasTensor.first),
855 node.name().c_str());
856 }
857 else
858 {
859 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
860 weightTensor.first,
861 EmptyOptional(),
862 node.name().c_str());
863 }
864 ARMNN_ASSERT(layer != nullptr);
865
866 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
867 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
868 m_TensorsInfo[node.input(1)].m_info->GetShape() });
869
870 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
871
872 // register the input connection slots for the layer, connections are made after all layers have been created
873 // only the tensors for the inputs are relevant, exclude the const tensors
874 RegisterInputSlots(layer, {node.input(0)});
875
876 // register the output connection slots for the layer, connections are made after all layers have been created
877 RegisterOutputSlots(layer, {node.output(0)});
878}
879
telsoa01c577f2c2018-08-31 09:22:23 +0100880void OnnxParser::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
881{
882
883 // find matmul inputs
884 std::string weightName;
885 std::string inputName;
886 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
887 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
888 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
889
890 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
891
892 FullyConnectedDescriptor desc;
893 desc.m_BiasEnabled = addNode != nullptr;
894
895 IConnectableLayer* layer = nullptr;
896 if(desc.m_BiasEnabled)
897 {
898 // find bias const
899 std::string biasName;
900 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
901 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
902 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
903
904 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
905
906 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
907 To1DTensor(biasName, CHECK_LOCATION());
908 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
909 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
910
911 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
912 {
913 throw ParseException(boost::str(
914 boost::format("Shape of weights '%1%' and bias of following Add node '%2%' do not match : %3%"
915 " and %4% ( /!\\ bias should be a 1D tensor) %5%")
916 % weightName
917 % addNode->name()
918 % TensorInfoAsString(*m_TensorsInfo[weightName].m_info,
919 weightName,
920 m_TensorsInfo[weightName].m_dtype)
921 % TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
922 m_TensorsInfo[biasName].m_dtype )
923 % CHECK_LOCATION().AsString()));
924 }
925 layer = m_Network->AddFullyConnectedLayer(desc,
926 CreateConstTensor(weightName).first,
Matteo Martincighfc598e12019-05-14 10:36:13 +0100927 Optional<ConstTensor>(CreateConstTensor(biasName).first),
telsoa01c577f2c2018-08-31 09:22:23 +0100928 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100929 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100930
931 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
932 {m_TensorsInfo[inputName].m_info->GetShape(),
933 m_TensorsInfo[weightName].m_info->GetShape()});
934
935 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
936
937 RegisterInputSlots(layer, {inputName});
938 RegisterOutputSlots(layer, {addNode->output(0)});
939 }
940 else
941 {
Matteo Martincighfc598e12019-05-14 10:36:13 +0100942 layer = m_Network->AddFullyConnectedLayer(desc,
943 CreateConstTensor(weightName).first,
944 EmptyOptional(),
945 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100946 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100947
948 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
949 {m_TensorsInfo[inputName].m_info->GetShape(),
950 m_TensorsInfo[weightName].m_info->GetShape()});
951 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
952
953 RegisterInputSlots(layer, {inputName});
954 RegisterOutputSlots(layer, {matmulNode.output(0)});
955 }
956}
957
telsoa01c577f2c2018-08-31 09:22:23 +0100958void OnnxParser::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
959{
960
961 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
962 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
963
964 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
965
966 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
967 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
968 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
969
970 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
971 desc.m_PoolWidth = kernel_shape[1];
972 desc.m_PoolHeight = kernel_shape[0];
973
974 if(strides.empty())
975 {
976 desc.m_StrideX = 1;
977 desc.m_StrideY = 1;
978 }
979 else
980 {
981 desc.m_StrideX = strides[1];
982 desc.m_StrideY = strides[0];
983 }
984
985 //Check new padding version first
986 if(pads.empty())
987 {
988 //Check deprecated version
989 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
990 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
991 {
992 bool isUpper;
993 if( paddingString == "SAME_LOWER")
994 {
995 isUpper = false;
996 }
997 else if (paddingString == "SAME_UPPER")
998 {
999 isUpper = true;
1000 }
1001 else
1002 {
1003 throw ParseException(boost::str(
1004 boost::format("Invalid auto_pad attribute for node %1%. "
Ryan OSheaed27ee72020-04-22 16:37:29 +01001005 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
telsoa01c577f2c2018-08-31 09:22:23 +01001006 % node.name()
1007 % paddingString
1008 % CHECK_LOCATION().AsString()));
1009 }
1010 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1011 uint32_t inputHeight = inputInfo.GetShape()[2];
1012 uint32_t inputWidth = inputInfo.GetShape()[3];
1013 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1014 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1015 }
1016 }
1017 else
1018 {
1019 desc.m_PadTop = pads[0];
1020 desc.m_PadLeft = pads[1];
1021 desc.m_PadBottom = pads[2];
1022 desc.m_PadRight = pads[3];
1023 }
1024
1025 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001026 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001027
1028 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1029 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1030
1031 // register the input connection slots for the layer, connections are made after all layers have been created
1032 // only the tensors for the inputs are relevant, exclude the const tensors
1033 RegisterInputSlots(layer, {node.input(0)});
1034
1035 // register the output connection slots for the layer, connections are made after all layers have been created
1036 RegisterOutputSlots(layer, {node.output(0)});
1037}
1038
Ryan OSheaed27ee72020-04-22 16:37:29 +01001039std::pair<std::string, std::string> OnnxParser::AddPrepareBroadcast(const std::string& input0,
1040 const std::string& input1)
1041{
1042 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1043
1044 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1045 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1046
1047 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1048 {
1049 auto outputName = boost::str(boost::format("reshape_output_%1%") % input1);
1050 PrependForBroadcast(outputName, input1, input0);
1051 inputs.second = outputName;
1052 }
1053 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1054 {
1055 auto outputName = boost::str(boost::format("reshape_output_%1%") % input0);
1056 PrependForBroadcast(outputName, input0, input1);
1057 inputs.first = outputName;
1058 }
1059 return inputs;
1060}
1061
1062void OnnxParser::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
1063{
1064 auto armnnTensor = CreateConstTensor(tensorName);
1065
1066 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1067 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1068 RegisterOutputSlots(layer, {tensorName});
1069}
1070
telsoa01c577f2c2018-08-31 09:22:23 +01001071void OnnxParser::CreateReshapeLayer(const std::string& inputName,
1072 const std::string& outputName,
1073 const std::string& layerName)
1074{
1075 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1076 ReshapeDescriptor reshapeDesc;
1077 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1078
1079 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001080 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001081 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1082
1083 // register the input connection slots for the layer, connections are made after all layers have been created
1084 // only the tensors for the inputs are relevant, exclude the const tensors
1085 RegisterInputSlots(layer, {inputName});
1086
1087 // register the output connection slots for the layer, connections are made after all layers have been created
1088 RegisterOutputSlots(layer, {outputName});
1089}
1090
Tee Jung7ff9a602019-11-01 07:04:42 +00001091void OnnxParser::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001092{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001093 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001094 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1095
1096 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1097
1098 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001099 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001100
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001101 if (func == ActivationFunction::BoundedReLu)
1102 {
1103 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1104 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1105 }
1106
telsoa01c577f2c2018-08-31 09:22:23 +01001107 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001108 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001109
1110 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1111 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1112
1113 // register the input connection slots for the layer, connections are made after all layers have been created
1114 // only the tensors for the inputs are relevant, exclude the const tensors
1115 RegisterInputSlots(layer, {node.input(0)});
1116
1117 // register the output connection slots for the layer, connections are made after all layers have been created
1118 RegisterOutputSlots(layer, {node.output(0)});
1119}
1120
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001121void OnnxParser::ParseClip(const onnx::NodeProto& node)
1122{
1123 ParseActivation(node, ActivationFunction::BoundedReLu);
1124}
1125
Tee Jung7ff9a602019-11-01 07:04:42 +00001126void OnnxParser::ParseSigmoid(const onnx::NodeProto& node)
1127{
1128 ParseActivation(node, ActivationFunction::Sigmoid);
1129}
1130
1131void OnnxParser::ParseTanh(const onnx::NodeProto& node)
1132{
1133 ParseActivation(node, ActivationFunction::TanH);
1134}
1135
1136void OnnxParser::ParseRelu(const onnx::NodeProto& node)
1137{
1138 ParseActivation(node, ActivationFunction::ReLu);
1139}
1140
1141void OnnxParser::ParseLeakyRelu(const onnx::NodeProto& node)
1142{
1143 ParseActivation(node, ActivationFunction::LeakyReLu);
1144}
telsoa01c577f2c2018-08-31 09:22:23 +01001145
Ryan OSheaed27ee72020-04-22 16:37:29 +01001146void OnnxParser::ParseAdd(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001147{
Ryan OSheaed27ee72020-04-22 16:37:29 +01001148 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1149 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +01001150
Ryan OSheaed27ee72020-04-22 16:37:29 +01001151 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
telsoa01c577f2c2018-08-31 09:22:23 +01001152
Ryan OSheaed27ee72020-04-22 16:37:29 +01001153 // TODO: unify broadcast validation code across layers
1154 // tracked by: IVGCVSW-1576
telsoa01c577f2c2018-08-31 09:22:23 +01001155
Ryan OSheaed27ee72020-04-22 16:37:29 +01001156 // Checking broadcast compatibility : only scalar or 1D tensors
1157 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1158 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1159 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1160 ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1161
1162 unsigned int numDims = input0.GetNumDimensions();
1163 for (unsigned int i = 0; i < numDims; i++)
telsoa01c577f2c2018-08-31 09:22:23 +01001164 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01001165 unsigned int dim0 = input0.GetShape()[i];
1166 unsigned int dim1 = input1.GetShape()[i];
1167 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
telsoa01c577f2c2018-08-31 09:22:23 +01001168 {
1169 throw ParseException(boost::str(
Ryan OSheaed27ee72020-04-22 16:37:29 +01001170 boost::format("Broadcast is only supported for scalar or 1D tensors in Add node '%1%'. "
1171 "Input dimensions should either match or one should be of size 1 and here, "
1172 "%2% and %3% %4%")
1173 % node.name()
1174 % TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1175 m_TensorsInfo[inputs.first].m_dtype)
1176 % TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1177 m_TensorsInfo[inputs.second].m_dtype)
1178 % CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001179 }
telsoa01c577f2c2018-08-31 09:22:23 +01001180 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001181
1182
1183 IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001184 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001185
1186 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
Ryan OSheaed27ee72020-04-22 16:37:29 +01001187 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1188 m_TensorsInfo[inputs.second].m_info->GetShape() });
telsoa01c577f2c2018-08-31 09:22:23 +01001189 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1190
Ryan OSheaed27ee72020-04-22 16:37:29 +01001191 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1192 if(m_TensorsInfo[inputs.first].isConstant()) {
1193 CreateConstantLayer(inputs.first, boost::str(boost::format("Add:constant_of_%1%") % node.input(0)));
1194 }
1195 if(m_TensorsInfo[inputs.second].isConstant()) {
1196 CreateConstantLayer(inputs.second, boost::str(boost::format("Add:constant_of_%1%") % node.input(1)));
1197 }
1198 RegisterInputSlots(layer, {inputs.first, inputs.second});
telsoa01c577f2c2018-08-31 09:22:23 +01001199
Ryan OSheaed27ee72020-04-22 16:37:29 +01001200 // register the output connection
telsoa01c577f2c2018-08-31 09:22:23 +01001201 RegisterOutputSlots(layer, {node.output(0)});
1202}
1203
Ryan OSheaed27ee72020-04-22 16:37:29 +01001204void OnnxParser::ParseAveragePool(const onnx::NodeProto& node)
1205{
1206 Pooling2dDescriptor desc;
1207 desc.m_PoolType = PoolingAlgorithm::Average;
1208
1209 uint32_t count_include_pad = 0;
1210 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1211 if(count_include_pad) {
1212 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1213 }
1214 AddPoolingLayer(node, desc);
1215}
1216
1217void OnnxParser::ParseBatchNormalization(const onnx::NodeProto& node)
1218{
1219 //IGNORE momentum parameter and spatial parameters
1220
1221 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1222 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1223
1224 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1225 for(int ind = 1; ind < node.input_size(); ++ind)
1226 {
1227 auto tensor = node.input(ind);
1228 if(! m_TensorsInfo[tensor].isConstant())
1229 {
1230 throw ParseException(boost::str(
1231 boost::format("Input tensor '%1%' should be constant in BatchNormalization node '%2%' %3%")
1232 % tensor
1233 % node.name()
1234 % CHECK_LOCATION().AsString()));
1235 }
1236 }
1237
1238 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1239 BatchNormalizationDescriptor desc;
1240 desc.m_Eps = epsilon;
1241
1242 auto scaleTensor = CreateConstTensor(node.input(1));
1243 auto biasTensor = CreateConstTensor(node.input(2));
1244 auto meanTensor = CreateConstTensor(node.input(3));
1245 auto varTensor = CreateConstTensor(node.input(4));
1246
1247 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1248 meanTensor.first,
1249 varTensor.first,
1250 biasTensor.first,
1251 scaleTensor.first,
1252 node.name().c_str());
1253 ARMNN_ASSERT(layer != nullptr);
1254
1255 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1256 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1257
1258 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1259
1260 // register the output connection
1261 RegisterOutputSlots(layer, {node.output(0)});
1262}
1263
1264void OnnxParser::ParseConstant(const onnx::NodeProto& node)
1265{
1266 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1267 if (!node.attribute(0).has_t())
1268 {
1269 throw ParseException(boost::str(
1270 boost::format("Value not found for Constant node '%1%' %2%")
1271 % node.name()
1272 % CHECK_LOCATION().AsString()));
1273 }
1274 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1275
1276 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
1277 CHECK_VALID_DATATYPE(node.name(), onnxTensor.name(),
1278 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
1279
1280 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1281 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1282 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1283 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1284
1285 CreateConstantLayer(node.output(0), node.name());
1286}
1287
telsoa01c577f2c2018-08-31 09:22:23 +01001288void OnnxParser::ParseConv(const onnx::NodeProto& node)
1289{
1290 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1291 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1292
1293 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1294
1295 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1296 {
1297 throw ParseException(boost::str(
1298 boost::format("ArmNN only supports 2D convolution and Conv layer '%1%' input %2% %3%")
Ryan OSheaed27ee72020-04-22 16:37:29 +01001299 % node.name()
1300 % TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1301 m_TensorsInfo[node.input(0)].m_dtype)
1302 % CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001303 }
1304
1305 if(!m_TensorsInfo[node.input(1)].isConstant())
1306 {
1307 throw ParseException(boost::str(
1308 boost::format("Weights '%1%' should be constant in Conv layer '%2%' %3%")
1309 % node.input(1)
1310 % node.name()
1311 % CHECK_LOCATION().AsString()));
1312 }
1313
1314 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1315
1316 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1317 if (!dilations.empty())
1318 {
1319 std::stringstream ss;
1320 ss << "[ ";
1321 for (auto dilation : dilations)
1322 {
1323 ss << dilation << ", ";
1324 if (dilation != 1u)
1325 {
1326 ss << "... ]";
1327 throw ParseException(boost::str(
1328 boost::format("ArmNN only supports Convolution layers with dilations [1,1], and node '%1%' "
1329 "has dilatation %2% %3%")
Ryan OSheaed27ee72020-04-22 16:37:29 +01001330 % node.name()
1331 % ss.str()
1332 % CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001333 }
1334 }
1335 }
1336
1337 Convolution2dDescriptor desc;
1338 desc.m_BiasEnabled = false;
1339
1340 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1341 if(strides.empty())
1342 {
1343 desc.m_StrideX = 1;
1344 desc.m_StrideY = 1;
1345 }
1346 else
1347 {
1348 desc.m_StrideX = strides[1];
1349 desc.m_StrideY = strides[0];
1350 }
1351
1352 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1353 //Check new padding version first
1354 if(pads.empty())
1355 {
1356 //Check deprecated version
1357 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1358 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1359 {
1360 bool isUpper;
1361 if( paddingString == "SAME_LOWER")
1362 {
1363 isUpper = false;
1364 }
1365 else if (paddingString == "SAME_UPPER")
1366 {
1367 isUpper = true;
1368 }
1369 else
1370 {
1371 throw ParseException(boost::str(
1372 boost::format("Invalid auto_pad attribute for node %1%. "
Ryan OSheaed27ee72020-04-22 16:37:29 +01001373 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
telsoa01c577f2c2018-08-31 09:22:23 +01001374 % node.name()
1375 % paddingString
1376 % CHECK_LOCATION().AsString()));
1377 }
1378 uint32_t inputHeight = inputInfo.GetShape()[2];
1379 uint32_t inputWidth = inputInfo.GetShape()[3];
1380
1381 uint32_t weightHeight;
1382 uint32_t weightWidth;
1383 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1384 if (kernel_shape.empty())
1385 {
1386 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1387 weightHeight = weightTensorInfo.GetShape()[2];
1388 weightWidth = weightTensorInfo.GetShape()[3];
1389 }
1390 else
1391 {
1392 weightHeight = kernel_shape[0];
1393 weightWidth = kernel_shape[1];
1394 }
1395 CalcPadding(inputHeight, weightHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1396 CalcPadding(inputWidth, weightWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1397 }
1398 }
1399 else
1400 {
1401 desc.m_PadTop = pads[0];
1402 desc.m_PadLeft = pads[1];
1403 desc.m_PadBottom = pads[2];
1404 desc.m_PadRight = pads[3];
1405 }
1406
1407 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1408 if(group > 1)
1409 {
1410 if (group > inputInfo.GetShape()[1])
1411 {
1412 throw ParseException(
1413 boost::str(
1414 boost::format(
1415 "Error parsing Convolution node: %1%. "
1416 "The 'group'=%2% parameter cannot be larger than the "
1417 "channel of the input shape=%3% (in NCHW format). %4%") %
Ryan OSheaed27ee72020-04-22 16:37:29 +01001418 node.name() %
1419 group %
1420 inputInfo.GetShape()[1] %
1421 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001422 }
1423 else if (group == inputInfo.GetShape()[1])
1424 {
1425 // we use a depthwise convolution here, because the number of groups equals to the
1426 // input channels
1427 AddConvLayerWithDepthwiseConv(node, desc);
1428 return;
1429 }
1430 else
1431 {
1432 // TODO: split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +01001433 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +01001434 throw ParseException(boost::str(
1435 boost::format("Error parsing Convolution node: %1%. "
Ryan OSheaed27ee72020-04-22 16:37:29 +01001436 "The 'group'=%2% parameter should be 1 or be equal to the "
1437 "channel of the input shape=%3% (in NCHW format). %4%") %
telsoa01c577f2c2018-08-31 09:22:23 +01001438 node.name() %
1439 group %
1440 inputInfo.GetShape()[1] %
1441 CHECK_LOCATION().AsString()));
1442 }
1443 }
1444
1445 armnn::IConnectableLayer* layer;
1446 auto weightTensor = CreateConstTensor(node.input(1));
1447
1448 if (node.input_size() == 3)
1449 {
1450 if(!m_TensorsInfo[node.input(2)].isConstant())
1451 {
1452 throw ParseException(boost::str(
1453 boost::format("Bias '%1%' should be constant in Conv layer '%2%' %3%")
Ryan OSheaed27ee72020-04-22 16:37:29 +01001454 % node.input(2)
1455 % node.name()
1456 % CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001457 }
1458 desc.m_BiasEnabled = true;
1459 auto biasTensor = CreateConstTensor(node.input(2));
1460 layer = m_Network->AddConvolution2dLayer(desc,
1461 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001462 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001463 node.name().c_str());
1464 }
1465 else
1466 {
1467 layer = m_Network->AddConvolution2dLayer(desc,
1468 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001469 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001470 node.name().c_str());
1471 }
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001472 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001473
1474 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1475 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1476 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1477 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1478
1479 // register the input connection slots for the layer, connections are made after all layers have been created
1480 // only the tensors for the inputs are relevant, exclude the const tensors
1481 RegisterInputSlots(layer, {node.input(0)});
1482
1483 // register the output connection slots for the layer, connections are made after all layers have been created
1484 RegisterOutputSlots(layer, {node.output(0)});
1485}
1486
Ryan OSheaed27ee72020-04-22 16:37:29 +01001487void OnnxParser::ParseFlatten(const onnx::NodeProto& node)
1488{
1489 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1490 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1491
1492 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1493 m_TensorsInfo[node.input(0)].m_dtype,
1494 onnx::TensorProto::FLOAT);
1495
1496 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1497 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1498
1499 /// Negative axis conversion
1500 if (axis < 0)
1501 {
1502 axis += inputShape.GetNumDimensions();
1503 }
1504
1505 /// Check Axis is within dimensions
1506 if (axis < 0 || axis >= inputShape.GetNumDimensions())
1507 {
1508 throw ParseException( boost::str(
1509 boost::format("Axis '%1%' invalid. Tensor has '%2%' dimensions in FlattenLayer '%3%'")
1510 % axis % inputShape.GetNumDimensions() % node.name()));
1511 }
1512
1513 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1514 uint dimension1{1};
1515 uint dimension2{1};
1516 uint i{0};
1517
1518 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1519 for (i = 0; i < axis; i++){
1520 dimension1 *= inputShape[i];
1521 }
1522
1523 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
1524 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
1525 dimension2 *= inputShape[i];
1526 }
1527
1528 TensorShape outputShape{dimension1, dimension2};
1529
1530 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
1531 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1532 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1533}
1534
1535void OnnxParser::ParseGlobalAveragePool(const onnx::NodeProto& node)
1536{
1537 Pooling2dDescriptor desc = Pooling2dDescriptor();
1538 desc.m_PoolType = PoolingAlgorithm::Average;
1539
1540 //kernel size is the same as input
1541 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1542 desc.m_PoolWidth = inputShape[3];
1543 desc.m_PoolHeight = inputShape[2];
1544
1545 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1546 ARMNN_ASSERT(layer != nullptr);
1547
1548 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
1549 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1550
1551 // register the input connection slots for the layer, connections are made after all layers have been created
1552 // only the tensors for the inputs are relevant, exclude the const tensors
1553 RegisterInputSlots(layer, {node.input(0)});
1554
1555 // register the output connection slots for the layer, connections are made after all layers have been created
1556 RegisterOutputSlots(layer, {node.output(0)});
1557}
1558
1559void OnnxParser::ParseMaxPool(const onnx::NodeProto& node)
1560{
1561 Pooling2dDescriptor desc;
1562 desc.m_PoolType = PoolingAlgorithm::Max;
1563 desc.m_PaddingMethod = PaddingMethod::Exclude;
1564 AddPoolingLayer(node, desc);
1565}
1566
1567void OnnxParser::ParseReshape(const onnx::NodeProto& node)
1568{
1569 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1570 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1571
1572 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1573 m_TensorsInfo[node.input(0)].m_dtype,
1574 onnx::TensorProto::FLOAT); //input
1575 CHECK_VALID_DATATYPE(node.name(), node.input(1),
1576 m_TensorsInfo[node.input(1)].m_dtype,
1577 onnx::TensorProto::INT64); //shape
1578
1579 if(!m_TensorsInfo[node.input(1)].isConstant())
1580 {
1581 throw ParseException(boost::str(
1582 boost::format("Shape '%1%' should be constant in Reshape layer '%2%' %3%")
1583 % node.input(1)
1584 % node.name()
1585 % CHECK_LOCATION().AsString()));
1586 }
1587
1588 if(m_TensorsInfo[node.input(0)].isConstant())
1589 {
1590 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
1591 if(m_TensorsInfo.count(node.output(0)) == 0)
1592 {
1593 m_TensorsInfo[node.output(0)] = OnnxTensor();
1594 }
1595 m_TensorsInfo[node.output(0)].m_tensor =
1596 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1597 }
1598 else
1599 {
1600 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1601
1602 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
1603 {
1604 uint64_t dims = static_cast<uint64_t>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
1605 TensorShape targetShape{static_cast<unsigned int>(dims), 1};
1606
1607 for(uint i = 0; i < dims; i++)
1608 {
1609 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
1610 targetShape[i]= static_cast<unsigned int>(val);
1611 }
1612
1613 auto outInfo = ComputeReshapeInfo(targetShape, inputShape, node.output(0));
1614 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1615 }
1616
1617 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1618 }
1619}
1620
telsoa01c577f2c2018-08-31 09:22:23 +01001621void OnnxParser::PrependForBroadcast(const std::string& outputName,
1622 const std::string& input0,
1623 const std::string& input1)
1624{
1625 //input0 should be reshaped to have same number of dim as input1
1626 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
1627
1628 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1629 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1630
1631 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
1632 std::vector<uint32_t> newShape;
1633 while(diff > 0)
1634 {
1635 newShape.push_back(1);
1636 diff--;
1637 }
1638 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
1639 {
1640 newShape.push_back(input0Shape[dim]);
1641 }
1642 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1643
1644 //add the new tensor to m_TensorsInfo
1645 m_TensorsInfo[outputName] = OnnxTensor();
1646 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1647
1648 //add reshape layer if the parent was not constant...
1649 if( ! m_TensorsInfo[input0].isConstant())
1650 {
1651 CreateReshapeLayer(input0, outputName, boost::str(boost::format("Add:reshapeOf%1%") % input0));
1652 }
1653 else //make it constant and it will be create in Add
1654 {
1655 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1656
1657 }
1658}
1659
telsoa01c577f2c2018-08-31 09:22:23 +01001660void OnnxParser::SetupInputLayers()
1661{
1662 //Find user input and add their layers
1663 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1664 {
1665 auto input = m_Graph->input(inputIndex);
1666 if (! m_TensorsInfo[input.name()].isConstant())
1667 {
1668 IConnectableLayer* layer =
1669 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
1670 auto tensorInfo = ToTensorInfo(input);
1671 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1672
1673 RegisterOutputSlots(layer,{ input.name() });
1674 }
1675 }
1676}
1677
1678void OnnxParser::SetupOutputLayers()
1679{
1680 if(m_Graph->output_size() == 0)
1681 {
1682 throw ParseException(boost::str(boost::format("The given model does not have any outputs %1%")
1683 % CHECK_LOCATION().AsString()));
1684 }
1685
1686 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
1687 {
1688 IConnectableLayer* layer =
1689 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
1690 m_Graph->output(outputIndex).name().c_str());
1691
1692 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
1693 }
1694}
1695
1696void OnnxParser::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1697{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001698 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001699 if (tensorIds.size() != layer->GetNumInputSlots())
1700 {
1701 throw ParseException(
1702 boost::str(boost::format("The number of tensor inputs (%1%) does not match the number expected (%2%) %3%") %
1703 tensorIds.size() %
1704 layer->GetNumInputSlots() %
1705 CHECK_LOCATION().AsString()));
1706 }
1707 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
1708 {
1709 std::string tensorId = tensorIds[slotIndex];
1710 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
1711
1712 auto it = m_TensorConnections.find(tensorId);
1713
1714 if (it == m_TensorConnections.end())
1715 {
1716 //First time seing this tensor, we need to map it
1717 m_TensorConnections[tensorId] = TensorSlots();
1718 }
1719 m_TensorConnections[tensorId].inputSlots.push_back(slot);
1720 }
1721}
1722
1723void OnnxParser::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1724{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001725 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001726 if (tensorIds.size() != layer->GetNumOutputSlots())
1727 {
1728 throw ParseException(
1729 boost::str(boost::format("The number of tensor outputs (%1%) does not match the number expected (%2%) %3% ")
1730 % tensorIds.size()
1731 % layer->GetNumOutputSlots()
1732 % CHECK_LOCATION().AsString()));
1733 }
1734
1735 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
1736 {
1737 std::string tensorId = tensorIds[slotIndex];
1738 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
1739
1740 auto it = m_TensorConnections.find(tensorId);
1741
1742 if (it == m_TensorConnections.end())
1743 {
1744 //First time seing this tensor, we need to map it
1745 m_TensorConnections[tensorId] = TensorSlots();
1746 }
1747
Ryan OShea337c17f2020-02-21 12:33:17 +00001748 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01001749
1750 // assuming there is only one producer for that tensor
1751 if (tensorSlots.outputSlot != nullptr)
1752 {
1753 throw ParseException(boost::str(
1754 boost::format("Another layer has already registered itself as the producer of "
Ryan OShea337c17f2020-02-21 12:33:17 +00001755 "tensor:%1% %2%")
1756 % tensorId
1757 % CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001758 }
1759 tensorSlots.outputSlot = slot;
1760 }
1761}
1762
1763BindingPointInfo OnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
1764{
1765 for(int i = 0; i < m_Graph->input_size(); ++i)
1766 {
1767 auto input = m_Graph->input(i);
1768 if(input.name() == name)
1769 {
1770 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(input));
1771 }
1772 }
1773 throw InvalidArgumentException(boost::str(boost::format("The input layer '%1%' does not exist %2%")
1774 % name % CHECK_LOCATION().AsString()));
1775}
1776
1777BindingPointInfo OnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
1778{
1779 for(int i = 0; i < m_Graph->output_size(); ++i)
1780 {
1781 auto output = m_Graph->output(i);
1782 if(output.name() == name)
1783 {
1784 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(output));
1785 }
1786 }
1787 throw InvalidArgumentException(boost::str(boost::format("The output layer '%1%' does not exist %2%")
1788 % name % CHECK_LOCATION().AsString()));
1789}
1790
1791std::vector<std::string> OnnxParser::GetInputs(ModelPtr& model)
1792{
1793 if(model == nullptr) {
1794 throw InvalidArgumentException(boost::str(
1795 boost::format("The given model cannot be null %1%")
1796 % CHECK_LOCATION().AsString()));
1797 }
1798
1799 std::vector<std::string> inputNames;
1800 std::map<std::string, bool> isConstant;
1801 for(auto tensor : model->graph().initializer())
1802 {
1803 isConstant[tensor.name()] = true;
1804 }
1805 for(auto input : model->graph().input())
1806 {
1807 auto it = isConstant.find(input.name());
1808 if(it == isConstant.end())
1809 {
1810 inputNames.push_back(input.name());
1811 }
1812 }
1813 return inputNames;
1814}
1815
1816std::vector<std::string> OnnxParser::GetOutputs(ModelPtr& model)
1817{
1818 if(model == nullptr) {
1819 throw InvalidArgumentException(boost::str(
1820 boost::format("The given model cannot be null %1%")
1821 % CHECK_LOCATION().AsString()));
1822 }
1823
1824 std::vector<std::string> outputNames;
1825 for(auto output : model->graph().output())
1826 {
1827 outputNames.push_back(output.name());
1828 }
1829 return outputNames;
1830}
1831
1832} // namespace armnnOnnxParser