blob: 3fcb7ab60370140f0abfe4f30b966d64973273b1 [file] [log] [blame]
telsoa01c577f2c2018-08-31 09:22:23 +01001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
David Beckecb56cd2018-09-05 12:52:57 +01003// SPDX-License-Identifier: MIT
telsoa01c577f2c2018-08-31 09:22:23 +01004//
5#include "OnnxParser.hpp"
6
Matthew Sloyanac001ee2021-02-03 10:43:04 +00007#include "armnnOnnxParser/Version.hpp"
8
Matthew Bentham39ef3e52020-01-20 10:09:09 +00009#include <armnn/Descriptors.hpp>
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +010010#include <armnn/utility/Assert.hpp>
Matthew Sloyan589e3e82020-09-11 16:17:48 +010011#include <armnn/utility/NumericCast.hpp>
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +010012#include <ParserHelper.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010013#include <VerificationHelpers.hpp>
14
James Ward58dec6b2020-09-11 17:32:44 +010015#include <fmt/format.h>
Aron Virginas-Tard4f0fea2019-04-09 14:08:06 +010016
telsoa01c577f2c2018-08-31 09:22:23 +010017#include <google/protobuf/text_format.h>
18#include <google/protobuf/io/zero_copy_stream_impl.h>
19
Matthew Sloyanac001ee2021-02-03 10:43:04 +000020#include <iostream>
telsoa01c577f2c2018-08-31 09:22:23 +010021#include <numeric>
Jan Eilers53ef7952021-06-02 12:01:25 +010022#include <armnnUtils/Permute.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010023
24using namespace armnn;
25
26namespace armnnOnnxParser
27{
Kevin Mayef33cb12021-01-29 14:24:57 +000028
29IOnnxParser::IOnnxParser() : pOnnxParserImpl(new OnnxParserImpl()) {}
30
31IOnnxParser::~IOnnxParser() = default;
32
33IOnnxParser* IOnnxParser::CreateRaw()
34{
35 return new IOnnxParser();
36}
37
38IOnnxParserPtr IOnnxParser::Create()
39{
40 return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
41}
42
43void IOnnxParser::Destroy(IOnnxParser* parser)
44{
45 delete parser;
46}
47
48armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
49{
50 return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile);
51}
52
53armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile)
54{
55 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile);
56}
57
58armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText)
59{
60 return pOnnxParserImpl->CreateNetworkFromString(protoText);
61}
62
63BindingPointInfo IOnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
64{
65 return pOnnxParserImpl->GetNetworkInputBindingInfo(name);
66}
67
68BindingPointInfo IOnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
69{
70 return pOnnxParserImpl->GetNetworkOutputBindingInfo(name);
71}
72
telsoa01c577f2c2018-08-31 09:22:23 +010073namespace
74{
75void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
76 const onnx::TensorProto::DataType actualValue,
77 const char* validExpr,
78 std::string nodeName,
79 std::string tensorName,
80 const armnn::CheckLocation& location)
81{
82 bool isValid = std::any_of(validInputTypes.begin(),
83 validInputTypes.end(),
84 [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
85 if (!isValid)
86 {
87 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +010088 fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
89 onnx::TensorProto::DataType_Name(actualValue),
90 tensorName,
91 nodeName,
92 validExpr,
93 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +010094 }
95}
96
97#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
98CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
99
100using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
101#define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
102
103template <typename Callable>
104void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
105 const std::string& attribName,
106 onnx::AttributeProto::AttributeType expectedType,
107 Callable callable)
108{
109 auto attribs = node.attribute();
110 int attriNum = 0;
111 while (attriNum < node.attribute_size())
112 {
113 if (attribs.Get(attriNum).name() == attribName)
114 {
115 if (attribs.Get(attriNum).type() == expectedType)
116 {
117 callable(attribs.Get(attriNum));
118 }
119 else
120 {
James Ward58dec6b2020-09-11 17:32:44 +0100121 throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
122 "onnx::AttributeProto::AttributeType, but found {} instead {}",
123 attribName,
124 node.name(),
125 onnx::AttributeProto::AttributeType_Name(expectedType),
126 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
127 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100128 }
129 break;
130 }
131 ++attriNum;
132 }
133 if (attriNum == node.attribute_size())
134 {
James Ward58dec6b2020-09-11 17:32:44 +0100135 throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
136 attribName, node.name(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100137 }
138}
139
140template <typename Callable>
141void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
142 const std::string& attribName,
143 onnx::AttributeProto::AttributeType expectedType,
144 Callable callable)
145{
146 auto attribs = node.attribute();
147 for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
148 {
149 if (attribs.Get(attriNum).name() == attribName)
150 {
151 if (attribs.Get(attriNum).type() == expectedType)
152 {
153 callable(attribs.Get(attriNum));
154 }
155 else
156 {
James Ward58dec6b2020-09-11 17:32:44 +0100157 throw ParseException(
158 fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
159 "but found {} instead {}",
160 attribName,
161 node.name(),
162 onnx::AttributeProto::AttributeType_Name(expectedType),
163 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
164 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100165 }
166 }
167 }
168}
169
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +0100170int ReadMandatoryNodeIntAttribute(const onnx::NodeProto& node,
171 const std::string& name)
172{
173 int attribValue = 0;
174 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
175 [&attribValue](const onnx::AttributeProto& attrValue)
176 {
177 attribValue = CHECKED_INT32(attrValue.i());
178 });
179 return attribValue;
180}
181
Ryan OSheaed27ee72020-04-22 16:37:29 +0100182int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
183 const std::string& name,
184 const int64_t defaultValue = 0)
185{
186 int64_t attribValue = defaultValue;
187 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
188 [&attribValue](const onnx::AttributeProto& attrValue)
189 {
190 attribValue = attrValue.i();
191 });
192 return attribValue;
193}
194
telsoa01c577f2c2018-08-31 09:22:23 +0100195std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
196 const std::string& name)
197{
198 std::vector<uint32_t> attriList;
199 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
200 [&attriList](const onnx::AttributeProto& attrValue)
201 {
202 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
203 {
204 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
205 }
206 });
207 return attriList;
208}
209
210uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
211 const std::string& name,
212 const uint32_t defaultVal = 0u)
213{
214 uint32_t attribValue = defaultVal;
215 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
216 [&attribValue](const onnx::AttributeProto& attrValue)
217 {
218 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
219 });
220 return attribValue;
221}
222
223std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
224 const std::string& name)
225{
226 std::vector<uint32_t> attriList;
227 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
228 [&attriList](const onnx::AttributeProto& attrValue)
229 {
230 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
231 {
232 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
233 }
234 });
235
236 return attriList;
237}
238
239float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
240 const std::string& name,
241 const float defaultValue = 0.0f)
242{
243 float attribValue = defaultValue;
244 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
245 [&attribValue](const onnx::AttributeProto& attrValue)
246 {
247 attribValue = attrValue.f();
248 });
249 return attribValue;
250}
251
252std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
253{
254 std::string attribValue = "";
255 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
256 [&attribValue](const onnx::AttributeProto& attrValue)
257 {
258 attribValue = attrValue.s();
259 });
260 return attribValue;
261}
262
Tee Jungfcf6fd52019-11-01 05:27:28 +0000263armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100264{
telsoa01c577f2c2018-08-31 09:22:23 +0100265 DataType type;
Tee Jungfcf6fd52019-11-01 05:27:28 +0000266 switch(data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100267 {
268 case onnx::TensorProto::FLOAT:
269 {
270 type = DataType::Float32;
271 break;
272 }
273 case onnx::TensorProto::INT32:
274 case onnx::TensorProto::INT64:
275 {
276 type = DataType::Signed32;
277 break;
278 }
279 default:
280 {
281 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100282 fmt::format("'{}' is not a currently supported datatype for tensor {}."
283 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
284 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
285 name,
286 CHECK_LOCATION().AsString() ));
telsoa01c577f2c2018-08-31 09:22:23 +0100287 }
telsoa01c577f2c2018-08-31 09:22:23 +0100288 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000289
290 // To avoid crashes by trivial tensors
291 if (shape.empty())
292 {
293 return TensorInfo(TensorShape(), type);
294 }
295
Tee Jungfcf6fd52019-11-01 05:27:28 +0000296 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
297}
298
299armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
300{
301 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
302 std::vector<unsigned int> shapeDims;
303 for (int i = 0; i < onnxShape.dim_size(); ++i)
304 {
305 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
306 }
307
Ryan OShea337c17f2020-02-21 12:33:17 +0000308 if (shapeDims.empty())
309 {
310 shapeDims.push_back(1);
311 }
312
Tee Jungfcf6fd52019-11-01 05:27:28 +0000313 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
314}
315
316armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
317{
318 std::vector<unsigned int> shapeDims;
Ryan OShea337c17f2020-02-21 12:33:17 +0000319
Tee Jungfcf6fd52019-11-01 05:27:28 +0000320 for (auto dim: tensor.dims())
321 {
322 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
323 }
324
Ryan OShea337c17f2020-02-21 12:33:17 +0000325 if (shapeDims.empty())
326 {
327 shapeDims.push_back(1);
328 }
329
Tee Jungfcf6fd52019-11-01 05:27:28 +0000330 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100331}
332
333std::string TensorInfoAsString(const TensorInfo& info,
334 const std::string& name,
335 const onnx::TensorProto::DataType& type)
336{
337 const TensorShape shape = info.GetShape();
338 std::stringstream ss;
339 ss << "tensor '" << name << "' contains "
340 << onnx::TensorProto::DataType_Name(type)
341 << " and has shape [";
342
343 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
344 {
345 ss << shape[i] << ", ";
346 }
347 ss << shape[shape.GetNumDimensions() - 1] << "]";
348 return ss.str();
349}
350
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000351void CalcPadding(uint32_t inputSize,
352 uint32_t filterSize,
353 uint32_t stride,
354 uint32_t dilation,
355 uint32_t* paddingFront,
356 uint32_t* paddingBack,
357 bool isUpper)
telsoa01c577f2c2018-08-31 09:22:23 +0100358{
359 uint32_t outputSize = (inputSize + stride - 1) / stride;
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000360 uint32_t dilatedSize = filterSize + (dilation - 1) * (filterSize - 1);
361 uint32_t temp = (outputSize - 1) * stride + dilatedSize;
telsoa01c577f2c2018-08-31 09:22:23 +0100362 *paddingFront = (temp - inputSize) / 2;
363 *paddingBack = *paddingFront;
364 if((temp - inputSize) % 2 == 1)
365 {
366 if (isUpper)
367 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000368 *paddingBack += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100369 }
370 else
371 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000372 *paddingFront += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100373 }
374 }
375}
376
Ryan OSheaed27ee72020-04-22 16:37:29 +0100377TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
telsoa01c577f2c2018-08-31 09:22:23 +0100378 const TensorShape& inShape,
379 const std::string& outName)
380{
381 std::vector<int> targetDims;
Ryan OSheaed27ee72020-04-22 16:37:29 +0100382 for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
telsoa01c577f2c2018-08-31 09:22:23 +0100383 {
Ryan OSheaed27ee72020-04-22 16:37:29 +0100384 int val = CHECKED_INT32(targetShapeTensor[i]);
telsoa01c577f2c2018-08-31 09:22:23 +0100385 if(val == 0)
386 {
387 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
388 }
389 else
390 {
391 targetDims.push_back(val);
392 }
393 }
394
395 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
396 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
397 if (stretchDim != targetDims.end())
398 {
399 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
400 {
401 std::stringstream ss;
402 ss << "[ ";
403 for(uint i = 0; i < targetDims.size() - 1; ++i)
404 {
405 ss << targetDims[i] << ", ";
406 }
407 ss << targetDims[targetDims.size() - 1] << " ]";
408
James Ward58dec6b2020-09-11 17:32:44 +0100409 throw ParseException(
410 fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
411 " -1 and here, shape is {} {}",
412 outName,
413 ss.str(),
414 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100415 }
416
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100417 auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
telsoa01c577f2c2018-08-31 09:22:23 +0100418 -1, std::multiplies<int32_t>()));
419 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
420 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
421 }
422 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
423 return TensorInfo(outShape, DataType::Float32);
424}
425
426} //namespace
427
Kevin Mayef33cb12021-01-29 14:24:57 +0000428const std::map<std::string, OnnxParserImpl::OperationParsingFunction> OnnxParserImpl::m_ParserFunctions = {
429 { "BatchNormalization", &OnnxParserImpl::ParseBatchNormalization},
430 { "GlobalAveragePool", &OnnxParserImpl::ParseGlobalAveragePool},
431 { "AveragePool", &OnnxParserImpl::ParseAveragePool },
432 { "Clip", &OnnxParserImpl::ParseClip },
433 { "Constant", &OnnxParserImpl::ParseConstant },
434 { "MaxPool", &OnnxParserImpl::ParseMaxPool },
435 { "Reshape", &OnnxParserImpl::ParseReshape },
436 { "Sigmoid", &OnnxParserImpl::ParseSigmoid },
437 { "Tanh", &OnnxParserImpl::ParseTanh },
438 { "Relu", &OnnxParserImpl::ParseRelu },
439 { "LeakyRelu", &OnnxParserImpl::ParseLeakyRelu },
440 { "Conv", &OnnxParserImpl::ParseConv },
441 { "Add", &OnnxParserImpl::ParseAdd },
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +0100442 { "Flatten", &OnnxParserImpl::ParseFlatten },
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100443 { "Shape", &OnnxParserImpl::ParseShape },
444 { "Gather", &OnnxParserImpl::ParseGather },
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +0100445 { "Unsqueeze", &OnnxParserImpl::ParseUnsqueeze },
446 { "Concat", &OnnxParserImpl::ParseConcat }
telsoa01c577f2c2018-08-31 09:22:23 +0100447};
448
449template<typename TypePair, typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000450void OnnxParserImpl::ValidateInputs(const onnx::NodeProto& node,
telsoa01c577f2c2018-08-31 09:22:23 +0100451 TypePair validInputs,
452 const Location& location)
453{
454 for(auto input : node.input())
455 {
456 CheckValidDataType(validInputs.second,
457 m_TensorsInfo[input].m_dtype,
458 validInputs.first,
459 node.name(),
460 input,
461 location);
462 }
463}
464
465#define VALID_INPUTS(NODE, VALID_INPUTS) \
Kevin Mayef33cb12021-01-29 14:24:57 +0000466 OnnxParserImpl::ValidateInputs(NODE, \
telsoa01c577f2c2018-08-31 09:22:23 +0100467 VALID_INPUTS, \
468 CHECK_LOCATION())
469
Kevin Mayef33cb12021-01-29 14:24:57 +0000470std::vector<TensorInfo> OnnxParserImpl::ComputeOutputInfo(std::vector<std::string> outNames,
471 const IConnectableLayer* layer,
472 std::vector<TensorShape> inputShapes)
telsoa01c577f2c2018-08-31 09:22:23 +0100473{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100474 ARMNN_ASSERT(! outNames.empty());
telsoa01c577f2c2018-08-31 09:22:23 +0100475 bool needCompute = std::any_of(outNames.begin(),
476 outNames.end(),
477 [this](std::string name)
478 {
479 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr);
480 });
481 std::vector<TensorInfo> outInfo;
482 //if the output info(s) are not here, we need to compute them
483 std::vector<TensorShape> inferredShapes;
484 if(needCompute)
485 {
486 inferredShapes = layer->InferOutputShapes(inputShapes);
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100487 ARMNN_ASSERT(inferredShapes.size() == outNames.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100488 }
489 for (uint i = 0; i < outNames.size(); ++i)
490 {
491 if(needCompute)
492 {
493 m_TensorsInfo[outNames[i]] = OnnxTensor();
494 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
495 TensorInfo(inferredShapes[i], DataType::Float32));
496 }
497 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
498 }
499 return outInfo;
500}
501
Kevin Mayef33cb12021-01-29 14:24:57 +0000502OnnxParserImpl::OnnxParserImpl()
telsoa01c577f2c2018-08-31 09:22:23 +0100503 : m_Network(nullptr, nullptr)
504{
505}
506
Kevin Mayef33cb12021-01-29 14:24:57 +0000507void OnnxParserImpl::ResetParser()
telsoa01c577f2c2018-08-31 09:22:23 +0100508{
509 m_Network = armnn::INetworkPtr(nullptr, nullptr);
510 m_Graph = nullptr;
511}
512
Kevin Mayef33cb12021-01-29 14:24:57 +0000513void OnnxParserImpl::Cleanup()
telsoa01c577f2c2018-08-31 09:22:23 +0100514{
515 m_TensorConnections.clear();
516 m_TensorsInfo.clear();
517 m_OutputsMap.clear();
518 m_OutputsFusedAndUsed.clear();
519}
520
Jan Eilers53ef7952021-06-02 12:01:25 +0100521template<typename T>
522std::pair<armnn::ConstTensor, std::unique_ptr<T[]>>
523CreateConstTensorImpl(const T* bufferPtr,
524 armnn::TensorInfo& tensorInfo,
525 const armnn::Optional<armnn::PermutationVector&> permutationVector)
telsoa01c577f2c2018-08-31 09:22:23 +0100526{
Jan Eilers53ef7952021-06-02 12:01:25 +0100527 ARMNN_ASSERT_MSG(bufferPtr != nullptr, fmt::format("Buffer for permutation is null").c_str());
528
529 std::unique_ptr<T[]> data(new T[tensorInfo.GetNumElements()]);
530
531 if (permutationVector.has_value() && permutationVector.value().GetSize() > 0)
532 {
533 tensorInfo = armnnUtils::Permuted(tensorInfo, permutationVector.value());
534 armnnUtils::Permute(tensorInfo.GetShape(), permutationVector.value(),
535 reinterpret_cast<const T*>(bufferPtr), data.get(), sizeof(T));
536 }
537 else
538 {
539 ::memcpy(data.get(), bufferPtr, tensorInfo.GetNumBytes());
540 }
541
542 return std::make_pair(ConstTensor(tensorInfo, data.get()), std::move(data));
543}
544
545std::pair<ConstTensor, std::unique_ptr<float[]>>
546OnnxParserImpl::CreateConstTensor(const std::string name,
547 armnn::Optional<armnn::PermutationVector&> permutationVector)
548{
549 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
telsoa01c577f2c2018-08-31 09:22:23 +0100550 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
551
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100552 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
553 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
554 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
555
Matthew Sloyan81beae32021-07-13 19:46:11 +0100556 // Makes sure IsConstant flag is set.
557 tensorInfo.SetConstant();
558
Jan Eilers53ef7952021-06-02 12:01:25 +0100559 // Const tensors requires at least a list of values
560 if (tensorInfo.GetNumElements() == 0)
561 {
562 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
563 name,
564 CHECK_LOCATION().AsString()));
565 }
566
telsoa01c577f2c2018-08-31 09:22:23 +0100567 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100568 // Copy the value list entries into the destination
569 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100570 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100571 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
572 {
James Ward58dec6b2020-09-11 17:32:44 +0100573 throw ParseException(
574 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
575 "elements ({}) {}",
576 onnxTensor.float_data_size(),
577 name,
578 tensorInfo.GetNumElements(),
579 CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100580 }
Jan Eilers53ef7952021-06-02 12:01:25 +0100581 return CreateConstTensorImpl<float>(srcData, tensorInfo, permutationVector);
telsoa01c577f2c2018-08-31 09:22:23 +0100582 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100583 else
584 {
Jan Eilers53ef7952021-06-02 12:01:25 +0100585 return CreateConstTensorImpl<float>(reinterpret_cast<const float*>(onnxTensor.raw_data().c_str()),
586 tensorInfo,
587 permutationVector);
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100588 }
telsoa01c577f2c2018-08-31 09:22:23 +0100589}
590
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +0100591std::pair<ConstTensor, std::unique_ptr<int32_t[]>>
592OnnxParserImpl::CreateInt64ConstTensor(const std::string name,
593 armnn::Optional<armnn::PermutationVector&> permutationVector)
594{
595 TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
596 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
597
598 CHECK_VALID_DATATYPE(name, onnxTensor.name(),
599 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::INT64);
600
601 // Makes sure IsConstant flag is set.
602 tensorInfo.SetConstant();
603 uint numElements = tensorInfo.GetNumElements();
604
605 // Const tensors requires at least a list of values
606 if (numElements == 0)
607 {
608 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
609 name,
610 CHECK_LOCATION().AsString()));
611 }
612
613 // Copy the value list entries into the destination
614 if (!onnxTensor.has_raw_data())
615 {
616 auto srcData = onnxTensor.int64_data().data();
617 if(numElements != static_cast<uint>(onnxTensor.int64_data_size()))
618 {
619 throw ParseException(
620 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
621 "elements ({}) {}",
622 onnxTensor.int64_data_size(),
623 name,
624 tensorInfo.GetNumElements(),
625 CHECK_LOCATION().AsString()));
626 }
627
628 std::vector<int32_t> int32Data;
629 for(uint i = 0; i < numElements; i++)
630 {
631 int32_t int32Value = CHECKED_INT32(srcData[i]);
632 int32Data.push_back(int32Value);
633 }
634
635 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
636 }
637 else
638 {
639 auto srcData = reinterpret_cast<const int64_t*>(onnxTensor.raw_data().c_str());
640 std::vector<int32_t> int32Data;
641 for(uint i = 0; i < numElements; i++)
642 {
643 int32_t int32Value = CHECKED_INT32(srcData[i]);
644 int32Data.push_back(int32Value);
645 }
646 return CreateConstTensorImpl<int32_t>(int32Data.data(), tensorInfo, permutationVector);
647 }
648}
649
Kevin Mayef33cb12021-01-29 14:24:57 +0000650ModelPtr OnnxParserImpl::LoadModelFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100651{
652 FILE* fd = fopen(graphFile, "r");
653
654 if (fd == nullptr)
655 {
James Ward58dec6b2020-09-11 17:32:44 +0100656 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100657 }
658
659 // Parse the file into a message
660 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
661 using google::protobuf::io::FileInputStream;
662 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
663 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
664 fclose(fd);
665
666 if (!success)
667 {
668 std::stringstream error;
669 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100670 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100671 }
672 return modelProto;
673}
674
Kevin Mayef33cb12021-01-29 14:24:57 +0000675INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100676{
677 ResetParser();
678 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
679 return CreateNetworkFromModel(*modelProto);
680}
681
682
Kevin Mayef33cb12021-01-29 14:24:57 +0000683ModelPtr OnnxParserImpl::LoadModelFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100684{
685 FILE* fd = fopen(graphFile, "rb");
686
687 if (fd == nullptr)
688 {
James Ward58dec6b2020-09-11 17:32:44 +0100689 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100690 }
691
692 // Parse the file into a message
693 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
694
695 google::protobuf::io::FileInputStream inStream(fileno(fd));
696 google::protobuf::io::CodedInputStream codedStream(&inStream);
Nikhil Raje5181532020-10-09 14:52:25 +0100697 codedStream.SetTotalBytesLimit(INT_MAX);
telsoa01c577f2c2018-08-31 09:22:23 +0100698 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
699 fclose(fd);
700
701 if (!success)
702 {
703 std::stringstream error;
704 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100705 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100706 }
707 return modelProto;
708
709}
710
Kevin Mayef33cb12021-01-29 14:24:57 +0000711INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100712{
713 ResetParser();
714 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
715 return CreateNetworkFromModel(*modelProto);
716}
717
Kevin Mayef33cb12021-01-29 14:24:57 +0000718ModelPtr OnnxParserImpl::LoadModelFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100719{
720 if (protoText == "")
721 {
James Ward58dec6b2020-09-11 17:32:44 +0100722 throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
723 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100724 }
725 // Parse the string into a message
726 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
727 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
728 if (!success)
729 {
730 std::stringstream error;
731 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100732 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100733 }
734 return modelProto;
735}
736
Kevin Mayef33cb12021-01-29 14:24:57 +0000737INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100738{
739 ResetParser();
740 ModelPtr modelProto = LoadModelFromString(protoText);
741 return CreateNetworkFromModel(*modelProto);
742}
743
Kevin Mayef33cb12021-01-29 14:24:57 +0000744INetworkPtr OnnxParserImpl::CreateNetworkFromModel(onnx::ModelProto& model)
telsoa01c577f2c2018-08-31 09:22:23 +0100745{
746 m_Network = INetwork::Create();
747 try
748 {
749 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
750 LoadGraph();
751 }
752 catch (const ParseException& e)
753 {
754 Cleanup();
755 throw e;
756 }
757 Cleanup();
758 return std::move(m_Network);
759}
760
Kevin Mayef33cb12021-01-29 14:24:57 +0000761void OnnxParserImpl::LoadGraph()
telsoa01c577f2c2018-08-31 09:22:23 +0100762{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100763 ARMNN_ASSERT(m_Graph.get() != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100764
765 //Fill m_TensorsInfo with the shapes and value of every tensor
766 SetupInfo(m_Graph->mutable_output());
767 SetupInfo(m_Graph->mutable_input());
768 SetupInfo(m_Graph->mutable_value_info());
769
770 for (auto tensor : m_Graph->initializer())
771 {
772 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000773 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
774 m_TensorsInfo[tensor.name()].m_dtype =
775 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100776 }
777
778 SetupInputLayers();
779 SetupOutputLayers();
780
781 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
782 DetectFullyConnected();
783
784 //Parsing the graph
785 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
786 {
787 auto node = m_Graph->node(static_cast<int>(nodeIndex));
788 const std::string& operation = node.op_type();
789
790 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000791 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100792 {
793 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
794 {
795 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
796 AddFullyConnected(node);
797 }
798 }
799 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
800 {
801 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
802 AddFullyConnected(m_Graph->node(matmulIndex), &node);
803 }
804 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
805 {
806 auto it = m_ParserFunctions.find(operation);
807 if (it != m_ParserFunctions.end())
808 {
809 auto func = it->second;
810 (this->*func)(node);
811 }
812 else
813 {
James Ward58dec6b2020-09-11 17:32:44 +0100814 throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
815 operation,
816 node.name(),
817 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100818 }
819 }
820 }
821
822 //Making the connections between outputs and inputs of each layers
823 for (const auto& tensorCon : m_TensorConnections)
824 {
825 if (tensorCon.second.outputSlot != nullptr)
826 {
827 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
828 {
829 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
830 }
831 }
832 }
833}
834
Kevin Mayef33cb12021-01-29 14:24:57 +0000835void OnnxParserImpl::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
telsoa01c577f2c2018-08-31 09:22:23 +0100836{
837 for (auto tensor : *list)
838 {
839 m_TensorsInfo[tensor.name()] = OnnxTensor();
840 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000841 m_TensorsInfo[tensor.name()].m_dtype =
842 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100843 }
844}
845
Kevin Mayef33cb12021-01-29 14:24:57 +0000846void OnnxParserImpl::DetectFullyConnected()
telsoa01c577f2c2018-08-31 09:22:23 +0100847{
848 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
849 auto matmulAndConstant = [&](const std::string& constInput,
850 const std::string& matmulInput,
851 int& nodeIndex)
852 {
853 auto matmulIt = m_OutputsMap.find(matmulInput);
854 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
855 && m_TensorsInfo[constInput].isConstant())
856 {
857 nodeIndex = matmulIt->second.second;
858 return true;
859 }
860 return false;
861 };
862
863 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
864 {
865 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
866 for (const std::string& output : node->output())
867 {
868 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
869 }
870
871 for (const std::string& input : node->input()) //count how many time a node is used as input
872 {
873 auto matmulIt = m_OutputsMap.find(input);
874 if(matmulIt != m_OutputsMap.end()){
875 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
876 }
877 }
878
879 if (node->op_type() == "Add")
880 {
881 int matmulIndex = 0;
882 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
883 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
884 {
885 //matmul and add were fused
886 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
887 .push_back(static_cast<size_t>(nodeIndex));
888
889 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
890 .push_back(static_cast<size_t>(matmulIndex));
891 }
892 }
893 }
894
895 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
896 auto matmulIt = m_OutputsMap.find(output.name());
897 if(matmulIt != m_OutputsMap.end()){
898 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
899 }
900 }
901}
902
903template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000904void OnnxParserImpl::GetInputAndParam(const onnx::NodeProto& node,
905 std::string* inputName,
906 std::string* constName,
907 const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +0100908{
909 int cstIndex;
910 if (m_TensorsInfo[node.input(0)].isConstant())
911 {
912 cstIndex = 0;
913 }
914 else if (m_TensorsInfo[node.input(1)].isConstant())
915 {
916 cstIndex = 1;
917 }
918 else
919 {
James Ward58dec6b2020-09-11 17:32:44 +0100920 throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
921 node.input(0),
922 node.input(1),
923 node.name(),
924 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100925 }
926 if(constName)
927 {
928 *constName = node.input(cstIndex);
929 }
930 if(inputName)
931 {
932 *inputName = node.input(!cstIndex);
933 }
934}
935
936template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000937void OnnxParserImpl::To1DTensor(const std::string& name, const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +0100938{
939 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
940 std::vector<uint32_t> newShape;
941 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
942 {
943 if(shape[i] != 1)
944 {
James Ward58dec6b2020-09-11 17:32:44 +0100945 throw ParseException(
946 fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
947 TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
948 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100949 }
950 }
951 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
952
953 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
954}
955
Kevin Mayef33cb12021-01-29 14:24:57 +0000956void OnnxParserImpl::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
Ryan OSheaed27ee72020-04-22 16:37:29 +0100957{
958 ARMNN_ASSERT(node.op_type() == "Conv");
959
960 DepthwiseConvolution2dDescriptor desc;
961 desc.m_PadLeft = convDesc.m_PadLeft;
962 desc.m_PadRight = convDesc.m_PadRight;
963 desc.m_PadTop = convDesc.m_PadTop;
964 desc.m_PadBottom = convDesc.m_PadBottom;
965 desc.m_StrideX = convDesc.m_StrideX;
966 desc.m_StrideY = convDesc.m_StrideY;
967 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
968
969 armnn::IConnectableLayer* layer;
Jan Eilers53ef7952021-06-02 12:01:25 +0100970
971 // weights come in as [O,1,H,W] from ONNX and need to be converted to ArmNNs dephtwise weights layout [1,H,W,O]
972 armnn::PermutationVector perVec {3,0,1,2};
973 auto weightTensor = CreateConstTensor(node.input(1), perVec);
Ryan OSheaed27ee72020-04-22 16:37:29 +0100974
975 if (node.input_size() == 3)
976 {
977 if(!m_TensorsInfo[node.input(2)].isConstant())
978 {
James Ward58dec6b2020-09-11 17:32:44 +0100979 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
980 node.input(2),
981 node.name(),
982 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +0100983 }
984 desc.m_BiasEnabled = true;
985 auto biasTensor = CreateConstTensor(node.input(2));
986 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
987 weightTensor.first,
988 Optional<ConstTensor>(biasTensor.first),
989 node.name().c_str());
990 }
991 else
992 {
993 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
994 weightTensor.first,
995 EmptyOptional(),
996 node.name().c_str());
997 }
998 ARMNN_ASSERT(layer != nullptr);
999
1000 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1001 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
Jan Eilers53ef7952021-06-02 12:01:25 +01001002 weightTensor.first.GetInfo().GetShape() });
Ryan OSheaed27ee72020-04-22 16:37:29 +01001003
1004 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1005
1006 // register the input connection slots for the layer, connections are made after all layers have been created
1007 // only the tensors for the inputs are relevant, exclude the const tensors
1008 RegisterInputSlots(layer, {node.input(0)});
1009
1010 // register the output connection slots for the layer, connections are made after all layers have been created
1011 RegisterOutputSlots(layer, {node.output(0)});
1012}
1013
Kevin Mayef33cb12021-01-29 14:24:57 +00001014void OnnxParserImpl::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
telsoa01c577f2c2018-08-31 09:22:23 +01001015{
1016
1017 // find matmul inputs
1018 std::string weightName;
1019 std::string inputName;
1020 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
1021 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
1022 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
1023
1024 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
1025
1026 FullyConnectedDescriptor desc;
1027 desc.m_BiasEnabled = addNode != nullptr;
1028
1029 IConnectableLayer* layer = nullptr;
1030 if(desc.m_BiasEnabled)
1031 {
1032 // find bias const
1033 std::string biasName;
1034 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
1035 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
1036 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
1037
1038 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
1039
1040 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
1041 To1DTensor(biasName, CHECK_LOCATION());
1042 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1043 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
1044
1045 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
1046 {
James Ward58dec6b2020-09-11 17:32:44 +01001047 throw ParseException(
1048 fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
1049 " and {} ( /!\\ bias should be a 1D tensor) {}",
1050 weightName,
1051 addNode->name(),
1052 TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
1053 m_TensorsInfo[weightName].m_dtype),
1054 TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
1055 m_TensorsInfo[biasName].m_dtype ),
1056 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001057 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01001058
1059 // Just add a FullyConnected layer, weights and biases are handled as inputs now.
1060 layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001061 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001062
1063 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
1064 {m_TensorsInfo[inputName].m_info->GetShape(),
1065 m_TensorsInfo[weightName].m_info->GetShape()});
telsoa01c577f2c2018-08-31 09:22:23 +01001066 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1067
Matthew Sloyan81beae32021-07-13 19:46:11 +01001068 // Add constant layer to store weights/biases and connect to FullyConnected layer..
1069 if(m_TensorsInfo[weightName].isConstant())
1070 {
1071 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1072
1073 weightInfo.SetConstant();
1074 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1075 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1076 }
1077
1078 if(m_TensorsInfo[biasName].isConstant())
1079 {
1080 IConnectableLayer* biasLayer = m_Network->AddConstantLayer(CreateConstTensor(biasName).first);
1081
1082 biasInfo.SetConstant();
1083 biasLayer->GetOutputSlot(0).SetTensorInfo(biasInfo);
1084 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
1085 }
1086
1087 RegisterInputSlots(layer, {inputName, weightName, biasName});
telsoa01c577f2c2018-08-31 09:22:23 +01001088 RegisterOutputSlots(layer, {addNode->output(0)});
1089 }
1090 else
1091 {
Matthew Sloyan81beae32021-07-13 19:46:11 +01001092 layer = m_Network->AddFullyConnectedLayer(desc, matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001093 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001094
1095 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
1096 {m_TensorsInfo[inputName].m_info->GetShape(),
1097 m_TensorsInfo[weightName].m_info->GetShape()});
1098 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1099
Matthew Sloyan81beae32021-07-13 19:46:11 +01001100 // Add constant layer to store weights and connect to FullyConnected layer.
1101 if(m_TensorsInfo[weightName].isConstant())
1102 {
1103 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
1104 IConnectableLayer* weightsLayer = m_Network->AddConstantLayer(CreateConstTensor(weightName).first);
1105
1106 weightInfo.SetConstant();
1107 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
1108 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightInfo);
1109 }
1110
1111 RegisterInputSlots(layer, {inputName, weightName});
telsoa01c577f2c2018-08-31 09:22:23 +01001112 RegisterOutputSlots(layer, {matmulNode.output(0)});
1113 }
1114}
1115
Kevin Mayef33cb12021-01-29 14:24:57 +00001116void OnnxParserImpl::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
telsoa01c577f2c2018-08-31 09:22:23 +01001117{
1118
1119 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1120 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1121
1122 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1123
1124 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
1125 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1126 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1127
1128 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
1129 desc.m_PoolWidth = kernel_shape[1];
1130 desc.m_PoolHeight = kernel_shape[0];
1131
1132 if(strides.empty())
1133 {
1134 desc.m_StrideX = 1;
1135 desc.m_StrideY = 1;
1136 }
1137 else
1138 {
1139 desc.m_StrideX = strides[1];
1140 desc.m_StrideY = strides[0];
1141 }
1142
1143 //Check new padding version first
1144 if(pads.empty())
1145 {
1146 //Check deprecated version
1147 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1148 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1149 {
1150 bool isUpper;
1151 if( paddingString == "SAME_LOWER")
1152 {
1153 isUpper = false;
1154 }
1155 else if (paddingString == "SAME_UPPER")
1156 {
1157 isUpper = true;
1158 }
1159 else
1160 {
James Ward58dec6b2020-09-11 17:32:44 +01001161 throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
1162 "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
1163 node.name(),
1164 paddingString,
1165 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001166 }
1167 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1168 uint32_t inputHeight = inputInfo.GetShape()[2];
1169 uint32_t inputWidth = inputInfo.GetShape()[3];
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001170 CalcPadding(inputHeight,
1171 desc.m_PoolHeight,
1172 desc.m_StrideY,
1173 1u,
1174 &desc.m_PadTop,
1175 &desc.m_PadBottom,
1176 isUpper);
1177 CalcPadding(inputWidth,
1178 desc.m_PoolWidth,
1179 desc.m_StrideX,
1180 1u,
1181 &desc.m_PadLeft,
1182 &desc.m_PadRight,
1183 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001184 }
1185 }
1186 else
1187 {
1188 desc.m_PadTop = pads[0];
1189 desc.m_PadLeft = pads[1];
1190 desc.m_PadBottom = pads[2];
1191 desc.m_PadRight = pads[3];
1192 }
1193
1194 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001195 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001196
1197 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1198 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1199
1200 // register the input connection slots for the layer, connections are made after all layers have been created
1201 // only the tensors for the inputs are relevant, exclude the const tensors
1202 RegisterInputSlots(layer, {node.input(0)});
1203
1204 // register the output connection slots for the layer, connections are made after all layers have been created
1205 RegisterOutputSlots(layer, {node.output(0)});
1206}
1207
Kevin Mayef33cb12021-01-29 14:24:57 +00001208std::pair<std::string, std::string> OnnxParserImpl::AddPrepareBroadcast(const std::string& input0,
1209 const std::string& input1)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001210{
1211 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1212
1213 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1214 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1215
1216 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1217 {
James Ward58dec6b2020-09-11 17:32:44 +01001218 auto outputName = fmt::format("reshape_output_{}", input1);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001219 PrependForBroadcast(outputName, input1, input0);
1220 inputs.second = outputName;
1221 }
1222 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1223 {
James Ward58dec6b2020-09-11 17:32:44 +01001224 auto outputName = fmt::format("reshape_output_{}", input0);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001225 PrependForBroadcast(outputName, input0, input1);
1226 inputs.first = outputName;
1227 }
1228 return inputs;
1229}
1230
Kevin Mayef33cb12021-01-29 14:24:57 +00001231void OnnxParserImpl::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001232{
1233 auto armnnTensor = CreateConstTensor(tensorName);
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001234 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1235 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1236 RegisterOutputSlots(layer, {tensorName});
1237}
Ryan OSheaed27ee72020-04-22 16:37:29 +01001238
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001239void OnnxParserImpl::CreateInt64ConstantLayer(const std::string& tensorName, const std::string& layerName)
1240{
1241 auto armnnTensor = CreateInt64ConstTensor(tensorName);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001242 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1243 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1244 RegisterOutputSlots(layer, {tensorName});
1245}
1246
Kevin Mayef33cb12021-01-29 14:24:57 +00001247void OnnxParserImpl::CreateReshapeLayer(const std::string& inputName,
1248 const std::string& outputName,
1249 const std::string& layerName)
telsoa01c577f2c2018-08-31 09:22:23 +01001250{
1251 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1252 ReshapeDescriptor reshapeDesc;
1253 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1254
1255 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001256 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001257 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1258
1259 // register the input connection slots for the layer, connections are made after all layers have been created
1260 // only the tensors for the inputs are relevant, exclude the const tensors
1261 RegisterInputSlots(layer, {inputName});
1262
1263 // register the output connection slots for the layer, connections are made after all layers have been created
1264 RegisterOutputSlots(layer, {outputName});
1265}
1266
Kevin Mayef33cb12021-01-29 14:24:57 +00001267void OnnxParserImpl::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001268{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001269 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001270 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1271
1272 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1273
1274 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001275 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001276
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001277 if (func == ActivationFunction::BoundedReLu)
1278 {
Narumol Prangnawaratf106ab72021-09-15 17:30:37 +01001279 if (node.input_size() == 1 && node.attribute_size() > 0)
1280 {
1281 desc.m_A = ReadOptionalNodeFloatAttribute(node, "max", std::numeric_limits<float>::max());
1282 desc.m_B = ReadOptionalNodeFloatAttribute(node, "min", std::numeric_limits<float>::lowest());
1283 }
1284 else
1285 {
1286 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1287 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1288 }
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001289 }
1290
telsoa01c577f2c2018-08-31 09:22:23 +01001291 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001292 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001293
1294 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1295 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1296
1297 // register the input connection slots for the layer, connections are made after all layers have been created
1298 // only the tensors for the inputs are relevant, exclude the const tensors
1299 RegisterInputSlots(layer, {node.input(0)});
1300
1301 // register the output connection slots for the layer, connections are made after all layers have been created
1302 RegisterOutputSlots(layer, {node.output(0)});
1303}
1304
Kevin Mayef33cb12021-01-29 14:24:57 +00001305void OnnxParserImpl::ParseClip(const onnx::NodeProto& node)
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001306{
1307 ParseActivation(node, ActivationFunction::BoundedReLu);
1308}
1309
Kevin Mayef33cb12021-01-29 14:24:57 +00001310void OnnxParserImpl::ParseSigmoid(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001311{
1312 ParseActivation(node, ActivationFunction::Sigmoid);
1313}
1314
Kevin Mayef33cb12021-01-29 14:24:57 +00001315void OnnxParserImpl::ParseTanh(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001316{
1317 ParseActivation(node, ActivationFunction::TanH);
1318}
1319
Kevin Mayef33cb12021-01-29 14:24:57 +00001320void OnnxParserImpl::ParseRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001321{
1322 ParseActivation(node, ActivationFunction::ReLu);
1323}
1324
Kevin Mayef33cb12021-01-29 14:24:57 +00001325void OnnxParserImpl::ParseLeakyRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001326{
1327 ParseActivation(node, ActivationFunction::LeakyReLu);
1328}
telsoa01c577f2c2018-08-31 09:22:23 +01001329
Kevin Mayef33cb12021-01-29 14:24:57 +00001330void OnnxParserImpl::ParseAdd(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001331{
Ryan OSheaed27ee72020-04-22 16:37:29 +01001332 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1333 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +01001334
Ryan OSheaed27ee72020-04-22 16:37:29 +01001335 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
telsoa01c577f2c2018-08-31 09:22:23 +01001336
Ryan OSheaed27ee72020-04-22 16:37:29 +01001337 // TODO: unify broadcast validation code across layers
1338 // tracked by: IVGCVSW-1576
telsoa01c577f2c2018-08-31 09:22:23 +01001339
Ryan OSheaed27ee72020-04-22 16:37:29 +01001340 // Checking broadcast compatibility : only scalar or 1D tensors
1341 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1342 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1343 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1344 ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1345
1346 unsigned int numDims = input0.GetNumDimensions();
1347 for (unsigned int i = 0; i < numDims; i++)
telsoa01c577f2c2018-08-31 09:22:23 +01001348 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01001349 unsigned int dim0 = input0.GetShape()[i];
1350 unsigned int dim1 = input1.GetShape()[i];
1351 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
telsoa01c577f2c2018-08-31 09:22:23 +01001352 {
James Ward58dec6b2020-09-11 17:32:44 +01001353 throw ParseException(
1354 fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1355 "Input dimensions should either match or one should be of size 1 and here, "
1356 "{} and {} {}",
1357 node.name(),
1358 TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1359 m_TensorsInfo[inputs.first].m_dtype),
1360 TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1361 m_TensorsInfo[inputs.second].m_dtype),
1362 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001363 }
telsoa01c577f2c2018-08-31 09:22:23 +01001364 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001365
1366
1367 IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001368 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001369
1370 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
Ryan OSheaed27ee72020-04-22 16:37:29 +01001371 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1372 m_TensorsInfo[inputs.second].m_info->GetShape() });
telsoa01c577f2c2018-08-31 09:22:23 +01001373 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1374
Ryan OSheaed27ee72020-04-22 16:37:29 +01001375 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1376 if(m_TensorsInfo[inputs.first].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001377 CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001378 }
1379 if(m_TensorsInfo[inputs.second].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001380 CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001381 }
1382 RegisterInputSlots(layer, {inputs.first, inputs.second});
telsoa01c577f2c2018-08-31 09:22:23 +01001383
Ryan OSheaed27ee72020-04-22 16:37:29 +01001384 // register the output connection
telsoa01c577f2c2018-08-31 09:22:23 +01001385 RegisterOutputSlots(layer, {node.output(0)});
1386}
1387
Kevin Mayef33cb12021-01-29 14:24:57 +00001388void OnnxParserImpl::ParseAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001389{
1390 Pooling2dDescriptor desc;
1391 desc.m_PoolType = PoolingAlgorithm::Average;
1392
1393 uint32_t count_include_pad = 0;
1394 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1395 if(count_include_pad) {
1396 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1397 }
1398 AddPoolingLayer(node, desc);
1399}
1400
Kevin Mayef33cb12021-01-29 14:24:57 +00001401void OnnxParserImpl::ParseBatchNormalization(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001402{
1403 //IGNORE momentum parameter and spatial parameters
1404
1405 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1406 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1407
1408 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1409 for(int ind = 1; ind < node.input_size(); ++ind)
1410 {
1411 auto tensor = node.input(ind);
1412 if(! m_TensorsInfo[tensor].isConstant())
1413 {
James Ward58dec6b2020-09-11 17:32:44 +01001414 throw ParseException(
1415 fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1416 tensor,
1417 node.name(),
1418 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001419 }
1420 }
1421
1422 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1423 BatchNormalizationDescriptor desc;
1424 desc.m_Eps = epsilon;
1425
1426 auto scaleTensor = CreateConstTensor(node.input(1));
1427 auto biasTensor = CreateConstTensor(node.input(2));
1428 auto meanTensor = CreateConstTensor(node.input(3));
1429 auto varTensor = CreateConstTensor(node.input(4));
1430
1431 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1432 meanTensor.first,
1433 varTensor.first,
1434 biasTensor.first,
1435 scaleTensor.first,
1436 node.name().c_str());
1437 ARMNN_ASSERT(layer != nullptr);
1438
1439 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1440 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1441
1442 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1443
1444 // register the output connection
1445 RegisterOutputSlots(layer, {node.output(0)});
1446}
1447
Narumol Prangnawaratbc3bb622021-09-24 16:08:34 +01001448void OnnxParserImpl::ParseConcat(const onnx::NodeProto& node)
1449{
1450 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1451
1452 uint32_t numConcatView = static_cast<uint32_t>(node.input_size());
1453 uint32_t inputRank = m_TensorsInfo[node.input(0)].m_info->GetNumDimensions();
1454
1455 int axisInt = ReadMandatoryNodeIntAttribute(node, "axis");
1456
1457 unsigned int concatDimInput = static_cast<unsigned int>(
1458 (static_cast<int>(inputRank) + axisInt) % static_cast<int>(inputRank));
1459
1460 OriginsDescriptor concatDescriptor(numConcatView, inputRank);
1461 concatDescriptor.SetConcatAxis(concatDimInput);
1462
1463 unsigned int mergeDimOrigin = 0;
1464
1465 std::vector<TensorShape> inputShapes;
1466 std::vector<std::string> tensorIds;
1467
1468 for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1469 {
1470 std::string nodeName = node.input(static_cast<int>(viewIndex));
1471 auto inputTensorInfo = *m_TensorsInfo[nodeName].m_info;
1472 inputShapes.push_back(inputTensorInfo.GetShape());
1473 tensorIds.push_back(nodeName);
1474
1475 // Set up concatDescriptor view origin
1476 armnnUtils::ProcessConcatInputTensorInfo(
1477 inputTensorInfo, concatDescriptor, concatDimInput, viewIndex, mergeDimOrigin);
1478 }
1479
1480 IConnectableLayer* layer = m_Network->AddConcatLayer(concatDescriptor, node.name().c_str());
1481 ARMNN_ASSERT(layer != nullptr);
1482
1483 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, inputShapes);
1484
1485 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1486
1487 // register the input connection slots for the layer, connections are made after all layers have been created
1488 RegisterInputSlots(layer, tensorIds);
1489
1490 // register the output connection slots for the layer, connections are made after all layers have been created
1491 RegisterOutputSlots(layer, { node.output(0) });
1492}
1493
Kevin Mayef33cb12021-01-29 14:24:57 +00001494void OnnxParserImpl::ParseConstant(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001495{
1496 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1497 if (!node.attribute(0).has_t())
1498 {
James Ward58dec6b2020-09-11 17:32:44 +01001499 throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1500 node.name(),
1501 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001502 }
1503 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1504
Ryan OSheaed27ee72020-04-22 16:37:29 +01001505 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1506 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1507 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1508 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1509
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001510 if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_FLOAT)
1511 {
1512 CreateConstantLayer(node.output(0), node.name());
1513 }
1514 else if (m_TensorsInfo[node.output(0)].m_dtype == onnx::TensorProto_DataType_INT64)
1515 {
1516 CreateInt64ConstantLayer(node.output(0), node.name());
1517 }
1518 else
1519 {
1520 throw ParseException(fmt::format("Data type not support for Constant node '{}' {}",
1521 node.name(),
1522 CHECK_LOCATION().AsString()));
1523 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001524}
1525
Kevin Mayef33cb12021-01-29 14:24:57 +00001526void OnnxParserImpl::ParseConv(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001527{
1528 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1529 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1530
1531 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1532
1533 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1534 {
James Ward58dec6b2020-09-11 17:32:44 +01001535 throw ParseException(
1536 fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1537 node.name(),
1538 TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1539 m_TensorsInfo[node.input(0)].m_dtype),
1540 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001541 }
1542
1543 if(!m_TensorsInfo[node.input(1)].isConstant())
1544 {
James Ward58dec6b2020-09-11 17:32:44 +01001545 throw ParseException(
1546 fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1547 node.input(1),
1548 node.name(),
1549 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001550 }
1551
1552 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1553
telsoa01c577f2c2018-08-31 09:22:23 +01001554 Convolution2dDescriptor desc;
1555 desc.m_BiasEnabled = false;
1556
1557 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1558 if(strides.empty())
1559 {
1560 desc.m_StrideX = 1;
1561 desc.m_StrideY = 1;
1562 }
1563 else
1564 {
1565 desc.m_StrideX = strides[1];
1566 desc.m_StrideY = strides[0];
1567 }
1568
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001569 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1570 if(!dilations.empty())
1571 {
1572 desc.m_DilationX = dilations[1];
1573 desc.m_DilationY = dilations[0];
1574 }
1575
telsoa01c577f2c2018-08-31 09:22:23 +01001576 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1577 //Check new padding version first
1578 if(pads.empty())
1579 {
1580 //Check deprecated version
1581 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1582 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1583 {
1584 bool isUpper;
1585 if( paddingString == "SAME_LOWER")
1586 {
1587 isUpper = false;
1588 }
1589 else if (paddingString == "SAME_UPPER")
1590 {
1591 isUpper = true;
1592 }
1593 else
1594 {
James Ward58dec6b2020-09-11 17:32:44 +01001595 throw ParseException(
1596 fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1597 "supported and found {} {}",
1598 node.name(),
1599 paddingString,
1600 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001601 }
1602 uint32_t inputHeight = inputInfo.GetShape()[2];
1603 uint32_t inputWidth = inputInfo.GetShape()[3];
1604
1605 uint32_t weightHeight;
1606 uint32_t weightWidth;
1607 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1608 if (kernel_shape.empty())
1609 {
1610 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1611 weightHeight = weightTensorInfo.GetShape()[2];
1612 weightWidth = weightTensorInfo.GetShape()[3];
1613 }
1614 else
1615 {
1616 weightHeight = kernel_shape[0];
1617 weightWidth = kernel_shape[1];
1618 }
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001619 CalcPadding(inputHeight,
1620 weightHeight,
1621 desc.m_StrideY,
1622 desc.m_DilationY,
1623 &desc.m_PadTop,
1624 &desc.m_PadBottom,
1625 isUpper);
1626 CalcPadding(inputWidth,
1627 weightWidth,
1628 desc.m_StrideX,
1629 desc.m_DilationX,
1630 &desc.m_PadLeft,
1631 &desc.m_PadRight,
1632 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001633 }
1634 }
1635 else
1636 {
1637 desc.m_PadTop = pads[0];
1638 desc.m_PadLeft = pads[1];
1639 desc.m_PadBottom = pads[2];
1640 desc.m_PadRight = pads[3];
1641 }
1642
1643 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1644 if(group > 1)
1645 {
1646 if (group > inputInfo.GetShape()[1])
1647 {
1648 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001649 fmt::format("Error parsing Convolution node: {}. "
1650 "The 'group'={} parameter cannot be larger than the "
1651 "channel of the input shape={} (in NCHW format). {}",
1652 node.name(),
1653 group,
1654 inputInfo.GetShape()[1],
1655 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001656 }
1657 else if (group == inputInfo.GetShape()[1])
1658 {
1659 // we use a depthwise convolution here, because the number of groups equals to the
1660 // input channels
1661 AddConvLayerWithDepthwiseConv(node, desc);
1662 return;
1663 }
1664 else
1665 {
1666 // TODO: split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +01001667 // and concatenate the results afterwards
James Ward58dec6b2020-09-11 17:32:44 +01001668 throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1669 "The 'group'={} parameter should be 1 or be equal to the "
1670 "channel of the input shape={} (in NCHW format). {}",
1671 node.name(),
1672 group,
1673 inputInfo.GetShape()[1],
1674 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001675 }
1676 }
1677
1678 armnn::IConnectableLayer* layer;
1679 auto weightTensor = CreateConstTensor(node.input(1));
1680
1681 if (node.input_size() == 3)
1682 {
1683 if(!m_TensorsInfo[node.input(2)].isConstant())
1684 {
James Ward58dec6b2020-09-11 17:32:44 +01001685 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1686 node.input(2),
1687 node.name(),
1688 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001689 }
1690 desc.m_BiasEnabled = true;
1691 auto biasTensor = CreateConstTensor(node.input(2));
1692 layer = m_Network->AddConvolution2dLayer(desc,
1693 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001694 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001695 node.name().c_str());
1696 }
1697 else
1698 {
1699 layer = m_Network->AddConvolution2dLayer(desc,
1700 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001701 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001702 node.name().c_str());
1703 }
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001704 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001705
1706 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1707 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1708 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1709 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1710
1711 // register the input connection slots for the layer, connections are made after all layers have been created
1712 // only the tensors for the inputs are relevant, exclude the const tensors
1713 RegisterInputSlots(layer, {node.input(0)});
1714
1715 // register the output connection slots for the layer, connections are made after all layers have been created
1716 RegisterOutputSlots(layer, {node.output(0)});
1717}
1718
Kevin Mayef33cb12021-01-29 14:24:57 +00001719void OnnxParserImpl::ParseFlatten(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001720{
1721 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1722 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1723
1724 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1725 m_TensorsInfo[node.input(0)].m_dtype,
1726 onnx::TensorProto::FLOAT);
1727
1728 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1729 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1730
1731 /// Negative axis conversion
1732 if (axis < 0)
1733 {
1734 axis += inputShape.GetNumDimensions();
1735 }
1736
1737 /// Check Axis is within dimensions
1738 if (axis < 0 || axis >= inputShape.GetNumDimensions())
1739 {
James Ward58dec6b2020-09-11 17:32:44 +01001740 throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
1741 axis, inputShape.GetNumDimensions(), node.name()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001742 }
1743
1744 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1745 uint dimension1{1};
1746 uint dimension2{1};
1747 uint i{0};
1748
1749 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1750 for (i = 0; i < axis; i++){
1751 dimension1 *= inputShape[i];
1752 }
1753
1754 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
1755 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
1756 dimension2 *= inputShape[i];
1757 }
1758
1759 TensorShape outputShape{dimension1, dimension2};
1760
1761 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
1762 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1763 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1764}
1765
Narumol Prangnawaratf10b15a2021-09-17 21:08:57 +01001766void OnnxParserImpl::ParseGather(const onnx::NodeProto& node)
1767{
1768 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1769 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1770
1771 armnn::GatherDescriptor gatherDescriptor;
1772 gatherDescriptor.m_Axis = static_cast<int>(ReadOptionalNodeInt64Attribute(node, "axis", 0));
1773
1774 IConnectableLayer* layer = m_Network->AddGatherLayer(gatherDescriptor, node.name().c_str());
1775 ARMNN_ASSERT(layer != nullptr);
1776
1777 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1778 TensorShape indicesShape = m_TensorsInfo[node.input(1)].m_info->GetShape();
1779 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, { inputShape, indicesShape });
1780 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1781
1782 // register the input connection slots for the layer, connections are made after all layers have been created
1783 RegisterInputSlots(layer, { node.input(0), node.input(1) });
1784
1785 // register the output connection slots for the layer, connections are made after all layers have been created
1786 RegisterOutputSlots(layer, { node.output(0) });
1787}
1788
Kevin Mayef33cb12021-01-29 14:24:57 +00001789void OnnxParserImpl::ParseGlobalAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001790{
1791 Pooling2dDescriptor desc = Pooling2dDescriptor();
1792 desc.m_PoolType = PoolingAlgorithm::Average;
1793
1794 //kernel size is the same as input
1795 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1796 desc.m_PoolWidth = inputShape[3];
1797 desc.m_PoolHeight = inputShape[2];
1798
1799 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1800 ARMNN_ASSERT(layer != nullptr);
1801
1802 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
1803 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1804
1805 // register the input connection slots for the layer, connections are made after all layers have been created
1806 // only the tensors for the inputs are relevant, exclude the const tensors
1807 RegisterInputSlots(layer, {node.input(0)});
1808
1809 // register the output connection slots for the layer, connections are made after all layers have been created
1810 RegisterOutputSlots(layer, {node.output(0)});
1811}
1812
Kevin Mayef33cb12021-01-29 14:24:57 +00001813void OnnxParserImpl::ParseMaxPool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001814{
1815 Pooling2dDescriptor desc;
1816 desc.m_PoolType = PoolingAlgorithm::Max;
1817 desc.m_PaddingMethod = PaddingMethod::Exclude;
1818 AddPoolingLayer(node, desc);
1819}
1820
Narumol Prangnawaratcdc495e2021-09-16 18:13:39 +01001821void OnnxParserImpl::ParseShape(const onnx::NodeProto& node)
1822{
1823 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1824 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1825
1826 // Output must be INT64
1827 CHECK_VALID_DATATYPE(node.name(), node.output(0),
1828 m_TensorsInfo[node.output(0)].m_dtype,
1829 onnx::TensorProto::INT64);
1830
1831 IConnectableLayer* layer = m_Network->AddShapeLayer(node.name().c_str());
1832 ARMNN_ASSERT(layer != nullptr);
1833
1834 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1835 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
1836 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1837
1838 // register the input connection slots for the layer, connections are made after all layers have been created
1839 RegisterInputSlots(layer, {node.input(0)});
1840
1841 // register the output connection slots for the layer, connections are made after all layers have been created
1842 RegisterOutputSlots(layer, {node.output(0)});
1843}
1844
Kevin Mayef33cb12021-01-29 14:24:57 +00001845void OnnxParserImpl::ParseReshape(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001846{
1847 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1848 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1849
1850 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1851 m_TensorsInfo[node.input(0)].m_dtype,
1852 onnx::TensorProto::FLOAT); //input
1853 CHECK_VALID_DATATYPE(node.name(), node.input(1),
1854 m_TensorsInfo[node.input(1)].m_dtype,
1855 onnx::TensorProto::INT64); //shape
1856
1857 if(!m_TensorsInfo[node.input(1)].isConstant())
1858 {
James Ward58dec6b2020-09-11 17:32:44 +01001859 throw ParseException(fmt::format("Shape '{}' should be constant in Reshape layer '{}' {}",
1860 node.input(1),
1861 node.name(),
1862 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001863 }
1864
1865 if(m_TensorsInfo[node.input(0)].isConstant())
1866 {
1867 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
1868 if(m_TensorsInfo.count(node.output(0)) == 0)
1869 {
1870 m_TensorsInfo[node.output(0)] = OnnxTensor();
1871 }
1872 m_TensorsInfo[node.output(0)].m_tensor =
1873 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1874 }
1875 else
1876 {
1877 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1878
1879 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
1880 {
1881 uint64_t dims = static_cast<uint64_t>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
1882 TensorShape targetShape{static_cast<unsigned int>(dims), 1};
1883
1884 for(uint i = 0; i < dims; i++)
1885 {
1886 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
1887 targetShape[i]= static_cast<unsigned int>(val);
1888 }
1889
1890 auto outInfo = ComputeReshapeInfo(targetShape, inputShape, node.output(0));
1891 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1892 }
1893
1894 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1895 }
1896}
1897
Narumol Prangnawaratfe6aa2f2021-09-23 16:11:17 +01001898void OnnxParserImpl::ParseUnsqueeze(const onnx::NodeProto& node)
1899{
1900 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.input_size()), 1, 2);
1901 CHECK_VALID_SIZE(armnn::numeric_cast<size_t>(node.output_size()), 1);
1902
1903 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1904 m_TensorsInfo[node.input(0)].m_dtype,
1905 onnx::TensorProto::FLOAT); //input
1906
1907 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1908 std::vector<uint32_t> dims;
1909 if (node.input_size() == 1 && node.attribute_size() > 0)
1910 {
1911 dims = ReadMandatoryNodeUint32ListAttribute(node, "axes");
1912 }
1913 else
1914 {
1915 CHECK_VALID_DATATYPE(node.name(), node.input(1),
1916 m_TensorsInfo[node.input(1)].m_dtype,
1917 onnx::TensorProto::INT64); //axes
1918
1919 auto int64Axes = m_TensorsInfo[node.input(1)].m_tensor->int64_data().data();
1920 uint numDim = armnn::numeric_cast<uint>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
1921
1922 for(uint i = 0; i < numDim; i++)
1923 {
1924 uint32_t uint32Value = CHECKED_NON_NEGATIVE(CHECKED_INT32(int64Axes[i]));
1925 dims.push_back(uint32Value);
1926 }
1927 }
1928
1929 // Ensure that the axes are sorted
1930 std::sort(dims.begin(), dims.end());
1931
1932 std::vector<unsigned int> targetShape;
1933
1934 for(uint i = 0; i < inputShape.GetNumDimensions(); i++)
1935 {
1936 targetShape.push_back(inputShape[i]);
1937 }
1938
1939 for(uint i = 0; i < dims.size(); i++)
1940 {
1941 targetShape.insert(targetShape.begin() + armnn::numeric_cast<int>(dims[i]), 1);
1942 }
1943
1944 auto outInfo = ComputeReshapeInfo(TensorShape(armnn::numeric_cast<unsigned int>(targetShape.size()),
1945 targetShape.data()), inputShape, node.output(0));
1946 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1947
1948 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1949}
1950
Kevin Mayef33cb12021-01-29 14:24:57 +00001951void OnnxParserImpl::PrependForBroadcast(const std::string& outputName,
1952 const std::string& input0,
1953 const std::string& input1)
telsoa01c577f2c2018-08-31 09:22:23 +01001954{
1955 //input0 should be reshaped to have same number of dim as input1
1956 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
1957
1958 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1959 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1960
1961 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
1962 std::vector<uint32_t> newShape;
1963 while(diff > 0)
1964 {
1965 newShape.push_back(1);
1966 diff--;
1967 }
1968 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
1969 {
1970 newShape.push_back(input0Shape[dim]);
1971 }
1972 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1973
1974 //add the new tensor to m_TensorsInfo
1975 m_TensorsInfo[outputName] = OnnxTensor();
1976 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1977
1978 //add reshape layer if the parent was not constant...
1979 if( ! m_TensorsInfo[input0].isConstant())
1980 {
James Ward58dec6b2020-09-11 17:32:44 +01001981 CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
telsoa01c577f2c2018-08-31 09:22:23 +01001982 }
1983 else //make it constant and it will be create in Add
1984 {
1985 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1986
1987 }
1988}
1989
Kevin Mayef33cb12021-01-29 14:24:57 +00001990void OnnxParserImpl::SetupInputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01001991{
1992 //Find user input and add their layers
1993 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1994 {
1995 auto input = m_Graph->input(inputIndex);
1996 if (! m_TensorsInfo[input.name()].isConstant())
1997 {
1998 IConnectableLayer* layer =
1999 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
2000 auto tensorInfo = ToTensorInfo(input);
2001 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2002
2003 RegisterOutputSlots(layer,{ input.name() });
2004 }
2005 }
2006}
2007
Kevin Mayef33cb12021-01-29 14:24:57 +00002008void OnnxParserImpl::SetupOutputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01002009{
2010 if(m_Graph->output_size() == 0)
2011 {
James Ward58dec6b2020-09-11 17:32:44 +01002012 throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002013 }
2014
2015 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
2016 {
2017 IConnectableLayer* layer =
2018 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
2019 m_Graph->output(outputIndex).name().c_str());
2020
2021 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
2022 }
2023}
2024
Kevin Mayef33cb12021-01-29 14:24:57 +00002025void OnnxParserImpl::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002026{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01002027 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01002028 if (tensorIds.size() != layer->GetNumInputSlots())
2029 {
2030 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002031 fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
2032 tensorIds.size(),
2033 layer->GetNumInputSlots(),
2034 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002035 }
Matthew Sloyan81beae32021-07-13 19:46:11 +01002036
telsoa01c577f2c2018-08-31 09:22:23 +01002037 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
2038 {
2039 std::string tensorId = tensorIds[slotIndex];
2040 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
2041
2042 auto it = m_TensorConnections.find(tensorId);
2043
2044 if (it == m_TensorConnections.end())
2045 {
2046 //First time seing this tensor, we need to map it
2047 m_TensorConnections[tensorId] = TensorSlots();
2048 }
2049 m_TensorConnections[tensorId].inputSlots.push_back(slot);
2050 }
2051}
2052
Kevin Mayef33cb12021-01-29 14:24:57 +00002053void OnnxParserImpl::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01002054{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01002055 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01002056 if (tensorIds.size() != layer->GetNumOutputSlots())
2057 {
2058 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01002059 fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
2060 tensorIds.size(),
2061 layer->GetNumOutputSlots(),
2062 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002063 }
2064
2065 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
2066 {
2067 std::string tensorId = tensorIds[slotIndex];
2068 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
2069
2070 auto it = m_TensorConnections.find(tensorId);
2071
2072 if (it == m_TensorConnections.end())
2073 {
2074 //First time seing this tensor, we need to map it
2075 m_TensorConnections[tensorId] = TensorSlots();
2076 }
2077
Ryan OShea337c17f2020-02-21 12:33:17 +00002078 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01002079
2080 // assuming there is only one producer for that tensor
2081 if (tensorSlots.outputSlot != nullptr)
2082 {
James Ward58dec6b2020-09-11 17:32:44 +01002083 throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
2084 "tensor:{} {}",
2085 tensorId,
2086 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002087 }
2088 tensorSlots.outputSlot = slot;
2089 }
2090}
2091
Kevin Mayef33cb12021-01-29 14:24:57 +00002092BindingPointInfo OnnxParserImpl::GetNetworkInputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002093{
2094 for(int i = 0; i < m_Graph->input_size(); ++i)
2095 {
2096 auto input = m_Graph->input(i);
2097 if(input.name() == name)
2098 {
2099 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(input));
2100 }
2101 }
James Ward58dec6b2020-09-11 17:32:44 +01002102 throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
2103 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002104}
2105
Kevin Mayef33cb12021-01-29 14:24:57 +00002106BindingPointInfo OnnxParserImpl::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01002107{
2108 for(int i = 0; i < m_Graph->output_size(); ++i)
2109 {
2110 auto output = m_Graph->output(i);
2111 if(output.name() == name)
2112 {
2113 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(output));
2114 }
2115 }
James Ward58dec6b2020-09-11 17:32:44 +01002116 throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
2117 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002118}
2119
Kevin Mayef33cb12021-01-29 14:24:57 +00002120std::vector<std::string> OnnxParserImpl::GetInputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002121{
2122 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002123 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2124 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002125 }
2126
2127 std::vector<std::string> inputNames;
2128 std::map<std::string, bool> isConstant;
2129 for(auto tensor : model->graph().initializer())
2130 {
2131 isConstant[tensor.name()] = true;
2132 }
2133 for(auto input : model->graph().input())
2134 {
2135 auto it = isConstant.find(input.name());
2136 if(it == isConstant.end())
2137 {
2138 inputNames.push_back(input.name());
2139 }
2140 }
2141 return inputNames;
2142}
2143
Kevin Mayef33cb12021-01-29 14:24:57 +00002144std::vector<std::string> OnnxParserImpl::GetOutputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01002145{
2146 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01002147 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
2148 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01002149 }
2150
2151 std::vector<std::string> outputNames;
2152 for(auto output : model->graph().output())
2153 {
2154 outputNames.push_back(output.name());
2155 }
2156 return outputNames;
2157}
2158
Matthew Sloyanac001ee2021-02-03 10:43:04 +00002159const std::string OnnxParserImpl::GetVersion()
2160{
2161 return ONNX_PARSER_VERSION;
2162}
2163
telsoa01c577f2c2018-08-31 09:22:23 +01002164} // namespace armnnOnnxParser