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());
#define CHECK_VALID_SIZE(ACTUAL,...)
std::unique_ptr< onnx::ModelProto > ModelPtr
#define VALID_INPUTS(NODE, VALID_INPUTS)
#define CHECKED_INT32(VALUE)
static ModelPtr LoadModelFromTextFile(const char *fileName)
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
unsigned int GetNumDimensions() const
uint32_t m_PadRight
Padding right value in the width dimension.
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
static std::vector< std::string > GetOutputs(ModelPtr &model)
Retrieve outputs names.
uint32_t m_PoolHeight
Pooling height value.
uint32_t m_PadTop
Padding top value in the height dimension.
virtual armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile) override
Create the network from a protobuf text file on disk.
An ActivationDescriptor for the ActivationLayer.
A BatchNormalizationDescriptor for the BatchNormalizationLayer.
uint32_t m_PadRight
Padding right value in the width dimension.
uint32_t m_PadBottom
Padding bottom value in the height dimension.
bool m_BiasEnabled
Enable/disable bias.
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
#define CHECKED_NON_NEGATIVE(VALUE)
virtual std::vector< TensorShape > InferOutputShapes(const std::vector< TensorShape > &inputShapes) const =0
A ReshapeDescriptor for the ReshapeLayer.
uint32_t m_PadTop
Padding top value in the height dimension.
TensorShape m_TargetShape
Target shape value.
uint32_t m_PadLeft
Padding left value in the width dimension.
uint32_t m_PoolWidth
Pooling width value.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
unsigned int GetNumBytes() const
virtual unsigned int GetNumOutputSlots() const =0
uint32_t m_PadLeft
Padding left value in the width dimension.
unsigned int GetNumElements() const
A FullyConnectedDescriptor for the FullyConnectedLayer.
float m_Eps
Value to add to the variance. Used to avoid dividing by zero.
void SetShape(const TensorShape &newShape)
static ModelPtr LoadModelFromString(const std::string &inputString)
void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t &outPadHead, uint32_t &outPadTail, bool samePadding)
PaddingMethod m_PaddingMethod
The padding method to be used. (Exclude, IgnoreValue).
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 ...
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
bool m_BiasEnabled
Enable/disable bias.
static std::vector< std::string > GetInputs(ModelPtr &model)
Retrieve inputs names.
virtual armnn::INetworkPtr CreateNetworkFromString(const std::string &protoText) override
Create the network directly from protobuf text in a string. Useful for debugging/testing.
OutputShapeRounding m_OutputShapeRounding
The rounding method for the output shape. (Floor, Ceiling).
armnn::BindingPointInfo BindingPointInfo
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square).
An output connection slot for a layer. The output slot may be connected to 1 or more input slots of s...
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
#define CHECK_VALID_DATATYPE(NODE, TENSOR, ACTUAL,...)
static ModelPtr LoadModelFromBinaryFile(const char *fileName)
unsigned int GetNumElements() const
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
A Pooling2dDescriptor for the Pooling2dLayer.
virtual unsigned int GetNumInputSlots() const =0
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...
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile) override
Create the network from a protobuf binary file on disk.
A Convolution2dDescriptor for the Convolution2dLayer.
armnn::TensorInfo ToTensorInfo(Deserializer::TensorRawPtr tensorPtr)
uint32_t m_PadBottom
Padding bottom value in the height dimension.
const TensorShape & GetShape() const
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
std::string AsString() const
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
std::unique_ptr< IOnnxParser, void(*)(IOnnxParser *parser)> IOnnxParserPtr