blob: 4ae6627ac2db6de0c023c3c1cc341d39dc364f15 [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
James Ward58dec6b2020-09-11 17:32:44 +010012#include <fmt/format.h>
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(
James Ward58dec6b2020-09-11 17:32:44 +010038 fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
39 onnx::TensorProto::DataType_Name(actualValue),
40 tensorName,
41 nodeName,
42 validExpr,
43 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +010044 }
45}
46
47#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
48CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
49
50using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
51#define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
52
53template <typename Callable>
54void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
55 const std::string& attribName,
56 onnx::AttributeProto::AttributeType expectedType,
57 Callable callable)
58{
59 auto attribs = node.attribute();
60 int attriNum = 0;
61 while (attriNum < node.attribute_size())
62 {
63 if (attribs.Get(attriNum).name() == attribName)
64 {
65 if (attribs.Get(attriNum).type() == expectedType)
66 {
67 callable(attribs.Get(attriNum));
68 }
69 else
70 {
James Ward58dec6b2020-09-11 17:32:44 +010071 throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
72 "onnx::AttributeProto::AttributeType, but found {} instead {}",
73 attribName,
74 node.name(),
75 onnx::AttributeProto::AttributeType_Name(expectedType),
76 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
77 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +010078 }
79 break;
80 }
81 ++attriNum;
82 }
83 if (attriNum == node.attribute_size())
84 {
James Ward58dec6b2020-09-11 17:32:44 +010085 throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
86 attribName, node.name(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +010087 }
88}
89
90template <typename Callable>
91void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
92 const std::string& attribName,
93 onnx::AttributeProto::AttributeType expectedType,
94 Callable callable)
95{
96 auto attribs = node.attribute();
97 for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
98 {
99 if (attribs.Get(attriNum).name() == attribName)
100 {
101 if (attribs.Get(attriNum).type() == expectedType)
102 {
103 callable(attribs.Get(attriNum));
104 }
105 else
106 {
James Ward58dec6b2020-09-11 17:32:44 +0100107 throw ParseException(
108 fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
109 "but found {} instead {}",
110 attribName,
111 node.name(),
112 onnx::AttributeProto::AttributeType_Name(expectedType),
113 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
114 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100115 }
116 }
117 }
118}
119
Ryan OSheaed27ee72020-04-22 16:37:29 +0100120int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
121 const std::string& name,
122 const int64_t defaultValue = 0)
123{
124 int64_t attribValue = defaultValue;
125 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
126 [&attribValue](const onnx::AttributeProto& attrValue)
127 {
128 attribValue = attrValue.i();
129 });
130 return attribValue;
131}
132
telsoa01c577f2c2018-08-31 09:22:23 +0100133std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
134 const std::string& name)
135{
136 std::vector<uint32_t> attriList;
137 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
138 [&attriList](const onnx::AttributeProto& attrValue)
139 {
140 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
141 {
142 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
143 }
144 });
145 return attriList;
146}
147
148uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
149 const std::string& name,
150 const uint32_t defaultVal = 0u)
151{
152 uint32_t attribValue = defaultVal;
153 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
154 [&attribValue](const onnx::AttributeProto& attrValue)
155 {
156 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
157 });
158 return attribValue;
159}
160
161std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
162 const std::string& name)
163{
164 std::vector<uint32_t> attriList;
165 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
166 [&attriList](const onnx::AttributeProto& attrValue)
167 {
168 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
169 {
170 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
171 }
172 });
173
174 return attriList;
175}
176
177float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
178 const std::string& name,
179 const float defaultValue = 0.0f)
180{
181 float attribValue = defaultValue;
182 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
183 [&attribValue](const onnx::AttributeProto& attrValue)
184 {
185 attribValue = attrValue.f();
186 });
187 return attribValue;
188}
189
190std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
191{
192 std::string attribValue = "";
193 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
194 [&attribValue](const onnx::AttributeProto& attrValue)
195 {
196 attribValue = attrValue.s();
197 });
198 return attribValue;
199}
200
Tee Jungfcf6fd52019-11-01 05:27:28 +0000201armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100202{
telsoa01c577f2c2018-08-31 09:22:23 +0100203 DataType type;
Tee Jungfcf6fd52019-11-01 05:27:28 +0000204 switch(data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100205 {
206 case onnx::TensorProto::FLOAT:
207 {
208 type = DataType::Float32;
209 break;
210 }
211 case onnx::TensorProto::INT32:
212 case onnx::TensorProto::INT64:
213 {
214 type = DataType::Signed32;
215 break;
216 }
217 default:
218 {
219 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100220 fmt::format("'{}' is not a currently supported datatype for tensor {}."
221 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
222 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
223 name,
224 CHECK_LOCATION().AsString() ));
telsoa01c577f2c2018-08-31 09:22:23 +0100225 }
telsoa01c577f2c2018-08-31 09:22:23 +0100226 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000227
228 // To avoid crashes by trivial tensors
229 if (shape.empty())
230 {
231 return TensorInfo(TensorShape(), type);
232 }
233
Tee Jungfcf6fd52019-11-01 05:27:28 +0000234 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
235}
236
237armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
238{
239 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
240 std::vector<unsigned int> shapeDims;
241 for (int i = 0; i < onnxShape.dim_size(); ++i)
242 {
243 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
244 }
245
Ryan OShea337c17f2020-02-21 12:33:17 +0000246 if (shapeDims.empty())
247 {
248 shapeDims.push_back(1);
249 }
250
Tee Jungfcf6fd52019-11-01 05:27:28 +0000251 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
252}
253
254armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
255{
256 std::vector<unsigned int> shapeDims;
Ryan OShea337c17f2020-02-21 12:33:17 +0000257
Tee Jungfcf6fd52019-11-01 05:27:28 +0000258 for (auto dim: tensor.dims())
259 {
260 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
261 }
262
Ryan OShea337c17f2020-02-21 12:33:17 +0000263 if (shapeDims.empty())
264 {
265 shapeDims.push_back(1);
266 }
267
Tee Jungfcf6fd52019-11-01 05:27:28 +0000268 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100269}
270
271std::string TensorInfoAsString(const TensorInfo& info,
272 const std::string& name,
273 const onnx::TensorProto::DataType& type)
274{
275 const TensorShape shape = info.GetShape();
276 std::stringstream ss;
277 ss << "tensor '" << name << "' contains "
278 << onnx::TensorProto::DataType_Name(type)
279 << " and has shape [";
280
281 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
282 {
283 ss << shape[i] << ", ";
284 }
285 ss << shape[shape.GetNumDimensions() - 1] << "]";
286 return ss.str();
287}
288
289void CalcPadding(uint32_t inputSize, uint32_t filterSize, uint32_t stride, uint32_t* paddingFront,
290 uint32_t* paddingBack, bool isUpper)
291{
292 uint32_t outputSize = (inputSize + stride - 1) / stride;
293 uint32_t temp = (outputSize - 1) * stride + filterSize;
294 *paddingFront = (temp - inputSize) / 2;
295 *paddingBack = *paddingFront;
296 if((temp - inputSize) % 2 == 1)
297 {
298 if (isUpper)
299 {
300 *paddingBack += 1;
301 }
302 else
303 {
304 *paddingFront += 1;
305 }
306 }
307}
308
Ryan OSheaed27ee72020-04-22 16:37:29 +0100309TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
telsoa01c577f2c2018-08-31 09:22:23 +0100310 const TensorShape& inShape,
311 const std::string& outName)
312{
313 std::vector<int> targetDims;
Ryan OSheaed27ee72020-04-22 16:37:29 +0100314 for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
telsoa01c577f2c2018-08-31 09:22:23 +0100315 {
Ryan OSheaed27ee72020-04-22 16:37:29 +0100316 int val = CHECKED_INT32(targetShapeTensor[i]);
telsoa01c577f2c2018-08-31 09:22:23 +0100317 if(val == 0)
318 {
319 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
320 }
321 else
322 {
323 targetDims.push_back(val);
324 }
325 }
326
327 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
328 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
329 if (stretchDim != targetDims.end())
330 {
331 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
332 {
333 std::stringstream ss;
334 ss << "[ ";
335 for(uint i = 0; i < targetDims.size() - 1; ++i)
336 {
337 ss << targetDims[i] << ", ";
338 }
339 ss << targetDims[targetDims.size() - 1] << " ]";
340
James Ward58dec6b2020-09-11 17:32:44 +0100341 throw ParseException(
342 fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
343 " -1 and here, shape is {} {}",
344 outName,
345 ss.str(),
346 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100347 }
348
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100349 auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
telsoa01c577f2c2018-08-31 09:22:23 +0100350 -1, std::multiplies<int32_t>()));
351 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
352 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
353 }
354 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
355 return TensorInfo(outShape, DataType::Float32);
356}
357
358} //namespace
359
360const std::map<std::string, OnnxParser::OperationParsingFunction> OnnxParser::m_ParserFunctions = {
361 { "BatchNormalization", &OnnxParser::ParseBatchNormalization},
362 { "GlobalAveragePool", &OnnxParser::ParseGlobalAveragePool},
363 { "AveragePool", &OnnxParser::ParseAveragePool },
Finn Williams7ee5d2c2020-03-27 11:11:50 +0000364 { "Clip", &OnnxParser::ParseClip },
telsoa01c577f2c2018-08-31 09:22:23 +0100365 { "Constant", &OnnxParser::ParseConstant },
366 { "MaxPool", &OnnxParser::ParseMaxPool },
367 { "Reshape", &OnnxParser::ParseReshape },
Tee Jung7ff9a602019-11-01 07:04:42 +0000368 { "Sigmoid", &OnnxParser::ParseSigmoid },
369 { "Tanh", &OnnxParser::ParseTanh },
telsoa01c577f2c2018-08-31 09:22:23 +0100370 { "Relu", &OnnxParser::ParseRelu },
Tee Jung7ff9a602019-11-01 07:04:42 +0000371 { "LeakyRelu", &OnnxParser::ParseLeakyRelu },
telsoa01c577f2c2018-08-31 09:22:23 +0100372 { "Conv", &OnnxParser::ParseConv },
373 { "Add", &OnnxParser::ParseAdd },
Ryan OSheaed27ee72020-04-22 16:37:29 +0100374 { "Flatten", &OnnxParser::ParseFlatten},
telsoa01c577f2c2018-08-31 09:22:23 +0100375};
376
377template<typename TypePair, typename Location>
378void OnnxParser::ValidateInputs(const onnx::NodeProto& node,
379 TypePair validInputs,
380 const Location& location)
381{
382 for(auto input : node.input())
383 {
384 CheckValidDataType(validInputs.second,
385 m_TensorsInfo[input].m_dtype,
386 validInputs.first,
387 node.name(),
388 input,
389 location);
390 }
391}
392
393#define VALID_INPUTS(NODE, VALID_INPUTS) \
394 OnnxParser::ValidateInputs(NODE, \
395 VALID_INPUTS, \
396 CHECK_LOCATION())
397
398std::vector<TensorInfo> OnnxParser::ComputeOutputInfo(std::vector<std::string> outNames,
399 const IConnectableLayer* layer,
400 std::vector<TensorShape> inputShapes)
401{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100402 ARMNN_ASSERT(! outNames.empty());
telsoa01c577f2c2018-08-31 09:22:23 +0100403 bool needCompute = std::any_of(outNames.begin(),
404 outNames.end(),
405 [this](std::string name)
406 {
407 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr);
408 });
409 std::vector<TensorInfo> outInfo;
410 //if the output info(s) are not here, we need to compute them
411 std::vector<TensorShape> inferredShapes;
412 if(needCompute)
413 {
414 inferredShapes = layer->InferOutputShapes(inputShapes);
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100415 ARMNN_ASSERT(inferredShapes.size() == outNames.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100416 }
417 for (uint i = 0; i < outNames.size(); ++i)
418 {
419 if(needCompute)
420 {
421 m_TensorsInfo[outNames[i]] = OnnxTensor();
422 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
423 TensorInfo(inferredShapes[i], DataType::Float32));
424 }
425 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
426 }
427 return outInfo;
428}
429
430IOnnxParser* IOnnxParser::CreateRaw()
431{
432 return new OnnxParser();
433}
434
435IOnnxParserPtr IOnnxParser::Create()
436{
437 return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
438}
439
440void IOnnxParser::Destroy(IOnnxParser* parser)
441{
442 delete parser;
443}
444
445OnnxParser::OnnxParser()
446 : m_Network(nullptr, nullptr)
447{
448}
449
450void OnnxParser::ResetParser()
451{
452 m_Network = armnn::INetworkPtr(nullptr, nullptr);
453 m_Graph = nullptr;
454}
455
456void OnnxParser::Cleanup()
457{
458 m_TensorConnections.clear();
459 m_TensorsInfo.clear();
460 m_OutputsMap.clear();
461 m_OutputsFusedAndUsed.clear();
462}
463
464std::pair<ConstTensor, std::unique_ptr<float[]>> OnnxParser::CreateConstTensor(const std::string name)
465{
466 const TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
467 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
468
469 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100470 std::unique_ptr<float[]> tensorData(new float[tensorInfo.GetNumElements()]);
471 const size_t tensorSizeInBytes = tensorInfo.GetNumBytes();
472 // Copy the value list entries into the destination
473 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100474 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100475 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
476 {
James Ward58dec6b2020-09-11 17:32:44 +0100477 throw ParseException(
478 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
479 "elements ({}) {}",
480 onnxTensor.float_data_size(),
481 name,
482 tensorInfo.GetNumElements(),
483 CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100484 }
485 ::memcpy(tensorData.get(), srcData, tensorSizeInBytes);
telsoa01c577f2c2018-08-31 09:22:23 +0100486 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100487 else
488 {
489 ::memcpy(tensorData.get(), onnxTensor.raw_data().c_str(), tensorSizeInBytes);
490 }
telsoa01c577f2c2018-08-31 09:22:23 +0100491
492 // Const tensors requires at least a list of values
493 if (tensorInfo.GetNumElements() == 0)
494 {
James Ward58dec6b2020-09-11 17:32:44 +0100495 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
496 name,
497 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100498 }
499 return std::make_pair(ConstTensor(tensorInfo, tensorData.get()), std::move(tensorData));
500}
501
502ModelPtr OnnxParser::LoadModelFromTextFile(const char* graphFile)
503{
504 FILE* fd = fopen(graphFile, "r");
505
506 if (fd == nullptr)
507 {
James Ward58dec6b2020-09-11 17:32:44 +0100508 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100509 }
510
511 // Parse the file into a message
512 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
513 using google::protobuf::io::FileInputStream;
514 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
515 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
516 fclose(fd);
517
518 if (!success)
519 {
520 std::stringstream error;
521 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100522 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100523 }
524 return modelProto;
525}
526
527INetworkPtr OnnxParser::CreateNetworkFromTextFile(const char* graphFile)
528{
529 ResetParser();
530 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
531 return CreateNetworkFromModel(*modelProto);
532}
533
534
535ModelPtr OnnxParser::LoadModelFromBinaryFile(const char* graphFile)
536{
537 FILE* fd = fopen(graphFile, "rb");
538
539 if (fd == nullptr)
540 {
James Ward58dec6b2020-09-11 17:32:44 +0100541 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100542 }
543
544 // Parse the file into a message
545 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
546
547 google::protobuf::io::FileInputStream inStream(fileno(fd));
548 google::protobuf::io::CodedInputStream codedStream(&inStream);
549 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
550 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
551 fclose(fd);
552
553 if (!success)
554 {
555 std::stringstream error;
556 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100557 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100558 }
559 return modelProto;
560
561}
562
563INetworkPtr OnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
564{
565 ResetParser();
566 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
567 return CreateNetworkFromModel(*modelProto);
568}
569
570ModelPtr OnnxParser::LoadModelFromString(const std::string& protoText)
571{
572 if (protoText == "")
573 {
James Ward58dec6b2020-09-11 17:32:44 +0100574 throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
575 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100576 }
577 // Parse the string into a message
578 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
579 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
580 if (!success)
581 {
582 std::stringstream error;
583 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100584 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100585 }
586 return modelProto;
587}
588
589INetworkPtr OnnxParser::CreateNetworkFromString(const std::string& protoText)
590{
591 ResetParser();
592 ModelPtr modelProto = LoadModelFromString(protoText);
593 return CreateNetworkFromModel(*modelProto);
594}
595
596INetworkPtr OnnxParser::CreateNetworkFromModel(onnx::ModelProto& model)
597{
598 m_Network = INetwork::Create();
599 try
600 {
601 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
602 LoadGraph();
603 }
604 catch (const ParseException& e)
605 {
606 Cleanup();
607 throw e;
608 }
609 Cleanup();
610 return std::move(m_Network);
611}
612
613void OnnxParser::LoadGraph()
614{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100615 ARMNN_ASSERT(m_Graph.get() != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100616
617 //Fill m_TensorsInfo with the shapes and value of every tensor
618 SetupInfo(m_Graph->mutable_output());
619 SetupInfo(m_Graph->mutable_input());
620 SetupInfo(m_Graph->mutable_value_info());
621
622 for (auto tensor : m_Graph->initializer())
623 {
624 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000625 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
626 m_TensorsInfo[tensor.name()].m_dtype =
627 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100628 }
629
630 SetupInputLayers();
631 SetupOutputLayers();
632
633 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
634 DetectFullyConnected();
635
636 //Parsing the graph
637 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
638 {
639 auto node = m_Graph->node(static_cast<int>(nodeIndex));
640 const std::string& operation = node.op_type();
641
642 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000643 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100644 {
645 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
646 {
647 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
648 AddFullyConnected(node);
649 }
650 }
651 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
652 {
653 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
654 AddFullyConnected(m_Graph->node(matmulIndex), &node);
655 }
656 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
657 {
658 auto it = m_ParserFunctions.find(operation);
659 if (it != m_ParserFunctions.end())
660 {
661 auto func = it->second;
662 (this->*func)(node);
663 }
664 else
665 {
James Ward58dec6b2020-09-11 17:32:44 +0100666 throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
667 operation,
668 node.name(),
669 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100670 }
671 }
672 }
673
674 //Making the connections between outputs and inputs of each layers
675 for (const auto& tensorCon : m_TensorConnections)
676 {
677 if (tensorCon.second.outputSlot != nullptr)
678 {
679 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
680 {
681 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
682 }
683 }
684 }
685}
686
687void OnnxParser::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
688{
689 for (auto tensor : *list)
690 {
691 m_TensorsInfo[tensor.name()] = OnnxTensor();
692 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000693 m_TensorsInfo[tensor.name()].m_dtype =
694 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100695 }
696}
697
698void OnnxParser::DetectFullyConnected()
699{
700 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
701 auto matmulAndConstant = [&](const std::string& constInput,
702 const std::string& matmulInput,
703 int& nodeIndex)
704 {
705 auto matmulIt = m_OutputsMap.find(matmulInput);
706 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
707 && m_TensorsInfo[constInput].isConstant())
708 {
709 nodeIndex = matmulIt->second.second;
710 return true;
711 }
712 return false;
713 };
714
715 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
716 {
717 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
718 for (const std::string& output : node->output())
719 {
720 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
721 }
722
723 for (const std::string& input : node->input()) //count how many time a node is used as input
724 {
725 auto matmulIt = m_OutputsMap.find(input);
726 if(matmulIt != m_OutputsMap.end()){
727 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
728 }
729 }
730
731 if (node->op_type() == "Add")
732 {
733 int matmulIndex = 0;
734 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
735 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
736 {
737 //matmul and add were fused
738 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
739 .push_back(static_cast<size_t>(nodeIndex));
740
741 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
742 .push_back(static_cast<size_t>(matmulIndex));
743 }
744 }
745 }
746
747 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
748 auto matmulIt = m_OutputsMap.find(output.name());
749 if(matmulIt != m_OutputsMap.end()){
750 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
751 }
752 }
753}
754
755template<typename Location>
756void OnnxParser::GetInputAndParam(const onnx::NodeProto& node,
757 std::string* inputName,
758 std::string* constName,
759 const Location& location)
760{
761 int cstIndex;
762 if (m_TensorsInfo[node.input(0)].isConstant())
763 {
764 cstIndex = 0;
765 }
766 else if (m_TensorsInfo[node.input(1)].isConstant())
767 {
768 cstIndex = 1;
769 }
770 else
771 {
James Ward58dec6b2020-09-11 17:32:44 +0100772 throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
773 node.input(0),
774 node.input(1),
775 node.name(),
776 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100777 }
778 if(constName)
779 {
780 *constName = node.input(cstIndex);
781 }
782 if(inputName)
783 {
784 *inputName = node.input(!cstIndex);
785 }
786}
787
788template<typename Location>
789void OnnxParser::To1DTensor(const std::string& name, const Location& location)
790{
791 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
792 std::vector<uint32_t> newShape;
793 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
794 {
795 if(shape[i] != 1)
796 {
James Ward58dec6b2020-09-11 17:32:44 +0100797 throw ParseException(
798 fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
799 TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
800 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100801 }
802 }
803 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
804
805 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
806}
807
Ryan OSheaed27ee72020-04-22 16:37:29 +0100808void OnnxParser::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
809{
810 ARMNN_ASSERT(node.op_type() == "Conv");
811
812 DepthwiseConvolution2dDescriptor desc;
813 desc.m_PadLeft = convDesc.m_PadLeft;
814 desc.m_PadRight = convDesc.m_PadRight;
815 desc.m_PadTop = convDesc.m_PadTop;
816 desc.m_PadBottom = convDesc.m_PadBottom;
817 desc.m_StrideX = convDesc.m_StrideX;
818 desc.m_StrideY = convDesc.m_StrideY;
819 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
820
821 armnn::IConnectableLayer* layer;
822 auto weightTensor = CreateConstTensor(node.input(1));
823 TensorShape& weightShape = weightTensor.first.GetShape();
824 weightShape[1] = weightShape[0];
825 weightShape[0] = 1;
826 m_TensorsInfo[node.input(1)].m_info->SetShape(weightShape);
827
828 if (node.input_size() == 3)
829 {
830 if(!m_TensorsInfo[node.input(2)].isConstant())
831 {
James Ward58dec6b2020-09-11 17:32:44 +0100832 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
833 node.input(2),
834 node.name(),
835 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +0100836 }
837 desc.m_BiasEnabled = true;
838 auto biasTensor = CreateConstTensor(node.input(2));
839 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
840 weightTensor.first,
841 Optional<ConstTensor>(biasTensor.first),
842 node.name().c_str());
843 }
844 else
845 {
846 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
847 weightTensor.first,
848 EmptyOptional(),
849 node.name().c_str());
850 }
851 ARMNN_ASSERT(layer != nullptr);
852
853 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
854 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
855 m_TensorsInfo[node.input(1)].m_info->GetShape() });
856
857 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
858
859 // register the input connection slots for the layer, connections are made after all layers have been created
860 // only the tensors for the inputs are relevant, exclude the const tensors
861 RegisterInputSlots(layer, {node.input(0)});
862
863 // register the output connection slots for the layer, connections are made after all layers have been created
864 RegisterOutputSlots(layer, {node.output(0)});
865}
866
telsoa01c577f2c2018-08-31 09:22:23 +0100867void OnnxParser::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
868{
869
870 // find matmul inputs
871 std::string weightName;
872 std::string inputName;
873 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
874 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
875 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
876
877 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
878
879 FullyConnectedDescriptor desc;
880 desc.m_BiasEnabled = addNode != nullptr;
881
882 IConnectableLayer* layer = nullptr;
883 if(desc.m_BiasEnabled)
884 {
885 // find bias const
886 std::string biasName;
887 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
888 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
889 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
890
891 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
892
893 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
894 To1DTensor(biasName, CHECK_LOCATION());
895 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
896 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
897
898 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
899 {
James Ward58dec6b2020-09-11 17:32:44 +0100900 throw ParseException(
901 fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
902 " and {} ( /!\\ bias should be a 1D tensor) {}",
903 weightName,
904 addNode->name(),
905 TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
906 m_TensorsInfo[weightName].m_dtype),
907 TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
908 m_TensorsInfo[biasName].m_dtype ),
909 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100910 }
911 layer = m_Network->AddFullyConnectedLayer(desc,
912 CreateConstTensor(weightName).first,
Matteo Martincighfc598e12019-05-14 10:36:13 +0100913 Optional<ConstTensor>(CreateConstTensor(biasName).first),
telsoa01c577f2c2018-08-31 09:22:23 +0100914 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100915 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100916
917 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
918 {m_TensorsInfo[inputName].m_info->GetShape(),
919 m_TensorsInfo[weightName].m_info->GetShape()});
920
921 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
922
923 RegisterInputSlots(layer, {inputName});
924 RegisterOutputSlots(layer, {addNode->output(0)});
925 }
926 else
927 {
Matteo Martincighfc598e12019-05-14 10:36:13 +0100928 layer = m_Network->AddFullyConnectedLayer(desc,
929 CreateConstTensor(weightName).first,
930 EmptyOptional(),
931 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100932 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100933
934 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
935 {m_TensorsInfo[inputName].m_info->GetShape(),
936 m_TensorsInfo[weightName].m_info->GetShape()});
937 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
938
939 RegisterInputSlots(layer, {inputName});
940 RegisterOutputSlots(layer, {matmulNode.output(0)});
941 }
942}
943
telsoa01c577f2c2018-08-31 09:22:23 +0100944void OnnxParser::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
945{
946
947 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
948 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
949
950 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
951
952 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
953 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
954 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
955
956 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
957 desc.m_PoolWidth = kernel_shape[1];
958 desc.m_PoolHeight = kernel_shape[0];
959
960 if(strides.empty())
961 {
962 desc.m_StrideX = 1;
963 desc.m_StrideY = 1;
964 }
965 else
966 {
967 desc.m_StrideX = strides[1];
968 desc.m_StrideY = strides[0];
969 }
970
971 //Check new padding version first
972 if(pads.empty())
973 {
974 //Check deprecated version
975 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
976 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
977 {
978 bool isUpper;
979 if( paddingString == "SAME_LOWER")
980 {
981 isUpper = false;
982 }
983 else if (paddingString == "SAME_UPPER")
984 {
985 isUpper = true;
986 }
987 else
988 {
James Ward58dec6b2020-09-11 17:32:44 +0100989 throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
990 "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
991 node.name(),
992 paddingString,
993 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100994 }
995 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
996 uint32_t inputHeight = inputInfo.GetShape()[2];
997 uint32_t inputWidth = inputInfo.GetShape()[3];
998 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
999 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1000 }
1001 }
1002 else
1003 {
1004 desc.m_PadTop = pads[0];
1005 desc.m_PadLeft = pads[1];
1006 desc.m_PadBottom = pads[2];
1007 desc.m_PadRight = pads[3];
1008 }
1009
1010 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001011 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001012
1013 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1014 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1015
1016 // register the input connection slots for the layer, connections are made after all layers have been created
1017 // only the tensors for the inputs are relevant, exclude the const tensors
1018 RegisterInputSlots(layer, {node.input(0)});
1019
1020 // register the output connection slots for the layer, connections are made after all layers have been created
1021 RegisterOutputSlots(layer, {node.output(0)});
1022}
1023
Ryan OSheaed27ee72020-04-22 16:37:29 +01001024std::pair<std::string, std::string> OnnxParser::AddPrepareBroadcast(const std::string& input0,
1025 const std::string& input1)
1026{
1027 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1028
1029 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1030 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1031
1032 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1033 {
James Ward58dec6b2020-09-11 17:32:44 +01001034 auto outputName = fmt::format("reshape_output_{}", input1);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001035 PrependForBroadcast(outputName, input1, input0);
1036 inputs.second = outputName;
1037 }
1038 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1039 {
James Ward58dec6b2020-09-11 17:32:44 +01001040 auto outputName = fmt::format("reshape_output_{}", input0);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001041 PrependForBroadcast(outputName, input0, input1);
1042 inputs.first = outputName;
1043 }
1044 return inputs;
1045}
1046
1047void OnnxParser::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
1048{
1049 auto armnnTensor = CreateConstTensor(tensorName);
1050
1051 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1052 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1053 RegisterOutputSlots(layer, {tensorName});
1054}
1055
telsoa01c577f2c2018-08-31 09:22:23 +01001056void OnnxParser::CreateReshapeLayer(const std::string& inputName,
1057 const std::string& outputName,
1058 const std::string& layerName)
1059{
1060 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1061 ReshapeDescriptor reshapeDesc;
1062 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1063
1064 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001065 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001066 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1067
1068 // register the input connection slots for the layer, connections are made after all layers have been created
1069 // only the tensors for the inputs are relevant, exclude the const tensors
1070 RegisterInputSlots(layer, {inputName});
1071
1072 // register the output connection slots for the layer, connections are made after all layers have been created
1073 RegisterOutputSlots(layer, {outputName});
1074}
1075
Tee Jung7ff9a602019-11-01 07:04:42 +00001076void OnnxParser::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001077{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001078 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001079 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1080
1081 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1082
1083 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001084 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001085
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001086 if (func == ActivationFunction::BoundedReLu)
1087 {
1088 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1089 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1090 }
1091
telsoa01c577f2c2018-08-31 09:22:23 +01001092 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001093 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001094
1095 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1096 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1097
1098 // register the input connection slots for the layer, connections are made after all layers have been created
1099 // only the tensors for the inputs are relevant, exclude the const tensors
1100 RegisterInputSlots(layer, {node.input(0)});
1101
1102 // register the output connection slots for the layer, connections are made after all layers have been created
1103 RegisterOutputSlots(layer, {node.output(0)});
1104}
1105
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001106void OnnxParser::ParseClip(const onnx::NodeProto& node)
1107{
1108 ParseActivation(node, ActivationFunction::BoundedReLu);
1109}
1110
Tee Jung7ff9a602019-11-01 07:04:42 +00001111void OnnxParser::ParseSigmoid(const onnx::NodeProto& node)
1112{
1113 ParseActivation(node, ActivationFunction::Sigmoid);
1114}
1115
1116void OnnxParser::ParseTanh(const onnx::NodeProto& node)
1117{
1118 ParseActivation(node, ActivationFunction::TanH);
1119}
1120
1121void OnnxParser::ParseRelu(const onnx::NodeProto& node)
1122{
1123 ParseActivation(node, ActivationFunction::ReLu);
1124}
1125
1126void OnnxParser::ParseLeakyRelu(const onnx::NodeProto& node)
1127{
1128 ParseActivation(node, ActivationFunction::LeakyReLu);
1129}
telsoa01c577f2c2018-08-31 09:22:23 +01001130
Ryan OSheaed27ee72020-04-22 16:37:29 +01001131void OnnxParser::ParseAdd(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001132{
Ryan OSheaed27ee72020-04-22 16:37:29 +01001133 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1134 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +01001135
Ryan OSheaed27ee72020-04-22 16:37:29 +01001136 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
telsoa01c577f2c2018-08-31 09:22:23 +01001137
Ryan OSheaed27ee72020-04-22 16:37:29 +01001138 // TODO: unify broadcast validation code across layers
1139 // tracked by: IVGCVSW-1576
telsoa01c577f2c2018-08-31 09:22:23 +01001140
Ryan OSheaed27ee72020-04-22 16:37:29 +01001141 // Checking broadcast compatibility : only scalar or 1D tensors
1142 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1143 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1144 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1145 ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1146
1147 unsigned int numDims = input0.GetNumDimensions();
1148 for (unsigned int i = 0; i < numDims; i++)
telsoa01c577f2c2018-08-31 09:22:23 +01001149 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01001150 unsigned int dim0 = input0.GetShape()[i];
1151 unsigned int dim1 = input1.GetShape()[i];
1152 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
telsoa01c577f2c2018-08-31 09:22:23 +01001153 {
James Ward58dec6b2020-09-11 17:32:44 +01001154 throw ParseException(
1155 fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1156 "Input dimensions should either match or one should be of size 1 and here, "
1157 "{} and {} {}",
1158 node.name(),
1159 TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1160 m_TensorsInfo[inputs.first].m_dtype),
1161 TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1162 m_TensorsInfo[inputs.second].m_dtype),
1163 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001164 }
telsoa01c577f2c2018-08-31 09:22:23 +01001165 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001166
1167
1168 IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001169 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001170
1171 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
Ryan OSheaed27ee72020-04-22 16:37:29 +01001172 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1173 m_TensorsInfo[inputs.second].m_info->GetShape() });
telsoa01c577f2c2018-08-31 09:22:23 +01001174 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1175
Ryan OSheaed27ee72020-04-22 16:37:29 +01001176 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1177 if(m_TensorsInfo[inputs.first].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001178 CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001179 }
1180 if(m_TensorsInfo[inputs.second].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001181 CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001182 }
1183 RegisterInputSlots(layer, {inputs.first, inputs.second});
telsoa01c577f2c2018-08-31 09:22:23 +01001184
Ryan OSheaed27ee72020-04-22 16:37:29 +01001185 // register the output connection
telsoa01c577f2c2018-08-31 09:22:23 +01001186 RegisterOutputSlots(layer, {node.output(0)});
1187}
1188
Ryan OSheaed27ee72020-04-22 16:37:29 +01001189void OnnxParser::ParseAveragePool(const onnx::NodeProto& node)
1190{
1191 Pooling2dDescriptor desc;
1192 desc.m_PoolType = PoolingAlgorithm::Average;
1193
1194 uint32_t count_include_pad = 0;
1195 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1196 if(count_include_pad) {
1197 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1198 }
1199 AddPoolingLayer(node, desc);
1200}
1201
1202void OnnxParser::ParseBatchNormalization(const onnx::NodeProto& node)
1203{
1204 //IGNORE momentum parameter and spatial parameters
1205
1206 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1207 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1208
1209 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1210 for(int ind = 1; ind < node.input_size(); ++ind)
1211 {
1212 auto tensor = node.input(ind);
1213 if(! m_TensorsInfo[tensor].isConstant())
1214 {
James Ward58dec6b2020-09-11 17:32:44 +01001215 throw ParseException(
1216 fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1217 tensor,
1218 node.name(),
1219 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001220 }
1221 }
1222
1223 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1224 BatchNormalizationDescriptor desc;
1225 desc.m_Eps = epsilon;
1226
1227 auto scaleTensor = CreateConstTensor(node.input(1));
1228 auto biasTensor = CreateConstTensor(node.input(2));
1229 auto meanTensor = CreateConstTensor(node.input(3));
1230 auto varTensor = CreateConstTensor(node.input(4));
1231
1232 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1233 meanTensor.first,
1234 varTensor.first,
1235 biasTensor.first,
1236 scaleTensor.first,
1237 node.name().c_str());
1238 ARMNN_ASSERT(layer != nullptr);
1239
1240 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1241 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1242
1243 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1244
1245 // register the output connection
1246 RegisterOutputSlots(layer, {node.output(0)});
1247}
1248
1249void OnnxParser::ParseConstant(const onnx::NodeProto& node)
1250{
1251 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1252 if (!node.attribute(0).has_t())
1253 {
James Ward58dec6b2020-09-11 17:32:44 +01001254 throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1255 node.name(),
1256 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001257 }
1258 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1259
1260 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
1261 CHECK_VALID_DATATYPE(node.name(), onnxTensor.name(),
1262 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
1263
1264 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1265 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1266 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1267 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1268
1269 CreateConstantLayer(node.output(0), node.name());
1270}
1271
telsoa01c577f2c2018-08-31 09:22:23 +01001272void OnnxParser::ParseConv(const onnx::NodeProto& node)
1273{
1274 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1275 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1276
1277 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1278
1279 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1280 {
James Ward58dec6b2020-09-11 17:32:44 +01001281 throw ParseException(
1282 fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1283 node.name(),
1284 TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1285 m_TensorsInfo[node.input(0)].m_dtype),
1286 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001287 }
1288
1289 if(!m_TensorsInfo[node.input(1)].isConstant())
1290 {
James Ward58dec6b2020-09-11 17:32:44 +01001291 throw ParseException(
1292 fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1293 node.input(1),
1294 node.name(),
1295 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001296 }
1297
1298 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1299
1300 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1301 if (!dilations.empty())
1302 {
1303 std::stringstream ss;
1304 ss << "[ ";
1305 for (auto dilation : dilations)
1306 {
1307 ss << dilation << ", ";
1308 if (dilation != 1u)
1309 {
1310 ss << "... ]";
James Ward58dec6b2020-09-11 17:32:44 +01001311 throw ParseException(
1312 fmt::format("ArmNN only supports Convolution layers with dilations [1,1], and node '{}' "
1313 "has dilatation {} {}",
1314 node.name(), ss.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001315 }
1316 }
1317 }
1318
1319 Convolution2dDescriptor desc;
1320 desc.m_BiasEnabled = false;
1321
1322 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1323 if(strides.empty())
1324 {
1325 desc.m_StrideX = 1;
1326 desc.m_StrideY = 1;
1327 }
1328 else
1329 {
1330 desc.m_StrideX = strides[1];
1331 desc.m_StrideY = strides[0];
1332 }
1333
1334 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1335 //Check new padding version first
1336 if(pads.empty())
1337 {
1338 //Check deprecated version
1339 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1340 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1341 {
1342 bool isUpper;
1343 if( paddingString == "SAME_LOWER")
1344 {
1345 isUpper = false;
1346 }
1347 else if (paddingString == "SAME_UPPER")
1348 {
1349 isUpper = true;
1350 }
1351 else
1352 {
James Ward58dec6b2020-09-11 17:32:44 +01001353 throw ParseException(
1354 fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1355 "supported and found {} {}",
1356 node.name(),
1357 paddingString,
1358 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001359 }
1360 uint32_t inputHeight = inputInfo.GetShape()[2];
1361 uint32_t inputWidth = inputInfo.GetShape()[3];
1362
1363 uint32_t weightHeight;
1364 uint32_t weightWidth;
1365 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1366 if (kernel_shape.empty())
1367 {
1368 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1369 weightHeight = weightTensorInfo.GetShape()[2];
1370 weightWidth = weightTensorInfo.GetShape()[3];
1371 }
1372 else
1373 {
1374 weightHeight = kernel_shape[0];
1375 weightWidth = kernel_shape[1];
1376 }
1377 CalcPadding(inputHeight, weightHeight, desc.m_StrideY, &desc.m_PadTop, &desc.m_PadBottom, isUpper);
1378 CalcPadding(inputWidth, weightWidth, desc.m_StrideX, &desc.m_PadLeft, &desc.m_PadRight, isUpper);
1379 }
1380 }
1381 else
1382 {
1383 desc.m_PadTop = pads[0];
1384 desc.m_PadLeft = pads[1];
1385 desc.m_PadBottom = pads[2];
1386 desc.m_PadRight = pads[3];
1387 }
1388
1389 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1390 if(group > 1)
1391 {
1392 if (group > inputInfo.GetShape()[1])
1393 {
1394 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001395 fmt::format("Error parsing Convolution node: {}. "
1396 "The 'group'={} parameter cannot be larger than the "
1397 "channel of the input shape={} (in NCHW format). {}",
1398 node.name(),
1399 group,
1400 inputInfo.GetShape()[1],
1401 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001402 }
1403 else if (group == inputInfo.GetShape()[1])
1404 {
1405 // we use a depthwise convolution here, because the number of groups equals to the
1406 // input channels
1407 AddConvLayerWithDepthwiseConv(node, desc);
1408 return;
1409 }
1410 else
1411 {
1412 // TODO: split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +01001413 // and concatenate the results afterwards
James Ward58dec6b2020-09-11 17:32:44 +01001414 throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1415 "The 'group'={} parameter should be 1 or be equal to the "
1416 "channel of the input shape={} (in NCHW format). {}",
1417 node.name(),
1418 group,
1419 inputInfo.GetShape()[1],
1420 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001421 }
1422 }
1423
1424 armnn::IConnectableLayer* layer;
1425 auto weightTensor = CreateConstTensor(node.input(1));
1426
1427 if (node.input_size() == 3)
1428 {
1429 if(!m_TensorsInfo[node.input(2)].isConstant())
1430 {
James Ward58dec6b2020-09-11 17:32:44 +01001431 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1432 node.input(2),
1433 node.name(),
1434 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001435 }
1436 desc.m_BiasEnabled = true;
1437 auto biasTensor = CreateConstTensor(node.input(2));
1438 layer = m_Network->AddConvolution2dLayer(desc,
1439 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001440 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001441 node.name().c_str());
1442 }
1443 else
1444 {
1445 layer = m_Network->AddConvolution2dLayer(desc,
1446 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001447 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001448 node.name().c_str());
1449 }
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001450 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001451
1452 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1453 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1454 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1455 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1456
1457 // register the input connection slots for the layer, connections are made after all layers have been created
1458 // only the tensors for the inputs are relevant, exclude the const tensors
1459 RegisterInputSlots(layer, {node.input(0)});
1460
1461 // register the output connection slots for the layer, connections are made after all layers have been created
1462 RegisterOutputSlots(layer, {node.output(0)});
1463}
1464
Ryan OSheaed27ee72020-04-22 16:37:29 +01001465void OnnxParser::ParseFlatten(const onnx::NodeProto& node)
1466{
1467 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1468 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1469
1470 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1471 m_TensorsInfo[node.input(0)].m_dtype,
1472 onnx::TensorProto::FLOAT);
1473
1474 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1475 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1476
1477 /// Negative axis conversion
1478 if (axis < 0)
1479 {
1480 axis += inputShape.GetNumDimensions();
1481 }
1482
1483 /// Check Axis is within dimensions
1484 if (axis < 0 || axis >= inputShape.GetNumDimensions())
1485 {
James Ward58dec6b2020-09-11 17:32:44 +01001486 throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
1487 axis, inputShape.GetNumDimensions(), node.name()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001488 }
1489
1490 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1491 uint dimension1{1};
1492 uint dimension2{1};
1493 uint i{0};
1494
1495 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1496 for (i = 0; i < axis; i++){
1497 dimension1 *= inputShape[i];
1498 }
1499
1500 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
1501 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
1502 dimension2 *= inputShape[i];
1503 }
1504
1505 TensorShape outputShape{dimension1, dimension2};
1506
1507 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
1508 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1509 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1510}
1511
1512void OnnxParser::ParseGlobalAveragePool(const onnx::NodeProto& node)
1513{
1514 Pooling2dDescriptor desc = Pooling2dDescriptor();
1515 desc.m_PoolType = PoolingAlgorithm::Average;
1516
1517 //kernel size is the same as input
1518 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1519 desc.m_PoolWidth = inputShape[3];
1520 desc.m_PoolHeight = inputShape[2];
1521
1522 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1523 ARMNN_ASSERT(layer != nullptr);
1524
1525 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
1526 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1527
1528 // register the input connection slots for the layer, connections are made after all layers have been created
1529 // only the tensors for the inputs are relevant, exclude the const tensors
1530 RegisterInputSlots(layer, {node.input(0)});
1531
1532 // register the output connection slots for the layer, connections are made after all layers have been created
1533 RegisterOutputSlots(layer, {node.output(0)});
1534}
1535
1536void OnnxParser::ParseMaxPool(const onnx::NodeProto& node)
1537{
1538 Pooling2dDescriptor desc;
1539 desc.m_PoolType = PoolingAlgorithm::Max;
1540 desc.m_PaddingMethod = PaddingMethod::Exclude;
1541 AddPoolingLayer(node, desc);
1542}
1543
1544void OnnxParser::ParseReshape(const onnx::NodeProto& node)
1545{
1546 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1547 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1548
1549 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1550 m_TensorsInfo[node.input(0)].m_dtype,
1551 onnx::TensorProto::FLOAT); //input
1552 CHECK_VALID_DATATYPE(node.name(), node.input(1),
1553 m_TensorsInfo[node.input(1)].m_dtype,
1554 onnx::TensorProto::INT64); //shape
1555
1556 if(!m_TensorsInfo[node.input(1)].isConstant())
1557 {
James Ward58dec6b2020-09-11 17:32:44 +01001558 throw ParseException(fmt::format("Shape '{}' should be constant in Reshape layer '{}' {}",
1559 node.input(1),
1560 node.name(),
1561 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001562 }
1563
1564 if(m_TensorsInfo[node.input(0)].isConstant())
1565 {
1566 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
1567 if(m_TensorsInfo.count(node.output(0)) == 0)
1568 {
1569 m_TensorsInfo[node.output(0)] = OnnxTensor();
1570 }
1571 m_TensorsInfo[node.output(0)].m_tensor =
1572 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1573 }
1574 else
1575 {
1576 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1577
1578 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
1579 {
1580 uint64_t dims = static_cast<uint64_t>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
1581 TensorShape targetShape{static_cast<unsigned int>(dims), 1};
1582
1583 for(uint i = 0; i < dims; i++)
1584 {
1585 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
1586 targetShape[i]= static_cast<unsigned int>(val);
1587 }
1588
1589 auto outInfo = ComputeReshapeInfo(targetShape, inputShape, node.output(0));
1590 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1591 }
1592
1593 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1594 }
1595}
1596
telsoa01c577f2c2018-08-31 09:22:23 +01001597void OnnxParser::PrependForBroadcast(const std::string& outputName,
1598 const std::string& input0,
1599 const std::string& input1)
1600{
1601 //input0 should be reshaped to have same number of dim as input1
1602 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
1603
1604 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1605 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1606
1607 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
1608 std::vector<uint32_t> newShape;
1609 while(diff > 0)
1610 {
1611 newShape.push_back(1);
1612 diff--;
1613 }
1614 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
1615 {
1616 newShape.push_back(input0Shape[dim]);
1617 }
1618 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1619
1620 //add the new tensor to m_TensorsInfo
1621 m_TensorsInfo[outputName] = OnnxTensor();
1622 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1623
1624 //add reshape layer if the parent was not constant...
1625 if( ! m_TensorsInfo[input0].isConstant())
1626 {
James Ward58dec6b2020-09-11 17:32:44 +01001627 CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
telsoa01c577f2c2018-08-31 09:22:23 +01001628 }
1629 else //make it constant and it will be create in Add
1630 {
1631 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1632
1633 }
1634}
1635
telsoa01c577f2c2018-08-31 09:22:23 +01001636void OnnxParser::SetupInputLayers()
1637{
1638 //Find user input and add their layers
1639 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1640 {
1641 auto input = m_Graph->input(inputIndex);
1642 if (! m_TensorsInfo[input.name()].isConstant())
1643 {
1644 IConnectableLayer* layer =
1645 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
1646 auto tensorInfo = ToTensorInfo(input);
1647 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1648
1649 RegisterOutputSlots(layer,{ input.name() });
1650 }
1651 }
1652}
1653
1654void OnnxParser::SetupOutputLayers()
1655{
1656 if(m_Graph->output_size() == 0)
1657 {
James Ward58dec6b2020-09-11 17:32:44 +01001658 throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001659 }
1660
1661 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
1662 {
1663 IConnectableLayer* layer =
1664 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
1665 m_Graph->output(outputIndex).name().c_str());
1666
1667 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
1668 }
1669}
1670
1671void OnnxParser::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1672{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001673 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001674 if (tensorIds.size() != layer->GetNumInputSlots())
1675 {
1676 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001677 fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
1678 tensorIds.size(),
1679 layer->GetNumInputSlots(),
1680 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001681 }
1682 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
1683 {
1684 std::string tensorId = tensorIds[slotIndex];
1685 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
1686
1687 auto it = m_TensorConnections.find(tensorId);
1688
1689 if (it == m_TensorConnections.end())
1690 {
1691 //First time seing this tensor, we need to map it
1692 m_TensorConnections[tensorId] = TensorSlots();
1693 }
1694 m_TensorConnections[tensorId].inputSlots.push_back(slot);
1695 }
1696}
1697
1698void OnnxParser::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
1699{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001700 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001701 if (tensorIds.size() != layer->GetNumOutputSlots())
1702 {
1703 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001704 fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
1705 tensorIds.size(),
1706 layer->GetNumOutputSlots(),
1707 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001708 }
1709
1710 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
1711 {
1712 std::string tensorId = tensorIds[slotIndex];
1713 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
1714
1715 auto it = m_TensorConnections.find(tensorId);
1716
1717 if (it == m_TensorConnections.end())
1718 {
1719 //First time seing this tensor, we need to map it
1720 m_TensorConnections[tensorId] = TensorSlots();
1721 }
1722
Ryan OShea337c17f2020-02-21 12:33:17 +00001723 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01001724
1725 // assuming there is only one producer for that tensor
1726 if (tensorSlots.outputSlot != nullptr)
1727 {
James Ward58dec6b2020-09-11 17:32:44 +01001728 throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
1729 "tensor:{} {}",
1730 tensorId,
1731 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001732 }
1733 tensorSlots.outputSlot = slot;
1734 }
1735}
1736
1737BindingPointInfo OnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
1738{
1739 for(int i = 0; i < m_Graph->input_size(); ++i)
1740 {
1741 auto input = m_Graph->input(i);
1742 if(input.name() == name)
1743 {
1744 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(input));
1745 }
1746 }
James Ward58dec6b2020-09-11 17:32:44 +01001747 throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
1748 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001749}
1750
1751BindingPointInfo OnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
1752{
1753 for(int i = 0; i < m_Graph->output_size(); ++i)
1754 {
1755 auto output = m_Graph->output(i);
1756 if(output.name() == name)
1757 {
1758 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(output));
1759 }
1760 }
James Ward58dec6b2020-09-11 17:32:44 +01001761 throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
1762 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001763}
1764
1765std::vector<std::string> OnnxParser::GetInputs(ModelPtr& model)
1766{
1767 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01001768 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
1769 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001770 }
1771
1772 std::vector<std::string> inputNames;
1773 std::map<std::string, bool> isConstant;
1774 for(auto tensor : model->graph().initializer())
1775 {
1776 isConstant[tensor.name()] = true;
1777 }
1778 for(auto input : model->graph().input())
1779 {
1780 auto it = isConstant.find(input.name());
1781 if(it == isConstant.end())
1782 {
1783 inputNames.push_back(input.name());
1784 }
1785 }
1786 return inputNames;
1787}
1788
1789std::vector<std::string> OnnxParser::GetOutputs(ModelPtr& model)
1790{
1791 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01001792 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
1793 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001794 }
1795
1796 std::vector<std::string> outputNames;
1797 for(auto output : model->graph().output())
1798 {
1799 outputNames.push_back(output.name());
1800 }
1801 return outputNames;
1802}
1803
1804} // namespace armnnOnnxParser