ArmNN
 25.11
Loading...
Searching...
No Matches
OnnxParser.cpp
Go to the documentation of this file.
1//
2// Copyright © 2017,2022-2024 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5#include "OnnxParser.hpp"
6
8
12#include <ParserHelper.hpp>
14
15#include <fmt/format.h>
16
17#include <google/protobuf/text_format.h>
18#include <google/protobuf/io/zero_copy_stream_impl.h>
19
20#include <iostream>
21#include <numeric>
23
24using namespace armnn;
25
26namespace armnnOnnxParser
27{
28
29IOnnxParser::IOnnxParser() : pOnnxParserImpl(new OnnxParserImpl()) {}
30
31IOnnxParser::~IOnnxParser() = default;
32
34{
35 return new IOnnxParser();
36}
37
44
45void IOnnxParser::Destroy(IOnnxParser* parser)
46{
47 delete parser;
48}
49
51{
52 return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile);
53}
54
55armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent)
56{
57 return pOnnxParserImpl->CreateNetworkFromBinary(binaryContent);
58}
59
60armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent,
61 const std::map<std::string, armnn::TensorShape>& inputShapes)
62{
63 return pOnnxParserImpl->CreateNetworkFromBinary(binaryContent, inputShapes);
64}
65
67{
68 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile);
69}
70
72{
73 return pOnnxParserImpl->CreateNetworkFromString(protoText);
74}
75
77 const char* graphFile,
78 const std::map<std::string, armnn::TensorShape>& inputShapes)
79{
80 return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile, inputShapes);
81}
82
84 const std::map<std::string, armnn::TensorShape>& inputShapes)
85{
86 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile, inputShapes);
87}
88
90 const std::map<std::string, armnn::TensorShape>& inputShapes)
91{
92 return pOnnxParserImpl->CreateNetworkFromString(protoText, inputShapes);
93}
94
96{
97 return pOnnxParserImpl->GetNetworkInputBindingInfo(name);
98}
99
101{
102 return pOnnxParserImpl->GetNetworkOutputBindingInfo(name);
103}
104
105namespace
106{
107void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
108 const onnx::TensorProto::DataType actualValue,
109 const char* validExpr,
110 std::string nodeName,
111 std::string tensorName,
112 const armnn::CheckLocation& location)
113{
114 bool isValid = std::any_of(validInputTypes.begin(),
115 validInputTypes.end(),
116 [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
117 if (!isValid)
118 {
119 throw ParseException(
120 fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
121 onnx::TensorProto::DataType_Name(actualValue),
122 tensorName,
123 nodeName,
124 validExpr,
125 location.AsString()));
126 }
127}
128
129#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
130CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
131
132using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
133#define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
134
135template <typename Callable>
136void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
137 const std::string& attribName,
138 onnx::AttributeProto::AttributeType expectedType,
139 Callable callable)
140{
141 auto attribs = node.attribute();
142 int attriNum = 0;
143 while (attriNum < node.attribute_size())
144 {
145 if (attribs.Get(attriNum).name() == attribName)
146 {
147 if (attribs.Get(attriNum).type() == expectedType)
148 {
149 callable(attribs.Get(attriNum));
150 }
151 else
152 {
153 throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
154 "onnx::AttributeProto::AttributeType, but found {} instead {}",
155 attribName,
156 node.name(),
157 onnx::AttributeProto::AttributeType_Name(expectedType),
158 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
159 CHECK_LOCATION().AsString()));
160 }
161 break;
162 }
163 ++attriNum;
164 }
165 if (attriNum == node.attribute_size())
166 {
167 throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
168 attribName, node.name(), CHECK_LOCATION().AsString()));
169 }
170}
171
172template <typename Callable>
173void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
174 const std::string& attribName,
175 onnx::AttributeProto::AttributeType expectedType,
176 Callable callable)
177{
178 auto attribs = node.attribute();
179 for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
180 {
181 if (attribs.Get(attriNum).name() == attribName)
182 {
183 if (attribs.Get(attriNum).type() == expectedType)
184 {
185 callable(attribs.Get(attriNum));
186 }
187 else
188 {
189 throw ParseException(
190 fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
191 "but found {} instead {}",
192 attribName,
193 node.name(),
194 onnx::AttributeProto::AttributeType_Name(expectedType),
195 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
196 CHECK_LOCATION().AsString()));
197 }
198 }
199 }
200}
201
202int ReadMandatoryNodeIntAttribute(const onnx::NodeProto& node,
203 const std::string& name)
204{
205 int attribValue = 0;
206 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
207 [&attribValue](const onnx::AttributeProto& attrValue)
208 {
209 attribValue = CHECKED_INT32(attrValue.i());
210 });
211 return attribValue;
212}
213
214int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
215 const std::string& name,
216 const int64_t defaultValue = 0)
217{
218 int64_t attribValue = defaultValue;
219 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
220 [&attribValue](const onnx::AttributeProto& attrValue)
221 {
222 attribValue = attrValue.i();
223 });
224 return attribValue;
225}
226
227std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
228 const std::string& name)
229{
230 std::vector<uint32_t> attriList;
231 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
232 [&attriList](const onnx::AttributeProto& attrValue)
233 {
234 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
235 {
236 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
237 }
238 });
239 return attriList;
240}
241
242uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
243 const std::string& name,
244 const uint32_t defaultVal = 0u)
245{
246 uint32_t attribValue = defaultVal;
247 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
248 [&attribValue](const onnx::AttributeProto& attrValue)
249 {
250 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
251 });
252 return attribValue;
253}
254
255std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
256 const std::string& name)
257{
258 std::vector<uint32_t> attriList;
259 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
260 [&attriList](const onnx::AttributeProto& attrValue)
261 {
262 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
263 {
264 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
265 }
266 });
267
268 return attriList;
269}
270
271float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
272 const std::string& name,
273 const float defaultValue = 0.0f)
274{
275 float attribValue = defaultValue;
276 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
277 [&attribValue](const onnx::AttributeProto& attrValue)
278 {
279 attribValue = attrValue.f();
280 });
281 return attribValue;
282}
283
284std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
285{
286 std::string attribValue = "";
287 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
288 [&attribValue](const onnx::AttributeProto& attrValue)
289 {
290 attribValue = attrValue.s();
291 });
292 return attribValue;
293}
294
295armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
296{
297 DataType type;
298 switch(data_type)
299 {
300 case onnx::TensorProto::FLOAT:
301 {
302 type = DataType::Float32;
303 break;
304 }
305 case onnx::TensorProto::INT32:
306 case onnx::TensorProto::INT64:
307 {
308 type = DataType::Signed32;
309 break;
310 }
311 default:
312 {
313 throw ParseException(
314 fmt::format("'{}' is not a currently supported datatype for tensor {}."
315 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
316 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
317 name,
318 CHECK_LOCATION().AsString() ));
319 }
320 }
321
322 // Scalar Tensor
323 if (shape.empty())
324 {
326 }
327
328 // Dynamic Tensor
329 if(std::find(shape.begin(), shape.end(), 0) != shape.end())
330 {
332 }
333
334 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
335}
336
337armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
338{
339 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
340 std::vector<unsigned int> shapeDims;
341 for (int i = 0; i < onnxShape.dim_size(); ++i)
342 {
343 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
344 }
345
346 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
347}
348
349armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
350{
351 std::vector<unsigned int> shapeDims;
352
353 for (auto dim: tensor.dims())
354 {
355 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
356 }
357
358 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
359}
360
361std::string TensorInfoAsString(const TensorInfo& info,
362 const std::string& name,
363 const onnx::TensorProto::DataType& type)
364{
365 const TensorShape shape = info.GetShape();
366 std::stringstream ss;
367 ss << "tensor '" << name << "' contains "
368 << onnx::TensorProto::DataType_Name(type)
369 << " and has shape [";
370
371 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
372 {
373 ss << shape[i] << ", ";
374 }
375 ss << shape[shape.GetNumDimensions() - 1] << "]";
376 return ss.str();
377}
378
379void CalcPadding(uint32_t inputSize,
380 uint32_t filterSize,
381 uint32_t stride,
382 uint32_t dilation,
383 uint32_t* paddingFront,
384 uint32_t* paddingBack,
385 bool isUpper)
386{
387 uint32_t outputSize = (inputSize + stride - 1) / stride;
388 uint32_t dilatedSize = filterSize + (dilation - 1) * (filterSize - 1);
389 uint32_t temp = (outputSize - 1) * stride + dilatedSize;
390 *paddingFront = (temp - inputSize) / 2;
391 *paddingBack = *paddingFront;
392 if((temp - inputSize) % 2 == 1)
393 {
394 if (isUpper)
395 {
396 *paddingBack += 1;
397 }
398 else
399 {
400 *paddingFront += 1;
401 }
402 }
403}
404
405TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
406 const TensorShape& inShape,
407 const std::string& outName,
408 DataType dataType = DataType::Float32)
409{
410 std::vector<int> targetDims;
411 for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
412 {
413 int val = CHECKED_INT32(targetShapeTensor[i]);
414 if(val == 0)
415 {
416 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
417 }
418 else
419 {
420 targetDims.push_back(val);
421 }
422 }
423
424 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
425 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
426 if (stretchDim != targetDims.end())
427 {
428 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
429 {
430 std::stringstream ss;
431 ss << "[ ";
432 for(uint i = 0; i < targetDims.size() - 1; ++i)
433 {
434 ss << targetDims[i] << ", ";
435 }
436 ss << targetDims[targetDims.size() - 1] << " ]";
437
438 throw ParseException(
439 fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
440 " -1 and here, shape is {} {}",
441 outName,
442 ss.str(),
443 CHECK_LOCATION().AsString()));
444 }
445
446 auto targetNumElements = armnn::numeric_cast<unsigned int>(
447 std::accumulate(targetDims.begin(), targetDims.end(), -1, std::multiplies<int32_t>()));
448 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
449 if (targetNumElements == 0)
450 {
451 if (inShape.GetNumElements() == 0)
452 {
453 outDims[stretchIndex] = 0;
454 }
455 else
456 {
457 throw ParseException(
458 fmt::format("Input to reshape is a tensor with elements, but the requested shape has 0. {}",
459 CHECK_LOCATION().AsString()));
460 }
461 }
462 else
463 {
464 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
465 }
466 }
467 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
468 return TensorInfo(outShape, dataType);
469}
470
471} //namespace
472
473const std::map<std::string, OnnxParserImpl::OperationParsingFunction> OnnxParserImpl::m_ParserFunctions = {
474 { "BatchNormalization", &OnnxParserImpl::ParseBatchNormalization},
475 { "GlobalAveragePool", &OnnxParserImpl::ParseGlobalAveragePool},
476 { "AveragePool", &OnnxParserImpl::ParseAveragePool },
477 { "Clip", &OnnxParserImpl::ParseClip },
478 { "Constant", &OnnxParserImpl::ParseConstant },
479 { "MaxPool", &OnnxParserImpl::ParseMaxPool },
480 { "Reshape", &OnnxParserImpl::ParseReshape },
481 { "Sigmoid", &OnnxParserImpl::ParseSigmoid },
482 { "Tanh", &OnnxParserImpl::ParseTanh },
483 { "Relu", &OnnxParserImpl::ParseRelu },
484 { "LeakyRelu", &OnnxParserImpl::ParseLeakyRelu },
485 { "Conv", &OnnxParserImpl::ParseConv },
486 { "Add", &OnnxParserImpl::ParseAdd },
487 { "Flatten", &OnnxParserImpl::ParseFlatten },
488 { "Shape", &OnnxParserImpl::ParseShape },
489 { "Gather", &OnnxParserImpl::ParseGather },
490 { "Unsqueeze", &OnnxParserImpl::ParseUnsqueeze },
491 { "Concat", &OnnxParserImpl::ParseConcat },
492 { "Gemm", &OnnxParserImpl::ParseGemm }
493};
494
495template<typename TypePair, typename Location>
496void OnnxParserImpl::ValidateInputs(const onnx::NodeProto& node,
497 TypePair validInputs,
498 const Location& location)
499{
500 for(auto input : node.input())
501 {
502 CheckValidDataType(validInputs.second,
503 m_TensorsInfo[input].m_dtype,
504 validInputs.first,
505 node.name(),
506 input,
507 location);
508 }
509}
510
511#define VALID_INPUTS(NODE, VALID_INPUTS) \
512 OnnxParserImpl::ValidateInputs(NODE, \
513 VALID_INPUTS, \
514 CHECK_LOCATION())
515
516std::vector<TensorInfo> OnnxParserImpl::ComputeOutputInfo(std::vector<std::string> outNames,
517 const IConnectableLayer* layer,
518 std::vector<TensorShape> inputShapes,
519 const onnx::TensorProto::DataType& dataType)
520{
521 if (outNames.empty())
522 {
523 throw armnn::ParseException(fmt::format("Output names are empty {}", CHECK_LOCATION().AsString()));
524 }
525
526 bool needCompute = std::any_of(outNames.begin(),
527 outNames.end(),
528 [this](std::string name)
529 {
530 return (m_TensorsInfo.count(name) == 0 ||
531 m_TensorsInfo[name].m_info == nullptr ||
532 m_TensorsInfo[name].m_info->GetShape().GetDimensionality() ==
533 Dimensionality::NotSpecified);
534 });
535 std::vector<TensorInfo> outInfo;
536 //if the output info(s) are not here, we need to compute them
537 std::vector<TensorShape> inferredShapes;
538 DataType armnnType = DataType::Float32;
539 if(needCompute) {
540 inferredShapes = layer->InferOutputShapes(inputShapes);
541 if (inferredShapes.size() != outNames.size())
542 {
543 throw armnn::ParseException(fmt::format("Inferred shapes does not match number of output names {}",
544 CHECK_LOCATION().AsString()));
545 }
546 switch (dataType) {
547 case onnx::TensorProto::FLOAT: {
548 armnnType = DataType::Float32;
549 break;
550 }
551 case onnx::TensorProto::INT32:
552 case onnx::TensorProto::INT64: {
553 armnnType = DataType::Signed32;
554 break;
555 }
556 default: {
557 throw ParseException(
558 fmt::format("'{}' is not a currently supported datatype for {}."
559 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
560 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(dataType)),
561 layer->GetName(),
562 CHECK_LOCATION().AsString()));
563 }
564 }
565 }
566 for (uint i = 0; i < outNames.size(); ++i)
567 {
568 if(needCompute)
569 {
570 m_TensorsInfo[outNames[i]] = OnnxTensor();
571 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
572 TensorInfo(inferredShapes[i], armnnType));
573 m_TensorsInfo[outNames[i]].m_dtype = dataType;
574 }
575 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
576 }
577 return outInfo;
578}
579
581 : m_Network(nullptr, nullptr)
582{
583}
584
585void OnnxParserImpl::ResetParser()
586{
587 m_Network = armnn::INetworkPtr(nullptr, nullptr);
588 m_Graph = nullptr;
589 m_InputInfos.clear();
590 m_OutputInfos.clear();
591}
592
593void OnnxParserImpl::Cleanup()
594{
595 m_TensorConnections.clear();
596 m_TensorsInfo.clear();
597 m_OutputsMap.clear();
598 m_OutputsFusedAndUsed.clear();
599 m_InputShapes.clear();
600}
601
602template<typename T>
603std::pair<armnn::ConstTensor, std::unique_ptr<T[]>>
604CreateConstTensorImpl(const T* bufferPtr,
605 armnn::TensorInfo& tensorInfo,
606 const armnn::Optional<armnn::PermutationVector&> permutationVector)
607{
608 if (bufferPtr == nullptr)
609 {
610 throw armnn::ParseException(fmt::format("Buffer for permutation is null {}", CHECK_LOCATION().AsString()));
611 }
612
613 std::unique_ptr<T[]> data(new T[tensorInfo.GetNumElements()]);
614
615 if (permutationVector.has_value() && permutationVector.value().GetSize() > 0)
616 {
617 tensorInfo = armnnUtils::Permuted(tensorInfo, permutationVector.value());
618 armnnUtils::Permute(tensorInfo.GetShape(), permutationVector.value(),
619 reinterpret_cast<const T*>(bufferPtr), data.get(), sizeof(T));
620 }
621 else
622 {
623 ::memcpy(data.get(), bufferPtr, tensorInfo.GetNumBytes());
624 }
625
626 return std::make_pair(ConstTensor(tensorInfo, data.get()), std::move(data));
627}
628
629std::pair<ConstTensor, std::unique_ptr<float[]>>
630OnnxParserImpl::CreateConstTensor(const std::string name,
632{
633 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
634 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
635
636 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
637 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
638 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
639
640 // Makes sure IsConstant flag is set.
641 tensorInfo.SetConstant();
642
643 // Const tensors requires at least a list of values
644 if (tensorInfo.GetNumElements() == 0)
645 {
646 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
647 name,
648 CHECK_LOCATION().AsString()));
649 }
650
651 auto srcData = onnxTensor.float_data().data();
652 // Copy the value list entries into the destination
653 if (!onnxTensor.has_raw_data())
654 {
655 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
656 {
657 throw ParseException(
658 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
659 "elements ({}) {}",
660 onnxTensor.float_data_size(),
661 name,
662 tensorInfo.GetNumElements(),
663 CHECK_LOCATION().AsString()));
664 }
665 return CreateConstTensorImpl<float>(srcData, tensorInfo, permutationVector);
666 }
667 else
668 {
669 return CreateConstTensorImpl<float>(reinterpret_cast<const float*>(onnxTensor.raw_data().c_str()),
670 tensorInfo,
671 permutationVector);
672 }
673}
674
675std::pair<ConstTensor, std::unique_ptr<int32_t[]>>
676OnnxParserImpl::CreateInt64ConstTensor(const std::string name,
677 armnn::Optional<armnn::PermutationVector&> permutationVector)
678{
679 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
680 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
681
682 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
683 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::INT64);
684
685 // Makes sure IsConstant flag is set.
686 tensorInfo.SetConstant();
687 uint numElements = tensorInfo.GetNumElements();
688
689 // Const tensors requires at least a list of values
690 if (numElements == 0)
691 {
692 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
693 name,
694 CHECK_LOCATION().AsString()));
695 }
696
697 // Copy the value list entries into the destination
698 if (!onnxTensor.has_raw_data())
699 {
700 auto srcData = onnxTensor.int64_data().data();
701 if(numElements != static_cast<uint>(onnxTensor.int64_data_size()))
702 {
703 throw ParseException(
704 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
705 "elements ({}) {}",
706 onnxTensor.int64_data_size(),
707 name,
708 tensorInfo.GetNumElements(),
709 CHECK_LOCATION().AsString()));
710 }
711
712 std::vector<int32_t> int32Data;
713 for(uint i = 0; i < numElements; i++)
714 {
715 int32_t int32Value = CHECKED_INT32(srcData[i]);
716 int32Data.push_back(int32Value);
717 }
718
719 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
720 }
721 else
722 {
723 auto srcData = reinterpret_cast<const int64_t*>(onnxTensor.raw_data().c_str());
724 std::vector<int32_t> int32Data;
725 for(uint i = 0; i < numElements; i++)
726 {
727 int32_t int32Value = CHECKED_INT32(srcData[i]);
728 int32Data.push_back(int32Value);
729 }
730 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
731 }
732}
733
735{
736 FILE* fd = fopen(graphFile, "r");
737
738 if (fd == nullptr)
739 {
740 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
741 }
742
743 // Parse the file into a message
744 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
745 using google::protobuf::io::FileInputStream;
746 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
747 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
748 fclose(fd);
749
750 if (!success)
751 {
752 std::stringstream error;
753 error << "Failed to parse graph file";
754 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
755 }
756 return modelProto;
757}
758
760{
761 ResetParser();
762 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
763 return CreateNetworkFromModel(*modelProto);
764}
765
767 const std::map<std::string, armnn::TensorShape>& inputShapes)
768{
769 ResetParser();
770 m_InputShapes = inputShapes;
771 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
772 return CreateNetworkFromModel(*modelProto);
773}
774
775INetworkPtr OnnxParserImpl::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent)
776{
777 ResetParser();
778 ModelPtr modelProto = LoadModelFromBinary(binaryContent);
779 return CreateNetworkFromModel(*modelProto);
780}
781
782INetworkPtr OnnxParserImpl::CreateNetworkFromBinary(const std::vector<uint8_t>& binaryContent,
783 const std::map<std::string, armnn::TensorShape>& inputShapes)
784{
785 ResetParser();
786 m_InputShapes = inputShapes;
787 ModelPtr modelProto = LoadModelFromBinary(binaryContent);
788 return CreateNetworkFromModel(*modelProto);
789}
790
791ModelPtr OnnxParserImpl::LoadModelFromBinary(const std::vector<uint8_t>& binaryContent)
792{
793 if (binaryContent.size() == 0)
794 {
795 throw ParseException(fmt::format("Missing binary content", CHECK_LOCATION().AsString()));
796 }
797 // Parse the file into a message
798 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
799
800 google::protobuf::io::CodedInputStream codedStream(binaryContent.data(), static_cast<int>(binaryContent.size()));
801 codedStream.SetTotalBytesLimit(INT_MAX);
802 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
803
804 if (!success)
805 {
806 std::stringstream error;
807 error << "Failed to parse graph";
808 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
809 }
810 return modelProto;
811}
812
814{
815 FILE* fd = fopen(graphFile, "rb");
816
817 if (fd == nullptr)
818 {
819 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
820 }
821
822 // Parse the file into a message
823 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
824
825 google::protobuf::io::FileInputStream inStream(fileno(fd));
826 google::protobuf::io::CodedInputStream codedStream(&inStream);
827 codedStream.SetTotalBytesLimit(INT_MAX);
828 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
829 fclose(fd);
830
831 if (!success)
832 {
833 std::stringstream error;
834 error << "Failed to parse graph file";
835 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
836 }
837 return modelProto;
838
839}
840
842{
843 ResetParser();
844 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
845 return CreateNetworkFromModel(*modelProto);
846}
847
849 const std::map<std::string, armnn::TensorShape>& inputShapes)
850{
851 ResetParser();
852 m_InputShapes = inputShapes;
853 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
854 return CreateNetworkFromModel(*modelProto);
855}
856
858{
859 if (protoText == "")
860 {
861 throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
862 CHECK_LOCATION().AsString()));
863 }
864 // Parse the string into a message
865 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
866 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
867 if (!success)
868 {
869 std::stringstream error;
870 error << "Failed to parse graph file";
871 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
872 }
873 return modelProto;
874}
875
877{
878 ResetParser();
879 ModelPtr modelProto = LoadModelFromString(protoText);
880 return CreateNetworkFromModel(*modelProto);
881}
882
884 const std::map<std::string, armnn::TensorShape>& inputShapes)
885{
886 ResetParser();
887 m_InputShapes = inputShapes;
888 ModelPtr modelProto = LoadModelFromString(protoText);
889 return CreateNetworkFromModel(*modelProto);
890}
891
892INetworkPtr OnnxParserImpl::CreateNetworkFromModel(onnx::ModelProto& model)
893{
894 m_Network = INetwork::Create();
895 try
896 {
897 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
898 LoadGraph();
899 }
900 catch (const ParseException& e)
901 {
902 Cleanup();
903 throw e;
904 }
905 Cleanup();
906 return std::move(m_Network);
907}
908
909void OnnxParserImpl::LoadGraph()
910{
911 if (m_Graph.get() == nullptr)
912 {
913 throw armnn::ParseException(fmt::format("Graph pointer is null {}", CHECK_LOCATION().AsString()));
914 }
915
916 //Fill m_TensorsInfo with the shapes and value of every tensor
917 SetupInfo(m_Graph->mutable_output());
918 SetupInfo(m_Graph->mutable_input());
919 SetupInfo(m_Graph->mutable_value_info());
920
921 for (auto tensor : m_Graph->initializer())
922 {
923 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
924 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
925 m_TensorsInfo[tensor.name()].m_dtype =
926 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
927 }
928
929 SetupInputLayers();
930 SetupOutputLayers();
931
932 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
933 DetectFullyConnected();
934
935 //Parsing the graph
936 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
937 {
938 auto node = m_Graph->node(static_cast<int>(nodeIndex));
939 const std::string& operation = node.op_type();
940
941 // check which layers we handled already (add and matmul fused as FC)
942 if (operation == "MatMul" )
943 {
944 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
945 {
946 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
947 AddFullyConnected(node);
948 }
949 }
950 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
951 {
952 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
953 AddFullyConnected(m_Graph->node(matmulIndex), &node);
954 }
955 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
956 {
957 auto it = m_ParserFunctions.find(operation);
958 if (it != m_ParserFunctions.end())
959 {
960 auto func = it->second;
961 (this->*func)(node);
962 }
963 else
964 {
965 throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
966 operation,
967 node.name(),
968 CHECK_LOCATION().AsString()));
969 }
970 }
971 }
972
973 //Making the connections between outputs and inputs of each layers
974 for (const auto& tensorCon : m_TensorConnections)
975 {
976 if (tensorCon.second.outputSlot != nullptr)
977 {
978 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
979 {
980 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
981 }
982 }
983 }
984
985 // Get output info.
986 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
987 {
988 auto output = m_Graph->output(outputIndex);
989 m_OutputInfos[output.name()] = *m_TensorsInfo[output.name()].m_info;
990 }
991}
992
993void OnnxParserImpl::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
994{
995 for (auto tensor : *list)
996 {
997 m_TensorsInfo[tensor.name()] = OnnxTensor();
998 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
999 m_TensorsInfo[tensor.name()].m_dtype =
1000 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
1001 }
1002}
1003
1004void OnnxParserImpl::DetectFullyConnected()
1005{
1006 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
1007 auto matmulAndConstant = [&](const std::string& constInput,
1008 const std::string& matmulInput,
1009 int& nodeIndex)
1010 {
1011 auto matmulIt = m_OutputsMap.find(matmulInput);
1012 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
1013 && m_TensorsInfo[constInput].isConstant())
1014 {
1015 nodeIndex = matmulIt->second.second;
1016 return true;
1017 }
1018 return false;
1019 };
1020
1021 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
1022 {
1023 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
1024 for (const std::string& output : node->output())
1025 {
1026 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
1027 }
1028
1029 for (const std::string& input : node->input()) //count how many time a node is used as input
1030 {
1031 auto matmulIt = m_OutputsMap.find(input);
1032 if(matmulIt != m_OutputsMap.end()){
1033 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
1034 }
1035 }
1036
1037 if (node->op_type() == "Add")
1038 {
1039 int matmulIndex = 0;
1040 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
1041 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
1042 {
1043 //matmul and add were fused
1044 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
1045 .push_back(static_cast<size_t>(nodeIndex));
1046
1047 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
1048 .push_back(static_cast<size_t>(matmulIndex));
1049 }
1050 }
1051 }
1052
1053 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
1054 auto matmulIt = m_OutputsMap.find(output.name());
1055 if(matmulIt != m_OutputsMap.end()){
1056 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
1057 }
1058 }
1059}
1060
1061template<typename Location>
1062void OnnxParserImpl::GetInputAndParam(const onnx::NodeProto& node,
1063 std::string* inputName,
1064 std::string* constName,
1065 const Location& location)
1066{
1067 int cstIndex;
1068 if (m_TensorsInfo[node.input(0)].isConstant())
1069 {
1070 cstIndex = 0;
1071 }
1072 else if (m_TensorsInfo[node.input(1)].isConstant())
1073 {
1074 cstIndex = 1;
1075 }
1076 else
1077 {
1078 throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
1079 node.input(0),
1080 node.input(1),
1081 node.name(),
1082 location.AsString()));
1083 }
1084 if(constName)
1085 {
1086 *constName = node.input(cstIndex);
1087 }
1088 if(inputName)
1089 {
1090 *inputName = node.input(!cstIndex);
1091 }
1092}
1093
1094template<typename Location>
1095void OnnxParserImpl::To1DTensor(const std::string& name, const Location& location)
1096{
1097 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
1098 std::vector<uint32_t> newShape;
1099 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
1100 {
1101 if(shape[i] != 1)
1102 {
1103 throw ParseException(
1104 fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
1105 TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
1106 location.AsString()));
1107 }
1108 }
1109 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
1110
1111 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1112}
1113
1114void OnnxParserImpl::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
1115{
1116 ARMNN_ASSERT(node.op_type() == "Conv");
1117
1118 DepthwiseConvolution2dDescriptor desc;
1119 desc.m_PadLeft = convDesc.m_PadLeft;
1120 desc.m_PadRight = convDesc.m_PadRight;
1121 desc.m_PadTop = convDesc.m_PadTop;
1122 desc.m_PadBottom = convDesc.m_PadBottom;
1123 desc.m_StrideX = convDesc.m_StrideX;
1124 desc.m_StrideY = convDesc.m_StrideY;
1125 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
1126
1127 armnn::IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, node.name().c_str());
1128 std::string permuteStr = "permute_" + node.input(1);
1129 std::vector<std::string> tensorIndexes= {node.input(0), permuteStr};
1130
1131 auto weightTensor = CreateConstTensor(node.input(1));
1132 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
1133
1134 // weights come in as [O,1,H,W] from ONNX and need to be converted to ArmNNs depthwise weights layout [1,H,W,O]
1135 armnn::PermutationVector perVec {3, 0, 1, 2};
1136 TensorInfo weightsPermuted = armnnUtils::Permuted(weightTensor.first.GetInfo(), perVec);
1137
1138 // Inserts NewLayer so layers don't need to be re-sorted.
1139 IConnectableLayer* permuteLayer = m_Network->AddPermuteLayer(PermuteDescriptor(perVec),
1140 "permute_layer");
1141 permuteLayer->GetOutputSlot(0).SetTensorInfo(weightsPermuted);
1142 permuteLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1143
1144 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
1145 weightsLayer->GetOutputSlot(0).Connect(permuteLayer->GetInputSlot(0u));
1146
1147 if (node.input_size() == 3)
1148 {
1149 if(!m_TensorsInfo[node.input(2)].isConstant())
1150 {
1151 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1152 node.input(2),
1153 node.name(),
1154 CHECK_LOCATION().AsString()));
1155 }
1156
1157 desc.m_BiasEnabled = true;
1158 auto biasTensor = CreateConstTensor(node.input(2));
1159 tensorIndexes.emplace_back(node.input(2));
1160
1161 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1162 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1163 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1164 }
1165
1166 if (!layer)
1167 {
1168 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1169 }
1170
1171 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1172 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1173 weightsPermuted.GetShape() });
1174
1175 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1176
1177 // register the input connection slots for the layer, connections are made after all layers have been created
1178 // only the tensors for the inputs are relevant, exclude the const tensors
1179 RegisterInputSlots(layer, tensorIndexes);
1180
1181 // register the output connection slots for the layer, connections are made after all layers have been created
1182 RegisterOutputSlots(layer, {node.output(0)});
1183}
1184
1185void OnnxParserImpl::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
1186{
1187 // find matmul inputs
1188 std::string inputName;
1189 std::string weightName;
1190 std::string biasName;
1191 std::string outputName;
1192 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
1193 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
1194 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
1195
1196 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
1197
1198 TensorInfo inputInfo = *m_TensorsInfo[inputName].m_info;
1199 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1200 TensorInfo biasInfo;
1201
1202 std::vector<std::string> inputNames;
1203
1204 FullyConnectedDescriptor desc;
1205 desc.m_BiasEnabled = addNode != nullptr;
1206
1207 IConnectableLayer* layer = nullptr;
1208 if(desc.m_BiasEnabled)
1209 {
1210 // find bias const
1211 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
1212 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
1213 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
1214
1215 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
1216
1217 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
1218 To1DTensor(biasName, CHECK_LOCATION());
1219 biasInfo = *m_TensorsInfo[biasName].m_info;
1220
1221 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
1222 {
1223 throw ParseException(
1224 fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
1225 " and {} ( /!\\ bias should be a 1D tensor) {}",
1226 weightName,
1227 addNode->name(),
1228 TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
1229 m_TensorsInfo[weightName].m_dtype),
1230 TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
1231 m_TensorsInfo[biasName].m_dtype ),
1232 CHECK_LOCATION().AsString()));
1233 }
1234
1235 inputNames = { inputName, weightName, biasName };
1236 outputName = addNode->output(0);
1237 }
1238 else
1239 {
1240 inputNames = { inputName, weightName };
1241 outputName = matmulNode.output(0);
1242 }
1243
1244 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1245 layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
1246
1247 if (!layer)
1248 {
1249 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1250 }
1251
1252 if (inputInfo.GetNumDimensions() > 2)
1253 {
1254 // Add reshape to flatten to 2D [batch_size, input_size],
1255 // where "input_size" corresponds to the number of inputs to the layer,
1256 // matching the second dimension of weights,
1257 // and "batch_size" is calculated by dividing the number of elements by "input_size".
1258 std::vector<unsigned int> reshapedDimensions(2);
1259 reshapedDimensions[1] = weightInfo.GetShape()[0];
1260 reshapedDimensions[0] = inputInfo.GetNumElements() / reshapedDimensions[1];
1261
1262 if (inputInfo.GetNumElements() % reshapedDimensions[1] != 0)
1263 {
1264 throw ParseException(
1265 fmt::format("Failed to deduce input tensor shape from filter size {} {}",
1266 reshapedDimensions[1],
1267 CHECK_LOCATION().AsString()));
1268 }
1269
1270 TensorInfo reshapedTensorInfo = inputInfo;
1271 reshapedTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
1272 inputInfo = reshapedTensorInfo;
1273
1274 ReshapeDescriptor reshapeDescriptor;
1275 reshapeDescriptor.m_TargetShape = reshapedTensorInfo.GetShape();
1276
1277 std::string reshapeLayerName = fmt::format("Reshape_for:{}", layer->GetName());
1278 IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(reshapeDescriptor, reshapeLayerName.c_str());
1279
1280 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedTensorInfo);
1281 reshapeLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
1282
1283 RegisterInputSlots(reshapeLayer, {inputName});
1284 inputNames[0] = reshapeLayerName;
1285 }
1286
1287 auto outputInfo = ComputeOutputInfo({ outputName },
1288 layer,
1289 { inputInfo.GetShape(),
1290 weightInfo.GetShape() });
1291 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1292
1293 RegisterInputSlots(layer, inputNames);
1294
1295 // Add constant layer to store weights/biases and connect to FullyConnected layer..
1296 if(m_TensorsInfo[weightName].isConstant())
1297 {
1298 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1299
1300 weightInfo.SetConstant();
1301 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1302 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1303 }
1304
1305 if(desc.m_BiasEnabled && m_TensorsInfo[biasName].isConstant())
1306 {
1307 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(biasName).first);
1308
1309 biasInfo.SetConstant();
1310 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1311 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1312 }
1313
1314 if (outputInfo[0].GetNumDimensions() > 2)
1315 {
1316 // Calculate reshape to flatten to 2D [batch_size, input_size]
1317 std::vector<unsigned int> reshapedDimensions(2);
1318 reshapedDimensions[1] = weightInfo.GetShape()[1];
1319 reshapedDimensions[0] = outputInfo[0].GetNumElements() / reshapedDimensions[1];
1320
1321 if (outputInfo[0].GetNumElements() % reshapedDimensions[1] != 0)
1322 {
1323 throw ParseException(
1324 fmt::format("Failed to deduce output tensor shape from filter size {} {}",
1325 reshapedDimensions[1],
1326 CHECK_LOCATION().AsString()));
1327 }
1328
1329 armnn::TensorInfo reshapedOutputTensorInfo = outputInfo[0];
1330 reshapedOutputTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
1331 layer->GetOutputSlot(0).SetTensorInfo(reshapedOutputTensorInfo);
1332
1333 ReshapeDescriptor desc;
1334 desc.m_TargetShape = outputInfo[0].GetShape();
1335
1336 std::string reshapeLayerName = fmt::format("ExpandDims_for:{}", layer->GetName());
1337 IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(desc, reshapeLayerName.c_str());
1338
1339 layer->GetOutputSlot(0).Connect(reshapeLayer->GetInputSlot(0));
1340 reshapeLayer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1341
1342 RegisterInputSlots(reshapeLayer, {layer->GetName()});
1343 layer = reshapeLayer;
1344 }
1345
1346 RegisterOutputSlots(layer, { outputName });
1347}
1348
1349void OnnxParserImpl::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
1350{
1351
1352 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1353 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1354
1355 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1356
1357 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
1358 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1359 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1360
1361 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
1362 desc.m_PoolWidth = kernel_shape[1];
1363 desc.m_PoolHeight = kernel_shape[0];
1364
1365 if(strides.empty())
1366 {
1367 desc.m_StrideX = 1;
1368 desc.m_StrideY = 1;
1369 }
1370 else
1371 {
1372 desc.m_StrideX = strides[1];
1373 desc.m_StrideY = strides[0];
1374 }
1375
1376 //Check new padding version first
1377 if(pads.empty())
1378 {
1379 //Check deprecated version
1380 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1381 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1382 {
1383 bool isUpper;
1384 if( paddingString == "SAME_LOWER")
1385 {
1386 isUpper = false;
1387 }
1388 else if (paddingString == "SAME_UPPER")
1389 {
1390 isUpper = true;
1391 }
1392 else
1393 {
1394 throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
1395 "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
1396 node.name(),
1397 paddingString,
1398 CHECK_LOCATION().AsString()));
1399 }
1400 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1401 uint32_t inputHeight = inputInfo.GetShape()[2];
1402 uint32_t inputWidth = inputInfo.GetShape()[3];
1403 CalcPadding(inputHeight,
1404 desc.m_PoolHeight,
1405 desc.m_StrideY,
1406 1u,
1407 &desc.m_PadTop,
1408 &desc.m_PadBottom,
1409 isUpper);
1410 CalcPadding(inputWidth,
1411 desc.m_PoolWidth,
1412 desc.m_StrideX,
1413 1u,
1414 &desc.m_PadLeft,
1415 &desc.m_PadRight,
1416 isUpper);
1417 }
1418 }
1419 else
1420 {
1421 desc.m_PadTop = pads[0];
1422 desc.m_PadLeft = pads[1];
1423 desc.m_PadBottom = pads[2];
1424 desc.m_PadRight = pads[3];
1425 }
1426
1427 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1428
1429 if (!layer)
1430 {
1431 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1432 }
1433
1434 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1435 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1436
1437 // register the input connection slots for the layer, connections are made after all layers have been created
1438 // only the tensors for the inputs are relevant, exclude the const tensors
1439 RegisterInputSlots(layer, {node.input(0)});
1440
1441 // register the output connection slots for the layer, connections are made after all layers have been created
1442 RegisterOutputSlots(layer, {node.output(0)});
1443}
1444
1445std::pair<std::string, std::string> OnnxParserImpl::AddPrepareBroadcast(const std::string& input0,
1446 const std::string& input1)
1447{
1448 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1449
1450 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1451 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1452
1453 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1454 {
1455 auto outputName = fmt::format("reshape_output_{}", input1);
1456 PrependForBroadcast(outputName, input1, input0);
1457 inputs.second = outputName;
1458 }
1459 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1460 {
1461 auto outputName = fmt::format("reshape_output_{}", input0);
1462 PrependForBroadcast(outputName, input0, input1);
1463 inputs.first = outputName;
1464 }
1465 return inputs;
1466}
1467
1468void OnnxParserImpl::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
1469{
1470 auto armnnTensor = CreateConstTensor(tensorName);
1471 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1472 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1473 RegisterOutputSlots(layer, {tensorName});
1474}
1475
1476void OnnxParserImpl::CreateInt64ConstantLayer(const std::string& tensorName, const std::string& layerName)
1477{
1478 auto armnnTensor = CreateInt64ConstTensor(tensorName);
1479 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1480 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1481 RegisterOutputSlots(layer, {tensorName});
1482}
1483
1484void OnnxParserImpl::CreateReshapeLayer(const std::string& inputName,
1485 const std::string& outputName,
1486 const std::string& layerName)
1487{
1488 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1489 ReshapeDescriptor reshapeDesc;
1490 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1491
1492 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
1493
1494 if (!layer)
1495 {
1496 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1497 }
1498
1499 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1500
1501 // register the input connection slots for the layer, connections are made after all layers have been created
1502 // only the tensors for the inputs are relevant, exclude the const tensors
1503 RegisterInputSlots(layer, {inputName});
1504
1505 // register the output connection slots for the layer, connections are made after all layers have been created
1506 RegisterOutputSlots(layer, {outputName});
1507}
1508
1509void OnnxParserImpl::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
1510{
1511 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
1512 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1513
1514 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1515
1516 ActivationDescriptor desc;
1517 desc.m_Function = func;
1518
1519 if (func == ActivationFunction::BoundedReLu)
1520 {
1521 if (node.input_size() == 1 && node.attribute_size() > 0)
1522 {
1523 desc.m_A = ReadOptionalNodeFloatAttribute(node, "max", std::numeric_limits<float>::max());
1524 desc.m_B = ReadOptionalNodeFloatAttribute(node, "min", std::numeric_limits<float>::lowest());
1525 }
1526 else
1527 {
1528 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1529 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1530 }
1531 }
1532
1533 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
1534
1535 if (!layer)
1536 {
1537 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1538 }
1539
1540 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1541 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1542
1543 // register the input connection slots for the layer, connections are made after all layers have been created
1544 // only the tensors for the inputs are relevant, exclude the const tensors
1545 RegisterInputSlots(layer, {node.input(0)});
1546
1547 // register the output connection slots for the layer, connections are made after all layers have been created
1548 RegisterOutputSlots(layer, {node.output(0)});
1549}
1550
1551void OnnxParserImpl::ParseClip(const onnx::NodeProto& node)
1552{
1553 ParseActivation(node, ActivationFunction::BoundedReLu);
1554}
1555
1556void OnnxParserImpl::ParseSigmoid(const onnx::NodeProto& node)
1557{
1558 ParseActivation(node, ActivationFunction::Sigmoid);
1559}
1560
1561void OnnxParserImpl::ParseTanh(const onnx::NodeProto& node)
1562{
1563 ParseActivation(node, ActivationFunction::TanH);
1564}
1565
1566void OnnxParserImpl::ParseRelu(const onnx::NodeProto& node)
1567{
1568 ParseActivation(node, ActivationFunction::ReLu);
1569}
1570
1571void OnnxParserImpl::ParseLeakyRelu(const onnx::NodeProto& node)
1572{
1573 ParseActivation(node, ActivationFunction::LeakyReLu);
1574}
1575
1576void OnnxParserImpl::ParseAdd(const onnx::NodeProto& node)
1577{
1578 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1579 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1580
1581 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1582
1583 // IVGCVSW-1576: unify broadcast validation code across layers
1584
1585 // Checking broadcast compatibility : only scalar or 1D tensors
1586 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1587 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1588 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1589 if (input0.GetNumDimensions() != input1.GetNumDimensions())
1590 {
1591 throw armnn::ParseException(fmt::format("Dimension mismatch in node {} {}",
1592 node.name(),
1593 CHECK_LOCATION().AsString()));
1594 }
1595
1596 unsigned int numDims = input0.GetNumDimensions();
1597 for (unsigned int i = 0; i < numDims; i++)
1598 {
1599 unsigned int dim0 = input0.GetShape()[i];
1600 unsigned int dim1 = input1.GetShape()[i];
1601 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
1602 {
1603 throw ParseException(
1604 fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1605 "Input dimensions should either match or one should be of size 1 and here, "
1606 "{} and {} {}",
1607 node.name(),
1608 TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1609 m_TensorsInfo[inputs.first].m_dtype),
1610 TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1611 m_TensorsInfo[inputs.second].m_dtype),
1612 CHECK_LOCATION().AsString()));
1613 }
1614 }
1615
1616
1617 IConnectableLayer* layer = m_Network->AddElementwiseBinaryLayer(BinaryOperation::Add, node.name().c_str());
1618
1619 if (!layer)
1620 {
1621 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1622 }
1623
1624 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1625 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1626 m_TensorsInfo[inputs.second].m_info->GetShape() });
1627 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1628
1629 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1630 if(m_TensorsInfo[inputs.first].isConstant()) {
1631 CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
1632 }
1633 if(m_TensorsInfo[inputs.second].isConstant()) {
1634 CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
1635 }
1636 RegisterInputSlots(layer, {inputs.first, inputs.second});
1637
1638 // register the output connection
1639 RegisterOutputSlots(layer, {node.output(0)});
1640}
1641
1642void OnnxParserImpl::ParseAveragePool(const onnx::NodeProto& node)
1643{
1645 desc.m_PoolType = PoolingAlgorithm::Average;
1646
1647 uint32_t count_include_pad = 0;
1648 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1649 if(count_include_pad) {
1650 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1651 }
1652 AddPoolingLayer(node, desc);
1653}
1654
1655void OnnxParserImpl::ParseBatchNormalization(const onnx::NodeProto& node)
1656{
1657 //IGNORE momentum parameter and spatial parameters
1658
1659 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1660 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1661
1662 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1663 for(int ind = 1; ind < node.input_size(); ++ind)
1664 {
1665 auto tensor = node.input(ind);
1666 if(! m_TensorsInfo[tensor].isConstant())
1667 {
1668 throw ParseException(
1669 fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1670 tensor,
1671 node.name(),
1672 CHECK_LOCATION().AsString()));
1673 }
1674 }
1675
1676 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1677 BatchNormalizationDescriptor desc;
1678 desc.m_Eps = epsilon;
1679
1680 auto scaleTensor = CreateConstTensor(node.input(1));
1681 auto biasTensor = CreateConstTensor(node.input(2));
1682 auto meanTensor = CreateConstTensor(node.input(3));
1683 auto varTensor = CreateConstTensor(node.input(4));
1684
1685 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1686 meanTensor.first,
1687 varTensor.first,
1688 biasTensor.first,
1689 scaleTensor.first,
1690 node.name().c_str());
1691
1692 if (!layer)
1693 {
1694 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1695 }
1696
1697 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1698 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1699
1700 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1701
1702 // register the output connection
1703 RegisterOutputSlots(layer, {node.output(0)});
1704}
1705
1706void OnnxParserImpl::ParseConcat(const onnx::NodeProto& node)
1707{
1708 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1709
1710 uint32_t numConcatView = static_cast<uint32_t>(node.input_size());
1711 uint32_t inputRank = m_TensorsInfo[node.input(0)].m_info->GetNumDimensions();
1712
1713 int axisInt = ReadMandatoryNodeIntAttribute(node, "axis");
1714
1715 unsigned int concatDimInput = static_cast<unsigned int>(
1716 (static_cast<int>(inputRank) + axisInt) % static_cast<int>(inputRank));
1717
1718 OriginsDescriptor concatDescriptor(numConcatView, inputRank);
1719 concatDescriptor.SetConcatAxis(concatDimInput);
1720
1721 unsigned int mergeDimOrigin = 0;
1722
1723 std::vector<TensorShape> inputShapes;
1724 std::vector<std::string> tensorIds;
1725
1726 for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1727 {
1728 std::string nodeName = node.input(static_cast<int>(viewIndex));
1729 auto inputTensorInfo = *m_TensorsInfo[nodeName].m_info;
1730 inputShapes.push_back(inputTensorInfo.GetShape());
1731 tensorIds.push_back(nodeName);
1732
1733 // Set up concatDescriptor view origin
1735 inputTensorInfo, concatDescriptor, concatDimInput, viewIndex, mergeDimOrigin);
1736 }
1737
1738 IConnectableLayer* layer = m_Network->AddConcatLayer(concatDescriptor, node.name().c_str());
1739
1740 if (!layer)
1741 {
1742 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1743 }
1744
1745 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, inputShapes,
1746 m_TensorsInfo[node.input(0)].m_dtype);
1747
1748 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1749
1750 // register the input connection slots for the layer, connections are made after all layers have been created
1751 RegisterInputSlots(layer, tensorIds);
1752
1753 // register the output connection slots for the layer, connections are made after all layers have been created
1754 RegisterOutputSlots(layer, { node.output(0) });
1755}
1756
1757void OnnxParserImpl::ParseConstant(const onnx::NodeProto& node)
1758{
1759 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1760 if (!node.attribute(0).has_t())
1761 {
1762 throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1763 node.name(),
1764 CHECK_LOCATION().AsString()));
1765 }
1766 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1767
1768 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1769 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1770 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1771 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1772
1773 if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_FLOAT)
1774 {
1775 CreateConstantLayer(node.output(0), node.name());
1776 }
1777 else if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_INT64)
1778 {
1779 CreateInt64ConstantLayer(node.output(0), node.name());
1780 }
1781 else
1782 {
1783 throw ParseException(fmt::format("Data type not support for Constant node '{}' {}",
1784 node.name(),
1785 CHECK_LOCATION().AsString()));
1786 }
1787}
1788
1789void OnnxParserImpl::ParseConv(const onnx::NodeProto& node)
1790{
1791 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1792 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1793
1794 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1795
1796 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1797 {
1798 throw ParseException(
1799 fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1800 node.name(),
1801 TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1802 m_TensorsInfo[node.input(0)].m_dtype),
1803 CHECK_LOCATION().AsString()));
1804 }
1805
1806 if(!m_TensorsInfo[node.input(1)].isConstant())
1807 {
1808 throw ParseException(
1809 fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1810 node.input(1),
1811 node.name(),
1812 CHECK_LOCATION().AsString()));
1813 }
1814
1815 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1816
1817 Convolution2dDescriptor desc;
1818 desc.m_BiasEnabled = false;
1819
1820 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1821 if(strides.empty())
1822 {
1823 desc.m_StrideX = 1;
1824 desc.m_StrideY = 1;
1825 }
1826 else
1827 {
1828 desc.m_StrideX = strides[1];
1829 desc.m_StrideY = strides[0];
1830 }
1831
1832 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1833 if(!dilations.empty())
1834 {
1835 desc.m_DilationX = dilations[1];
1836 desc.m_DilationY = dilations[0];
1837 }
1838
1839 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1840 //Check new padding version first
1841 if(pads.empty())
1842 {
1843 //Check deprecated version
1844 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1845 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1846 {
1847 bool isUpper;
1848 if( paddingString == "SAME_LOWER")
1849 {
1850 isUpper = false;
1851 }
1852 else if (paddingString == "SAME_UPPER")
1853 {
1854 isUpper = true;
1855 }
1856 else
1857 {
1858 throw ParseException(
1859 fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1860 "supported and found {} {}",
1861 node.name(),
1862 paddingString,
1863 CHECK_LOCATION().AsString()));
1864 }
1865 uint32_t inputHeight = inputInfo.GetShape()[2];
1866 uint32_t inputWidth = inputInfo.GetShape()[3];
1867
1868 uint32_t weightHeight;
1869 uint32_t weightWidth;
1870 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1871 if (kernel_shape.empty())
1872 {
1873 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1874 weightHeight = weightTensorInfo.GetShape()[2];
1875 weightWidth = weightTensorInfo.GetShape()[3];
1876 }
1877 else
1878 {
1879 weightHeight = kernel_shape[0];
1880 weightWidth = kernel_shape[1];
1881 }
1882 CalcPadding(inputHeight,
1883 weightHeight,
1884 desc.m_StrideY,
1885 desc.m_DilationY,
1886 &desc.m_PadTop,
1887 &desc.m_PadBottom,
1888 isUpper);
1889 CalcPadding(inputWidth,
1890 weightWidth,
1891 desc.m_StrideX,
1892 desc.m_DilationX,
1893 &desc.m_PadLeft,
1894 &desc.m_PadRight,
1895 isUpper);
1896 }
1897 }
1898 else
1899 {
1900 desc.m_PadTop = pads[0];
1901 desc.m_PadLeft = pads[1];
1902 desc.m_PadBottom = pads[2];
1903 desc.m_PadRight = pads[3];
1904 }
1905
1906 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1907 if(group > 1)
1908 {
1909 if (group > inputInfo.GetShape()[1])
1910 {
1911 throw ParseException(
1912 fmt::format("Error parsing Convolution node: {}. "
1913 "The 'group'={} parameter cannot be larger than the "
1914 "channel of the input shape={} (in NCHW format). {}",
1915 node.name(),
1916 group,
1917 inputInfo.GetShape()[1],
1918 CHECK_LOCATION().AsString()));
1919 }
1920 else if (group == inputInfo.GetShape()[1])
1921 {
1922 // we use a depthwise convolution here, because the number of groups equals to the
1923 // input channels
1924 AddConvLayerWithDepthwiseConv(node, desc);
1925 return;
1926 }
1927 else
1928 {
1929 throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1930 "The 'group'={} parameter should be 1 or be equal to the "
1931 "channel of the input shape={} (in NCHW format). {}",
1932 node.name(),
1933 group,
1934 inputInfo.GetShape()[1],
1935 CHECK_LOCATION().AsString()));
1936 }
1937 }
1938
1939 node.input_size() == 3 ? desc.m_BiasEnabled = true : desc.m_BiasEnabled = false;
1940 armnn::IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, node.name().c_str());
1941 std::vector<std::string> tensorIndexes= {node.input(0), node.input(1)};
1942
1943 auto weightTensor = CreateConstTensor(node.input(1));
1944
1945 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(weightTensor.first);
1946 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightTensor.first.GetInfo());
1947 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1948
1949 if (node.input_size() == 3)
1950 {
1951 if(!m_TensorsInfo[node.input(2)].isConstant())
1952 {
1953 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1954 node.input(2),
1955 node.name(),
1956 CHECK_LOCATION().AsString()));
1957 }
1958 desc.m_BiasEnabled = true;
1959 auto biasTensor = CreateConstTensor(node.input(2));
1960
1961 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(biasTensor.first);
1962 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensor.first.GetInfo());
1963 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1964
1965 tensorIndexes.emplace_back(node.input(2));
1966 }
1967
1968 if (!layer)
1969 {
1970 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
1971 }
1972
1973 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1974 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1975 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1976 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1977
1978 // register the input connection slots for the layer, connections are made after all layers have been created
1979 // only the tensors for the inputs are relevant, exclude the const tensors
1980 RegisterInputSlots(layer, tensorIndexes);
1981
1982 // register the output connection slots for the layer, connections are made after all layers have been created
1983 RegisterOutputSlots(layer, {node.output(0)});
1984}
1985
1986void OnnxParserImpl::ParseFlatten(const onnx::NodeProto& node)
1987{
1988 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1989 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1990
1991 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1992 m_TensorsInfo[node.input(0)].m_dtype,
1993 onnx::TensorProto::FLOAT);
1994
1995 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1996 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1997
1998 /// Negative axis conversion
1999 if (axis < 0)
2000 {
2001 axis += inputShape.GetNumDimensions();
2002 }
2003
2004 /// Check Axis is within dimensions
2005 if (axis < 0 || axis >= inputShape.GetNumDimensions())
2006 {
2007 throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
2008 axis, inputShape.GetNumDimensions(), node.name()));
2009 }
2010
2011 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
2012 uint dimension1{1};
2013 uint dimension2{1};
2014 uint i{0};
2015
2016 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
2017 for (i = 0; i < axis; i++){
2018 dimension1 *= inputShape[i];
2019 }
2020
2021 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
2022 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
2023 dimension2 *= inputShape[i];
2024 }
2025
2026 TensorShape outputShape{dimension1, dimension2};
2027
2028 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
2029 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2030 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2031}
2032
2033void OnnxParserImpl::ParseGather(const onnx::NodeProto& node)
2034{
2035 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2036 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2037
2038 armnn::GatherDescriptor gatherDescriptor;
2039 gatherDescriptor.m_Axis = static_cast<int>(ReadOptionalNodeInt64Attribute(node, "axis", 0));
2040
2041 IConnectableLayer* layer = m_Network->AddGatherLayer(gatherDescriptor, node.name().c_str());
2042
2043 if (!layer)
2044 {
2045 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2046 }
2047
2048 const TensorShape& inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2049 const TensorShape& indicesShape = m_TensorsInfo[node.input(1)].m_info->GetShape();
2050 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, { inputShape, indicesShape },
2051 m_TensorsInfo[node.input(0)].m_dtype);
2052 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2053
2054 // register the input connection slots for the layer, connections are made after all layers have been created
2055 RegisterInputSlots(layer, { node.input(0), node.input(1) });
2056
2057 // register the output connection slots for the layer, connections are made after all layers have been created
2058 RegisterOutputSlots(layer, { node.output(0) });
2059}
2060
2061void OnnxParserImpl::ParseGemm(const onnx::NodeProto& node)
2062{
2063 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3);
2064 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2065
2066 int transA = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transA", 0));
2067 int transB = static_cast<int>(ReadOptionalNodeUint32Attribute(node, "transB", 0));
2068 float alpha = ReadOptionalNodeFloatAttribute(node, "alpha", 1.0);
2069 float beta = ReadOptionalNodeFloatAttribute(node, "beta", 1.0);
2070 bool biasEnabled = node.input_size() == 3;
2071
2072 TensorShape input0Shape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2073 TensorShape input1Shape = m_TensorsInfo[node.input(1)].m_info->GetShape();
2074
2075 // if transB != 0, add transpose to the input1 (tanspose weight matrix in FullyConnected)
2076 armnn::FullyConnectedDescriptor fullyConnectedDescriptor;
2077 fullyConnectedDescriptor.m_BiasEnabled = biasEnabled;
2078 fullyConnectedDescriptor.m_TransposeWeightMatrix = transB;
2079
2080 IConnectableLayer* layer = nullptr;
2081
2082 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
2083 layer = m_Network->AddFullyConnectedLayer(fullyConnectedDescriptor, node.name().c_str());
2084
2085 if (!layer)
2086 {
2087 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2088 }
2089
2090 // if transA != 0, add transpose to the input0
2091 if (transA != 0)
2092 {
2093 std::string transAName = "transpose_" + node.input(0);
2094 armnn::TransposeDescriptor transposeADescriptor;
2095 transposeADescriptor.m_DimMappings = { 1, 0 };
2096 IConnectableLayer* transALayer = m_Network->AddTransposeLayer(transposeADescriptor, transAName.c_str());
2097
2098 if (!transALayer)
2099 {
2100 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2101 }
2102
2103 auto transAInfo = ComputeOutputInfo({ transAName }, transALayer, { input0Shape });
2104 transALayer->GetOutputSlot(0).SetTensorInfo(transAInfo[0]);
2105 transALayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
2106 // register the input connection slots for the layer, connections are made after all layers have been created
2107 RegisterInputSlot(transALayer, node.input(0), 0);
2108 input0Shape = transAInfo[0].GetShape();
2109 }
2110 else
2111 {
2112 RegisterInputSlot(layer, node.input(0), 0);
2113 }
2114
2115 // Add constant layer to store weights/biases and connect to FullyConnected layer.
2116 if(m_TensorsInfo[node.input(1)].isConstant())
2117 {
2118 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(1)).first);
2119 TensorInfo weightInfo = *m_TensorsInfo[node.input(1)].m_info;
2120 weightInfo.SetConstant();
2121 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
2122
2123 // if alpha != 1, multiply to the weight
2124 if (alpha != 1)
2125 {
2126 std::string activationName = "activation_" + node.input(1);
2127 armnn::ActivationDescriptor activationDescriptor;
2128 activationDescriptor.m_A = alpha;
2129 activationDescriptor.m_Function = ActivationFunction::Linear;
2130 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
2131
2132 if (!actLayer)
2133 {
2134 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2135 }
2136
2137 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { weightInfo.GetShape() });
2138 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2139 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2140 weightsLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
2141 input1Shape = actInfo[0].GetShape();
2142 }
2143 else
2144 {
2145 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2146 input1Shape = weightInfo.GetShape();
2147 }
2148 }
2149 else
2150 {
2151 // if alpha != 1, multiply to the weight
2152 if (alpha != 1)
2153 {
2154 std::string activationName = "activation_" + node.input(1);
2155 armnn::ActivationDescriptor activationDescriptor;
2156 activationDescriptor.m_A = alpha;
2157 activationDescriptor.m_Function = ActivationFunction::Linear;
2158 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
2159
2160 if (!actLayer)
2161 {
2162 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2163 }
2164
2165 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { input1Shape });
2166 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2167 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
2168 RegisterInputSlot(actLayer, node.input(1), 0);
2169 input1Shape = actInfo[0].GetShape();
2170 }
2171 else
2172 {
2173 RegisterInputSlot(layer, node.input(1), 1);
2174 }
2175 }
2176
2177 if(biasEnabled && m_TensorsInfo[node.input(2)].isConstant())
2178 {
2179 To1DTensor(node.input(2), CHECK_LOCATION());
2180 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(node.input(2)).first);
2181 TensorInfo biasInfo = *m_TensorsInfo[node.input(2)].m_info;
2182 biasInfo.SetConstant();
2183 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
2184
2185 // if beta != 1, multiply to the bias
2186 if (beta != 1)
2187 {
2188 std::string activationName = "activation_" + node.input(2);
2189 armnn::ActivationDescriptor activationDescriptor;
2190 activationDescriptor.m_A = beta;
2191 activationDescriptor.m_Function = ActivationFunction::Linear;
2192 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
2193
2194 if (!actLayer)
2195 {
2196 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2197 }
2198
2199 auto actInfo = ComputeOutputInfo({ activationName }, actLayer, { biasInfo.GetShape() });
2200 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2201 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2202 biasLayer->GetOutputSlot(0).Connect(actLayer->GetInputSlot(0u));
2203 }
2204 else
2205 {
2206 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2207 }
2208 }
2209 else if (biasEnabled)
2210 {
2211 // Currently we support non-constant tensor of input C (bias) of Gemm when the dimension is 1
2212 if (m_TensorsInfo[node.input(2)].m_info->GetNumDimensions() != 1)
2213 {
2214 throw ParseException(fmt::format("The parser supports constant or non-constant with 1 dimension for "
2215 "Input C of Gemm. Input '{}' in '{}' is not supported '{}'",
2216 node.input(2),
2217 node.name(),
2218 CHECK_LOCATION().AsString()));
2219 }
2220 // if beta != 1, multiply to the bias
2221 if (beta != 1)
2222 {
2223 std::string activationName = "activation_" + node.input(2);
2224 armnn::ActivationDescriptor activationDescriptor;
2225 activationDescriptor.m_A = beta;
2226 activationDescriptor.m_Function = ActivationFunction::Linear;
2227 IConnectableLayer* actLayer = m_Network->AddActivationLayer(activationDescriptor, activationName.c_str());
2228
2229 if (!layer)
2230 {
2231 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2232 }
2233
2234 auto actInfo = ComputeOutputInfo({ activationName },
2235 actLayer,
2236 { m_TensorsInfo[node.input(2)].m_info->GetShape() });
2237 actLayer->GetOutputSlot(0).SetTensorInfo(actInfo[0]);
2238 actLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
2239 RegisterInputSlot(actLayer, node.input(2), 0);
2240 }
2241 else
2242 {
2243 RegisterInputSlot(layer, node.input(2), 2);
2244 }
2245 }
2246
2247 // Set final output of the FullyConnected layer
2248 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
2249 { input0Shape, input1Shape });
2250 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2251
2252 RegisterOutputSlots(layer, {node.output(0)});
2253}
2254
2255void OnnxParserImpl::ParseGlobalAveragePool(const onnx::NodeProto& node)
2256{
2258 desc.m_PoolType = PoolingAlgorithm::Average;
2259
2260 //kernel size is the same as input
2261 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2262 desc.m_PoolWidth = inputShape[3];
2263 desc.m_PoolHeight = inputShape[2];
2264
2265 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
2266
2267 if (!layer)
2268 {
2269 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2270 }
2271
2272 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
2273 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2274
2275 // register the input connection slots for the layer, connections are made after all layers have been created
2276 // only the tensors for the inputs are relevant, exclude the const tensors
2277 RegisterInputSlots(layer, {node.input(0)});
2278
2279 // register the output connection slots for the layer, connections are made after all layers have been created
2280 RegisterOutputSlots(layer, {node.output(0)});
2281}
2282
2283void OnnxParserImpl::ParseMaxPool(const onnx::NodeProto& node)
2284{
2286 desc.m_PoolType = PoolingAlgorithm::Max;
2287 desc.m_PaddingMethod = PaddingMethod::Exclude;
2288 AddPoolingLayer(node, desc);
2289}
2290
2291void OnnxParserImpl::ParseShape(const onnx::NodeProto& node)
2292{
2293 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
2294 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2295
2296 IConnectableLayer* layer = m_Network->AddShapeLayer(node.name().c_str());
2297
2298 if (!layer)
2299 {
2300 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2301 }
2302
2303 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2304 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape}, onnx::TensorProto::INT64);
2305 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
2306
2307 // register the input connection slots for the layer, connections are made after all layers have been created
2308 RegisterInputSlots(layer, {node.input(0)});
2309
2310 // register the output connection slots for the layer, connections are made after all layers have been created
2311 RegisterOutputSlots(layer, {node.output(0)});
2312}
2313
2314void OnnxParserImpl::ParseReshape(const onnx::NodeProto& node)
2315{
2316 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
2317 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
2318
2319 CHECK_VALID_DATATYPE(node.name(), node.input(0),
2320 m_TensorsInfo[node.input(0)].m_dtype,
2321 onnx::TensorProto::FLOAT); //input
2322 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2323 m_TensorsInfo[node.input(1)].m_dtype,
2324 onnx::TensorProto::INT64); //shape
2325
2326 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2327
2328 std::vector<unsigned int> targetShape;
2329 if(m_TensorsInfo[node.input(1)].isConstant())
2330 {
2331 unsigned int dims = static_cast<unsigned int>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2332 targetShape.reserve(dims);
2333
2334 for(uint i = 0; i < dims; i++)
2335 {
2336 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
2337 targetShape[i]= static_cast<unsigned int>(val);
2338 }
2339 }
2340 else
2341 {
2342 // The parser only supports shape (batch, -1) or (-1) for non-constant shape input.
2343 unsigned int dims = m_TensorsInfo[node.input(1)].m_info->GetNumDimensions();
2344 TensorShape shapes = m_TensorsInfo[node.input(1)].m_info->GetShape();
2345 if (dims != 1 || shapes[0] > 2)
2346 {
2347 throw ParseException(fmt::format("Invalid input shape '{}' in Reshape layer '{}' {}",
2348 node.input(1),
2349 node.name(),
2350 CHECK_LOCATION().AsString()));
2351 }
2352
2353 unsigned int numInputElements = m_TensorsInfo[node.input(0)].m_info->GetNumElements();
2354 if (shapes[0] == 1)
2355 {
2356 targetShape = { numInputElements };
2357 }
2358 else if (shapes[0] == 2)
2359 {
2360 targetShape = { inputShape[0] , numInputElements / inputShape[0] };
2361 }
2362 }
2363
2364 if(m_TensorsInfo[node.input(0)].isConstant())
2365 {
2366 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
2367 if(m_TensorsInfo.count(node.output(0)) == 0)
2368 {
2369 m_TensorsInfo[node.output(0)] = OnnxTensor();
2370 }
2371 m_TensorsInfo[node.output(0)].m_tensor =
2372 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
2373 }
2374 else
2375 {
2376 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
2377 {
2378 auto outInfo = ComputeReshapeInfo(
2379 TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2380 inputShape, node.output(0));
2381 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2382 }
2383
2384 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2385 }
2386}
2387
2388void OnnxParserImpl::ParseUnsqueeze(const onnx::NodeProto& node)
2389{
2390 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.input_size()), 1, 2);
2391 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.output_size()), 1);
2392
2393 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
2394 std::vector<uint32_t> dims;
2395 if (node.input_size() == 1 && node.attribute_size() > 0)
2396 {
2397 dims = ReadMandatoryNodeUint32ListAttribute(node, "axes");
2398 }
2399 else
2400 {
2401 CHECK_VALID_DATATYPE(node.name(), node.input(1),
2402 m_TensorsInfo[node.input(1)].m_dtype,
2403 onnx::TensorProto::INT64); //axes
2404
2405 auto int64Axes = m_TensorsInfo[node.input(1)].m_tensor->int64_data().data();
2406 uint numDim = armnn::numeric_cast<uint>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
2407
2408 for(uint i = 0; i < numDim; i++)
2409 {
2410 uint32_t uint32Value = CHECKED_NON_NEGATIVE(CHECKED_INT32(int64Axes[i]));
2411 dims.push_back(uint32Value);
2412 }
2413 }
2414
2415 // Ensure that the axes are sorted
2416 std::sort(dims.begin(), dims.end());
2417
2418 std::vector<unsigned int> targetShape;
2419
2420 if (inputShape.GetDimensionality() != Dimensionality::Scalar)
2421 {
2422 for(uint i = 0; i < inputShape.GetNumDimensions(); i++)
2423 {
2424 targetShape.push_back(inputShape[i]);
2425 }
2426 }
2427
2428 for(uint i = 0; i < dims.size(); i++)
2429 {
2430 targetShape.insert(targetShape.begin() + armnn::numeric_cast<int>(dims[i]), 1);
2431 }
2432
2433 auto outInfo = ComputeReshapeInfo(TensorShape(static_cast<unsigned int>(targetShape.size()), targetShape.data()),
2434 inputShape, node.output(0), m_TensorsInfo[node.input(0)].m_info->GetDataType());
2435 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
2436 m_TensorsInfo[node.output(0)].m_dtype = m_TensorsInfo[node.input(0)].m_dtype;
2437
2438 CreateReshapeLayer(node.input(0), node.output(0), node.name());
2439}
2440
2441void OnnxParserImpl::PrependForBroadcast(const std::string& outputName,
2442 const std::string& input0,
2443 const std::string& input1)
2444{
2445 //input0 should be reshaped to have same number of dim as input1
2446 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
2447
2448 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
2449 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
2450
2451 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
2452 std::vector<uint32_t> newShape;
2453 while(diff > 0)
2454 {
2455 newShape.push_back(1);
2456 diff--;
2457 }
2458 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
2459 {
2460 newShape.push_back(input0Shape[dim]);
2461 }
2462 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
2463
2464 //add the new tensor to m_TensorsInfo
2465 m_TensorsInfo[outputName] = OnnxTensor();
2466 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
2467
2468 //add reshape layer if the parent was not constant...
2469 if( ! m_TensorsInfo[input0].isConstant())
2470 {
2471 CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
2472 }
2473 else //make it constant and it will be create in Add
2474 {
2475 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
2476
2477 }
2478}
2479
2480void OnnxParserImpl::SetupInputLayers()
2481{
2482 //Find user input and add their layers
2483 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
2484 {
2485 auto input = m_Graph->input(inputIndex);
2486 if (!m_TensorsInfo[input.name()].isConstant())
2487 {
2488 IConnectableLayer* layer =
2489 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
2490 TensorInfo tensorInfo = *m_TensorsInfo[input.name()].m_info;
2491 if (tensorInfo.GetShape().GetDimensionality() == Dimensionality::NotSpecified)
2492 {
2493 if (m_InputShapes.find(input.name()) == m_InputShapes.end())
2494 {
2495 throw ParseException(fmt::format("The parser does not support dynamic tensor, "
2496 "please specify input shape for {}. {}",
2497 input.name(),
2498 CHECK_LOCATION().AsString()));
2499 }
2500 else
2501 {
2502 tensorInfo.SetShape(m_InputShapes[input.name()]);
2503 m_TensorsInfo[input.name()].m_info = std::make_unique<TensorInfo>(tensorInfo);
2504 }
2505
2506 }
2507 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2508
2509 m_InputInfos[input.name()] = tensorInfo;
2510
2511 RegisterOutputSlots(layer,{ input.name() });
2512 }
2513 }
2514}
2515
2516void OnnxParserImpl::SetupOutputLayers()
2517{
2518 if(m_Graph->output_size() == 0)
2519 {
2520 throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
2521 }
2522
2523 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
2524 {
2525 IConnectableLayer* layer =
2526 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
2527 m_Graph->output(outputIndex).name().c_str());
2528
2529 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
2530 }
2531}
2532
2533void OnnxParserImpl::RegisterInputSlot(IConnectableLayer* layer,
2534 const std::string& tensorId,
2535 unsigned int slotIndex)
2536{
2537 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2538
2539 auto it = m_TensorConnections.find(tensorId);
2540
2541 if (it == m_TensorConnections.end())
2542 {
2543 //First time seeing this tensor, we need to map it
2544 m_TensorConnections[tensorId] = TensorSlots();
2545 }
2546 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2547}
2548
2549void OnnxParserImpl::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
2550{
2551 if (!layer)
2552 {
2553 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2554 }
2555
2556 if (tensorIds.size() != layer->GetNumInputSlots())
2557 {
2558 throw ParseException(
2559 fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
2560 tensorIds.size(),
2561 layer->GetNumInputSlots(),
2562 CHECK_LOCATION().AsString()));
2563 }
2564
2565 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
2566 {
2567 std::string tensorId = tensorIds[slotIndex];
2568 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2569
2570 auto it = m_TensorConnections.find(tensorId);
2571
2572 if (it == m_TensorConnections.end())
2573 {
2574 // First time seing this tensor, we need to map it
2575 m_TensorConnections[tensorId] = TensorSlots();
2576 }
2577 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2578 }
2579}
2580
2581void OnnxParserImpl::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
2582{
2583 if (!layer)
2584 {
2585 throw armnn::NullPointerException(fmt::format("Layer pointer is null {}", CHECK_LOCATION().AsString()));
2586 }
2587
2588 if (tensorIds.size() != layer->GetNumOutputSlots())
2589 {
2590 throw ParseException(
2591 fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
2592 tensorIds.size(),
2593 layer->GetNumOutputSlots(),
2594 CHECK_LOCATION().AsString()));
2595 }
2596
2597 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
2598 {
2599 std::string tensorId = tensorIds[slotIndex];
2600 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
2601
2602 auto it = m_TensorConnections.find(tensorId);
2603
2604 if (it == m_TensorConnections.end())
2605 {
2606 //First time seing this tensor, we need to map it
2607 m_TensorConnections[tensorId] = TensorSlots();
2608 }
2609
2610 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
2611
2612 // assuming there is only one producer for that tensor
2613 if (tensorSlots.outputSlot != nullptr)
2614 {
2615 throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
2616 "tensor:{} {}",
2617 tensorId,
2618 CHECK_LOCATION().AsString()));
2619 }
2620 tensorSlots.outputSlot = slot;
2621 }
2622
2623}
2624
2626{
2627 for(int i = 0; i < m_Graph->input_size(); ++i)
2628 {
2629 auto input = m_Graph->input(i);
2630 if(input.name() == name)
2631 {
2632 auto it = m_InputInfos.find(name);
2633
2634 if (it != m_InputInfos.end())
2635 {
2636 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2637 }
2638 }
2639 }
2640 throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
2641 name, CHECK_LOCATION().AsString()));
2642}
2643
2645{
2646 for(int i = 0; i < m_Graph->output_size(); ++i)
2647 {
2648 auto output = m_Graph->output(i);
2649 if(output.name() == name)
2650 {
2651 auto it = m_OutputInfos.find(name);
2652
2653 if (it != m_OutputInfos.end())
2654 {
2655 return std::make_pair(static_cast<armnn::LayerBindingId>(i), it->second);
2656 }
2657 }
2658 }
2659 throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
2660 name, CHECK_LOCATION().AsString()));
2661}
2662
2663std::vector<std::string> OnnxParserImpl::GetInputs(ModelPtr& model)
2664{
2665 if(model == nullptr) {
2666 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2667 CHECK_LOCATION().AsString()));
2668 }
2669
2670 std::vector<std::string> inputNames;
2671 std::map<std::string, bool> isConstant;
2672 for(auto tensor : model->graph().initializer())
2673 {
2674 isConstant[tensor.name()] = true;
2675 }
2676 for(auto input : model->graph().input())
2677 {
2678 auto it = isConstant.find(input.name());
2679 if(it == isConstant.end())
2680 {
2681 inputNames.push_back(input.name());
2682 }
2683 }
2684 return inputNames;
2685}
2686
2687std::vector<std::string> OnnxParserImpl::GetOutputs(ModelPtr& model)
2688{
2689 if(model == nullptr) {
2690 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2691 CHECK_LOCATION().AsString()));
2692 }
2693
2694 std::vector<std::string> outputNames;
2695 for(auto output : model->graph().output())
2696 {
2697 outputNames.push_back(output.name());
2698 }
2699 return outputNames;
2700}
2701
2703{
2704 return ONNX_PARSER_VERSION;
2705}
2706
2707} // namespace armnnOnnxParser
#define ARMNN_ASSERT(COND)
Definition Assert.hpp:14
#define ARMNN_NO_DEPRECATE_WARN_BEGIN
#define ARMNN_NO_DEPRECATE_WARN_END
#define CHECK_LOCATION()
#define VALID_INPUTS(NODE, VALID_INPUTS)
#define STR_LIST(...)
#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL,...)
#define CHECK_VALID_SIZE(ACTUAL,...)
#define CHECKED_INT32(VALUE)
#define CHECKED_NON_NEGATIVE(VALUE)
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition Tensor.hpp:330
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition INetwork.hpp:81
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
virtual std::vector< TensorShape > InferOutputShapes(const std::vector< TensorShape > &inputShapes) const =0
Infer the shape of the output(s) based on the provided input shape(s)
virtual unsigned int GetNumInputSlots() const =0
Returns the number of connectable input slots.
virtual unsigned int GetNumOutputSlots() const =0
Returns the number of connectable output slots.
virtual const char * GetName() const =0
Returns the name of the layer.
static INetworkPtr Create(const NetworkOptions &networkOptions={})
Definition Network.cpp:682
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
virtual int Connect(IInputSlot &destination)=0
bool has_value() const noexcept
Definition Optional.hpp:53
const TensorShape & GetShape() const
Definition Tensor.hpp:193
unsigned int GetNumDimensions() const
Definition Tensor.hpp:197
unsigned int GetNumElements() const
Definition Tensor.hpp:198
void SetConstant(const bool IsConstant=true)
Marks the data corresponding to this tensor info as constant.
Definition Tensor.cpp:518
unsigned int GetNumBytes() const
Definition Tensor.cpp:427
void SetShape(const TensorShape &newShape)
Definition Tensor.hpp:195
unsigned int GetNumDimensions() const
Function that returns the tensor rank.
Definition Tensor.cpp:174
Dimensionality GetDimensionality() const
Function that returns the tensor type.
Definition Tensor.hpp:92
unsigned int GetNumElements() const
Function that calculates the tensor elements by multiplying all dimension size which are Specified.
Definition Tensor.cpp:181
static IOnnxParser * CreateRaw()
BindingPointInfo GetNetworkOutputBindingInfo(const std::string &name) const
Retrieve binding info (layer id and tensor info) for the network output identified by the given layer...
armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile)
Create the network from a protobuf binary file on disk.
static void Destroy(IOnnxParser *parser)
armnn::INetworkPtr CreateNetworkFromString(const std::string &protoText)
Create the network directly from protobuf text in a string. Useful for debugging/testing.
BindingPointInfo GetNetworkInputBindingInfo(const std::string &name) const
Retrieve binding info (layer id and tensor info) for the network input identified by the given layer ...
armnn::INetworkPtr CreateNetworkFromBinary(const std::vector< uint8_t > &binaryContent)
Create the network from a protobuf binary vector.
armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile)
Create the network from a protobuf text file on disk.
static IOnnxParserPtr Create()
static ModelPtr LoadModelFromString(const std::string &inputString)
armnn::INetworkPtr CreateNetworkFromString(const std::string &protoText)
Create the network directly from protobuf text in a string. Useful for debugging/testing.
armnn::INetworkPtr CreateNetworkFromBinary(const std::vector< uint8_t > &binaryContent)
Create the network from a protobuf binary.
BindingPointInfo GetNetworkOutputBindingInfo(const std::string &name) const
Retrieve binding info (layer id and tensor info) for the network output identified by the given layer...
static std::vector< std::string > GetInputs(ModelPtr &model)
Retrieve inputs names.
BindingPointInfo GetNetworkInputBindingInfo(const std::string &name) const
Retrieve binding info (layer id and tensor info) for the network input identified by the given layer ...
static ModelPtr LoadModelFromBinary(const std::vector< uint8_t > &binaryContent)
static ModelPtr LoadModelFromTextFile(const char *fileName)
static const std::string GetVersion()
Retrieve version in X.Y.Z form.
armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile)
Create the network from a protobuf text file on disk.
static ModelPtr LoadModelFromBinaryFile(const char *fileName)
static std::vector< std::string > GetOutputs(ModelPtr &model)
Retrieve outputs names.
armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile)
Create the network from a protobuf binary file on disk.
#define ONNX_PARSER_VERSION
ONNX_PARSER_VERSION: "X.Y.Z" where: X = Major version number Y = Minor version number Z = Patch versi...
Definition Version.hpp:25
const armnnSerializer::Pooling2dDescriptor * Pooling2dDescriptor
armnn::TensorInfo ToTensorInfo(TensorRawPtr tensorPtr)
Copyright (c) 2021 ARM Limited and Contributors.
ActivationFunction
Definition Types.hpp:87
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
int LayerBindingId
Type of identifiers for bindable layers (inputs, outputs).
Definition Types.hpp:311
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition INetwork.hpp:339
DataType
Definition Types.hpp:49
std::unique_ptr< onnx::ModelProto > ModelPtr
armnn::BindingPointInfo BindingPointInfo
std::pair< armnn::ConstTensor, std::unique_ptr< T[]> > CreateConstTensorImpl(const T *bufferPtr, armnn::TensorInfo &tensorInfo, const armnn::Optional< armnn::PermutationVector & > permutationVector)
std::unique_ptr< IOnnxParser, void(*)(IOnnxParser *parser)> IOnnxParserPtr
void ProcessConcatInputTensorInfo(armnn::TensorInfo &inputTensorInfo, armnn::OriginsDescriptor &concatDescriptor, const unsigned int &concatAxis, unsigned int inputIndex, unsigned int &mergeDimOrigin)
armnn::TensorShape Permuted(const armnn::TensorShape &srcShape, const armnn::PermutationVector &mappings)
Definition Permute.cpp:125
float m_A
Alpha upper bound value used by the activation functions. (BoundedReLu, Linear, TanH,...
float m_B
Beta lower bound value used by the activation functions. (BoundedReLu, Linear, TanH).
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu,...
float m_Eps
Value to add to the variance. Used to avoid dividing by zero.
std::string AsString() const
uint32_t m_PadRight
Padding right value in the width dimension.
uint32_t m_DilationY
Dilation along y axis.
uint32_t m_PadTop
Padding top value in the height dimension.
uint32_t m_DilationX
Dilation along x axis.
uint32_t m_PadBottom
Padding bottom value in the height dimension.
uint32_t m_PadLeft
Padding left value in the width dimension.
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
bool m_BiasEnabled
Enable/disable bias.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
uint32_t m_PadRight
Padding right value in the width dimension.
uint32_t m_PadTop
Padding top value in the height dimension.
uint32_t m_PadBottom
Padding bottom value in the height dimension.
uint32_t m_PadLeft
Padding left value in the width dimension.
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
bool m_BiasEnabled
Enable/disable bias.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
bool m_TransposeWeightMatrix
Enable/disable transpose weight matrix.
bool m_BiasEnabled
Enable/disable bias.
int32_t m_Axis
The axis in params to gather indices from.
uint32_t m_PadRight
Padding right value in the width dimension.
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
uint32_t m_PoolHeight
Pooling height value.
uint32_t m_PadTop
Padding top value in the height dimension.
uint32_t m_PoolWidth
Pooling width value.
PaddingMethod m_PaddingMethod
The padding method to be used. (Exclude, IgnoreValue).
uint32_t m_PadBottom
Padding bottom value in the height dimension.
uint32_t m_PadLeft
Padding left value in the width dimension.
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
OutputShapeRounding m_OutputShapeRounding
The rounding method for the output shape. (Floor, Ceiling).
TensorShape m_TargetShape
Target shape value.
PermutationVector m_DimMappings
Indicates how to translate tensor elements from a given source into the target destination,...