aboutsummaryrefslogtreecommitdiff
path: root/src/armnnTfParser
diff options
context:
space:
mode:
authorFerran Balaguer <ferran.balaguer@arm.com>2018-12-28 18:15:24 +0000
committerMatthew Bentham <matthew.bentham@arm.com>2018-12-31 14:56:00 +0000
commitfbdad03c927aa5d30deec6fa1a61eef10f8c265f (patch)
tree0f435edc1178c470a6b9b7dcb3a408d486da5b3e /src/armnnTfParser
parentd59180174bda6d533e233d733ec526d2db64a22e (diff)
downloadarmnn-fbdad03c927aa5d30deec6fa1a61eef10f8c265f.tar.gz
IVGCVSW-2375 Add ParseAddN function to TfParser
* Unit tests in AddN.cpp Change-Id: Ifb2fa1051d5d92c5d9a5ca751abee4e81ebe39c9
Diffstat (limited to 'src/armnnTfParser')
-rw-r--r--src/armnnTfParser/TfParser.cpp178
-rw-r--r--src/armnnTfParser/TfParser.hpp26
-rw-r--r--src/armnnTfParser/test/AddN.cpp180
3 files changed, 384 insertions, 0 deletions
diff --git a/src/armnnTfParser/TfParser.cpp b/src/armnnTfParser/TfParser.cpp
index 45c039bb15..1a0047fce6 100644
--- a/src/armnnTfParser/TfParser.cpp
+++ b/src/armnnTfParser/TfParser.cpp
@@ -327,6 +327,7 @@ OutputId ParseOutputId(const std::string & name)
const std::map<std::string, TfParser::OperationParsingFunction> 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<unsigned int> 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<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numberOfInputs);
+ unsigned int numberOfAdditions = 0;
+ std::vector<IConnectableLayer*> 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<IConnectableLayer*> 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<SingleLayerParsedTfOperation>(this, nodeDef, finalLayer);
+ }
+}
+
ParsedTfOperationPtr TfParser::ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
{
std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
diff --git a/src/armnnTfParser/TfParser.hpp b/src/armnnTfParser/TfParser.hpp
index 55797471e2..0d1e497e29 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<armnn::LayerBindingId, armnn::TensorInfo> GetBindingInfo(const std::string& layerName,
const char* bindingPointDesc,
const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo);
diff --git a/src/armnnTfParser/test/AddN.cpp b/src/armnnTfParser/test/AddN.cpp
new file mode 100644
index 0000000000..19affa85c7
--- /dev/null
+++ b/src/armnnTfParser/test/AddN.cpp
@@ -0,0 +1,180 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include <boost/assert.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include "armnnTfParser/ITfParser.hpp"
+#include "ParserPrototxtFixture.hpp"
+
+#include <map>
+#include <string>
+
+
+BOOST_AUTO_TEST_SUITE(TensorflowParser)
+
+struct AddNFixture : public armnnUtils::ParserPrototxtFixture<armnnTfParser::ITfParser>
+{
+ AddNFixture(const std::vector<armnn::TensorShape> 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<std::string, armnn::TensorShape> 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()