From: Ferran Balaguer Date: Fri, 28 Dec 2018 18:15:24 +0000 (+0000) Subject: IVGCVSW-2375 Add ParseAddN function to TfParser X-Git-Tag: submit/tizen/20200316.035456~976 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=fbdad03c927aa5d30deec6fa1a61eef10f8c265f;p=platform%2Fupstream%2Farmnn.git IVGCVSW-2375 Add ParseAddN function to TfParser * Unit tests in AddN.cpp Change-Id: Ifb2fa1051d5d92c5d9a5ca751abee4e81ebe39c9 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bafd03..be68798 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,6 +389,7 @@ if(BUILD_UNIT_TESTS) list(APPEND unittest_sources src/armnnTfParser/test/Activations.cpp src/armnnTfParser/test/Addition.cpp + src/armnnTfParser/test/AddN.cpp src/armnnTfParser/test/BiasAdd.cpp src/armnnTfParser/test/BroadcastForAdd.cpp src/armnnTfParser/test/Convolution2d.cpp diff --git a/src/armnnTfParser/TfParser.cpp b/src/armnnTfParser/TfParser.cpp index 45c039b..1a0047f 100644 --- a/src/armnnTfParser/TfParser.cpp +++ b/src/armnnTfParser/TfParser.cpp @@ -327,6 +327,7 @@ OutputId ParseOutputId(const std::string & name) const std::map TfParser::ms_OperationNameToParsingFunctions = { { "Const", &TfParser::ParseConst }, { "Add", &TfParser::ParseAdd }, + { "AddN", &TfParser::ParseAddN }, { "BiasAdd", &TfParser::ParseBiasAdd }, { "Identity", &TfParser::ParseIdentity }, { "Conv2D", &TfParser::ParseConv2D }, @@ -610,6 +611,183 @@ TfParser::GetInputParsedTfOperationsChecked(const tensorflow::NodeDef& nodeDef, return result; } +IConnectableLayer* TfParser::CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + IOutputSlot* input0Slot, + IOutputSlot* input1Slot, + const std::string& layerName) +{ + const TensorInfo& input0Info = input0Slot->GetTensorInfo(); + const TensorInfo& input1Info = input1Slot->GetTensorInfo(); + + const unsigned int input0Dim = input0Info.GetNumDimensions(); + const unsigned int input1Dim = input1Info.GetNumDimensions(); + if (input0Dim != input1Dim) + { + // broadcasting where input0 and input1 have different number of dimensions + // is only supported for 1D and 4D tensors pair + if (input0Dim == 1 && input1Dim == 4) + { + input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, true, *m_Network, nodeDef); + } + else if (input0Dim == 4 && input1Dim == 1) + { + input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, true, *m_Network, nodeDef); + } + else + { + throw ParseException( + boost::str( + boost::format("Unsupported broadcast configuration for %1% operation %2% %3%") + % layerName + % nodeDef.name() + % CHECK_LOCATION().AsString())); + } + } + IConnectableLayer* const layer = m_Network->AddAdditionLayer(layerName.c_str()); + + input0Slot->Connect(layer->GetInputSlot(0)); + input1Slot->Connect(layer->GetInputSlot(1)); + + // Ensure the output tensor has the correct dimensions even if a broadcast has been done + TensorInfo outputInfo = input0Slot->GetTensorInfo(); + std::vector outputShape; + + const TensorShape& input0Shape = input0Slot->GetTensorInfo().GetShape(); + const TensorShape& input1Shape = input1Slot->GetTensorInfo().GetShape(); + + for (unsigned int i = 0; i < input0Shape.GetNumDimensions(); i++) + { + outputShape.push_back(std::max(input0Shape[i], input1Shape[i])); + } + + outputInfo.SetShape(TensorShape(input0Shape.GetNumDimensions(), outputShape.data())); + layer->GetOutputSlot(0).SetTensorInfo(outputInfo); + + return layer; +} + +IConnectableLayer* TfParser::CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + IConnectableLayer* layerOne, + IConnectableLayer* layerTwo, + unsigned int numberOfAddition, + unsigned long numberOfLayersToConnect, + bool isOdd) +{ + IOutputSlot* input0Slot = &layerOne->GetOutputSlot(0); + IOutputSlot* input1Slot = &layerTwo->GetOutputSlot(0); + std::string layerName(nodeDef.name()); + if (isOdd || numberOfLayersToConnect != 2) + { + // we are not connecting the final layer + layerName.append("_addN_").append(std::to_string(numberOfAddition)); + } + return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, layerName); +} + +IConnectableLayer* TfParser::CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + const OutputOfParsedTfOperation& opOne, + const OutputOfParsedTfOperation& opTwo, + unsigned int numberOfAddition) +{ + IOutputSlot* input0Slot = &opOne.m_IndexedValue->ResolveArmnnOutputSlot(opOne.m_Index); + IOutputSlot* input1Slot = &opTwo.m_IndexedValue->ResolveArmnnOutputSlot(opTwo.m_Index); + std::string layerName(nodeDef.name()); + layerName.append("_addN_").append(std::to_string(numberOfAddition)); + return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, layerName); +} + +IConnectableLayer* TfParser::CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + const OutputOfParsedTfOperation& op, + IConnectableLayer* layer) +{ + IOutputSlot* input0Slot = &op.m_IndexedValue->ResolveArmnnOutputSlot(op.m_Index); + IOutputSlot* input1Slot = &layer->GetOutputSlot(0); + return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, nodeDef.name()); +} + +ParsedTfOperationPtr TfParser::ParseAddN(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) +{ + uint32_t numberOfInputs = ReadMandatoryNodeUint32Attribute(nodeDef, "N"); + if (numberOfInputs < 2) + { + // should never happen + throw ParseException( + boost::str( + boost::format( + "AddN Node with name '%1%' has less than two (%2) inputs %3%") + % nodeDef.name() + % std::to_string(numberOfInputs) + % CHECK_LOCATION().AsString())); + } + else if (numberOfInputs == 2) + { + //this is the same as a simple Add operation + return AddAdditionLayer(nodeDef, false); + } + else + { + // build a binary tree of Add layers and return the final Add as the return from the function + // if we have an odd number of inputs then the final Add will consist of a layer connecting to an + // OutputOfParsedTfOperation, otherwise it will be two layers being added together + std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, numberOfInputs); + unsigned int numberOfAdditions = 0; + std::vector layers; + // NOTE: at this point we will have a minimum of three inputs + for (unsigned int i = 0; i < numberOfInputs; ++i) + { + // every time i is odd we have two inputs to process. + bool onSecondItem = i % 2; + if (onSecondItem) + { + ++numberOfAdditions; + IConnectableLayer* newLayer = CreateAdditionLayer( + nodeDef, inputs[ i - 1], inputs[i], numberOfAdditions); + layers.push_back(newLayer); + } + } + + std::vector layersToConnect(layers); + unsigned long numberOfLayersToConnect = layersToConnect.size(); + bool isOdd = numberOfInputs % 2; + + while (numberOfLayersToConnect > 1) + { + layers.clear(); + for (unsigned long i = 0; i < numberOfLayersToConnect; ++i) { + bool onSecondItem = i % 2; + if (onSecondItem) { + ++numberOfAdditions; + IConnectableLayer* newLayer = CreateAdditionLayer( + nodeDef, + layersToConnect[i - 1], + layersToConnect[i], + numberOfAdditions, + numberOfLayersToConnect, + isOdd); + layers.push_back(newLayer); + } + } + //OK... need to go again... maybe + layersToConnect = layers; + numberOfLayersToConnect = layersToConnect.size(); + } + IConnectableLayer* finalLayer = layersToConnect[0]; + // if we had an odd number of inputs we need to connect the final layer to the + // last OutputOfParsedTfOperation in order to create the last Add layer we will + // be handing back. + if (isOdd) + { + // connect the final layer to the last op + finalLayer = CreateAdditionLayer(nodeDef, inputs[numberOfInputs - 1], finalLayer); + } + return std::make_unique(this, nodeDef, finalLayer); + } +} + ParsedTfOperationPtr TfParser::ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); diff --git a/src/armnnTfParser/TfParser.hpp b/src/armnnTfParser/TfParser.hpp index 5579747..0d1e497 100644 --- a/src/armnnTfParser/TfParser.hpp +++ b/src/armnnTfParser/TfParser.hpp @@ -129,6 +129,7 @@ private: bool HasParsedConstTensor(ParsedTfOperation* parsedTfOpPtr) const; ParsedTfOperationPtr ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef); + ParsedTfOperationPtr ParseAddN(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef); ParsedTfOperationPtr ParseBiasAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef); ParsedTfOperationPtr ParseConv2D(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef); ParsedTfOperationPtr ParseDepthwiseConv2D(const tensorflow::NodeDef& nodeDef,const tensorflow::GraphDef& graphDef); @@ -187,6 +188,31 @@ private: armnn::IConnectableLayer* const layer, const tensorflow::NodeDef& nodeDef); + armnn::IConnectableLayer* CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + armnn::IOutputSlot* input0Slot, + armnn::IOutputSlot* input1Slot, + const std::string& layerName); + + armnn::IConnectableLayer* CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + const OutputOfParsedTfOperation& opOne, + const OutputOfParsedTfOperation& opTwo, + unsigned int numberOfAddition); + + armnn::IConnectableLayer* CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + armnn::IConnectableLayer* layerOne, + armnn::IConnectableLayer* layerTwo, + unsigned int numberOfAddition, + unsigned long numberOfLayersToConnect, + bool isOdd); + + armnn::IConnectableLayer* CreateAdditionLayer( + const tensorflow::NodeDef& nodeDef, + const OutputOfParsedTfOperation& op, + armnn::IConnectableLayer* layer); + static std::pair GetBindingInfo(const std::string& layerName, const char* bindingPointDesc, const std::unordered_map& nameToBindingInfo); diff --git a/src/armnnTfParser/test/AddN.cpp b/src/armnnTfParser/test/AddN.cpp new file mode 100644 index 0000000..19affa8 --- /dev/null +++ b/src/armnnTfParser/test/AddN.cpp @@ -0,0 +1,180 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include +#include + +#include "armnnTfParser/ITfParser.hpp" +#include "ParserPrototxtFixture.hpp" + +#include +#include + + +BOOST_AUTO_TEST_SUITE(TensorflowParser) + +struct AddNFixture : public armnnUtils::ParserPrototxtFixture +{ + AddNFixture(const std::vector inputShapes, unsigned int numberOfInputs) + { + BOOST_ASSERT(inputShapes.size() == numberOfInputs); + m_Prototext = ""; + for (unsigned int i = 0; i < numberOfInputs; i++) + { + m_Prototext.append("node { \n"); + m_Prototext.append(" name: \"input").append(std::to_string(i)).append("\"\n"); + m_Prototext += R"( op: "Placeholder" + attr { + key: "dtype" + value { + type: DT_FLOAT + } + } + attr { + key: "shape" + value { + shape { + } + } + } +} +)"; + } + m_Prototext += R"(node { + name: "output" + op: "AddN" +)"; + for (unsigned int i = 0; i < numberOfInputs; i++) + { + m_Prototext.append(" input: \"input").append(std::to_string(i)).append("\"\n"); + } + m_Prototext += R"( attr { + key: "N" + value { +)"; + m_Prototext.append(" i: ").append(std::to_string(numberOfInputs)).append("\n"); + m_Prototext += R"( } + } + attr { + key: "T" + value { + type: DT_FLOAT + } + } +})"; + + std::map inputs; + for (unsigned int i = 0; i < numberOfInputs; i++) + { + std::string name("input"); + name.append(std::to_string(i)); + inputs.emplace(std::make_pair(name, inputShapes[i])); + } + Setup(inputs, {"output"}); + } + +}; + +// try with 2, 3, 5 and 8 inputs +struct FiveTwoDimInputsFixture : AddNFixture +{ + FiveTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 } }, 5) {} +}; + + +BOOST_FIXTURE_TEST_CASE(FiveTwoDimInputs, FiveTwoDimInputsFixture) +{ + RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } }, + { "input1", { 1.0, 5.0, 2.0, 2.0 } }, + { "input2", { 1.0, 1.0, 2.0, 2.0 } }, + { "input3", { 3.0, 7.0, 1.0, 2.0 } }, + { "input4", { 8.0, 0.0, -2.0, -3.0 } } }, + { { "output", { 14.0, 15.0, 6.0, 7.0 } } }); +} + +struct TwoTwoDimInputsFixture : AddNFixture +{ + TwoTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 } }, 2) {} +}; + +BOOST_FIXTURE_TEST_CASE(TwoTwoDimInputs, TwoTwoDimInputsFixture) +{ + RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } }, + { "input1", { 1.0, 5.0, 2.0, 2.0 } } }, + { { "output", { 2.0, 7.0, 5.0, 6.0 } } }); +} + +struct ThreeTwoDimInputsFixture : AddNFixture +{ + ThreeTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 }, { 2, 2 } }, 3) {} +}; + +BOOST_FIXTURE_TEST_CASE(ThreeTwoDimInputs, ThreeTwoDimInputsFixture) +{ + RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } }, + { "input1", { 1.0, 5.0, 2.0, 2.0 } }, + { "input2", { 1.0, 1.0, 2.0, 2.0 } } }, + { { "output", { 3.0, 8.0, 7.0, 8.0 } } }); +} + +struct EightTwoDimInputsFixture : AddNFixture +{ + EightTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 }, + { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 } }, 8) {} +}; + +BOOST_FIXTURE_TEST_CASE(EightTwoDimInputs, EightTwoDimInputsFixture) +{ + RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } }, + { "input1", { 1.0, 5.0, 2.0, 2.0 } }, + { "input2", { 1.0, 1.0, 2.0, 2.0 } }, + { "input3", { 3.0, 7.0, 1.0, 2.0 } }, + { "input4", { 8.0, 0.0, -2.0, -3.0 } }, + { "input5", {-3.0, 2.0, -1.0, -5.0 } }, + { "input6", { 1.0, 6.0, 2.0, 2.0 } }, + { "input7", {-19.0, 7.0, 1.0, -10.0 } } }, + { { "output", {-7.0, 30.0, 8.0, -6.0 } } }); +} + +struct ThreeInputBroadcast1D4D4DInputsFixture : AddNFixture +{ + ThreeInputBroadcast1D4D4DInputsFixture() : AddNFixture({ { 1 }, { 1, 1, 2, 2 }, { 1, 1, 2, 2 } }, 3) {} +}; + +BOOST_FIXTURE_TEST_CASE(ThreeInputBroadcast1D4D4DInputs, ThreeInputBroadcast1D4D4DInputsFixture) +{ + RunTest<4>({ { "input0", { 1.0 } }, + { "input1", { 1.0, 5.0, 2.0, 2.0 } }, + { "input2", { 1.0, 1.0, 2.0, 2.0 } } }, + { { "output", { 3.0, 7.0, 5.0, 5.0 } } }); +} + +struct ThreeInputBroadcast4D1D4DInputsFixture : AddNFixture +{ + ThreeInputBroadcast4D1D4DInputsFixture() : AddNFixture({ { 1, 1, 2, 2 }, { 1 }, { 1, 1, 2, 2 } }, 3) {} +}; + +BOOST_FIXTURE_TEST_CASE(ThreeInputBroadcast4D1D4DInputs, ThreeInputBroadcast4D1D4DInputsFixture) +{ + RunTest<4>({ { "input0", { 1.0, 3.0, 9.0, 4.0 } }, + { "input1", {-2.0 } }, + { "input2", { 1.0, 1.0, 2.0, 2.0 } } }, + { { "output", { 0.0, 2.0, 9.0, 4.0 } } }); +} + +struct ThreeInputBroadcast4D4D1DInputsFixture : AddNFixture +{ + ThreeInputBroadcast4D4D1DInputsFixture() : AddNFixture({ { 1, 1, 2, 2 }, { 1, 1, 2, 2 }, { 1 } }, 3) {} +}; + +BOOST_FIXTURE_TEST_CASE(ThreeInputBroadcast4D4D1DInputs, ThreeInputBroadcast4D4D1DInputsFixture) +{ + RunTest<4>({ { "input0", { 1.0, 5.0, 2.0, 2.0 } }, + { "input1", { 1.0, 1.0, 2.0, 2.0 } }, + { "input2", { 1.0 } } }, + { { "output", { 3.0, 7.0, 5.0, 5.0 } } }); +} + +BOOST_AUTO_TEST_SUITE_END()