// // Copyright © 2017 Arm Ltd. All rights reserved. // SPDX-License-Identifier: MIT // #include "TfParser.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/types.pb.h" #include "tensorflow/core/framework/tensor.pb.h" #include "tensorflow/core/framework/tensor_shape.pb.h" #include #include #include #include #include #include #include #include #include #include using namespace armnn; namespace armnnTfParser { namespace { const PermutationVector NHWCToArmNN = { 0, 2, 3, 1 }; const PermutationVector ArmNNToNHWC = { 0, 3, 1, 2 }; IConnectableLayer* AddSwizzleLayer(INetwork& network, IOutputSlot& input, const PermutationVector& mapping, const std::string& name) { // Adds swizzle layer. IConnectableLayer* const layer = network.AddPermuteLayer(mapping, name.c_str()); // Connects intput to swizzle layer. input.Connect(layer->GetInputSlot(0)); // Sets up swizzled output. const TensorInfo outInfo = armnnUtils::Permuted(input.GetTensorInfo(), mapping); layer->GetOutputSlot(0).SetTensorInfo(outInfo); return layer; } IConnectableLayer* SwizzleInDeswizzleOut(INetwork& network, IOutputSlot& input, IConnectableLayer& layer, const std::string& name) { // Adds swizzle layer. IConnectableLayer* const swizzleLayer = AddSwizzleLayer(network, input, NHWCToArmNN, "swizzle_for-" + name); // Connects swizzledInput to layer. swizzleLayer->GetOutputSlot(0).Connect(layer.GetInputSlot(0)); // Adds deswizzle layer. IConnectableLayer* const deswizzleLayer = AddSwizzleLayer(network, layer.GetOutputSlot(0), ArmNNToNHWC, "deswizzle_for-" + name); return deswizzleLayer; } template void ReadMandatoryNodeAttributeImpl(const tensorflow::NodeDef& nodeDef, const std::string& attribName, tensorflow::AttrValue::ValueCase expectedValueCase, Callable callable) { auto iter = nodeDef.attr().find(attribName); if (iter != nodeDef.attr().end()) { const auto& attrValue = iter->second; if (attrValue.value_case() == expectedValueCase) { callable(attrValue); } else { throw ParseException( boost::str( boost::format( "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, " "but found %4% instead %5%") % attribName % nodeDef.name() % static_cast(expectedValueCase) % static_cast(attrValue.value_case()) % CHECK_LOCATION().AsString())); } } else { throw ParseException( boost::str( boost::format( "Could not find required attribute %1% in node %2% %3%") % attribName % nodeDef.name() % CHECK_LOCATION().AsString())); } } template void ReadOptionalNodeAttributeImpl(const tensorflow::NodeDef& nodeDef, const std::string& attribName, tensorflow::AttrValue::ValueCase expectedValueCase, Callable callable) { auto iter = nodeDef.attr().find(attribName); if (iter != nodeDef.attr().end()) { const auto& attrValue = iter->second; if (attrValue.value_case() == expectedValueCase) { callable(attrValue); } else { throw ParseException( boost::str( boost::format( "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, " "but found %4% instead %5%") % attribName % nodeDef.name() % static_cast(expectedValueCase) % static_cast(attrValue.value_case()) % CHECK_LOCATION().AsString())); } } } float ReadMandatoryNodeFloatAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name) { float attribValue = 0.0f; ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kF, [&attribValue](const tensorflow::AttrValue& attrValue) { attribValue = attrValue.f(); }); return attribValue; } uint32_t ReadMandatoryNodeUint32Attribute(const tensorflow::NodeDef& nodeDef, const std::string& name) { uint32_t attribValue = 0u; ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kI, [&attribValue](const tensorflow::AttrValue& attrValue) { attribValue = static_cast(attrValue.i()); }); return attribValue; } std::string ReadMandatoryNodeStringAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name) { std::string attribValue = ""; ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kS, [&attribValue](const tensorflow::AttrValue& attrValue) { attribValue = attrValue.s(); }); return attribValue; } std::vector ReadMandatoryNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name) { std::vector attriList; ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList, [&attriList](const tensorflow::AttrValue& attrValue) { for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum) { attriList.push_back(static_cast(attrValue.list().i(attriNum))); } }); return attriList; } std::vector ReadOptionalNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name) { std::vector attriList; ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList, [&attriList](const tensorflow::AttrValue& attrValue) { for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum) { attriList.push_back(static_cast(attrValue.list().i(attriNum))); } }); return attriList; } bool ReadOptionalNodeBoolAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name, bool defaultValue = false) { bool attribValue = defaultValue; ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kB, [&attribValue](const tensorflow::AttrValue& attrValue) { attribValue = attrValue.b(); }); return attribValue; } tensorflow::DataType ReadMandatoryNodeTypeAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name) { tensorflow::DataType attribValue = tensorflow::DT_INVALID; ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kType, [&attribValue](const tensorflow::AttrValue& attrValue) { attribValue = attrValue.type(); }); return attribValue; } TensorInfo PrepareReshape(const TensorInfo& input, const std::vector& targetDims) { std::vector outDims(targetDims.begin(), targetDims.end()); const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1); if (stretchDim != targetDims.end()) { if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end()) { throw ParseException( boost::str( boost::format( "At most one component of shape can be -1 %1%") % CHECK_LOCATION().AsString())); } auto targetNumElements = boost::numeric_cast( std::accumulate(targetDims.begin(), targetDims.end(), -1, std::multiplies())); auto stretchIndex = static_cast(std::distance(targetDims.begin(), stretchDim)); outDims[stretchIndex] = input.GetNumElements() / targetNumElements; } TensorInfo reshapeInfo = input; reshapeInfo.SetShape(TensorShape{ static_cast(outDims.size()), outDims.data() }); return reshapeInfo; } // We need the input0Slot to guide the reshape for input1Slot. IOutputSlot* AddBroadcastReshapeLayer(IOutputSlot* input0Slot, IOutputSlot* input1Slot, bool isNHWC, INetwork& m_Network, const tensorflow::NodeDef& nodeDef) { const TensorInfo& input1Info = input1Slot->GetTensorInfo(); const TensorInfo inputTensorInfo = input0Slot->GetTensorInfo(); const unsigned int matchDim = inputTensorInfo.GetNumDimensions() - (isNHWC ? 1 : 3); std::array reshapedDimensions; std::fill_n(reshapedDimensions.begin(), inputTensorInfo.GetNumDimensions(), 1); reshapedDimensions[matchDim] = input1Info.GetShape()[0]; armnn::TensorInfo reshapedInfo = input1Info; reshapedInfo.SetShape(TensorShape{ inputTensorInfo.GetNumDimensions(), reshapedDimensions.data() }); const std::string reshapeLayerName = "reshape_for-" + nodeDef.name(); ReshapeDescriptor reshapeDesc; reshapeDesc.m_TargetShape = reshapedInfo.GetShape(); IConnectableLayer* const reshapeLayer = m_Network.AddReshapeLayer(reshapeDesc, reshapeLayerName.c_str()); input1Slot->Connect(reshapeLayer->GetInputSlot(0)); reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo); input1Slot = &reshapeLayer->GetOutputSlot(0); return input1Slot; } OutputId ParseOutputId(const std::string & name) { unsigned int outputNum = 0; size_t colonPos = name.find_last_of(":"); if (colonPos != std::string::npos) { int n = std::stoi(name.substr(colonPos+1)); if (n<0 || n>100) { throw ParseException( boost::str( boost::format( "Output tensor id is out of range for %1% %2%") % name % CHECK_LOCATION().AsString())); } outputNum = static_cast(n); } return OutputId(name.substr(0,colonPos),outputNum); } #define CHECK_DATA_FORMAT(NODE_DEF, FORMAT, NODE_TYPE) \ if( FORMAT != "NHWC" && FORMAT != "NCHW" ) \ { \ throw ParseException( \ boost::str( \ boost::format( \ "Unsupported data format %1% passed for %2% node %3%. " \ "Only NHWC and NCHW supported %4%") \ % FORMAT \ % NODE_TYPE \ % NODE_DEF.name() \ % CHECK_LOCATION().AsString())); \ } #define CHECK_PADDING_TYPE(NODE_DEF, PADDING) \ if(PADDING != "SAME" && PADDING != "VALID" ) \ { \ throw ParseException( \ boost::str( \ boost::format( \ "Only 'SAME' and 'VALID' padding supported. Got %1% for %2% %3%") \ % PADDING \ % NODE_DEF.name() \ % CHECK_LOCATION().AsString())); \ } \ } // namespace const std::map TfParser::ms_OperationNameToParsingFunctions = { { "Const", &TfParser::ParseConst }, { "Add", &TfParser::ParseAdd }, { "BiasAdd", &TfParser::ParseBiasAdd }, { "Identity", &TfParser::ParseIdentity }, { "Conv2D", &TfParser::ParseConv2D }, { "DepthwiseConv2dNative", &TfParser::ParseDepthwiseConv2D }, { "FusedBatchNorm", &TfParser::ParseFusedBatchNorm }, { "ConcatV2", &TfParser::ParseConcat }, { "LRN", &TfParser::ParseLrn }, { "MatMul", &TfParser::ParseMatMul }, { "Mul", &TfParser::ParseMul }, { "Placeholder", &TfParser::ParsePlaceholder }, { "RealDiv", &TfParser::ParseRealDiv }, { "Relu", &TfParser::ParseRelu }, { "Relu6", &TfParser::ParseRelu6 }, { "Reshape", &TfParser::ParseReshape }, { "ResizeBilinear", &TfParser::ParseResizeBilinear }, { "Shape", &TfParser::ParseShape }, { "Squeeze", &TfParser::ParseSqueeze }, { "Sigmoid", &TfParser::ParseSigmoid }, { "Softmax", &TfParser::ParseSoftmax }, { "Softplus", &TfParser::ParseSoftplus }, { "Tanh", &TfParser::ParseTanh }, { "MaxPool", &TfParser::ParseMaxPool }, { "AvgPool", &TfParser::ParseAvgPool }, { "Maximum", &TfParser::ParseMaximum }, }; ITfParser* ITfParser::CreateRaw() { return new TfParser(); } ITfParserPtr ITfParser::Create() { return ITfParserPtr(CreateRaw(), &ITfParser::Destroy); } void ITfParser::Destroy(ITfParser* parser) { delete parser; } inline void CalculateSamePadding(uint32_t inputSize, uint32_t stride, uint32_t filterSize, bool samePadding, uint32_t* paddingFront, uint32_t* paddingBack) { *paddingFront = 0; *paddingBack = 0; if (samePadding) { uint32_t outputSize = (inputSize + stride - 1) / stride; uint32_t temp = (outputSize - 1) * stride + filterSize; if (temp > inputSize) { *paddingFront = (temp - inputSize) / 2; *paddingBack = (temp - inputSize) - *paddingFront; } } } void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail, bool samePadding) { CalculateSamePadding(input, stride, kernel, samePadding, &outPadHead, &outPadTail); } /// An Abstract base class which represents a single tensorflow operation (node) /// that has been (potentially partially) converted to Armnn. /// It may not yet have been fully converted into actual Armnn layers. class ParsedTfOperation { public: ParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node) : m_Parser(parser) , m_Node(node) { } virtual ~ParsedTfOperation() {}; const tensorflow::NodeDef& GetNode() const { return m_Node; } /// Gets the ArmNN IOutputSlot corresponding to the given output index of the Tensorflow operation. /// This may result in the creation of Armnn layers if this was deferred (e.g. see ParsedConstTfOperation). virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) = 0; /// If this operation is an Identity then this will follow return the 'parent' operation (recursively). virtual ParsedTfOperation* ResolveIdentityOperations() { return this; } protected: TfParser* m_Parser; const tensorflow::NodeDef& m_Node; }; /// An ParsedTfOperation where the Armnn equivalent is a single layer, /// with output slots that correspond directly to the Tf node outputs. class SingleLayerParsedTfOperation : public ParsedTfOperation { public: SingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node, IConnectableLayer* layer) : ParsedTfOperation(parser, node) , m_Layer(layer) { } IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override { BOOST_ASSERT(m_Layer); // Assumes one-to-one mapping between Tf and armnn output slots. unsigned int armnnOutputSlotIdx = tfOutputIndex; if (armnnOutputSlotIdx >= m_Layer->GetNumOutputSlots()) { throw ParseException( boost::str( boost::format( "The requested output slot #%1% " "for %2% does not exist %3%") % armnnOutputSlotIdx % m_Layer->GetName() % CHECK_LOCATION().AsString())); } return m_Layer->GetOutputSlot(armnnOutputSlotIdx); } protected: IConnectableLayer* m_Layer; }; /// A SingleLayerParsedTfOperation for deferred layer creation. class DeferredSingleLayerParsedTfOperation : public SingleLayerParsedTfOperation { public: DeferredSingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node) : SingleLayerParsedTfOperation(parser, node, nullptr) { } IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override { if (!m_Layer) { CreateLayerDeferred(); } return SingleLayerParsedTfOperation::ResolveArmnnOutputSlot(tfOutputIndex); } private: virtual void CreateLayerDeferred() = 0; }; TfParser::TfParser() : m_Network(nullptr, nullptr) { } const tensorflow::NodeDef* TfParser::ResolveIdentityNode(const tensorflow::NodeDef* nodeDef) { if (nodeDef->op() != "Identity") { return nodeDef; } if (nodeDef->input_size() != 1) { throw ParseException( boost::str( boost::format( "Identity node should have a single input! %1% has %2% inputs %3%") % nodeDef->name() % nodeDef->input_size() % CHECK_LOCATION().AsString())); } auto it = m_NodesByName.find(nodeDef->input(0)); if (it != m_NodesByName.end()) { const tensorflow::NodeDef* inputNode = it->second; return ResolveIdentityNode(inputNode); } else { throw ParseException( boost::str( boost::format( "Cannot find what the Identity node %1% is linked to! %2%") % nodeDef->name() % CHECK_LOCATION().AsString())); } } std::vector TfParser::GetTfInputNodes(const tensorflow::NodeDef& nodeDef) const { std::vector ret; if (nodeDef.op() == "Const") { // For some reason const node can have "Control Inputs". We ignore them for now. return ret; } ret.reserve(boost::numeric_cast(nodeDef.input_size())); for (int j = 0; j < nodeDef.input_size(); ++j) { OutputId outputId = ParseOutputId(nodeDef.input(j)); if (nodeDef.input(j)[0] == '^') // I couldn't find a better test for control inputs. { throw ParseException( boost::str( boost::format( "Node '%1%' has Control Input '%2%' for input #%3% which is unsupported. %4%") % nodeDef.name() % nodeDef.input(j) % j % CHECK_LOCATION().AsString())); } auto inputIt = m_NodesByName.find(outputId.m_IndexedValue); if (inputIt == m_NodesByName.end()) { throw ParseException( boost::str( boost::format( "Can't find node '%1%', which is listed as an input of '%2%' %3%") % nodeDef.input(j) % nodeDef.name() % CHECK_LOCATION().AsString())); } ret.push_back(OutputOfConstNodeDef(inputIt->second,outputId.m_Index)); } return ret; } std::vector TfParser::GetInputParsedTfOperationsChecked(const tensorflow::NodeDef& nodeDef, std::size_t expectedNumInputs) { // Fetches the tensorflow nodes connected as inputs and validate the size. std::vector nodes = GetTfInputNodes(nodeDef); const std::size_t numInputs = nodes.size(); if (numInputs != expectedNumInputs) { throw ParseException( boost::str( boost::format( "Unexpected number of inputs for node %1%. Expected %2%, found %3% %4%") % nodeDef.name() % expectedNumInputs % numInputs % CHECK_LOCATION().AsString())); } // Fetches the corresponding ParsedTfOperation operations std::vector result; for (auto&& node : nodes) { auto it = m_ParsedTfOperations.find(node.m_IndexedValue->name()); if (it == m_ParsedTfOperations.end()) { throw ParseException( boost::str( boost::format( "Node with name '%1%' has not been parsed %2%") % node.m_IndexedValue->name() % CHECK_LOCATION().AsString())); } ParsedTfOperation* parsedOp = it->second.get(); // Transparently 'skip' any Identity operations. This simplifies the logic inside the ParseXXX() functions. parsedOp = parsedOp->ResolveIdentityOperations(); result.push_back(OutputOfParsedTfOperation(parsedOp,node.m_Index)); } return result; } ParsedTfOperationPtr TfParser::ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); // If one of the inputs is a MatMul and the other is a const, then we handle both nodes // together as FullyConnected. if (inputs[0].m_IndexedValue->GetNode().op() == "MatMul" && HasParsedConstTensor(inputs[1].m_IndexedValue->GetNode().name())) { IConnectableLayer* layer = AddFullyConnectedLayer(inputs[0].m_IndexedValue->GetNode(), &nodeDef,nodeDef.name().c_str()); return std::make_unique(this, nodeDef, layer); } else if (HasParsedConstTensor(inputs[0].m_IndexedValue->GetNode().name()) && inputs[1].m_IndexedValue->GetNode().op() == "MatMul") { IConnectableLayer* layer = AddFullyConnectedLayer(inputs[1].m_IndexedValue->GetNode(), &nodeDef,nodeDef.name().c_str()); return std::make_unique(this, nodeDef, layer); } else { // Otherwise it's just a regular addition. return AddAdditionLayer(nodeDef); } } ParsedTfOperationPtr TfParser::ParseBiasAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { return AddAdditionLayer(nodeDef, true); } /// An ParsedTfOperation which forwards to another (used for Identity nodes). class ParsedIdentityTfOperation : public ParsedTfOperation { public: ParsedIdentityTfOperation(TfParser* parser, const tensorflow::NodeDef& node, ParsedTfOperation* representative) : ParsedTfOperation(parser, node) , m_Representative(representative) { } virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override { BOOST_ASSERT(m_Representative); return m_Representative->ResolveArmnnOutputSlot(tfOutputIndex); } virtual ParsedTfOperation* ResolveIdentityOperations() override { return m_Representative->ResolveIdentityOperations(); } private: ParsedTfOperation* m_Representative; }; ParsedTfOperationPtr TfParser::ParseIdentity(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 1); // Any requests for the output slots of this node should be forwarded to the node connected as input. return std::make_unique(this, nodeDef, inputs[0].m_IndexedValue); } /// An ParsedTfOperation for a Const node. /// Creation of the armnn ConstLayer is deferred until it is actually needed, because Const nodes are mostly used /// for weight inputs to MatMul/Conv2D nodes and in these cases armnn doesn't need a ConstLayer. template class ParsedConstTfOperation : public DeferredSingleLayerParsedTfOperation { public: ParsedConstTfOperation(TfParser* parser, const tensorflow::NodeDef& node, const T* tensorData, const TensorInfo& tensorInfo) : DeferredSingleLayerParsedTfOperation(parser, node), m_Storage(tensorData, tensorData + tensorInfo.GetNumElements()), m_TensorInfo(tensorInfo) { BOOST_ASSERT(tensorInfo.GetDataType() == GetDataType()); } void CreateLayerDeferred() override { BOOST_ASSERT(m_Layer == nullptr); m_Layer = m_Parser->m_Network->AddConstantLayer(ConstTensor(m_TensorInfo, m_Storage), m_Node.name().c_str()); m_Layer->GetOutputSlot(0).SetTensorInfo(m_TensorInfo); } ConstTensor GetConstTensor(bool swizzleForConvolutionWeights, std::vector& outputTensorData) const { // Mappings from TensorFlow filter tensors to the ArmNN filter tensors. // Tensorflow weights are [H, W, In, Out]. // ArmNN weights are [Out, In, H, W]. static const PermutationVector HWIOToOIHW = {2, 3, 1, 0}; const TensorInfo outInfo = swizzleForConvolutionWeights ? armnnUtils::Permuted(m_TensorInfo, HWIOToOIHW) : m_TensorInfo; outputTensorData.resize(m_TensorInfo.GetNumElements()); // Copies or swizzles from the permanent storage into the storage the caller provided. if (swizzleForConvolutionWeights) { armnnUtils::Permute(outInfo.GetShape(), HWIOToOIHW, m_Storage.data(), outputTensorData.data()); } else { memcpy(outputTensorData.data(), m_Storage.data(), m_TensorInfo.GetNumBytes()); } // Updates the result to point to the user provided storage. ConstTensor constTensor(outInfo, outputTensorData); return constTensor; } private: ///< Manages the lifetime of the tensor data. std::vector m_Storage; ///< Describes the layout of the tensor and points to the data in m_Storage. TensorInfo m_TensorInfo; }; DataType ConvertTfTensorDataType(const tensorflow::DataType tfDataType, const tensorflow::NodeDef& nodeDef) { switch (tfDataType) { case tensorflow::DT_FLOAT: return DataType::Float32; break; case tensorflow::DT_INT32: return DataType::Signed32; break; default: throw ParseException( boost::str( boost::format( "Unknown DataType %1% for node %2% %3%") % tensorflow::DataType_Name(tfDataType) % nodeDef.name() % CHECK_LOCATION().AsString())); } } struct ParseTfTensorValueList { template static void Parse( const tensorflow::TensorProto& tfTensor, unsigned int dstElements, std::vector& outputData); template static void ReadData(const void* srcData, unsigned int numSrcElements, std::vector& dstData, unsigned int numDstElements) { // If there are no entries in the list, perform no action. if (numSrcElements == 0) { return; } // If no size was provided, use the length of the value list. if (numDstElements == 0) { numDstElements = numSrcElements; } // Allocates memory. dstData.resize(std::max(numSrcElements, numDstElements) * sizeof(DataType)); const DataType* srcTensor = reinterpret_cast(srcData); DataType* dstTensor = reinterpret_cast(dstData.data()); // Copies the value list entries into the destination. std::copy(srcTensor, srcTensor + numSrcElements, dstTensor); if (numDstElements > numSrcElements) { // Uses the last element in the list to fill the remaining entries. std::fill(dstTensor + numSrcElements, dstTensor + numDstElements, srcTensor[numSrcElements - 1]); } } }; template <> void ParseTfTensorValueList::Parse(const tensorflow::TensorProto& tfTensor, unsigned int dstElements, std::vector& outputData) { ReadData(tfTensor.float_val().data(), static_cast(tfTensor.float_val_size()), outputData, dstElements); } template <> void ParseTfTensorValueList::Parse(const tensorflow::TensorProto& tfTensor, unsigned int dstElements, std::vector& outputData) { ReadData(tfTensor.int_val().data(), static_cast(tfTensor.int_val_size()), outputData, dstElements); } template class OperatorType, typename T = int8_t> struct MakeTfOperation { template inline static std::unique_ptr> Parse(TfParser* parser, const tensorflow::NodeDef& node, Args&&... args) { return std::make_unique>(parser, node, std::forward(args)...); } }; template <> struct MakeTfOperation { template inline static std::unique_ptr> Parse(TfParser* parser, const tensorflow::NodeDef& node, const std::vector& tensorData, const TensorInfo& tensorInfo) { return std::make_unique>(parser, node, reinterpret_cast(tensorData.data()), tensorInfo); } }; template struct InvokeParseFunction { template inline static ResType Result(DataType dataType, Args&&... args) { if (dataType == DataType::Float32) { return FuncType::template Parse(std::forward(args)...); } else if (dataType == DataType::Signed32) { return FuncType::template Parse(std::forward(args)...); } return ResType(); } template inline static void Result(DataType dataType, Args&&... args) { if (dataType == DataType::Float32) { FuncType::template Parse(std::forward(args)...); } else if (dataType == DataType::Signed32) { FuncType::template Parse(std::forward(args)...); } } }; ParsedTfOperationPtr TfParser::ParseConst(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { BOOST_ASSERT(nodeDef.op() == "Const"); if (nodeDef.attr().count("value") == 0) { throw ParseException( boost::str( boost::format( "Value not found for Const node - %1% %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } const tensorflow::TensorProto& tfTensor = nodeDef.attr().at("value").tensor(); const tensorflow::TensorShapeProto& tfTensorShape = tfTensor.tensor_shape(); const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "dtype"); const auto GetDimensionSize = [](auto& d) { return d.size(); }; std::vector dimensionSizes; std::transform(tfTensorShape.dim().begin(), tfTensorShape.dim().end(), std::back_inserter(dimensionSizes), GetDimensionSize); // Calculates number of elements. const DataType dataType = ConvertTfTensorDataType(tfDataType, nodeDef); unsigned int numElements = 0U; if (!dimensionSizes.empty()) { numElements = std::accumulate(dimensionSizes.begin(), dimensionSizes.end(), 1U, std::multiplies()); } std::vector tensorData; // Get tensor data from the list of values attribute. if (tfTensor.tensor_content().empty()) { InvokeParseFunction::Result(dataType, tfTensor, numElements, tensorData); // If the tensor shape is not defined, but there is a value list, then interpret the data as a 1D // tensor of the provided number of elements. if (numElements == 0) { const unsigned int tfNumElements = static_cast(tensorData.size()) / GetDataTypeSize(dataType); dimensionSizes.push_back(tfNumElements); } } // Gets tensor data from tensor content attribute. else { tensorData.assign(tfTensor.tensor_content().begin(), tfTensor.tensor_content().end()); // Checks if a tensor shape is defined for the tensor content. if (numElements == 0) { throw ParseException( boost::str( boost::format( "No tensor shape found for Const node - %1% %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } } // Const node requires at least a list of values or a content attribute. if (tensorData.empty()) { throw ParseException( boost::str( boost::format( "No tensor data found for Const node - %1% %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } const TensorInfo tensorInfo(static_cast(dimensionSizes.size()), dimensionSizes.data(), dataType); // If we have a list of values, then the length of the list must be // less than or equal to the number of elements implied by the shape argument. if (tensorData.size() > tensorInfo.GetNumBytes()) { throw ParseException( boost::str( boost::format( "Number of elements (%1%) should be less than or equal " "to the number of elements implied by the shape argument (%2%) for Const node - %3% %4%") % (tensorData.size() / GetDataTypeSize(dataType)) % tensorInfo.GetNumElements() % nodeDef.name() % CHECK_LOCATION().AsString())); } return InvokeParseFunction>::Result( dataType, this, nodeDef, tensorData, tensorInfo); } template bool TfParser::HasParsedConstTensor(const std::string & nodeName) const { auto it = m_ParsedTfOperations.find(nodeName); if (it == m_ParsedTfOperations.end() || dynamic_cast*>(it->second.get()) == nullptr) { return false; } else { return true; } } ParsedTfOperationPtr TfParser::ParseConv2D(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); TensorInfo inputTensorInfo = inputSlot.GetTensorInfo(); if (!HasParsedConstTensor(inputs[1].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports Convolution layers with constant weights for %1%, input %2% %3%") % nodeDef.name() % inputs[1].m_IndexedValue->GetNode().name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* weightNode = boost::polymorphic_downcast *>(inputs[1].m_IndexedValue); std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding"); std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format"); std::vector strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides"); // Read the dilations, if present - only [1,1,1,1] (the default) is supported. std::vector dilations = ReadOptionalNodeUint32ListAttribute(nodeDef, "dilations"); if (!dilations.empty()) { for (auto dilation : dilations) { if (dilation != 1u) { throw ParseException( boost::str( boost::format( "ArmNN only supports Convolution layers with dilations [1,1,1,1] for %1% %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } } } Convolution2dDescriptor desc; desc.m_BiasEnabled = false; CHECK_DATA_FORMAT(nodeDef, dataFormat, "Conv2D"); if (dataFormat == "NHWC") { desc.m_StrideX = strides[2]; desc.m_StrideY = strides[1]; // Swizzles input to supported memory layout. inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN); } else if (dataFormat == "NCHW") { desc.m_StrideX = strides[3]; desc.m_StrideY = strides[2]; } uint32_t inputHeight = inputTensorInfo.GetShape()[2]; uint32_t inputWidth = inputTensorInfo.GetShape()[3]; std::vector outputTensorData; ConstTensor weightTensor = weightNode->GetConstTensor(true, outputTensorData); uint32_t weightHeight = weightTensor.GetShape()[2]; uint32_t weightWidth = weightTensor.GetShape()[3]; bool padding = false; TensorInfo outputInfo; CHECK_PADDING_TYPE(nodeDef, paddingString); if (paddingString == "SAME") { padding = true; outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0], weightTensor.GetShape()[0], static_cast(ceil( static_cast(inputHeight) / static_cast(desc.m_StrideY))), static_cast(ceil( static_cast(inputWidth) / static_cast(desc.m_StrideX))) }, DataType::Float32); } else if (paddingString == "VALID") { padding = false; outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0], weightTensor.GetShape()[0], static_cast(ceil( static_cast(inputHeight - weightHeight + 1) / static_cast(desc.m_StrideY))), static_cast(ceil( static_cast(inputWidth - weightWidth + 1) / static_cast(desc.m_StrideX))) }, DataType::Float32); } CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding); CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding); IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str()); layer->GetOutputSlot(0).SetTensorInfo(outputInfo); if (dataFormat == "NHWC") { layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name()); } else { inputSlot.Connect(layer->GetInputSlot(0)); } return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseDepthwiseConv2D(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); TensorInfo inputTensorInfo = inputSlot.GetTensorInfo(); if (!HasParsedConstTensor(inputs[1].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports Depthwise Convolution layer with constant weights. " "Non const input found %1% for node %2% %3%") % inputs[1].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* weightNode = boost::polymorphic_downcast *>(inputs[1].m_IndexedValue); std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding"); std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format"); std::vector strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides"); DepthwiseConvolution2dDescriptor desc; desc.m_BiasEnabled = false; CHECK_DATA_FORMAT(nodeDef, dataFormat, "DepthwiseConv2dNative"); if (dataFormat == "NHWC") { desc.m_StrideX = strides[2]; desc.m_StrideY = strides[1]; // Swizzles input to supported memory layout. inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN); } else if (dataFormat == "NCHW") { desc.m_StrideX = strides[3]; desc.m_StrideY = strides[2]; } uint32_t inputHeight = inputTensorInfo.GetShape()[2]; uint32_t inputWidth = inputTensorInfo.GetShape()[3]; std::vector outputTensorData; ConstTensor weightTensor = weightNode->GetConstTensor(true, outputTensorData); uint32_t weightHeight = weightTensor.GetShape()[2]; uint32_t weightWidth = weightTensor.GetShape()[3]; bool padding = false; TensorInfo outputInfo; CHECK_PADDING_TYPE(nodeDef, paddingString); if (paddingString == "SAME") { padding = true; outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0], weightTensor.GetShape()[0] * weightTensor.GetShape()[1], static_cast(ceil( static_cast(inputHeight) / static_cast(desc.m_StrideY))), static_cast(ceil( static_cast(inputWidth) / static_cast(desc.m_StrideX))) }, DataType::Float32); } else if (paddingString == "VALID") { padding = false; outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0], weightTensor.GetShape()[0] * weightTensor.GetShape()[1], static_cast(ceil( static_cast(inputHeight - weightHeight + 1) / static_cast(desc.m_StrideY))), static_cast(ceil( static_cast(inputWidth - weightWidth + 1) / static_cast(desc.m_StrideX))) }, DataType::Float32); } CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding); CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding); IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str()); layer->GetOutputSlot(0).SetTensorInfo(outputInfo); if (dataFormat == "NHWC") { layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name()); } else { inputSlot.Connect(layer->GetInputSlot(0)); } return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseFusedBatchNorm(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 5); if (!HasParsedConstTensor(inputs[1].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports FusedBatchNormalization layers with constant scale. " "Input %1%. Node %2% %3%") % inputs[1].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* scaleNode = boost::polymorphic_downcast *>(inputs[1].m_IndexedValue); if (!HasParsedConstTensor(inputs[2].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports FusedBatchNormalization layers with constant offset. " "Input %1%. Node %2% %3%") % inputs[2].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* offsetNode = boost::polymorphic_downcast *>(inputs[2].m_IndexedValue); if (!HasParsedConstTensor(inputs[3].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports FusedBatchNormalization layers with constant mean. " "Input %1%. Node %2% %3%") % inputs[3].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* meanNode = boost::polymorphic_downcast *>(inputs[3].m_IndexedValue); if (!HasParsedConstTensor(inputs[4].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports FusedBatchNormalization layers with constant variance. " "Input %1%. Node %2% %3%") % inputs[4].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* varianceNode = boost::polymorphic_downcast *>(inputs[4].m_IndexedValue); // The descriptor only has the epsilon attribute. BatchNormalizationDescriptor desc; desc.m_Eps = ReadMandatoryNodeFloatAttribute(nodeDef, "epsilon"); // Data for the parsed tensor args (scale, offset, mean, variance) must be stored // locally until the layer is added. std::vector scaleTensorData; ConstTensor scaleTensor = scaleNode->GetConstTensor(false, scaleTensorData); std::vector offsetTensorData; ConstTensor offsetTensor = offsetNode->GetConstTensor(false, offsetTensorData); std::vector meanTensorData; ConstTensor meanTensor = meanNode->GetConstTensor(false, meanTensorData); std::vector varianceTensorData; ConstTensor varianceTensor = varianceNode->GetConstTensor(false, varianceTensorData); IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc, meanTensor, varianceTensor, offsetTensor, scaleTensor, nodeDef.name().c_str()); IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format"); if (dataFormat == "NHWC") { const TensorInfo outputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN); layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo); layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name()); } else { layer->GetOutputSlot(0).SetTensorInfo(inputSlot.GetTensorInfo()); inputSlot.Connect(layer->GetInputSlot(0)); } return std::make_unique(this, nodeDef, layer); } bool TfParser::IsSupportedLeakyReluPattern(const tensorflow::NodeDef& mulNodeDef, size_t alphaLayerIndex, const OutputOfParsedTfOperation& otherOp, armnn::IOutputSlot** outputOfLeakyRelu, armnn::ActivationDescriptor & desc) { const tensorflow::NodeDef& otherNodeDef = otherOp.m_IndexedValue->GetNode(); // Verifying all these assumptions hold: // // 1, the mulNodeDef is an elementwise multiplication node "Mul" // 2, the alphaLayerIndex selects a constant node from the inputs of the "Mul" node // 3, the inputLayerIndex selects a layer which has the same name as otherNodeDef // if (mulNodeDef.op() == "Mul") { size_t otherLayerIndex = (alphaLayerIndex == 0 ? 1 : 0); std::vector inputs = GetInputParsedTfOperationsChecked(mulNodeDef, 2); BOOST_ASSERT(inputs.size() == 2); BOOST_ASSERT((otherLayerIndex == 0 || alphaLayerIndex == 0)); BOOST_ASSERT((otherLayerIndex == 1 || alphaLayerIndex == 1)); BOOST_ASSERT(((otherLayerIndex + alphaLayerIndex) == 1)); if (inputs[otherLayerIndex].m_IndexedValue->GetNode().name() == otherNodeDef.name()) { if (HasParsedConstTensor(inputs[alphaLayerIndex].m_IndexedValue->GetNode().name())) { ParsedConstTfOperation* alpha = boost::polymorphic_downcast *>( inputs[alphaLayerIndex].m_IndexedValue); std::vector const_data; ConstTensor const_tensor = alpha->GetConstTensor(false, const_data); if (const_data.size() == 1) { desc.m_Function = ActivationFunction::LeakyReLu; desc.m_A = const_data[0]; *outputOfLeakyRelu = &(otherOp.m_IndexedValue->ResolveArmnnOutputSlot(otherOp.m_Index)); return true; } } } } return false; } // For max nodes, we only support those as part of a leaky relu, i.e., // as part for a max(mul(a, x), x) expression. We thus need to // identify one input as a multiplication with a scalar constant, // extract the constant and the two inputs, verify that the two other // inputs are the same node, and then create a leaky relu node. ParsedTfOperationPtr TfParser::ParseMaximum(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); auto inputNode0 = inputs[0].m_IndexedValue->GetNode(); auto inputNode1 = inputs[1].m_IndexedValue->GetNode(); IOutputSlot* outputOfLeakyRelu = nullptr; ActivationDescriptor desc; // There are four possible scenarios we need to support (respectively below): // 1, max(mul(a, x), x) // 2, max(mul(x, a), x) // 3, max(x, mul(a, x)) // 4, max(x, mul(x, a)) if (IsSupportedLeakyReluPattern(inputNode0, 0, inputs[1], &outputOfLeakyRelu, desc) || IsSupportedLeakyReluPattern(inputNode0, 1, inputs[1], &outputOfLeakyRelu, desc) || IsSupportedLeakyReluPattern(inputNode1, 0, inputs[0], &outputOfLeakyRelu, desc) || IsSupportedLeakyReluPattern(inputNode1, 1, inputs[0], &outputOfLeakyRelu, desc)) { BOOST_ASSERT(outputOfLeakyRelu != nullptr); IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, nodeDef.name().c_str()); outputOfLeakyRelu->Connect(layer->GetInputSlot(0)); layer->GetOutputSlot(0).SetTensorInfo(outputOfLeakyRelu->GetTensorInfo()); return std::make_unique(this, nodeDef, layer); } else { throw ParseException( boost::str( boost::format( "ArmNN currenly offers limited support for Maximum node when it can be fused to " "form a LeakyRelu activation as leakyrelu=max(mul(alpha, X), X). " "Node: %1% %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } } ParsedTfOperationPtr TfParser::ParseConcat(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector nodes = GetTfInputNodes(nodeDef); // In tensorflow, we have the last input of the Concat layer as the axis for concatenation. unsigned int numInputs = static_cast(nodes.size()); unsigned int numConcatView = numInputs - 1; OriginsDescriptor concatDescriptor(static_cast(numConcatView), MaxNumOfTensorDimensions); std::vectormergeDimSizes(MaxNumOfTensorDimensions, 0u); unsigned int mergeDim = 0; std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs); // The last input is the axis for concatenation. if (!HasParsedConstTensor(inputs[numInputs - 1].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports Concat with constant axis. " "Input %1%. Node %2% %3%") % inputs[numInputs - 1].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* shapeNode = boost::polymorphic_downcast*>(inputs[numInputs - 1].m_IndexedValue); std::vector axisTensorData; ConstTensor axisTensor = shapeNode->GetConstTensor(false, axisTensorData); // This concatDim indicates the data format: 3 is the NHWC, 1 is the NCHW. const unsigned int concatDimInput = static_cast(axisTensorData[0]); // Armnn supports concatenation along the channel dimension for data formats NHWC and NCHW. if (concatDimInput == 0 || concatDimInput == 2) { throw ParseException( boost::str( boost::format( "Dimension %1% for concatenation is not supported by Armnn. " "Node %2% %3%") % concatDimInput % nodeDef.name() % CHECK_LOCATION().AsString())); } // This is the only concatDim we support in armnn. const unsigned int concatDim = 1; for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex) { // Need to double check whether it should be IOutputSlot& inputSlot = inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index); TensorInfo inputTensorInfo = inputSlot.GetTensorInfo(); // process the input tensor info armnnUtils::ProcessConcatInputTensorInfo(inputTensorInfo, concatDescriptor, concatDimInput, viewIndex, mergeDimSizes, mergeDim); } mergeDimSizes[concatDim] = mergeDim; armnn::IConnectableLayer *layer = m_Network->AddMergerLayer(concatDescriptor, nodeDef.name().c_str()); layer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(MaxNumOfTensorDimensions, mergeDimSizes.data(), DataType::Float32)); for (unsigned int v = 0; v < numConcatView; ++v) { IOutputSlot& inputSlot = inputs[v].m_IndexedValue->ResolveArmnnOutputSlot(inputs[v].m_Index); if (concatDimInput == 3) { IConnectableLayer* const swizzleLayer = AddSwizzleLayer(*m_Network, inputSlot, NHWCToArmNN, "swizzle_for-" + nodeDef.name()); swizzleLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(v)); } else { inputSlot.Connect(layer->GetInputSlot(v)); } } if (concatDimInput == 3) { IConnectableLayer* const deswizzleLayer = AddSwizzleLayer(*m_Network, layer->GetOutputSlot(0), ArmNNToNHWC, "deswizzle_for-" + nodeDef.name()); layer = deswizzleLayer; } return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseShape(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { // Note: the Shape layer is handled in a special way, because: // 1. ARMNN doesn't support int32 tensors which it outputs. // 2. ARMNN works with statically shaped tensors which are known at parse time. // 3. because of 1. and 2. we treat the output of Shape as a temporary const int32 // tensor which may be used as an input to other ops, most likely a Reshape. const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "out_type"); if (tfDataType != tensorflow::DT_INT32) { throw ParseException( boost::str( boost::format( "Armnn only supports DT_INT32 as out_type. Got %1% for Node %2% %3%") % tensorflow::DataType_Name(tfDataType) % nodeDef.name() % CHECK_LOCATION().AsString())); } const std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 1); IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); const TensorInfo& prevLayerTensorInfo = prevLayerOutputSlot.GetTensorInfo(); unsigned int prevLayerDimensions = prevLayerTensorInfo.GetNumDimensions(); std::vector shapeTensorData; shapeTensorData.reserve(prevLayerDimensions); for (unsigned int i=0; i(prevLayerTensorInfo.GetShape()[i])); } TensorInfo shapeTensorInfo(1, &prevLayerDimensions, DataType::Signed32); return std::make_unique>(this, nodeDef, &shapeTensorData[0], shapeTensorInfo); } ParsedTfOperationPtr TfParser::ParseReshape(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); ParsedTfOperation* inputNode = inputs[0].m_IndexedValue; if (!HasParsedConstTensor(inputs[1].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports Reshape layers with constant shapes. " "Input %1% Node %2% %3%") % inputs[1].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* shapeNode = boost::polymorphic_downcast*>(inputs[1].m_IndexedValue); armnn::IOutputSlot& prevLayerOutputSlot = inputNode->ResolveArmnnOutputSlot(inputs[0].m_Index); TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo(); std::vector shapeTensorData; ConstTensor shapeTensor = shapeNode->GetConstTensor(false, shapeTensorData); const TensorInfo outputTensorInfo = PrepareReshape(inputTensorInfo, shapeTensorData); TensorShape targetShape = outputTensorInfo.GetShape(); ReshapeDescriptor reshapeDesc; reshapeDesc.m_TargetShape = targetShape; IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str()); prevLayerOutputSlot.Connect(layer->GetInputSlot(0)); layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo); return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseResizeBilinear(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); if (!HasParsedConstTensor(inputs[1].m_IndexedValue->GetNode().name())) { throw ParseException( boost::str( boost::format( "ArmNN only supports ResizeBilinear layers with constant sizes. " "Input %1%. Node %2% %3%") % inputs[1].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } ParsedConstTfOperation* sizeNode = boost::polymorphic_downcast*>(inputs[1].m_IndexedValue); // Checks the align_corners attribute is not set. if (ReadOptionalNodeBoolAttribute(nodeDef, "align_corners", false)) { throw ParseException( boost::str( boost::format( "ArmNN only supports ResizeBilinear layers with align_corners set to false. " "Node %1% %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } // Data for the parsed tensor args (size) must be stored locally. std::vector sizeTensorData; ConstTensor sizeTensor = sizeNode->GetConstTensor(false, sizeTensorData); // The descriptor only has target height and width attributes, which we get from the size tensor. ResizeBilinearDescriptor desc; desc.m_TargetHeight = static_cast (sizeTensorData[0]); desc.m_TargetWidth = static_cast (sizeTensorData[1]); IConnectableLayer* layer = m_Network->AddResizeBilinearLayer(desc, nodeDef.name().c_str()); IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); TensorInfo inputTensorInfo = inputSlot.GetTensorInfo(); // The input shape is always in BHWC format, this will be swizzled below; for now, // get the batch and channels to make up the ArmNN output shape with the target size. unsigned int outBatch = inputTensorInfo.GetShape()[0]; unsigned int outChannels = inputTensorInfo.GetShape()[3]; unsigned int outHeight = desc.m_TargetHeight; unsigned int outWidth = desc.m_TargetWidth; TensorShape outShape({outBatch, outChannels, outHeight, outWidth}); // The output DataType is always Float32, regardless of the input DataType. const TensorInfo outputTensorInfo(outShape, armnn::DataType::Float32); layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo); // TensorFlow ResizeBilinear input is always in BHWC format, so add swizzle and deswizzle layers. layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name()); return std::make_unique(this, nodeDef, layer); } TensorInfo OutputShapeOfSqueeze(const tensorflow::NodeDef& nodeDef, TensorInfo inputTensorInfo) { BOOST_ASSERT(nodeDef.op() == "Squeeze"); tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "T"); DataType type; if (tfDataType == tensorflow::DT_FLOAT) { type = DataType::Float32; } else if (tfDataType == tensorflow::DT_INT32) { type = DataType::Signed32; } else { throw ParseException( boost::str( boost::format("Unsupported DataType %1% for Squeeze operation %2% %3%") % tensorflow::DataType_Name(tfDataType) % nodeDef.name() % CHECK_LOCATION().AsString())); } if (inputTensorInfo.GetNumDimensions() > 4) { throw ParseException( boost::str( boost::format( "Unsupported number of dimensions: %1% for input shape for Squeeze %2% %3%") % inputTensorInfo.GetNumDimensions() % nodeDef.name() % CHECK_LOCATION().AsString())); } std::vector squeezeDims = ReadOptionalNodeUint32ListAttribute(nodeDef, "squeeze_dims"); static const uint32_t dimensionSequence[] = { 0, 1, 2, 3 }; if (squeezeDims.empty()) { squeezeDims.assign(dimensionSequence, dimensionSequence+inputTensorInfo.GetNumDimensions()); } std::vector outputDims; for(unsigned int i = 0; i < inputTensorInfo.GetNumDimensions(); i++) { bool skipSqueeze = (std::find(squeezeDims.begin(), squeezeDims.end(), i) == squeezeDims.end()); auto currentDimension = inputTensorInfo.GetShape()[i]; if (skipSqueeze || currentDimension != 1) { outputDims.push_back(currentDimension); } } if (outputDims.size() > 4) { throw ParseException( boost::str( boost::format( "Unsupported number of dimensions: %1% for output shape for Squeeze %2% %3%") % outputDims.size() % nodeDef.name() % CHECK_LOCATION().AsString())); } TensorShape outShape = TensorShape(static_cast(outputDims.size()), outputDims.data()); TensorInfo outTensorInfo = inputTensorInfo; outTensorInfo.SetShape(outShape); outTensorInfo.SetDataType(type); return outTensorInfo; } ParsedTfOperationPtr TfParser::ParseSqueeze(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 1); IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo(); TensorInfo outputInfo; outputInfo = OutputShapeOfSqueeze(nodeDef, inputTensorInfo); ReshapeDescriptor reshapeDesc; reshapeDesc.m_TargetShape = outputInfo.GetShape(); IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str()); prevLayerOutputSlot.Connect(layer->GetInputSlot(0)); layer->GetOutputSlot(0).SetTensorInfo(outputInfo); return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseLrn(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 1); NormalizationDescriptor normalizationDescriptor; normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness; normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across; normalizationDescriptor.m_Alpha = ReadMandatoryNodeFloatAttribute(nodeDef, "alpha"); normalizationDescriptor.m_Beta = ReadMandatoryNodeFloatAttribute(nodeDef, "beta"); normalizationDescriptor.m_K = ReadMandatoryNodeFloatAttribute(nodeDef, "bias"); normalizationDescriptor.m_NormSize = ReadMandatoryNodeUint32Attribute(nodeDef, "depth_radius"); // The window size must be an odd value. For a window size of (2 * n + 1), TensorFlow defines depth_radius = n. normalizationDescriptor.m_NormSize = normalizationDescriptor.m_NormSize * 2 + 1; IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); IConnectableLayer* layer = m_Network->AddNormalizationLayer(normalizationDescriptor, nodeDef.name().c_str()); const TensorInfo permutedInfo = armnnUtils::Permuted(prevLayerOutputSlot.GetTensorInfo(), NHWCToArmNN); layer->GetOutputSlot(0).SetTensorInfo(permutedInfo); layer = SwizzleInDeswizzleOut(*m_Network, prevLayerOutputSlot, *layer, nodeDef.name()); return std::make_unique(this, nodeDef, layer); } /// An ParsedTfOperation for a MatMul node. /// Creation of the armnn FullyConnected layer is deferred until it is actually needed, because /// MatMul nodes are often used for the first part of a biased FullyConnected (MatMul followed /// by Add) and in these cases armnn doesn't need a separate layer for the MatMul. /// class ParsedMatMulTfOperation : public DeferredSingleLayerParsedTfOperation { public: ParsedMatMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node) : DeferredSingleLayerParsedTfOperation(parser, node) { } void CreateLayerDeferred() override { BOOST_ASSERT(m_Layer == nullptr); m_Layer = m_Parser->AddFullyConnectedLayer(m_Node, nullptr, m_Node.name().c_str()); } }; ParsedTfOperationPtr TfParser::ParseMatMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { // Defers the creation of the layer (see ParsedMatMulTfOperation). return std::make_unique(this, nodeDef); } /// An ParsedTfOperation for a Mul node. /// Creation of the armnn Mul layer is deferred until it is actually needed, because Mul nodes /// are also used for the first part of a leaky relu activation function (Mul followed by Maximum) /// and in these cases armnn doesn't need a separate layer for the Mul. /// class ParsedMulTfOperation : public DeferredSingleLayerParsedTfOperation { public: ParsedMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node) : DeferredSingleLayerParsedTfOperation(parser, node) { } void CreateLayerDeferred() override { BOOST_ASSERT(m_Layer == nullptr); m_Layer = m_Parser->AddMultiplicationLayer(m_Node); } }; ParsedTfOperationPtr TfParser::ParseMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); return std::make_unique(this, nodeDef); } ParsedTfOperationPtr TfParser::ParsePlaceholder(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 0); const LayerBindingId layerId = boost::numeric_cast(m_NetworkInputsBindingInfo.size()); auto it = m_InputShapes.find(nodeDef.name()); if (it == m_InputShapes.end()) { throw ParseException( boost::str( boost::format( "Missing input shape for Placeholder '%1%' %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } TensorInfo tensorInfo(it->second, DataType::Float32); IConnectableLayer* const layer = m_Network->AddInputLayer(layerId, nodeDef.name().c_str()); layer->GetOutputSlot(0).SetTensorInfo(tensorInfo); TrackInputBinding(layer, layerId, tensorInfo); return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseRealDiv(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); return AddRealDivLayer(nodeDef); } ParsedTfOperationPtr TfParser::ParseRelu(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); ActivationDescriptor activationDesc; activationDesc.m_Function = ActivationFunction::ReLu; return AddActivationLayer(nodeDef, activationDesc); } ParsedTfOperationPtr TfParser::ParseRelu6(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); ActivationDescriptor activationDesc; activationDesc.m_Function = ActivationFunction::BoundedReLu; activationDesc.m_A = 6.0f; activationDesc.m_B = 0.0f; return AddActivationLayer(nodeDef, activationDesc); } ParsedTfOperationPtr TfParser::ParseSigmoid(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); ActivationDescriptor activationDesc; activationDesc.m_Function = ActivationFunction::Sigmoid; return AddActivationLayer(nodeDef, activationDesc); } ParsedTfOperationPtr TfParser::ParseSoftmax(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 1); SoftmaxDescriptor softmaxDescriptor; IConnectableLayer* const layer = m_Network->AddSoftmaxLayer(softmaxDescriptor, nodeDef.name().c_str()); IOutputSlot& prevLayerSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); prevLayerSlot.Connect(layer->GetInputSlot(0)); layer->GetOutputSlot(0).SetTensorInfo(prevLayerSlot.GetTensorInfo()); return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseSoftplus(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); ActivationDescriptor activationDesc; activationDesc.m_Function = ActivationFunction::SoftReLu; return AddActivationLayer(nodeDef, activationDesc); } ParsedTfOperationPtr TfParser::ParseTanh(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { boost::ignore_unused(graphDef); ActivationDescriptor activationDesc; activationDesc.m_Function = ActivationFunction::TanH; activationDesc.m_A = 1.0f; activationDesc.m_B = 1.0f; return AddActivationLayer(nodeDef, activationDesc); } ParsedTfOperationPtr TfParser::AddActivationLayer(const tensorflow::NodeDef& nodeDef, ActivationDescriptor& activationDesc) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 1); IConnectableLayer* const layer = m_Network->AddActivationLayer(activationDesc, nodeDef.name().c_str()); IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); prevLayerOutputSlot.Connect(layer->GetInputSlot(0)); layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo()); return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::ParseMaxPool(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Max); } ParsedTfOperationPtr TfParser::ParseAvgPool(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Average); } ParsedTfOperationPtr TfParser::ParsePooling2d(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef, PoolingAlgorithm pooltype) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 1); IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); TensorInfo inputTensorInfo = inputSlot.GetTensorInfo(); if (inputs.size() != 1) { throw ParseException( boost::str( boost::format( "2D Pooling expects one input!. Got %1% for Node %2% %3%") % inputs.size() % nodeDef.name() % CHECK_LOCATION().AsString())); } std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding"); std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format"); std::vector strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides"); std::vector ksize = ReadMandatoryNodeUint32ListAttribute(nodeDef, "ksize"); // size of pool windows Pooling2dDescriptor pooling2dDescriptor; pooling2dDescriptor.m_PoolType = pooltype; pooling2dDescriptor.m_PaddingMethod = PaddingMethod::Exclude; pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Floor; CHECK_DATA_FORMAT(nodeDef, dataFormat, "Pooling2D"); if (dataFormat == "NHWC") { pooling2dDescriptor.m_StrideX = strides[2]; pooling2dDescriptor.m_StrideY = strides[1]; pooling2dDescriptor.m_PoolWidth = ksize[2]; pooling2dDescriptor.m_PoolHeight = ksize[1]; // Swizzles input to supported memory layout. inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN); } else if (dataFormat == "NCHW") { pooling2dDescriptor.m_StrideX = strides[3]; pooling2dDescriptor.m_StrideY = strides[2]; pooling2dDescriptor.m_PoolWidth = ksize[3]; pooling2dDescriptor.m_PoolHeight = ksize[2]; } uint32_t inputHeight = inputTensorInfo.GetShape()[2]; uint32_t inputWidth = inputTensorInfo.GetShape()[3]; bool padding = false; TensorInfo outputInfo; CHECK_PADDING_TYPE(nodeDef, paddingString); if (paddingString == "SAME") { padding = true; outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0], inputTensorInfo.GetShape()[1], static_cast(ceil( static_cast(inputHeight) / static_cast(pooling2dDescriptor.m_StrideY))), static_cast(ceil( static_cast(inputWidth) / static_cast(pooling2dDescriptor.m_StrideX))) }, DataType::Float32); } else if (paddingString == "VALID") { padding = false; outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0], inputTensorInfo.GetShape()[1], static_cast(ceil( static_cast(inputHeight - pooling2dDescriptor.m_PoolHeight + 1) / static_cast(pooling2dDescriptor.m_StrideY))), static_cast(ceil( static_cast(inputWidth - pooling2dDescriptor.m_PoolWidth + 1) / static_cast(pooling2dDescriptor.m_StrideX))) }, DataType::Float32); } CalcPadding(inputWidth, pooling2dDescriptor.m_PoolWidth, pooling2dDescriptor.m_StrideX, pooling2dDescriptor.m_PadLeft, pooling2dDescriptor.m_PadRight, padding); CalcPadding(inputHeight, pooling2dDescriptor.m_PoolHeight, pooling2dDescriptor.m_StrideY, pooling2dDescriptor.m_PadTop, pooling2dDescriptor.m_PadBottom, padding); IConnectableLayer* layer = m_Network->AddPooling2dLayer(pooling2dDescriptor, nodeDef.name().c_str()); if (layer == nullptr) { throw ParseException( boost::str( boost::format( "Failed to add pooling2d layer for %1% %2%") % nodeDef.name() % CHECK_LOCATION().AsString())); } layer->GetOutputSlot(0).SetTensorInfo(outputInfo); if (dataFormat == "NHWC") { layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name()); } else { inputSlot.Connect(layer->GetInputSlot(0)); } return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::AddAdditionLayer(const tensorflow::NodeDef& nodeDef, bool isBiasAdd) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index); const TensorInfo& input0Info = input0Slot->GetTensorInfo(); const TensorInfo& input1Info = input1Slot->GetTensorInfo(); if (isBiasAdd) { // BiasAdd takes bias as a 1D tensor. We need to add a reshape layer to create a 4D tensor // with the same data in the correct dimension for broadcast in addition. if(input1Info.GetNumDimensions() != 1) { throw ParseException( boost::str( boost::format( "Unsupported bias for BiasAdd. It should be a 1D vector. " "Got %1% dimensions for input %2%. Node %3% %4%") % input1Info.GetNumDimensions() % inputs[1].m_IndexedValue->GetNode().name() % nodeDef.name() % CHECK_LOCATION().AsString())); } const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format"); CHECK_DATA_FORMAT(nodeDef, dataFormat, "BiasAdd"); input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, dataFormat == "NHWC", *m_Network, nodeDef); } else { if (input0Info.GetNumDimensions() == 1) { const bool isNHWC = true; input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef); } if (input1Info.GetNumDimensions() == 1) { const bool isNHWC = true; input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef); } } IConnectableLayer* const layer = m_Network->AddAdditionLayer(nodeDef.name().c_str()); input0Slot->Connect(layer->GetInputSlot(0)); input1Slot->Connect(layer->GetInputSlot(1)); if (input0Info.GetNumDimensions() == 1 && isBiasAdd == false) { layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo()); } else { layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo()); } return std::make_unique(this, nodeDef, layer); } ParsedTfOperationPtr TfParser::AddRealDivLayer(const tensorflow::NodeDef& nodeDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); IConnectableLayer* const layer = m_Network->AddDivisionLayer(nodeDef.name().c_str()); IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index); auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions(); auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions(); if (input0NumDims < input1NumDims) { const bool isNHWC = true; input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef); } if (input1NumDims < input0NumDims) { const bool isNHWC = true; input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef); } input0Slot->Connect(layer->GetInputSlot(0)); input1Slot->Connect(layer->GetInputSlot(1)); if (input0NumDims < input1NumDims) { layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo()); } else { layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo()); } return std::make_unique(this, nodeDef, layer); } IConnectableLayer* TfParser::AddMultiplicationLayer(const tensorflow::NodeDef& nodeDef) { std::vector inputs = GetInputParsedTfOperationsChecked(nodeDef, 2); IConnectableLayer* const layer = m_Network->AddMultiplicationLayer(nodeDef.name().c_str()); IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index); IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index); auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions(); auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions(); if (input0NumDims < input1NumDims) { const bool isNHWC = true; input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef); } if (input1NumDims < input0NumDims) { const bool isNHWC = true; input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef); } input0Slot->Connect(layer->GetInputSlot(0)); input1Slot->Connect(layer->GetInputSlot(1)); if (input0NumDims < input1NumDims) { layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo()); } else { layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo()); } return layer; } IConnectableLayer* TfParser::AddFullyConnectedLayer(const tensorflow::NodeDef& matMulNodeDef, const tensorflow::NodeDef* addNodeDef, const char* armnnLayerName) { // Finds bias const (if applicable). ParsedConstTfOperation* biasNode = nullptr; if (addNodeDef != nullptr) { std::vector addInputs = GetInputParsedTfOperationsChecked(*addNodeDef, 2); // Finds our inputs. if (HasParsedConstTensor(addInputs[0].m_IndexedValue->GetNode().name())) { biasNode = boost::polymorphic_downcast*>(addInputs[0].m_IndexedValue); } else if (HasParsedConstTensor(addInputs[1].m_IndexedValue->GetNode().name())) { biasNode = boost::polymorphic_downcast*>(addInputs[1].m_IndexedValue); } else { throw ParseException( boost::str( boost::format( "ArmNN only supports fully connected layers with constant bias. " "Inputs %1% and %2%. AddNode %3%. MatMulNode %4% %5%") % addInputs[0].m_IndexedValue->GetNode().name() % addInputs[1].m_IndexedValue->GetNode().name() % addNodeDef->name() % matMulNodeDef.name() % CHECK_LOCATION().AsString())); } } // Finds matmul inputs. ParsedConstTfOperation* weightNode = nullptr; ParsedTfOperation* inputNode = nullptr; unsigned int inputIdx = 0; std::vector mulInputs = GetInputParsedTfOperationsChecked(matMulNodeDef, 2); if (HasParsedConstTensor(mulInputs[0].m_IndexedValue->GetNode().name())) { weightNode = boost::polymorphic_downcast*>(mulInputs[0].m_IndexedValue); inputNode = mulInputs[1].m_IndexedValue; inputIdx = mulInputs[1].m_Index; } else if (HasParsedConstTensor(mulInputs[1].m_IndexedValue->GetNode().name())) { weightNode = boost::polymorphic_downcast*>(mulInputs[1].m_IndexedValue); inputNode = mulInputs[0].m_IndexedValue; inputIdx = mulInputs[0].m_Index; } else { throw ParseException( boost::str( boost::format( "ArmNN only supports fully connected layers with constant weights. " "Inputs %1% and %2%. MatMulNode %3% %4%") % mulInputs[0].m_IndexedValue->GetNode().name() % mulInputs[1].m_IndexedValue->GetNode().name() % matMulNodeDef.name() % CHECK_LOCATION().AsString())); } std::vector weightTensorData; // Handles weight. ConstTensor weights = weightNode->GetConstTensor(false, weightTensorData); FullyConnectedDescriptor desc; desc.m_BiasEnabled = addNodeDef != nullptr; IConnectableLayer* layer = nullptr; // Makes the layer. if (addNodeDef != nullptr) { std::vector biasTensorData; ConstTensor biases = biasNode->GetConstTensor(false, biasTensorData); if (weights.GetShape()[1] != biases.GetShape()[0]) { throw ParseException( boost::str( boost::format( "Shape of matmul weights and bias do not match. " "AddNode %1%. MatMulNode %2% %3%") % addNodeDef->name() % matMulNodeDef.name() % CHECK_LOCATION().AsString())); } layer = m_Network->AddFullyConnectedLayer(desc, weights, biases, armnnLayerName); } else { layer = m_Network->AddFullyConnectedLayer(desc, weights, armnnLayerName); } BOOST_ASSERT(layer != nullptr); inputNode->ResolveArmnnOutputSlot(inputIdx).Connect(layer->GetInputSlot(0)); unsigned int batches = inputNode->ResolveArmnnOutputSlot(inputIdx).GetTensorInfo().GetShape()[0]; // Handles output. TensorInfo outputInfo({ batches, weights.GetShape()[1] }, DataType::Float32); layer->GetOutputSlot(0).SetTensorInfo(outputInfo); return layer; } void TfParser::LoadNodeDef(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef) { // Gets the type of the node (assume float). tensorflow::DataType type = tensorflow::DT_FLOAT; if (nodeDef.attr().count("T") != 0) { auto attr = nodeDef.attr().at("T"); type = attr.type(); } else if (nodeDef.attr().count("dtype") != 0) { auto attr = nodeDef.attr().at("dtype"); type = attr.type(); } if (type != tensorflow::DT_FLOAT && nodeDef.op() != "Const") { throw ParseException( boost::str( boost::format( "Currently only FLOAT is supported for tensorflow nodes (apart from Const). " "Got %1% for Node %2% %3%") % tensorflow::DataType_Name(type) % nodeDef.name() % CHECK_LOCATION().AsString())); } const std::string& operation = nodeDef.op(); auto it = ms_OperationNameToParsingFunctions.find(operation); if (it != ms_OperationNameToParsingFunctions.end()) { auto func = it->second; ParsedTfOperationPtr parsedTfOperation = (this->*func)(nodeDef, graphDef); ParsedTfOperation* parsedTfOperationRaw = parsedTfOperation.get(); // Stores the parsed operation so that dependent layers can connect to it. auto it = m_ParsedTfOperations.find(nodeDef.name()); if (it != m_ParsedTfOperations.end()) { throw ParseException(boost::str(boost::format("Name %1% used by more than one node") % nodeDef.name())); } m_ParsedTfOperations[nodeDef.name()] = std::move(parsedTfOperation); // If this node was requested as an output from the network, then adds an ArmNN output layer. if (std::find(m_RequestedOutputs.begin(), m_RequestedOutputs.end(), nodeDef.name()) != m_RequestedOutputs.end()) { auto outId = ParseOutputId(nodeDef.name()); const LayerBindingId layerId = boost::numeric_cast(m_NetworkOutputsBindingInfo.size()); IOutputSlot& prevSlot = parsedTfOperationRaw->ResolveArmnnOutputSlot(outId.m_Index); TensorInfo tensorInfo = prevSlot.GetTensorInfo(); IConnectableLayer* outputLayer = m_Network->AddOutputLayer(layerId, nodeDef.name().c_str()); prevSlot.Connect(outputLayer->GetInputSlot(0)); TrackOutputBinding(outputLayer, layerId, tensorInfo); } } else { throw ParseException( boost::str( boost::format( "Unsupported operation %1% in tensorflow::GraphDef %2%") % operation % CHECK_LOCATION().AsString())); } } void TfParser::LoadGraphDef(const tensorflow::GraphDef& graphDef) { // Adds all nodes to our map. m_NodesByName.clear(); m_NetworkInputsBindingInfo.clear(); m_NetworkOutputsBindingInfo.clear(); for (int i = 0; i < graphDef.node_size(); ++i) { const tensorflow::NodeDef& node = graphDef.node(i); m_NodesByName[node.name()] = &node; } // Finds the output nodes the user requested. std::vector targetNodes; for (const std::string& requestedOutputName : m_RequestedOutputs) { auto nodeIt = m_NodesByName.find(requestedOutputName); if (nodeIt == m_NodesByName.end()) { throw ParseException( boost::str( boost::format( "Couldn't find requested output node '%1%' in graph %2%") % requestedOutputName % CHECK_LOCATION().AsString())); } targetNodes.push_back(nodeIt->second); } // Sorts them into a linear ordering such that all inputs of a node are before the node itself. std::vector sortedNodes; if (!armnnUtils::GraphTopologicalSort( targetNodes, [this](const tensorflow::NodeDef* node) { auto outputs = GetTfInputNodes(*node); std::vector nodesOnly; for (const auto & o : outputs) { nodesOnly.push_back(o.m_IndexedValue); } return nodesOnly; }, sortedNodes)) { throw ParseException( boost::str( boost::format( "Cycle detected in graph %1%") % CHECK_LOCATION().AsString())); } // Parses each node in order, knowing that all inputs of a node will be processed before the node itself. for (const auto& it : sortedNodes) { const tensorflow::NodeDef& currentNode = *it; LoadNodeDef(currentNode, graphDef); } } INetworkPtr TfParser::CreateNetworkFromTextFile(const char* graphFile, const std::map& inputShapes, const std::vector& requestedOutputs) { FILE* fd = fopen(graphFile, "r"); if (fd == nullptr) { throw FileNotFoundException( boost::str( boost::format( "Graph file %1% failed to open %2%") % graphFile % CHECK_LOCATION().AsString())); } // Parses the file into a message. tensorflow::GraphDef graphDef; auto input = new google::protobuf::io::FileInputStream(fileno(fd)); bool success = google::protobuf::TextFormat::Parse(input, &graphDef); delete input; fclose(fd); if (!success) { throw ParseException( boost::str( boost::format( "Failed to parse graph file %1%") % CHECK_LOCATION().AsString())); } return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs); } INetworkPtr TfParser::CreateNetworkFromString(const char* protoText, const std::map& inputShapes, const std::vector& requestedOutputs) { // Parses the string into a message. tensorflow::GraphDef graphDef; bool success = google::protobuf::TextFormat::ParseFromString(protoText, &graphDef); if (!success) { throw ParseException( boost::str( boost::format( "Failed to parse graph file %1%") % CHECK_LOCATION().AsString())); } return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs); } INetworkPtr TfParser::CreateNetworkFromBinaryFile(const char* graphFile, const std::map& inputShapes, const std::vector& requestedOutputs) { FILE* fd = fopen(graphFile, "rb"); if (fd == nullptr) { throw FileNotFoundException( boost::str( boost::format( "Graph file %1% failed to open %2%") % graphFile % CHECK_LOCATION().AsString())); } // Parses the file into a message. tensorflow::GraphDef graphDef; google::protobuf::io::FileInputStream inStream(fileno(fd)); google::protobuf::io::CodedInputStream codedStream(&inStream); codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX); bool success = graphDef.ParseFromCodedStream(&codedStream); fclose(fd); if (!success) { throw ParseException( boost::str( boost::format( "Failed to parse protobuf file %1% %2%") % graphFile % CHECK_LOCATION().AsString())); } return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs); } INetworkPtr TfParser::CreateNetworkFromGraphDef(const tensorflow::GraphDef& graphDef, const std::map& inputShapes, const std::vector& requestedOutputs) { m_Network = INetwork::Create(); m_InputShapes = inputShapes; if (requestedOutputs.size() == 0) { throw ParseException( boost::str( boost::format( "requestedOutputs must have at least one entry %1%") % CHECK_LOCATION().AsString())); } m_RequestedOutputs = requestedOutputs; try { LoadGraphDef(graphDef); } catch (const ParseException& e) { Cleanup(); throw e; } Cleanup(); return std::move(m_Network); } void TfParser::Cleanup() { // Cleanup, in case we reuse this parser. m_InputShapes.clear(); m_RequestedOutputs.clear(); m_NodesByName.clear(); m_ParsedTfOperations.clear(); } BindingPointInfo TfParser::GetNetworkInputBindingInfo(const std::string& name) const { return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo); } BindingPointInfo TfParser::GetNetworkOutputBindingInfo(const std::string& name) const { return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo); } std::pair TfParser::GetBindingInfo(const std::string& layerName, const char* bindingPointDesc, const std::unordered_map& nameToBindingInfo) { auto it = nameToBindingInfo.find(layerName); if (it == nameToBindingInfo.end()) { throw InvalidArgumentException( boost::str( boost::format( "Unknown %1% '%2%' %3%") % bindingPointDesc % layerName % CHECK_LOCATION().AsString())); } return it->second; } void TfParser::TrackInputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo) { return TrackBindingPoint(layer, id, tensorInfo, "input", m_NetworkInputsBindingInfo); } void TfParser::TrackOutputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo) { return TrackBindingPoint(layer, id, tensorInfo, "output", m_NetworkOutputsBindingInfo); } void TfParser::TrackBindingPoint(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo, const char* bindingPointDesc, std::unordered_map& nameToBindingInfo) { const std::string layerName = layer->GetName(); auto it = nameToBindingInfo.find(layerName); if (it == nameToBindingInfo.end()) { nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo); } else { throw ParseException( boost::str( boost::format( "Id %1% used by more than one %2% layer %3%") % id % bindingPointDesc % CHECK_LOCATION().AsString())); } } } // namespace armnnTfParser