11 #include <boost/format.hpp> 12 #include <boost/numeric/conversion/cast.hpp> 14 #include <google/protobuf/text_format.h> 15 #include <google/protobuf/io/zero_copy_stream_impl.h> 19 using namespace armnn;
25 void CheckValidDataType(std::initializer_list<onnx::TensorProto::DataType> validInputTypes,
27 const char* validExpr,
29 std::string tensorName,
32 bool isValid = std::any_of(validInputTypes.begin(),
33 validInputTypes.end(),
39 boost::format(
"Datatype %1% is not valid for tensor '%2%' of node '%3%', not in {%4%}. %5%") %
40 onnx::TensorProto::DataType_Name(actualValue) %
48 #define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL, ...) \ 49 CheckValidDataType({__VA_ARGS__}, ACTUAL, #__VA_ARGS__, NODE, TENSOR, CHECK_LOCATION()) 51 using StrTypeListPair = std::pair<const char*, std::initializer_list<onnx::TensorProto::DataType>>;
52 #define STR_LIST(...) StrTypeListPair(#__VA_ARGS__, {__VA_ARGS__}) 54 template <
typename Callable>
55 void ReadMandatoryNodeAttributeImpl(
const onnx::NodeProto& node,
56 const std::string& attribName,
57 onnx::AttributeProto::AttributeType expectedType,
60 auto attribs = node.attribute();
62 while (attriNum < node.attribute_size())
64 if (attribs.Get(attriNum).name() == attribName)
66 if (attribs.Get(attriNum).type() == expectedType)
68 callable(attribs.Get(attriNum));
73 "Attribute %1% of node %2% expected to have %3% as onnx::AttributeProto::AttributeType, " 74 "but found %4% instead %5%")
77 % onnx::AttributeProto::AttributeType_Name(expectedType)
78 % onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type())
85 if (attriNum == node.attribute_size())
87 throw ParseException(boost::str(boost::format(
"Could not find required attribute %1% in node %2% %3%")
92 template <
typename Callable>
93 void ReadOptionalNodeAttributeImpl(
const onnx::NodeProto& node,
94 const std::string& attribName,
95 onnx::AttributeProto::AttributeType expectedType,
98 auto attribs = node.attribute();
99 for (
int attriNum = 0; attriNum < node.attribute_size(); ++attriNum)
101 if (attribs.Get(attriNum).name() == attribName)
103 if (attribs.Get(attriNum).type() == expectedType)
105 callable(attribs.Get(attriNum));
110 "Attribute %1% of node %2% expected to have %3% as onnx::AttributeProto::AttributeType, " 111 "but found %4% instead %5%")
114 % onnx::AttributeProto::AttributeType_Name(expectedType)
115 % onnx::AttributeProto::AttributeType_Name(attribs.Get(attriNum).type())
122 std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(
const onnx::NodeProto& node,
123 const std::string& name)
125 std::vector<uint32_t> attriList;
126 ReadMandatoryNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
127 [&attriList](
const onnx::AttributeProto& attrValue)
129 for (
int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
137 uint32_t ReadOptionalNodeUint32Attribute(
const onnx::NodeProto& node,
138 const std::string& name,
139 const uint32_t defaultVal = 0u)
141 uint32_t attribValue = defaultVal;
142 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INT,
143 [&attribValue](
const onnx::AttributeProto& attrValue)
150 std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(
const onnx::NodeProto& node,
151 const std::string& name)
153 std::vector<uint32_t> attriList;
154 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::INTS,
155 [&attriList](
const onnx::AttributeProto& attrValue)
157 for (
int attriNum = 0; attriNum < attrValue.ints_size(); ++attriNum)
166 float ReadOptionalNodeFloatAttribute(
const onnx::NodeProto& node,
167 const std::string& name,
168 const float defaultValue = 0.0f)
170 float attribValue = defaultValue;
171 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::FLOAT,
172 [&attribValue](
const onnx::AttributeProto& attrValue)
174 attribValue = attrValue.f();
179 std::string ReadOptionalNodeStringAttribute(
const onnx::NodeProto& node,
const std::string& name)
181 std::string attribValue =
"";
182 ReadOptionalNodeAttributeImpl(node, name, onnx::AttributeProto::STRING,
183 [&attribValue](
const onnx::AttributeProto& attrValue)
185 attribValue = attrValue.s();
195 case onnx::TensorProto::FLOAT:
200 case onnx::TensorProto::INT32:
201 case onnx::TensorProto::INT64:
210 boost::format(
"'%1%' is not a currently supported datatype for tensor %2%." 211 " Supported dataTypes are FLOAT, INT32 and INT64. %3%") %
212 onnx::TensorProto::DataType_Name(
213 static_cast<onnx::TensorProto::DataType>(data_type)) %
230 const onnx::TensorShapeProto onnxShape = info.type().tensor_type().shape();
231 std::vector<unsigned int> shapeDims;
232 for (
int i = 0; i < onnxShape.dim_size(); ++i)
237 if (shapeDims.empty())
239 shapeDims.push_back(1);
242 return ToTensorInfo(info.name(), shapeDims, info.type().tensor_type().elem_type());
247 std::vector<unsigned int> shapeDims;
249 for (
auto dim: tensor.dims())
254 if (shapeDims.empty())
256 shapeDims.push_back(1);
259 return ToTensorInfo(tensor.name(), shapeDims, tensor.data_type());
262 std::string TensorInfoAsString(
const TensorInfo& info,
263 const std::string& name,
267 std::stringstream ss;
268 ss <<
"tensor '" << name <<
"' contains " 269 << onnx::TensorProto::DataType_Name(type)
270 <<
" and has shape [";
274 ss << shape[i] <<
", ";
280 void CalcPadding(uint32_t inputSize, uint32_t filterSize, uint32_t stride, uint32_t* paddingFront,
281 uint32_t* paddingBack,
bool isUpper)
283 uint32_t outputSize = (inputSize + stride - 1) / stride;
284 uint32_t temp = (outputSize - 1) * stride + filterSize;
285 *paddingFront = (temp - inputSize) / 2;
286 *paddingBack = *paddingFront;
287 if((temp - inputSize) % 2 == 1)
300 TensorInfo ComputeReshapeInfo(
const onnx::TensorProto& targetShapeTensor,
302 const std::string& outName)
304 std::vector<int> targetDims;
305 for(
int i = 0; i < targetShapeTensor.int64_data_size(); ++i)
310 targetDims.push_back(static_cast<int>(inShape[static_cast<uint>(i)]));
314 targetDims.push_back(val);
318 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
319 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
320 if (stretchDim != targetDims.end())
322 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
324 std::stringstream ss;
326 for(uint i = 0; i < targetDims.size() - 1; ++i)
328 ss << targetDims[i] <<
", ";
330 ss << targetDims[targetDims.size() - 1] <<
" ]";
333 boost::format(
"Error during creation of reshaped tensor '%1%'. At most one component of shape can be " 334 " -1 and here, shape is %2% %3%")
340 auto targetNumElements =
boost::numeric_cast<
unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
341 -1, std::multiplies<int32_t>()));
342 auto stretchIndex =
static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
343 outDims[stretchIndex] = inShape.
GetNumElements() / targetNumElements;
351 const std::map<std::string, OnnxParser::OperationParsingFunction> OnnxParser::m_ParserFunctions = {
352 {
"BatchNormalization", &OnnxParser::ParseBatchNormalization},
353 {
"GlobalAveragePool", &OnnxParser::ParseGlobalAveragePool},
354 {
"AveragePool", &OnnxParser::ParseAveragePool },
355 {
"Constant", &OnnxParser::ParseConstant },
356 {
"MaxPool", &OnnxParser::ParseMaxPool },
357 {
"Reshape", &OnnxParser::ParseReshape },
358 {
"Sigmoid", &OnnxParser::ParseSigmoid },
359 {
"Tanh", &OnnxParser::ParseTanh },
360 {
"Relu", &OnnxParser::ParseRelu },
361 {
"LeakyRelu", &OnnxParser::ParseLeakyRelu },
362 {
"Conv", &OnnxParser::ParseConv },
363 {
"Add", &OnnxParser::ParseAdd },
366 template<
typename TypePair,
typename Location>
367 void OnnxParser::ValidateInputs(
const onnx::NodeProto& node,
368 TypePair validInputs,
369 const Location& location)
371 for(
auto input : node.input())
373 CheckValidDataType(validInputs.second,
374 m_TensorsInfo[input].m_dtype,
382 #define VALID_INPUTS(NODE, VALID_INPUTS) \ 383 OnnxParser::ValidateInputs(NODE, \ 387 std::vector<TensorInfo> OnnxParser::ComputeOutputInfo(std::vector<std::string> outNames,
389 std::vector<TensorShape> inputShapes)
391 BOOST_ASSERT(! outNames.empty());
392 bool needCompute = std::any_of(outNames.begin(),
394 [
this](std::string name)
396 return (m_TensorsInfo.count(name) == 0 || m_TensorsInfo[name].m_info ==
nullptr);
398 std::vector<TensorInfo> outInfo;
400 std::vector<TensorShape> inferredShapes;
404 BOOST_ASSERT(inferredShapes.size() == outNames.size());
406 for (uint i = 0; i < outNames.size(); ++i)
410 m_TensorsInfo[outNames[i]] = OnnxTensor();
411 m_TensorsInfo[outNames[i]].m_info = std::make_unique<TensorInfo>(
414 outInfo.push_back(*m_TensorsInfo[outNames[i]].m_info);
434 OnnxParser::OnnxParser()
435 : m_Network(nullptr, nullptr)
439 void OnnxParser::ResetParser()
445 void OnnxParser::Cleanup()
447 m_TensorConnections.clear();
448 m_TensorsInfo.clear();
449 m_OutputsMap.clear();
450 m_OutputsFusedAndUsed.clear();
453 std::pair<ConstTensor, std::unique_ptr<float[]>> OnnxParser::CreateConstTensor(
const std::string name)
455 const TensorInfo tensorInfo = *m_TensorsInfo[name].m_info;
456 onnx::TensorProto onnxTensor = *m_TensorsInfo[name].m_tensor;
458 auto srcData = onnxTensor.float_data().data();
459 std::unique_ptr<float[]> tensorData(
new float[tensorInfo.
GetNumElements()]);
460 const size_t tensorSizeInBytes = tensorInfo.
GetNumBytes();
462 if (!onnxTensor.has_raw_data())
464 if(tensorInfo.
GetNumElements() !=
static_cast<uint
>(onnxTensor.float_data_size()))
467 boost::format(
"The number of data provided (%1%) does not match the tensor '%2%' number of elements" 469 % onnxTensor.float_data_size()
474 ::memcpy(tensorData.get(), srcData, tensorSizeInBytes);
478 ::memcpy(tensorData.get(), onnxTensor.raw_data().c_str(), tensorSizeInBytes);
485 boost::format(
"No tensor data found for Const tensor '%1%' %2%")
489 return std::make_pair(
ConstTensor(tensorInfo, tensorData.get()), std::move(tensorData));
494 FILE* fd = fopen(graphFile,
"r");
499 boost::format(
"Invalid (null) filename %1%") %
CHECK_LOCATION().AsString()));
503 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
504 using google::protobuf::io::FileInputStream;
505 std::unique_ptr<FileInputStream> input = std::make_unique<FileInputStream>(fileno(fd));
506 bool success = google::protobuf::TextFormat::Parse(input.get(), modelProto.get());
511 std::stringstream
error;
512 error <<
"Failed to parse graph file";
514 boost::format(
"%1% %2%") % error.str() %
CHECK_LOCATION().AsString()));
523 return CreateNetworkFromModel(*modelProto);
529 FILE* fd = fopen(graphFile,
"rb");
534 boost::format(
"Invalid (null) filename %1%") %
CHECK_LOCATION().AsString()));
538 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
540 google::protobuf::io::FileInputStream inStream(fileno(fd));
541 google::protobuf::io::CodedInputStream codedStream(&inStream);
542 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
543 bool success = modelProto.get()->ParseFromCodedStream(&codedStream);
548 std::stringstream
error;
549 error <<
"Failed to parse graph file";
551 boost::format(
"%1% %2%") % error.str() %
CHECK_LOCATION().AsString()));
561 return CreateNetworkFromModel(*modelProto);
569 boost::format(
"Invalid (empty) string for model parameter %1%") %
CHECK_LOCATION().AsString()));
572 ModelPtr modelProto = std::make_unique<onnx::ModelProto>();
573 bool success = google::protobuf::TextFormat::ParseFromString(protoText, modelProto.get());
576 std::stringstream
error;
577 error <<
"Failed to parse graph file";
579 boost::format(
"%1% %2%") % error.str() %
CHECK_LOCATION().AsString()));
588 return CreateNetworkFromModel(*modelProto);
591 INetworkPtr OnnxParser::CreateNetworkFromModel(onnx::ModelProto& model)
593 m_Network = INetwork::Create();
596 m_Graph = std::make_unique<onnx::GraphProto>(*model.mutable_graph());
605 return std::move(m_Network);
608 void OnnxParser::LoadGraph()
610 BOOST_ASSERT(m_Graph.get() !=
nullptr);
613 SetupInfo(m_Graph->mutable_output());
614 SetupInfo(m_Graph->mutable_input());
615 SetupInfo(m_Graph->mutable_value_info());
617 for (
auto tensor : m_Graph->initializer())
619 m_TensorsInfo[tensor.name()].m_tensor = std::make_unique<const onnx::TensorProto>(tensor);
620 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(
ToTensorInfo(tensor));
621 m_TensorsInfo[tensor.name()].m_dtype =
629 DetectFullyConnected();
632 for(
size_t nodeIndex = 0; nodeIndex < static_cast<size_t>(m_Graph->node_size()); nodeIndex++)
634 auto node = m_Graph->node(static_cast<int>(nodeIndex));
635 const std::string& operation = node.op_type();
638 if (operation ==
"MatMul" )
640 if(m_OutputsFusedAndUsed[nodeIndex].inputForNodes != m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.size())
643 AddFullyConnected(node);
646 else if (!(m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty()) && operation ==
"Add")
648 int matmulIndex =
static_cast<int> (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes[0]);
649 AddFullyConnected(m_Graph->node(matmulIndex), &node);
651 else if (m_OutputsFusedAndUsed[nodeIndex].fusedWithNodes.empty())
653 auto it = m_ParserFunctions.find(operation);
654 if (it != m_ParserFunctions.end())
656 auto func = it->second;
662 boost::format(
"Unsupported operation %1% for node '%2%' %3%")
671 for (
const auto& tensorCon : m_TensorConnections)
673 if (tensorCon.second.outputSlot !=
nullptr)
675 for (
size_t inputSlotIdx = 0; inputSlotIdx < tensorCon.second.inputSlots.size(); ++inputSlotIdx)
677 tensorCon.second.outputSlot->Connect(*(tensorCon.second.inputSlots[inputSlotIdx]));
683 void OnnxParser::SetupInfo(
const google::protobuf::RepeatedPtrField<onnx::ValueInfoProto >* list)
685 for (
auto tensor : *list)
687 m_TensorsInfo[tensor.name()] = OnnxTensor();
688 m_TensorsInfo[tensor.name()].m_info = std::make_unique<TensorInfo>(
ToTensorInfo(tensor));
689 m_TensorsInfo[tensor.name()].m_dtype =
694 void OnnxParser::DetectFullyConnected()
696 m_OutputsFusedAndUsed = std::vector<UsageSummary> (
static_cast<size_t>(m_Graph->node_size()), UsageSummary());
697 auto matmulAndConstant = [&](
const std::string& constInput,
698 const std::string& matmulInput,
701 auto matmulIt = m_OutputsMap.find(matmulInput);
702 if(matmulIt != m_OutputsMap.end() && matmulIt->second.first->op_type() ==
"MatMul" 703 && m_TensorsInfo[constInput].isConstant())
705 nodeIndex = matmulIt->second.second;
711 for(
int nodeIndex = 0; nodeIndex < m_Graph->node_size(); nodeIndex++)
713 const onnx::NodeProto* node = &m_Graph->node(nodeIndex);
714 for (
const std::string& output : node->output())
716 m_OutputsMap[output] = std::make_pair(node, nodeIndex);
719 for (
const std::string& input : node->input())
721 auto matmulIt = m_OutputsMap.find(input);
722 if(matmulIt != m_OutputsMap.end()){
723 ++m_OutputsFusedAndUsed[
static_cast<size_t>(matmulIt->second.second)].inputForNodes;
727 if (node->op_type() ==
"Add")
730 if (matmulAndConstant(node->input(0), node->input(1), matmulIndex) ||
731 matmulAndConstant(node->input(1), node->input(0), matmulIndex))
734 m_OutputsFusedAndUsed[
static_cast<size_t>(matmulIndex)].fusedWithNodes
735 .push_back(static_cast<size_t>(nodeIndex));
737 m_OutputsFusedAndUsed[
static_cast<size_t>(nodeIndex)].fusedWithNodes
738 .push_back(static_cast<size_t>(matmulIndex));
743 for (
auto output: m_Graph->output()) {
744 auto matmulIt = m_OutputsMap.find(output.name());
745 if(matmulIt != m_OutputsMap.end()){
746 ++m_OutputsFusedAndUsed[
static_cast<size_t>(matmulIt->second.second)].inputForNodes;
751 template<
typename Location>
752 void OnnxParser::GetInputAndParam(
const onnx::NodeProto& node,
753 std::string* inputName,
754 std::string* constName,
755 const Location& location)
758 if (m_TensorsInfo[node.input(0)].isConstant())
762 else if (m_TensorsInfo[node.input(1)].isConstant())
769 boost::format(
"One of the input tensors ('%1%' or '%2%') should be constant in node '%3%' %4%")
773 % location.AsString()));
777 *constName = node.input(cstIndex);
781 *inputName = node.input(!cstIndex);
785 template<
typename Location>
786 void OnnxParser::To1DTensor(
const std::string& name,
const Location& location)
788 TensorShape shape = m_TensorsInfo[name].m_info->GetShape();
789 std::vector<uint32_t> newShape;
795 boost::format(
"Only tensors with shape [1, ..., 1, X] can be converted to 1D and %1% %2%")
796 % TensorInfoAsString(*m_TensorsInfo[name].m_info, name, m_TensorsInfo[name].m_dtype)
797 % location.AsString()));
802 m_TensorsInfo[name].m_info->SetShape(
TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
805 void OnnxParser::AddFullyConnected(
const onnx::NodeProto& matmulNode,
const onnx::NodeProto* addNode)
809 std::string weightName;
810 std::string inputName;
815 GetInputAndParam(matmulNode, &inputName, &weightName,
CHECK_LOCATION());
824 std::string biasName;
833 TensorInfo weightInfo = *m_TensorsInfo[weightName].m_info;
834 TensorInfo biasInfo = *m_TensorsInfo[biasName].m_info;
839 boost::format(
"Shape of weights '%1%' and bias of following Add node '%2%' do not match : %3%" 840 " and %4% ( /!\\ bias should be a 1D tensor) %5%")
843 % TensorInfoAsString(*m_TensorsInfo[weightName].m_info,
845 m_TensorsInfo[weightName].m_dtype)
846 % TensorInfoAsString(*m_TensorsInfo[biasName].m_info, biasName,
847 m_TensorsInfo[biasName].m_dtype )
850 layer = m_Network->AddFullyConnectedLayer(desc,
851 CreateConstTensor(weightName).first,
853 matmulNode.name().c_str());
854 BOOST_ASSERT(layer !=
nullptr);
856 auto outputInfo = ComputeOutputInfo({addNode->output(0)}, layer,
857 {m_TensorsInfo[inputName].m_info->GetShape(),
858 m_TensorsInfo[weightName].m_info->GetShape()});
862 RegisterInputSlots(layer, {inputName});
863 RegisterOutputSlots(layer, {addNode->output(0)});
867 layer = m_Network->AddFullyConnectedLayer(desc,
868 CreateConstTensor(weightName).first,
870 matmulNode.name().c_str());
871 BOOST_ASSERT(layer !=
nullptr);
873 auto outputInfo = ComputeOutputInfo({matmulNode.output(0)}, layer,
874 {m_TensorsInfo[inputName].m_info->GetShape(),
875 m_TensorsInfo[weightName].m_info->GetShape()});
878 RegisterInputSlots(layer, {inputName});
879 RegisterOutputSlots(layer, {matmulNode.output(0)});
883 void OnnxParser::CreateConstantLayer(
const std::string& tensorName,
const std::string& layerName)
885 auto armnnTensor = CreateConstTensor(tensorName);
887 IConnectableLayer* layer = m_Network->AddConstantLayer(armnnTensor.first, layerName.c_str());
889 RegisterOutputSlots(layer, {tensorName});
892 void OnnxParser::ParseConstant(
const onnx::NodeProto& node)
895 if (!node.attribute(0).has_t())
898 boost::format(
"Value not found for Constant node '%1%' %2%")
902 const onnx::TensorProto& onnxTensor = node.attribute(0).t();
909 m_TensorsInfo[node.output(0)].m_tensor = std::make_unique<const onnx::TensorProto>(onnxTensor);
910 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(
ToTensorInfo(onnxTensor));
913 CreateConstantLayer(node.output(0), node.name());
916 void OnnxParser::ParseMaxPool(
const onnx::NodeProto& node)
921 AddPoolingLayer(node, desc);
924 void OnnxParser::ParseGlobalAveragePool(
const onnx::NodeProto& node)
930 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
934 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
935 BOOST_ASSERT(layer !=
nullptr);
937 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {inputShape});
942 RegisterInputSlots(layer, {node.input(0)});
945 RegisterOutputSlots(layer, {node.output(0)});
948 void OnnxParser::ParseAveragePool(
const onnx::NodeProto& node)
953 uint32_t count_include_pad = 0;
954 count_include_pad = ReadOptionalNodeUint32Attribute(node,
"count_include_pad");
955 if(count_include_pad) {
958 AddPoolingLayer(node, desc);
969 std::vector<uint32_t> kernel_shape = ReadMandatoryNodeUint32ListAttribute(node,
"kernel_shape");
970 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node,
"strides");
971 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node,
"pads");
992 std::string paddingString = ReadOptionalNodeStringAttribute(node,
"auto_pad");
993 if(paddingString !=
"VALID" && paddingString !=
"" && paddingString !=
"NOTSET")
996 if( paddingString ==
"SAME_LOWER")
1000 else if (paddingString ==
"SAME_UPPER")
1007 boost::format(
"Invalid auto_pad attribute for node %1%. " 1008 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
1013 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1014 uint32_t inputHeight = inputInfo.GetShape()[2];
1015 uint32_t inputWidth = inputInfo.GetShape()[3];
1028 IConnectableLayer* layer = m_Network->AddPooling2dLayer(desc, node.name().c_str());
1029 BOOST_ASSERT(layer !=
nullptr);
1031 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1036 RegisterInputSlots(layer, {node.input(0)});
1039 RegisterOutputSlots(layer, {node.output(0)});
1042 void OnnxParser::CreateReshapeLayer(
const std::string& inputName,
1043 const std::string& outputName,
1044 const std::string& layerName)
1046 const TensorInfo outputTensorInfo = *m_TensorsInfo[outputName].m_info;
1050 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, layerName.c_str());
1051 BOOST_ASSERT(layer !=
nullptr);
1056 RegisterInputSlots(layer, {inputName});
1059 RegisterOutputSlots(layer, {outputName});
1062 void OnnxParser::ParseReshape(
const onnx::NodeProto& node)
1068 m_TensorsInfo[node.input(0)].m_dtype,
1069 onnx::TensorProto::FLOAT);
1071 m_TensorsInfo[node.input(1)].m_dtype,
1072 onnx::TensorProto::INT64);
1074 if(!m_TensorsInfo[node.input(1)].isConstant())
1077 boost::format(
"Shape '%1%' should be constant in Reshape layer '%2%' %3%")
1083 if(m_TensorsInfo[node.input(0)].isConstant())
1086 if(m_TensorsInfo.count(node.output(0)) == 0)
1088 m_TensorsInfo[node.output(0)] = OnnxTensor();
1090 m_TensorsInfo[node.output(0)].m_tensor =
1091 std::make_unique<onnx::TensorProto>(*m_TensorsInfo[node.input(0)].m_tensor);
1095 TensorShape inputShape = m_TensorsInfo[node.input(0)].m_info->GetShape();
1097 if(m_TensorsInfo.count(node.output(0)) == 0 || m_TensorsInfo[node.output(0)].m_info ==
nullptr)
1099 auto outInfo = ComputeReshapeInfo(*m_TensorsInfo[node.input(1)].m_tensor, inputShape, node.output(0));
1100 m_TensorsInfo[node.output(0)].m_info = std::make_unique<TensorInfo>(outInfo);
1103 CreateReshapeLayer(node.input(0), node.output(0), node.name());
1117 IConnectableLayer*
const layer = m_Network->AddActivationLayer(desc, node.name().c_str());
1118 BOOST_ASSERT(layer !=
nullptr);
1120 auto outputInfo = ComputeOutputInfo({ node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1125 RegisterInputSlots(layer, {node.input(0)});
1128 RegisterOutputSlots(layer, {node.output(0)});
1131 void OnnxParser::ParseSigmoid(
const onnx::NodeProto& node)
1133 ParseActivation(node, ActivationFunction::Sigmoid);
1136 void OnnxParser::ParseTanh(
const onnx::NodeProto& node)
1138 ParseActivation(node, ActivationFunction::TanH);
1141 void OnnxParser::ParseRelu(
const onnx::NodeProto& node)
1143 ParseActivation(node, ActivationFunction::ReLu);
1146 void OnnxParser::ParseLeakyRelu(
const onnx::NodeProto& node)
1148 ParseActivation(node, ActivationFunction::LeakyReLu);
1151 void OnnxParser::AddConvLayerWithDepthwiseConv(
const onnx::NodeProto& node,
const Convolution2dDescriptor& convDesc)
1153 BOOST_ASSERT(node.op_type() ==
"Conv");
1165 auto weightTensor = CreateConstTensor(node.input(1));
1166 TensorShape& weightShape = weightTensor.first.GetShape();
1167 weightShape[1] = weightShape[0];
1169 m_TensorsInfo[node.input(1)].m_info->SetShape(weightShape);
1171 if (node.input_size() == 3)
1173 if(!m_TensorsInfo[node.input(2)].isConstant())
1176 boost::format(
"Bias '%1%' should be constant in Conv layer '%2%' %3%")
1181 desc.m_BiasEnabled =
true;
1182 auto biasTensor = CreateConstTensor(node.input(2));
1183 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1186 node.name().c_str());
1190 layer = m_Network->AddDepthwiseConvolution2dLayer(desc,
1193 node.name().c_str());
1195 BOOST_ASSERT(layer !=
nullptr);
1197 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1198 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1199 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1205 RegisterInputSlots(layer, {node.input(0)});
1208 RegisterOutputSlots(layer, {node.output(0)});
1211 void OnnxParser::ParseConv(
const onnx::NodeProto& node)
1218 if(m_TensorsInfo[node.input(0)].m_info->GetNumDimensions() != 4)
1221 boost::format(
"ArmNN only supports 2D convolution and Conv layer '%1%' input %2% %3%")
1223 % TensorInfoAsString(*m_TensorsInfo[node.input(0)].m_info, node.input(0),
1224 m_TensorsInfo[node.input(0)].m_dtype)
1228 if(!m_TensorsInfo[node.input(1)].isConstant())
1231 boost::format(
"Weights '%1%' should be constant in Conv layer '%2%' %3%")
1237 auto inputInfo = *m_TensorsInfo[node.input(0)].m_info;
1239 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(node,
"dilations");
1240 if (!dilations.empty())
1242 std::stringstream ss;
1244 for (
auto dilation : dilations)
1246 ss << dilation <<
", ";
1251 boost::format(
"ArmNN only supports Convolution layers with dilations [1,1], and node '%1%' " 1252 "has dilatation %2% %3%")
1263 std::vector<uint32_t> strides = ReadOptionalNodeUint32ListAttribute(node,
"strides");
1275 std::vector<uint32_t> pads = ReadOptionalNodeUint32ListAttribute(node,
"pads");
1280 std::string paddingString = ReadOptionalNodeStringAttribute(node,
"auto_pad");
1281 if(paddingString !=
"VALID" && paddingString !=
"" && paddingString !=
"NOTSET")
1284 if( paddingString ==
"SAME_LOWER")
1288 else if (paddingString ==
"SAME_UPPER")
1295 boost::format(
"Invalid auto_pad attribute for node %1%. " 1296 "Only SAME_UPPER, SAME_LOWER or VALID supported and found %2% %3%")
1301 uint32_t inputHeight = inputInfo.GetShape()[2];
1302 uint32_t inputWidth = inputInfo.GetShape()[3];
1304 uint32_t weightHeight;
1305 uint32_t weightWidth;
1306 std::vector<uint32_t> kernel_shape = ReadOptionalNodeUint32ListAttribute(node,
"kernel_shape");
1307 if (kernel_shape.empty())
1309 const TensorInfo weightTensorInfo = *m_TensorsInfo[node.input(1)].m_info;
1310 weightHeight = weightTensorInfo.
GetShape()[2];
1311 weightWidth = weightTensorInfo.
GetShape()[3];
1315 weightHeight = kernel_shape[0];
1316 weightWidth = kernel_shape[1];
1330 uint32_t group = ReadOptionalNodeUint32Attribute(node,
"group", 1);
1333 if (group > inputInfo.GetShape()[1])
1338 "Error parsing Convolution node: %1%. " 1339 "The 'group'=%2% parameter cannot be larger than the " 1340 "channel of the input shape=%3% (in NCHW format). %4%") %
1343 inputInfo.GetShape()[1] %
1346 else if (group == inputInfo.GetShape()[1])
1350 AddConvLayerWithDepthwiseConv(node, desc);
1358 boost::format(
"Error parsing Convolution node: %1%. " 1359 "The 'group'=%2% parameter should be 1 or be equal to the " 1360 "channel of the input shape=%3% (in NCHW format). %4%") %
1363 inputInfo.GetShape()[1] %
1369 auto weightTensor = CreateConstTensor(node.input(1));
1371 if (node.input_size() == 3)
1373 if(!m_TensorsInfo[node.input(2)].isConstant())
1376 boost::format(
"Bias '%1%' should be constant in Conv layer '%2%' %3%")
1382 auto biasTensor = CreateConstTensor(node.input(2));
1383 layer = m_Network->AddConvolution2dLayer(desc,
1386 node.name().c_str());
1390 layer = m_Network->AddConvolution2dLayer(desc,
1393 node.name().c_str());
1395 BOOST_ASSERT(layer !=
nullptr);
1397 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1398 { m_TensorsInfo[node.input(0)].m_info->GetShape(),
1399 m_TensorsInfo[node.input(1)].m_info->GetShape() });
1404 RegisterInputSlots(layer, {node.input(0)});
1407 RegisterOutputSlots(layer, {node.output(0)});
1410 void OnnxParser::PrependForBroadcast(
const std::string& outputName,
1411 const std::string& input0,
1412 const std::string& input1)
1417 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1418 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1421 std::vector<uint32_t> newShape;
1424 newShape.push_back(1);
1429 newShape.push_back(input0Shape[dim]);
1431 outputTensorInfo.
SetShape(
TensorShape(static_cast<unsigned int>(newShape.size()), newShape.data()));
1434 m_TensorsInfo[outputName] = OnnxTensor();
1435 m_TensorsInfo[outputName].m_info = std::make_unique<TensorInfo>(outputTensorInfo);
1438 if( ! m_TensorsInfo[input0].isConstant())
1440 CreateReshapeLayer(input0, outputName, boost::str(boost::format(
"Add:reshapeOf%1%") % input0));
1444 m_TensorsInfo[outputName].m_tensor = std::make_unique<onnx::TensorProto>(*m_TensorsInfo[input0].m_tensor);
1449 std::pair<std::string, std::string> OnnxParser::AddPrepareBroadcast(
const std::string& input0,
1450 const std::string& input1)
1452 std::pair<std::string, std::string> inputs = std::make_pair(input0, input1);
1454 TensorShape input0Shape = m_TensorsInfo[input0].m_info->GetShape();
1455 TensorShape input1Shape = m_TensorsInfo[input1].m_info->GetShape();
1459 auto outputName = boost::str(boost::format(
"reshape_output_%1%") % input1);
1460 PrependForBroadcast(outputName, input1, input0);
1461 inputs.second = outputName;
1465 auto outputName = boost::str(boost::format(
"reshape_output_%1%") % input0);
1466 PrependForBroadcast(outputName, input0, input1);
1467 inputs.first = outputName;
1472 void OnnxParser::ParseAdd(
const onnx::NodeProto& node)
1483 auto inputs = AddPrepareBroadcast(node.input(0), node.input(1));
1484 auto input0 = *m_TensorsInfo[inputs.first].m_info;
1485 auto input1 = *m_TensorsInfo[inputs.second].m_info;
1486 BOOST_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions());
1488 unsigned int numDims = input0.GetNumDimensions();
1489 for (
unsigned int i = 0; i < numDims; i++)
1491 unsigned int dim0 = input0.GetShape()[i];
1492 unsigned int dim1 = input1.GetShape()[i];
1493 if (dim0 != dim1 && dim0 != 1 && dim1 != 1)
1496 boost::format(
"Broadcast is only supported for scalar or 1D tensors in Add node '%1%'. " 1497 "Input dimensions should either match or one should be of size 1 and here, " 1500 % TensorInfoAsString(*m_TensorsInfo[inputs.first].m_info, inputs.first,
1501 m_TensorsInfo[inputs.first].m_dtype)
1502 % TensorInfoAsString(*m_TensorsInfo[inputs.second].m_info, inputs.second,
1503 m_TensorsInfo[inputs.second].m_dtype)
1510 BOOST_ASSERT(layer !=
nullptr);
1512 auto outputInfo = ComputeOutputInfo({ node.output(0) }, layer,
1513 { m_TensorsInfo[inputs.first].m_info->GetShape(),
1514 m_TensorsInfo[inputs.second].m_info->GetShape() });
1518 if(m_TensorsInfo[inputs.first].isConstant()) {
1519 CreateConstantLayer(inputs.first, boost::str(boost::format(
"Add:constant_of_%1%") % node.input(0)));
1521 if(m_TensorsInfo[inputs.second].isConstant()) {
1522 CreateConstantLayer(inputs.second, boost::str(boost::format(
"Add:constant_of_%1%") % node.input(1)));
1524 RegisterInputSlots(layer, {inputs.first, inputs.second});
1527 RegisterOutputSlots(layer, {node.output(0)});
1530 void OnnxParser::ParseBatchNormalization(
const onnx::NodeProto& node)
1538 for(
int ind = 1; ind < node.input_size(); ++ind)
1540 auto tensor = node.input(ind);
1541 if(! m_TensorsInfo[tensor].isConstant())
1544 boost::format(
"Input tensor '%1%' should be constant in BatchNormalization node '%2%' %3%")
1551 float epsilon = ReadOptionalNodeFloatAttribute(node,
"epsilon", 1e-5f);
1553 desc.
m_Eps = epsilon;
1555 auto scaleTensor = CreateConstTensor(node.input(1));
1556 auto biasTensor = CreateConstTensor(node.input(2));
1557 auto meanTensor = CreateConstTensor(node.input(3));
1558 auto varTensor = CreateConstTensor(node.input(4));
1565 node.name().c_str());
1566 BOOST_ASSERT(layer !=
nullptr);
1568 auto outputInfo = ComputeOutputInfo({node.output(0)}, layer, {m_TensorsInfo[node.input(0)].m_info->GetShape()});
1569 layer->GetOutputSlot(0).SetTensorInfo(outputInfo[0]);
1571 RegisterInputSlots(layer, {node.input(0)});
1574 RegisterOutputSlots(layer, {node.output(0)});
1577 void OnnxParser::SetupInputLayers()
1580 for(
int inputIndex = 0; inputIndex < m_Graph->input_size(); ++inputIndex)
1582 auto input = m_Graph->input(inputIndex);
1583 if (! m_TensorsInfo[input.name()].isConstant())
1586 m_Network->AddInputLayer(static_cast<armnn::LayerBindingId>(inputIndex), input.name().c_str());
1590 RegisterOutputSlots(layer,{ input.name() });
1595 void OnnxParser::SetupOutputLayers()
1597 if(m_Graph->output_size() == 0)
1599 throw ParseException(boost::str(boost::format(
"The given model does not have any outputs %1%")
1603 for(
int outputIndex = 0; outputIndex < m_Graph->output_size(); ++outputIndex)
1606 m_Network->AddOutputLayer(static_cast<armnn::LayerBindingId>(outputIndex),
1607 m_Graph->output(outputIndex).name().c_str());
1609 RegisterInputSlots(layer, { m_Graph->output(outputIndex).name() });
1613 void OnnxParser::RegisterInputSlots(
IConnectableLayer* layer,
const std::vector<std::string>& tensorIds)
1615 BOOST_ASSERT(layer !=
nullptr);
1619 boost::str(boost::format(
"The number of tensor inputs (%1%) does not match the number expected (%2%) %3%") %
1624 for (
unsigned int slotIndex = 0; slotIndex < layer->
GetNumInputSlots(); ++slotIndex)
1626 std::string tensorId = tensorIds[slotIndex];
1629 auto it = m_TensorConnections.find(tensorId);
1631 if (it == m_TensorConnections.end())
1634 m_TensorConnections[tensorId] = TensorSlots();
1636 m_TensorConnections[tensorId].inputSlots.push_back(slot);
1640 void OnnxParser::RegisterOutputSlots(
IConnectableLayer* layer,
const std::vector<std::string>& tensorIds)
1642 BOOST_ASSERT(layer !=
nullptr);
1646 boost::str(boost::format(
"The number of tensor outputs (%1%) does not match the number expected (%2%) %3% ")
1652 for (
unsigned int slotIndex = 0; slotIndex < layer->
GetNumOutputSlots(); ++slotIndex)
1654 std::string tensorId = tensorIds[slotIndex];
1657 auto it = m_TensorConnections.find(tensorId);
1659 if (it == m_TensorConnections.end())
1662 m_TensorConnections[tensorId] = TensorSlots();
1665 TensorSlots& tensorSlots = m_TensorConnections[tensorId];
1668 if (tensorSlots.outputSlot !=
nullptr)
1671 boost::format(
"Another layer has already registered itself as the producer of " 1676 tensorSlots.outputSlot = slot;
1682 for(
int i = 0; i < m_Graph->input_size(); ++i)
1684 auto input = m_Graph->input(i);
1685 if(input.name() == name)
1687 return std::make_pair(static_cast<armnn::LayerBindingId>(i),
ToTensorInfo(input));
1696 for(
int i = 0; i < m_Graph->output_size(); ++i)
1698 auto output = m_Graph->output(i);
1699 if(output.name() == name)
1701 return std::make_pair(static_cast<armnn::LayerBindingId>(i),
ToTensorInfo(output));
1710 if(model ==
nullptr) {
1712 boost::format(
"The given model cannot be null %1%")
1716 std::vector<std::string> inputNames;
1717 std::map<std::string, bool> isConstant;
1718 for(
auto tensor : model->graph().initializer())
1720 isConstant[tensor.name()] =
true;
1722 for(
auto input : model->graph().input())
1724 auto it = isConstant.find(input.name());
1725 if(it == isConstant.end())
1727 inputNames.push_back(input.name());
1735 if(model ==
nullptr) {
1737 boost::format(
"The given model cannot be null %1%")
1741 std::vector<std::string> outputNames;
1742 for(
auto output : model->graph().output())
1744 outputNames.push_back(output.name());
unsigned int GetNumElements() const
uint32_t m_PadBottom
Padding bottom value in the height dimension.
bool m_BiasEnabled
Enable/disable bias.
virtual unsigned int GetNumOutputSlots() const =0
Returns the number of connectable output slots.
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
uint32_t m_PadBottom
Padding bottom value in the height dimension.
virtual unsigned int GetNumInputSlots() const =0
Returns the number of connectable input slots.
const TensorShape & GetShape() const
uint32_t m_PadLeft
Padding left value in the width dimension.
std::string AsString() const
A ReshapeDescriptor for the ReshapeLayer.
virtual BindingPointInfo GetNetworkOutputBindingInfo(const std::string &name) const override
Retrieve binding info (layer id and tensor info) for the network output identified by the given layer...
uint32_t m_PoolWidth
Pooling width value.
virtual armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile) override
Create the network from a protobuf text file on disk.
A Convolution2dDescriptor for the Convolution2dLayer.
unsigned int GetNumBytes() const
float m_Eps
Value to add to the variance. Used to avoid dividing by zero.
PaddingMethod m_PaddingMethod
The padding method to be used. (Exclude, IgnoreValue).
uint32_t m_PadTop
Padding top value in the height dimension.
void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t &outPadHead, uint32_t &outPadTail, bool samePadding)
uint32_t m_PadRight
Padding right value in the width dimension.
#define VALID_INPUTS(NODE, VALID_INPUTS)
Copyright (c) 2020 ARM Limited.
static ModelPtr LoadModelFromBinaryFile(const char *fileName)
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
void SetShape(const TensorShape &newShape)
static ModelPtr LoadModelFromTextFile(const char *fileName)
virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile) override
Create the network from a protobuf binary file on disk.
TensorShape m_TargetShape
Target shape value.
static std::vector< std::string > GetInputs(ModelPtr &model)
Retrieve inputs names.
uint32_t m_PoolHeight
Pooling height value.
uint32_t m_PadTop
Padding top value in the height dimension.
static std::vector< std::string > GetOutputs(ModelPtr &model)
Retrieve outputs names.
armnn::TensorInfo ToTensorInfo(Deserializer::TensorRawPtr tensorPtr)
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
std::unique_ptr< onnx::ModelProto > ModelPtr
virtual BindingPointInfo GetNetworkInputBindingInfo(const std::string &name) const override
Retrieve binding info (layer id and tensor info) for the network input identified by the given layer ...
uint32_t m_PadRight
Padding right value in the width dimension.
An output connection slot for a layer.
A FullyConnectedDescriptor for the FullyConnectedLayer.
bool m_BiasEnabled
Enable/disable bias.
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
#define CHECK_VALID_SIZE(ACTUAL,...)
#define CHECKED_NON_NEGATIVE(VALUE)
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
An ActivationDescriptor for the ActivationLayer.
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
EmptyOptional is used to initialize the Optional class in case we want to have default value for an O...
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
unsigned int GetNumDimensions() const
OutputShapeRounding m_OutputShapeRounding
The rounding method for the output shape. (Floor, Ceiling).
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
virtual armnn::INetworkPtr CreateNetworkFromString(const std::string &protoText) override
Create the network directly from protobuf text in a string. Useful for debugging/testing.
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
static ModelPtr LoadModelFromString(const std::string &inputString)
#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL,...)
armnn::BindingPointInfo BindingPointInfo
virtual std::vector< TensorShape > InferOutputShapes(const std::vector< TensorShape > &inputShapes) const =0
Infer the shape of the output(s) based on the provided input shape(s)
#define CHECKED_INT32(VALUE)
A Pooling2dDescriptor for the Pooling2dLayer.
std::unique_ptr< IOnnxParser, void(*)(IOnnxParser *parser)> IOnnxParserPtr
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square).
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
A BatchNormalizationDescriptor for the BatchNormalizationLayer.
uint32_t m_PadLeft
Padding left value in the width dimension.
unsigned int GetNumElements() const