blob: b4e71332398be97592f49b4e8a73f471df06cce5 [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 Bentham39ef3e52020-01-20 10:09:09 +00007#include <armnn/Descriptors.hpp>
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01008#include <armnn/utility/Assert.hpp>
Matthew Sloyan589e3e82020-09-11 16:17:48 +01009#include <armnn/utility/NumericCast.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010010#include <VerificationHelpers.hpp>
11
James Ward58dec6b2020-09-11 17:32:44 +010012#include <fmt/format.h>
Aron Virginas-Tard4f0fea2019-04-09 14:08:06 +010013
telsoa01c577f2c2018-08-31 09:22:23 +010014#include <google/protobuf/text_format.h>
15#include <google/protobuf/io/zero_copy_stream_impl.h>
16
telsoa01c577f2c2018-08-31 09:22:23 +010017#include <numeric>
18
19using namespace armnn;
20
21namespace armnnOnnxParser
22{
Kevin Mayef33cb12021-01-29 14:24:57 +000023
24IOnnxParser::IOnnxParser() : pOnnxParserImpl(new OnnxParserImpl()) {}
25
26IOnnxParser::~IOnnxParser() = default;
27
28IOnnxParser* IOnnxParser::CreateRaw()
29{
30 return new IOnnxParser();
31}
32
33IOnnxParserPtr IOnnxParser::Create()
34{
35 return IOnnxParserPtr(CreateRaw(), &IOnnxParser::Destroy);
36}
37
38void IOnnxParser::Destroy(IOnnxParser* parser)
39{
40 delete parser;
41}
42
43armnn::INetworkPtr IOnnxParser::CreateNetworkFromBinaryFile(const char* graphFile)
44{
45 return pOnnxParserImpl->CreateNetworkFromBinaryFile(graphFile);
46}
47
48armnn::INetworkPtr IOnnxParser::CreateNetworkFromTextFile(const char* graphFile)
49{
50 return pOnnxParserImpl->CreateNetworkFromTextFile(graphFile);
51}
52
53armnn::INetworkPtr IOnnxParser::CreateNetworkFromString(const std::string& protoText)
54{
55 return pOnnxParserImpl->CreateNetworkFromString(protoText);
56}
57
58BindingPointInfo IOnnxParser::GetNetworkInputBindingInfo(const std::string& name) const
59{
60 return pOnnxParserImpl->GetNetworkInputBindingInfo(name);
61}
62
63BindingPointInfo IOnnxParser::GetNetworkOutputBindingInfo(const std::string& name) const
64{
65 return pOnnxParserImpl->GetNetworkOutputBindingInfo(name);
66}
67
telsoa01c577f2c2018-08-31 09:22:23 +010068namespace
69{
70void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
71 const onnx::TensorProto::DataType actualValue,
72 const char* validExpr,
73 std::string nodeName,
74 std::string tensorName,
75 const armnn::CheckLocation& location)
76{
77 bool isValid = std::any_of(validInputTypes.begin(),
78 validInputTypes.end(),
79 [&actualValue](onnx::TensorProto::DataType x) { return x == actualValue; } );
80 if (!isValid)
81 {
82 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +010083 fmt::format("Datatype {} is not valid for tensor '{}' of node '{}', not in {{{}}}. {}",
84 onnx::TensorProto::DataType_Name(actualValue),
85 tensorName,
86 nodeName,
87 validExpr,
88 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +010089 }
90}
91
92#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \
93CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION())
94
95using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
96#define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__})
97
98template <typename Callable>
99void ReadMandatoryNodeAttributeImpl(const onnx::NodeProto& node,
100 const std::string& attribName,
101 onnx::AttributeProto::AttributeType expectedType,
102 Callable callable)
103{
104 auto attribs = node.attribute();
105 int attriNum = 0;
106 while (attriNum < node.attribute_size())
107 {
108 if (attribs.Get(attriNum).name() == attribName)
109 {
110 if (attribs.Get(attriNum).type() == expectedType)
111 {
112 callable(attribs.Get(attriNum));
113 }
114 else
115 {
James Ward58dec6b2020-09-11 17:32:44 +0100116 throw ParseException(fmt::format("Attribute {} of node {} expected to have {} as "
117 "onnx::AttributeProto::AttributeType, but found {} instead {}",
118 attribName,
119 node.name(),
120 onnx::AttributeProto::AttributeType_Name(expectedType),
121 onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type()),
122 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100123 }
124 break;
125 }
126 ++attriNum;
127 }
128 if (attriNum == node.attribute_size())
129 {
James Ward58dec6b2020-09-11 17:32:44 +0100130 throw ParseException(fmt::format("Could not find required attribute {} in node {} {}",
131 attribName, node.name(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100132 }
133}
134
135template <typename Callable>
136void ReadOptionalNodeAttributeImpl(const onnx::NodeProto& node,
137 const std::string& attribName,
138 onnx::AttributeProto::AttributeType expectedType,
139 Callable callable)
140{
141 auto attribs = node.attribute();
142 for (int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
143 {
144 if (attribs.Get(attriNum).name() == attribName)
145 {
146 if (attribs.Get(attriNum).type() == expectedType)
147 {
148 callable(attribs.Get(attriNum));
149 }
150 else
151 {
James Ward58dec6b2020-09-11 17:32:44 +0100152 throw ParseException(
153 fmt::format("Attribute {} of node {} expected to have {} as onnx::AttributeProto::AttributeType, "
154 "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()));
telsoa01c577f2c2018-08-31 09:22:23 +0100160 }
161 }
162 }
163}
164
Ryan OSheaed27ee72020-04-22 16:37:29 +0100165int64_t ReadOptionalNodeInt64Attribute(const onnx::NodeProto& node,
166 const std::string& name,
167 const int64_t defaultValue = 0)
168{
169 int64_t attribValue = defaultValue;
170 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
171 [&attribValue](const onnx::AttributeProto& attrValue)
172 {
173 attribValue = attrValue.i();
174 });
175 return attribValue;
176}
177
telsoa01c577f2c2018-08-31 09:22:23 +0100178std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const onnx::NodeProto& node,
179 const std::string& name)
180{
181 std::vector<uint32_t> attriList;
182 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
183 [&attriList](const onnx::AttributeProto& attrValue)
184 {
185 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
186 {
187 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
188 }
189 });
190 return attriList;
191}
192
193uint32_t ReadOptionalNodeUint32Attribute(const onnx::NodeProto& node,
194 const std::string& name,
195 const uint32_t defaultVal = 0u)
196{
197 uint32_t attribValue = defaultVal;
198 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
199 [&attribValue](const onnx::AttributeProto& attrValue)
200 {
201 attribValue = CHECKED_NON_NEGATIVE(CHECKED_INT32((attrValue.i())));
202 });
203 return attribValue;
204}
205
206std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const onnx::NodeProto& node,
207 const std::string& name)
208{
209 std::vector<uint32_t> attriList;
210 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
211 [&attriList](const onnx::AttributeProto& attrValue)
212 {
213 for (int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
214 {
215 attriList.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(attrValue.ints().Get(attriNum))));
216 }
217 });
218
219 return attriList;
220}
221
222float ReadOptionalNodeFloatAttribute(const onnx::NodeProto& node,
223 const std::string& name,
224 const float defaultValue = 0.0f)
225{
226 float attribValue = defaultValue;
227 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
228 [&attribValue](const onnx::AttributeProto& attrValue)
229 {
230 attribValue = attrValue.f();
231 });
232 return attribValue;
233}
234
235std::string ReadOptionalNodeStringAttribute(const onnx::NodeProto& node, const std::string& name)
236{
237 std::string attribValue = "";
238 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
239 [&attribValue](const onnx::AttributeProto& attrValue)
240 {
241 attribValue = attrValue.s();
242 });
243 return attribValue;
244}
245
Tee Jungfcf6fd52019-11-01 05:27:28 +0000246armnn::TensorInfo ToTensorInfo(const std::string& name, std::vector<unsigned int>& shape, int data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100247{
telsoa01c577f2c2018-08-31 09:22:23 +0100248 DataType type;
Tee Jungfcf6fd52019-11-01 05:27:28 +0000249 switch(data_type)
telsoa01c577f2c2018-08-31 09:22:23 +0100250 {
251 case onnx::TensorProto::FLOAT:
252 {
253 type = DataType::Float32;
254 break;
255 }
256 case onnx::TensorProto::INT32:
257 case onnx::TensorProto::INT64:
258 {
259 type = DataType::Signed32;
260 break;
261 }
262 default:
263 {
264 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100265 fmt::format("'{}' is not a currently supported datatype for tensor {}."
266 " Supported dataTypes are FLOAT, INT32 and INT64. {}",
267 onnx::TensorProto::DataType_Name(static_cast<onnx::TensorProto::DataType>(data_type)),
268 name,
269 CHECK_LOCATION().AsString() ));
telsoa01c577f2c2018-08-31 09:22:23 +0100270 }
telsoa01c577f2c2018-08-31 09:22:23 +0100271 }
Tee Jungcaf2bdd2019-11-13 07:23:14 +0000272
273 // To avoid crashes by trivial tensors
274 if (shape.empty())
275 {
276 return TensorInfo(TensorShape(), type);
277 }
278
Tee Jungfcf6fd52019-11-01 05:27:28 +0000279 return TensorInfo(TensorShape(static_cast<unsigned int>(shape.size()), shape.data()), type);
280}
281
282armnn::TensorInfo ToTensorInfo(const onnx::ValueInfoProto& info)
283{
284 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
285 std::vector<unsigned int> shapeDims;
286 for (int i = 0; i < onnxShape.dim_size(); ++i)
287 {
288 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(onnxShape.dim(i).dim_value())));
289 }
290
Ryan OShea337c17f2020-02-21 12:33:17 +0000291 if (shapeDims.empty())
292 {
293 shapeDims.push_back(1);
294 }
295
Tee Jungfcf6fd52019-11-01 05:27:28 +0000296 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
297}
298
299armnn::TensorInfo ToTensorInfo(const onnx::TensorProto& tensor)
300{
301 std::vector<unsigned int> shapeDims;
Ryan OShea337c17f2020-02-21 12:33:17 +0000302
Tee Jungfcf6fd52019-11-01 05:27:28 +0000303 for (auto dim: tensor.dims())
304 {
305 shapeDims.push_back(CHECKED_NON_NEGATIVE(CHECKED_INT32(dim)));
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(tensor.name(), shapeDims, tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100314}
315
316std::string TensorInfoAsString(const TensorInfo& info,
317 const std::string& name,
318 const onnx::TensorProto::DataType& type)
319{
320 const TensorShape shape = info.GetShape();
321 std::stringstream ss;
322 ss << "tensor '" << name << "' contains "
323 << onnx::TensorProto::DataType_Name(type)
324 << " and has shape [";
325
326 for (uint32_t i = 0; i < shape.GetNumDimensions() - 1; ++i)
327 {
328 ss << shape[i] << ", ";
329 }
330 ss << shape[shape.GetNumDimensions() - 1] << "]";
331 return ss.str();
332}
333
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000334void CalcPadding(uint32_t inputSize,
335 uint32_t filterSize,
336 uint32_t stride,
337 uint32_t dilation,
338 uint32_t* paddingFront,
339 uint32_t* paddingBack,
340 bool isUpper)
telsoa01c577f2c2018-08-31 09:22:23 +0100341{
342 uint32_t outputSize = (inputSize + stride - 1) / stride;
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000343 uint32_t dilatedSize = filterSize + (dilation - 1) * (filterSize - 1);
344 uint32_t temp = (outputSize - 1) * stride + dilatedSize;
telsoa01c577f2c2018-08-31 09:22:23 +0100345 *paddingFront = (temp - inputSize) / 2;
346 *paddingBack = *paddingFront;
347 if((temp - inputSize) % 2 == 1)
348 {
349 if (isUpper)
350 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000351 *paddingBack += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100352 }
353 else
354 {
Sadik Armagan60bb9d82021-01-11 15:15:01 +0000355 *paddingFront += 1;
telsoa01c577f2c2018-08-31 09:22:23 +0100356 }
357 }
358}
359
Ryan OSheaed27ee72020-04-22 16:37:29 +0100360TensorInfo ComputeReshapeInfo(const TensorShape& targetShapeTensor,
telsoa01c577f2c2018-08-31 09:22:23 +0100361 const TensorShape& inShape,
362 const std::string& outName)
363{
364 std::vector<int> targetDims;
Ryan OSheaed27ee72020-04-22 16:37:29 +0100365 for(uint i = 0; i < targetShapeTensor.GetNumDimensions(); ++i)
telsoa01c577f2c2018-08-31 09:22:23 +0100366 {
Ryan OSheaed27ee72020-04-22 16:37:29 +0100367 int val = CHECKED_INT32(targetShapeTensor[i]);
telsoa01c577f2c2018-08-31 09:22:23 +0100368 if(val == 0)
369 {
370 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
371 }
372 else
373 {
374 targetDims.push_back(val);
375 }
376 }
377
378 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
379 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
380 if (stretchDim != targetDims.end())
381 {
382 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
383 {
384 std::stringstream ss;
385 ss << "[ ";
386 for(uint i = 0; i < targetDims.size() - 1; ++i)
387 {
388 ss << targetDims[i] << ", ";
389 }
390 ss << targetDims[targetDims.size() - 1] << " ]";
391
James Ward58dec6b2020-09-11 17:32:44 +0100392 throw ParseException(
393 fmt::format("Error during creation of reshaped tensor '{}'. At most one component of shape can be "
394 " -1 and here, shape is {} {}",
395 outName,
396 ss.str(),
397 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100398 }
399
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100400 auto targetNumElements = armnn::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
telsoa01c577f2c2018-08-31 09:22:23 +0100401 -1, std::multiplies<int32_t>()));
402 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
403 outDims[stretchIndex] = inShape.GetNumElements() / targetNumElements;
404 }
405 TensorShape outShape = TensorShape{static_cast<unsigned int>(outDims.size()), outDims.data()};
406 return TensorInfo(outShape, DataType::Float32);
407}
408
409} //namespace
410
Kevin Mayef33cb12021-01-29 14:24:57 +0000411const std::map<std::string, OnnxParserImpl::OperationParsingFunction> OnnxParserImpl::m_ParserFunctions = {
412 { "BatchNormalization", &OnnxParserImpl::ParseBatchNormalization},
413 { "GlobalAveragePool", &OnnxParserImpl::ParseGlobalAveragePool},
414 { "AveragePool", &OnnxParserImpl::ParseAveragePool },
415 { "Clip", &OnnxParserImpl::ParseClip },
416 { "Constant", &OnnxParserImpl::ParseConstant },
417 { "MaxPool", &OnnxParserImpl::ParseMaxPool },
418 { "Reshape", &OnnxParserImpl::ParseReshape },
419 { "Sigmoid", &OnnxParserImpl::ParseSigmoid },
420 { "Tanh", &OnnxParserImpl::ParseTanh },
421 { "Relu", &OnnxParserImpl::ParseRelu },
422 { "LeakyRelu", &OnnxParserImpl::ParseLeakyRelu },
423 { "Conv", &OnnxParserImpl::ParseConv },
424 { "Add", &OnnxParserImpl::ParseAdd },
425 { "Flatten", &OnnxParserImpl::ParseFlatten},
telsoa01c577f2c2018-08-31 09:22:23 +0100426};
427
428template<typename TypePair, typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000429void OnnxParserImpl::ValidateInputs(const onnx::NodeProto& node,
telsoa01c577f2c2018-08-31 09:22:23 +0100430 TypePair validInputs,
431 const Location& location)
432{
433 for(auto input : node.input())
434 {
435 CheckValidDataType(validInputs.second,
436 m_TensorsInfo[input].m_dtype,
437 validInputs.first,
438 node.name(),
439 input,
440 location);
441 }
442}
443
444#define VALID_INPUTS(NODE, VALID_INPUTS) \
Kevin Mayef33cb12021-01-29 14:24:57 +0000445 OnnxParserImpl::ValidateInputs(NODE, \
telsoa01c577f2c2018-08-31 09:22:23 +0100446 VALID_INPUTS, \
447 CHECK_LOCATION())
448
Kevin Mayef33cb12021-01-29 14:24:57 +0000449std::vector<TensorInfo> OnnxParserImpl::ComputeOutputInfo(std::vector<std::string> outNames,
450 const IConnectableLayer* layer,
451 std::vector<TensorShape> inputShapes)
telsoa01c577f2c2018-08-31 09:22:23 +0100452{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100453 ARMNN_ASSERT(! outNames.empty());
telsoa01c577f2c2018-08-31 09:22:23 +0100454 bool needCompute = std::any_of(outNames.begin(),
455 outNames.end(),
456 [this](std::string name)
457 {
458 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info == nullptr);
459 });
460 std::vector<TensorInfo> outInfo;
461 //if the output info(s) are not here, we need to compute them
462 std::vector<TensorShape> inferredShapes;
463 if(needCompute)
464 {
465 inferredShapes = layer->InferOutputShapes(inputShapes);
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100466 ARMNN_ASSERT(inferredShapes.size() == outNames.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100467 }
468 for (uint i = 0; i < outNames.size(); ++i)
469 {
470 if(needCompute)
471 {
472 m_TensorsInfo[outNames[i]] = OnnxTensor();
473 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
474 TensorInfo(inferredShapes[i], DataType::Float32));
475 }
476 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
477 }
478 return outInfo;
479}
480
Kevin Mayef33cb12021-01-29 14:24:57 +0000481OnnxParserImpl::OnnxParserImpl()
telsoa01c577f2c2018-08-31 09:22:23 +0100482 : m_Network(nullptr, nullptr)
483{
484}
485
Kevin Mayef33cb12021-01-29 14:24:57 +0000486void OnnxParserImpl::ResetParser()
telsoa01c577f2c2018-08-31 09:22:23 +0100487{
488 m_Network = armnn::INetworkPtr(nullptr, nullptr);
489 m_Graph = nullptr;
490}
491
Kevin Mayef33cb12021-01-29 14:24:57 +0000492void OnnxParserImpl::Cleanup()
telsoa01c577f2c2018-08-31 09:22:23 +0100493{
494 m_TensorConnections.clear();
495 m_TensorsInfo.clear();
496 m_OutputsMap.clear();
497 m_OutputsFusedAndUsed.clear();
498}
499
Kevin Mayef33cb12021-01-29 14:24:57 +0000500std::pair<ConstTensor, std::unique_ptr<float[]>> OnnxParserImpl::CreateConstTensor(const std::string name)
telsoa01c577f2c2018-08-31 09:22:23 +0100501{
502 const TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
503 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
504
505 auto srcData = onnxTensor.float_data().data();
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100506 std::unique_ptr<float[]> tensorData(new float[tensorInfo.GetNumElements()]);
507 const size_t tensorSizeInBytes = tensorInfo.GetNumBytes();
508 // Copy the value list entries into the destination
509 if (!onnxTensor.has_raw_data())
telsoa01c577f2c2018-08-31 09:22:23 +0100510 {
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100511 if(tensorInfo.GetNumElements() != static_cast<uint>(onnxTensor.float_data_size()))
512 {
James Ward58dec6b2020-09-11 17:32:44 +0100513 throw ParseException(
514 fmt::format("The number of data provided ({}) does not match the tensor '{}' number of "
515 "elements ({}) {}",
516 onnxTensor.float_data_size(),
517 name,
518 tensorInfo.GetNumElements(),
519 CHECK_LOCATION().AsString()));
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100520 }
521 ::memcpy(tensorData.get(), srcData, tensorSizeInBytes);
telsoa01c577f2c2018-08-31 09:22:23 +0100522 }
Pablo Tello3dcc1c62019-04-24 14:20:21 +0100523 else
524 {
525 ::memcpy(tensorData.get(), onnxTensor.raw_data().c_str(), tensorSizeInBytes);
526 }
telsoa01c577f2c2018-08-31 09:22:23 +0100527
528 // Const tensors requires at least a list of values
529 if (tensorInfo.GetNumElements() == 0)
530 {
James Ward58dec6b2020-09-11 17:32:44 +0100531 throw ParseException(fmt::format("No tensor data found for Const tensor '{}' {}",
532 name,
533 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100534 }
535 return std::make_pair(ConstTensor(tensorInfo, tensorData.get()), std::move(tensorData));
536}
537
Kevin Mayef33cb12021-01-29 14:24:57 +0000538ModelPtr OnnxParserImpl::LoadModelFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100539{
540 FILE* fd = fopen(graphFile, "r");
541
542 if (fd == nullptr)
543 {
James Ward58dec6b2020-09-11 17:32:44 +0100544 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100545 }
546
547 // Parse the file into a message
548 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
549 using google::protobuf::io::FileInputStream;
550 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
551 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
552 fclose(fd);
553
554 if (!success)
555 {
556 std::stringstream error;
557 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100558 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100559 }
560 return modelProto;
561}
562
Kevin Mayef33cb12021-01-29 14:24:57 +0000563INetworkPtr OnnxParserImpl::CreateNetworkFromTextFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100564{
565 ResetParser();
566 ModelPtr modelProto = LoadModelFromTextFile(graphFile);
567 return CreateNetworkFromModel(*modelProto);
568}
569
570
Kevin Mayef33cb12021-01-29 14:24:57 +0000571ModelPtr OnnxParserImpl::LoadModelFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100572{
573 FILE* fd = fopen(graphFile, "rb");
574
575 if (fd == nullptr)
576 {
James Ward58dec6b2020-09-11 17:32:44 +0100577 throw FileNotFoundException(fmt::format("Invalid (null) filename {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100578 }
579
580 // Parse the file into a message
581 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
582
583 google::protobuf::io::FileInputStream inStream(fileno(fd));
584 google::protobuf::io::CodedInputStream codedStream(&inStream);
Nikhil Raje5181532020-10-09 14:52:25 +0100585 codedStream.SetTotalBytesLimit(INT_MAX);
telsoa01c577f2c2018-08-31 09:22:23 +0100586 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
587 fclose(fd);
588
589 if (!success)
590 {
591 std::stringstream error;
592 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100593 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100594 }
595 return modelProto;
596
597}
598
Kevin Mayef33cb12021-01-29 14:24:57 +0000599INetworkPtr OnnxParserImpl::CreateNetworkFromBinaryFile(const char* graphFile)
telsoa01c577f2c2018-08-31 09:22:23 +0100600{
601 ResetParser();
602 ModelPtr modelProto = LoadModelFromBinaryFile(graphFile);
603 return CreateNetworkFromModel(*modelProto);
604}
605
Kevin Mayef33cb12021-01-29 14:24:57 +0000606ModelPtr OnnxParserImpl::LoadModelFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100607{
608 if (protoText == "")
609 {
James Ward58dec6b2020-09-11 17:32:44 +0100610 throw InvalidArgumentException(fmt::format("Invalid (empty) string for model parameter {}",
611 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100612 }
613 // Parse the string into a message
614 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
615 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
616 if (!success)
617 {
618 std::stringstream error;
619 error << "Failed to parse graph file";
James Ward58dec6b2020-09-11 17:32:44 +0100620 throw ParseException(fmt::format("{} {}", error.str(), CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100621 }
622 return modelProto;
623}
624
Kevin Mayef33cb12021-01-29 14:24:57 +0000625INetworkPtr OnnxParserImpl::CreateNetworkFromString(const std::string& protoText)
telsoa01c577f2c2018-08-31 09:22:23 +0100626{
627 ResetParser();
628 ModelPtr modelProto = LoadModelFromString(protoText);
629 return CreateNetworkFromModel(*modelProto);
630}
631
Kevin Mayef33cb12021-01-29 14:24:57 +0000632INetworkPtr OnnxParserImpl::CreateNetworkFromModel(onnx::ModelProto& model)
telsoa01c577f2c2018-08-31 09:22:23 +0100633{
634 m_Network = INetwork::Create();
635 try
636 {
637 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
638 LoadGraph();
639 }
640 catch (const ParseException& e)
641 {
642 Cleanup();
643 throw e;
644 }
645 Cleanup();
646 return std::move(m_Network);
647}
648
Kevin Mayef33cb12021-01-29 14:24:57 +0000649void OnnxParserImpl::LoadGraph()
telsoa01c577f2c2018-08-31 09:22:23 +0100650{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100651 ARMNN_ASSERT(m_Graph.get() != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100652
653 //Fill m_TensorsInfo with the shapes and value of every tensor
654 SetupInfo(m_Graph->mutable_output());
655 SetupInfo(m_Graph->mutable_input());
656 SetupInfo(m_Graph->mutable_value_info());
657
658 for (auto tensor : m_Graph->initializer())
659 {
660 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
Tee Jungfcf6fd52019-11-01 05:27:28 +0000661 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
662 m_TensorsInfo[tensor.name()].m_dtype =
663 static_cast<onnx::TensorProto::DataType>(tensor.data_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100664 }
665
666 SetupInputLayers();
667 SetupOutputLayers();
668
669 //Detect FullyConnected layers with bias and update the FusedAndUsed map acccordingly
670 DetectFullyConnected();
671
672 //Parsing the graph
673 for(size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
674 {
675 auto node = m_Graph->node(static_cast<int>(nodeIndex));
676 const std::string& operation = node.op_type();
677
678 // check which layers we handled already (add and matmul fused as FC)
Ryan OShea337c17f2020-02-21 12:33:17 +0000679 if (operation == "MatMul" )
telsoa01c577f2c2018-08-31 09:22:23 +0100680 {
681 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
682 {
683 //Node which can not be fused as a FullyConnected layer (used in layers as a simple matmul output)
684 AddFullyConnected(node);
685 }
686 }
687 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation == "Add")
688 {
689 int matmulIndex = static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
690 AddFullyConnected(m_Graph->node(matmulIndex), &node);
691 }
692 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) //node is not part of a fused layer
693 {
694 auto it = m_ParserFunctions.find(operation);
695 if (it != m_ParserFunctions.end())
696 {
697 auto func = it->second;
698 (this->*func)(node);
699 }
700 else
701 {
James Ward58dec6b2020-09-11 17:32:44 +0100702 throw ParseException(fmt::format("Unsupported operation {} for node '{}' {}",
703 operation,
704 node.name(),
705 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100706 }
707 }
708 }
709
710 //Making the connections between outputs and inputs of each layers
711 for (const auto& tensorCon : m_TensorConnections)
712 {
713 if (tensorCon.second.outputSlot != nullptr)
714 {
715 for (size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
716 {
717 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
718 }
719 }
720 }
721}
722
Kevin Mayef33cb12021-01-29 14:24:57 +0000723void OnnxParserImpl::SetupInfo(const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
telsoa01c577f2c2018-08-31 09:22:23 +0100724{
725 for (auto tensor : *list)
726 {
727 m_TensorsInfo[tensor.name()] = OnnxTensor();
728 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(ToTensorInfo(tensor));
Matteo Martincighe355dc22018-12-10 13:45:27 +0000729 m_TensorsInfo[tensor.name()].m_dtype =
730 static_cast<onnx::TensorProto::DataType>(tensor.type().tensor_type().elem_type());
telsoa01c577f2c2018-08-31 09:22:23 +0100731 }
732}
733
Kevin Mayef33cb12021-01-29 14:24:57 +0000734void OnnxParserImpl::DetectFullyConnected()
telsoa01c577f2c2018-08-31 09:22:23 +0100735{
736 m_OutputsFusedAndUsed = std::vector<UsageSummary> (static_cast<size_t>(m_Graph->node_size()), UsageSummary());
737 auto matmulAndConstant = [&](const std::string& constInput,
738 const std::string& matmulInput,
739 int& nodeIndex)
740 {
741 auto matmulIt = m_OutputsMap.find(matmulInput);
742 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() == "MatMul"
743 && m_TensorsInfo[constInput].isConstant())
744 {
745 nodeIndex = matmulIt->second.second;
746 return true;
747 }
748 return false;
749 };
750
751 for(int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
752 {
753 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
754 for (const std::string& output : node->output())
755 {
756 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
757 }
758
759 for (const std::string& input : node->input()) //count how many time a node is used as input
760 {
761 auto matmulIt = m_OutputsMap.find(input);
762 if(matmulIt != m_OutputsMap.end()){
763 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes; //node used
764 }
765 }
766
767 if (node->op_type() == "Add")
768 {
769 int matmulIndex = 0;
770 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
771 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
772 {
773 //matmul and add were fused
774 m_OutputsFusedAndUsed[static_cast<size_t>(matmulIndex)].fusedWithNodes
775 .push_back(static_cast<size_t>(nodeIndex));
776
777 m_OutputsFusedAndUsed[static_cast<size_t>(nodeIndex)].fusedWithNodes
778 .push_back(static_cast<size_t>(matmulIndex));
779 }
780 }
781 }
782
783 for (auto output: m_Graph->output()) { //Add usages as output of the graph in count of usages
784 auto matmulIt = m_OutputsMap.find(output.name());
785 if(matmulIt != m_OutputsMap.end()){
786 ++m_OutputsFusedAndUsed[static_cast<size_t>(matmulIt->second.second)].inputForNodes;
787 }
788 }
789}
790
791template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000792void OnnxParserImpl::GetInputAndParam(const onnx::NodeProto& node,
793 std::string* inputName,
794 std::string* constName,
795 const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +0100796{
797 int cstIndex;
798 if (m_TensorsInfo[node.input(0)].isConstant())
799 {
800 cstIndex = 0;
801 }
802 else if (m_TensorsInfo[node.input(1)].isConstant())
803 {
804 cstIndex = 1;
805 }
806 else
807 {
James Ward58dec6b2020-09-11 17:32:44 +0100808 throw ParseException(fmt::format("One of the input tensors ('{}' or '{}') should be constant in node '{}' {}",
809 node.input(0),
810 node.input(1),
811 node.name(),
812 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100813 }
814 if(constName)
815 {
816 *constName = node.input(cstIndex);
817 }
818 if(inputName)
819 {
820 *inputName = node.input(!cstIndex);
821 }
822}
823
824template<typename Location>
Kevin Mayef33cb12021-01-29 14:24:57 +0000825void OnnxParserImpl::To1DTensor(const std::string& name, const Location& location)
telsoa01c577f2c2018-08-31 09:22:23 +0100826{
827 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
828 std::vector<uint32_t> newShape;
829 for(uint i = 0; i < shape.GetNumDimensions() - 1; ++i)
830 {
831 if(shape[i] != 1)
832 {
James Ward58dec6b2020-09-11 17:32:44 +0100833 throw ParseException(
834 fmt::format("Only tensors with shape [1, ..., 1, X] can be converted to 1D and {} {}",
835 TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype),
836 location.AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100837 }
838 }
839 newShape.push_back(shape[shape.GetNumDimensions() - 1]);
840
841 m_TensorsInfo[name].m_info->SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
842}
843
Kevin Mayef33cb12021-01-29 14:24:57 +0000844void OnnxParserImpl::AddConvLayerWithDepthwiseConv(const onnx::NodeProto& node, const Convolution2dDescriptor& convDesc)
Ryan OSheaed27ee72020-04-22 16:37:29 +0100845{
846 ARMNN_ASSERT(node.op_type() == "Conv");
847
848 DepthwiseConvolution2dDescriptor desc;
849 desc.m_PadLeft = convDesc.m_PadLeft;
850 desc.m_PadRight = convDesc.m_PadRight;
851 desc.m_PadTop = convDesc.m_PadTop;
852 desc.m_PadBottom = convDesc.m_PadBottom;
853 desc.m_StrideX = convDesc.m_StrideX;
854 desc.m_StrideY = convDesc.m_StrideY;
855 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
856
857 armnn::IConnectableLayer* layer;
858 auto weightTensor = CreateConstTensor(node.input(1));
859 TensorShape& weightShape = weightTensor.first.GetShape();
860 weightShape[1] = weightShape[0];
861 weightShape[0] = 1;
862 m_TensorsInfo[node.input(1)].m_info->SetShape(weightShape);
863
864 if (node.input_size() == 3)
865 {
866 if(!m_TensorsInfo[node.input(2)].isConstant())
867 {
James Ward58dec6b2020-09-11 17:32:44 +0100868 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
869 node.input(2),
870 node.name(),
871 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +0100872 }
873 desc.m_BiasEnabled = true;
874 auto biasTensor = CreateConstTensor(node.input(2));
875 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
876 weightTensor.first,
877 Optional<ConstTensor>(biasTensor.first),
878 node.name().c_str());
879 }
880 else
881 {
882 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
883 weightTensor.first,
884 EmptyOptional(),
885 node.name().c_str());
886 }
887 ARMNN_ASSERT(layer != nullptr);
888
889 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
890 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
891 m_TensorsInfo[node.input(1)].m_info->GetShape() });
892
893 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
894
895 // register the input connection slots for the layer, connections are made after all layers have been created
896 // only the tensors for the inputs are relevant, exclude the const tensors
897 RegisterInputSlots(layer, {node.input(0)});
898
899 // register the output connection slots for the layer, connections are made after all layers have been created
900 RegisterOutputSlots(layer, {node.output(0)});
901}
902
Kevin Mayef33cb12021-01-29 14:24:57 +0000903void OnnxParserImpl::AddFullyConnected(const onnx::NodeProto& matmulNode, const onnx::NodeProto* addNode)
telsoa01c577f2c2018-08-31 09:22:23 +0100904{
905
906 // find matmul inputs
907 std::string weightName;
908 std::string inputName;
909 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.input_size()), 2);
910 CHECK_VALID_SIZE(static_cast<size_t>(matmulNode.output_size()), 1);
911 VALID_INPUTS(matmulNode, STR_LIST(onnx::TensorProto::FLOAT));
912
913 GetInputAndParam(matmulNode, &inputName, &weightName, CHECK_LOCATION());
914
915 FullyConnectedDescriptor desc;
916 desc.m_BiasEnabled = addNode != nullptr;
917
918 IConnectableLayer* layer = nullptr;
919 if(desc.m_BiasEnabled)
920 {
921 // find bias const
922 std::string biasName;
923 CHECK_VALID_SIZE(static_cast<size_t>(addNode->input_size()), 2);
924 CHECK_VALID_SIZE(static_cast<size_t>(addNode->output_size()), 1);
925 VALID_INPUTS(*addNode, STR_LIST(onnx::TensorProto::FLOAT));
926
927 GetInputAndParam(*addNode, nullptr, &biasName, CHECK_LOCATION());
928
929 //Output shape is [1, weights[1]] and 1d vec in ONNX can be [1,X] so we convert biases to "armnn" 1D
930 To1DTensor(biasName, CHECK_LOCATION());
931 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
932 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
933
934 if (weightInfo.GetShape()[1] != biasInfo.GetShape()[0])
935 {
James Ward58dec6b2020-09-11 17:32:44 +0100936 throw ParseException(
937 fmt::format("Shape of weights '{}' and bias of following Add node '{}' do not match : {}"
938 " and {} ( /!\\ bias should be a 1D tensor) {}",
939 weightName,
940 addNode->name(),
941 TensorInfoAsString(*m_TensorsInfo[weightName].m_info, weightName,
942 m_TensorsInfo[weightName].m_dtype),
943 TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
944 m_TensorsInfo[biasName].m_dtype ),
945 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100946 }
947 layer = m_Network->AddFullyConnectedLayer(desc,
948 CreateConstTensor(weightName).first,
Matteo Martincighfc598e12019-05-14 10:36:13 +0100949 Optional<ConstTensor>(CreateConstTensor(biasName).first),
telsoa01c577f2c2018-08-31 09:22:23 +0100950 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100951 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100952
953 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
954 {m_TensorsInfo[inputName].m_info->GetShape(),
955 m_TensorsInfo[weightName].m_info->GetShape()});
956
957 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
958
959 RegisterInputSlots(layer, {inputName});
960 RegisterOutputSlots(layer, {addNode->output(0)});
961 }
962 else
963 {
Matteo Martincighfc598e12019-05-14 10:36:13 +0100964 layer = m_Network->AddFullyConnectedLayer(desc,
965 CreateConstTensor(weightName).first,
966 EmptyOptional(),
967 matmulNode.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100968 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +0100969
970 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
971 {m_TensorsInfo[inputName].m_info->GetShape(),
972 m_TensorsInfo[weightName].m_info->GetShape()});
973 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
974
975 RegisterInputSlots(layer, {inputName});
976 RegisterOutputSlots(layer, {matmulNode.output(0)});
977 }
978}
979
Kevin Mayef33cb12021-01-29 14:24:57 +0000980void OnnxParserImpl::AddPoolingLayer(const onnx::NodeProto& node, Pooling2dDescriptor& desc)
telsoa01c577f2c2018-08-31 09:22:23 +0100981{
982
983 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
984 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
985
986 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
987
988 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node, "kernel_shape"); //size of pool win
989 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
990 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
991
992 desc.m_OutputShapeRounding = OutputShapeRounding::Floor;
993 desc.m_PoolWidth = kernel_shape[1];
994 desc.m_PoolHeight = kernel_shape[0];
995
996 if(strides.empty())
997 {
998 desc.m_StrideX = 1;
999 desc.m_StrideY = 1;
1000 }
1001 else
1002 {
1003 desc.m_StrideX = strides[1];
1004 desc.m_StrideY = strides[0];
1005 }
1006
1007 //Check new padding version first
1008 if(pads.empty())
1009 {
1010 //Check deprecated version
1011 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1012 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1013 {
1014 bool isUpper;
1015 if( paddingString == "SAME_LOWER")
1016 {
1017 isUpper = false;
1018 }
1019 else if (paddingString == "SAME_UPPER")
1020 {
1021 isUpper = true;
1022 }
1023 else
1024 {
James Ward58dec6b2020-09-11 17:32:44 +01001025 throw ParseException(fmt::format("Invalid auto_pad attribute for node {}. "
1026 "Only SAME_UPPER, SAME_LOWER or VALID supported and found {} {}",
1027 node.name(),
1028 paddingString,
1029 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001030 }
1031 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1032 uint32_t inputHeight = inputInfo.GetShape()[2];
1033 uint32_t inputWidth = inputInfo.GetShape()[3];
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001034 CalcPadding(inputHeight,
1035 desc.m_PoolHeight,
1036 desc.m_StrideY,
1037 1u,
1038 &desc.m_PadTop,
1039 &desc.m_PadBottom,
1040 isUpper);
1041 CalcPadding(inputWidth,
1042 desc.m_PoolWidth,
1043 desc.m_StrideX,
1044 1u,
1045 &desc.m_PadLeft,
1046 &desc.m_PadRight,
1047 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001048 }
1049 }
1050 else
1051 {
1052 desc.m_PadTop = pads[0];
1053 desc.m_PadLeft = pads[1];
1054 desc.m_PadBottom = pads[2];
1055 desc.m_PadRight = pads[3];
1056 }
1057
1058 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001059 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001060
1061 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1062 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1063
1064 // register the input connection slots for the layer, connections are made after all layers have been created
1065 // only the tensors for the inputs are relevant, exclude the const tensors
1066 RegisterInputSlots(layer, {node.input(0)});
1067
1068 // register the output connection slots for the layer, connections are made after all layers have been created
1069 RegisterOutputSlots(layer, {node.output(0)});
1070}
1071
Kevin Mayef33cb12021-01-29 14:24:57 +00001072std::pair<std::string, std::string> OnnxParserImpl::AddPrepareBroadcast(const std::string& input0,
1073 const std::string& input1)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001074{
1075 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1076
1077 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1078 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1079
1080 if(input1Shape.GetNumDimensions() < input0Shape.GetNumDimensions())
1081 {
James Ward58dec6b2020-09-11 17:32:44 +01001082 auto outputName = fmt::format("reshape_output_{}", input1);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001083 PrependForBroadcast(outputName, input1, input0);
1084 inputs.second = outputName;
1085 }
1086 else if(input0Shape.GetNumDimensions() < input1Shape.GetNumDimensions())
1087 {
James Ward58dec6b2020-09-11 17:32:44 +01001088 auto outputName = fmt::format("reshape_output_{}", input0);
Ryan OSheaed27ee72020-04-22 16:37:29 +01001089 PrependForBroadcast(outputName, input0, input1);
1090 inputs.first = outputName;
1091 }
1092 return inputs;
1093}
1094
Kevin Mayef33cb12021-01-29 14:24:57 +00001095void OnnxParserImpl::CreateConstantLayer(const std::string& tensorName, const std::string& layerName)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001096{
1097 auto armnnTensor = CreateConstTensor(tensorName);
1098
1099 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
1100 layer->GetOutputSlot(0).SetTensorInfo(armnnTensor.first.GetInfo());
1101 RegisterOutputSlots(layer, {tensorName});
1102}
1103
Kevin Mayef33cb12021-01-29 14:24:57 +00001104void OnnxParserImpl::CreateReshapeLayer(const std::string& inputName,
1105 const std::string& outputName,
1106 const std::string& layerName)
telsoa01c577f2c2018-08-31 09:22:23 +01001107{
1108 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1109 ReshapeDescriptor reshapeDesc;
1110 reshapeDesc.m_TargetShape = outputTensorInfo.GetShape();
1111
1112 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001113 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001114 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1115
1116 // register the input connection slots for the layer, connections are made after all layers have been created
1117 // only the tensors for the inputs are relevant, exclude the const tensors
1118 RegisterInputSlots(layer, {inputName});
1119
1120 // register the output connection slots for the layer, connections are made after all layers have been created
1121 RegisterOutputSlots(layer, {outputName});
1122}
1123
Kevin Mayef33cb12021-01-29 14:24:57 +00001124void OnnxParserImpl::ParseActivation(const onnx::NodeProto& node, const armnn::ActivationFunction func)
telsoa01c577f2c2018-08-31 09:22:23 +01001125{
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001126 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1, 3);
telsoa01c577f2c2018-08-31 09:22:23 +01001127 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1128
1129 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1130
1131 ActivationDescriptor desc;
Tee Jung7ff9a602019-11-01 07:04:42 +00001132 desc.m_Function = func;
telsoa01c577f2c2018-08-31 09:22:23 +01001133
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001134 if (func == ActivationFunction::BoundedReLu)
1135 {
1136 desc.m_A = node.input(2).empty() ? std::numeric_limits<float>::max() : std::stof(node.input(2));
1137 desc.m_B = node.input(1).empty() ? std::numeric_limits<float>::lowest() : std::stof(node.input(1));
1138 }
1139
telsoa01c577f2c2018-08-31 09:22:23 +01001140 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001141 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001142
1143 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1144 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1145
1146 // register the input connection slots for the layer, connections are made after all layers have been created
1147 // only the tensors for the inputs are relevant, exclude the const tensors
1148 RegisterInputSlots(layer, {node.input(0)});
1149
1150 // register the output connection slots for the layer, connections are made after all layers have been created
1151 RegisterOutputSlots(layer, {node.output(0)});
1152}
1153
Kevin Mayef33cb12021-01-29 14:24:57 +00001154void OnnxParserImpl::ParseClip(const onnx::NodeProto& node)
Finn Williams7ee5d2c2020-03-27 11:11:50 +00001155{
1156 ParseActivation(node, ActivationFunction::BoundedReLu);
1157}
1158
Kevin Mayef33cb12021-01-29 14:24:57 +00001159void OnnxParserImpl::ParseSigmoid(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001160{
1161 ParseActivation(node, ActivationFunction::Sigmoid);
1162}
1163
Kevin Mayef33cb12021-01-29 14:24:57 +00001164void OnnxParserImpl::ParseTanh(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001165{
1166 ParseActivation(node, ActivationFunction::TanH);
1167}
1168
Kevin Mayef33cb12021-01-29 14:24:57 +00001169void OnnxParserImpl::ParseRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001170{
1171 ParseActivation(node, ActivationFunction::ReLu);
1172}
1173
Kevin Mayef33cb12021-01-29 14:24:57 +00001174void OnnxParserImpl::ParseLeakyRelu(const onnx::NodeProto& node)
Tee Jung7ff9a602019-11-01 07:04:42 +00001175{
1176 ParseActivation(node, ActivationFunction::LeakyReLu);
1177}
telsoa01c577f2c2018-08-31 09:22:23 +01001178
Kevin Mayef33cb12021-01-29 14:24:57 +00001179void OnnxParserImpl::ParseAdd(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001180{
Ryan OSheaed27ee72020-04-22 16:37:29 +01001181 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1182 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
telsoa01c577f2c2018-08-31 09:22:23 +01001183
Ryan OSheaed27ee72020-04-22 16:37:29 +01001184 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
telsoa01c577f2c2018-08-31 09:22:23 +01001185
Ryan OSheaed27ee72020-04-22 16:37:29 +01001186 // TODO: unify broadcast validation code across layers
1187 // tracked by: IVGCVSW-1576
telsoa01c577f2c2018-08-31 09:22:23 +01001188
Ryan OSheaed27ee72020-04-22 16:37:29 +01001189 // Checking broadcast compatibility : only scalar or 1D tensors
1190 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1191 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1192 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1193 ARMNN_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1194
1195 unsigned int numDims = input0.GetNumDimensions();
1196 for (unsigned int i = 0; i < numDims; i++)
telsoa01c577f2c2018-08-31 09:22:23 +01001197 {
Ryan OSheaed27ee72020-04-22 16:37:29 +01001198 unsigned int dim0 = input0.GetShape()[i];
1199 unsigned int dim1 = input1.GetShape()[i];
1200 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
telsoa01c577f2c2018-08-31 09:22:23 +01001201 {
James Ward58dec6b2020-09-11 17:32:44 +01001202 throw ParseException(
1203 fmt::format("Broadcast is only supported for scalar or 1D tensors in Add node '{}'. "
1204 "Input dimensions should either match or one should be of size 1 and here, "
1205 "{} and {} {}",
1206 node.name(),
1207 TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1208 m_TensorsInfo[inputs.first].m_dtype),
1209 TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1210 m_TensorsInfo[inputs.second].m_dtype),
1211 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001212 }
telsoa01c577f2c2018-08-31 09:22:23 +01001213 }
Ryan OSheaed27ee72020-04-22 16:37:29 +01001214
1215
1216 IConnectableLayer* layer = m_Network->AddAdditionLayer(node.name().c_str());
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001217 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001218
1219 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
Ryan OSheaed27ee72020-04-22 16:37:29 +01001220 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1221 m_TensorsInfo[inputs.second].m_info->GetShape() });
telsoa01c577f2c2018-08-31 09:22:23 +01001222 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1223
Ryan OSheaed27ee72020-04-22 16:37:29 +01001224 // register the input connection -> for constant inputs, we need to make a newDim constant layer
1225 if(m_TensorsInfo[inputs.first].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001226 CreateConstantLayer(inputs.first, fmt::format("Add:constant_of_{}", node.input(0)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001227 }
1228 if(m_TensorsInfo[inputs.second].isConstant()) {
James Ward58dec6b2020-09-11 17:32:44 +01001229 CreateConstantLayer(inputs.second, fmt::format("Add:constant_of_{}", node.input(1)));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001230 }
1231 RegisterInputSlots(layer, {inputs.first, inputs.second});
telsoa01c577f2c2018-08-31 09:22:23 +01001232
Ryan OSheaed27ee72020-04-22 16:37:29 +01001233 // register the output connection
telsoa01c577f2c2018-08-31 09:22:23 +01001234 RegisterOutputSlots(layer, {node.output(0)});
1235}
1236
Kevin Mayef33cb12021-01-29 14:24:57 +00001237void OnnxParserImpl::ParseAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001238{
1239 Pooling2dDescriptor desc;
1240 desc.m_PoolType = PoolingAlgorithm::Average;
1241
1242 uint32_t count_include_pad = 0;
1243 count_include_pad = ReadOptionalNodeUint32Attribute(node, "count_include_pad");
1244 if(count_include_pad) {
1245 desc.m_PaddingMethod = PaddingMethod::IgnoreValue;
1246 }
1247 AddPoolingLayer(node, desc);
1248}
1249
Kevin Mayef33cb12021-01-29 14:24:57 +00001250void OnnxParserImpl::ParseBatchNormalization(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001251{
1252 //IGNORE momentum parameter and spatial parameters
1253
1254 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 5);
1255 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1256
1257 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1258 for(int ind = 1; ind < node.input_size(); ++ind)
1259 {
1260 auto tensor = node.input(ind);
1261 if(! m_TensorsInfo[tensor].isConstant())
1262 {
James Ward58dec6b2020-09-11 17:32:44 +01001263 throw ParseException(
1264 fmt::format("Input tensor '{}' should be constant in BatchNormalization node '{}' {}",
1265 tensor,
1266 node.name(),
1267 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001268 }
1269 }
1270
1271 float epsilon = ReadOptionalNodeFloatAttribute(node, "epsilon", 1e-5f);
1272 BatchNormalizationDescriptor desc;
1273 desc.m_Eps = epsilon;
1274
1275 auto scaleTensor = CreateConstTensor(node.input(1));
1276 auto biasTensor = CreateConstTensor(node.input(2));
1277 auto meanTensor = CreateConstTensor(node.input(3));
1278 auto varTensor = CreateConstTensor(node.input(4));
1279
1280 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1281 meanTensor.first,
1282 varTensor.first,
1283 biasTensor.first,
1284 scaleTensor.first,
1285 node.name().c_str());
1286 ARMNN_ASSERT(layer != nullptr);
1287
1288 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1289 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1290
1291 RegisterInputSlots(layer, {node.input(0)}); //don't register constant inputs
1292
1293 // register the output connection
1294 RegisterOutputSlots(layer, {node.output(0)});
1295}
1296
Kevin Mayef33cb12021-01-29 14:24:57 +00001297void OnnxParserImpl::ParseConstant(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001298{
1299 CHECK_VALID_SIZE(static_cast<size_t>(node.attribute_size()), 1);
1300 if (!node.attribute(0).has_t())
1301 {
James Ward58dec6b2020-09-11 17:32:44 +01001302 throw ParseException(fmt::format("Value not found for Constant node '{}' {}",
1303 node.name(),
1304 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001305 }
1306 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
1307
1308 //ONNX can have Float16 and double constant nodes but ArmNN only supports float32
1309 CHECK_VALID_DATATYPE(node.name(), onnxTensor.name(),
1310 static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type()), onnx::TensorProto::FLOAT);
1311
1312 //Register this as a m_ConstParam so we know we can use it as a constant param in future layers.
1313 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
1314 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(ToTensorInfo(onnxTensor));
1315 m_TensorsInfo[node.output(0)].m_dtype = static_cast<onnx::TensorProto::DataType>(onnxTensor.data_type());
1316
1317 CreateConstantLayer(node.output(0), node.name());
1318}
1319
Kevin Mayef33cb12021-01-29 14:24:57 +00001320void OnnxParserImpl::ParseConv(const onnx::NodeProto& node)
telsoa01c577f2c2018-08-31 09:22:23 +01001321{
1322 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2, 3); //input, weight, (bias)
1323 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1324
1325 VALID_INPUTS(node, STR_LIST(onnx::TensorProto::FLOAT));
1326
1327 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1328 {
James Ward58dec6b2020-09-11 17:32:44 +01001329 throw ParseException(
1330 fmt::format("ArmNN only supports 2D convolution and Conv layer '{}' input {} {}",
1331 node.name(),
1332 TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1333 m_TensorsInfo[node.input(0)].m_dtype),
1334 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001335 }
1336
1337 if(!m_TensorsInfo[node.input(1)].isConstant())
1338 {
James Ward58dec6b2020-09-11 17:32:44 +01001339 throw ParseException(
1340 fmt::format("Weights '{}' should be constant in Conv layer '{}' {}",
1341 node.input(1),
1342 node.name(),
1343 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001344 }
1345
1346 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1347
telsoa01c577f2c2018-08-31 09:22:23 +01001348 Convolution2dDescriptor desc;
1349 desc.m_BiasEnabled = false;
1350
1351 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node, "strides");
1352 if(strides.empty())
1353 {
1354 desc.m_StrideX = 1;
1355 desc.m_StrideY = 1;
1356 }
1357 else
1358 {
1359 desc.m_StrideX = strides[1];
1360 desc.m_StrideY = strides[0];
1361 }
1362
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001363 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node, "dilations");
1364 if(!dilations.empty())
1365 {
1366 desc.m_DilationX = dilations[1];
1367 desc.m_DilationY = dilations[0];
1368 }
1369
telsoa01c577f2c2018-08-31 09:22:23 +01001370 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node, "pads");
1371 //Check new padding version first
1372 if(pads.empty())
1373 {
1374 //Check deprecated version
1375 std::string paddingString = ReadOptionalNodeStringAttribute(node, "auto_pad");
1376 if(paddingString != "VALID" && paddingString != "" && paddingString != "NOTSET")
1377 {
1378 bool isUpper;
1379 if( paddingString == "SAME_LOWER")
1380 {
1381 isUpper = false;
1382 }
1383 else if (paddingString == "SAME_UPPER")
1384 {
1385 isUpper = true;
1386 }
1387 else
1388 {
James Ward58dec6b2020-09-11 17:32:44 +01001389 throw ParseException(
1390 fmt::format("Invalid auto_pad attribute for node {}. Only SAME_UPPER, SAME_LOWER or VALID "
1391 "supported and found {} {}",
1392 node.name(),
1393 paddingString,
1394 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001395 }
1396 uint32_t inputHeight = inputInfo.GetShape()[2];
1397 uint32_t inputWidth = inputInfo.GetShape()[3];
1398
1399 uint32_t weightHeight;
1400 uint32_t weightWidth;
1401 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node, "kernel_shape");
1402 if (kernel_shape.empty())
1403 {
1404 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1405 weightHeight = weightTensorInfo.GetShape()[2];
1406 weightWidth = weightTensorInfo.GetShape()[3];
1407 }
1408 else
1409 {
1410 weightHeight = kernel_shape[0];
1411 weightWidth = kernel_shape[1];
1412 }
Sadik Armagan60bb9d82021-01-11 15:15:01 +00001413 CalcPadding(inputHeight,
1414 weightHeight,
1415 desc.m_StrideY,
1416 desc.m_DilationY,
1417 &desc.m_PadTop,
1418 &desc.m_PadBottom,
1419 isUpper);
1420 CalcPadding(inputWidth,
1421 weightWidth,
1422 desc.m_StrideX,
1423 desc.m_DilationX,
1424 &desc.m_PadLeft,
1425 &desc.m_PadRight,
1426 isUpper);
telsoa01c577f2c2018-08-31 09:22:23 +01001427 }
1428 }
1429 else
1430 {
1431 desc.m_PadTop = pads[0];
1432 desc.m_PadLeft = pads[1];
1433 desc.m_PadBottom = pads[2];
1434 desc.m_PadRight = pads[3];
1435 }
1436
1437 uint32_t group = ReadOptionalNodeUint32Attribute(node, "group", 1);
1438 if(group > 1)
1439 {
1440 if (group > inputInfo.GetShape()[1])
1441 {
1442 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001443 fmt::format("Error parsing Convolution node: {}. "
1444 "The 'group'={} parameter cannot be larger than the "
1445 "channel of the input shape={} (in NCHW format). {}",
1446 node.name(),
1447 group,
1448 inputInfo.GetShape()[1],
1449 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001450 }
1451 else if (group == inputInfo.GetShape()[1])
1452 {
1453 // we use a depthwise convolution here, because the number of groups equals to the
1454 // input channels
1455 AddConvLayerWithDepthwiseConv(node, desc);
1456 return;
1457 }
1458 else
1459 {
1460 // TODO: split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +01001461 // and concatenate the results afterwards
James Ward58dec6b2020-09-11 17:32:44 +01001462 throw ParseException(fmt::format("Error parsing Convolution node: {}. "
1463 "The 'group'={} parameter should be 1 or be equal to the "
1464 "channel of the input shape={} (in NCHW format). {}",
1465 node.name(),
1466 group,
1467 inputInfo.GetShape()[1],
1468 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001469 }
1470 }
1471
1472 armnn::IConnectableLayer* layer;
1473 auto weightTensor = CreateConstTensor(node.input(1));
1474
1475 if (node.input_size() == 3)
1476 {
1477 if(!m_TensorsInfo[node.input(2)].isConstant())
1478 {
James Ward58dec6b2020-09-11 17:32:44 +01001479 throw ParseException(fmt::format("Bias '{}' should be constant in Conv layer '{}' {}",
1480 node.input(2),
1481 node.name(),
1482 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001483 }
1484 desc.m_BiasEnabled = true;
1485 auto biasTensor = CreateConstTensor(node.input(2));
1486 layer = m_Network->AddConvolution2dLayer(desc,
1487 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001488 Optional<ConstTensor>(biasTensor.first),
telsoa01c577f2c2018-08-31 09:22:23 +01001489 node.name().c_str());
1490 }
1491 else
1492 {
1493 layer = m_Network->AddConvolution2dLayer(desc,
1494 weightTensor.first,
Matteo Martincighfc598e12019-05-14 10:36:13 +01001495 EmptyOptional(),
telsoa01c577f2c2018-08-31 09:22:23 +01001496 node.name().c_str());
1497 }
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001498 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001499
1500 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1501 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1502 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1503 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1504
1505 // register the input connection slots for the layer, connections are made after all layers have been created
1506 // only the tensors for the inputs are relevant, exclude the const tensors
1507 RegisterInputSlots(layer, {node.input(0)});
1508
1509 // register the output connection slots for the layer, connections are made after all layers have been created
1510 RegisterOutputSlots(layer, {node.output(0)});
1511}
1512
Kevin Mayef33cb12021-01-29 14:24:57 +00001513void OnnxParserImpl::ParseFlatten(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001514{
1515 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 1);
1516 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1517
1518 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1519 m_TensorsInfo[node.input(0)].m_dtype,
1520 onnx::TensorProto::FLOAT);
1521
1522 int64_t axis = ReadOptionalNodeInt64Attribute(node, "axis", 1);
1523 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1524
1525 /// Negative axis conversion
1526 if (axis < 0)
1527 {
1528 axis += inputShape.GetNumDimensions();
1529 }
1530
1531 /// Check Axis is within dimensions
1532 if (axis < 0 || axis >= inputShape.GetNumDimensions())
1533 {
James Ward58dec6b2020-09-11 17:32:44 +01001534 throw ParseException(fmt::format("Axis '{}' invalid. Tensor has '{}' dimensions in FlattenLayer '{}'",
1535 axis, inputShape.GetNumDimensions(), node.name()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001536 }
1537
1538 /// If axis chosen is 0 dimension1 will always be 1 in output , default dimension2 to 1 because 0 is invalid
1539 uint dimension1{1};
1540 uint dimension2{1};
1541 uint i{0};
1542
1543 /// dimension1 = (d_0 * d_1 ... d_(axis-1))
1544 for (i = 0; i < axis; i++){
1545 dimension1 *= inputShape[i];
1546 }
1547
1548 /// dimension2 = (d_axis * d_(axis+1) ... d_n)
1549 for (i = static_cast<uint>(axis); i < inputShape.GetNumDimensions(); i++){
1550 dimension2 *= inputShape[i];
1551 }
1552
1553 TensorShape outputShape{dimension1, dimension2};
1554
1555 auto outInfo = ComputeReshapeInfo(outputShape, inputShape, node.output(0));
1556 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1557 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1558}
1559
Kevin Mayef33cb12021-01-29 14:24:57 +00001560void OnnxParserImpl::ParseGlobalAveragePool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001561{
1562 Pooling2dDescriptor desc = Pooling2dDescriptor();
1563 desc.m_PoolType = PoolingAlgorithm::Average;
1564
1565 //kernel size is the same as input
1566 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1567 desc.m_PoolWidth = inputShape[3];
1568 desc.m_PoolHeight = inputShape[2];
1569
1570 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1571 ARMNN_ASSERT(layer != nullptr);
1572
1573 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
1574 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1575
1576 // register the input connection slots for the layer, connections are made after all layers have been created
1577 // only the tensors for the inputs are relevant, exclude the const tensors
1578 RegisterInputSlots(layer, {node.input(0)});
1579
1580 // register the output connection slots for the layer, connections are made after all layers have been created
1581 RegisterOutputSlots(layer, {node.output(0)});
1582}
1583
Kevin Mayef33cb12021-01-29 14:24:57 +00001584void OnnxParserImpl::ParseMaxPool(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001585{
1586 Pooling2dDescriptor desc;
1587 desc.m_PoolType = PoolingAlgorithm::Max;
1588 desc.m_PaddingMethod = PaddingMethod::Exclude;
1589 AddPoolingLayer(node, desc);
1590}
1591
Kevin Mayef33cb12021-01-29 14:24:57 +00001592void OnnxParserImpl::ParseReshape(const onnx::NodeProto& node)
Ryan OSheaed27ee72020-04-22 16:37:29 +01001593{
1594 CHECK_VALID_SIZE(static_cast<size_t>(node.input_size()), 2);
1595 CHECK_VALID_SIZE(static_cast<size_t>(node.output_size()), 1);
1596
1597 CHECK_VALID_DATATYPE(node.name(), node.input(0),
1598 m_TensorsInfo[node.input(0)].m_dtype,
1599 onnx::TensorProto::FLOAT); //input
1600 CHECK_VALID_DATATYPE(node.name(), node.input(1),
1601 m_TensorsInfo[node.input(1)].m_dtype,
1602 onnx::TensorProto::INT64); //shape
1603
1604 if(!m_TensorsInfo[node.input(1)].isConstant())
1605 {
James Ward58dec6b2020-09-11 17:32:44 +01001606 throw ParseException(fmt::format("Shape '{}' should be constant in Reshape layer '{}' {}",
1607 node.input(1),
1608 node.name(),
1609 CHECK_LOCATION().AsString()));
Ryan OSheaed27ee72020-04-22 16:37:29 +01001610 }
1611
1612 if(m_TensorsInfo[node.input(0)].isConstant())
1613 {
1614 //make a new cst tensor -> move the data to the output tensor (the shape is already good in the output tensor)
1615 if(m_TensorsInfo.count(node.output(0)) == 0)
1616 {
1617 m_TensorsInfo[node.output(0)] = OnnxTensor();
1618 }
1619 m_TensorsInfo[node.output(0)].m_tensor =
1620 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1621 }
1622 else
1623 {
1624 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1625
1626 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info == nullptr)
1627 {
1628 uint64_t dims = static_cast<uint64_t>(m_TensorsInfo[node.input(1)].m_tensor->int64_data_size());
1629 TensorShape targetShape{static_cast<unsigned int>(dims), 1};
1630
1631 for(uint i = 0; i < dims; i++)
1632 {
1633 int val = CHECKED_INT32(m_TensorsInfo[node.input(1)].m_tensor->int64_data(static_cast<int>(i)));
1634 targetShape[i]= static_cast<unsigned int>(val);
1635 }
1636
1637 auto outInfo = ComputeReshapeInfo(targetShape, inputShape, node.output(0));
1638 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1639 }
1640
1641 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1642 }
1643}
1644
Kevin Mayef33cb12021-01-29 14:24:57 +00001645void OnnxParserImpl::PrependForBroadcast(const std::string& outputName,
1646 const std::string& input0,
1647 const std::string& input1)
telsoa01c577f2c2018-08-31 09:22:23 +01001648{
1649 //input0 should be reshaped to have same number of dim as input1
1650 TensorInfo outputTensorInfo = TensorInfo(*m_TensorsInfo[input0].m_info);
1651
1652 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1653 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1654
1655 uint32_t diff = input1Shape.GetNumDimensions() - input0Shape.GetNumDimensions();
1656 std::vector<uint32_t> newShape;
1657 while(diff > 0)
1658 {
1659 newShape.push_back(1);
1660 diff--;
1661 }
1662 for (uint dim = 0; dim < input0Shape.GetNumDimensions(); ++dim)
1663 {
1664 newShape.push_back(input0Shape[dim]);
1665 }
1666 outputTensorInfo.SetShape(TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1667
1668 //add the new tensor to m_TensorsInfo
1669 m_TensorsInfo[outputName] = OnnxTensor();
1670 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1671
1672 //add reshape layer if the parent was not constant...
1673 if( ! m_TensorsInfo[input0].isConstant())
1674 {
James Ward58dec6b2020-09-11 17:32:44 +01001675 CreateReshapeLayer(input0, outputName, fmt::format("Add:reshapeOf{}", input0));
telsoa01c577f2c2018-08-31 09:22:23 +01001676 }
1677 else //make it constant and it will be create in Add
1678 {
1679 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1680
1681 }
1682}
1683
Kevin Mayef33cb12021-01-29 14:24:57 +00001684void OnnxParserImpl::SetupInputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01001685{
1686 //Find user input and add their layers
1687 for(int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1688 {
1689 auto input = m_Graph->input(inputIndex);
1690 if (! m_TensorsInfo[input.name()].isConstant())
1691 {
1692 IConnectableLayer* layer =
1693 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
1694 auto tensorInfo = ToTensorInfo(input);
1695 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1696
1697 RegisterOutputSlots(layer,{ input.name() });
1698 }
1699 }
1700}
1701
Kevin Mayef33cb12021-01-29 14:24:57 +00001702void OnnxParserImpl::SetupOutputLayers()
telsoa01c577f2c2018-08-31 09:22:23 +01001703{
1704 if(m_Graph->output_size() == 0)
1705 {
James Ward58dec6b2020-09-11 17:32:44 +01001706 throw ParseException(fmt::format("The given model does not have any outputs {}", CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001707 }
1708
1709 for(int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
1710 {
1711 IConnectableLayer* layer =
1712 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
1713 m_Graph->output(outputIndex).name().c_str());
1714
1715 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
1716 }
1717}
1718
Kevin Mayef33cb12021-01-29 14:24:57 +00001719void OnnxParserImpl::RegisterInputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01001720{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001721 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001722 if (tensorIds.size() != layer->GetNumInputSlots())
1723 {
1724 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001725 fmt::format("The number of tensor inputs ({}) does not match the number expected ({}) {}",
1726 tensorIds.size(),
1727 layer->GetNumInputSlots(),
1728 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001729 }
1730 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumInputSlots(); ++slotIndex)
1731 {
1732 std::string tensorId = tensorIds[slotIndex];
1733 armnn::IInputSlot* slot = &(layer->GetInputSlot(slotIndex));
1734
1735 auto it = m_TensorConnections.find(tensorId);
1736
1737 if (it == m_TensorConnections.end())
1738 {
1739 //First time seing this tensor, we need to map it
1740 m_TensorConnections[tensorId] = TensorSlots();
1741 }
1742 m_TensorConnections[tensorId].inputSlots.push_back(slot);
1743 }
1744}
1745
Kevin Mayef33cb12021-01-29 14:24:57 +00001746void OnnxParserImpl::RegisterOutputSlots(IConnectableLayer* layer, const std::vector<std::string>& tensorIds)
telsoa01c577f2c2018-08-31 09:22:23 +01001747{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +01001748 ARMNN_ASSERT(layer != nullptr);
telsoa01c577f2c2018-08-31 09:22:23 +01001749 if (tensorIds.size() != layer->GetNumOutputSlots())
1750 {
1751 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001752 fmt::format("The number of tensor outputs ({}) does not match the number expected ({}) {} ",
1753 tensorIds.size(),
1754 layer->GetNumOutputSlots(),
1755 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001756 }
1757
1758 for (unsigned int slotIndex = 0; slotIndex < layer->GetNumOutputSlots(); ++slotIndex)
1759 {
1760 std::string tensorId = tensorIds[slotIndex];
1761 armnn::IOutputSlot* slot = &(layer->GetOutputSlot(slotIndex));
1762
1763 auto it = m_TensorConnections.find(tensorId);
1764
1765 if (it == m_TensorConnections.end())
1766 {
1767 //First time seing this tensor, we need to map it
1768 m_TensorConnections[tensorId] = TensorSlots();
1769 }
1770
Ryan OShea337c17f2020-02-21 12:33:17 +00001771 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
telsoa01c577f2c2018-08-31 09:22:23 +01001772
1773 // assuming there is only one producer for that tensor
1774 if (tensorSlots.outputSlot != nullptr)
1775 {
James Ward58dec6b2020-09-11 17:32:44 +01001776 throw ParseException(fmt::format("Another layer has already registered itself as the producer of "
1777 "tensor:{} {}",
1778 tensorId,
1779 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001780 }
1781 tensorSlots.outputSlot = slot;
1782 }
1783}
1784
Kevin Mayef33cb12021-01-29 14:24:57 +00001785BindingPointInfo OnnxParserImpl::GetNetworkInputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01001786{
1787 for(int i = 0; i < m_Graph->input_size(); ++i)
1788 {
1789 auto input = m_Graph->input(i);
1790 if(input.name() == name)
1791 {
1792 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(input));
1793 }
1794 }
James Ward58dec6b2020-09-11 17:32:44 +01001795 throw InvalidArgumentException(fmt::format("The input layer '{}' does not exist {}",
1796 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001797}
1798
Kevin Mayef33cb12021-01-29 14:24:57 +00001799BindingPointInfo OnnxParserImpl::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa01c577f2c2018-08-31 09:22:23 +01001800{
1801 for(int i = 0; i < m_Graph->output_size(); ++i)
1802 {
1803 auto output = m_Graph->output(i);
1804 if(output.name() == name)
1805 {
1806 return std::make_pair(static_cast<armnn::LayerBindingId>(i), ToTensorInfo(output));
1807 }
1808 }
James Ward58dec6b2020-09-11 17:32:44 +01001809 throw InvalidArgumentException(fmt::format("The output layer '{}' does not exist {}",
1810 name, CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001811}
1812
Kevin Mayef33cb12021-01-29 14:24:57 +00001813std::vector<std::string> OnnxParserImpl::GetInputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01001814{
1815 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01001816 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
1817 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001818 }
1819
1820 std::vector<std::string> inputNames;
1821 std::map<std::string, bool> isConstant;
1822 for(auto tensor : model->graph().initializer())
1823 {
1824 isConstant[tensor.name()] = true;
1825 }
1826 for(auto input : model->graph().input())
1827 {
1828 auto it = isConstant.find(input.name());
1829 if(it == isConstant.end())
1830 {
1831 inputNames.push_back(input.name());
1832 }
1833 }
1834 return inputNames;
1835}
1836
Kevin Mayef33cb12021-01-29 14:24:57 +00001837std::vector<std::string> OnnxParserImpl::GetOutputs(ModelPtr& model)
telsoa01c577f2c2018-08-31 09:22:23 +01001838{
1839 if(model == nullptr) {
James Ward58dec6b2020-09-11 17:32:44 +01001840 throw InvalidArgumentException(fmt::format("The given model cannot be null {}",
1841 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001842 }
1843
1844 std::vector<std::string> outputNames;
1845 for(auto output : model->graph().output())
1846 {
1847 outputNames.push_back(output.name());
1848 }
1849 return outputNames;
1850}
1851
1852} // namespace armnnOnnxParser