blob: 63fb60382cf580455e2fc9ef69097f91f13d230f [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 Sloyanac001ee2021-02-03 10:43:04 +00007#include "armnnOnnxParser/Version.hpp"
8
Matthew Bentham39ef3e52020-01-20 10:09:09 +00009#include <armnn/Descriptors.hpp>
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +010010#include <armnn/utility/Assert.hpp>
Matthew Sloyan589e3e82020-09-11 16:17:48 +010011#include <armnn/utility/NumericCast.hpp>
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +010012#include <ParserHelper.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010013#include <VerificationHelpers.hpp>
14
James Ward58dec6b2020-09-11 17:32:44 +010015#include <fmt/format.h>
Aron Virginas-Tard4f0fea2019-04-09 14:08:06 +010016
telsoa01c577f2c2018-08-31 09:22:23 +010017#include <google/protobuf/text_format.h>
18#include <google/protobuf/io/zero_copy_stream_impl.h>
19
Matthew Sloyanac001ee2021-02-03 10:43:04 +000020#include <iostream>
telsoa01c577f2c2018-08-31 09:22:23 +010021#include <numeric>
Jan Eilers53ef7952021-06-02 12:01:25 +010022#include <armnnUtils/Permute.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010023
24using namespace armnn;
25
26namespace armnnOnnxParser
27{
Kevin Mayef33cb12021-01-29 14:24:57 +000028
29IOnnxParser::IOnnxParser() : pOnnxParserImpl(new OnnxParserImpl()) {}
30
31IOnnxParser::~IOnnxParser() = default;
32
33IOnnxParser* IOnnxParser::CreateRaw()
34{
35 return new IOnnxParser();
36}
37
38IOnnxParserPtr IOnnxParser::Create()
39{
40 return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
41}
42
43void IOnnxParser::Destroy(IOnnxParser* parser)
44{
45 delete parser;
46}
47
48armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
49{
50 return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile);
51}
52
53armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile)
54{
55 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile);
56}
57
58armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText)
59{
60 return pOnnxParserImpl->CreateNetworkFromString(protoText);
61}
62
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +010063armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinaryFile(
64 const char* graphFile,
65 const std::map<std::string, armnn::TensorShape>& inputShapes)
66{
67 return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile, inputShapes);
68}
69
70armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile,
71 const std::map<std::string, armnn::TensorShape>& inputShapes)
72{
73 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile, inputShapes);
74}
75
76armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText,
77 const std::map<std::string, armnn::TensorShape>& inputShapes)
78{
79 return pOnnxParserImpl->CreateNetworkFromString(protoText, inputShapes);
80}
81
Kevin Mayef33cb12021-01-29 14:24:57 +000082BindingPointInfo IOnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
83{
84 return pOnnxParserImpl->GetNetworkInputBindingInfo(name);
85}
86
87BindingPointInfo IOnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
88{
89 return pOnnxParserImpl->GetNetworkOutputBindingInfo(name);
90}
91
telsoa01c577f2c2018-08-31 09:22:23 +010092namespace
93{
94void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
95 const onnx::TensorProto::DataType actualValue,
96 const char* validExpr,
97 std::string nodeName,
98 std::string tensorName,
99 const armnn::CheckLocation& location)
100{
101 bool isValid = std::any_of(validInputTypes.begin(),
102 validInputTypes.end(),
103 [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
104 if (!isValid)
105 {
106 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100107 fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
108 onnx::TensorProto::DataType_Name(actualValue),
109 tensorName,
110 nodeName,
111 validExpr,
112 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100113 }
114}
115
116#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
117CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
118
119using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
120#define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
121
122template <typename Callable>
123void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
124 const std::string& attribName,
125 onnx::AttributeProto::AttributeType expectedType,
126 Callable callable)
127{
128 auto attribs = node.attribute();
129 int attriNum = 0;
130 while (attriNum < node.attribute_size())
131 {
132 if (attribs.Get(attriNum).name() == attribName)
133 {
134 if (attribs.Get(attriNum).type() == expectedType)
135 {
136 callable(attribs.Get(attriNum));
137 }
138 else
139 {
James Ward58dec6b2020-09-11 17:32:44 +0100140 throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
141 "onnx::AttributeProto::AttributeType, but found {} instead {}",
142 attribName,
143 node.name(),
144 onnx::AttributeProto::AttributeType_Name(expectedType),
145 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
146 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100147 }
148 break;
149 }
150 ++attriNum;
151 }
152 if (attriNum == node.attribute_size())
153 {
James Ward58dec6b2020-09-11 17:32:44 +0100154 throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
155 attribName, node.name(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100156 }
157}
158
159template <typename Callable>
160void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
161 const std::string& attribName,
162 onnx::AttributeProto::AttributeType expectedType,
163 Callable callable)
164{
165 auto attribs = node.attribute();
166 for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
167 {
168 if (attribs.Get(attriNum).name() == attribName)
169 {
170 if (attribs.Get(attriNum).type() == expectedType)
171 {
172 callable(attribs.Get(attriNum));
173 }
174 else
175 {
James Ward58dec6b2020-09-11 17:32:44 +0100176 throw ParseException(
177 fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
178 "but found {} instead {}",
179 attribName,
180 node.name(),
181 onnx::AttributeProto::AttributeType_Name(expectedType),
182 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
183 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100184 }
185 }
186 }
187}
188
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +0100189int ReadMandatoryNodeIntAttribute(const onnx::NodeProto& node,
190 const std::string& name)
191{
192 int attribValue = 0;
193 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
194 [&attribValue](const onnx::AttributeProto& attrValue)
195 {
196 attribValue = CHECKED_INT32(attrValue.i());
197 });
198 return attribValue;
199}
200
Ryan OSheaed27ee72020-04-22 16:37:29 +0100201int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
202 const std::string& name,
203 const int64_t defaultValue = 0)
204{
205 int64_t attribValue = defaultValue;
206 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
207 [&attribValue](const onnx::AttributeProto& attrValue)
208 {
209 attribValue = attrValue.i();
210 });
211 return attribValue;
212}
213
telsoa01c577f2c2018-08-31 09:22:23 +0100214std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
215 const std::string& name)
216{
217 std::vector<uint32_t> attriList;
218 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
219 [&attriList](const onnx::AttributeProto& attrValue)
220 {
221 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
222 {
223 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
224 }
225 });
226 return attriList;
227}
228
229uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
230 const std::string& name,
231 const uint32_t defaultVal = 0u)
232{
233 uint32_t attribValue = defaultVal;
234 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
235 [&attribValue](const onnx::AttributeProto& attrValue)
236 {
237 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
238 });
239 return attribValue;
240}
241
242std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
243 const std::string& name)
244{
245 std::vector<uint32_t> attriList;
246 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
247 [&attriList](const onnx::AttributeProto& attrValue)
248 {
249 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
250 {
251 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
252 }
253 });
254
255 return attriList;
256}
257
258float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
259 const std::string& name,
260 const float defaultValue = 0.0f)
261{
262 float attribValue = defaultValue;
263 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
264 [&attribValue](const onnx::AttributeProto& attrValue)
265 {
266 attribValue = attrValue.f();
267 });
268 return attribValue;
269}
270
271std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
272{
273 std::string attribValue = "";
274 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
275 [&attribValue](const onnx::AttributeProto& attrValue)
276 {
277 attribValue = attrValue.s();
278 });
279 return attribValue;
280}
281
Tee Jungfcf6fd52019-11-01 05:27:28 +0000282armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100283{
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100284 DataType type;
285 switch(data_type)
286 {
287 case onnx::TensorProto::FLOAT:
288 {
289 type = DataType::Float32;
telsoa01c577f2c2018-08-31 09:22:23 +0100290 break;
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100291 }
292 case onnx::TensorProto::INT32:
293 case onnx::TensorProto::INT64:
294 {
295 type = DataType::Signed32;
296 break;
297 }
298 default:
299 {
300 throw ParseException(
301 fmt::format("'{}' is not a currently supported datatype for tensor {}."
302 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
303 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
304 name,
305 CHECK_LOCATION().AsString() ));
306 }
307 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000308
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100309 // Scalar Tensor
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100310 if (shape.empty())
311 {
312 return TensorInfo(TensorShape(Dimensionality::Scalar), type);
313 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000314
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100315 // Dynamic Tensor
316 if(std::find(shape.begin(), shape.end(), 0) != shape.end())
317 {
318 return TensorInfo(TensorShape(Dimensionality::NotSpecified), type);
319 }
320
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100321 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000322}
323
324armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
325{
326 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
327 std::vector<unsigned int> shapeDims;
328 for (int i = 0; i < onnxShape.dim_size(); ++i)
329 {
330 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
331 }
332
333 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
334}
335
336armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
337{
338 std::vector<unsigned int> shapeDims;
Ryan OShea337c17f2020-02-21 12:33:17 +0000339
Tee Jungfcf6fd52019-11-01 05:27:28 +0000340 for (auto dim: tensor.dims())
341 {
342 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
343 }
344
345 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100346}
347
348std::string TensorInfoAsString(const TensorInfo& info,
349 const std::string& name,
350 const onnx::TensorProto::DataType& type)
351{
352 const TensorShape shape = info.GetShape();
353 std::stringstream ss;
354 ss << "tensor '" << name << "' contains "
355 << onnx::TensorProto::DataType_Name(type)
356 << " and has shape [";
357
358 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
359 {
360 ss << shape[i] << ", ";
361 }
362 ss << shape[shape.GetNumDimensions() - 1] << "]";
363 return ss.str();
364}
365
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000366void CalcPadding(uint32_t inputSize,
367 uint32_t filterSize,
368 uint32_t stride,
369 uint32_t dilation,
370 uint32_t* paddingFront,
371 uint32_t* paddingBack,
372 bool isUpper)
telsoa01c577f2c2018-08-31 09:22:23 +0100373{
374 uint32_t outputSize = (inputSize + stride - 1) / stride;
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000375 uint32_t dilatedSize = filterSize + (dilation - 1) * (filterSize - 1);
376 uint32_t temp = (outputSize - 1) * stride + dilatedSize;
telsoa01c577f2c2018-08-31 09:22:23 +0100377 *paddingFront = (temp - inputSize) / 2;
378 *paddingBack = *paddingFront;
379 if((temp - inputSize) % 2 == 1)
380 {
381 if (isUpper)
382 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000383 *paddingBack += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100384 }
385 else
386 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000387 *paddingFront += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100388 }
389 }
390}
391
Ryan OSheaed27ee72020-04-22 16:37:29 +0100392TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
telsoa01c577f2c2018-08-31 09:22:23 +0100393 const TensorShape& inShape,
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100394 const std::string& outName,
395 DataType dataType = DataType::Float32)
telsoa01c577f2c2018-08-31 09:22:23 +0100396{
397 std::vector<int> targetDims;
Ryan OSheaed27ee72020-04-22 16:37:29 +0100398 for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
telsoa01c577f2c2018-08-31 09:22:23 +0100399 {
Ryan OSheaed27ee72020-04-22 16:37:29 +0100400 int val = CHECKED_INT32(targetShapeTensor[i]);
telsoa01c577f2c2018-08-31 09:22:23 +0100401 if(val == 0)
402 {
403 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
404 }
405 else
406 {
407 targetDims.push_back(val);
408 }
409 }
410
411 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
412 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
413 if (stretchDim != targetDims.end())
414 {
415 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
416 {
417 std::stringstream ss;
418 ss << "[ ";
419 for(uint i = 0; i < targetDims.size() - 1; ++i)
420 {
421 ss << targetDims[i] << ", ";
422 }
423 ss << targetDims[targetDims.size() - 1] << " ]";
424
James Ward58dec6b2020-09-11 17:32:44 +0100425 throw ParseException(
426 fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
427 " -1 and here, shape is {} {}",
428 outName,
429 ss.str(),
430 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100431 }
432
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100433 auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
telsoa01c577f2c2018-08-31 09:22:23 +0100434 -1, std::multiplies<int32_t>()));
435 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
436 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
437 }
438 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100439 return TensorInfo(outShape, dataType);
telsoa01c577f2c2018-08-31 09:22:23 +0100440}
441
442} //namespace
443
Kevin Mayef33cb12021-01-29 14:24:57 +0000444const std::map<std::string, OnnxParserImpl::OperationParsingFunction> OnnxParserImpl::m_ParserFunctions = {
445 { "BatchNormalization", &OnnxParserImpl::ParseBatchNormalization},
446 { "GlobalAveragePool", &OnnxParserImpl::ParseGlobalAveragePool},
447 { "AveragePool", &OnnxParserImpl::ParseAveragePool },
448 { "Clip", &OnnxParserImpl::ParseClip },
449 { "Constant", &OnnxParserImpl::ParseConstant },
450 { "MaxPool", &OnnxParserImpl::ParseMaxPool },
451 { "Reshape", &OnnxParserImpl::ParseReshape },
452 { "Sigmoid", &OnnxParserImpl::ParseSigmoid },
453 { "Tanh", &OnnxParserImpl::ParseTanh },
454 { "Relu", &OnnxParserImpl::ParseRelu },
455 { "LeakyRelu", &OnnxParserImpl::ParseLeakyRelu },
456 { "Conv", &OnnxParserImpl::ParseConv },
457 { "Add", &OnnxParserImpl::ParseAdd },
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +0100458 { "Flatten", &OnnxParserImpl::ParseFlatten },
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100459 { "Shape", &OnnxParserImpl::ParseShape },
460 { "Gather", &OnnxParserImpl::ParseGather },
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +0100461 { "Unsqueeze", &OnnxParserImpl::ParseUnsqueeze },
Narumol Prangnawarat1112b012021-09-30 12:10:50 +0100462 { "Concat", &OnnxParserImpl::ParseConcat },
463 { "Gemm", &OnnxParserImpl::ParseGemm }
telsoa01c577f2c2018-08-31 09:22:23 +0100464};
465
466template<typename TypePair, typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000467void OnnxParserImpl::ValidateInputs(const onnx::NodeProto& node,
telsoa01c577f2c2018-08-31 09:22:23 +0100468 TypePair validInputs,
469 const Location& location)
470{
471 for(auto input : node.input())
472 {
473 CheckValidDataType(validInputs.second,
474 m_TensorsInfo[input].m_dtype,
475 validInputs.first,
476 node.name(),
477 input,
478 location);
479 }
480}
481
482#define VALID_INPUTS(NODE, VALID_INPUTS) \
Kevin Mayef33cb12021-01-29 14:24:57 +0000483 OnnxParserImpl::ValidateInputs(NODE, \
telsoa01c577f2c2018-08-31 09:22:23 +0100484 VALID_INPUTS, \
485 CHECK_LOCATION())
486
Kevin Mayef33cb12021-01-29 14:24:57 +0000487std::vector<TensorInfo> OnnxParserImpl::ComputeOutputInfo(std::vector<std::string> outNames,
488 const IConnectableLayer* layer,
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100489 std::vector<TensorShape> inputShapes,
490 const onnx::TensorProto::DataType& dataType)
telsoa01c577f2c2018-08-31 09:22:23 +0100491{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100492 ARMNN_ASSERT(! outNames.empty());
telsoa01c577f2c2018-08-31 09:22:23 +0100493 bool needCompute = std::any_of(outNames.begin(),
494 outNames.end(),
495 [this](std::string name)
496 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100497 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr
498 || m_TensorsInfo[name].m_info->GetShape().GetDimensionality() ==
499 Dimensionality::NotSpecified);
telsoa01c577f2c2018-08-31 09:22:23 +0100500 });
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100501 std::vector<TensorInfo> outInfo;
502 //if the output info(s) are not here, we need to compute them
503 std::vector<TensorShape> inferredShapes;
504 DataType armnnType = DataType::Float32;
505 if(needCompute) {
506 inferredShapes = layer->InferOutputShapes(inputShapes);
507 ARMNN_ASSERT(inferredShapes.size() == outNames.size());
508 switch (dataType) {
509 case onnx::TensorProto::FLOAT: {
510 armnnType = DataType::Float32;
511 break;
512 }
513 case onnx::TensorProto::INT32:
514 case onnx::TensorProto::INT64: {
515 armnnType = DataType::Signed32;
516 break;
517 }
518 default: {
519 throw ParseException(
520 fmt::format("'{}' is not a currently supported datatype for {}."
521 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
522 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(dataType)),
523 layer->GetName(),
524 CHECK_LOCATION().AsString()));
525 }
526 }
527 }
528 for (uint i = 0; i < outNames.size(); ++i)
529 {
530 if(needCompute)
531 {
532 m_TensorsInfo[outNames[i]] = OnnxTensor();
533 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
534 TensorInfo(inferredShapes[i], armnnType));
535 m_TensorsInfo[outNames[i]].m_dtype = dataType;
536 }
telsoa01c577f2c2018-08-31 09:22:23 +0100537 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100538 }
539 return outInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100540}
541
Kevin Mayef33cb12021-01-29 14:24:57 +0000542OnnxParserImpl::OnnxParserImpl()
telsoa01c577f2c2018-08-31 09:22:23 +0100543 : m_Network(nullptr, nullptr)
544{
545}
546
Kevin Mayef33cb12021-01-29 14:24:57 +0000547void OnnxParserImpl::ResetParser()
telsoa01c577f2c2018-08-31 09:22:23 +0100548{
549 m_Network = armnn::INetworkPtr(nullptr, nullptr);
550 m_Graph = nullptr;
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100551 m_InputInfos.clear();
552 m_OutputInfos.clear();
telsoa01c577f2c2018-08-31 09:22:23 +0100553}
554
Kevin Mayef33cb12021-01-29 14:24:57 +0000555void OnnxParserImpl::Cleanup()
telsoa01c577f2c2018-08-31 09:22:23 +0100556{
557 m_TensorConnections.clear();
558 m_TensorsInfo.clear();
559 m_OutputsMap.clear();
560 m_OutputsFusedAndUsed.clear();
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100561 m_InputShapes.clear();
telsoa01c577f2c2018-08-31 09:22:23 +0100562}
563
Jan Eilers53ef7952021-06-02 12:01:25 +0100564template<typename T>
565std::pair<armnn::ConstTensor, std::unique_ptr<T[]>>
566CreateConstTensorImpl(const T* bufferPtr,
567 armnn::TensorInfo& tensorInfo,
568 const armnn::Optional<armnn::PermutationVector&> permutationVector)
telsoa01c577f2c2018-08-31 09:22:23 +0100569{
Jan Eilers53ef7952021-06-02 12:01:25 +0100570 ARMNN_ASSERT_MSG(bufferPtr != nullptr, fmt::format("Buffer for permutation is null").c_str());
571
572 std::unique_ptr<T[]> data(new T[tensorInfo.GetNumElements()]);
573
574 if (permutationVector.has_value() && permutationVector.value().GetSize() > 0)
575 {
576 tensorInfo = armnnUtils::Permuted(tensorInfo, permutationVector.value());
577 armnnUtils::Permute(tensorInfo.GetShape(), permutationVector.value(),
578 reinterpret_cast<const T*>(bufferPtr), data.get(), sizeof(T));
579 }
580 else
581 {
582 ::memcpy(data.get(), bufferPtr, tensorInfo.GetNumBytes());
583 }
584
585 return std::make_pair(ConstTensor(tensorInfo, data.get()), std::move(data));
586}
587
588std::pair<ConstTensor, std::unique_ptr<float[]>>
589OnnxParserImpl::CreateConstTensor(const std::string name,
590 armnn::Optional<armnn::PermutationVector&> permutationVector)
591{
592 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
telsoa01c577f2c2018-08-31 09:22:23 +0100593 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
594
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100595 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
596 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
597 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
598
Matthew Sloyan81beae32021-07-13 19:46:11 +0100599 // Makes sure IsConstant flag is set.
600 tensorInfo.SetConstant();
601
Jan Eilers53ef7952021-06-02 12:01:25 +0100602 // Const tensors requires at least a list of values
603 if (tensorInfo.GetNumElements() == 0)
604 {
605 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
606 name,
607 CHECK_LOCATION().AsString()));
608 }
609
telsoa01c577f2c2018-08-31 09:22:23 +0100610 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100611 // Copy the value list entries into the destination
612 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100613 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100614 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
615 {
James Ward58dec6b2020-09-11 17:32:44 +0100616 throw ParseException(
617 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
618 "elements ({}) {}",
619 onnxTensor.float_data_size(),
620 name,
621 tensorInfo.GetNumElements(),
622 CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100623 }
Jan Eilers53ef7952021-06-02 12:01:25 +0100624 return CreateConstTensorImpl<float>(srcData, tensorInfo, permutationVector);
telsoa01c577f2c2018-08-31 09:22:23 +0100625 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100626 else
627 {
Jan Eilers53ef7952021-06-02 12:01:25 +0100628 return CreateConstTensorImpl<float>(reinterpret_cast<const float*>(onnxTensor.raw_data().c_str()),
629 tensorInfo,
630 permutationVector);
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100631 }
telsoa01c577f2c2018-08-31 09:22:23 +0100632}
633
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100634std::pair<ConstTensor, std::unique_ptr<int32_t[]>>
635OnnxParserImpl::CreateInt64ConstTensor(const std::string name,
636 armnn::Optional<armnn::PermutationVector&> permutationVector)
637{
638 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
639 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
640
641 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
642 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::INT64);
643
644 // Makes sure IsConstant flag is set.
645 tensorInfo.SetConstant();
646 uint numElements = tensorInfo.GetNumElements();
647
648 // Const tensors requires at least a list of values
649 if (numElements == 0)
650 {
651 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
652 name,
653 CHECK_LOCATION().AsString()));
654 }
655
656 // Copy the value list entries into the destination
657 if (!onnxTensor.has_raw_data())
658 {
659 auto srcData = onnxTensor.int64_data().data();
660 if(numElements != static_cast<uint>(onnxTensor.int64_data_size()))
661 {
662 throw ParseException(
663 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
664 "elements ({}) {}",
665 onnxTensor.int64_data_size(),
666 name,
667 tensorInfo.GetNumElements(),
668 CHECK_LOCATION().AsString()));
669 }
670
671 std::vector<int32_t> int32Data;
672 for(uint i = 0; i < numElements; i++)
673 {
674 int32_t int32Value = CHECKED_INT32(srcData[i]);
675 int32Data.push_back(int32Value);
676 }
677
678 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
679 }
680 else
681 {
682 auto srcData = reinterpret_cast<const int64_t*>(onnxTensor.raw_data().c_str());
683 std::vector<int32_t> int32Data;
684 for(uint i = 0; i < numElements; i++)
685 {
686 int32_t int32Value = CHECKED_INT32(srcData[i]);
687 int32Data.push_back(int32Value);
688 }
689 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
690 }
691}
692
Kevin Mayef33cb12021-01-29 14:24:57 +0000693ModelPtr OnnxParserImpl::LoadModelFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100694{
695 FILE* fd = fopen(graphFile, "r");
696
697 if (fd == nullptr)
698 {
James Ward58dec6b2020-09-11 17:32:44 +0100699 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100700 }
701
702 // Parse the file into a message
703 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
704 using google::protobuf::io::FileInputStream;
705 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
706 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
707 fclose(fd);
708
709 if (!success)
710 {
711 std::stringstream error;
712 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100713 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100714 }
715 return modelProto;
716}
717
Kevin Mayef33cb12021-01-29 14:24:57 +0000718INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100719{
720 ResetParser();
721 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
722 return CreateNetworkFromModel(*modelProto);
723}
724
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100725INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile,
726 const std::map<std::string, armnn::TensorShape>& inputShapes)
727{
728 ResetParser();
729 m_InputShapes = inputShapes;
730 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
731 return CreateNetworkFromModel(*modelProto);
732}
telsoa01c577f2c2018-08-31 09:22:23 +0100733
Kevin Mayef33cb12021-01-29 14:24:57 +0000734ModelPtr OnnxParserImpl::LoadModelFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100735{
736 FILE* fd = fopen(graphFile, "rb");
737
738 if (fd == nullptr)
739 {
James Ward58dec6b2020-09-11 17:32:44 +0100740 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100741 }
742
743 // Parse the file into a message
744 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
745
746 google::protobuf::io::FileInputStream inStream(fileno(fd));
747 google::protobuf::io::CodedInputStream codedStream(&inStream);
Nikhil Raje5181532020-10-09 14:52:25 +0100748 codedStream.SetTotalBytesLimit(INT_MAX);
telsoa01c577f2c2018-08-31 09:22:23 +0100749 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
750 fclose(fd);
751
752 if (!success)
753 {
754 std::stringstream error;
755 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100756 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100757 }
758 return modelProto;
759
760}
761
Kevin Mayef33cb12021-01-29 14:24:57 +0000762INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100763{
764 ResetParser();
765 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
766 return CreateNetworkFromModel(*modelProto);
767}
768
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100769INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile,
770 const std::map<std::string, armnn::TensorShape>& inputShapes)
771{
772 ResetParser();
773 m_InputShapes = inputShapes;
774 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
775 return CreateNetworkFromModel(*modelProto);
776}
777
Kevin Mayef33cb12021-01-29 14:24:57 +0000778ModelPtr OnnxParserImpl::LoadModelFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100779{
780 if (protoText == "")
781 {
James Ward58dec6b2020-09-11 17:32:44 +0100782 throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
783 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100784 }
785 // Parse the string into a message
786 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
787 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
788 if (!success)
789 {
790 std::stringstream error;
791 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100792 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100793 }
794 return modelProto;
795}
796
Kevin Mayef33cb12021-01-29 14:24:57 +0000797INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100798{
799 ResetParser();
800 ModelPtr modelProto = LoadModelFromString(protoText);
801 return CreateNetworkFromModel(*modelProto);
802}
803
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100804INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText,
805 const std::map<std::string, armnn::TensorShape>& inputShapes)
806{
807 ResetParser();
808 m_InputShapes = inputShapes;
809 ModelPtr modelProto = LoadModelFromString(protoText);
810 return CreateNetworkFromModel(*modelProto);
811}
812
Kevin Mayef33cb12021-01-29 14:24:57 +0000813INetworkPtr OnnxParserImpl::CreateNetworkFromModel(onnx::ModelProto& model)
telsoa01c577f2c2018-08-31 09:22:23 +0100814{
815 m_Network = INetwork::Create();
816 try
817 {
818 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
819 LoadGraph();
820 }
821 catch (const ParseException& e)
822 {
823 Cleanup();
824 throw e;
825 }
826 Cleanup();
827 return std::move(m_Network);
828}
829
Kevin Mayef33cb12021-01-29 14:24:57 +0000830void OnnxParserImpl::LoadGraph()
telsoa01c577f2c2018-08-31 09:22:23 +0100831{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100832 ARMNN_ASSERT(m_Graph.get() != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100833
834 //Fill m_TensorsInfo with the shapes and value of every tensor
835 SetupInfo(m_Graph->mutable_output());
836 SetupInfo(m_Graph->mutable_input());
837 SetupInfo(m_Graph->mutable_value_info());
838
839 for (auto tensor : m_Graph->initializer())
840 {
841 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000842 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
843 m_TensorsInfo[tensor.name()].m_dtype =
844 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100845 }
846
847 SetupInputLayers();
848 SetupOutputLayers();
849
850 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
851 DetectFullyConnected();
852
853 //Parsing the graph
854 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
855 {
856 auto node = m_Graph->node(static_cast<int>(nodeIndex));
857 const std::string& operation = node.op_type();
858
859 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000860 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100861 {
862 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
863 {
864 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
865 AddFullyConnected(node);
866 }
867 }
868 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
869 {
870 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
871 AddFullyConnected(m_Graph->node(matmulIndex), &node);
872 }
873 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
874 {
875 auto it = m_ParserFunctions.find(operation);
876 if (it != m_ParserFunctions.end())
877 {
878 auto func = it->second;
879 (this->*func)(node);
880 }
881 else
882 {
James Ward58dec6b2020-09-11 17:32:44 +0100883 throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
884 operation,
885 node.name(),
886 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100887 }
888 }
889 }
890
891 //Making the connections between outputs and inputs of each layers
892 for (const auto& tensorCon : m_TensorConnections)
893 {
894 if (tensorCon.second.outputSlot != nullptr)
895 {
896 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
897 {
898 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
899 }
900 }
901 }
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100902
903 // Get output info.
904 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
905 {
906 auto output = m_Graph->output(outputIndex);
907 m_OutputInfos[output.name()] = *m_TensorsInfo[output.name()].m_info;
908 }
telsoa01c577f2c2018-08-31 09:22:23 +0100909}
910
Kevin Mayef33cb12021-01-29 14:24:57 +0000911void OnnxParserImpl::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
telsoa01c577f2c2018-08-31 09:22:23 +0100912{
913 for (auto tensor : *list)
914 {
915 m_TensorsInfo[tensor.name()] = OnnxTensor();
916 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000917 m_TensorsInfo[tensor.name()].m_dtype =
918 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100919 }
920}
921
Kevin Mayef33cb12021-01-29 14:24:57 +0000922void OnnxParserImpl::DetectFullyConnected()
telsoa01c577f2c2018-08-31 09:22:23 +0100923{
924 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
925 auto matmulAndConstant = [&](const std::string& constInput,
926 const std::string& matmulInput,
927 int& nodeIndex)
928 {
929 auto matmulIt = m_OutputsMap.find(matmulInput);
930 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
931 && m_TensorsInfo[constInput].isConstant())
932 {
933 nodeIndex = matmulIt->second.second;
934 return true;
935 }
936 return false;
937 };
938
939 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
940 {
941 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
942 for (const std::string& output : node->output())
943 {
944 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
945 }
946
947 for (const std::string& input : node->input()) //count how many time a node is used as input
948 {
949 auto matmulIt = m_OutputsMap.find(input);
950 if(matmulIt != m_OutputsMap.end()){
951 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
952 }
953 }
954
955 if (node->op_type() == "Add")
956 {
957 int matmulIndex = 0;
958 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
959 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
960 {
961 //matmul and add were fused
962 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
963 .push_back(static_cast<size_t>(nodeIndex));
964
965 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
966 .push_back(static_cast<size_t>(matmulIndex));
967 }
968 }
969 }
970
971 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
972 auto matmulIt = m_OutputsMap.find(output.name());
973 if(matmulIt != m_OutputsMap.end()){
974 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
975 }
976 }
977}
978
979template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000980void OnnxParserImpl::GetInputAndParam(const onnx::NodeProto& node,
981 std::string* inputName,
982 std::string* constName,
983 const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +0100984{
985 int cstIndex;
986 if (m_TensorsInfo[node.input(0)].isConstant())
987 {
988 cstIndex = 0;
989 }
990 else if (m_TensorsInfo[node.input(1)].isConstant())
991 {
992 cstIndex = 1;
993 }
994 else
995 {
James Ward58dec6b2020-09-11 17:32:44 +0100996 throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
997 node.input(0),
998 node.input(1),
999 node.name(),
1000 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001001 }
1002 if(constName)
1003 {
1004 *constName = node.input(cstIndex);
1005 }
1006 if(inputName)
1007 {
1008 *inputName = node.input(!cstIndex);
1009 }
1010}
1011
1012template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +00001013void OnnxParserImpl::To1DTensor(const std::string& name, const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +01001014{
1015 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
1016 std::vector<uint32_t> newShape;
1017 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
1018 {
1019 if(shape[i] != 1)
1020 {
James Ward58dec6b2020-09-11 17:32:44 +01001021 throw ParseException(
1022 fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
1023 TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
1024 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001025 }
1026 }
1027 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
1028
1029 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1030}
1031
Kevin Mayef33cb12021-01-29 14:24:57 +00001032void OnnxParserImpl::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001033{
1034 ARMNN_ASSERT(node.op_type() == "Conv");
1035
1036 DepthwiseConvolution2dDescriptor desc;
1037 desc.m_PadLeft = convDesc.m_PadLeft;
1038 desc.m_PadRight = convDesc.m_PadRight;
1039 desc.m_PadTop = convDesc.m_PadTop;
1040 desc.m_PadBottom = convDesc.m_PadBottom;
1041 desc.m_StrideX = convDesc.m_StrideX;
1042 desc.m_StrideY = convDesc.m_StrideY;
1043 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
1044
Cathal Corbett06902652022-04-14 17:55:11 +01001045 armnn::IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, node.name().c_str());
Cathal Corbett541880f2022-05-16 15:20:56 +01001046 std::string permuteStr = "permute_" + node.input(1);
1047 std::vector<std::string> tensorIndexes= {node.input(0), permuteStr};
Jan Eilers53ef7952021-06-02 12:01:25 +01001048
Cathal Corbett541880f2022-05-16 15:20:56 +01001049 auto weightTensor = CreateConstTensor(node.input(1));
Cathal Corbett06902652022-04-14 17:55:11 +01001050 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
Cathal Corbett541880f2022-05-16 15:20:56 +01001051
1052 // weights come in as [O,1,H,W] from ONNX and need to be converted to ArmNNs depthwise weights layout [1,H,W,O]
1053 armnn::PermutationVector perVec {3, 0, 1, 2};
1054 TensorInfo weightsPermuted = armnnUtils::Permuted(weightTensor.first.GetInfo(), perVec);
1055
1056 // Inserts NewLayer so layers don't need to be re-sorted.
1057 IConnectableLayer* permuteLayer = m_Network->AddPermuteLayer(PermuteDescriptor(perVec),
1058 "permute_layer");
1059 permuteLayer->GetOutputSlot(0).SetTensorInfo(weightsPermuted);
1060 permuteLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1061
Cathal Corbett06902652022-04-14 17:55:11 +01001062 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
Cathal Corbett541880f2022-05-16 15:20:56 +01001063 weightsLayer->GetOutputSlot(0).Connect(permuteLayer->GetInputSlot(0u));
Cathal Corbett06902652022-04-14 17:55:11 +01001064
Ryan OSheaed27ee72020-04-22 16:37:29 +01001065 if (node.input_size() == 3)
1066 {
1067 if(!m_TensorsInfo[node.input(2)].isConstant())
1068 {
James Ward58dec6b2020-09-11 17:32:44 +01001069 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1070 node.input(2),
1071 node.name(),
1072 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001073 }
Cathal Corbett06902652022-04-14 17:55:11 +01001074
Ryan OSheaed27ee72020-04-22 16:37:29 +01001075 desc.m_BiasEnabled = true;
1076 auto biasTensor = CreateConstTensor(node.input(2));
Cathal Corbett06902652022-04-14 17:55:11 +01001077 tensorIndexes.emplace_back(node.input(2));
1078
1079 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1080 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1081 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001082 }
Cathal Corbett06902652022-04-14 17:55:11 +01001083
Ryan OSheaed27ee72020-04-22 16:37:29 +01001084 ARMNN_ASSERT(layer != nullptr);
1085
1086 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1087 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
Cathal Corbett541880f2022-05-16 15:20:56 +01001088 weightsPermuted.GetShape() });
Ryan OSheaed27ee72020-04-22 16:37:29 +01001089
1090 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1091
1092 // register the input connection slots for the layer, connections are made after all layers have been created
1093 // only the tensors for the inputs are relevant, exclude the const tensors
Cathal Corbett06902652022-04-14 17:55:11 +01001094 RegisterInputSlots(layer, tensorIndexes);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001095
1096 // register the output connection slots for the layer, connections are made after all layers have been created
1097 RegisterOutputSlots(layer, {node.output(0)});
1098}
1099
Kevin Mayef33cb12021-01-29 14:24:57 +00001100void OnnxParserImpl::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
telsoa01c577f2c2018-08-31 09:22:23 +01001101{
1102
1103 // find matmul inputs
1104 std::string weightName;
1105 std::string inputName;
1106 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
1107 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
1108 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
1109
1110 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
1111
1112 FullyConnectedDescriptor desc;
1113 desc.m_BiasEnabled = addNode != nullptr;
1114
1115 IConnectableLayer* layer = nullptr;
1116 if(desc.m_BiasEnabled)
1117 {
1118 // find bias const
1119 std::string biasName;
1120 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
1121 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
1122 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
1123
1124 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
1125
1126 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
1127 To1DTensor(biasName, CHECK_LOCATION());
1128 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1129 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
1130
1131 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
1132 {
James Ward58dec6b2020-09-11 17:32:44 +01001133 throw ParseException(
1134 fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
1135 " and {} ( /!\\ bias should be a 1D tensor) {}",
1136 weightName,
1137 addNode->name(),
1138 TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
1139 m_TensorsInfo[weightName].m_dtype),
1140 TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
1141 m_TensorsInfo[biasName].m_dtype ),
1142 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001143 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01001144
1145 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1146 layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001147 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001148
1149 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
1150 {m_TensorsInfo[inputName].m_info->GetShape(),
1151 m_TensorsInfo[weightName].m_info->GetShape()});
telsoa01c577f2c2018-08-31 09:22:23 +01001152 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1153
Matthew Sloyan81beae32021-07-13 19:46:11 +01001154 // Add constant layer to store weights/biases and connect to FullyConnected layer..
1155 if(m_TensorsInfo[weightName].isConstant())
1156 {
1157 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1158
1159 weightInfo.SetConstant();
1160 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1161 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1162 }
1163
1164 if(m_TensorsInfo[biasName].isConstant())
1165 {
1166 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(biasName).first);
1167
1168 biasInfo.SetConstant();
1169 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1170 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1171 }
1172
1173 RegisterInputSlots(layer, {inputName, weightName, biasName});
telsoa01c577f2c2018-08-31 09:22:23 +01001174 RegisterOutputSlots(layer, {addNode->output(0)});
1175 }
1176 else
1177 {
Matthew Sloyan81beae32021-07-13 19:46:11 +01001178 layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001179 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001180
1181 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
1182 {m_TensorsInfo[inputName].m_info->GetShape(),
1183 m_TensorsInfo[weightName].m_info->GetShape()});
1184 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1185
Matthew Sloyan81beae32021-07-13 19:46:11 +01001186 // Add constant layer to store weights and connect to FullyConnected layer.
1187 if(m_TensorsInfo[weightName].isConstant())
1188 {
1189 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1190 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1191
1192 weightInfo.SetConstant();
1193 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1194 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1195 }
1196
1197 RegisterInputSlots(layer, {inputName, weightName});
telsoa01c577f2c2018-08-31 09:22:23 +01001198 RegisterOutputSlots(layer, {matmulNode.output(0)});
1199 }
1200}
1201
Kevin Mayef33cb12021-01-29 14:24:57 +00001202void OnnxParserImpl::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
telsoa01c577f2c2018-08-31 09:22:23 +01001203{
1204
1205 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1206 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1207
1208 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1209
1210 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
1211 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1212 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1213
1214 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
1215 desc.m_PoolWidth = kernel_shape[1];
1216 desc.m_PoolHeight = kernel_shape[0];
1217
1218 if(strides.empty())
1219 {
1220 desc.m_StrideX = 1;
1221 desc.m_StrideY = 1;
1222 }
1223 else
1224 {
1225 desc.m_StrideX = strides[1];
1226 desc.m_StrideY = strides[0];
1227 }
1228
1229 //Check new padding version first
1230 if(pads.empty())
1231 {
1232 //Check deprecated version
1233 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1234 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1235 {
1236 bool isUpper;
1237 if( paddingString == "SAME_LOWER")
1238 {
1239 isUpper = false;
1240 }
1241 else if (paddingString == "SAME_UPPER")
1242 {
1243 isUpper = true;
1244 }
1245 else
1246 {
James Ward58dec6b2020-09-11 17:32:44 +01001247 throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
1248 "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
1249 node.name(),
1250 paddingString,
1251 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001252 }
1253 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1254 uint32_t inputHeight = inputInfo.GetShape()[2];
1255 uint32_t inputWidth = inputInfo.GetShape()[3];
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001256 CalcPadding(inputHeight,
1257 desc.m_PoolHeight,
1258 desc.m_StrideY,
1259 1u,
1260 &desc.m_PadTop,
1261 &desc.m_PadBottom,
1262 isUpper);
1263 CalcPadding(inputWidth,
1264 desc.m_PoolWidth,
1265 desc.m_StrideX,
1266 1u,
1267 &desc.m_PadLeft,
1268 &desc.m_PadRight,
1269 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001270 }
1271 }
1272 else
1273 {
1274 desc.m_PadTop = pads[0];
1275 desc.m_PadLeft = pads[1];
1276 desc.m_PadBottom = pads[2];
1277 desc.m_PadRight = pads[3];
1278 }
1279
1280 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001281 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001282
1283 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1284 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1285
1286 // register the input connection slots for the layer, connections are made after all layers have been created
1287 // only the tensors for the inputs are relevant, exclude the const tensors
1288 RegisterInputSlots(layer, {node.input(0)});
1289
1290 // register the output connection slots for the layer, connections are made after all layers have been created
1291 RegisterOutputSlots(layer, {node.output(0)});
1292}
1293
Kevin Mayef33cb12021-01-29 14:24:57 +00001294std::pair<std::string, std::string> OnnxParserImpl::AddPrepareBroadcast(const std::string& input0,
1295 const std::string& input1)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001296{
1297 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1298
1299 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1300 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1301
1302 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1303 {
James Ward58dec6b2020-09-11 17:32:44 +01001304 auto outputName = fmt::format("reshape_output_{}", input1);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001305 PrependForBroadcast(outputName, input1, input0);
1306 inputs.second = outputName;
1307 }
1308 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1309 {
James Ward58dec6b2020-09-11 17:32:44 +01001310 auto outputName = fmt::format("reshape_output_{}", input0);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001311 PrependForBroadcast(outputName, input0, input1);
1312 inputs.first = outputName;
1313 }
1314 return inputs;
1315}
1316
Kevin Mayef33cb12021-01-29 14:24:57 +00001317void OnnxParserImpl::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001318{
1319 auto armnnTensor = CreateConstTensor(tensorName);
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001320 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1321 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1322 RegisterOutputSlots(layer, {tensorName});
1323}
Ryan OSheaed27ee72020-04-22 16:37:29 +01001324
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001325void OnnxParserImpl::CreateInt64ConstantLayer(const std::string& tensorName, const std::string& layerName)
1326{
1327 auto armnnTensor = CreateInt64ConstTensor(tensorName);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001328 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1329 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1330 RegisterOutputSlots(layer, {tensorName});
1331}
1332
Kevin Mayef33cb12021-01-29 14:24:57 +00001333void OnnxParserImpl::CreateReshapeLayer(const std::string& inputName,
1334 const std::string& outputName,
1335 const std::string& layerName)
telsoa01c577f2c2018-08-31 09:22:23 +01001336{
1337 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1338 ReshapeDescriptor reshapeDesc;
1339 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1340
1341 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001342 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001343 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1344
1345 // register the input connection slots for the layer, connections are made after all layers have been created
1346 // only the tensors for the inputs are relevant, exclude the const tensors
1347 RegisterInputSlots(layer, {inputName});
1348
1349 // register the output connection slots for the layer, connections are made after all layers have been created
1350 RegisterOutputSlots(layer, {outputName});
1351}
1352
Kevin Mayef33cb12021-01-29 14:24:57 +00001353void OnnxParserImpl::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001354{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001355 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001356 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1357
1358 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1359
1360 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001361 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001362
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001363 if (func == ActivationFunction::BoundedReLu)
1364 {
Narumol Prangnawaratf106ab72021-09-15 17:30:37 +01001365 if (node.input_size() == 1 && node.attribute_size() > 0)
1366 {
1367 desc.m_A = ReadOptionalNodeFloatAttribute(node, "max", std::numeric_limits<float>::max());
1368 desc.m_B = ReadOptionalNodeFloatAttribute(node, "min", std::numeric_limits<float>::lowest());
1369 }
1370 else
1371 {
1372 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1373 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1374 }
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001375 }
1376
telsoa01c577f2c2018-08-31 09:22:23 +01001377 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001378 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001379
1380 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1381 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1382
1383 // register the input connection slots for the layer, connections are made after all layers have been created
1384 // only the tensors for the inputs are relevant, exclude the const tensors
1385 RegisterInputSlots(layer, {node.input(0)});
1386
1387 // register the output connection slots for the layer, connections are made after all layers have been created
1388 RegisterOutputSlots(layer, {node.output(0)});
1389}
1390
Kevin Mayef33cb12021-01-29 14:24:57 +00001391void OnnxParserImpl::ParseClip(const onnx::NodeProto& node)
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001392{
1393 ParseActivation(node, ActivationFunction::BoundedReLu);
1394}
1395
Kevin Mayef33cb12021-01-29 14:24:57 +00001396void OnnxParserImpl::ParseSigmoid(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001397{
1398 ParseActivation(node, ActivationFunction::Sigmoid);
1399}
1400
Kevin Mayef33cb12021-01-29 14:24:57 +00001401void OnnxParserImpl::ParseTanh(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001402{
1403 ParseActivation(node, ActivationFunction::TanH);
1404}
1405
Kevin Mayef33cb12021-01-29 14:24:57 +00001406void OnnxParserImpl::ParseRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001407{
1408 ParseActivation(node, ActivationFunction::ReLu);
1409}
1410
Kevin Mayef33cb12021-01-29 14:24:57 +00001411void OnnxParserImpl::ParseLeakyRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001412{
1413 ParseActivation(node, ActivationFunction::LeakyReLu);
1414}
telsoa01c577f2c2018-08-31 09:22:23 +01001415
Kevin Mayef33cb12021-01-29 14:24:57 +00001416void OnnxParserImpl::ParseAdd(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001417{
Ryan OSheaed27ee72020-04-22 16:37:29 +01001418 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1419 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +01001420
Ryan OSheaed27ee72020-04-22 16:37:29 +01001421 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
telsoa01c577f2c2018-08-31 09:22:23 +01001422
Ryan OSheaed27ee72020-04-22 16:37:29 +01001423 // TODO: unify broadcast validation code across layers
1424 // tracked by: IVGCVSW-1576
telsoa01c577f2c2018-08-31 09:22:23 +01001425
Ryan OSheaed27ee72020-04-22 16:37:29 +01001426 // Checking broadcast compatibility : only scalar or 1D tensors
1427 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1428 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1429 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1430 ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1431
1432 unsigned int numDims = input0.GetNumDimensions();
1433 for (unsigned int i = 0; i < numDims; i++)
telsoa01c577f2c2018-08-31 09:22:23 +01001434 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01001435 unsigned int dim0 = input0.GetShape()[i];
1436 unsigned int dim1 = input1.GetShape()[i];
1437 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
telsoa01c577f2c2018-08-31 09:22:23 +01001438 {
James Ward58dec6b2020-09-11 17:32:44 +01001439 throw ParseException(
1440 fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1441 "Input dimensions should either match or one should be of size 1 and here, "
1442 "{} and {} {}",
1443 node.name(),
1444 TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1445 m_TensorsInfo[inputs.first].m_dtype),
1446 TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1447 m_TensorsInfo[inputs.second].m_dtype),
1448 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001449 }
telsoa01c577f2c2018-08-31 09:22:23 +01001450 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001451
1452
1453 IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001454 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001455
1456 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
Ryan OSheaed27ee72020-04-22 16:37:29 +01001457 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1458 m_TensorsInfo[inputs.second].m_info->GetShape() });
telsoa01c577f2c2018-08-31 09:22:23 +01001459 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1460
Ryan OSheaed27ee72020-04-22 16:37:29 +01001461 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1462 if(m_TensorsInfo[inputs.first].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001463 CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001464 }
1465 if(m_TensorsInfo[inputs.second].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001466 CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001467 }
1468 RegisterInputSlots(layer, {inputs.first, inputs.second});
telsoa01c577f2c2018-08-31 09:22:23 +01001469
Ryan OSheaed27ee72020-04-22 16:37:29 +01001470 // register the output connection
telsoa01c577f2c2018-08-31 09:22:23 +01001471 RegisterOutputSlots(layer, {node.output(0)});
1472}
1473
Kevin Mayef33cb12021-01-29 14:24:57 +00001474void OnnxParserImpl::ParseAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001475{
1476 Pooling2dDescriptor desc;
1477 desc.m_PoolType = PoolingAlgorithm::Average;
1478
1479 uint32_t count_include_pad = 0;
1480 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1481 if(count_include_pad) {
1482 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1483 }
1484 AddPoolingLayer(node, desc);
1485}
1486
Kevin Mayef33cb12021-01-29 14:24:57 +00001487void OnnxParserImpl::ParseBatchNormalization(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001488{
1489 //IGNORE momentum parameter and spatial parameters
1490
1491 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1492 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1493
1494 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1495 for(int ind = 1; ind < node.input_size(); ++ind)
1496 {
1497 auto tensor = node.input(ind);
1498 if(! m_TensorsInfo[tensor].isConstant())
1499 {
James Ward58dec6b2020-09-11 17:32:44 +01001500 throw ParseException(
1501 fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1502 tensor,
1503 node.name(),
1504 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001505 }
1506 }
1507
1508 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1509 BatchNormalizationDescriptor desc;
1510 desc.m_Eps = epsilon;
1511
1512 auto scaleTensor = CreateConstTensor(node.input(1));
1513 auto biasTensor = CreateConstTensor(node.input(2));
1514 auto meanTensor = CreateConstTensor(node.input(3));
1515 auto varTensor = CreateConstTensor(node.input(4));
1516
1517 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1518 meanTensor.first,
1519 varTensor.first,
1520 biasTensor.first,
1521 scaleTensor.first,
1522 node.name().c_str());
1523 ARMNN_ASSERT(layer != nullptr);
1524
1525 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1526 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1527
1528 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1529
1530 // register the output connection
1531 RegisterOutputSlots(layer, {node.output(0)});
1532}
1533
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001534void OnnxParserImpl::ParseConcat(const onnx::NodeProto& node)
1535{
1536 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1537
1538 uint32_t numConcatView = static_cast<uint32_t>(node.input_size());
1539 uint32_t inputRank = m_TensorsInfo[node.input(0)].m_info->GetNumDimensions();
1540
1541 int axisInt = ReadMandatoryNodeIntAttribute(node, "axis");
1542
1543 unsigned int concatDimInput = static_cast<unsigned int>(
1544 (static_cast<int>(inputRank) + axisInt) % static_cast<int>(inputRank));
1545
1546 OriginsDescriptor concatDescriptor(numConcatView, inputRank);
1547 concatDescriptor.SetConcatAxis(concatDimInput);
1548
1549 unsigned int mergeDimOrigin = 0;
1550
1551 std::vector<TensorShape> inputShapes;
1552 std::vector<std::string> tensorIds;
1553
1554 for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1555 {
1556 std::string nodeName = node.input(static_cast<int>(viewIndex));
1557 auto inputTensorInfo = *m_TensorsInfo[nodeName].m_info;
1558 inputShapes.push_back(inputTensorInfo.GetShape());
1559 tensorIds.push_back(nodeName);
1560
1561 // Set up concatDescriptor view origin
1562 armnnUtils::ProcessConcatInputTensorInfo(
1563 inputTensorInfo, concatDescriptor, concatDimInput, viewIndex, mergeDimOrigin);
1564 }
1565
1566 IConnectableLayer* layer = m_Network->AddConcatLayer(concatDescriptor, node.name().c_str());
1567 ARMNN_ASSERT(layer != nullptr);
1568
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01001569 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, inputShapes,
1570 m_TensorsInfo[node.input(0)].m_dtype);
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001571
1572 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1573
1574 // register the input connection slots for the layer, connections are made after all layers have been created
1575 RegisterInputSlots(layer, tensorIds);
1576
1577 // register the output connection slots for the layer, connections are made after all layers have been created
1578 RegisterOutputSlots(layer, { node.output(0) });
1579}
1580
Kevin Mayef33cb12021-01-29 14:24:57 +00001581void OnnxParserImpl::ParseConstant(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001582{
1583 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1584 if (!node.attribute(0).has_t())
1585 {
James Ward58dec6b2020-09-11 17:32:44 +01001586 throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1587 node.name(),
1588 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001589 }
1590 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1591
Ryan OSheaed27ee72020-04-22 16:37:29 +01001592 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1593 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1594 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1595 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1596
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001597 if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_FLOAT)
1598 {
1599 CreateConstantLayer(node.output(0), node.name());
1600 }
1601 else if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_INT64)
1602 {
1603 CreateInt64ConstantLayer(node.output(0), node.name());
1604 }
1605 else
1606 {
1607 throw ParseException(fmt::format("Data type not support for Constant node '{}' {}",
1608 node.name(),
1609 CHECK_LOCATION().AsString()));
1610 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001611}
1612
Kevin Mayef33cb12021-01-29 14:24:57 +00001613void OnnxParserImpl::ParseConv(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001614{
1615 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1616 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1617
1618 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1619
1620 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1621 {
James Ward58dec6b2020-09-11 17:32:44 +01001622 throw ParseException(
1623 fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1624 node.name(),
1625 TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1626 m_TensorsInfo[node.input(0)].m_dtype),
1627 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001628 }
1629
1630 if(!m_TensorsInfo[node.input(1)].isConstant())
1631 {
James Ward58dec6b2020-09-11 17:32:44 +01001632 throw ParseException(
1633 fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1634 node.input(1),
1635 node.name(),
1636 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001637 }
1638
1639 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1640
telsoa01c577f2c2018-08-31 09:22:23 +01001641 Convolution2dDescriptor desc;
1642 desc.m_BiasEnabled = false;
1643
1644 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1645 if(strides.empty())
1646 {
1647 desc.m_StrideX = 1;
1648 desc.m_StrideY = 1;
1649 }
1650 else
1651 {
1652 desc.m_StrideX = strides[1];
1653 desc.m_StrideY = strides[0];
1654 }
1655
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001656 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1657 if(!dilations.empty())
1658 {
1659 desc.m_DilationX = dilations[1];
1660 desc.m_DilationY = dilations[0];
1661 }
1662
telsoa01c577f2c2018-08-31 09:22:23 +01001663 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1664 //Check new padding version first
1665 if(pads.empty())
1666 {
1667 //Check deprecated version
1668 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1669 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1670 {
1671 bool isUpper;
1672 if( paddingString == "SAME_LOWER")
1673 {
1674 isUpper = false;
1675 }
1676 else if (paddingString == "SAME_UPPER")
1677 {
1678 isUpper = true;
1679 }
1680 else
1681 {
James Ward58dec6b2020-09-11 17:32:44 +01001682 throw ParseException(
1683 fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1684 "supported and found {} {}",
1685 node.name(),
1686 paddingString,
1687 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001688 }
1689 uint32_t inputHeight = inputInfo.GetShape()[2];
1690 uint32_t inputWidth = inputInfo.GetShape()[3];
1691
1692 uint32_t weightHeight;
1693 uint32_t weightWidth;
1694 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1695 if (kernel_shape.empty())
1696 {
1697 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1698 weightHeight = weightTensorInfo.GetShape()[2];
1699 weightWidth = weightTensorInfo.GetShape()[3];
1700 }
1701 else
1702 {
1703 weightHeight = kernel_shape[0];
1704 weightWidth = kernel_shape[1];
1705 }
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001706 CalcPadding(inputHeight,
1707 weightHeight,
1708 desc.m_StrideY,
1709 desc.m_DilationY,
1710 &desc.m_PadTop,
1711 &desc.m_PadBottom,
1712 isUpper);
1713 CalcPadding(inputWidth,
1714 weightWidth,
1715 desc.m_StrideX,
1716 desc.m_DilationX,
1717 &desc.m_PadLeft,
1718 &desc.m_PadRight,
1719 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001720 }
1721 }
1722 else
1723 {
1724 desc.m_PadTop = pads[0];
1725 desc.m_PadLeft = pads[1];
1726 desc.m_PadBottom = pads[2];
1727 desc.m_PadRight = pads[3];
1728 }
1729
1730 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1731 if(group > 1)
1732 {
1733 if (group > inputInfo.GetShape()[1])
1734 {
1735 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001736 fmt::format("Error parsing Convolution node: {}. "
1737 "The 'group'={} parameter cannot be larger than the "
1738 "channel of the input shape={} (in NCHW format). {}",
1739 node.name(),
1740 group,
1741 inputInfo.GetShape()[1],
1742 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001743 }
1744 else if (group == inputInfo.GetShape()[1])
1745 {
1746 // we use a depthwise convolution here, because the number of groups equals to the
1747 // input channels
1748 AddConvLayerWithDepthwiseConv(node, desc);
1749 return;
1750 }
1751 else
1752 {
1753 // TODO: split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +01001754 // and concatenate the results afterwards
James Ward58dec6b2020-09-11 17:32:44 +01001755 throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1756 "The 'group'={} parameter should be 1 or be equal to the "
1757 "channel of the input shape={} (in NCHW format). {}",
1758 node.name(),
1759 group,
1760 inputInfo.GetShape()[1],
1761 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001762 }
1763 }
1764
Keith Davis721e6292022-05-17 10:06:53 +01001765 node.input_size() == 3 ? desc.m_BiasEnabled = true : desc.m_BiasEnabled = false;
1766 armnn::IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, node.name().c_str());
Keith Davisb4dd5cc2022-04-07 11:32:00 +01001767 std::vector<std::string> tensorIndexes= {node.input(0), node.input(1)};
1768
telsoa01c577f2c2018-08-31 09:22:23 +01001769 auto weightTensor = CreateConstTensor(node.input(1));
1770
Keith Davis721e6292022-05-17 10:06:53 +01001771 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
1772 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
1773 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1774
telsoa01c577f2c2018-08-31 09:22:23 +01001775 if (node.input_size() == 3)
1776 {
1777 if(!m_TensorsInfo[node.input(2)].isConstant())
1778 {
James Ward58dec6b2020-09-11 17:32:44 +01001779 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1780 node.input(2),
1781 node.name(),
1782 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001783 }
1784 desc.m_BiasEnabled = true;
1785 auto biasTensor = CreateConstTensor(node.input(2));
Keith Davis721e6292022-05-17 10:06:53 +01001786
1787 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1788 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1789 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1790
1791 tensorIndexes.emplace_back(node.input(2));
telsoa01c577f2c2018-08-31 09:22:23 +01001792 }
Keith Davis721e6292022-05-17 10:06:53 +01001793
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001794 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001795
1796 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1797 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1798 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1799 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1800
1801 // register the input connection slots for the layer, connections are made after all layers have been created
1802 // only the tensors for the inputs are relevant, exclude the const tensors
Keith Davisb4dd5cc2022-04-07 11:32:00 +01001803 RegisterInputSlots(layer, tensorIndexes);
telsoa01c577f2c2018-08-31 09:22:23 +01001804
1805 // register the output connection slots for the layer, connections are made after all layers have been created
1806 RegisterOutputSlots(layer, {node.output(0)});
1807}
1808
Kevin Mayef33cb12021-01-29 14:24:57 +00001809void OnnxParserImpl::ParseFlatten(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001810{
1811 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1812 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1813
1814 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1815 m_TensorsInfo[node.input(0)].m_dtype,
1816 onnx::TensorProto::FLOAT);
1817
1818 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1819 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1820
1821 /// Negative axis conversion
1822 if (axis < 0)
1823 {
1824 axis += inputShape.GetNumDimensions();
1825 }
1826
1827 /// Check Axis is within dimensions
1828 if (axis < 0 || axis >= inputShape.GetNumDimensions())
1829 {
James Ward58dec6b2020-09-11 17:32:44 +01001830 throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
1831 axis, inputShape.GetNumDimensions(), node.name()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001832 }
1833
1834 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1835 uint dimension1{1};
1836 uint dimension2{1};
1837 uint i{0};
1838
1839 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1840 for (i = 0; i < axis; i++){
1841 dimension1 *= inputShape[i];
1842 }
1843
1844 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
1845 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
1846 dimension2 *= inputShape[i];
1847 }
1848
1849 TensorShape outputShape{dimension1, dimension2};
1850
1851 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
1852 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1853 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1854}
1855
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001856void OnnxParserImpl::ParseGather(const onnx::NodeProto& node)
1857{
1858 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1859 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1860
1861 armnn::GatherDescriptor gatherDescriptor;
1862 gatherDescriptor.m_Axis = static_cast<int>(ReadOptionalNodeInt64Attribute(node, "axis", 0));
1863
1864 IConnectableLayer* layer = m_Network->AddGatherLayer(gatherDescriptor, node.name().c_str());
1865 ARMNN_ASSERT(layer != nullptr);
1866
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01001867 const TensorShape& inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1868 const TensorShape& indicesShape = m_TensorsInfo[node.input(1)].m_info->GetShape();
1869 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, { inputShape, indicesShape },
1870 m_TensorsInfo[node.input(0)].m_dtype);
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001871 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1872
1873 // register the input connection slots for the layer, connections are made after all layers have been created
1874 RegisterInputSlots(layer, { node.input(0), node.input(1) });
1875
1876 // register the output connection slots for the layer, connections are made after all layers have been created
1877 RegisterOutputSlots(layer, { node.output(0) });
1878}
1879
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01001880void OnnxParserImpl::ParseGemm(const onnx::NodeProto& node)
1881{
1882 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3);
1883 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1884
1885 int transA = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transA", 0));
1886 int transB = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transB", 0));
1887 float alpha = ReadOptionalNodeFloatAttribute(node, "alpha", 1.0);
1888 float beta = ReadOptionalNodeFloatAttribute(node, "beta", 1.0);
1889 bool biasEnabled = node.input_size() == 3;
1890
1891 TensorShape input0Shape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1892 TensorShape input1Shape = m_TensorsInfo[node.input(1)].m_info->GetShape();
1893
1894 // if transB != 0, add transpose to the input1 (tanspose weight matrix in FullyConnected)
1895 armnn::FullyConnectedDescriptor fullyConnectedDescriptor;
1896 fullyConnectedDescriptor.m_BiasEnabled = biasEnabled;
1897 fullyConnectedDescriptor.m_TransposeWeightMatrix = transB;
1898
1899 IConnectableLayer* layer = nullptr;
1900
1901 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1902 layer = m_Network->AddFullyConnectedLayer(fullyConnectedDescriptor, node.name().c_str());
1903 ARMNN_ASSERT(layer != nullptr);
1904
1905 // if transA != 0, add transpose to the input0
1906 if (transA != 0)
1907 {
1908 std::string transAName = "transpose_" + node.input(0);
1909 armnn::TransposeDescriptor transposeADescriptor;
1910 transposeADescriptor.m_DimMappings = { 1, 0 };
1911 IConnectableLayer* transALayer = m_Network->AddTransposeLayer(transposeADescriptor, transAName.c_str());
1912 ARMNN_ASSERT(transALayer != nullptr);
1913 auto transAInfo = ComputeOutputInfo({ transAName }, transALayer, { input0Shape });
1914 transALayer->GetOutputSlot(0).SetTensorInfo(transAInfo[0]);
1915 transALayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
1916 // register the input connection slots for the layer, connections are made after all layers have been created
1917 RegisterInputSlot(transALayer, node.input(0), 0);
1918 input0Shape = transAInfo[0].GetShape();
1919 }
1920 else
1921 {
1922 RegisterInputSlot(layer, node.input(0), 0);
1923 }
1924
1925 // Add constant layer to store weights/biases and connect to FullyConnected layer.
1926 if(m_TensorsInfo[node.input(1)].isConstant())
1927 {
1928 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(1)).first);
1929 TensorInfo weightInfo = *m_TensorsInfo[node.input(1)].m_info;
1930 weightInfo.SetConstant();
1931 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1932
1933 // if alpha != 1, multiply to the weight
1934 if (alpha != 1)
1935 {
1936 std::string activationName = "activation_" + node.input(1);
1937 armnn::ActivationDescriptor activationDescriptor;
1938 activationDescriptor.m_A = alpha;
1939 activationDescriptor.m_Function = ActivationFunction::Linear;
1940 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
1941 ARMNN_ASSERT(actLayer != nullptr);
1942
1943 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { weightInfo.GetShape() });
1944 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
1945 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1946 weightsLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
1947 input1Shape = actInfo[0].GetShape();
1948 }
1949 else
1950 {
1951 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1952 input1Shape = weightInfo.GetShape();
1953 }
1954 }
1955 else
1956 {
1957 // if alpha != 1, multiply to the weight
1958 if (alpha != 1)
1959 {
1960 std::string activationName = "activation_" + node.input(1);
1961 armnn::ActivationDescriptor activationDescriptor;
1962 activationDescriptor.m_A = alpha;
1963 activationDescriptor.m_Function = ActivationFunction::Linear;
1964 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
1965 ARMNN_ASSERT(actLayer != nullptr);
1966
1967 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { input1Shape });
1968 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
1969 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1970 RegisterInputSlot(actLayer, node.input(1), 0);
1971 input1Shape = actInfo[0].GetShape();
1972 }
1973 else
1974 {
1975 RegisterInputSlot(layer, node.input(1), 1);
1976 }
1977 }
1978
1979 if(biasEnabled && m_TensorsInfo[node.input(2)].isConstant())
1980 {
1981 To1DTensor(node.input(2), CHECK_LOCATION());
1982 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(2)).first);
1983 TensorInfo biasInfo = *m_TensorsInfo[node.input(2)].m_info;
1984 biasInfo.SetConstant();
1985 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1986
1987 // if beta != 1, multiply to the bias
1988 if (beta != 1)
1989 {
1990 std::string activationName = "activation_" + node.input(2);
1991 armnn::ActivationDescriptor activationDescriptor;
1992 activationDescriptor.m_A = beta;
1993 activationDescriptor.m_Function = ActivationFunction::Linear;
1994 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
1995 ARMNN_ASSERT(actLayer != nullptr);
1996
1997 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { biasInfo.GetShape() });
1998 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
1999 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2000 biasLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
2001 }
2002 else
2003 {
2004 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2005 }
2006 }
2007 else if (biasEnabled)
2008 {
2009 // Currently we support non-constant tensor of input C (bias) of Gemm when the dimension is 1
2010 if (m_TensorsInfo[node.input(2)].m_info->GetNumDimensions() != 1)
2011 {
2012 throw ParseException(fmt::format("The parser supports constant or non-constant with 1 dimension for "
2013 "Input C of Gemm. Input '{}' in '{}' is not supported '{}'",
2014 node.input(2),
2015 node.name(),
2016 CHECK_LOCATION().AsString()));
2017 }
2018 // if beta != 1, multiply to the bias
2019 if (beta != 1)
2020 {
2021 std::string activationName = "activation_" + node.input(2);
2022 armnn::ActivationDescriptor activationDescriptor;
2023 activationDescriptor.m_A = beta;
2024 activationDescriptor.m_Function = ActivationFunction::Linear;
2025 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
2026 ARMNN_ASSERT(actLayer != nullptr);
2027
2028 auto actInfo = ComputeOutputInfo({ activationName },
2029 actLayer,
2030 { m_TensorsInfo[node.input(2)].m_info->GetShape() });
2031 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2032 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2033 RegisterInputSlot(actLayer, node.input(2), 0);
2034 }
2035 else
2036 {
2037 RegisterInputSlot(layer, node.input(2), 2);
2038 }
2039 }
2040
2041 // Set final output of the FullyConnected layer
2042 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
2043 { input0Shape, input1Shape });
2044 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2045
2046 RegisterOutputSlots(layer, {node.output(0)});
2047}
2048
Kevin Mayef33cb12021-01-29 14:24:57 +00002049void OnnxParserImpl::ParseGlobalAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002050{
2051 Pooling2dDescriptor desc = Pooling2dDescriptor();
2052 desc.m_PoolType = PoolingAlgorithm::Average;
2053
2054 //kernel size is the same as input
2055 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2056 desc.m_PoolWidth = inputShape[3];
2057 desc.m_PoolHeight = inputShape[2];
2058
2059 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
2060 ARMNN_ASSERT(layer != nullptr);
2061
2062 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
2063 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2064
2065 // register the input connection slots for the layer, connections are made after all layers have been created
2066 // only the tensors for the inputs are relevant, exclude the const tensors
2067 RegisterInputSlots(layer, {node.input(0)});
2068
2069 // register the output connection slots for the layer, connections are made after all layers have been created
2070 RegisterOutputSlots(layer, {node.output(0)});
2071}
2072
Kevin Mayef33cb12021-01-29 14:24:57 +00002073void OnnxParserImpl::ParseMaxPool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002074{
2075 Pooling2dDescriptor desc;
2076 desc.m_PoolType = PoolingAlgorithm::Max;
2077 desc.m_PaddingMethod = PaddingMethod::Exclude;
2078 AddPoolingLayer(node, desc);
2079}
2080
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002081void OnnxParserImpl::ParseShape(const onnx::NodeProto& node)
2082{
2083 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
2084 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2085
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002086 IConnectableLayer* layer = m_Network->AddShapeLayer(node.name().c_str());
2087 ARMNN_ASSERT(layer != nullptr);
2088
2089 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002090 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape}, onnx::TensorProto::INT64);
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002091 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2092
2093 // register the input connection slots for the layer, connections are made after all layers have been created
2094 RegisterInputSlots(layer, {node.input(0)});
2095
2096 // register the output connection slots for the layer, connections are made after all layers have been created
2097 RegisterOutputSlots(layer, {node.output(0)});
2098}
2099
Kevin Mayef33cb12021-01-29 14:24:57 +00002100void OnnxParserImpl::ParseReshape(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002101{
2102 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2103 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2104
2105 CHECK_VALID_DATATYPE(node.name(), node.input(0),
2106 m_TensorsInfo[node.input(0)].m_dtype,
2107 onnx::TensorProto::FLOAT); //input
2108 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2109 m_TensorsInfo[node.input(1)].m_dtype,
2110 onnx::TensorProto::INT64); //shape
2111
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002112 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2113
2114 std::vector<unsigned int> targetShape;
2115 if(m_TensorsInfo[node.input(1)].isConstant())
Ryan OSheaed27ee72020-04-22 16:37:29 +01002116 {
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002117 unsigned int dims = static_cast<unsigned int>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2118 targetShape.reserve(dims);
2119
2120 for(uint i = 0; i < dims; i++)
2121 {
2122 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
2123 targetShape[i]= static_cast<unsigned int>(val);
2124 }
2125 }
2126 else
2127 {
2128 // The parser only supports shape (batch, -1) or (-1) for non-constant shape input.
2129 unsigned int dims = m_TensorsInfo[node.input(1)].m_info->GetNumDimensions();
2130 TensorShape shapes = m_TensorsInfo[node.input(1)].m_info->GetShape();
2131 if (dims != 1 || shapes[0] > 2)
2132 {
2133 throw ParseException(fmt::format("Invalid input shape '{}' in Reshape layer '{}' {}",
2134 node.input(1),
2135 node.name(),
2136 CHECK_LOCATION().AsString()));
2137 }
2138
2139 unsigned int numInputElements = m_TensorsInfo[node.input(0)].m_info->GetNumElements();
2140 if (shapes[0] == 1)
2141 {
2142 targetShape = { numInputElements };
2143 }
2144 else if (shapes[0] == 2)
2145 {
2146 targetShape = { inputShape[0] , numInputElements / inputShape[0] };
2147 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01002148 }
2149
2150 if(m_TensorsInfo[node.input(0)].isConstant())
2151 {
2152 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
2153 if(m_TensorsInfo.count(node.output(0)) == 0)
2154 {
2155 m_TensorsInfo[node.output(0)] = OnnxTensor();
2156 }
2157 m_TensorsInfo[node.output(0)].m_tensor =
2158 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
2159 }
2160 else
2161 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01002162 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
2163 {
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002164 auto outInfo = ComputeReshapeInfo(
2165 TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2166 inputShape, node.output(0));
Ryan OSheaed27ee72020-04-22 16:37:29 +01002167 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2168 }
2169
2170 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2171 }
2172}
2173
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002174void OnnxParserImpl::ParseUnsqueeze(const onnx::NodeProto& node)
2175{
2176 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.input_size()), 1, 2);
2177 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.output_size()), 1);
2178
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002179 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2180 std::vector<uint32_t> dims;
2181 if (node.input_size() == 1 && node.attribute_size() > 0)
2182 {
2183 dims = ReadMandatoryNodeUint32ListAttribute(node, "axes");
2184 }
2185 else
2186 {
2187 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2188 m_TensorsInfo[node.input(1)].m_dtype,
2189 onnx::TensorProto::INT64); //axes
2190
2191 auto int64Axes = m_TensorsInfo[node.input(1)].m_tensor->int64_data().data();
2192 uint numDim = armnn::numeric_cast<uint>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2193
2194 for(uint i = 0; i < numDim; i++)
2195 {
2196 uint32_t uint32Value = CHECKED_NON_NEGATIVE(CHECKED_INT32(int64Axes[i]));
2197 dims.push_back(uint32Value);
2198 }
2199 }
2200
2201 // Ensure that the axes are sorted
2202 std::sort(dims.begin(), dims.end());
2203
2204 std::vector<unsigned int> targetShape;
2205
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002206 if (inputShape.GetDimensionality() != Dimensionality::Scalar)
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002207 {
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002208 for(uint i = 0; i < inputShape.GetNumDimensions(); i++)
2209 {
2210 targetShape.push_back(inputShape[i]);
2211 }
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002212 }
2213
2214 for(uint i = 0; i < dims.size(); i++)
2215 {
2216 targetShape.insert(targetShape.begin() + armnn::numeric_cast<int>(dims[i]), 1);
2217 }
2218
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002219 auto outInfo = ComputeReshapeInfo(TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2220 inputShape, node.output(0), m_TensorsInfo[node.input(0)].m_info->GetDataType());
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002221 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002222 m_TensorsInfo[node.output(0)].m_dtype = m_TensorsInfo[node.input(0)].m_dtype;
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002223
2224 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2225}
2226
Kevin Mayef33cb12021-01-29 14:24:57 +00002227void OnnxParserImpl::PrependForBroadcast(const std::string& outputName,
2228 const std::string& input0,
2229 const std::string& input1)
telsoa01c577f2c2018-08-31 09:22:23 +01002230{
2231 //input0 should be reshaped to have same number of dim as input1
2232 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
2233
2234 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
2235 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
2236
2237 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
2238 std::vector<uint32_t> newShape;
2239 while(diff > 0)
2240 {
2241 newShape.push_back(1);
2242 diff--;
2243 }
2244 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
2245 {
2246 newShape.push_back(input0Shape[dim]);
2247 }
2248 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
2249
2250 //add the new tensor to m_TensorsInfo
2251 m_TensorsInfo[outputName] = OnnxTensor();
2252 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
2253
2254 //add reshape layer if the parent was not constant...
2255 if( ! m_TensorsInfo[input0].isConstant())
2256 {
James Ward58dec6b2020-09-11 17:32:44 +01002257 CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
telsoa01c577f2c2018-08-31 09:22:23 +01002258 }
2259 else //make it constant and it will be create in Add
2260 {
2261 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
2262
2263 }
2264}
2265
Kevin Mayef33cb12021-01-29 14:24:57 +00002266void OnnxParserImpl::SetupInputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01002267{
2268 //Find user input and add their layers
2269 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
2270 {
2271 auto input = m_Graph->input(inputIndex);
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002272 if (!m_TensorsInfo[input.name()].isConstant())
telsoa01c577f2c2018-08-31 09:22:23 +01002273 {
2274 IConnectableLayer* layer =
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002275 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
2276 TensorInfo tensorInfo = *m_TensorsInfo[input.name()].m_info;
2277 if (tensorInfo.GetShape().GetDimensionality() == Dimensionality::NotSpecified)
2278 {
2279 if (m_InputShapes.find(input.name()) == m_InputShapes.end())
2280 {
2281 throw ParseException(fmt::format("The parser does not support dynamic tensor, "
2282 "please specify input shape for {}. {}",
2283 input.name(),
2284 CHECK_LOCATION().AsString()));
2285 }
2286 else
2287 {
2288 tensorInfo.SetShape(m_InputShapes[input.name()]);
2289 m_TensorsInfo[input.name()].m_info = std::make_unique<TensorInfo>(tensorInfo);
2290 }
2291
2292 }
telsoa01c577f2c2018-08-31 09:22:23 +01002293 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2294
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002295 m_InputInfos[input.name()] = tensorInfo;
2296
telsoa01c577f2c2018-08-31 09:22:23 +01002297 RegisterOutputSlots(layer,{ input.name() });
2298 }
2299 }
2300}
2301
Kevin Mayef33cb12021-01-29 14:24:57 +00002302void OnnxParserImpl::SetupOutputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01002303{
2304 if(m_Graph->output_size() == 0)
2305 {
James Ward58dec6b2020-09-11 17:32:44 +01002306 throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002307 }
2308
2309 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
2310 {
2311 IConnectableLayer* layer =
2312 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
2313 m_Graph->output(outputIndex).name().c_str());
2314
2315 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
2316 }
2317}
2318
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002319void OnnxParserImpl::RegisterInputSlot(IConnectableLayer* layer,
2320 const std::string& tensorId,
2321 unsigned int slotIndex)
2322{
2323 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2324
2325 auto it = m_TensorConnections.find(tensorId);
2326
2327 if (it == m_TensorConnections.end())
2328 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002329 //First time seeing this tensor, we need to map it
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002330 m_TensorConnections[tensorId] = TensorSlots();
2331 }
2332 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2333}
2334
Kevin Mayef33cb12021-01-29 14:24:57 +00002335void OnnxParserImpl::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002336{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01002337 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01002338 if (tensorIds.size() != layer->GetNumInputSlots())
2339 {
2340 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002341 fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
2342 tensorIds.size(),
2343 layer->GetNumInputSlots(),
2344 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002345 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01002346
telsoa01c577f2c2018-08-31 09:22:23 +01002347 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
2348 {
2349 std::string tensorId = tensorIds[slotIndex];
2350 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2351
2352 auto it = m_TensorConnections.find(tensorId);
2353
2354 if (it == m_TensorConnections.end())
2355 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002356 // First time seing this tensor, we need to map it
telsoa01c577f2c2018-08-31 09:22:23 +01002357 m_TensorConnections[tensorId] = TensorSlots();
2358 }
2359 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2360 }
2361}
2362
Kevin Mayef33cb12021-01-29 14:24:57 +00002363void OnnxParserImpl::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002364{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01002365 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01002366 if (tensorIds.size() != layer->GetNumOutputSlots())
2367 {
2368 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002369 fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
2370 tensorIds.size(),
2371 layer->GetNumOutputSlots(),
2372 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002373 }
2374
2375 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
2376 {
2377 std::string tensorId = tensorIds[slotIndex];
2378 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
2379
2380 auto it = m_TensorConnections.find(tensorId);
2381
2382 if (it == m_TensorConnections.end())
2383 {
2384 //First time seing this tensor, we need to map it
2385 m_TensorConnections[tensorId] = TensorSlots();
2386 }
2387
Ryan OShea337c17f2020-02-21 12:33:17 +00002388 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01002389
2390 // assuming there is only one producer for that tensor
2391 if (tensorSlots.outputSlot != nullptr)
2392 {
James Ward58dec6b2020-09-11 17:32:44 +01002393 throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
2394 "tensor:{} {}",
2395 tensorId,
2396 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002397 }
2398 tensorSlots.outputSlot = slot;
2399 }
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002400
telsoa01c577f2c2018-08-31 09:22:23 +01002401}
2402
Kevin Mayef33cb12021-01-29 14:24:57 +00002403BindingPointInfo OnnxParserImpl::GetNetworkInputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002404{
2405 for(int i = 0; i < m_Graph->input_size(); ++i)
2406 {
2407 auto input = m_Graph->input(i);
2408 if(input.name() == name)
2409 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002410 auto it = m_InputInfos.find(name);
2411
2412 if (it != m_InputInfos.end())
2413 {
2414 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2415 }
telsoa01c577f2c2018-08-31 09:22:23 +01002416 }
2417 }
James Ward58dec6b2020-09-11 17:32:44 +01002418 throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
2419 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002420}
2421
Kevin Mayef33cb12021-01-29 14:24:57 +00002422BindingPointInfo OnnxParserImpl::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002423{
2424 for(int i = 0; i < m_Graph->output_size(); ++i)
2425 {
2426 auto output = m_Graph->output(i);
2427 if(output.name() == name)
2428 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002429 auto it = m_OutputInfos.find(name);
2430
2431 if (it != m_OutputInfos.end())
2432 {
2433 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2434 }
telsoa01c577f2c2018-08-31 09:22:23 +01002435 }
2436 }
James Ward58dec6b2020-09-11 17:32:44 +01002437 throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
2438 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002439}
2440
Kevin Mayef33cb12021-01-29 14:24:57 +00002441std::vector<std::string> OnnxParserImpl::GetInputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002442{
2443 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002444 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2445 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002446 }
2447
2448 std::vector<std::string> inputNames;
2449 std::map<std::string, bool> isConstant;
2450 for(auto tensor : model->graph().initializer())
2451 {
2452 isConstant[tensor.name()] = true;
2453 }
2454 for(auto input : model->graph().input())
2455 {
2456 auto it = isConstant.find(input.name());
2457 if(it == isConstant.end())
2458 {
2459 inputNames.push_back(input.name());
2460 }
2461 }
2462 return inputNames;
2463}
2464
Kevin Mayef33cb12021-01-29 14:24:57 +00002465std::vector<std::string> OnnxParserImpl::GetOutputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002466{
2467 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002468 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2469 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002470 }
2471
2472 std::vector<std::string> outputNames;
2473 for(auto output : model->graph().output())
2474 {
2475 outputNames.push_back(output.name());
2476 }
2477 return outputNames;
2478}
2479
Matthew Sloyanac001ee2021-02-03 10:43:04 +00002480const std::string OnnxParserImpl::GetVersion()
2481{
2482 return ONNX_PARSER_VERSION;
2483}
2484
telsoa01c577f2c2018-08-31 09:22:23 +01002485} // namespace armnnOnnxParser