blob: f165df9e1459f04f1e4d3eccdbda9759d807e6f6 [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
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100444 auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
telsoa01c577f2c2018-08-31 09:22:23 +0100445 -1, std::multiplies<int32_t>()));
446 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
447 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
448 }
449 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100450 return TensorInfo(outShape, dataType);
telsoa01c577f2c2018-08-31 09:22:23 +0100451}
452
453} //namespace
454
Kevin Mayef33cb12021-01-29 14:24:57 +0000455const std::map<std::string, OnnxParserImpl::OperationParsingFunction> OnnxParserImpl::m_ParserFunctions = {
456 { "BatchNormalization", &OnnxParserImpl::ParseBatchNormalization},
457 { "GlobalAveragePool", &OnnxParserImpl::ParseGlobalAveragePool},
458 { "AveragePool", &OnnxParserImpl::ParseAveragePool },
459 { "Clip", &OnnxParserImpl::ParseClip },
460 { "Constant", &OnnxParserImpl::ParseConstant },
461 { "MaxPool", &OnnxParserImpl::ParseMaxPool },
462 { "Reshape", &OnnxParserImpl::ParseReshape },
463 { "Sigmoid", &OnnxParserImpl::ParseSigmoid },
464 { "Tanh", &OnnxParserImpl::ParseTanh },
465 { "Relu", &OnnxParserImpl::ParseRelu },
466 { "LeakyRelu", &OnnxParserImpl::ParseLeakyRelu },
467 { "Conv", &OnnxParserImpl::ParseConv },
468 { "Add", &OnnxParserImpl::ParseAdd },
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +0100469 { "Flatten", &OnnxParserImpl::ParseFlatten },
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100470 { "Shape", &OnnxParserImpl::ParseShape },
471 { "Gather", &OnnxParserImpl::ParseGather },
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +0100472 { "Unsqueeze", &OnnxParserImpl::ParseUnsqueeze },
Narumol Prangnawarat1112b012021-09-30 12:10:50 +0100473 { "Concat", &OnnxParserImpl::ParseConcat },
474 { "Gemm", &OnnxParserImpl::ParseGemm }
telsoa01c577f2c2018-08-31 09:22:23 +0100475};
476
477template<typename TypePair, typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000478void OnnxParserImpl::ValidateInputs(const onnx::NodeProto& node,
telsoa01c577f2c2018-08-31 09:22:23 +0100479 TypePair validInputs,
480 const Location& location)
481{
482 for(auto input : node.input())
483 {
484 CheckValidDataType(validInputs.second,
485 m_TensorsInfo[input].m_dtype,
486 validInputs.first,
487 node.name(),
488 input,
489 location);
490 }
491}
492
493#define VALID_INPUTS(NODE, VALID_INPUTS) \
Kevin Mayef33cb12021-01-29 14:24:57 +0000494 OnnxParserImpl::ValidateInputs(NODE, \
telsoa01c577f2c2018-08-31 09:22:23 +0100495 VALID_INPUTS, \
496 CHECK_LOCATION())
497
Kevin Mayef33cb12021-01-29 14:24:57 +0000498std::vector<TensorInfo> OnnxParserImpl::ComputeOutputInfo(std::vector<std::string> outNames,
499 const IConnectableLayer* layer,
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100500 std::vector<TensorShape> inputShapes,
501 const onnx::TensorProto::DataType& dataType)
telsoa01c577f2c2018-08-31 09:22:23 +0100502{
Ryan OSheac229b3f2023-06-27 22:34:54 +0100503 if (outNames.empty())
504 {
505 throw armnn::ParseException(fmt::format("Output names are empty {}", CHECK_LOCATION().AsString()));
506 }
507
telsoa01c577f2c2018-08-31 09:22:23 +0100508 bool needCompute = std::any_of(outNames.begin(),
509 outNames.end(),
510 [this](std::string name)
511 {
Matthew Sloyanca361232023-02-16 14:50:22 +0000512 return (m_TensorsInfo.count(name) == 0 ||
513 m_TensorsInfo[name].m_info == nullptr ||
514 m_TensorsInfo[name].m_info->GetShape().GetDimensionality() ==
515 Dimensionality::NotSpecified);
telsoa01c577f2c2018-08-31 09:22:23 +0100516 });
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100517 std::vector<TensorInfo> outInfo;
518 //if the output info(s) are not here, we need to compute them
519 std::vector<TensorShape> inferredShapes;
520 DataType armnnType = DataType::Float32;
521 if(needCompute) {
522 inferredShapes = layer->InferOutputShapes(inputShapes);
Ryan OSheac229b3f2023-06-27 22:34:54 +0100523 if (inferredShapes.size() != outNames.size())
524 {
525 throw armnn::ParseException(fmt::format("Inferred shapes does not match number of output names {}",
526 CHECK_LOCATION().AsString()));
527 }
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100528 switch (dataType) {
529 case onnx::TensorProto::FLOAT: {
530 armnnType = DataType::Float32;
531 break;
532 }
533 case onnx::TensorProto::INT32:
534 case onnx::TensorProto::INT64: {
535 armnnType = DataType::Signed32;
536 break;
537 }
538 default: {
539 throw ParseException(
540 fmt::format("'{}' is not a currently supported datatype for {}."
541 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
542 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(dataType)),
543 layer->GetName(),
544 CHECK_LOCATION().AsString()));
545 }
546 }
547 }
548 for (uint i = 0; i < outNames.size(); ++i)
549 {
550 if(needCompute)
551 {
552 m_TensorsInfo[outNames[i]] = OnnxTensor();
553 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
554 TensorInfo(inferredShapes[i], armnnType));
555 m_TensorsInfo[outNames[i]].m_dtype = dataType;
556 }
telsoa01c577f2c2018-08-31 09:22:23 +0100557 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
Narumol Prangnawarat452274c2021-09-23 16:12:19 +0100558 }
559 return outInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100560}
561
Kevin Mayef33cb12021-01-29 14:24:57 +0000562OnnxParserImpl::OnnxParserImpl()
telsoa01c577f2c2018-08-31 09:22:23 +0100563 : m_Network(nullptr, nullptr)
564{
565}
566
Kevin Mayef33cb12021-01-29 14:24:57 +0000567void OnnxParserImpl::ResetParser()
telsoa01c577f2c2018-08-31 09:22:23 +0100568{
569 m_Network = armnn::INetworkPtr(nullptr, nullptr);
570 m_Graph = nullptr;
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100571 m_InputInfos.clear();
572 m_OutputInfos.clear();
telsoa01c577f2c2018-08-31 09:22:23 +0100573}
574
Kevin Mayef33cb12021-01-29 14:24:57 +0000575void OnnxParserImpl::Cleanup()
telsoa01c577f2c2018-08-31 09:22:23 +0100576{
577 m_TensorConnections.clear();
578 m_TensorsInfo.clear();
579 m_OutputsMap.clear();
580 m_OutputsFusedAndUsed.clear();
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100581 m_InputShapes.clear();
telsoa01c577f2c2018-08-31 09:22:23 +0100582}
583
Jan Eilers53ef7952021-06-02 12:01:25 +0100584template<typename T>
585std::pair<armnn::ConstTensor, std::unique_ptr<T[]>>
586CreateConstTensorImpl(const T* bufferPtr,
587 armnn::TensorInfo& tensorInfo,
588 const armnn::Optional<armnn::PermutationVector&> permutationVector)
telsoa01c577f2c2018-08-31 09:22:23 +0100589{
Ryan OSheac229b3f2023-06-27 22:34:54 +0100590 if (bufferPtr == nullptr)
591 {
592 throw armnn::ParseException(fmt::format("Buffer for permutation is null {}", CHECK_LOCATION().AsString()));
593 }
Jan Eilers53ef7952021-06-02 12:01:25 +0100594
595 std::unique_ptr<T[]> data(new T[tensorInfo.GetNumElements()]);
596
597 if (permutationVector.has_value() && permutationVector.value().GetSize() > 0)
598 {
599 tensorInfo = armnnUtils::Permuted(tensorInfo, permutationVector.value());
600 armnnUtils::Permute(tensorInfo.GetShape(), permutationVector.value(),
601 reinterpret_cast<const T*>(bufferPtr), data.get(), sizeof(T));
602 }
603 else
604 {
605 ::memcpy(data.get(), bufferPtr, tensorInfo.GetNumBytes());
606 }
607
608 return std::make_pair(ConstTensor(tensorInfo, data.get()), std::move(data));
609}
610
611std::pair<ConstTensor, std::unique_ptr<float[]>>
612OnnxParserImpl::CreateConstTensor(const std::string name,
613 armnn::Optional<armnn::PermutationVector&> permutationVector)
614{
615 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
telsoa01c577f2c2018-08-31 09:22:23 +0100616 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
617
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100618 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
619 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
620 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
621
Matthew Sloyan81beae32021-07-13 19:46:11 +0100622 // Makes sure IsConstant flag is set.
623 tensorInfo.SetConstant();
624
Jan Eilers53ef7952021-06-02 12:01:25 +0100625 // Const tensors requires at least a list of values
626 if (tensorInfo.GetNumElements() == 0)
627 {
628 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
629 name,
630 CHECK_LOCATION().AsString()));
631 }
632
telsoa01c577f2c2018-08-31 09:22:23 +0100633 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100634 // Copy the value list entries into the destination
635 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100636 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100637 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
638 {
James Ward58dec6b2020-09-11 17:32:44 +0100639 throw ParseException(
640 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
641 "elements ({}) {}",
642 onnxTensor.float_data_size(),
643 name,
644 tensorInfo.GetNumElements(),
645 CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100646 }
Jan Eilers53ef7952021-06-02 12:01:25 +0100647 return CreateConstTensorImpl<float>(srcData, tensorInfo, permutationVector);
telsoa01c577f2c2018-08-31 09:22:23 +0100648 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100649 else
650 {
Jan Eilers53ef7952021-06-02 12:01:25 +0100651 return CreateConstTensorImpl<float>(reinterpret_cast<const float*>(onnxTensor.raw_data().c_str()),
652 tensorInfo,
653 permutationVector);
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100654 }
telsoa01c577f2c2018-08-31 09:22:23 +0100655}
656
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100657std::pair<ConstTensor, std::unique_ptr<int32_t[]>>
658OnnxParserImpl::CreateInt64ConstTensor(const std::string name,
659 armnn::Optional<armnn::PermutationVector&> permutationVector)
660{
661 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
662 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
663
664 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
665 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::INT64);
666
667 // Makes sure IsConstant flag is set.
668 tensorInfo.SetConstant();
669 uint numElements = tensorInfo.GetNumElements();
670
671 // Const tensors requires at least a list of values
672 if (numElements == 0)
673 {
674 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
675 name,
676 CHECK_LOCATION().AsString()));
677 }
678
679 // Copy the value list entries into the destination
680 if (!onnxTensor.has_raw_data())
681 {
682 auto srcData = onnxTensor.int64_data().data();
683 if(numElements != static_cast<uint>(onnxTensor.int64_data_size()))
684 {
685 throw ParseException(
686 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
687 "elements ({}) {}",
688 onnxTensor.int64_data_size(),
689 name,
690 tensorInfo.GetNumElements(),
691 CHECK_LOCATION().AsString()));
692 }
693
694 std::vector<int32_t> int32Data;
695 for(uint i = 0; i < numElements; i++)
696 {
697 int32_t int32Value = CHECKED_INT32(srcData[i]);
698 int32Data.push_back(int32Value);
699 }
700
701 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
702 }
703 else
704 {
705 auto srcData = reinterpret_cast<const int64_t*>(onnxTensor.raw_data().c_str());
706 std::vector<int32_t> int32Data;
707 for(uint i = 0; i < numElements; i++)
708 {
709 int32_t int32Value = CHECKED_INT32(srcData[i]);
710 int32Data.push_back(int32Value);
711 }
712 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
713 }
714}
715
Kevin Mayef33cb12021-01-29 14:24:57 +0000716ModelPtr OnnxParserImpl::LoadModelFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100717{
718 FILE* fd = fopen(graphFile, "r");
719
720 if (fd == nullptr)
721 {
James Ward58dec6b2020-09-11 17:32:44 +0100722 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100723 }
724
725 // Parse the file into a message
726 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
727 using google::protobuf::io::FileInputStream;
728 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
729 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
730 fclose(fd);
731
732 if (!success)
733 {
734 std::stringstream error;
735 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100736 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100737 }
738 return modelProto;
739}
740
Kevin Mayef33cb12021-01-29 14:24:57 +0000741INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100742{
743 ResetParser();
744 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
745 return CreateNetworkFromModel(*modelProto);
746}
747
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100748INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile,
749 const std::map<std::string, armnn::TensorShape>& inputShapes)
750{
751 ResetParser();
752 m_InputShapes = inputShapes;
753 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
754 return CreateNetworkFromModel(*modelProto);
755}
telsoa01c577f2c2018-08-31 09:22:23 +0100756
Mike Kelly2ae32242022-11-25 13:55:24 +0000757INetworkPtr OnnxParserImpl::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent)
758{
759 ResetParser();
760 ModelPtr modelProto = LoadModelFromBinary(binaryContent);
761 return CreateNetworkFromModel(*modelProto);
762}
763
764INetworkPtr OnnxParserImpl::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent,
765 const std::map<std::string, armnn::TensorShape>& inputShapes)
766{
767 ResetParser();
768 m_InputShapes = inputShapes;
769 ModelPtr modelProto = LoadModelFromBinary(binaryContent);
770 return CreateNetworkFromModel(*modelProto);
771}
772
773ModelPtr OnnxParserImpl::LoadModelFromBinary(const std::vector<uint8_t>& binaryContent)
774{
775 if (binaryContent.size() == 0)
776 {
777 throw ParseException(fmt::format("Missing binary content", CHECK_LOCATION().AsString()));
778 }
779 // Parse the file into a message
780 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
781
782 google::protobuf::io::CodedInputStream codedStream(binaryContent.data(), static_cast<int>(binaryContent.size()));
783 codedStream.SetTotalBytesLimit(INT_MAX);
784 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
785
786 if (!success)
787 {
788 std::stringstream error;
789 error << "Failed to parse graph";
790 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
791 }
792 return modelProto;
793}
794
Kevin Mayef33cb12021-01-29 14:24:57 +0000795ModelPtr OnnxParserImpl::LoadModelFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100796{
797 FILE* fd = fopen(graphFile, "rb");
798
799 if (fd == nullptr)
800 {
James Ward58dec6b2020-09-11 17:32:44 +0100801 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100802 }
803
804 // Parse the file into a message
805 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
806
807 google::protobuf::io::FileInputStream inStream(fileno(fd));
808 google::protobuf::io::CodedInputStream codedStream(&inStream);
Nikhil Raje5181532020-10-09 14:52:25 +0100809 codedStream.SetTotalBytesLimit(INT_MAX);
telsoa01c577f2c2018-08-31 09:22:23 +0100810 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
811 fclose(fd);
812
813 if (!success)
814 {
815 std::stringstream error;
816 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100817 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100818 }
819 return modelProto;
820
821}
822
Kevin Mayef33cb12021-01-29 14:24:57 +0000823INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100824{
825 ResetParser();
826 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
827 return CreateNetworkFromModel(*modelProto);
828}
829
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100830INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile,
831 const std::map<std::string, armnn::TensorShape>& inputShapes)
832{
833 ResetParser();
834 m_InputShapes = inputShapes;
835 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
836 return CreateNetworkFromModel(*modelProto);
837}
838
Kevin Mayef33cb12021-01-29 14:24:57 +0000839ModelPtr OnnxParserImpl::LoadModelFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100840{
841 if (protoText == "")
842 {
James Ward58dec6b2020-09-11 17:32:44 +0100843 throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
844 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100845 }
846 // Parse the string into a message
847 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
848 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
849 if (!success)
850 {
851 std::stringstream error;
852 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100853 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100854 }
855 return modelProto;
856}
857
Kevin Mayef33cb12021-01-29 14:24:57 +0000858INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100859{
860 ResetParser();
861 ModelPtr modelProto = LoadModelFromString(protoText);
862 return CreateNetworkFromModel(*modelProto);
863}
864
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100865INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText,
866 const std::map<std::string, armnn::TensorShape>& inputShapes)
867{
868 ResetParser();
869 m_InputShapes = inputShapes;
870 ModelPtr modelProto = LoadModelFromString(protoText);
871 return CreateNetworkFromModel(*modelProto);
872}
873
Kevin Mayef33cb12021-01-29 14:24:57 +0000874INetworkPtr OnnxParserImpl::CreateNetworkFromModel(onnx::ModelProto& model)
telsoa01c577f2c2018-08-31 09:22:23 +0100875{
876 m_Network = INetwork::Create();
877 try
878 {
879 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
880 LoadGraph();
881 }
882 catch (const ParseException& e)
883 {
884 Cleanup();
885 throw e;
886 }
887 Cleanup();
888 return std::move(m_Network);
889}
890
Kevin Mayef33cb12021-01-29 14:24:57 +0000891void OnnxParserImpl::LoadGraph()
telsoa01c577f2c2018-08-31 09:22:23 +0100892{
Ryan OSheac229b3f2023-06-27 22:34:54 +0100893 if (m_Graph.get() == nullptr)
894 {
895 throw armnn::ParseException(fmt::format("Graph pointer is null {}", CHECK_LOCATION().AsString()));
896 }
telsoa01c577f2c2018-08-31 09:22:23 +0100897
898 //Fill m_TensorsInfo with the shapes and value of every tensor
899 SetupInfo(m_Graph->mutable_output());
900 SetupInfo(m_Graph->mutable_input());
901 SetupInfo(m_Graph->mutable_value_info());
902
903 for (auto tensor : m_Graph->initializer())
904 {
905 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000906 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
907 m_TensorsInfo[tensor.name()].m_dtype =
908 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100909 }
910
911 SetupInputLayers();
912 SetupOutputLayers();
913
914 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
915 DetectFullyConnected();
916
917 //Parsing the graph
918 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
919 {
920 auto node = m_Graph->node(static_cast<int>(nodeIndex));
921 const std::string& operation = node.op_type();
922
923 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000924 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100925 {
926 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
927 {
928 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
929 AddFullyConnected(node);
930 }
931 }
932 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
933 {
934 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
935 AddFullyConnected(m_Graph->node(matmulIndex), &node);
936 }
937 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
938 {
939 auto it = m_ParserFunctions.find(operation);
940 if (it != m_ParserFunctions.end())
941 {
942 auto func = it->second;
943 (this->*func)(node);
944 }
945 else
946 {
James Ward58dec6b2020-09-11 17:32:44 +0100947 throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
948 operation,
949 node.name(),
950 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100951 }
952 }
953 }
954
955 //Making the connections between outputs and inputs of each layers
956 for (const auto& tensorCon : m_TensorConnections)
957 {
958 if (tensorCon.second.outputSlot != nullptr)
959 {
960 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
961 {
962 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
963 }
964 }
965 }
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +0100966
967 // Get output info.
968 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
969 {
970 auto output = m_Graph->output(outputIndex);
971 m_OutputInfos[output.name()] = *m_TensorsInfo[output.name()].m_info;
972 }
telsoa01c577f2c2018-08-31 09:22:23 +0100973}
974
Kevin Mayef33cb12021-01-29 14:24:57 +0000975void OnnxParserImpl::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
telsoa01c577f2c2018-08-31 09:22:23 +0100976{
977 for (auto tensor : *list)
978 {
979 m_TensorsInfo[tensor.name()] = OnnxTensor();
980 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000981 m_TensorsInfo[tensor.name()].m_dtype =
982 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100983 }
984}
985
Kevin Mayef33cb12021-01-29 14:24:57 +0000986void OnnxParserImpl::DetectFullyConnected()
telsoa01c577f2c2018-08-31 09:22:23 +0100987{
988 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
989 auto matmulAndConstant = [&](const std::string& constInput,
990 const std::string& matmulInput,
991 int& nodeIndex)
992 {
993 auto matmulIt = m_OutputsMap.find(matmulInput);
994 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
995 && m_TensorsInfo[constInput].isConstant())
996 {
997 nodeIndex = matmulIt->second.second;
998 return true;
999 }
1000 return false;
1001 };
1002
1003 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
1004 {
1005 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
1006 for (const std::string& output : node->output())
1007 {
1008 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
1009 }
1010
1011 for (const std::string& input : node->input()) //count how many time a node is used as input
1012 {
1013 auto matmulIt = m_OutputsMap.find(input);
1014 if(matmulIt != m_OutputsMap.end()){
1015 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
1016 }
1017 }
1018
1019 if (node->op_type() == "Add")
1020 {
1021 int matmulIndex = 0;
1022 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
1023 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
1024 {
1025 //matmul and add were fused
1026 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
1027 .push_back(static_cast<size_t>(nodeIndex));
1028
1029 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
1030 .push_back(static_cast<size_t>(matmulIndex));
1031 }
1032 }
1033 }
1034
1035 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
1036 auto matmulIt = m_OutputsMap.find(output.name());
1037 if(matmulIt != m_OutputsMap.end()){
1038 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
1039 }
1040 }
1041}
1042
1043template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +00001044void OnnxParserImpl::GetInputAndParam(const onnx::NodeProto& node,
1045 std::string* inputName,
1046 std::string* constName,
1047 const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +01001048{
1049 int cstIndex;
1050 if (m_TensorsInfo[node.input(0)].isConstant())
1051 {
1052 cstIndex = 0;
1053 }
1054 else if (m_TensorsInfo[node.input(1)].isConstant())
1055 {
1056 cstIndex = 1;
1057 }
1058 else
1059 {
James Ward58dec6b2020-09-11 17:32:44 +01001060 throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
1061 node.input(0),
1062 node.input(1),
1063 node.name(),
1064 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001065 }
1066 if(constName)
1067 {
1068 *constName = node.input(cstIndex);
1069 }
1070 if(inputName)
1071 {
1072 *inputName = node.input(!cstIndex);
1073 }
1074}
1075
1076template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +00001077void OnnxParserImpl::To1DTensor(const std::string& name, const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +01001078{
1079 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
1080 std::vector<uint32_t> newShape;
1081 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
1082 {
1083 if(shape[i] != 1)
1084 {
James Ward58dec6b2020-09-11 17:32:44 +01001085 throw ParseException(
1086 fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
1087 TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
1088 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001089 }
1090 }
1091 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
1092
1093 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1094}
1095
Kevin Mayef33cb12021-01-29 14:24:57 +00001096void OnnxParserImpl::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001097{
1098 ARMNN_ASSERT(node.op_type() == "Conv");
1099
1100 DepthwiseConvolution2dDescriptor desc;
1101 desc.m_PadLeft = convDesc.m_PadLeft;
1102 desc.m_PadRight = convDesc.m_PadRight;
1103 desc.m_PadTop = convDesc.m_PadTop;
1104 desc.m_PadBottom = convDesc.m_PadBottom;
1105 desc.m_StrideX = convDesc.m_StrideX;
1106 desc.m_StrideY = convDesc.m_StrideY;
1107 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
1108
Cathal Corbett06902652022-04-14 17:55:11 +01001109 armnn::IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, node.name().c_str());
Cathal Corbett541880f2022-05-16 15:20:56 +01001110 std::string permuteStr = "permute_" + node.input(1);
1111 std::vector<std::string> tensorIndexes= {node.input(0), permuteStr};
Jan Eilers53ef7952021-06-02 12:01:25 +01001112
Cathal Corbett541880f2022-05-16 15:20:56 +01001113 auto weightTensor = CreateConstTensor(node.input(1));
Cathal Corbett06902652022-04-14 17:55:11 +01001114 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
Cathal Corbett541880f2022-05-16 15:20:56 +01001115
1116 // weights come in as [O,1,H,W] from ONNX and need to be converted to ArmNNs depthwise weights layout [1,H,W,O]
1117 armnn::PermutationVector perVec {3, 0, 1, 2};
1118 TensorInfo weightsPermuted = armnnUtils::Permuted(weightTensor.first.GetInfo(), perVec);
1119
1120 // Inserts NewLayer so layers don't need to be re-sorted.
1121 IConnectableLayer* permuteLayer = m_Network->AddPermuteLayer(PermuteDescriptor(perVec),
1122 "permute_layer");
1123 permuteLayer->GetOutputSlot(0).SetTensorInfo(weightsPermuted);
1124 permuteLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1125
Cathal Corbett06902652022-04-14 17:55:11 +01001126 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
Cathal Corbett541880f2022-05-16 15:20:56 +01001127 weightsLayer->GetOutputSlot(0).Connect(permuteLayer->GetInputSlot(0u));
Cathal Corbett06902652022-04-14 17:55:11 +01001128
Ryan OSheaed27ee72020-04-22 16:37:29 +01001129 if (node.input_size() == 3)
1130 {
1131 if(!m_TensorsInfo[node.input(2)].isConstant())
1132 {
James Ward58dec6b2020-09-11 17:32:44 +01001133 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1134 node.input(2),
1135 node.name(),
1136 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001137 }
Cathal Corbett06902652022-04-14 17:55:11 +01001138
Ryan OSheaed27ee72020-04-22 16:37:29 +01001139 desc.m_BiasEnabled = true;
1140 auto biasTensor = CreateConstTensor(node.input(2));
Cathal Corbett06902652022-04-14 17:55:11 +01001141 tensorIndexes.emplace_back(node.input(2));
1142
1143 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1144 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1145 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001146 }
Cathal Corbett06902652022-04-14 17:55:11 +01001147
Ryan OSheac229b3f2023-06-27 22:34:54 +01001148 if (!layer)
1149 {
1150 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1151 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001152
1153 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1154 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
Cathal Corbett541880f2022-05-16 15:20:56 +01001155 weightsPermuted.GetShape() });
Ryan OSheaed27ee72020-04-22 16:37:29 +01001156
1157 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1158
1159 // register the input connection slots for the layer, connections are made after all layers have been created
1160 // only the tensors for the inputs are relevant, exclude the const tensors
Cathal Corbett06902652022-04-14 17:55:11 +01001161 RegisterInputSlots(layer, tensorIndexes);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001162
1163 // register the output connection slots for the layer, connections are made after all layers have been created
1164 RegisterOutputSlots(layer, {node.output(0)});
1165}
1166
Kevin Mayef33cb12021-01-29 14:24:57 +00001167void OnnxParserImpl::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
telsoa01c577f2c2018-08-31 09:22:23 +01001168{
telsoa01c577f2c2018-08-31 09:22:23 +01001169 // find matmul inputs
telsoa01c577f2c2018-08-31 09:22:23 +01001170 std::string inputName;
Matthew Sloyanca361232023-02-16 14:50:22 +00001171 std::string weightName;
1172 std::string biasName;
1173 std::string outputName;
telsoa01c577f2c2018-08-31 09:22:23 +01001174 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
1175 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
1176 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
1177
1178 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
1179
Matthew Sloyanca361232023-02-16 14:50:22 +00001180 TensorInfo inputInfo = *m_TensorsInfo[inputName].m_info;
1181 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1182 TensorInfo biasInfo;
1183
1184 std::vector<std::string> inputNames;
1185
telsoa01c577f2c2018-08-31 09:22:23 +01001186 FullyConnectedDescriptor desc;
1187 desc.m_BiasEnabled = addNode != nullptr;
1188
1189 IConnectableLayer* layer = nullptr;
1190 if(desc.m_BiasEnabled)
1191 {
1192 // find bias const
telsoa01c577f2c2018-08-31 09:22:23 +01001193 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
1194 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
1195 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
1196
1197 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
1198
1199 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
1200 To1DTensor(biasName, CHECK_LOCATION());
Matthew Sloyanca361232023-02-16 14:50:22 +00001201 biasInfo = *m_TensorsInfo[biasName].m_info;
telsoa01c577f2c2018-08-31 09:22:23 +01001202
1203 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
1204 {
James Ward58dec6b2020-09-11 17:32:44 +01001205 throw ParseException(
1206 fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
1207 " and {} ( /!\\ bias should be a 1D tensor) {}",
1208 weightName,
1209 addNode->name(),
1210 TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
1211 m_TensorsInfo[weightName].m_dtype),
1212 TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
1213 m_TensorsInfo[biasName].m_dtype ),
1214 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001215 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01001216
Matthew Sloyanca361232023-02-16 14:50:22 +00001217 inputNames = { inputName, weightName, biasName };
1218 outputName = addNode->output(0);
telsoa01c577f2c2018-08-31 09:22:23 +01001219 }
1220 else
1221 {
Matthew Sloyanca361232023-02-16 14:50:22 +00001222 inputNames = { inputName, weightName };
1223 outputName = matmulNode.output(0);
1224 }
telsoa01c577f2c2018-08-31 09:22:23 +01001225
Matthew Sloyanca361232023-02-16 14:50:22 +00001226 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1227 layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001228
1229 if (!layer)
1230 {
1231 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1232 }
telsoa01c577f2c2018-08-31 09:22:23 +01001233
Matthew Sloyanca361232023-02-16 14:50:22 +00001234 if (inputInfo.GetNumDimensions() > 2)
1235 {
1236 // Add reshape to flatten to 2D [batch_size, input_size],
1237 // where "input_size" corresponds to the number of inputs to the layer,
1238 // matching the second dimension of weights,
1239 // and "batch_size" is calculated by dividing the number of elements by "input_size".
1240 std::vector<unsigned int> reshapedDimensions(2);
1241 reshapedDimensions[1] = weightInfo.GetShape()[0];
1242 reshapedDimensions[0] = inputInfo.GetNumElements() / reshapedDimensions[1];
1243
1244 if (inputInfo.GetNumElements() % reshapedDimensions[1] != 0)
Matthew Sloyan81beae32021-07-13 19:46:11 +01001245 {
Matthew Sloyanca361232023-02-16 14:50:22 +00001246 throw ParseException(
1247 fmt::format("Failed to deduce input tensor shape from filter size {} {}",
1248 reshapedDimensions[1],
1249 CHECK_LOCATION().AsString()));
Matthew Sloyan81beae32021-07-13 19:46:11 +01001250 }
1251
Matthew Sloyanca361232023-02-16 14:50:22 +00001252 TensorInfo reshapedTensorInfo = inputInfo;
1253 reshapedTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
1254 inputInfo = reshapedTensorInfo;
1255
1256 ReshapeDescriptor reshapeDescriptor;
1257 reshapeDescriptor.m_TargetShape = reshapedTensorInfo.GetShape();
1258
1259 std::string reshapeLayerName = fmt::format("Reshape_for:{}", layer->GetName());
1260 IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(reshapeDescriptor, reshapeLayerName.c_str());
1261
1262 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedTensorInfo);
1263 reshapeLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
1264
1265 RegisterInputSlots(reshapeLayer, {inputName});
1266 inputNames[0] = reshapeLayerName;
telsoa01c577f2c2018-08-31 09:22:23 +01001267 }
Matthew Sloyanca361232023-02-16 14:50:22 +00001268
1269 auto outputInfo = ComputeOutputInfo({ outputName },
1270 layer,
1271 { inputInfo.GetShape(),
1272 weightInfo.GetShape() });
1273 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1274
1275 RegisterInputSlots(layer, inputNames);
1276
1277 // Add constant layer to store weights/biases and connect to FullyConnected layer..
1278 if(m_TensorsInfo[weightName].isConstant())
1279 {
1280 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1281
1282 weightInfo.SetConstant();
1283 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1284 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1285 }
1286
1287 if(desc.m_BiasEnabled && m_TensorsInfo[biasName].isConstant())
1288 {
1289 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(biasName).first);
1290
1291 biasInfo.SetConstant();
1292 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1293 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1294 }
1295
1296 if (outputInfo[0].GetNumDimensions() > 2)
1297 {
1298 // Calculate reshape to flatten to 2D [batch_size, input_size]
1299 std::vector<unsigned int> reshapedDimensions(2);
1300 reshapedDimensions[1] = weightInfo.GetShape()[1];
1301 reshapedDimensions[0] = outputInfo[0].GetNumElements() / reshapedDimensions[1];
1302
1303 if (outputInfo[0].GetNumElements() % reshapedDimensions[1] != 0)
1304 {
1305 throw ParseException(
1306 fmt::format("Failed to deduce output tensor shape from filter size {} {}",
1307 reshapedDimensions[1],
1308 CHECK_LOCATION().AsString()));
1309 }
1310
1311 armnn::TensorInfo reshapedOutputTensorInfo = outputInfo[0];
1312 reshapedOutputTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
1313 layer->GetOutputSlot(0).SetTensorInfo(reshapedOutputTensorInfo);
1314
1315 ReshapeDescriptor desc;
1316 desc.m_TargetShape = outputInfo[0].GetShape();
1317
1318 std::string reshapeLayerName = fmt::format("ExpandDims_for:{}", layer->GetName());
1319 IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(desc, reshapeLayerName.c_str());
1320
1321 layer->GetOutputSlot(0).Connect(reshapeLayer->GetInputSlot(0));
1322 reshapeLayer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1323
1324 RegisterInputSlots(reshapeLayer, {layer->GetName()});
1325 layer = reshapeLayer;
1326 }
1327
1328 RegisterOutputSlots(layer, { outputName });
telsoa01c577f2c2018-08-31 09:22:23 +01001329}
1330
Kevin Mayef33cb12021-01-29 14:24:57 +00001331void OnnxParserImpl::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
telsoa01c577f2c2018-08-31 09:22:23 +01001332{
1333
1334 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1335 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1336
1337 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1338
1339 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
1340 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1341 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1342
1343 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
1344 desc.m_PoolWidth = kernel_shape[1];
1345 desc.m_PoolHeight = kernel_shape[0];
1346
1347 if(strides.empty())
1348 {
1349 desc.m_StrideX = 1;
1350 desc.m_StrideY = 1;
1351 }
1352 else
1353 {
1354 desc.m_StrideX = strides[1];
1355 desc.m_StrideY = strides[0];
1356 }
1357
1358 //Check new padding version first
1359 if(pads.empty())
1360 {
1361 //Check deprecated version
1362 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1363 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1364 {
1365 bool isUpper;
1366 if( paddingString == "SAME_LOWER")
1367 {
1368 isUpper = false;
1369 }
1370 else if (paddingString == "SAME_UPPER")
1371 {
1372 isUpper = true;
1373 }
1374 else
1375 {
James Ward58dec6b2020-09-11 17:32:44 +01001376 throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
1377 "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
1378 node.name(),
1379 paddingString,
1380 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001381 }
1382 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1383 uint32_t inputHeight = inputInfo.GetShape()[2];
1384 uint32_t inputWidth = inputInfo.GetShape()[3];
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001385 CalcPadding(inputHeight,
1386 desc.m_PoolHeight,
1387 desc.m_StrideY,
1388 1u,
1389 &desc.m_PadTop,
1390 &desc.m_PadBottom,
1391 isUpper);
1392 CalcPadding(inputWidth,
1393 desc.m_PoolWidth,
1394 desc.m_StrideX,
1395 1u,
1396 &desc.m_PadLeft,
1397 &desc.m_PadRight,
1398 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001399 }
1400 }
1401 else
1402 {
1403 desc.m_PadTop = pads[0];
1404 desc.m_PadLeft = pads[1];
1405 desc.m_PadBottom = pads[2];
1406 desc.m_PadRight = pads[3];
1407 }
1408
1409 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001410
1411 if (!layer)
1412 {
1413 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1414 }
telsoa01c577f2c2018-08-31 09:22:23 +01001415
1416 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1417 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1418
1419 // register the input connection slots for the layer, connections are made after all layers have been created
1420 // only the tensors for the inputs are relevant, exclude the const tensors
1421 RegisterInputSlots(layer, {node.input(0)});
1422
1423 // register the output connection slots for the layer, connections are made after all layers have been created
1424 RegisterOutputSlots(layer, {node.output(0)});
1425}
1426
Kevin Mayef33cb12021-01-29 14:24:57 +00001427std::pair<std::string, std::string> OnnxParserImpl::AddPrepareBroadcast(const std::string& input0,
1428 const std::string& input1)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001429{
1430 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1431
1432 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1433 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1434
1435 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1436 {
James Ward58dec6b2020-09-11 17:32:44 +01001437 auto outputName = fmt::format("reshape_output_{}", input1);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001438 PrependForBroadcast(outputName, input1, input0);
1439 inputs.second = outputName;
1440 }
1441 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1442 {
James Ward58dec6b2020-09-11 17:32:44 +01001443 auto outputName = fmt::format("reshape_output_{}", input0);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001444 PrependForBroadcast(outputName, input0, input1);
1445 inputs.first = outputName;
1446 }
1447 return inputs;
1448}
1449
Kevin Mayef33cb12021-01-29 14:24:57 +00001450void OnnxParserImpl::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001451{
1452 auto armnnTensor = CreateConstTensor(tensorName);
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001453 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1454 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1455 RegisterOutputSlots(layer, {tensorName});
1456}
Ryan OSheaed27ee72020-04-22 16:37:29 +01001457
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001458void OnnxParserImpl::CreateInt64ConstantLayer(const std::string& tensorName, const std::string& layerName)
1459{
1460 auto armnnTensor = CreateInt64ConstTensor(tensorName);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001461 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1462 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1463 RegisterOutputSlots(layer, {tensorName});
1464}
1465
Kevin Mayef33cb12021-01-29 14:24:57 +00001466void OnnxParserImpl::CreateReshapeLayer(const std::string& inputName,
1467 const std::string& outputName,
1468 const std::string& layerName)
telsoa01c577f2c2018-08-31 09:22:23 +01001469{
1470 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1471 ReshapeDescriptor reshapeDesc;
1472 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1473
1474 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001475
1476 if (!layer)
1477 {
1478 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1479 }
1480
telsoa01c577f2c2018-08-31 09:22:23 +01001481 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1482
1483 // register the input connection slots for the layer, connections are made after all layers have been created
1484 // only the tensors for the inputs are relevant, exclude the const tensors
1485 RegisterInputSlots(layer, {inputName});
1486
1487 // register the output connection slots for the layer, connections are made after all layers have been created
1488 RegisterOutputSlots(layer, {outputName});
1489}
1490
Kevin Mayef33cb12021-01-29 14:24:57 +00001491void OnnxParserImpl::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001492{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001493 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001494 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1495
1496 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1497
1498 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001499 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001500
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001501 if (func == ActivationFunction::BoundedReLu)
1502 {
Narumol Prangnawaratf106ab72021-09-15 17:30:37 +01001503 if (node.input_size() == 1 && node.attribute_size() > 0)
1504 {
1505 desc.m_A = ReadOptionalNodeFloatAttribute(node, "max", std::numeric_limits<float>::max());
1506 desc.m_B = ReadOptionalNodeFloatAttribute(node, "min", std::numeric_limits<float>::lowest());
1507 }
1508 else
1509 {
1510 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1511 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1512 }
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001513 }
1514
telsoa01c577f2c2018-08-31 09:22:23 +01001515 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001516
1517 if (!layer)
1518 {
1519 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1520 }
telsoa01c577f2c2018-08-31 09:22:23 +01001521
1522 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1523 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1524
1525 // register the input connection slots for the layer, connections are made after all layers have been created
1526 // only the tensors for the inputs are relevant, exclude the const tensors
1527 RegisterInputSlots(layer, {node.input(0)});
1528
1529 // register the output connection slots for the layer, connections are made after all layers have been created
1530 RegisterOutputSlots(layer, {node.output(0)});
1531}
1532
Kevin Mayef33cb12021-01-29 14:24:57 +00001533void OnnxParserImpl::ParseClip(const onnx::NodeProto& node)
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001534{
1535 ParseActivation(node, ActivationFunction::BoundedReLu);
1536}
1537
Kevin Mayef33cb12021-01-29 14:24:57 +00001538void OnnxParserImpl::ParseSigmoid(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001539{
1540 ParseActivation(node, ActivationFunction::Sigmoid);
1541}
1542
Kevin Mayef33cb12021-01-29 14:24:57 +00001543void OnnxParserImpl::ParseTanh(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001544{
1545 ParseActivation(node, ActivationFunction::TanH);
1546}
1547
Kevin Mayef33cb12021-01-29 14:24:57 +00001548void OnnxParserImpl::ParseRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001549{
1550 ParseActivation(node, ActivationFunction::ReLu);
1551}
1552
Kevin Mayef33cb12021-01-29 14:24:57 +00001553void OnnxParserImpl::ParseLeakyRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001554{
1555 ParseActivation(node, ActivationFunction::LeakyReLu);
1556}
telsoa01c577f2c2018-08-31 09:22:23 +01001557
Kevin Mayef33cb12021-01-29 14:24:57 +00001558void OnnxParserImpl::ParseAdd(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001559{
Ryan OSheaed27ee72020-04-22 16:37:29 +01001560 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1561 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +01001562
Ryan OSheaed27ee72020-04-22 16:37:29 +01001563 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
telsoa01c577f2c2018-08-31 09:22:23 +01001564
David Monahanf6585542023-05-08 13:14:32 +01001565 // IVGCVSW-1576: unify broadcast validation code across layers
telsoa01c577f2c2018-08-31 09:22:23 +01001566
Ryan OSheaed27ee72020-04-22 16:37:29 +01001567 // Checking broadcast compatibility : only scalar or 1D tensors
1568 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1569 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1570 auto input1 = *m_TensorsInfo[inputs.second].m_info;
Ryan OSheac229b3f2023-06-27 22:34:54 +01001571 if (input0.GetNumDimensions() != input1.GetNumDimensions())
1572 {
1573 throw armnn::ParseException(fmt::format("Dimension mismatch in node {} {}",
1574 node.name(),
1575 CHECK_LOCATION().AsString()));
1576 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001577
1578 unsigned int numDims = input0.GetNumDimensions();
1579 for (unsigned int i = 0; i < numDims; i++)
telsoa01c577f2c2018-08-31 09:22:23 +01001580 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01001581 unsigned int dim0 = input0.GetShape()[i];
1582 unsigned int dim1 = input1.GetShape()[i];
1583 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
telsoa01c577f2c2018-08-31 09:22:23 +01001584 {
James Ward58dec6b2020-09-11 17:32:44 +01001585 throw ParseException(
1586 fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1587 "Input dimensions should either match or one should be of size 1 and here, "
1588 "{} and {} {}",
1589 node.name(),
1590 TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1591 m_TensorsInfo[inputs.first].m_dtype),
1592 TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1593 m_TensorsInfo[inputs.second].m_dtype),
1594 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001595 }
telsoa01c577f2c2018-08-31 09:22:23 +01001596 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001597
1598
Mike Kelly3ec30772023-03-08 13:47:17 +00001599 IConnectableLayer* layer = m_Network->AddElementwiseBinaryLayer(BinaryOperation::Add, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001600
1601 if (!layer)
1602 {
1603 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1604 }
telsoa01c577f2c2018-08-31 09:22:23 +01001605
1606 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
Ryan OSheaed27ee72020-04-22 16:37:29 +01001607 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1608 m_TensorsInfo[inputs.second].m_info->GetShape() });
telsoa01c577f2c2018-08-31 09:22:23 +01001609 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1610
Ryan OSheaed27ee72020-04-22 16:37:29 +01001611 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1612 if(m_TensorsInfo[inputs.first].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001613 CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001614 }
1615 if(m_TensorsInfo[inputs.second].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001616 CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001617 }
1618 RegisterInputSlots(layer, {inputs.first, inputs.second});
telsoa01c577f2c2018-08-31 09:22:23 +01001619
Ryan OSheaed27ee72020-04-22 16:37:29 +01001620 // register the output connection
telsoa01c577f2c2018-08-31 09:22:23 +01001621 RegisterOutputSlots(layer, {node.output(0)});
1622}
1623
Kevin Mayef33cb12021-01-29 14:24:57 +00001624void OnnxParserImpl::ParseAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001625{
1626 Pooling2dDescriptor desc;
1627 desc.m_PoolType = PoolingAlgorithm::Average;
1628
1629 uint32_t count_include_pad = 0;
1630 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1631 if(count_include_pad) {
1632 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1633 }
1634 AddPoolingLayer(node, desc);
1635}
1636
Kevin Mayef33cb12021-01-29 14:24:57 +00001637void OnnxParserImpl::ParseBatchNormalization(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001638{
1639 //IGNORE momentum parameter and spatial parameters
1640
1641 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1642 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1643
1644 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1645 for(int ind = 1; ind < node.input_size(); ++ind)
1646 {
1647 auto tensor = node.input(ind);
1648 if(! m_TensorsInfo[tensor].isConstant())
1649 {
James Ward58dec6b2020-09-11 17:32:44 +01001650 throw ParseException(
1651 fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1652 tensor,
1653 node.name(),
1654 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001655 }
1656 }
1657
1658 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1659 BatchNormalizationDescriptor desc;
1660 desc.m_Eps = epsilon;
1661
1662 auto scaleTensor = CreateConstTensor(node.input(1));
1663 auto biasTensor = CreateConstTensor(node.input(2));
1664 auto meanTensor = CreateConstTensor(node.input(3));
1665 auto varTensor = CreateConstTensor(node.input(4));
1666
1667 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1668 meanTensor.first,
1669 varTensor.first,
1670 biasTensor.first,
1671 scaleTensor.first,
1672 node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001673
1674 if (!layer)
1675 {
1676 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1677 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001678
1679 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1680 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1681
1682 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1683
1684 // register the output connection
1685 RegisterOutputSlots(layer, {node.output(0)});
1686}
1687
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001688void OnnxParserImpl::ParseConcat(const onnx::NodeProto& node)
1689{
1690 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1691
1692 uint32_t numConcatView = static_cast<uint32_t>(node.input_size());
1693 uint32_t inputRank = m_TensorsInfo[node.input(0)].m_info->GetNumDimensions();
1694
1695 int axisInt = ReadMandatoryNodeIntAttribute(node, "axis");
1696
1697 unsigned int concatDimInput = static_cast<unsigned int>(
1698 (static_cast<int>(inputRank) + axisInt) % static_cast<int>(inputRank));
1699
1700 OriginsDescriptor concatDescriptor(numConcatView, inputRank);
1701 concatDescriptor.SetConcatAxis(concatDimInput);
1702
1703 unsigned int mergeDimOrigin = 0;
1704
1705 std::vector<TensorShape> inputShapes;
1706 std::vector<std::string> tensorIds;
1707
1708 for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1709 {
1710 std::string nodeName = node.input(static_cast<int>(viewIndex));
1711 auto inputTensorInfo = *m_TensorsInfo[nodeName].m_info;
1712 inputShapes.push_back(inputTensorInfo.GetShape());
1713 tensorIds.push_back(nodeName);
1714
1715 // Set up concatDescriptor view origin
1716 armnnUtils::ProcessConcatInputTensorInfo(
1717 inputTensorInfo, concatDescriptor, concatDimInput, viewIndex, mergeDimOrigin);
1718 }
1719
1720 IConnectableLayer* layer = m_Network->AddConcatLayer(concatDescriptor, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01001721
1722 if (!layer)
1723 {
1724 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1725 }
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001726
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01001727 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, inputShapes,
1728 m_TensorsInfo[node.input(0)].m_dtype);
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001729
1730 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1731
1732 // register the input connection slots for the layer, connections are made after all layers have been created
1733 RegisterInputSlots(layer, tensorIds);
1734
1735 // register the output connection slots for the layer, connections are made after all layers have been created
1736 RegisterOutputSlots(layer, { node.output(0) });
1737}
1738
Kevin Mayef33cb12021-01-29 14:24:57 +00001739void OnnxParserImpl::ParseConstant(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001740{
1741 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1742 if (!node.attribute(0).has_t())
1743 {
James Ward58dec6b2020-09-11 17:32:44 +01001744 throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1745 node.name(),
1746 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001747 }
1748 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1749
Ryan OSheaed27ee72020-04-22 16:37:29 +01001750 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1751 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1752 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1753 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1754
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001755 if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_FLOAT)
1756 {
1757 CreateConstantLayer(node.output(0), node.name());
1758 }
1759 else if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_INT64)
1760 {
1761 CreateInt64ConstantLayer(node.output(0), node.name());
1762 }
1763 else
1764 {
1765 throw ParseException(fmt::format("Data type not support for Constant node '{}' {}",
1766 node.name(),
1767 CHECK_LOCATION().AsString()));
1768 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001769}
1770
Kevin Mayef33cb12021-01-29 14:24:57 +00001771void OnnxParserImpl::ParseConv(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001772{
1773 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1774 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1775
1776 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1777
1778 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1779 {
James Ward58dec6b2020-09-11 17:32:44 +01001780 throw ParseException(
1781 fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1782 node.name(),
1783 TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1784 m_TensorsInfo[node.input(0)].m_dtype),
1785 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001786 }
1787
1788 if(!m_TensorsInfo[node.input(1)].isConstant())
1789 {
James Ward58dec6b2020-09-11 17:32:44 +01001790 throw ParseException(
1791 fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1792 node.input(1),
1793 node.name(),
1794 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001795 }
1796
1797 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1798
telsoa01c577f2c2018-08-31 09:22:23 +01001799 Convolution2dDescriptor desc;
1800 desc.m_BiasEnabled = false;
1801
1802 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1803 if(strides.empty())
1804 {
1805 desc.m_StrideX = 1;
1806 desc.m_StrideY = 1;
1807 }
1808 else
1809 {
1810 desc.m_StrideX = strides[1];
1811 desc.m_StrideY = strides[0];
1812 }
1813
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001814 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1815 if(!dilations.empty())
1816 {
1817 desc.m_DilationX = dilations[1];
1818 desc.m_DilationY = dilations[0];
1819 }
1820
telsoa01c577f2c2018-08-31 09:22:23 +01001821 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1822 //Check new padding version first
1823 if(pads.empty())
1824 {
1825 //Check deprecated version
1826 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1827 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1828 {
1829 bool isUpper;
1830 if( paddingString == "SAME_LOWER")
1831 {
1832 isUpper = false;
1833 }
1834 else if (paddingString == "SAME_UPPER")
1835 {
1836 isUpper = true;
1837 }
1838 else
1839 {
James Ward58dec6b2020-09-11 17:32:44 +01001840 throw ParseException(
1841 fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1842 "supported and found {} {}",
1843 node.name(),
1844 paddingString,
1845 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001846 }
1847 uint32_t inputHeight = inputInfo.GetShape()[2];
1848 uint32_t inputWidth = inputInfo.GetShape()[3];
1849
1850 uint32_t weightHeight;
1851 uint32_t weightWidth;
1852 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1853 if (kernel_shape.empty())
1854 {
1855 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1856 weightHeight = weightTensorInfo.GetShape()[2];
1857 weightWidth = weightTensorInfo.GetShape()[3];
1858 }
1859 else
1860 {
1861 weightHeight = kernel_shape[0];
1862 weightWidth = kernel_shape[1];
1863 }
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001864 CalcPadding(inputHeight,
1865 weightHeight,
1866 desc.m_StrideY,
1867 desc.m_DilationY,
1868 &desc.m_PadTop,
1869 &desc.m_PadBottom,
1870 isUpper);
1871 CalcPadding(inputWidth,
1872 weightWidth,
1873 desc.m_StrideX,
1874 desc.m_DilationX,
1875 &desc.m_PadLeft,
1876 &desc.m_PadRight,
1877 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001878 }
1879 }
1880 else
1881 {
1882 desc.m_PadTop = pads[0];
1883 desc.m_PadLeft = pads[1];
1884 desc.m_PadBottom = pads[2];
1885 desc.m_PadRight = pads[3];
1886 }
1887
1888 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1889 if(group > 1)
1890 {
1891 if (group > inputInfo.GetShape()[1])
1892 {
1893 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001894 fmt::format("Error parsing Convolution node: {}. "
1895 "The 'group'={} parameter cannot be larger than the "
1896 "channel of the input shape={} (in NCHW format). {}",
1897 node.name(),
1898 group,
1899 inputInfo.GetShape()[1],
1900 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001901 }
1902 else if (group == inputInfo.GetShape()[1])
1903 {
1904 // we use a depthwise convolution here, because the number of groups equals to the
1905 // input channels
1906 AddConvLayerWithDepthwiseConv(node, desc);
1907 return;
1908 }
1909 else
1910 {
James Ward58dec6b2020-09-11 17:32:44 +01001911 throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1912 "The 'group'={} parameter should be 1 or be equal to the "
1913 "channel of the input shape={} (in NCHW format). {}",
1914 node.name(),
1915 group,
1916 inputInfo.GetShape()[1],
1917 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001918 }
1919 }
1920
Keith Davis721e6292022-05-17 10:06:53 +01001921 node.input_size() == 3 ? desc.m_BiasEnabled = true : desc.m_BiasEnabled = false;
1922 armnn::IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, node.name().c_str());
Keith Davisb4dd5cc2022-04-07 11:32:00 +01001923 std::vector<std::string> tensorIndexes= {node.input(0), node.input(1)};
1924
telsoa01c577f2c2018-08-31 09:22:23 +01001925 auto weightTensor = CreateConstTensor(node.input(1));
1926
Keith Davis721e6292022-05-17 10:06:53 +01001927 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
1928 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
1929 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1930
telsoa01c577f2c2018-08-31 09:22:23 +01001931 if (node.input_size() == 3)
1932 {
1933 if(!m_TensorsInfo[node.input(2)].isConstant())
1934 {
James Ward58dec6b2020-09-11 17:32:44 +01001935 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1936 node.input(2),
1937 node.name(),
1938 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001939 }
1940 desc.m_BiasEnabled = true;
1941 auto biasTensor = CreateConstTensor(node.input(2));
Keith Davis721e6292022-05-17 10:06:53 +01001942
1943 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1944 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1945 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1946
1947 tensorIndexes.emplace_back(node.input(2));
telsoa01c577f2c2018-08-31 09:22:23 +01001948 }
Keith Davis721e6292022-05-17 10:06:53 +01001949
Ryan OSheac229b3f2023-06-27 22:34:54 +01001950 if (!layer)
1951 {
1952 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1953 }
telsoa01c577f2c2018-08-31 09:22:23 +01001954
1955 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1956 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1957 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1958 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1959
1960 // register the input connection slots for the layer, connections are made after all layers have been created
1961 // only the tensors for the inputs are relevant, exclude the const tensors
Keith Davisb4dd5cc2022-04-07 11:32:00 +01001962 RegisterInputSlots(layer, tensorIndexes);
telsoa01c577f2c2018-08-31 09:22:23 +01001963
1964 // register the output connection slots for the layer, connections are made after all layers have been created
1965 RegisterOutputSlots(layer, {node.output(0)});
1966}
1967
Kevin Mayef33cb12021-01-29 14:24:57 +00001968void OnnxParserImpl::ParseFlatten(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001969{
1970 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1971 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1972
1973 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1974 m_TensorsInfo[node.input(0)].m_dtype,
1975 onnx::TensorProto::FLOAT);
1976
1977 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1978 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1979
1980 /// Negative axis conversion
1981 if (axis < 0)
1982 {
1983 axis += inputShape.GetNumDimensions();
1984 }
1985
1986 /// Check Axis is within dimensions
1987 if (axis < 0 || axis >= inputShape.GetNumDimensions())
1988 {
James Ward58dec6b2020-09-11 17:32:44 +01001989 throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
1990 axis, inputShape.GetNumDimensions(), node.name()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001991 }
1992
1993 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1994 uint dimension1{1};
1995 uint dimension2{1};
1996 uint i{0};
1997
1998 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1999 for (i = 0; i < axis; i++){
2000 dimension1 *= inputShape[i];
2001 }
2002
2003 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
2004 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
2005 dimension2 *= inputShape[i];
2006 }
2007
2008 TensorShape outputShape{dimension1, dimension2};
2009
2010 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
2011 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2012 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2013}
2014
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01002015void OnnxParserImpl::ParseGather(const onnx::NodeProto& node)
2016{
2017 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2018 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2019
2020 armnn::GatherDescriptor gatherDescriptor;
2021 gatherDescriptor.m_Axis = static_cast<int>(ReadOptionalNodeInt64Attribute(node, "axis", 0));
2022
2023 IConnectableLayer* layer = m_Network->AddGatherLayer(gatherDescriptor, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002024
2025 if (!layer)
2026 {
2027 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2028 }
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01002029
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002030 const TensorShape& inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2031 const TensorShape& indicesShape = m_TensorsInfo[node.input(1)].m_info->GetShape();
2032 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, { inputShape, indicesShape },
2033 m_TensorsInfo[node.input(0)].m_dtype);
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01002034 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2035
2036 // register the input connection slots for the layer, connections are made after all layers have been created
2037 RegisterInputSlots(layer, { node.input(0), node.input(1) });
2038
2039 // register the output connection slots for the layer, connections are made after all layers have been created
2040 RegisterOutputSlots(layer, { node.output(0) });
2041}
2042
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002043void OnnxParserImpl::ParseGemm(const onnx::NodeProto& node)
2044{
2045 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3);
2046 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2047
2048 int transA = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transA", 0));
2049 int transB = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transB", 0));
2050 float alpha = ReadOptionalNodeFloatAttribute(node, "alpha", 1.0);
2051 float beta = ReadOptionalNodeFloatAttribute(node, "beta", 1.0);
2052 bool biasEnabled = node.input_size() == 3;
2053
2054 TensorShape input0Shape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2055 TensorShape input1Shape = m_TensorsInfo[node.input(1)].m_info->GetShape();
2056
2057 // if transB != 0, add transpose to the input1 (tanspose weight matrix in FullyConnected)
2058 armnn::FullyConnectedDescriptor fullyConnectedDescriptor;
2059 fullyConnectedDescriptor.m_BiasEnabled = biasEnabled;
2060 fullyConnectedDescriptor.m_TransposeWeightMatrix = transB;
2061
2062 IConnectableLayer* layer = nullptr;
2063
2064 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
2065 layer = m_Network->AddFullyConnectedLayer(fullyConnectedDescriptor, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002066
2067 if (!layer)
2068 {
2069 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2070 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002071
2072 // if transA != 0, add transpose to the input0
2073 if (transA != 0)
2074 {
2075 std::string transAName = "transpose_" + node.input(0);
2076 armnn::TransposeDescriptor transposeADescriptor;
2077 transposeADescriptor.m_DimMappings = { 1, 0 };
2078 IConnectableLayer* transALayer = m_Network->AddTransposeLayer(transposeADescriptor, transAName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002079
2080 if (!transALayer)
2081 {
2082 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2083 }
2084
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002085 auto transAInfo = ComputeOutputInfo({ transAName }, transALayer, { input0Shape });
2086 transALayer->GetOutputSlot(0).SetTensorInfo(transAInfo[0]);
2087 transALayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
2088 // register the input connection slots for the layer, connections are made after all layers have been created
2089 RegisterInputSlot(transALayer, node.input(0), 0);
2090 input0Shape = transAInfo[0].GetShape();
2091 }
2092 else
2093 {
2094 RegisterInputSlot(layer, node.input(0), 0);
2095 }
2096
2097 // Add constant layer to store weights/biases and connect to FullyConnected layer.
2098 if(m_TensorsInfo[node.input(1)].isConstant())
2099 {
2100 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(1)).first);
2101 TensorInfo weightInfo = *m_TensorsInfo[node.input(1)].m_info;
2102 weightInfo.SetConstant();
2103 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
2104
2105 // if alpha != 1, multiply to the weight
2106 if (alpha != 1)
2107 {
2108 std::string activationName = "activation_" + node.input(1);
2109 armnn::ActivationDescriptor activationDescriptor;
2110 activationDescriptor.m_A = alpha;
2111 activationDescriptor.m_Function = ActivationFunction::Linear;
2112 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002113
2114 if (!actLayer)
2115 {
2116 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2117 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002118
2119 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { weightInfo.GetShape() });
2120 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2121 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2122 weightsLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
2123 input1Shape = actInfo[0].GetShape();
2124 }
2125 else
2126 {
2127 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2128 input1Shape = weightInfo.GetShape();
2129 }
2130 }
2131 else
2132 {
2133 // if alpha != 1, multiply to the weight
2134 if (alpha != 1)
2135 {
2136 std::string activationName = "activation_" + node.input(1);
2137 armnn::ActivationDescriptor activationDescriptor;
2138 activationDescriptor.m_A = alpha;
2139 activationDescriptor.m_Function = ActivationFunction::Linear;
2140 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002141
2142 if (!actLayer)
2143 {
2144 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2145 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002146
2147 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { input1Shape });
2148 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2149 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2150 RegisterInputSlot(actLayer, node.input(1), 0);
2151 input1Shape = actInfo[0].GetShape();
2152 }
2153 else
2154 {
2155 RegisterInputSlot(layer, node.input(1), 1);
2156 }
2157 }
2158
2159 if(biasEnabled && m_TensorsInfo[node.input(2)].isConstant())
2160 {
2161 To1DTensor(node.input(2), CHECK_LOCATION());
2162 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(2)).first);
2163 TensorInfo biasInfo = *m_TensorsInfo[node.input(2)].m_info;
2164 biasInfo.SetConstant();
2165 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
2166
2167 // if beta != 1, multiply to the bias
2168 if (beta != 1)
2169 {
2170 std::string activationName = "activation_" + node.input(2);
2171 armnn::ActivationDescriptor activationDescriptor;
2172 activationDescriptor.m_A = beta;
2173 activationDescriptor.m_Function = ActivationFunction::Linear;
2174 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002175
2176 if (!actLayer)
2177 {
2178 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2179 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002180
2181 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { biasInfo.GetShape() });
2182 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2183 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2184 biasLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
2185 }
2186 else
2187 {
2188 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2189 }
2190 }
2191 else if (biasEnabled)
2192 {
2193 // Currently we support non-constant tensor of input C (bias) of Gemm when the dimension is 1
2194 if (m_TensorsInfo[node.input(2)].m_info->GetNumDimensions() != 1)
2195 {
2196 throw ParseException(fmt::format("The parser supports constant or non-constant with 1 dimension for "
2197 "Input C of Gemm. Input '{}' in '{}' is not supported '{}'",
2198 node.input(2),
2199 node.name(),
2200 CHECK_LOCATION().AsString()));
2201 }
2202 // if beta != 1, multiply to the bias
2203 if (beta != 1)
2204 {
2205 std::string activationName = "activation_" + node.input(2);
2206 armnn::ActivationDescriptor activationDescriptor;
2207 activationDescriptor.m_A = beta;
2208 activationDescriptor.m_Function = ActivationFunction::Linear;
2209 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002210
2211 if (!layer)
2212 {
2213 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2214 }
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002215
2216 auto actInfo = ComputeOutputInfo({ activationName },
2217 actLayer,
2218 { m_TensorsInfo[node.input(2)].m_info->GetShape() });
2219 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2220 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2221 RegisterInputSlot(actLayer, node.input(2), 0);
2222 }
2223 else
2224 {
2225 RegisterInputSlot(layer, node.input(2), 2);
2226 }
2227 }
2228
2229 // Set final output of the FullyConnected layer
2230 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
2231 { input0Shape, input1Shape });
2232 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2233
2234 RegisterOutputSlots(layer, {node.output(0)});
2235}
2236
Kevin Mayef33cb12021-01-29 14:24:57 +00002237void OnnxParserImpl::ParseGlobalAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002238{
2239 Pooling2dDescriptor desc = Pooling2dDescriptor();
2240 desc.m_PoolType = PoolingAlgorithm::Average;
2241
2242 //kernel size is the same as input
2243 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2244 desc.m_PoolWidth = inputShape[3];
2245 desc.m_PoolHeight = inputShape[2];
2246
2247 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002248
2249 if (!layer)
2250 {
2251 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2252 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01002253
2254 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
2255 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2256
2257 // register the input connection slots for the layer, connections are made after all layers have been created
2258 // only the tensors for the inputs are relevant, exclude the const tensors
2259 RegisterInputSlots(layer, {node.input(0)});
2260
2261 // register the output connection slots for the layer, connections are made after all layers have been created
2262 RegisterOutputSlots(layer, {node.output(0)});
2263}
2264
Kevin Mayef33cb12021-01-29 14:24:57 +00002265void OnnxParserImpl::ParseMaxPool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002266{
2267 Pooling2dDescriptor desc;
2268 desc.m_PoolType = PoolingAlgorithm::Max;
2269 desc.m_PaddingMethod = PaddingMethod::Exclude;
2270 AddPoolingLayer(node, desc);
2271}
2272
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002273void OnnxParserImpl::ParseShape(const onnx::NodeProto& node)
2274{
2275 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
2276 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2277
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002278 IConnectableLayer* layer = m_Network->AddShapeLayer(node.name().c_str());
Ryan OSheac229b3f2023-06-27 22:34:54 +01002279
2280 if (!layer)
2281 {
2282 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2283 }
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002284
2285 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002286 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape}, onnx::TensorProto::INT64);
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01002287 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2288
2289 // register the input connection slots for the layer, connections are made after all layers have been created
2290 RegisterInputSlots(layer, {node.input(0)});
2291
2292 // register the output connection slots for the layer, connections are made after all layers have been created
2293 RegisterOutputSlots(layer, {node.output(0)});
2294}
2295
Kevin Mayef33cb12021-01-29 14:24:57 +00002296void OnnxParserImpl::ParseReshape(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01002297{
2298 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2299 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2300
2301 CHECK_VALID_DATATYPE(node.name(), node.input(0),
2302 m_TensorsInfo[node.input(0)].m_dtype,
2303 onnx::TensorProto::FLOAT); //input
2304 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2305 m_TensorsInfo[node.input(1)].m_dtype,
2306 onnx::TensorProto::INT64); //shape
2307
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002308 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2309
2310 std::vector<unsigned int> targetShape;
2311 if(m_TensorsInfo[node.input(1)].isConstant())
Ryan OSheaed27ee72020-04-22 16:37:29 +01002312 {
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002313 unsigned int dims = static_cast<unsigned int>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2314 targetShape.reserve(dims);
2315
2316 for(uint i = 0; i < dims; i++)
2317 {
2318 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
2319 targetShape[i]= static_cast<unsigned int>(val);
2320 }
2321 }
2322 else
2323 {
2324 // The parser only supports shape (batch, -1) or (-1) for non-constant shape input.
2325 unsigned int dims = m_TensorsInfo[node.input(1)].m_info->GetNumDimensions();
2326 TensorShape shapes = m_TensorsInfo[node.input(1)].m_info->GetShape();
2327 if (dims != 1 || shapes[0] > 2)
2328 {
2329 throw ParseException(fmt::format("Invalid input shape '{}' in Reshape layer '{}' {}",
2330 node.input(1),
2331 node.name(),
2332 CHECK_LOCATION().AsString()));
2333 }
2334
2335 unsigned int numInputElements = m_TensorsInfo[node.input(0)].m_info->GetNumElements();
2336 if (shapes[0] == 1)
2337 {
2338 targetShape = { numInputElements };
2339 }
2340 else if (shapes[0] == 2)
2341 {
2342 targetShape = { inputShape[0] , numInputElements / inputShape[0] };
2343 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01002344 }
2345
2346 if(m_TensorsInfo[node.input(0)].isConstant())
2347 {
2348 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
2349 if(m_TensorsInfo.count(node.output(0)) == 0)
2350 {
2351 m_TensorsInfo[node.output(0)] = OnnxTensor();
2352 }
2353 m_TensorsInfo[node.output(0)].m_tensor =
2354 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
2355 }
2356 else
2357 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01002358 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
2359 {
Narumol Prangnawarat4b536e32021-10-18 12:35:19 +01002360 auto outInfo = ComputeReshapeInfo(
2361 TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2362 inputShape, node.output(0));
Ryan OSheaed27ee72020-04-22 16:37:29 +01002363 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2364 }
2365
2366 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2367 }
2368}
2369
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002370void OnnxParserImpl::ParseUnsqueeze(const onnx::NodeProto& node)
2371{
2372 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.input_size()), 1, 2);
2373 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.output_size()), 1);
2374
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002375 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2376 std::vector<uint32_t> dims;
2377 if (node.input_size() == 1 && node.attribute_size() > 0)
2378 {
2379 dims = ReadMandatoryNodeUint32ListAttribute(node, "axes");
2380 }
2381 else
2382 {
2383 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2384 m_TensorsInfo[node.input(1)].m_dtype,
2385 onnx::TensorProto::INT64); //axes
2386
2387 auto int64Axes = m_TensorsInfo[node.input(1)].m_tensor->int64_data().data();
2388 uint numDim = armnn::numeric_cast<uint>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2389
2390 for(uint i = 0; i < numDim; i++)
2391 {
2392 uint32_t uint32Value = CHECKED_NON_NEGATIVE(CHECKED_INT32(int64Axes[i]));
2393 dims.push_back(uint32Value);
2394 }
2395 }
2396
2397 // Ensure that the axes are sorted
2398 std::sort(dims.begin(), dims.end());
2399
2400 std::vector<unsigned int> targetShape;
2401
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002402 if (inputShape.GetDimensionality() != Dimensionality::Scalar)
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002403 {
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002404 for(uint i = 0; i < inputShape.GetNumDimensions(); i++)
2405 {
2406 targetShape.push_back(inputShape[i]);
2407 }
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002408 }
2409
2410 for(uint i = 0; i < dims.size(); i++)
2411 {
2412 targetShape.insert(targetShape.begin() + armnn::numeric_cast<int>(dims[i]), 1);
2413 }
2414
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002415 auto outInfo = ComputeReshapeInfo(TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2416 inputShape, node.output(0), m_TensorsInfo[node.input(0)].m_info->GetDataType());
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002417 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
Narumol Prangnawarat452274c2021-09-23 16:12:19 +01002418 m_TensorsInfo[node.output(0)].m_dtype = m_TensorsInfo[node.input(0)].m_dtype;
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01002419
2420 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2421}
2422
Kevin Mayef33cb12021-01-29 14:24:57 +00002423void OnnxParserImpl::PrependForBroadcast(const std::string& outputName,
2424 const std::string& input0,
2425 const std::string& input1)
telsoa01c577f2c2018-08-31 09:22:23 +01002426{
2427 //input0 should be reshaped to have same number of dim as input1
2428 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
2429
2430 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
2431 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
2432
2433 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
2434 std::vector<uint32_t> newShape;
2435 while(diff > 0)
2436 {
2437 newShape.push_back(1);
2438 diff--;
2439 }
2440 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
2441 {
2442 newShape.push_back(input0Shape[dim]);
2443 }
2444 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
2445
2446 //add the new tensor to m_TensorsInfo
2447 m_TensorsInfo[outputName] = OnnxTensor();
2448 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
2449
2450 //add reshape layer if the parent was not constant...
2451 if( ! m_TensorsInfo[input0].isConstant())
2452 {
James Ward58dec6b2020-09-11 17:32:44 +01002453 CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
telsoa01c577f2c2018-08-31 09:22:23 +01002454 }
2455 else //make it constant and it will be create in Add
2456 {
2457 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
2458
2459 }
2460}
2461
Kevin Mayef33cb12021-01-29 14:24:57 +00002462void OnnxParserImpl::SetupInputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01002463{
2464 //Find user input and add their layers
2465 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
2466 {
2467 auto input = m_Graph->input(inputIndex);
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002468 if (!m_TensorsInfo[input.name()].isConstant())
telsoa01c577f2c2018-08-31 09:22:23 +01002469 {
2470 IConnectableLayer* layer =
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002471 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
2472 TensorInfo tensorInfo = *m_TensorsInfo[input.name()].m_info;
2473 if (tensorInfo.GetShape().GetDimensionality() == Dimensionality::NotSpecified)
2474 {
2475 if (m_InputShapes.find(input.name()) == m_InputShapes.end())
2476 {
2477 throw ParseException(fmt::format("The parser does not support dynamic tensor, "
2478 "please specify input shape for {}. {}",
2479 input.name(),
2480 CHECK_LOCATION().AsString()));
2481 }
2482 else
2483 {
2484 tensorInfo.SetShape(m_InputShapes[input.name()]);
2485 m_TensorsInfo[input.name()].m_info = std::make_unique<TensorInfo>(tensorInfo);
2486 }
2487
2488 }
telsoa01c577f2c2018-08-31 09:22:23 +01002489 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2490
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002491 m_InputInfos[input.name()] = tensorInfo;
2492
telsoa01c577f2c2018-08-31 09:22:23 +01002493 RegisterOutputSlots(layer,{ input.name() });
2494 }
2495 }
2496}
2497
Kevin Mayef33cb12021-01-29 14:24:57 +00002498void OnnxParserImpl::SetupOutputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01002499{
2500 if(m_Graph->output_size() == 0)
2501 {
James Ward58dec6b2020-09-11 17:32:44 +01002502 throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002503 }
2504
2505 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
2506 {
2507 IConnectableLayer* layer =
2508 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
2509 m_Graph->output(outputIndex).name().c_str());
2510
2511 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
2512 }
2513}
2514
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002515void OnnxParserImpl::RegisterInputSlot(IConnectableLayer* layer,
2516 const std::string& tensorId,
2517 unsigned int slotIndex)
2518{
2519 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2520
2521 auto it = m_TensorConnections.find(tensorId);
2522
2523 if (it == m_TensorConnections.end())
2524 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002525 //First time seeing this tensor, we need to map it
Narumol Prangnawarat1112b012021-09-30 12:10:50 +01002526 m_TensorConnections[tensorId] = TensorSlots();
2527 }
2528 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2529}
2530
Kevin Mayef33cb12021-01-29 14:24:57 +00002531void OnnxParserImpl::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002532{
Ryan OSheac229b3f2023-06-27 22:34:54 +01002533 if (!layer)
2534 {
2535 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2536 }
2537
telsoa01c577f2c2018-08-31 09:22:23 +01002538 if (tensorIds.size() != layer->GetNumInputSlots())
2539 {
2540 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002541 fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
2542 tensorIds.size(),
2543 layer->GetNumInputSlots(),
2544 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002545 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01002546
telsoa01c577f2c2018-08-31 09:22:23 +01002547 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
2548 {
2549 std::string tensorId = tensorIds[slotIndex];
2550 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2551
2552 auto it = m_TensorConnections.find(tensorId);
2553
2554 if (it == m_TensorConnections.end())
2555 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002556 // First time seing this tensor, we need to map it
telsoa01c577f2c2018-08-31 09:22:23 +01002557 m_TensorConnections[tensorId] = TensorSlots();
2558 }
2559 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2560 }
2561}
2562
Kevin Mayef33cb12021-01-29 14:24:57 +00002563void OnnxParserImpl::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002564{
Ryan OSheac229b3f2023-06-27 22:34:54 +01002565 if (!layer)
2566 {
2567 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2568 }
2569
telsoa01c577f2c2018-08-31 09:22:23 +01002570 if (tensorIds.size() != layer->GetNumOutputSlots())
2571 {
2572 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002573 fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
2574 tensorIds.size(),
2575 layer->GetNumOutputSlots(),
2576 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002577 }
2578
2579 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
2580 {
2581 std::string tensorId = tensorIds[slotIndex];
2582 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
2583
2584 auto it = m_TensorConnections.find(tensorId);
2585
2586 if (it == m_TensorConnections.end())
2587 {
2588 //First time seing this tensor, we need to map it
2589 m_TensorConnections[tensorId] = TensorSlots();
2590 }
2591
Ryan OShea337c17f2020-02-21 12:33:17 +00002592 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01002593
2594 // assuming there is only one producer for that tensor
2595 if (tensorSlots.outputSlot != nullptr)
2596 {
James Ward58dec6b2020-09-11 17:32:44 +01002597 throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
2598 "tensor:{} {}",
2599 tensorId,
2600 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002601 }
2602 tensorSlots.outputSlot = slot;
2603 }
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002604
telsoa01c577f2c2018-08-31 09:22:23 +01002605}
2606
Kevin Mayef33cb12021-01-29 14:24:57 +00002607BindingPointInfo OnnxParserImpl::GetNetworkInputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002608{
2609 for(int i = 0; i < m_Graph->input_size(); ++i)
2610 {
2611 auto input = m_Graph->input(i);
2612 if(input.name() == name)
2613 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002614 auto it = m_InputInfos.find(name);
2615
2616 if (it != m_InputInfos.end())
2617 {
2618 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2619 }
telsoa01c577f2c2018-08-31 09:22:23 +01002620 }
2621 }
James Ward58dec6b2020-09-11 17:32:44 +01002622 throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
2623 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002624}
2625
Kevin Mayef33cb12021-01-29 14:24:57 +00002626BindingPointInfo OnnxParserImpl::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002627{
2628 for(int i = 0; i < m_Graph->output_size(); ++i)
2629 {
2630 auto output = m_Graph->output(i);
2631 if(output.name() == name)
2632 {
Narumol Prangnawarat1b11f322021-10-13 11:44:50 +01002633 auto it = m_OutputInfos.find(name);
2634
2635 if (it != m_OutputInfos.end())
2636 {
2637 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2638 }
telsoa01c577f2c2018-08-31 09:22:23 +01002639 }
2640 }
James Ward58dec6b2020-09-11 17:32:44 +01002641 throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
2642 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002643}
2644
Kevin Mayef33cb12021-01-29 14:24:57 +00002645std::vector<std::string> OnnxParserImpl::GetInputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002646{
2647 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002648 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2649 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002650 }
2651
2652 std::vector<std::string> inputNames;
2653 std::map<std::string, bool> isConstant;
2654 for(auto tensor : model->graph().initializer())
2655 {
2656 isConstant[tensor.name()] = true;
2657 }
2658 for(auto input : model->graph().input())
2659 {
2660 auto it = isConstant.find(input.name());
2661 if(it == isConstant.end())
2662 {
2663 inputNames.push_back(input.name());
2664 }
2665 }
2666 return inputNames;
2667}
2668
Kevin Mayef33cb12021-01-29 14:24:57 +00002669std::vector<std::string> OnnxParserImpl::GetOutputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002670{
2671 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002672 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2673 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002674 }
2675
2676 std::vector<std::string> outputNames;
2677 for(auto output : model->graph().output())
2678 {
2679 outputNames.push_back(output.name());
2680 }
2681 return outputNames;
2682}
2683
Matthew Sloyanac001ee2021-02-03 10:43:04 +00002684const std::string OnnxParserImpl::GetVersion()
2685{
2686 return ONNX_PARSER_VERSION;
2687}
2688
telsoa01c577f2c2018-08-31 09:22:23 +01002689} // namespace armnnOnnxParser