blob: 5ec99ede74c6460d910975732e90d5fd2017d8ab [file] [log] [blame]
telsoa01c577f2c2018-08-31 09:22:23 +01001//
Matthew Sloyanca361232023-02-16 14:50:22 +00002// Copyright © 2017,2022-2023 Arm Ltd and Contributors. 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
Mike Kelly2ae32242022-11-25 13:55:24 +000053armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent)
54{
55 return pOnnxParserImpl->CreateNetworkFromBinary(binaryContent);
56}
57
58armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent,
59 const std::map<std::string, armnn::TensorShape>& inputShapes)
60{
61 return pOnnxParserImpl->CreateNetworkFromBinary(binaryContent, inputShapes);
62}
63
Kevin Mayef33cb12021-01-29 14:24:57 +000064armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile)
65{
66 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile);
67}
68
69armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText)
70{
71 return pOnnxParserImpl->CreateNetworkFromString(protoText);
72}
73
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +010074armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinaryFile(
75 const char* graphFile,
76 const std::map<std::string, armnn::TensorShape>& inputShapes)
77{
78 return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile, inputShapes);
79}
80
81armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile,
82 const std::map<std::string, armnn::TensorShape>& inputShapes)
83{
84 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile, inputShapes);
85}
86
87armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText,
88 const std::map<std::string, armnn::TensorShape>& inputShapes)
89{
90 return pOnnxParserImpl->CreateNetworkFromString(protoText, inputShapes);
91}
92
Kevin Mayef33cb12021-01-29 14:24:57 +000093BindingPointInfo IOnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
94{
95 return pOnnxParserImpl->GetNetworkInputBindingInfo(name);
96}
97
98BindingPointInfo IOnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
99{
100 return pOnnxParserImpl->GetNetworkOutputBindingInfo(name);
101}
102
telsoa01c577f2c2018-08-31 09:22:23 +0100103namespace
104{
105void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
106 const onnx::TensorProto::DataType actualValue,
107 const char* validExpr,
108 std::string nodeName,
109 std::string tensorName,
110 const armnn::CheckLocation& location)
111{
112 bool isValid = std::any_of(validInputTypes.begin(),
113 validInputTypes.end(),
114 [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
115 if (!isValid)
116 {
117 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100118 fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
119 onnx::TensorProto::DataType_Name(actualValue),
120 tensorName,
121 nodeName,
122 validExpr,
123 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100124 }
125}
126
127#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
128CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
129
130using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
131#define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
132
133template <typename Callable>
134void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
135 const std::string& attribName,
136 onnx::AttributeProto::AttributeType expectedType,
137 Callable callable)
138{
139 auto attribs = node.attribute();
140 int attriNum = 0;
141 while (attriNum < node.attribute_size())
142 {
143 if (attribs.Get(attriNum).name() == attribName)
144 {
145 if (attribs.Get(attriNum).type() == expectedType)
146 {
147 callable(attribs.Get(attriNum));
148 }
149 else
150 {
James Ward58dec6b2020-09-11 17:32:44 +0100151 throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
152 "onnx::AttributeProto::AttributeType, but found {} instead {}",
153 attribName,
154 node.name(),
155 onnx::AttributeProto::AttributeType_Name(expectedType),
156 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
157 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100158 }
159 break;
160 }
161 ++attriNum;
162 }
163 if (attriNum == node.attribute_size())
164 {
James Ward58dec6b2020-09-11 17:32:44 +0100165 throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
166 attribName, node.name(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100167 }
168}
169
170template <typename Callable>
171void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
172 const std::string& attribName,
173 onnx::AttributeProto::AttributeType expectedType,
174 Callable callable)
175{
176 auto attribs = node.attribute();
177 for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
178 {
179 if (attribs.Get(attriNum).name() == attribName)
180 {
181 if (attribs.Get(attriNum).type() == expectedType)
182 {
183 callable(attribs.Get(attriNum));
184 }
185 else
186 {
James Ward58dec6b2020-09-11 17:32:44 +0100187 throw ParseException(
188 fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
189 "but found {} instead {}",
190 attribName,
191 node.name(),
192 onnx::AttributeProto::AttributeType_Name(expectedType),
193 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
194 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100195 }
196 }
197 }
198}
199
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +0100200int ReadMandatoryNodeIntAttribute(const onnx::NodeProto& node,
201 const std::string& name)
202{
203 int attribValue = 0;
204 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
205 [&attribValue](const onnx::AttributeProto& attrValue)
206 {
207 attribValue = CHECKED_INT32(attrValue.i());
208 });
209 return attribValue;
210}
211
Ryan OSheaed27ee72020-04-22 16:37:29 +0100212int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
213 const std::string& name,
214 const int64_t defaultValue = 0)
215{
216 int64_t attribValue = defaultValue;
217 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
218 [&attribValue](const onnx::AttributeProto& attrValue)
219 {
220 attribValue = attrValue.i();
221 });
222 return attribValue;
223}
224
telsoa01c577f2c2018-08-31 09:22:23 +0100225std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
226 const std::string& name)
227{
228 std::vector<uint32_t> attriList;
229 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
230 [&attriList](const onnx::AttributeProto& attrValue)
231 {
232 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
233 {
234 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
235 }
236 });
237 return attriList;
238}
239
240uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
241 const std::string& name,
242 const uint32_t defaultVal = 0u)
243{
244 uint32_t attribValue = defaultVal;
245 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
246 [&attribValue](const onnx::AttributeProto& attrValue)
247 {
248 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
249 });
250 return attribValue;
251}
252
253std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
254 const std::string& name)
255{
256 std::vector<uint32_t> attriList;
257 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
258 [&attriList](const onnx::AttributeProto& attrValue)
259 {
260 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
261 {
262 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
263 }
264 });
265
266 return attriList;
267}
268
269float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
270 const std::string& name,
271 const float defaultValue = 0.0f)
272{
273 float attribValue = defaultValue;
274 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
275 [&attribValue](const onnx::AttributeProto& attrValue)
276 {
277 attribValue = attrValue.f();
278 });
279 return attribValue;
280}
281
282std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
283{
284 std::string attribValue = "";
285 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
286 [&attribValue](const onnx::AttributeProto& attrValue)
287 {
288 attribValue = attrValue.s();
289 });
290 return attribValue;
291}
292
Tee Jungfcf6fd52019-11-01 05:27:28 +0000293armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100294{
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100295 DataType type;
296 switch(data_type)
297 {
298 case onnx::TensorProto::FLOAT:
299 {
300 type = DataType::Float32;
telsoa01c577f2c2018-08-31 09:22:23 +0100301 break;
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100302 }
303 case onnx::TensorProto::INT32:
304 case onnx::TensorProto::INT64:
305 {
306 type = DataType::Signed32;
307 break;
308 }
309 default:
310 {
311 throw ParseException(
312 fmt::format("'{}' is not a currently supported datatype for tensor {}."
313 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
314 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
315 name,
316 CHECK_LOCATION().AsString() ));
317 }
318 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000319
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100320 // Scalar Tensor
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100321 if (shape.empty())
322 {
323 return TensorInfo(TensorShape(Dimensionality::Scalar), type);
324 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000325
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100326 // Dynamic Tensor
327 if(std::find(shape.begin(), shape.end(), 0) != shape.end())
328 {
329 return TensorInfo(TensorShape(Dimensionality::NotSpecified), type);
330 }
331
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100332 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000333}
334
335armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
336{
337 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
338 std::vector<unsigned int> shapeDims;
339 for (int i = 0; i < onnxShape.dim_size(); ++i)
340 {
341 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
342 }
343
344 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
345}
346
347armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
348{
349 std::vector<unsigned int> shapeDims;
Ryan OShea337c17f2020-02-21 12:33:17 +0000350
Tee Jungfcf6fd52019-11-01 05:27:28 +0000351 for (auto dim: tensor.dims())
352 {
353 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
354 }
355
356 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100357}
358
359std::string TensorInfoAsString(const TensorInfo& info,
360 const std::string& name,
361 const onnx::TensorProto::DataType& type)
362{
363 const TensorShape shape = info.GetShape();
364 std::stringstream ss;
365 ss << "tensor '" << name << "' contains "
366 << onnx::TensorProto::DataType_Name(type)
367 << " and has shape [";
368
369 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
370 {
371 ss << shape[i] << ", ";
372 }
373 ss << shape[shape.GetNumDimensions() - 1] << "]";
374 return ss.str();
375}
376
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000377void CalcPadding(uint32_t inputSize,
378 uint32_t filterSize,
379 uint32_t stride,
380 uint32_t dilation,
381 uint32_t* paddingFront,
382 uint32_t* paddingBack,
383 bool isUpper)
telsoa01c577f2c2018-08-31 09:22:23 +0100384{
385 uint32_t outputSize = (inputSize + stride - 1) / stride;
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000386 uint32_t dilatedSize = filterSize + (dilation - 1) * (filterSize - 1);
387 uint32_t temp = (outputSize - 1) * stride + dilatedSize;
telsoa01c577f2c2018-08-31 09:22:23 +0100388 *paddingFront = (temp - inputSize) / 2;
389 *paddingBack = *paddingFront;
390 if((temp - inputSize) % 2 == 1)
391 {
392 if (isUpper)
393 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000394 *paddingBack += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100395 }
396 else
397 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000398 *paddingFront += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100399 }
400 }
401}
402
Ryan OSheaed27ee72020-04-22 16:37:29 +0100403TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
telsoa01c577f2c2018-08-31 09:22:23 +0100404 const TensorShape& inShape,
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100405 const std::string& outName,
406 DataType dataType = DataType::Float32)
telsoa01c577f2c2018-08-31 09:22:23 +0100407{
408 std::vector<int> targetDims;
Ryan OSheaed27ee72020-04-22 16:37:29 +0100409 for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
telsoa01c577f2c2018-08-31 09:22:23 +0100410 {
Ryan OSheaed27ee72020-04-22 16:37:29 +0100411 int val = CHECKED_INT32(targetShapeTensor[i]);
telsoa01c577f2c2018-08-31 09:22:23 +0100412 if(val == 0)
413 {
414 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
415 }
416 else
417 {
418 targetDims.push_back(val);
419 }
420 }
421
422 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
423 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
424 if (stretchDim != targetDims.end())
425 {
426 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
427 {
428 std::stringstream ss;
429 ss << "[ ";
430 for(uint i = 0; i < targetDims.size() - 1; ++i)
431 {
432 ss << targetDims[i] << ", ";
433 }
434 ss << targetDims[targetDims.size() - 1] << " ]";
435
James Ward58dec6b2020-09-11 17:32:44 +0100436 throw ParseException(
437 fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
438 " -1 and here, shape is {} {}",
439 outName,
440 ss.str(),
441 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100442 }
443
Tianle Cheng20773482023-10-03 12:01:11 +0100444 auto targetNumElements = armnn::numeric_cast<unsigned int>(
445 std::accumulate(targetDims.begin(), targetDims.end(), -1, std::multiplies<int32_t>()));
telsoa01c577f2c2018-08-31 09:22:23 +0100446 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
Tianle Cheng20773482023-10-03 12:01:11 +0100447 if (targetNumElements == 0)
448 {
449 if (inShape.GetNumElements() == 0)
450 {
451 outDims[stretchIndex] = 0;
452 }
453 else
454 {
455 throw ParseException(
456 fmt::format("Input to reshape is a tensor with elements, but the requested shape has 0. {}",
457 CHECK_LOCATION().AsString()));
458 }
459 }
460 else
461 {
462 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
463 }
telsoa01c577f2c2018-08-31 09:22:23 +0100464 }
465 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100466 return TensorInfo(outShape, dataType);
telsoa01c577f2c2018-08-31 09:22:23 +0100467}
468
469} //namespace
470
Kevin Mayef33cb12021-01-29 14:24:57 +0000471const std::map<std::string, OnnxParserImpl::OperationParsingFunction> OnnxParserImpl::m_ParserFunctions = {
472 { "BatchNormalization", &OnnxParserImpl::ParseBatchNormalization},
473 { "GlobalAveragePool", &OnnxParserImpl::ParseGlobalAveragePool},
474 { "AveragePool", &OnnxParserImpl::ParseAveragePool },
475 { "Clip", &OnnxParserImpl::ParseClip },
476 { "Constant", &OnnxParserImpl::ParseConstant },
477 { "MaxPool", &OnnxParserImpl::ParseMaxPool },
478 { "Reshape", &OnnxParserImpl::ParseReshape },
479 { "Sigmoid", &OnnxParserImpl::ParseSigmoid },
480 { "Tanh", &OnnxParserImpl::ParseTanh },
481 { "Relu", &OnnxParserImpl::ParseRelu },
482 { "LeakyRelu", &OnnxParserImpl::ParseLeakyRelu },
483 { "Conv", &OnnxParserImpl::ParseConv },
484 { "Add", &OnnxParserImpl::ParseAdd },
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +0100485 { "Flatten", &OnnxParserImpl::ParseFlatten },
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100486 { "Shape", &OnnxParserImpl::ParseShape },
487 { "Gather", &OnnxParserImpl::ParseGather },
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +0100488 { "Unsqueeze", &OnnxParserImpl::ParseUnsqueeze },
Narumol Prangnawarat1112b012021-09-30 12:10:50 +0100489 { "Concat", &OnnxParserImpl::ParseConcat },
490 { "Gemm", &OnnxParserImpl::ParseGemm }
telsoa01c577f2c2018-08-31 09:22:23 +0100491};
492
493template<typename TypePair, typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000494void OnnxParserImpl::ValidateInputs(const onnx::NodeProto& node,
telsoa01c577f2c2018-08-31 09:22:23 +0100495 TypePair validInputs,
496 const Location& location)
497{
498 for(auto input : node.input())
499 {
500 CheckValidDataType(validInputs.second,
501 m_TensorsInfo[input].m_dtype,
502 validInputs.first,
503 node.name(),
504 input,
505 location);
506 }
507}
508
509#define VALID_INPUTS(NODE, VALID_INPUTS) \
Kevin Mayef33cb12021-01-29 14:24:57 +0000510 OnnxParserImpl::ValidateInputs(NODE, \
telsoa01c577f2c2018-08-31 09:22:23 +0100511 VALID_INPUTS, \
512 CHECK_LOCATION())
513
Kevin Mayef33cb12021-01-29 14:24:57 +0000514std::vector<TensorInfo> OnnxParserImpl::ComputeOutputInfo(std::vector<std::string> outNames,
515 const IConnectableLayer* layer,
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100516 std::vector<TensorShape> inputShapes,
517 const onnx::TensorProto::DataType& dataType)
telsoa01c577f2c2018-08-31 09:22:23 +0100518{
Ryan OSheac229b3f2023-06-27 22:34:54 +0100519 if (outNames.empty())
520 {
521 throw armnn::ParseException(fmt::format("Output names are empty {}", CHECK_LOCATION().AsString()));
522 }
523
telsoa01c577f2c2018-08-31 09:22:23 +0100524 bool needCompute = std::any_of(outNames.begin(),
525 outNames.end(),
526 [this](std::string name)
527 {
Matthew Sloyanca361232023-02-16 14:50:22 +0000528 return (m_TensorsInfo.count(name) == 0 ||
529 m_TensorsInfo[name].m_info == nullptr ||
530 m_TensorsInfo[name].m_info->GetShape().GetDimensionality() ==
531 Dimensionality::NotSpecified);
telsoa01c577f2c2018-08-31 09:22:23 +0100532 });
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100533 std::vector<TensorInfo> outInfo;
534 //if the output info(s) are not here, we need to compute them
535 std::vector<TensorShape> inferredShapes;
536 DataType armnnType = DataType::Float32;
537 if(needCompute) {
538 inferredShapes = layer->InferOutputShapes(inputShapes);
Ryan OSheac229b3f2023-06-27 22:34:54 +0100539 if (inferredShapes.size() != outNames.size())
540 {
541 throw armnn::ParseException(fmt::format("Inferred shapes does not match number of output names {}",
542 CHECK_LOCATION().AsString()));
543 }
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100544 switch (dataType) {
545 case onnx::TensorProto::FLOAT: {
546 armnnType = DataType::Float32;
547 break;
548 }
549 case onnx::TensorProto::INT32:
550 case onnx::TensorProto::INT64: {
551 armnnType = DataType::Signed32;
552 break;
553 }
554 default: {
555 throw ParseException(
556 fmt::format("'{}' is not a currently supported datatype for {}."
557 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
558 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(dataType)),
559 layer->GetName(),
560 CHECK_LOCATION().AsString()));
561 }
562 }
563 }
564 for (uint i = 0; i < outNames.size(); ++i)
565 {
566 if(needCompute)
567 {
568 m_TensorsInfo[outNames[i]] = OnnxTensor();
569 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
570 TensorInfo(inferredShapes[i], armnnType));
571 m_TensorsInfo[outNames[i]].m_dtype = dataType;
572 }
telsoa01c577f2c2018-08-31 09:22:23 +0100573 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100574 }
575 return outInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100576}
577
Kevin Mayef33cb12021-01-29 14:24:57 +0000578OnnxParserImpl::OnnxParserImpl()
telsoa01c577f2c2018-08-31 09:22:23 +0100579 : m_Network(nullptr, nullptr)
580{
581}
582
Kevin Mayef33cb12021-01-29 14:24:57 +0000583void OnnxParserImpl::ResetParser()
telsoa01c577f2c2018-08-31 09:22:23 +0100584{
585 m_Network = armnn::INetworkPtr(nullptr, nullptr);
586 m_Graph = nullptr;
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100587 m_InputInfos.clear();
588 m_OutputInfos.clear();
telsoa01c577f2c2018-08-31 09:22:23 +0100589}
590
Kevin Mayef33cb12021-01-29 14:24:57 +0000591void OnnxParserImpl::Cleanup()
telsoa01c577f2c2018-08-31 09:22:23 +0100592{
593 m_TensorConnections.clear();
594 m_TensorsInfo.clear();
595 m_OutputsMap.clear();
596 m_OutputsFusedAndUsed.clear();
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100597 m_InputShapes.clear();
telsoa01c577f2c2018-08-31 09:22:23 +0100598}
599
Jan Eilers53ef7952021-06-02 12:01:25 +0100600template<typename T>
601std::pair<armnn::ConstTensor, std::unique_ptr<T[]>>
602CreateConstTensorImpl(const T* bufferPtr,
603 armnn::TensorInfo& tensorInfo,
604 const armnn::Optional<armnn::PermutationVector&> permutationVector)
telsoa01c577f2c2018-08-31 09:22:23 +0100605{
Ryan OSheac229b3f2023-06-27 22:34:54 +0100606 if (bufferPtr == nullptr)
607 {
608 throw armnn::ParseException(fmt::format("Buffer for permutation is null {}", CHECK_LOCATION().AsString()));
609 }
Jan Eilers53ef7952021-06-02 12:01:25 +0100610
611 std::unique_ptr<T[]> data(new T[tensorInfo.GetNumElements()]);
612
613 if (permutationVector.has_value() && permutationVector.value().GetSize() > 0)
614 {
615 tensorInfo = armnnUtils::Permuted(tensorInfo, permutationVector.value());
616 armnnUtils::Permute(tensorInfo.GetShape(), permutationVector.value(),
617 reinterpret_cast<const T*>(bufferPtr), data.get(), sizeof(T));
618 }
619 else
620 {
621 ::memcpy(data.get(), bufferPtr, tensorInfo.GetNumBytes());
622 }
623
624 return std::make_pair(ConstTensor(tensorInfo, data.get()), std::move(data));
625}
626
627std::pair<ConstTensor, std::unique_ptr<float[]>>
628OnnxParserImpl::CreateConstTensor(const std::string name,
629 armnn::Optional<armnn::PermutationVector&> permutationVector)
630{
631 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
telsoa01c577f2c2018-08-31 09:22:23 +0100632 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
633
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100634 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
635 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
636 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
637
Matthew Sloyan81beae32021-07-13 19:46:11 +0100638 // Makes sure IsConstant flag is set.
639 tensorInfo.SetConstant();
640
Jan Eilers53ef7952021-06-02 12:01:25 +0100641 // Const tensors requires at least a list of values
642 if (tensorInfo.GetNumElements() == 0)
643 {
644 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
645 name,
646 CHECK_LOCATION().AsString()));
647 }
648
telsoa01c577f2c2018-08-31 09:22:23 +0100649 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100650 // Copy the value list entries into the destination
651 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100652 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100653 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
654 {
James Ward58dec6b2020-09-11 17:32:44 +0100655 throw ParseException(
656 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
657 "elements ({}) {}",
658 onnxTensor.float_data_size(),
659 name,
660 tensorInfo.GetNumElements(),
661 CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100662 }
Jan Eilers53ef7952021-06-02 12:01:25 +0100663 return CreateConstTensorImpl<float>(srcData, tensorInfo, permutationVector);
telsoa01c577f2c2018-08-31 09:22:23 +0100664 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100665 else
666 {
Jan Eilers53ef7952021-06-02 12:01:25 +0100667 return CreateConstTensorImpl<float>(reinterpret_cast<const float*>(onnxTensor.raw_data().c_str()),
668 tensorInfo,
669 permutationVector);
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100670 }
telsoa01c577f2c2018-08-31 09:22:23 +0100671}
672
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100673std::pair<ConstTensor, std::unique_ptr<int32_t[]>>
674OnnxParserImpl::CreateInt64ConstTensor(const std::string name,
675 armnn::Optional<armnn::PermutationVector&> permutationVector)
676{
677 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
678 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
679
680 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
681 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::INT64);
682
683 // Makes sure IsConstant flag is set.
684 tensorInfo.SetConstant();
685 uint numElements = tensorInfo.GetNumElements();
686
687 // Const tensors requires at least a list of values
688 if (numElements == 0)
689 {
690 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
691 name,
692 CHECK_LOCATION().AsString()));
693 }
694
695 // Copy the value list entries into the destination
696 if (!onnxTensor.has_raw_data())
697 {
698 auto srcData = onnxTensor.int64_data().data();
699 if(numElements != static_cast<uint>(onnxTensor.int64_data_size()))
700 {
701 throw ParseException(
702 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
703 "elements ({}) {}",
704 onnxTensor.int64_data_size(),
705 name,
706 tensorInfo.GetNumElements(),
707 CHECK_LOCATION().AsString()));
708 }
709
710 std::vector<int32_t> int32Data;
711 for(uint i = 0; i < numElements; i++)
712 {
713 int32_t int32Value = CHECKED_INT32(srcData[i]);
714 int32Data.push_back(int32Value);
715 }
716
717 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
718 }
719 else
720 {
721 auto srcData = reinterpret_cast<const int64_t*>(onnxTensor.raw_data().c_str());
722 std::vector<int32_t> int32Data;
723 for(uint i = 0; i < numElements; i++)
724 {
725 int32_t int32Value = CHECKED_INT32(srcData[i]);
726 int32Data.push_back(int32Value);
727 }
728 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
729 }
730}
731
Kevin Mayef33cb12021-01-29 14:24:57 +0000732ModelPtr OnnxParserImpl::LoadModelFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100733{
734 FILE* fd = fopen(graphFile, "r");
735
736 if (fd == nullptr)
737 {
James Ward58dec6b2020-09-11 17:32:44 +0100738 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100739 }
740
741 // Parse the file into a message
742 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
743 using google::protobuf::io::FileInputStream;
744 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
745 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
746 fclose(fd);
747
748 if (!success)
749 {
750 std::stringstream error;
751 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100752 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100753 }
754 return modelProto;
755}
756
Kevin Mayef33cb12021-01-29 14:24:57 +0000757INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100758{
759 ResetParser();
760 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
761 return CreateNetworkFromModel(*modelProto);
762}
763
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100764INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile,
765 const std::map<std::string, armnn::TensorShape>& inputShapes)
766{
767 ResetParser();
768 m_InputShapes = inputShapes;
769 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
770 return CreateNetworkFromModel(*modelProto);
771}
telsoa01c577f2c2018-08-31 09:22:23 +0100772
Mike Kelly2ae32242022-11-25 13:55:24 +0000773INetworkPtr OnnxParserImpl::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent)
774{
775 ResetParser();
776 ModelPtr modelProto = LoadModelFromBinary(binaryContent);
777 return CreateNetworkFromModel(*modelProto);
778}
779
780INetworkPtr OnnxParserImpl::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent,
781 const std::map<std::string, armnn::TensorShape>& inputShapes)
782{
783 ResetParser();
784 m_InputShapes = inputShapes;
785 ModelPtr modelProto = LoadModelFromBinary(binaryContent);
786 return CreateNetworkFromModel(*modelProto);
787}
788
789ModelPtr OnnxParserImpl::LoadModelFromBinary(const std::vector<uint8_t>& binaryContent)
790{
791 if (binaryContent.size() == 0)
792 {
793 throw ParseException(fmt::format("Missing binary content", CHECK_LOCATION().AsString()));
794 }
795 // Parse the file into a message
796 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
797
798 google::protobuf::io::CodedInputStream codedStream(binaryContent.data(), static_cast<int>(binaryContent.size()));
799 codedStream.SetTotalBytesLimit(INT_MAX);
800 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
801
802 if (!success)
803 {
804 std::stringstream error;
805 error << "Failed to parse graph";
806 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
807 }
808 return modelProto;
809}
810
Kevin Mayef33cb12021-01-29 14:24:57 +0000811ModelPtr OnnxParserImpl::LoadModelFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100812{
813 FILE* fd = fopen(graphFile, "rb");
814
815 if (fd == nullptr)
816 {
James Ward58dec6b2020-09-11 17:32:44 +0100817 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100818 }
819
820 // Parse the file into a message
821 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
822
823 google::protobuf::io::FileInputStream inStream(fileno(fd));
824 google::protobuf::io::CodedInputStream codedStream(&inStream);
Nikhil Raje5181532020-10-09 14:52:25 +0100825 codedStream.SetTotalBytesLimit(INT_MAX);
telsoa01c577f2c2018-08-31 09:22:23 +0100826 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
827 fclose(fd);
828
829 if (!success)
830 {
831 std::stringstream error;
832 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100833 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100834 }
835 return modelProto;
836
837}
838
Kevin Mayef33cb12021-01-29 14:24:57 +0000839INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100840{
841 ResetParser();
842 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
843 return CreateNetworkFromModel(*modelProto);
844}
845
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100846INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile,
847 const std::map<std::string, armnn::TensorShape>& inputShapes)
848{
849 ResetParser();
850 m_InputShapes = inputShapes;
851 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
852 return CreateNetworkFromModel(*modelProto);
853}
854
Kevin Mayef33cb12021-01-29 14:24:57 +0000855ModelPtr OnnxParserImpl::LoadModelFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100856{
857 if (protoText == "")
858 {
James Ward58dec6b2020-09-11 17:32:44 +0100859 throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
860 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100861 }
862 // Parse the string into a message
863 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
864 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
865 if (!success)
866 {
867 std::stringstream error;
868 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100869 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100870 }
871 return modelProto;
872}
873
Kevin Mayef33cb12021-01-29 14:24:57 +0000874INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100875{
876 ResetParser();
877 ModelPtr modelProto = LoadModelFromString(protoText);
878 return CreateNetworkFromModel(*modelProto);
879}
880
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100881INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText,
882 const std::map<std::string, armnn::TensorShape>& inputShapes)
883{
884 ResetParser();
885 m_InputShapes = inputShapes;
886 ModelPtr modelProto = LoadModelFromString(protoText);
887 return CreateNetworkFromModel(*modelProto);
888}
889
Kevin Mayef33cb12021-01-29 14:24:57 +0000890INetworkPtr OnnxParserImpl::CreateNetworkFromModel(onnx::ModelProto& model)
telsoa01c577f2c2018-08-31 09:22:23 +0100891{
892 m_Network = INetwork::Create();
893 try
894 {
895 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
896 LoadGraph();
897 }
898 catch (const ParseException& e)
899 {
900 Cleanup();
901 throw e;
902 }
903 Cleanup();
904 return std::move(m_Network);
905}
906
Kevin Mayef33cb12021-01-29 14:24:57 +0000907void OnnxParserImpl::LoadGraph()
telsoa01c577f2c2018-08-31 09:22:23 +0100908{
Ryan OSheac229b3f2023-06-27 22:34:54 +0100909 if (m_Graph.get() == nullptr)
910 {
911 throw armnn::ParseException(fmt::format("Graph pointer is null {}", CHECK_LOCATION().AsString()));
912 }
telsoa01c577f2c2018-08-31 09:22:23 +0100913
914 //Fill m_TensorsInfo with the shapes and value of every tensor
915 SetupInfo(m_Graph->mutable_output());
916 SetupInfo(m_Graph->mutable_input());
917 SetupInfo(m_Graph->mutable_value_info());
918
919 for (auto tensor : m_Graph->initializer())
920 {
921 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000922 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
923 m_TensorsInfo[tensor.name()].m_dtype =
924 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100925 }
926
927 SetupInputLayers();
928 SetupOutputLayers();
929
930 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
931 DetectFullyConnected();
932
933 //Parsing the graph
934 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
935 {
936 auto node = m_Graph->node(static_cast<int>(nodeIndex));
937 const std::string& operation = node.op_type();
938
939 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000940 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100941 {
942 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
943 {
944 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
945 AddFullyConnected(node);
946 }
947 }
948 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
949 {
950 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
951 AddFullyConnected(m_Graph->node(matmulIndex), &node);
952 }
953 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
954 {
955 auto it = m_ParserFunctions.find(operation);
956 if (it != m_ParserFunctions.end())
957 {
958 auto func = it->second;
959 (this->*func)(node);
960 }
961 else
962 {
James Ward58dec6b2020-09-11 17:32:44 +0100963 throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
964 operation,
965 node.name(),
966 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100967 }
968 }
969 }
970
971 //Making the connections between outputs and inputs of each layers
972 for (const auto& tensorCon : m_TensorConnections)
973 {
974 if (tensorCon.second.outputSlot != nullptr)
975 {
976 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
977 {
978 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
979 }
980 }
981 }
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100982
983 // Get output info.
984 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
985 {
986 auto output = m_Graph->output(outputIndex);
987 m_OutputInfos[output.name()] = *m_TensorsInfo[output.name()].m_info;
988 }
telsoa01c577f2c2018-08-31 09:22:23 +0100989}
990
Kevin Mayef33cb12021-01-29 14:24:57 +0000991void OnnxParserImpl::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
telsoa01c577f2c2018-08-31 09:22:23 +0100992{
993 for (auto tensor : *list)
994 {
995 m_TensorsInfo[tensor.name()] = OnnxTensor();
996 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000997 m_TensorsInfo[tensor.name()].m_dtype =
998 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100999 }
1000}
1001
Kevin Mayef33cb12021-01-29 14:24:57 +00001002void OnnxParserImpl::DetectFullyConnected()
telsoa01c577f2c2018-08-31 09:22:23 +01001003{
1004 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
1005 auto matmulAndConstant = [&](const std::string& constInput,
1006 const std::string& matmulInput,
1007 int& nodeIndex)
1008 {
1009 auto matmulIt = m_OutputsMap.find(matmulInput);
1010 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
1011 && m_TensorsInfo[constInput].isConstant())
1012 {
1013 nodeIndex = matmulIt->second.second;
1014 return true;
1015 }
1016 return false;
1017 };
1018
1019 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
1020 {
1021 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
1022 for (const std::string& output : node->output())
1023 {
1024 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
1025 }
1026
1027 for (const std::string& input : node->input()) //count how many time a node is used as input
1028 {
1029 auto matmulIt = m_OutputsMap.find(input);
1030 if(matmulIt != m_OutputsMap.end()){
1031 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
1032 }
1033 }
1034
1035 if (node->op_type() == "Add")
1036 {
1037 int matmulIndex = 0;
1038 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
1039 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
1040 {
1041 //matmul and add were fused
1042 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
1043 .push_back(static_cast<size_t>(nodeIndex));
1044
1045 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
1046 .push_back(static_cast<size_t>(matmulIndex));
1047 }
1048 }
1049 }
1050
1051 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
1052 auto matmulIt = m_OutputsMap.find(output.name());
1053 if(matmulIt != m_OutputsMap.end()){
1054 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
1055 }
1056 }
1057}
1058
1059template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +00001060void OnnxParserImpl::GetInputAndParam(const onnx::NodeProto& node,
1061 std::string* inputName,
1062 std::string* constName,
1063 const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +01001064{
1065 int cstIndex;
1066 if (m_TensorsInfo[node.input(0)].isConstant())
1067 {
1068 cstIndex = 0;
1069 }
1070 else if (m_TensorsInfo[node.input(1)].isConstant())
1071 {
1072 cstIndex = 1;
1073 }
1074 else
1075 {
James Ward58dec6b2020-09-11 17:32:44 +01001076 throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
1077 node.input(0),
1078 node.input(1),
1079 node.name(),
1080 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001081 }
1082 if(constName)
1083 {
1084 *constName = node.input(cstIndex);
1085 }
1086 if(inputName)
1087 {
1088 *inputName = node.input(!cstIndex);
1089 }
1090}
1091
1092template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +00001093void OnnxParserImpl::To1DTensor(const std::string& name, const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +01001094{
1095 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
1096 std::vector<uint32_t> newShape;
1097 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
1098 {
1099 if(shape[i] != 1)
1100 {
James Ward58dec6b2020-09-11 17:32:44 +01001101 throw ParseException(
1102 fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
1103 TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
1104 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001105 }
1106 }
1107 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
1108
1109 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1110}
1111
Kevin Mayef33cb12021-01-29 14:24:57 +00001112void OnnxParserImpl::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001113{
1114 ARMNN_ASSERT(node.op_type() == "Conv");
1115
1116 DepthwiseConvolution2dDescriptor desc;
1117 desc.m_PadLeft = convDesc.m_PadLeft;
1118 desc.m_PadRight = convDesc.m_PadRight;
1119 desc.m_PadTop = convDesc.m_PadTop;
1120 desc.m_PadBottom = convDesc.m_PadBottom;
1121 desc.m_StrideX = convDesc.m_StrideX;
1122 desc.m_StrideY = convDesc.m_StrideY;
1123 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
1124
Cathal Corbett06902652022-04-14 17:55:11 +01001125 armnn::IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, node.name().c_str());
Cathal Corbett541880f2022-05-16 15:20:56 +01001126 std::string permuteStr = "permute_" + node.input(1);
1127 std::vector<std::string> tensorIndexes= {node.input(0), permuteStr};
Jan Eilers53ef7952021-06-02 12:01:25 +01001128
Cathal Corbett541880f2022-05-16 15:20:56 +01001129 auto weightTensor = CreateConstTensor(node.input(1));
Cathal Corbett06902652022-04-14 17:55:11 +01001130 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
Cathal Corbett541880f2022-05-16 15:20:56 +01001131
1132 // weights come in as [O,1,H,W] from ONNX and need to be converted to ArmNNs depthwise weights layout [1,H,W,O]
1133 armnn::PermutationVector perVec {3, 0, 1, 2};
1134 TensorInfo weightsPermuted = armnnUtils::Permuted(weightTensor.first.GetInfo(), perVec);
1135
1136 // Inserts NewLayer so layers don't need to be re-sorted.
1137 IConnectableLayer* permuteLayer = m_Network->AddPermuteLayer(PermuteDescriptor(perVec),
1138 "permute_layer");
1139 permuteLayer->GetOutputSlot(0).SetTensorInfo(weightsPermuted);
1140 permuteLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1141
Cathal Corbett06902652022-04-14 17:55:11 +01001142 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
Cathal Corbett541880f2022-05-16 15:20:56 +01001143 weightsLayer->GetOutputSlot(0).Connect(permuteLayer->GetInputSlot(0u));
Cathal Corbett06902652022-04-14 17:55:11 +01001144
Ryan OSheaed27ee72020-04-22 16:37:29 +01001145 if (node.input_size() == 3)
1146 {
1147 if(!m_TensorsInfo[node.input(2)].isConstant())
1148 {
James Ward58dec6b2020-09-11 17:32:44 +01001149 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1150 node.input(2),
1151 node.name(),
1152 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001153 }
Cathal Corbett06902652022-04-14 17:55:11 +01001154
Ryan OSheaed27ee72020-04-22 16:37:29 +01001155 desc.m_BiasEnabled = true;
1156 auto biasTensor = CreateConstTensor(node.input(2));
Cathal Corbett06902652022-04-14 17:55:11 +01001157 tensorIndexes.emplace_back(node.input(2));
1158
1159 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1160 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1161 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001162 }
Cathal Corbett06902652022-04-14 17:55:11 +01001163
Ryan OSheac229b3f2023-06-27 22:34:54 +01001164 if (!layer)
1165 {
1166 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1167 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001168
1169 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1170 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
Cathal Corbett541880f2022-05-16 15:20:56 +01001171 weightsPermuted.GetShape() });
Ryan OSheaed27ee72020-04-22 16:37:29 +01001172
1173 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1174
1175 // register the input connection slots for the layer, connections are made after all layers have been created
1176 // only the tensors for the inputs are relevant, exclude the const tensors
Cathal Corbett06902652022-04-14 17:55:11 +01001177 RegisterInputSlots(layer, tensorIndexes);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001178
1179 // register the output connection slots for the layer, connections are made after all layers have been created
1180 RegisterOutputSlots(layer, {node.output(0)});
1181}
1182
Kevin Mayef33cb12021-01-29 14:24:57 +00001183void OnnxParserImpl::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
telsoa01c577f2c2018-08-31 09:22:23 +01001184{
telsoa01c577f2c2018-08-31 09:22:23 +01001185 // find matmul inputs
telsoa01c577f2c2018-08-31 09:22:23 +01001186 std::string inputName;
Matthew Sloyanca361232023-02-16 14:50:22 +00001187 std::string weightName;
1188 std::string biasName;
1189 std::string outputName;
telsoa01c577f2c2018-08-31 09:22:23 +01001190 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
1191 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
1192 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
1193
1194 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
1195
Matthew Sloyanca361232023-02-16 14:50:22 +00001196 TensorInfo inputInfo = *m_TensorsInfo[inputName].m_info;
1197 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1198 TensorInfo biasInfo;
1199
1200 std::vector<std::string> inputNames;
1201
telsoa01c577f2c2018-08-31 09:22:23 +01001202 FullyConnectedDescriptor desc;
1203 desc.m_BiasEnabled = addNode != nullptr;
1204
1205 IConnectableLayer* layer = nullptr;
1206 if(desc.m_BiasEnabled)
1207 {
1208 // find bias const
telsoa01c577f2c2018-08-31 09:22:23 +01001209 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
1210 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
1211 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
1212
1213 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
1214
1215 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
1216 To1DTensor(biasName, CHECK_LOCATION());
Matthew Sloyanca361232023-02-16 14:50:22 +00001217 biasInfo = *m_TensorsInfo[biasName].m_info;
telsoa01c577f2c2018-08-31 09:22:23 +01001218
1219 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
1220 {
James Ward58dec6b2020-09-11 17:32:44 +01001221 throw ParseException(
1222 fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
1223 " and {} ( /!\\ bias should be a 1D tensor) {}",
1224 weightName,
1225 addNode->name(),
1226 TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
1227 m_TensorsInfo[weightName].m_dtype),
1228 TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
1229 m_TensorsInfo[biasName].m_dtype ),
1230 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001231 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01001232
Matthew Sloyanca361232023-02-16 14:50:22 +00001233 inputNames = { inputName, weightName, biasName };
1234 outputName = addNode->output(0);
telsoa01c577f2c2018-08-31 09:22:23 +01001235 }
1236 else
1237 {
Matthew Sloyanca361232023-02-16 14:50:22 +00001238 inputNames = { inputName, weightName };
1239 outputName = matmulNode.output(0);
1240 }
telsoa01c577f2c2018-08-31 09:22:23 +01001241
Matthew Sloyanca361232023-02-16 14:50:22 +00001242 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1243 layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001244
1245 if (!layer)
1246 {
1247 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1248 }
telsoa01c577f2c2018-08-31 09:22:23 +01001249
Matthew Sloyanca361232023-02-16 14:50:22 +00001250 if (inputInfo.GetNumDimensions() > 2)
1251 {
1252 // Add reshape to flatten to 2D [batch_size, input_size],
1253 // where "input_size" corresponds to the number of inputs to the layer,
1254 // matching the second dimension of weights,
1255 // and "batch_size" is calculated by dividing the number of elements by "input_size".
1256 std::vector<unsigned int> reshapedDimensions(2);
1257 reshapedDimensions[1] = weightInfo.GetShape()[0];
1258 reshapedDimensions[0] = inputInfo.GetNumElements() / reshapedDimensions[1];
1259
1260 if (inputInfo.GetNumElements() % reshapedDimensions[1] != 0)
Matthew Sloyan81beae32021-07-13 19:46:11 +01001261 {
Matthew Sloyanca361232023-02-16 14:50:22 +00001262 throw ParseException(
1263 fmt::format("Failed to deduce input tensor shape from filter size {} {}",
1264 reshapedDimensions[1],
1265 CHECK_LOCATION().AsString()));
Matthew Sloyan81beae32021-07-13 19:46:11 +01001266 }
1267
Matthew Sloyanca361232023-02-16 14:50:22 +00001268 TensorInfo reshapedTensorInfo = inputInfo;
1269 reshapedTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
1270 inputInfo = reshapedTensorInfo;
1271
1272 ReshapeDescriptor reshapeDescriptor;
1273 reshapeDescriptor.m_TargetShape = reshapedTensorInfo.GetShape();
1274
1275 std::string reshapeLayerName = fmt::format("Reshape_for:{}", layer->GetName());
1276 IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(reshapeDescriptor, reshapeLayerName.c_str());
1277
1278 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedTensorInfo);
1279 reshapeLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
1280
1281 RegisterInputSlots(reshapeLayer, {inputName});
1282 inputNames[0] = reshapeLayerName;
telsoa01c577f2c2018-08-31 09:22:23 +01001283 }
Matthew Sloyanca361232023-02-16 14:50:22 +00001284
1285 auto outputInfo = ComputeOutputInfo({ outputName },
1286 layer,
1287 { inputInfo.GetShape(),
1288 weightInfo.GetShape() });
1289 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1290
1291 RegisterInputSlots(layer, inputNames);
1292
1293 // Add constant layer to store weights/biases and connect to FullyConnected layer..
1294 if(m_TensorsInfo[weightName].isConstant())
1295 {
1296 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1297
1298 weightInfo.SetConstant();
1299 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1300 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1301 }
1302
1303 if(desc.m_BiasEnabled && m_TensorsInfo[biasName].isConstant())
1304 {
1305 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(biasName).first);
1306
1307 biasInfo.SetConstant();
1308 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1309 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1310 }
1311
1312 if (outputInfo[0].GetNumDimensions() > 2)
1313 {
1314 // Calculate reshape to flatten to 2D [batch_size, input_size]
1315 std::vector<unsigned int> reshapedDimensions(2);
1316 reshapedDimensions[1] = weightInfo.GetShape()[1];
1317 reshapedDimensions[0] = outputInfo[0].GetNumElements() / reshapedDimensions[1];
1318
1319 if (outputInfo[0].GetNumElements() % reshapedDimensions[1] != 0)
1320 {
1321 throw ParseException(
1322 fmt::format("Failed to deduce output tensor shape from filter size {} {}",
1323 reshapedDimensions[1],
1324 CHECK_LOCATION().AsString()));
1325 }
1326
1327 armnn::TensorInfo reshapedOutputTensorInfo = outputInfo[0];
1328 reshapedOutputTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
1329 layer->GetOutputSlot(0).SetTensorInfo(reshapedOutputTensorInfo);
1330
1331 ReshapeDescriptor desc;
1332 desc.m_TargetShape = outputInfo[0].GetShape();
1333
1334 std::string reshapeLayerName = fmt::format("ExpandDims_for:{}", layer->GetName());
1335 IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(desc, reshapeLayerName.c_str());
1336
1337 layer->GetOutputSlot(0).Connect(reshapeLayer->GetInputSlot(0));
1338 reshapeLayer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1339
1340 RegisterInputSlots(reshapeLayer, {layer->GetName()});
1341 layer = reshapeLayer;
1342 }
1343
1344 RegisterOutputSlots(layer, { outputName });
telsoa01c577f2c2018-08-31 09:22:23 +01001345}
1346
Kevin Mayef33cb12021-01-29 14:24:57 +00001347void OnnxParserImpl::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
telsoa01c577f2c2018-08-31 09:22:23 +01001348{
1349
1350 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1351 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1352
1353 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1354
1355 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
1356 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1357 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1358
1359 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
1360 desc.m_PoolWidth = kernel_shape[1];
1361 desc.m_PoolHeight = kernel_shape[0];
1362
1363 if(strides.empty())
1364 {
1365 desc.m_StrideX = 1;
1366 desc.m_StrideY = 1;
1367 }
1368 else
1369 {
1370 desc.m_StrideX = strides[1];
1371 desc.m_StrideY = strides[0];
1372 }
1373
1374 //Check new padding version first
1375 if(pads.empty())
1376 {
1377 //Check deprecated version
1378 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1379 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1380 {
1381 bool isUpper;
1382 if( paddingString == "SAME_LOWER")
1383 {
1384 isUpper = false;
1385 }
1386 else if (paddingString == "SAME_UPPER")
1387 {
1388 isUpper = true;
1389 }
1390 else
1391 {
James Ward58dec6b2020-09-11 17:32:44 +01001392 throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
1393 "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
1394 node.name(),
1395 paddingString,
1396 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001397 }
1398 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1399 uint32_t inputHeight = inputInfo.GetShape()[2];
1400 uint32_t inputWidth = inputInfo.GetShape()[3];
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001401 CalcPadding(inputHeight,
1402 desc.m_PoolHeight,
1403 desc.m_StrideY,
1404 1u,
1405 &desc.m_PadTop,
1406 &desc.m_PadBottom,
1407 isUpper);
1408 CalcPadding(inputWidth,
1409 desc.m_PoolWidth,
1410 desc.m_StrideX,
1411 1u,
1412 &desc.m_PadLeft,
1413 &desc.m_PadRight,
1414 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001415 }
1416 }
1417 else
1418 {
1419 desc.m_PadTop = pads[0];
1420 desc.m_PadLeft = pads[1];
1421 desc.m_PadBottom = pads[2];
1422 desc.m_PadRight = pads[3];
1423 }
1424
1425 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001426
1427 if (!layer)
1428 {
1429 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1430 }
telsoa01c577f2c2018-08-31 09:22:23 +01001431
1432 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1433 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1434
1435 // register the input connection slots for the layer, connections are made after all layers have been created
1436 // only the tensors for the inputs are relevant, exclude the const tensors
1437 RegisterInputSlots(layer, {node.input(0)});
1438
1439 // register the output connection slots for the layer, connections are made after all layers have been created
1440 RegisterOutputSlots(layer, {node.output(0)});
1441}
1442
Kevin Mayef33cb12021-01-29 14:24:57 +00001443std::pair<std::string, std::string> OnnxParserImpl::AddPrepareBroadcast(const std::string& input0,
1444 const std::string& input1)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001445{
1446 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1447
1448 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1449 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1450
1451 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1452 {
James Ward58dec6b2020-09-11 17:32:44 +01001453 auto outputName = fmt::format("reshape_output_{}", input1);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001454 PrependForBroadcast(outputName, input1, input0);
1455 inputs.second = outputName;
1456 }
1457 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1458 {
James Ward58dec6b2020-09-11 17:32:44 +01001459 auto outputName = fmt::format("reshape_output_{}", input0);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001460 PrependForBroadcast(outputName, input0, input1);
1461 inputs.first = outputName;
1462 }
1463 return inputs;
1464}
1465
Kevin Mayef33cb12021-01-29 14:24:57 +00001466void OnnxParserImpl::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001467{
1468 auto armnnTensor = CreateConstTensor(tensorName);
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001469 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1470 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1471 RegisterOutputSlots(layer, {tensorName});
1472}
Ryan OSheaed27ee72020-04-22 16:37:29 +01001473
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001474void OnnxParserImpl::CreateInt64ConstantLayer(const std::string& tensorName, const std::string& layerName)
1475{
1476 auto armnnTensor = CreateInt64ConstTensor(tensorName);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001477 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1478 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1479 RegisterOutputSlots(layer, {tensorName});
1480}
1481
Kevin Mayef33cb12021-01-29 14:24:57 +00001482void OnnxParserImpl::CreateReshapeLayer(const std::string& inputName,
1483 const std::string& outputName,
1484 const std::string& layerName)
telsoa01c577f2c2018-08-31 09:22:23 +01001485{
1486 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1487 ReshapeDescriptor reshapeDesc;
1488 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1489
1490 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001491
1492 if (!layer)
1493 {
1494 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1495 }
1496
telsoa01c577f2c2018-08-31 09:22:23 +01001497 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1498
1499 // register the input connection slots for the layer, connections are made after all layers have been created
1500 // only the tensors for the inputs are relevant, exclude the const tensors
1501 RegisterInputSlots(layer, {inputName});
1502
1503 // register the output connection slots for the layer, connections are made after all layers have been created
1504 RegisterOutputSlots(layer, {outputName});
1505}
1506
Kevin Mayef33cb12021-01-29 14:24:57 +00001507void OnnxParserImpl::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001508{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001509 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001510 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1511
1512 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1513
1514 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001515 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001516
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001517 if (func == ActivationFunction::BoundedReLu)
1518 {
Narumol Prangnawaratf106ab72021-09-15 17:30:37 +01001519 if (node.input_size() == 1 && node.attribute_size() > 0)
1520 {
1521 desc.m_A = ReadOptionalNodeFloatAttribute(node, "max", std::numeric_limits<float>::max());
1522 desc.m_B = ReadOptionalNodeFloatAttribute(node, "min", std::numeric_limits<float>::lowest());
1523 }
1524 else
1525 {
1526 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1527 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1528 }
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001529 }
1530
telsoa01c577f2c2018-08-31 09:22:23 +01001531 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001532
1533 if (!layer)
1534 {
1535 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1536 }
telsoa01c577f2c2018-08-31 09:22:23 +01001537
1538 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1539 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1540
1541 // register the input connection slots for the layer, connections are made after all layers have been created
1542 // only the tensors for the inputs are relevant, exclude the const tensors
1543 RegisterInputSlots(layer, {node.input(0)});
1544
1545 // register the output connection slots for the layer, connections are made after all layers have been created
1546 RegisterOutputSlots(layer, {node.output(0)});
1547}
1548
Kevin Mayef33cb12021-01-29 14:24:57 +00001549void OnnxParserImpl::ParseClip(const onnx::NodeProto& node)
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001550{
1551 ParseActivation(node, ActivationFunction::BoundedReLu);
1552}
1553
Kevin Mayef33cb12021-01-29 14:24:57 +00001554void OnnxParserImpl::ParseSigmoid(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001555{
1556 ParseActivation(node, ActivationFunction::Sigmoid);
1557}
1558
Kevin Mayef33cb12021-01-29 14:24:57 +00001559void OnnxParserImpl::ParseTanh(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001560{
1561 ParseActivation(node, ActivationFunction::TanH);
1562}
1563
Kevin Mayef33cb12021-01-29 14:24:57 +00001564void OnnxParserImpl::ParseRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001565{
1566 ParseActivation(node, ActivationFunction::ReLu);
1567}
1568
Kevin Mayef33cb12021-01-29 14:24:57 +00001569void OnnxParserImpl::ParseLeakyRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001570{
1571 ParseActivation(node, ActivationFunction::LeakyReLu);
1572}
telsoa01c577f2c2018-08-31 09:22:23 +01001573
Kevin Mayef33cb12021-01-29 14:24:57 +00001574void OnnxParserImpl::ParseAdd(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001575{
Ryan OSheaed27ee72020-04-22 16:37:29 +01001576 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1577 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +01001578
Ryan OSheaed27ee72020-04-22 16:37:29 +01001579 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
telsoa01c577f2c2018-08-31 09:22:23 +01001580
David Monahanf6585542023-05-08 13:14:32 +01001581 // IVGCVSW-1576: unify broadcast validation code across layers
telsoa01c577f2c2018-08-31 09:22:23 +01001582
Ryan OSheaed27ee72020-04-22 16:37:29 +01001583 // Checking broadcast compatibility : only scalar or 1D tensors
1584 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1585 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1586 auto input1 = *m_TensorsInfo[inputs.second].m_info;
Ryan OSheac229b3f2023-06-27 22:34:54 +01001587 if (input0.GetNumDimensions() != input1.GetNumDimensions())
1588 {
1589 throw armnn::ParseException(fmt::format("Dimension mismatch in node {} {}",
1590 node.name(),
1591 CHECK_LOCATION().AsString()));
1592 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001593
1594 unsigned int numDims = input0.GetNumDimensions();
1595 for (unsigned int i = 0; i < numDims; i++)
telsoa01c577f2c2018-08-31 09:22:23 +01001596 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01001597 unsigned int dim0 = input0.GetShape()[i];
1598 unsigned int dim1 = input1.GetShape()[i];
1599 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
telsoa01c577f2c2018-08-31 09:22:23 +01001600 {
James Ward58dec6b2020-09-11 17:32:44 +01001601 throw ParseException(
1602 fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1603 "Input dimensions should either match or one should be of size 1 and here, "
1604 "{} and {} {}",
1605 node.name(),
1606 TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1607 m_TensorsInfo[inputs.first].m_dtype),
1608 TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1609 m_TensorsInfo[inputs.second].m_dtype),
1610 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001611 }
telsoa01c577f2c2018-08-31 09:22:23 +01001612 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001613
1614
Mike Kelly3ec30772023-03-08 13:47:17 +00001615 IConnectableLayer* layer = m_Network->AddElementwiseBinaryLayer(BinaryOperation::Add, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001616
1617 if (!layer)
1618 {
1619 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1620 }
telsoa01c577f2c2018-08-31 09:22:23 +01001621
1622 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
Ryan OSheaed27ee72020-04-22 16:37:29 +01001623 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1624 m_TensorsInfo[inputs.second].m_info->GetShape() });
telsoa01c577f2c2018-08-31 09:22:23 +01001625 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1626
Ryan OSheaed27ee72020-04-22 16:37:29 +01001627 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1628 if(m_TensorsInfo[inputs.first].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001629 CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001630 }
1631 if(m_TensorsInfo[inputs.second].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001632 CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001633 }
1634 RegisterInputSlots(layer, {inputs.first, inputs.second});
telsoa01c577f2c2018-08-31 09:22:23 +01001635
Ryan OSheaed27ee72020-04-22 16:37:29 +01001636 // register the output connection
telsoa01c577f2c2018-08-31 09:22:23 +01001637 RegisterOutputSlots(layer, {node.output(0)});
1638}
1639
Kevin Mayef33cb12021-01-29 14:24:57 +00001640void OnnxParserImpl::ParseAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001641{
1642 Pooling2dDescriptor desc;
1643 desc.m_PoolType = PoolingAlgorithm::Average;
1644
1645 uint32_t count_include_pad = 0;
1646 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1647 if(count_include_pad) {
1648 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1649 }
1650 AddPoolingLayer(node, desc);
1651}
1652
Kevin Mayef33cb12021-01-29 14:24:57 +00001653void OnnxParserImpl::ParseBatchNormalization(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001654{
1655 //IGNORE momentum parameter and spatial parameters
1656
1657 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1658 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1659
1660 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1661 for(int ind = 1; ind < node.input_size(); ++ind)
1662 {
1663 auto tensor = node.input(ind);
1664 if(! m_TensorsInfo[tensor].isConstant())
1665 {
James Ward58dec6b2020-09-11 17:32:44 +01001666 throw ParseException(
1667 fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1668 tensor,
1669 node.name(),
1670 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001671 }
1672 }
1673
1674 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1675 BatchNormalizationDescriptor desc;
1676 desc.m_Eps = epsilon;
1677
1678 auto scaleTensor = CreateConstTensor(node.input(1));
1679 auto biasTensor = CreateConstTensor(node.input(2));
1680 auto meanTensor = CreateConstTensor(node.input(3));
1681 auto varTensor = CreateConstTensor(node.input(4));
1682
1683 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1684 meanTensor.first,
1685 varTensor.first,
1686 biasTensor.first,
1687 scaleTensor.first,
1688 node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001689
1690 if (!layer)
1691 {
1692 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1693 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001694
1695 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1696 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1697
1698 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1699
1700 // register the output connection
1701 RegisterOutputSlots(layer, {node.output(0)});
1702}
1703
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001704void OnnxParserImpl::ParseConcat(const onnx::NodeProto& node)
1705{
1706 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1707
1708 uint32_t numConcatView = static_cast<uint32_t>(node.input_size());
1709 uint32_t inputRank = m_TensorsInfo[node.input(0)].m_info->GetNumDimensions();
1710
1711 int axisInt = ReadMandatoryNodeIntAttribute(node, "axis");
1712
1713 unsigned int concatDimInput = static_cast<unsigned int>(
1714 (static_cast<int>(inputRank) + axisInt) % static_cast<int>(inputRank));
1715
1716 OriginsDescriptor concatDescriptor(numConcatView, inputRank);
1717 concatDescriptor.SetConcatAxis(concatDimInput);
1718
1719 unsigned int mergeDimOrigin = 0;
1720
1721 std::vector<TensorShape> inputShapes;
1722 std::vector<std::string> tensorIds;
1723
1724 for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1725 {
1726 std::string nodeName = node.input(static_cast<int>(viewIndex));
1727 auto inputTensorInfo = *m_TensorsInfo[nodeName].m_info;
1728 inputShapes.push_back(inputTensorInfo.GetShape());
1729 tensorIds.push_back(nodeName);
1730
1731 // Set up concatDescriptor view origin
1732 armnnUtils::ProcessConcatInputTensorInfo(
1733 inputTensorInfo, concatDescriptor, concatDimInput, viewIndex, mergeDimOrigin);
1734 }
1735
1736 IConnectableLayer* layer = m_Network->AddConcatLayer(concatDescriptor, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001737
1738 if (!layer)
1739 {
1740 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1741 }
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001742
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01001743 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, inputShapes,
1744 m_TensorsInfo[node.input(0)].m_dtype);
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001745
1746 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1747
1748 // register the input connection slots for the layer, connections are made after all layers have been created
1749 RegisterInputSlots(layer, tensorIds);
1750
1751 // register the output connection slots for the layer, connections are made after all layers have been created
1752 RegisterOutputSlots(layer, { node.output(0) });
1753}
1754
Kevin Mayef33cb12021-01-29 14:24:57 +00001755void OnnxParserImpl::ParseConstant(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001756{
1757 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1758 if (!node.attribute(0).has_t())
1759 {
James Ward58dec6b2020-09-11 17:32:44 +01001760 throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1761 node.name(),
1762 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001763 }
1764 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1765
Ryan OSheaed27ee72020-04-22 16:37:29 +01001766 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1767 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1768 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1769 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1770
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001771 if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_FLOAT)
1772 {
1773 CreateConstantLayer(node.output(0), node.name());
1774 }
1775 else if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_INT64)
1776 {
1777 CreateInt64ConstantLayer(node.output(0), node.name());
1778 }
1779 else
1780 {
1781 throw ParseException(fmt::format("Data type not support for Constant node '{}' {}",
1782 node.name(),
1783 CHECK_LOCATION().AsString()));
1784 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001785}
1786
Kevin Mayef33cb12021-01-29 14:24:57 +00001787void OnnxParserImpl::ParseConv(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001788{
1789 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1790 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1791
1792 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1793
1794 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1795 {
James Ward58dec6b2020-09-11 17:32:44 +01001796 throw ParseException(
1797 fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1798 node.name(),
1799 TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1800 m_TensorsInfo[node.input(0)].m_dtype),
1801 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001802 }
1803
1804 if(!m_TensorsInfo[node.input(1)].isConstant())
1805 {
James Ward58dec6b2020-09-11 17:32:44 +01001806 throw ParseException(
1807 fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1808 node.input(1),
1809 node.name(),
1810 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001811 }
1812
1813 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1814
telsoa01c577f2c2018-08-31 09:22:23 +01001815 Convolution2dDescriptor desc;
1816 desc.m_BiasEnabled = false;
1817
1818 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1819 if(strides.empty())
1820 {
1821 desc.m_StrideX = 1;
1822 desc.m_StrideY = 1;
1823 }
1824 else
1825 {
1826 desc.m_StrideX = strides[1];
1827 desc.m_StrideY = strides[0];
1828 }
1829
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001830 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1831 if(!dilations.empty())
1832 {
1833 desc.m_DilationX = dilations[1];
1834 desc.m_DilationY = dilations[0];
1835 }
1836
telsoa01c577f2c2018-08-31 09:22:23 +01001837 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1838 //Check new padding version first
1839 if(pads.empty())
1840 {
1841 //Check deprecated version
1842 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1843 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1844 {
1845 bool isUpper;
1846 if( paddingString == "SAME_LOWER")
1847 {
1848 isUpper = false;
1849 }
1850 else if (paddingString == "SAME_UPPER")
1851 {
1852 isUpper = true;
1853 }
1854 else
1855 {
James Ward58dec6b2020-09-11 17:32:44 +01001856 throw ParseException(
1857 fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1858 "supported and found {} {}",
1859 node.name(),
1860 paddingString,
1861 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001862 }
1863 uint32_t inputHeight = inputInfo.GetShape()[2];
1864 uint32_t inputWidth = inputInfo.GetShape()[3];
1865
1866 uint32_t weightHeight;
1867 uint32_t weightWidth;
1868 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1869 if (kernel_shape.empty())
1870 {
1871 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1872 weightHeight = weightTensorInfo.GetShape()[2];
1873 weightWidth = weightTensorInfo.GetShape()[3];
1874 }
1875 else
1876 {
1877 weightHeight = kernel_shape[0];
1878 weightWidth = kernel_shape[1];
1879 }
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001880 CalcPadding(inputHeight,
1881 weightHeight,
1882 desc.m_StrideY,
1883 desc.m_DilationY,
1884 &desc.m_PadTop,
1885 &desc.m_PadBottom,
1886 isUpper);
1887 CalcPadding(inputWidth,
1888 weightWidth,
1889 desc.m_StrideX,
1890 desc.m_DilationX,
1891 &desc.m_PadLeft,
1892 &desc.m_PadRight,
1893 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001894 }
1895 }
1896 else
1897 {
1898 desc.m_PadTop = pads[0];
1899 desc.m_PadLeft = pads[1];
1900 desc.m_PadBottom = pads[2];
1901 desc.m_PadRight = pads[3];
1902 }
1903
1904 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1905 if(group > 1)
1906 {
1907 if (group > inputInfo.GetShape()[1])
1908 {
1909 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001910 fmt::format("Error parsing Convolution node: {}. "
1911 "The 'group'={} parameter cannot be larger than the "
1912 "channel of the input shape={} (in NCHW format). {}",
1913 node.name(),
1914 group,
1915 inputInfo.GetShape()[1],
1916 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001917 }
1918 else if (group == inputInfo.GetShape()[1])
1919 {
1920 // we use a depthwise convolution here, because the number of groups equals to the
1921 // input channels
1922 AddConvLayerWithDepthwiseConv(node, desc);
1923 return;
1924 }
1925 else
1926 {
James Ward58dec6b2020-09-11 17:32:44 +01001927 throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1928 "The 'group'={} parameter should be 1 or be equal to the "
1929 "channel of the input shape={} (in NCHW format). {}",
1930 node.name(),
1931 group,
1932 inputInfo.GetShape()[1],
1933 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001934 }
1935 }
1936
Keith Davis721e6292022-05-17 10:06:53 +01001937 node.input_size() == 3 ? desc.m_BiasEnabled = true : desc.m_BiasEnabled = false;
1938 armnn::IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, node.name().c_str());
Keith Davisb4dd5cc2022-04-07 11:32:00 +01001939 std::vector<std::string> tensorIndexes= {node.input(0), node.input(1)};
1940
telsoa01c577f2c2018-08-31 09:22:23 +01001941 auto weightTensor = CreateConstTensor(node.input(1));
1942
Keith Davis721e6292022-05-17 10:06:53 +01001943 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
1944 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
1945 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1946
telsoa01c577f2c2018-08-31 09:22:23 +01001947 if (node.input_size() == 3)
1948 {
1949 if(!m_TensorsInfo[node.input(2)].isConstant())
1950 {
James Ward58dec6b2020-09-11 17:32:44 +01001951 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1952 node.input(2),
1953 node.name(),
1954 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001955 }
1956 desc.m_BiasEnabled = true;
1957 auto biasTensor = CreateConstTensor(node.input(2));
Keith Davis721e6292022-05-17 10:06:53 +01001958
1959 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1960 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1961 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1962
1963 tensorIndexes.emplace_back(node.input(2));
telsoa01c577f2c2018-08-31 09:22:23 +01001964 }
Keith Davis721e6292022-05-17 10:06:53 +01001965
Ryan OSheac229b3f2023-06-27 22:34:54 +01001966 if (!layer)
1967 {
1968 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1969 }
telsoa01c577f2c2018-08-31 09:22:23 +01001970
1971 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1972 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1973 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1974 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1975
1976 // register the input connection slots for the layer, connections are made after all layers have been created
1977 // only the tensors for the inputs are relevant, exclude the const tensors
Keith Davisb4dd5cc2022-04-07 11:32:00 +01001978 RegisterInputSlots(layer, tensorIndexes);
telsoa01c577f2c2018-08-31 09:22:23 +01001979
1980 // register the output connection slots for the layer, connections are made after all layers have been created
1981 RegisterOutputSlots(layer, {node.output(0)});
1982}
1983
Kevin Mayef33cb12021-01-29 14:24:57 +00001984void OnnxParserImpl::ParseFlatten(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001985{
1986 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1987 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1988
1989 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1990 m_TensorsInfo[node.input(0)].m_dtype,
1991 onnx::TensorProto::FLOAT);
1992
1993 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1994 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1995
1996 /// Negative axis conversion
1997 if (axis < 0)
1998 {
1999 axis += inputShape.GetNumDimensions();
2000 }
2001
2002 /// Check Axis is within dimensions
2003 if (axis < 0 || axis >= inputShape.GetNumDimensions())
2004 {
James Ward58dec6b2020-09-11 17:32:44 +01002005 throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
2006 axis, inputShape.GetNumDimensions(), node.name()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01002007 }
2008
2009 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
2010 uint dimension1{1};
2011 uint dimension2{1};
2012 uint i{0};
2013
2014 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
2015 for (i = 0; i < axis; i++){
2016 dimension1 *= inputShape[i];
2017 }
2018
2019 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
2020 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
2021 dimension2 *= inputShape[i];
2022 }
2023
2024 TensorShape outputShape{dimension1, dimension2};
2025
2026 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
2027 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2028 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2029}
2030
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01002031void OnnxParserImpl::ParseGather(const onnx::NodeProto& node)
2032{
2033 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2034 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2035
2036 armnn::GatherDescriptor gatherDescriptor;
2037 gatherDescriptor.m_Axis = static_cast<int>(ReadOptionalNodeInt64Attribute(node, "axis", 0));
2038
2039 IConnectableLayer* layer = m_Network->AddGatherLayer(gatherDescriptor, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002040
2041 if (!layer)
2042 {
2043 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2044 }
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01002045
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002046 const TensorShape& inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2047 const TensorShape& indicesShape = m_TensorsInfo[node.input(1)].m_info->GetShape();
2048 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, { inputShape, indicesShape },
2049 m_TensorsInfo[node.input(0)].m_dtype);
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01002050 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2051
2052 // register the input connection slots for the layer, connections are made after all layers have been created
2053 RegisterInputSlots(layer, { node.input(0), node.input(1) });
2054
2055 // register the output connection slots for the layer, connections are made after all layers have been created
2056 RegisterOutputSlots(layer, { node.output(0) });
2057}
2058
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002059void OnnxParserImpl::ParseGemm(const onnx::NodeProto& node)
2060{
2061 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3);
2062 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2063
2064 int transA = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transA", 0));
2065 int transB = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transB", 0));
2066 float alpha = ReadOptionalNodeFloatAttribute(node, "alpha", 1.0);
2067 float beta = ReadOptionalNodeFloatAttribute(node, "beta", 1.0);
2068 bool biasEnabled = node.input_size() == 3;
2069
2070 TensorShape input0Shape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2071 TensorShape input1Shape = m_TensorsInfo[node.input(1)].m_info->GetShape();
2072
2073 // if transB != 0, add transpose to the input1 (tanspose weight matrix in FullyConnected)
2074 armnn::FullyConnectedDescriptor fullyConnectedDescriptor;
2075 fullyConnectedDescriptor.m_BiasEnabled = biasEnabled;
2076 fullyConnectedDescriptor.m_TransposeWeightMatrix = transB;
2077
2078 IConnectableLayer* layer = nullptr;
2079
2080 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
2081 layer = m_Network->AddFullyConnectedLayer(fullyConnectedDescriptor, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002082
2083 if (!layer)
2084 {
2085 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2086 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002087
2088 // if transA != 0, add transpose to the input0
2089 if (transA != 0)
2090 {
2091 std::string transAName = "transpose_" + node.input(0);
2092 armnn::TransposeDescriptor transposeADescriptor;
2093 transposeADescriptor.m_DimMappings = { 1, 0 };
2094 IConnectableLayer* transALayer = m_Network->AddTransposeLayer(transposeADescriptor, transAName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002095
2096 if (!transALayer)
2097 {
2098 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2099 }
2100
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002101 auto transAInfo = ComputeOutputInfo({ transAName }, transALayer, { input0Shape });
2102 transALayer->GetOutputSlot(0).SetTensorInfo(transAInfo[0]);
2103 transALayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
2104 // register the input connection slots for the layer, connections are made after all layers have been created
2105 RegisterInputSlot(transALayer, node.input(0), 0);
2106 input0Shape = transAInfo[0].GetShape();
2107 }
2108 else
2109 {
2110 RegisterInputSlot(layer, node.input(0), 0);
2111 }
2112
2113 // Add constant layer to store weights/biases and connect to FullyConnected layer.
2114 if(m_TensorsInfo[node.input(1)].isConstant())
2115 {
2116 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(1)).first);
2117 TensorInfo weightInfo = *m_TensorsInfo[node.input(1)].m_info;
2118 weightInfo.SetConstant();
2119 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
2120
2121 // if alpha != 1, multiply to the weight
2122 if (alpha != 1)
2123 {
2124 std::string activationName = "activation_" + node.input(1);
2125 armnn::ActivationDescriptor activationDescriptor;
2126 activationDescriptor.m_A = alpha;
2127 activationDescriptor.m_Function = ActivationFunction::Linear;
2128 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002129
2130 if (!actLayer)
2131 {
2132 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2133 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002134
2135 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { weightInfo.GetShape() });
2136 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2137 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2138 weightsLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
2139 input1Shape = actInfo[0].GetShape();
2140 }
2141 else
2142 {
2143 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2144 input1Shape = weightInfo.GetShape();
2145 }
2146 }
2147 else
2148 {
2149 // if alpha != 1, multiply to the weight
2150 if (alpha != 1)
2151 {
2152 std::string activationName = "activation_" + node.input(1);
2153 armnn::ActivationDescriptor activationDescriptor;
2154 activationDescriptor.m_A = alpha;
2155 activationDescriptor.m_Function = ActivationFunction::Linear;
2156 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002157
2158 if (!actLayer)
2159 {
2160 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2161 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002162
2163 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { input1Shape });
2164 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2165 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2166 RegisterInputSlot(actLayer, node.input(1), 0);
2167 input1Shape = actInfo[0].GetShape();
2168 }
2169 else
2170 {
2171 RegisterInputSlot(layer, node.input(1), 1);
2172 }
2173 }
2174
2175 if(biasEnabled && m_TensorsInfo[node.input(2)].isConstant())
2176 {
2177 To1DTensor(node.input(2), CHECK_LOCATION());
2178 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(2)).first);
2179 TensorInfo biasInfo = *m_TensorsInfo[node.input(2)].m_info;
2180 biasInfo.SetConstant();
2181 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
2182
2183 // if beta != 1, multiply to the bias
2184 if (beta != 1)
2185 {
2186 std::string activationName = "activation_" + node.input(2);
2187 armnn::ActivationDescriptor activationDescriptor;
2188 activationDescriptor.m_A = beta;
2189 activationDescriptor.m_Function = ActivationFunction::Linear;
2190 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002191
2192 if (!actLayer)
2193 {
2194 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2195 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002196
2197 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { biasInfo.GetShape() });
2198 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2199 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2200 biasLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
2201 }
2202 else
2203 {
2204 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2205 }
2206 }
2207 else if (biasEnabled)
2208 {
2209 // Currently we support non-constant tensor of input C (bias) of Gemm when the dimension is 1
2210 if (m_TensorsInfo[node.input(2)].m_info->GetNumDimensions() != 1)
2211 {
2212 throw ParseException(fmt::format("The parser supports constant or non-constant with 1 dimension for "
2213 "Input C of Gemm. Input '{}' in '{}' is not supported '{}'",
2214 node.input(2),
2215 node.name(),
2216 CHECK_LOCATION().AsString()));
2217 }
2218 // if beta != 1, multiply to the bias
2219 if (beta != 1)
2220 {
2221 std::string activationName = "activation_" + node.input(2);
2222 armnn::ActivationDescriptor activationDescriptor;
2223 activationDescriptor.m_A = beta;
2224 activationDescriptor.m_Function = ActivationFunction::Linear;
2225 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002226
2227 if (!layer)
2228 {
2229 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2230 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002231
2232 auto actInfo = ComputeOutputInfo({ activationName },
2233 actLayer,
2234 { m_TensorsInfo[node.input(2)].m_info->GetShape() });
2235 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2236 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2237 RegisterInputSlot(actLayer, node.input(2), 0);
2238 }
2239 else
2240 {
2241 RegisterInputSlot(layer, node.input(2), 2);
2242 }
2243 }
2244
2245 // Set final output of the FullyConnected layer
2246 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
2247 { input0Shape, input1Shape });
2248 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2249
2250 RegisterOutputSlots(layer, {node.output(0)});
2251}
2252
Kevin Mayef33cb12021-01-29 14:24:57 +00002253void OnnxParserImpl::ParseGlobalAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002254{
2255 Pooling2dDescriptor desc = Pooling2dDescriptor();
2256 desc.m_PoolType = PoolingAlgorithm::Average;
2257
2258 //kernel size is the same as input
2259 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2260 desc.m_PoolWidth = inputShape[3];
2261 desc.m_PoolHeight = inputShape[2];
2262
2263 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002264
2265 if (!layer)
2266 {
2267 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2268 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01002269
2270 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
2271 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2272
2273 // register the input connection slots for the layer, connections are made after all layers have been created
2274 // only the tensors for the inputs are relevant, exclude the const tensors
2275 RegisterInputSlots(layer, {node.input(0)});
2276
2277 // register the output connection slots for the layer, connections are made after all layers have been created
2278 RegisterOutputSlots(layer, {node.output(0)});
2279}
2280
Kevin Mayef33cb12021-01-29 14:24:57 +00002281void OnnxParserImpl::ParseMaxPool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002282{
2283 Pooling2dDescriptor desc;
2284 desc.m_PoolType = PoolingAlgorithm::Max;
2285 desc.m_PaddingMethod = PaddingMethod::Exclude;
2286 AddPoolingLayer(node, desc);
2287}
2288
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002289void OnnxParserImpl::ParseShape(const onnx::NodeProto& node)
2290{
2291 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
2292 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2293
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002294 IConnectableLayer* layer = m_Network->AddShapeLayer(node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002295
2296 if (!layer)
2297 {
2298 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2299 }
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002300
2301 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002302 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape}, onnx::TensorProto::INT64);
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002303 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2304
2305 // register the input connection slots for the layer, connections are made after all layers have been created
2306 RegisterInputSlots(layer, {node.input(0)});
2307
2308 // register the output connection slots for the layer, connections are made after all layers have been created
2309 RegisterOutputSlots(layer, {node.output(0)});
2310}
2311
Kevin Mayef33cb12021-01-29 14:24:57 +00002312void OnnxParserImpl::ParseReshape(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002313{
2314 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2315 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2316
2317 CHECK_VALID_DATATYPE(node.name(), node.input(0),
2318 m_TensorsInfo[node.input(0)].m_dtype,
2319 onnx::TensorProto::FLOAT); //input
2320 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2321 m_TensorsInfo[node.input(1)].m_dtype,
2322 onnx::TensorProto::INT64); //shape
2323
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002324 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2325
2326 std::vector<unsigned int> targetShape;
2327 if(m_TensorsInfo[node.input(1)].isConstant())
Ryan OSheaed27ee72020-04-22 16:37:29 +01002328 {
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002329 unsigned int dims = static_cast<unsigned int>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2330 targetShape.reserve(dims);
2331
2332 for(uint i = 0; i < dims; i++)
2333 {
2334 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
2335 targetShape[i]= static_cast<unsigned int>(val);
2336 }
2337 }
2338 else
2339 {
2340 // The parser only supports shape (batch, -1) or (-1) for non-constant shape input.
2341 unsigned int dims = m_TensorsInfo[node.input(1)].m_info->GetNumDimensions();
2342 TensorShape shapes = m_TensorsInfo[node.input(1)].m_info->GetShape();
2343 if (dims != 1 || shapes[0] > 2)
2344 {
2345 throw ParseException(fmt::format("Invalid input shape '{}' in Reshape layer '{}' {}",
2346 node.input(1),
2347 node.name(),
2348 CHECK_LOCATION().AsString()));
2349 }
2350
2351 unsigned int numInputElements = m_TensorsInfo[node.input(0)].m_info->GetNumElements();
2352 if (shapes[0] == 1)
2353 {
2354 targetShape = { numInputElements };
2355 }
2356 else if (shapes[0] == 2)
2357 {
2358 targetShape = { inputShape[0] , numInputElements / inputShape[0] };
2359 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01002360 }
2361
2362 if(m_TensorsInfo[node.input(0)].isConstant())
2363 {
2364 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
2365 if(m_TensorsInfo.count(node.output(0)) == 0)
2366 {
2367 m_TensorsInfo[node.output(0)] = OnnxTensor();
2368 }
2369 m_TensorsInfo[node.output(0)].m_tensor =
2370 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
2371 }
2372 else
2373 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01002374 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
2375 {
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002376 auto outInfo = ComputeReshapeInfo(
2377 TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2378 inputShape, node.output(0));
Ryan OSheaed27ee72020-04-22 16:37:29 +01002379 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2380 }
2381
2382 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2383 }
2384}
2385
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002386void OnnxParserImpl::ParseUnsqueeze(const onnx::NodeProto& node)
2387{
2388 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.input_size()), 1, 2);
2389 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.output_size()), 1);
2390
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002391 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2392 std::vector<uint32_t> dims;
2393 if (node.input_size() == 1 && node.attribute_size() > 0)
2394 {
2395 dims = ReadMandatoryNodeUint32ListAttribute(node, "axes");
2396 }
2397 else
2398 {
2399 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2400 m_TensorsInfo[node.input(1)].m_dtype,
2401 onnx::TensorProto::INT64); //axes
2402
2403 auto int64Axes = m_TensorsInfo[node.input(1)].m_tensor->int64_data().data();
2404 uint numDim = armnn::numeric_cast<uint>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2405
2406 for(uint i = 0; i < numDim; i++)
2407 {
2408 uint32_t uint32Value = CHECKED_NON_NEGATIVE(CHECKED_INT32(int64Axes[i]));
2409 dims.push_back(uint32Value);
2410 }
2411 }
2412
2413 // Ensure that the axes are sorted
2414 std::sort(dims.begin(), dims.end());
2415
2416 std::vector<unsigned int> targetShape;
2417
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002418 if (inputShape.GetDimensionality() != Dimensionality::Scalar)
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002419 {
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002420 for(uint i = 0; i < inputShape.GetNumDimensions(); i++)
2421 {
2422 targetShape.push_back(inputShape[i]);
2423 }
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002424 }
2425
2426 for(uint i = 0; i < dims.size(); i++)
2427 {
2428 targetShape.insert(targetShape.begin() + armnn::numeric_cast<int>(dims[i]), 1);
2429 }
2430
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002431 auto outInfo = ComputeReshapeInfo(TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2432 inputShape, node.output(0), m_TensorsInfo[node.input(0)].m_info->GetDataType());
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002433 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002434 m_TensorsInfo[node.output(0)].m_dtype = m_TensorsInfo[node.input(0)].m_dtype;
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002435
2436 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2437}
2438
Kevin Mayef33cb12021-01-29 14:24:57 +00002439void OnnxParserImpl::PrependForBroadcast(const std::string& outputName,
2440 const std::string& input0,
2441 const std::string& input1)
telsoa01c577f2c2018-08-31 09:22:23 +01002442{
2443 //input0 should be reshaped to have same number of dim as input1
2444 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
2445
2446 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
2447 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
2448
2449 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
2450 std::vector<uint32_t> newShape;
2451 while(diff > 0)
2452 {
2453 newShape.push_back(1);
2454 diff--;
2455 }
2456 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
2457 {
2458 newShape.push_back(input0Shape[dim]);
2459 }
2460 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
2461
2462 //add the new tensor to m_TensorsInfo
2463 m_TensorsInfo[outputName] = OnnxTensor();
2464 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
2465
2466 //add reshape layer if the parent was not constant...
2467 if( ! m_TensorsInfo[input0].isConstant())
2468 {
James Ward58dec6b2020-09-11 17:32:44 +01002469 CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
telsoa01c577f2c2018-08-31 09:22:23 +01002470 }
2471 else //make it constant and it will be create in Add
2472 {
2473 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
2474
2475 }
2476}
2477
Kevin Mayef33cb12021-01-29 14:24:57 +00002478void OnnxParserImpl::SetupInputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01002479{
2480 //Find user input and add their layers
2481 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
2482 {
2483 auto input = m_Graph->input(inputIndex);
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002484 if (!m_TensorsInfo[input.name()].isConstant())
telsoa01c577f2c2018-08-31 09:22:23 +01002485 {
2486 IConnectableLayer* layer =
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002487 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
2488 TensorInfo tensorInfo = *m_TensorsInfo[input.name()].m_info;
2489 if (tensorInfo.GetShape().GetDimensionality() == Dimensionality::NotSpecified)
2490 {
2491 if (m_InputShapes.find(input.name()) == m_InputShapes.end())
2492 {
2493 throw ParseException(fmt::format("The parser does not support dynamic tensor, "
2494 "please specify input shape for {}. {}",
2495 input.name(),
2496 CHECK_LOCATION().AsString()));
2497 }
2498 else
2499 {
2500 tensorInfo.SetShape(m_InputShapes[input.name()]);
2501 m_TensorsInfo[input.name()].m_info = std::make_unique<TensorInfo>(tensorInfo);
2502 }
2503
2504 }
telsoa01c577f2c2018-08-31 09:22:23 +01002505 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2506
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002507 m_InputInfos[input.name()] = tensorInfo;
2508
telsoa01c577f2c2018-08-31 09:22:23 +01002509 RegisterOutputSlots(layer,{ input.name() });
2510 }
2511 }
2512}
2513
Kevin Mayef33cb12021-01-29 14:24:57 +00002514void OnnxParserImpl::SetupOutputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01002515{
2516 if(m_Graph->output_size() == 0)
2517 {
James Ward58dec6b2020-09-11 17:32:44 +01002518 throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002519 }
2520
2521 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
2522 {
2523 IConnectableLayer* layer =
2524 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
2525 m_Graph->output(outputIndex).name().c_str());
2526
2527 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
2528 }
2529}
2530
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002531void OnnxParserImpl::RegisterInputSlot(IConnectableLayer* layer,
2532 const std::string& tensorId,
2533 unsigned int slotIndex)
2534{
2535 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2536
2537 auto it = m_TensorConnections.find(tensorId);
2538
2539 if (it == m_TensorConnections.end())
2540 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002541 //First time seeing this tensor, we need to map it
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002542 m_TensorConnections[tensorId] = TensorSlots();
2543 }
2544 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2545}
2546
Kevin Mayef33cb12021-01-29 14:24:57 +00002547void OnnxParserImpl::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002548{
Ryan OSheac229b3f2023-06-27 22:34:54 +01002549 if (!layer)
2550 {
2551 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2552 }
2553
telsoa01c577f2c2018-08-31 09:22:23 +01002554 if (tensorIds.size() != layer->GetNumInputSlots())
2555 {
2556 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002557 fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
2558 tensorIds.size(),
2559 layer->GetNumInputSlots(),
2560 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002561 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01002562
telsoa01c577f2c2018-08-31 09:22:23 +01002563 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
2564 {
2565 std::string tensorId = tensorIds[slotIndex];
2566 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2567
2568 auto it = m_TensorConnections.find(tensorId);
2569
2570 if (it == m_TensorConnections.end())
2571 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002572 // First time seing this tensor, we need to map it
telsoa01c577f2c2018-08-31 09:22:23 +01002573 m_TensorConnections[tensorId] = TensorSlots();
2574 }
2575 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2576 }
2577}
2578
Kevin Mayef33cb12021-01-29 14:24:57 +00002579void OnnxParserImpl::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002580{
Ryan OSheac229b3f2023-06-27 22:34:54 +01002581 if (!layer)
2582 {
2583 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2584 }
2585
telsoa01c577f2c2018-08-31 09:22:23 +01002586 if (tensorIds.size() != layer->GetNumOutputSlots())
2587 {
2588 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002589 fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
2590 tensorIds.size(),
2591 layer->GetNumOutputSlots(),
2592 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002593 }
2594
2595 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
2596 {
2597 std::string tensorId = tensorIds[slotIndex];
2598 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
2599
2600 auto it = m_TensorConnections.find(tensorId);
2601
2602 if (it == m_TensorConnections.end())
2603 {
2604 //First time seing this tensor, we need to map it
2605 m_TensorConnections[tensorId] = TensorSlots();
2606 }
2607
Ryan OShea337c17f2020-02-21 12:33:17 +00002608 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01002609
2610 // assuming there is only one producer for that tensor
2611 if (tensorSlots.outputSlot != nullptr)
2612 {
James Ward58dec6b2020-09-11 17:32:44 +01002613 throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
2614 "tensor:{} {}",
2615 tensorId,
2616 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002617 }
2618 tensorSlots.outputSlot = slot;
2619 }
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002620
telsoa01c577f2c2018-08-31 09:22:23 +01002621}
2622
Kevin Mayef33cb12021-01-29 14:24:57 +00002623BindingPointInfo OnnxParserImpl::GetNetworkInputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002624{
2625 for(int i = 0; i < m_Graph->input_size(); ++i)
2626 {
2627 auto input = m_Graph->input(i);
2628 if(input.name() == name)
2629 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002630 auto it = m_InputInfos.find(name);
2631
2632 if (it != m_InputInfos.end())
2633 {
2634 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2635 }
telsoa01c577f2c2018-08-31 09:22:23 +01002636 }
2637 }
James Ward58dec6b2020-09-11 17:32:44 +01002638 throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
2639 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002640}
2641
Kevin Mayef33cb12021-01-29 14:24:57 +00002642BindingPointInfo OnnxParserImpl::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002643{
2644 for(int i = 0; i < m_Graph->output_size(); ++i)
2645 {
2646 auto output = m_Graph->output(i);
2647 if(output.name() == name)
2648 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002649 auto it = m_OutputInfos.find(name);
2650
2651 if (it != m_OutputInfos.end())
2652 {
2653 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2654 }
telsoa01c577f2c2018-08-31 09:22:23 +01002655 }
2656 }
James Ward58dec6b2020-09-11 17:32:44 +01002657 throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
2658 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002659}
2660
Kevin Mayef33cb12021-01-29 14:24:57 +00002661std::vector<std::string> OnnxParserImpl::GetInputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002662{
2663 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002664 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2665 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002666 }
2667
2668 std::vector<std::string> inputNames;
2669 std::map<std::string, bool> isConstant;
2670 for(auto tensor : model->graph().initializer())
2671 {
2672 isConstant[tensor.name()] = true;
2673 }
2674 for(auto input : model->graph().input())
2675 {
2676 auto it = isConstant.find(input.name());
2677 if(it == isConstant.end())
2678 {
2679 inputNames.push_back(input.name());
2680 }
2681 }
2682 return inputNames;
2683}
2684
Kevin Mayef33cb12021-01-29 14:24:57 +00002685std::vector<std::string> OnnxParserImpl::GetOutputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002686{
2687 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002688 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2689 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002690 }
2691
2692 std::vector<std::string> outputNames;
2693 for(auto output : model->graph().output())
2694 {
2695 outputNames.push_back(output.name());
2696 }
2697 return outputNames;
2698}
2699
Matthew Sloyanac001ee2021-02-03 10:43:04 +00002700const std::string OnnxParserImpl::GetVersion()
2701{
2702 return ONNX_PARSER_VERSION;
2703}
2704
telsoa01c577f2c2018-08-31 09:22:23 +01002705} // namespace armnnOnnxParser