From ce3e84a8d449cbf31cee57e30f0eef6a96c0ce94 Mon Sep 17 00:00:00 2001 From: telsoa01 Date: Fri, 31 Aug 2018 09:31:35 +0100 Subject: Release 18.08 --- ModelToINetworkConverter.cpp | 1003 ++++++++++++++++++++++++++++++++---------- 1 file changed, 776 insertions(+), 227 deletions(-) (limited to 'ModelToINetworkConverter.cpp') diff --git a/ModelToINetworkConverter.cpp b/ModelToINetworkConverter.cpp index fe4e8ac1..3da56ef8 100644 --- a/ModelToINetworkConverter.cpp +++ b/ModelToINetworkConverter.cpp @@ -6,7 +6,7 @@ #define LOG_TAG "ArmnnDriver" #include "ModelToINetworkConverter.hpp" -#include "OperationsUtils.h" +#include #include #include @@ -19,6 +19,8 @@ #include #include +using namespace android::hardware; + namespace armnn_driver { class LayerInputHandle @@ -105,45 +107,66 @@ inline bool IsOperandTypeSupportedForTensors(OperandType type) type == OperandType::TENSOR_INT32; } -void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail, - android::nn::PaddingScheme scheme) -{ - int32_t padHead; - int32_t padTail; - calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail); - outPadHead = boost::numeric_cast(padHead); - outPadTail = boost::numeric_cast(padTail); -} - -bool ValidateBroadcast(const V1_0::Model& model, const V1_0::Operation& operation, uint32_t numInputs) +void BroadcastTensor(LayerInputHandle& input0, LayerInputHandle& input1, armnn::IConnectableLayer* startLayer, + armnn::INetwork& network) { - assert(operation.inputs.size() > 0); // This should have been validated by the caller - // validateModel() has been called already so we know the operation.inputs indexes are valid within model.operands. - const Operand& firstInput = model.operands[operation.inputs[0]]; + BOOST_ASSERT(startLayer != nullptr); + const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo(); + const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo(); - // We don't support broadcasting yet - we require all input operands to have the same shape - for (uint32_t i = 1; i < numInputs; ++i) + if (inputTensorInfo0.GetNumDimensions() != inputTensorInfo1.GetNumDimensions()) { - const Operand& otherInput = model.operands[operation.inputs[i]]; + // If the number of dimensions do not match then we need to add degenerate dimensions + // to the "smaller" tensor using a reshape: + // Small Big + // | | + // Reshape | + // \ / + // Add + bool input0IsBigger = inputTensorInfo0.GetNumDimensions() > inputTensorInfo1.GetNumDimensions(); - if (firstInput.dimensions.size() != otherInput.dimensions.size()) - { - return Fail("%s: Broadcasting not supported (Input 0 dims: %i Input %i dims: %i)", - __func__, firstInput.dimensions.size(), i, otherInput.dimensions.size()); - } + LayerInputHandle& smallTensorHandle = input0IsBigger ? input1 : input0; + const armnn::TensorInfo& smallTensorDims = smallTensorHandle.GetTensorInfo(); + + LayerInputHandle& bigTensorHandle = input0IsBigger ? input0 : input1; + const armnn::TensorInfo& bigTensorDims = bigTensorHandle.GetTensorInfo(); - for (unsigned int d = 0; d < firstInput.dimensions.size(); ++d) + const unsigned int bigTensorDimsNumber = bigTensorDims.GetNumDimensions(); + std::vector reshapedDims(bigTensorDimsNumber, 1); + unsigned int sizeDifference = bigTensorDimsNumber - smallTensorDims.GetNumDimensions(); + for (unsigned i = sizeDifference; i < bigTensorDimsNumber; ++i) { - if (firstInput.dimensions[d] != otherInput.dimensions[d]) - { - return Fail("%s: Broadcasting not supported (Dimension %i size mismatch. " - "Input 0: %i Input %i: %i)", - __func__, d, firstInput.dimensions[d], i, otherInput.dimensions[d]); - } + reshapedDims[i] = smallTensorDims.GetShape()[i-sizeDifference]; } + armnn::TensorInfo reshapedInfo = smallTensorDims; + reshapedInfo.SetShape(armnn::TensorShape{ static_cast(reshapedDims.size()), + reshapedDims.data() }); + + armnn::ReshapeDescriptor reshapeDesc; + reshapeDesc.m_TargetShape = reshapedInfo.GetShape(); + armnn::IConnectableLayer* const reshapeLayer = network.AddReshapeLayer(reshapeDesc); + smallTensorHandle.Connect(reshapeLayer->GetInputSlot(0)); + reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo); + + // Connect the outputs from new reshape and original input layer + reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0)); + bigTensorHandle.Connect(startLayer->GetInputSlot(1)); } + else + { + input0.Connect(startLayer->GetInputSlot(0)); + input1.Connect(startLayer->GetInputSlot(1)); + } +} - return true; +void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail, + android::nn::PaddingScheme scheme) +{ + int32_t padHead; + int32_t padTail; + calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail); + outPadHead = boost::numeric_cast(padHead); + outPadTail = boost::numeric_cast(padTail); } Shape GetOperandShape(const Operand& operand) @@ -175,11 +198,17 @@ void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo, } } -const armnn::PermutationVector IdentityPermutation({ 0U, 1U, 2U, 3U }); +// 4D Tensor Permutations +const armnn::PermutationVector IdentityPermutation4D({ 0U, 1U, 2U, 3U }); const armnn::PermutationVector NHWCToArmNN({ 0U, 2U, 3U, 1U }); const armnn::PermutationVector ArmNNToNHWC({ 0U, 3U, 1U, 2U }); const armnn::PermutationVector SwapDim1And2({ 0U, 2U, 1U, 3U }); +// 3D Permutation Vectors +const armnn::PermutationVector IdentityPermutation3D({ 0U, 1U, 2U }); +const armnn::PermutationVector RotateTensorLeft({ 2U, 0U, 1U }); +const armnn::PermutationVector RotateTensorRight({ 1U, 2U, 0U }); + template armnn::IConnectableLayer& AddPermuteLayer(armnn::INetwork& network, OSlot& input, const armnn::PermutationVector& mappings) @@ -189,7 +218,7 @@ armnn::IConnectableLayer& AddPermuteLayer(armnn::INetwork& network, OSlot& input assert(layer != nullptr); - // Connect intput to swizzle layer + // Connect input to swizzle layer input.Connect(layer->GetInputSlot(0)); // Setup swizzled output @@ -199,22 +228,32 @@ armnn::IConnectableLayer& AddPermuteLayer(armnn::INetwork& network, OSlot& input return *layer; } -armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, LayerInputHandle& input, - armnn::IConnectableLayer& firstLayer, - armnn::IConnectableLayer& lastLayer) +void SwizzleIn(armnn::INetwork& network, LayerInputHandle& input, armnn::IConnectableLayer& layer, unsigned int index) { // Add swizzle layer armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, input, NHWCToArmNN); - // Connect swizzled input to layer - swizzleLayer.GetOutputSlot(0).Connect(firstLayer.GetInputSlot(0)); + swizzleLayer.GetOutputSlot(0).Connect(layer.GetInputSlot(index)); +} +armnn::IConnectableLayer& DeswizzleOut(armnn::INetwork& network, armnn::IConnectableLayer& layer, unsigned int index) +{ // Add deswizzle layer - armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(network, lastLayer.GetOutputSlot(0), ArmNNToNHWC); - + armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(network, layer.GetOutputSlot(index), ArmNNToNHWC); return deswizzleLayer; } +// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly +armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, + LayerInputHandle& input, + armnn::IConnectableLayer& firstLayer, + armnn::IConnectableLayer& lastLayer) +{ + SwizzleIn(network, input, firstLayer, 0); + return DeswizzleOut(network, lastLayer, 0); +} + +// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, LayerInputHandle& input, armnn::IConnectableLayer& layer) { @@ -264,12 +303,34 @@ bool ValidateConcatOutputShape(const std::vector & inputShap return true; } +bool RequiresReshape(armnn::TensorShape & inputShape) +{ + return inputShape.GetNumDimensions() < 3; +} + +template +armnn::IConnectableLayer& AddReshapeLayer(armnn::INetwork& network, OSlot& inputLayer, + armnn::TensorInfo reshapeInfo) +{ + armnn::ReshapeDescriptor reshapeDescriptor; + reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape(); + + armnn::IConnectableLayer* reshapeLayer = network.AddReshapeLayer(reshapeDescriptor); + assert(reshapeLayer != nullptr); + + // Attach the input layer to the reshape layer + inputLayer.Connect(reshapeLayer->GetInputSlot(0)); + reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapeInfo); + + return *reshapeLayer; +} + void SwizzleInputs(armnn::INetwork& network, std::vector& inputs, std::vector& inputShapes, const armnn::PermutationVector& mapping) { - if (!mapping.IsEqual(IdentityPermutation)) + if (!mapping.IsEqual(IdentityPermutation4D)) { size_t nInputs = inputs.size(); for (size_t i=0; i & permutationPair) +{ + assert(numberOfDimensions >= 3); + + // ArmNN uses Compute Library subtensors to perform concatenation + // This only works when concatenating along dimension 0 or 1 for a 4-D tensor, + // or along dimension 0 for a 3-D tensor. + if (numberOfDimensions == 4) + { + if (concatDimension == 3) + { + concatDimension = 1; + permutationPair = std::make_pair(NHWCToArmNN, ArmNNToNHWC); + } + else if (concatDimension == 2) + { + concatDimension = 1; + permutationPair = std::make_pair(SwapDim1And2, SwapDim1And2); + } + else + { + permutationPair = std::make_pair(IdentityPermutation4D, IdentityPermutation4D); + } + + } + else if (numberOfDimensions == 3) + { + if (concatDimension == 2) + { + concatDimension = 0; + permutationPair = std::make_pair(RotateTensorRight, RotateTensorLeft); + } + else if (concatDimension == 1) + { + concatDimension = 0; + permutationPair = std::make_pair(RotateTensorLeft, RotateTensorRight); + } + else + { + permutationPair = std::make_pair(IdentityPermutation3D, IdentityPermutation3D); + } + } +} + + } // namespace namespace armnn_driver @@ -294,7 +402,8 @@ class ConstTensorPin { public: // Creates an invalid tensor pin (can be used to signal errors) - ConstTensorPin() {} + // The optional flag can be set to indicate the tensor values were missing, but it was otherwise valid + ConstTensorPin(bool optional = false) : m_Optional(optional) {} // @param tensorInfo TensorInfo associated with the tensor. // @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with @@ -324,7 +433,17 @@ public: ConstTensorPin(ConstTensorPin&& other) = default; bool IsValid() const { return m_ConstTensor.GetMemoryArea() != nullptr; } + bool IsOptional() const { return m_Optional; } const armnn::ConstTensor& GetConstTensor() const { return m_ConstTensor; } + const armnn::ConstTensor* GetConstTensorPtr() const + { + if (IsValid() && m_ConstTensor.GetNumElements() > 0) + { + return &m_ConstTensor; + } + // tensor is either invalid, or has no elements (indicating an optional tensor that was not provided) + return nullptr; + } private: armnn::ConstTensor m_ConstTensor; @@ -332,9 +451,12 @@ private: // swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of // the pools associated with the model being converted. std::vector m_SwizzledTensorData; + // optional flag to indicate that an invalid tensor pin is not an error, but the optional values were not given + bool m_Optional; }; -ModelToINetworkConverter::ModelToINetworkConverter(armnn::Compute compute, const V1_0::Model& model, +ModelToINetworkConverter::ModelToINetworkConverter(armnn::Compute compute, + const neuralnetworks::V1_0::Model& model, const std::set& forcedUnsupportedOperations) : m_Compute(compute) , m_Model(model) @@ -471,37 +593,59 @@ void ModelToINetworkConverter::Convert() } } -bool ModelToINetworkConverter::ConvertOperation(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertOperation(const neuralnetworks::V1_0::Operation& operation) { switch (operation.type) { - case V1_0::OperationType::ADD: return ConvertAdd(operation); - case V1_0::OperationType::AVERAGE_POOL_2D: return ConvertAveragePool2d(operation); - case V1_0::OperationType::CONCATENATION: return ConvertConcatenation(operation); - case V1_0::OperationType::CONV_2D: return ConvertConv2d(operation); - case V1_0::OperationType::DEPTHWISE_CONV_2D: return ConvertDepthwiseConv2d(operation); - case V1_0::OperationType::FLOOR: return ConvertFloor(operation); - case V1_0::OperationType::FULLY_CONNECTED: return ConvertFullyConnected(operation); - case V1_0::OperationType::LOCAL_RESPONSE_NORMALIZATION: return ConvertLocalResponseNormalization(operation); - case V1_0::OperationType::LOGISTIC: return ConvertLogistic(operation); - case V1_0::OperationType::L2_NORMALIZATION: return ConvertL2Normalization(operation); - case V1_0::OperationType::L2_POOL_2D: return ConvertL2Pool2d(operation); - case V1_0::OperationType::MAX_POOL_2D: return ConvertMaxPool2d(operation); - case V1_0::OperationType::MUL: return ConvertMul(operation); - case V1_0::OperationType::RELU: return ConvertReLu(operation); - case V1_0::OperationType::RELU1: return ConvertReLu1(operation); - case V1_0::OperationType::RELU6: return ConvertReLu6(operation); - case V1_0::OperationType::SOFTMAX: return ConvertSoftmax(operation); - case V1_0::OperationType::TANH: return ConvertTanH(operation); - case V1_0::OperationType::RESHAPE: return ConvertReshape(operation); - case V1_0::OperationType::RESIZE_BILINEAR: return ConvertResizeBilinear(operation); - default: return Fail("%s: Operation type %s not supported in ArmnnDriver", - __func__, toString(operation.type).c_str()); + case neuralnetworks::V1_0::OperationType::ADD: + return ConvertAdd(operation); + case neuralnetworks::V1_0::OperationType::AVERAGE_POOL_2D: + return ConvertAveragePool2d(operation); + case neuralnetworks::V1_0::OperationType::CONCATENATION: + return ConvertConcatenation(operation); + case neuralnetworks::V1_0::OperationType::CONV_2D: + return ConvertConv2d(operation); + case neuralnetworks::V1_0::OperationType::DEPTHWISE_CONV_2D: + return ConvertDepthwiseConv2d(operation); + case neuralnetworks::V1_0::OperationType::FLOOR: + return ConvertFloor(operation); + case neuralnetworks::V1_0::OperationType::FULLY_CONNECTED: + return ConvertFullyConnected(operation); + case neuralnetworks::V1_0::OperationType::LOCAL_RESPONSE_NORMALIZATION: + return ConvertLocalResponseNormalization(operation); + case neuralnetworks::V1_0::OperationType::LOGISTIC: + return ConvertLogistic(operation); + case neuralnetworks::V1_0::OperationType::LSTM: + return ConvertLstm(operation); + case neuralnetworks::V1_0::OperationType::L2_NORMALIZATION: + return ConvertL2Normalization(operation); + case neuralnetworks::V1_0::OperationType::L2_POOL_2D: + return ConvertL2Pool2d(operation); + case neuralnetworks::V1_0::OperationType::MAX_POOL_2D: + return ConvertMaxPool2d(operation); + case neuralnetworks::V1_0::OperationType::MUL: + return ConvertMul(operation); + case neuralnetworks::V1_0::OperationType::RELU: + return ConvertReLu(operation); + case neuralnetworks::V1_0::OperationType::RELU1: + return ConvertReLu1(operation); + case neuralnetworks::V1_0::OperationType::RELU6: + return ConvertReLu6(operation); + case neuralnetworks::V1_0::OperationType::SOFTMAX: + return ConvertSoftmax(operation); + case neuralnetworks::V1_0::OperationType::TANH: + return ConvertTanH(operation); + case neuralnetworks::V1_0::OperationType::RESHAPE: + return ConvertReshape(operation); + case neuralnetworks::V1_0::OperationType::RESIZE_BILINEAR: + return ConvertResizeBilinear(operation); + default: + return Fail("%s: Operation type %s not supported in ArmnnDriver", + __func__, toString(operation.type).c_str()); } } - -bool ModelToINetworkConverter::ConvertAdd(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertAdd(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0); LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1); @@ -511,8 +655,10 @@ bool ModelToINetworkConverter::ConvertAdd(const V1_0::Operation& operation) return Fail("%s: Operation has invalid inputs", __func__); } + // The FuseActivation parameter is always the input index 2 + // and it should be optional ActivationFn activationFunction; - if (!GetInputActivationFunction(operation, 2, activationFunction)) + if (!GetOptionalInputActivation(operation, 2, activationFunction)) { return Fail("%s: Operation has invalid inputs", __func__); } @@ -543,49 +689,7 @@ bool ModelToINetworkConverter::ConvertAdd(const V1_0::Operation& operation) if (endLayer != nullptr) { - // If the number of dimensions do not match then we need to add degenerate dimensions - // to the "smaller" tensor using a reshape: - // Small Big - // | | - // Reshape | - // \ / - // Add - if (inputTensorInfo0.GetNumDimensions() != inputTensorInfo1.GetNumDimensions()) - { - bool input0IsBigger = inputTensorInfo0.GetNumDimensions() > inputTensorInfo1.GetNumDimensions(); - - LayerInputHandle& smallTensorHandle = input0IsBigger ? input1 : input0; - const armnn::TensorInfo& smallTensorDims = smallTensorHandle.GetTensorInfo(); - - LayerInputHandle& bigTensorHandle = input0IsBigger ? input0 : input1; - const armnn::TensorInfo& bigTensorDims = bigTensorHandle.GetTensorInfo(); - - std::vector reshapedDims(bigTensorDims.GetNumDimensions(), 1); - unsigned int sizeDifference = bigTensorDims.GetNumDimensions() - smallTensorDims.GetNumDimensions(); - for (unsigned i = sizeDifference; i < bigTensorDims.GetNumDimensions(); ++i) - { - reshapedDims[i] = smallTensorDims.GetShape()[i-sizeDifference]; - } - armnn::TensorInfo reshapedInfo = smallTensorDims; - reshapedInfo.SetShape(armnn::TensorShape{ static_cast(reshapedDims.size()), - reshapedDims.data() }); - - armnn::ReshapeDescriptor reshapeDesc; - reshapeDesc.m_TargetShape = reshapedInfo.GetShape(); - armnn::IConnectableLayer* const reshapeLayer = m_Network->AddReshapeLayer(reshapeDesc); - smallTensorHandle.Connect(reshapeLayer->GetInputSlot(0)); - reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo); - - // Connect the outputs from new reshape and original input layer - reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0)); - bigTensorHandle.Connect(startLayer->GetInputSlot(1)); - } - else - { - input0.Connect(startLayer->GetInputSlot(0)); - input1.Connect(startLayer->GetInputSlot(1)); - } - + BroadcastTensor(input0, input1, startLayer, *m_Network); return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer); } else @@ -594,12 +698,12 @@ bool ModelToINetworkConverter::ConvertAdd(const V1_0::Operation& operation) } } -bool ModelToINetworkConverter::ConvertAveragePool2d(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertAveragePool2d(const neuralnetworks::V1_0::Operation& operation) { return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Average); } -bool ModelToINetworkConverter::ConvertConcatenation(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertConcatenation(const neuralnetworks::V1_0::Operation& operation) { // The first N (0..N-1) inputs are tensors. The Nth input is the concatenation axis. if (operation.inputs.size() <= 1) @@ -622,6 +726,7 @@ bool ModelToINetworkConverter::ConvertConcatenation(const V1_0::Operation& opera return Fail("%s: Operation has no outputs", __func__); } + armnn::TensorInfo outputInfo = GetTensorInfoForOperand(*outputOperand); armnn::TensorShape outputShape = outputInfo.GetShape(); @@ -640,41 +745,15 @@ bool ModelToINetworkConverter::ConvertConcatenation(const V1_0::Operation& opera return Fail("%s: Operation has invalid concat axis: %d", __func__, concatDim); } - // ArmNN uses Compute Library subtensors to perform concatenation - // This only works when concatenating along dimension 0 or 1 for a 4-D tensor, - // or along dimension 0 for a 3-D tensor. - const armnn::PermutationVector* permuteVectorIn = &IdentityPermutation; - const armnn::PermutationVector* permuteVectorOut = &IdentityPermutation; - - assert(permuteVectorOut != nullptr); - - if (outputShape.GetNumDimensions() == 4) { - if (concatDim == 3) { - concatDim = 1; - permuteVectorIn = &NHWCToArmNN; - permuteVectorOut = &ArmNNToNHWC; - outputShape = armnnUtils::Permuted(outputShape, *permuteVectorIn); - outputInfo.SetShape(outputShape); - } else if (concatDim == 2) { - concatDim = 1; - permuteVectorIn = &SwapDim1And2; - permuteVectorOut = &SwapDim1And2; - outputShape = armnnUtils::Permuted(outputShape, *permuteVectorIn); - outputInfo.SetShape(outputShape); - } - } - else if (!(outputShape.GetNumDimensions() == 3 && concatDim == 0)) - { - // Operation unsupported - return false; - } - std::vector inputHandles; std::vector inputShapes; inputHandles.reserve(numInputTensors); inputShapes.reserve(numInputTensors); + bool inputsHaveBeenReshaped = false; + unsigned int tensorDimensionsAdded = 0; + for (uint32_t i = 0; i < numInputTensors; ++i) { const Operand* const operand = GetInputOperand(operation, i); @@ -683,9 +762,45 @@ bool ModelToINetworkConverter::ConvertConcatenation(const V1_0::Operation& opera return Fail("%s: Operation has invalid inputs", __func__); } - inputShapes.emplace_back(GetTensorShapeForOperand(*operand)); - inputHandles.emplace_back(ConvertToLayerInputHandle(operation, i)); + armnn::TensorShape operandShape = GetTensorShapeForOperand(*operand); + LayerInputHandle operandInputHandle = ConvertToLayerInputHandle(operation, i); + + if (operandShape.GetNumDimensions() == 0) + { + return Fail("%s: Operands with rank 0 are not supported", __func__); + } + + if (RequiresReshape(operandShape)) + { + inputsHaveBeenReshaped = true; + + armnn::TensorInfo reshapeInfo = operandInputHandle.GetTensorInfo(); + + // Expand the tensor to three dimensions + if (operandShape.GetNumDimensions() == 2) + { + reshapeInfo.SetShape(armnn::TensorShape({1, operandShape[0], operandShape[1]})); + tensorDimensionsAdded = 1; + } + else + { + reshapeInfo.SetShape(armnn::TensorShape({1, 1, operandShape[0]})); + tensorDimensionsAdded = 2; + } + armnn::IConnectableLayer& newReshape = AddReshapeLayer( + *m_Network, + operandInputHandle, + reshapeInfo + ); + + // Point to the reshape operation rather then the input operation + operandShape = reshapeInfo.GetShape(); + operandInputHandle = LayerInputHandle(true, &newReshape.GetOutputSlot(0), reshapeInfo); + } + + inputShapes.emplace_back(operandShape); + inputHandles.emplace_back(operandInputHandle); if (!inputHandles.back().IsValid()) { @@ -695,9 +810,34 @@ bool ModelToINetworkConverter::ConvertConcatenation(const V1_0::Operation& opera assert(inputShapes.size() == inputHandles.size()); + if (inputsHaveBeenReshaped) + { + // Adjust the concatenation dimension by the amount of dimensions added (if any) + concatDim += tensorDimensionsAdded; + + // Add extra dimensions to the output shape to reflect the addition of the reshape layers + if (tensorDimensionsAdded == 1) + { + outputShape = armnn::TensorShape({1, outputShape[0], outputShape[1]}); + } + else if (tensorDimensionsAdded == 2) + { + outputShape = armnn::TensorShape({1, 1, outputShape[0], outputShape[1]}); + } + } + + // Get the pair of permutations required for the concatenation + std::pair permutationPair = + std::make_pair(IdentityPermutation4D, IdentityPermutation4D); + + CreatePermutationParameters(inputShapes[0].GetNumDimensions(), concatDim, permutationPair); + + outputShape = armnnUtils::Permuted(outputShape, permutationPair.first); + outputInfo.SetShape(outputShape); + // this is no-op for identity swizzles, otherwise it replaces both // the handles and shapes with the swizzled layer output handles and shapes - SwizzleInputs(*m_Network, inputHandles, inputShapes, *permuteVectorIn); + SwizzleInputs(*m_Network, inputHandles, inputShapes, permutationPair.first); // Create an armnn merger layer descriptor - this will also perform validation on the input shapes armnn::OriginsDescriptor mergerDescriptor; @@ -746,19 +886,39 @@ bool ModelToINetworkConverter::ConvertConcatenation(const V1_0::Operation& opera inputHandles[static_cast(i)].Connect(layer->GetInputSlot(i)); } - if (permuteVectorOut != &IdentityPermutation) + // Add permutation layer and connect the output to it, the permutation becomes the output layer + armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(*m_Network, + layer->GetOutputSlot(0), + permutationPair.second); + layer = &deswizzleLayer; + + if (inputsHaveBeenReshaped) { - // Add permutation layer and connect the output to it, the permutation becomes the output layer - armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(*m_Network, - layer->GetOutputSlot(0), - *permuteVectorOut); - layer = &deswizzleLayer; + armnn::TensorInfo afterConcatInfo = layer->GetOutputSlot(0).GetTensorInfo(); + + // Undo the reshape knowing the amount of dimensions added + if (tensorDimensionsAdded == 1) + { + afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[1], + afterConcatInfo.GetShape()[2] })); + } + else if (tensorDimensionsAdded == 2) + { + afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[2], + afterConcatInfo.GetShape()[3] })); + } + + layer = &AddReshapeLayer( + *m_Network, + layer->GetOutputSlot(0), + afterConcatInfo + ); } return SetupAndTrackLayerOutputSlot(operation, 0, *layer); } -bool ModelToINetworkConverter::ConvertConv2d(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertConv2d(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -860,7 +1020,7 @@ bool ModelToINetworkConverter::ConvertConv2d(const V1_0::Operation& operation) } } -bool ModelToINetworkConverter::ConvertDepthwiseConv2d(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertDepthwiseConv2d(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -959,8 +1119,10 @@ bool ModelToINetworkConverter::ConvertDepthwiseConv2d(const V1_0::Operation& ope armnn::IsDepthwiseConvolutionSupported, m_Compute, swizzledInputInfo, + swizzledOutputInfo, desc, - weights.GetInfo())) + weights.GetInfo(), + bias.GetInfo())) { return false; } @@ -979,7 +1141,7 @@ bool ModelToINetworkConverter::ConvertDepthwiseConv2d(const V1_0::Operation& ope } } -bool ModelToINetworkConverter::ConvertFloor(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertFloor(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -1009,7 +1171,7 @@ bool ModelToINetworkConverter::ConvertFloor(const V1_0::Operation& operation) return SetupAndTrackLayerOutputSlot(operation, 0, *layer); } -bool ModelToINetworkConverter::ConvertFullyConnected(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertFullyConnected(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -1026,30 +1188,39 @@ bool ModelToINetworkConverter::ConvertFullyConnected(const V1_0::Operation& oper const armnn::TensorInfo& inputInfo = input.GetTensorInfo(); const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output); - armnn::TensorInfo reshapedInfo = inputInfo; + // ArmNN does not currently support non-fixed weights or bias + ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1); // 2D + ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2); // 1D + if (!weightsPin.IsValid() || !biasPin.IsValid()) + { + return Fail("%s: Operation has invalid inputs", __func__); + } + + armnn::ConstTensor weights = weightsPin.GetConstTensor(); + armnn::ConstTensor bias = biasPin.GetConstTensor(); + + armnn::TensorInfo reshapedInfo = inputInfo; if (inputInfo.GetNumDimensions() > 2U) { + unsigned int dim0 = inputInfo.GetShape()[0]; unsigned int dim1 = inputInfo.GetShape()[1]; + for (unsigned int i = 2U; i < inputInfo.GetNumDimensions(); ++i) { dim1 *= inputInfo.GetShape()[i]; } - reshapedInfo.SetShape(armnn::TensorShape({inputInfo.GetShape()[0], dim1})); - } - // ArmNN does not currently support non-fixed weights or bias - ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1); // 2D - ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2); // 1D + unsigned int divisor = weights.GetInfo().GetShape()[1] / dim1; + if(dim0 % divisor != 0) + { + return Fail("%s: Failed to deduce tensor shape", __func__); + } - if (!weightsPin.IsValid() || !biasPin.IsValid()) - { - return Fail("%s: Operation has invalid inputs", __func__); + reshapedInfo.SetShape(armnn::TensorShape({dim0 / divisor, dim1 * divisor})); } // ensuring that the bias value is within 1% of the weights input (small float differences can exist) - armnn::ConstTensor weights = weightsPin.GetConstTensor(); - armnn::ConstTensor bias = biasPin.GetConstTensor(); SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), reshapedInfo); ActivationFn activationFunction; @@ -1065,7 +1236,10 @@ bool ModelToINetworkConverter::ConvertFullyConnected(const V1_0::Operation& oper if (!IsLayerSupported(__func__, armnn::IsFullyConnectedSupported, m_Compute, - reshapedInfo, + inputInfo, + outputInfo, + weights.GetInfo(), + bias.GetInfo(), desc)) { return false; @@ -1100,7 +1274,7 @@ bool ModelToINetworkConverter::ConvertFullyConnected(const V1_0::Operation& oper } } -bool ModelToINetworkConverter::ConvertLocalResponseNormalization(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertLocalResponseNormalization(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -1158,7 +1332,7 @@ bool ModelToINetworkConverter::ConvertLocalResponseNormalization(const V1_0::Ope return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer); } -bool ModelToINetworkConverter::ConvertLogistic(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertLogistic(const neuralnetworks::V1_0::Operation& operation) { armnn::ActivationDescriptor desc; desc.m_Function = armnn::ActivationFunction::Sigmoid; @@ -1166,7 +1340,7 @@ bool ModelToINetworkConverter::ConvertLogistic(const V1_0::Operation& operation) return ConvertToActivation(operation, __func__, desc); } -bool ModelToINetworkConverter::ConvertL2Normalization(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertL2Normalization(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -1189,7 +1363,8 @@ bool ModelToINetworkConverter::ConvertL2Normalization(const V1_0::Operation& ope if (!IsLayerSupported(__func__, armnn::IsL2NormalizationSupported, m_Compute, - swizzledInputInfo)) + swizzledInputInfo, + swizzledOutputInfo)) { return false; } @@ -1203,17 +1378,17 @@ bool ModelToINetworkConverter::ConvertL2Normalization(const V1_0::Operation& ope return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer); } -bool ModelToINetworkConverter::ConvertL2Pool2d(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertL2Pool2d(const neuralnetworks::V1_0::Operation& operation) { return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::L2); } -bool ModelToINetworkConverter::ConvertMaxPool2d(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertMaxPool2d(const neuralnetworks::V1_0::Operation& operation) { return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Max); } -bool ModelToINetworkConverter::ConvertMul(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertMul(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0); LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1); @@ -1223,43 +1398,42 @@ bool ModelToINetworkConverter::ConvertMul(const V1_0::Operation& operation) return Fail("%s: Operation has invalid inputs", __func__); } + // The FuseActivation parameter is always the input index 2 + // and it should be optional ActivationFn activationFunction; - if (!GetInputActivationFunction(operation, 2, activationFunction)) + if (!GetOptionalInputActivation(operation, 2, activationFunction)) { return Fail("%s: Operation has invalid inputs", __func__); } - if (!ValidateBroadcast(m_Model, operation, 2u)) + const Operand* outputOperand = GetOutputOperand(operation, 0); + + if (outputOperand == nullptr) { - return Fail("%s is invalid due to broadcasting", __func__); + return false; } + const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand); + if (!IsLayerSupported(__func__, armnn::IsMultiplicationSupported, m_Compute, input0.GetTensorInfo(), - input1.GetTensorInfo())) - { - return false; - } - - const Operand* outputOperand = GetOutputOperand(operation, 0); - - if (outputOperand == nullptr) + input1.GetTensorInfo(), + outInfo)) { return false; } - const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand); - armnn::IConnectableLayer* const startLayer = m_Network->AddMultiplicationLayer(); armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer); + const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo(); + const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo(); + if (endLayer != nullptr) { - input0.Connect(startLayer->GetInputSlot(0)); - input1.Connect(startLayer->GetInputSlot(1)); - + BroadcastTensor(input0, input1, startLayer, *m_Network); return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer); } else @@ -1268,7 +1442,7 @@ bool ModelToINetworkConverter::ConvertMul(const V1_0::Operation& operation) } } -bool ModelToINetworkConverter::ConvertReLu(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertReLu(const neuralnetworks::V1_0::Operation& operation) { armnn::ActivationDescriptor desc; desc.m_Function = armnn::ActivationFunction::ReLu; @@ -1276,7 +1450,7 @@ bool ModelToINetworkConverter::ConvertReLu(const V1_0::Operation& operation) return ConvertToActivation(operation, __func__, desc); } -bool ModelToINetworkConverter::ConvertReLu1(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertReLu1(const neuralnetworks::V1_0::Operation& operation) { armnn::ActivationDescriptor desc; desc.m_Function = armnn::ActivationFunction::BoundedReLu; @@ -1286,7 +1460,7 @@ bool ModelToINetworkConverter::ConvertReLu1(const V1_0::Operation& operation) return ConvertToActivation(operation, __func__, desc); } -bool ModelToINetworkConverter::ConvertReLu6(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertReLu6(const neuralnetworks::V1_0::Operation& operation) { armnn::ActivationDescriptor desc; desc.m_Function = armnn::ActivationFunction::BoundedReLu; @@ -1295,7 +1469,7 @@ bool ModelToINetworkConverter::ConvertReLu6(const V1_0::Operation& operation) return ConvertToActivation(operation, __func__, desc); } -bool ModelToINetworkConverter::ConvertSoftmax(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertSoftmax(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -1303,6 +1477,14 @@ bool ModelToINetworkConverter::ConvertSoftmax(const V1_0::Operation& operation) return Fail("%s: Operation has invalid inputs", __func__); } + const Operand* outputOperand = GetOutputOperand(operation, 0); + if (!outputOperand) + { + return Fail("%s: Operation has no outputs", __func__); + } + + const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand); + armnn::SoftmaxDescriptor desc; if (!GetInputFloat32(operation, 1, desc.m_Beta)) { @@ -1313,6 +1495,7 @@ bool ModelToINetworkConverter::ConvertSoftmax(const V1_0::Operation& operation) armnn::IsSoftmaxSupported, m_Compute, input.GetTensorInfo(), + outInfo, desc)) { return false; @@ -1325,7 +1508,7 @@ bool ModelToINetworkConverter::ConvertSoftmax(const V1_0::Operation& operation) return SetupAndTrackLayerOutputSlot(operation, 0, *layer); } -bool ModelToINetworkConverter::ConvertTanH(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertTanH(const neuralnetworks::V1_0::Operation& operation) { armnn::ActivationDescriptor desc; desc.m_Function = armnn::ActivationFunction::TanH; @@ -1335,7 +1518,7 @@ bool ModelToINetworkConverter::ConvertTanH(const V1_0::Operation& operation) return ConvertToActivation(operation, __func__, desc); } -bool ModelToINetworkConverter::ConvertReshape(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertReshape(const neuralnetworks::V1_0::Operation& operation) { const Operand* inputOperand = GetInputOperand(operation, 0); const Operand* requestedShapeOperand = GetInputOperand(operation, 1); @@ -1403,7 +1586,7 @@ bool ModelToINetworkConverter::ConvertReshape(const V1_0::Operation& operation) return SetupAndTrackLayerOutputSlot(operation, 0, *layer); } -bool ModelToINetworkConverter::ConvertResizeBilinear(const V1_0::Operation& operation) +bool ModelToINetworkConverter::ConvertResizeBilinear(const neuralnetworks::V1_0::Operation& operation) { LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); if (!input.IsValid()) @@ -1449,7 +1632,307 @@ bool ModelToINetworkConverter::ConvertResizeBilinear(const V1_0::Operation& oper } -bool ModelToINetworkConverter::ConvertToActivation(const V1_0::Operation& operation, +bool ModelToINetworkConverter::ConvertLstm(const neuralnetworks::V1_0::Operation& operation) +{ + // Inputs: + // 00: The input: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, input_size], where + // “batch_size” corresponds to the batching dimension, and “input_size” is the size of the input. + LayerInputHandle input = ConvertToLayerInputHandle(operation, 0); + if (!input.IsValid()) + { + return Fail("%s: Could not read input 0: input", __func__); + } + // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. + LayerInputHandle outputStateIn = ConvertToLayerInputHandle(operation, 18); + if (!outputStateIn.IsValid()) + { + return Fail("%s: Could not read input 18: outputStateIn", __func__); + } + // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units]. + LayerInputHandle cellStateIn = ConvertToLayerInputHandle(operation, 19); + if (!cellStateIn.IsValid()) + { + return Fail("%s: Could not read input 19: cellStateIn", __func__); + } + + // Get the mandatory input tensors: + // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [num_units, input_size]. + const ConstTensorPin inputToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 2); + // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units, input_size]. + const ConstTensorPin inputToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 3); + // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [num_units, input_size]. + const ConstTensorPin inputToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 4); + // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [num_units, output_size]. + const ConstTensorPin recurrentToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 6); + // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [num_units, output_size]. + const ConstTensorPin recurrentToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 7); + // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [num_units, output_size]. + const ConstTensorPin recurrentToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 8); + // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units]. + const ConstTensorPin forgetGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 13); + // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units]. + const ConstTensorPin cellBiasPin = ConvertOperationInputToConstTensorPin(operation, 14); + // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units]. + const ConstTensorPin outputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 15); + + if (!inputToForgetWeightsPin.IsValid() || + !inputToCellWeightsPin.IsValid() || + !inputToOutputWeightsPin.IsValid() || + !recurrentToForgetWeightsPin.IsValid() || + !recurrentToCellWeightsPin.IsValid() || + !recurrentToOutputWeightsPin.IsValid() || + !forgetGateBiasPin.IsValid() || + !cellBiasPin.IsValid() || + !outputGateBiasPin.IsValid()) + { + return Fail("%s: Operation has invalid tensor inputs", __func__); + } + + // Get the optional input tensors: + // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [num_units, input_size], where “num_units” corresponds to the number of cell units. + const ConstTensorPin inputToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 1); + // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e., + // “num_units”), or the second dimension of the “projection_weights”, if defined. + const ConstTensorPin recurrentToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 5); + // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units]. + const ConstTensorPin cellToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 9); + // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units]. + const ConstTensorPin cellToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 10); + // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units]. + const ConstTensorPin cellToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 11); + // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units]. + const ConstTensorPin inputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 12); + // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape + // [output_size, num_units]. + const ConstTensorPin projectionWeightsPin = ConvertOperationInputToConstTensorPin(operation, 16); + // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [output_size]. + const ConstTensorPin projectionBiasPin = ConvertOperationInputToConstTensorPin(operation, 17); + + if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional()) || + (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional()) || + (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional()) || + (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional()) || + (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional()) || + (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional()) || + (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional()) || + (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional())) + { + return Fail("%s: Operation has invalid tensor inputs", __func__); + } + + // Get the mandatory input scalars (actually 1-D tensors of size 1): + // 20: The activation function: A value indicating the activation function: + // 0: None; 1: Relu; 3: Relu6; 4: Tanh; 6: Sigmoid. + // 21: The clipping threshold: for the cell state, such that values are bound within [-cell_clip, cell_clip]. + // If set to 0.0 then clipping is disabled. + // 22: The clipping threshold: for the output from the projection layer, such that values are bound within + // [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled. + ActivationFn activation; + float cellClip; + float projClip; + if (!GetInputActivationFunctionFromTensor(operation, 20, activation) || + !GetInputScalar(operation, 21, OperandType::FLOAT32, cellClip) || + !GetInputScalar(operation, 22, OperandType::FLOAT32, projClip)) + { + return Fail("%s: Operation has invalid scalar inputs", __func__); + } + + // Outputs: + // 00: The scratch buffer: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units * 4] with + // CIFG, or [batch_size, num_units * 3] without CIFG. + const Operand* scratchBuffer = GetOutputOperand(operation, 0); + if (!scratchBuffer) + { + return Fail("%s: Could not read output 0: scratchBuffer", __func__); + } + // 01: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. + const Operand* outputStateOut = GetOutputOperand(operation, 1); + if (!outputStateOut) + { + return Fail("%s: Could not read output 1: outputStateOut", __func__); + } + // 02: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units]. + const Operand* cellStateOut = GetOutputOperand(operation, 2); + if (!cellStateOut) + { + return Fail("%s: Could not read output 2: cellStateOut", __func__); + } + // 03: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. This is + // effectively the same as the current “output state (out)” value. + const Operand* output = GetOutputOperand(operation, 3); + if (!output) + { + return Fail("%s: Could not read output 3: output", __func__); + } + + // set the params structure for the AddLstmLayer call + armnn::LstmInputParams params; + params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr(); + params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr(); + params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr(); + params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr(); + params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr(); + params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr(); + params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr(); + params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr(); + params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr(); + params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr(); + params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr(); + params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr(); + params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr(); + params.m_CellBias = cellBiasPin.GetConstTensorPtr(); + params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr(); + params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr(); + params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr(); + + // set the layer descriptor + armnn::LstmDescriptor desc; + desc.m_ActivationFunc = activation; + desc.m_ClippingThresCell = cellClip; + desc.m_ClippingThresProj = projClip; + desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr || + params.m_RecurrentToInputWeights == nullptr || + params.m_InputGateBias == nullptr); + desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr || + params.m_CellToOutputWeights != nullptr); + desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr); + + // validate the optional input groups + if (desc.m_CifgEnabled && + (params.m_InputToInputWeights != nullptr || + params.m_RecurrentToInputWeights != nullptr || + params.m_InputGateBias != nullptr)) + { + return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights," + " and input gate bias must be provided", __func__); + } + + if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr) + { + return Fail("%s: projection bias should not be provided without projection weights", __func__); + } + + if (desc.m_PeepholeEnabled && + (params.m_CellToForgetWeights == nullptr || + params.m_CellToOutputWeights == nullptr || + (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr))) + { + return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided" + " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__); + } + + // Check if the layer is supported + // Inputs + const armnn::TensorInfo& inputInfo = input.GetTensorInfo(); + const armnn::TensorInfo& outputStateInInfo = outputStateIn.GetTensorInfo(); + const armnn::TensorInfo& cellStateInInfo = cellStateIn.GetTensorInfo(); + + // Outputs + const armnn::TensorInfo& scratchBufferInfo = GetTensorInfoForOperand(*scratchBuffer); + const armnn::TensorInfo& outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut); + const armnn::TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut); + const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output); + + // Basic parameters + const armnn::TensorInfo& inputToForgetWeights = params.m_InputToForgetWeights->GetInfo(); + const armnn::TensorInfo& inputToCellWeights = params.m_InputToCellWeights->GetInfo(); + const armnn::TensorInfo& inputToOutputWeights = params.m_InputToOutputWeights->GetInfo(); + const armnn::TensorInfo& recurrentToForgetWeights = params.m_RecurrentToForgetWeights->GetInfo(); + const armnn::TensorInfo& recurrentToCellWeights = params.m_RecurrentToCellWeights->GetInfo(); + const armnn::TensorInfo& recurrentToOutputWeights = params.m_RecurrentToOutputWeights->GetInfo(); + const armnn::TensorInfo& forgetGateBias = params.m_ForgetGateBias->GetInfo(); + const armnn::TensorInfo& cellBias = params.m_CellBias->GetInfo(); + const armnn::TensorInfo& outputGateBias = params.m_OutputGateBias->GetInfo(); + + //Optional parameters + const armnn::TensorInfo* inputToInputWeights = nullptr; + const armnn::TensorInfo* recurrentToInputWeights = nullptr; + const armnn::TensorInfo* cellToInputWeights = nullptr; + const armnn::TensorInfo* inputGateBias = nullptr; + const armnn::TensorInfo* projectionWeights = nullptr; + const armnn::TensorInfo* projectionBias = nullptr; + const armnn::TensorInfo* cellToForgetWeights = nullptr; + const armnn::TensorInfo* cellToOutputWeights = nullptr; + + if(!desc.m_CifgEnabled) + { + inputToInputWeights = &(params.m_InputToInputWeights->GetInfo()); + recurrentToInputWeights = &(params.m_RecurrentToInputWeights->GetInfo()); + if (params.m_CellToInputWeights != nullptr) + { + cellToInputWeights = &(params.m_CellToInputWeights->GetInfo()); + } + inputGateBias = &(params.m_InputGateBias->GetInfo()); + } + + if(desc.m_ProjectionEnabled) + { + projectionWeights = &(params.m_ProjectionWeights->GetInfo()); + if (params.m_ProjectionBias != nullptr) + { + projectionBias = &(params.m_ProjectionBias->GetInfo()); + } + } + + if(desc.m_PeepholeEnabled) + { + cellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo()); + cellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo()); + } + + if (!IsLayerSupported(__func__, + armnn::IsLstmSupported, + m_Compute, + inputInfo, + outputStateInInfo, + cellStateInInfo, + scratchBufferInfo, + outputStateOutInfo, + cellStateOutInfo, + outputInfo, + desc, + inputToForgetWeights, + inputToCellWeights, + inputToOutputWeights, + recurrentToForgetWeights, + recurrentToCellWeights, + recurrentToOutputWeights, + forgetGateBias, + cellBias, + outputGateBias, + inputToInputWeights, + recurrentToInputWeights, + cellToInputWeights, + inputGateBias, + projectionWeights, + projectionBias, + cellToForgetWeights, + cellToOutputWeights)) + { + return false; + } + + // Add the layer + armnn::IConnectableLayer* layer = m_Network->AddLstmLayer(desc, params, "Lstm"); + + input.Connect(layer->GetInputSlot(0)); + outputStateIn.Connect(layer->GetInputSlot(1)); + cellStateIn.Connect(layer->GetInputSlot(2)); + + return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0) && + SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1) && + SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2) && + SetupAndTrackLayerOutputSlot(operation, 3, *layer, 3)); +} + +bool ModelToINetworkConverter::ConvertToActivation(const neuralnetworks::V1_0::Operation& operation, const char* operationName, const armnn::ActivationDescriptor& activationDesc) { @@ -1459,10 +1942,17 @@ bool ModelToINetworkConverter::ConvertToActivation(const V1_0::Operation& operat return Fail("%s: Input 0 is invalid", operationName); } + const Operand* outputOperand = GetOutputOperand(operation, 0); + if (!outputOperand) + { + return false; + } + const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand); if (!IsLayerSupported(__func__, armnn::IsActivationSupported, m_Compute, input.GetTensorInfo(), + outInfo, activationDesc)) { return false; @@ -1475,7 +1965,7 @@ bool ModelToINetworkConverter::ConvertToActivation(const V1_0::Operation& operat return SetupAndTrackLayerOutputSlot(operation, 0, *layer); } -bool ModelToINetworkConverter::ConvertPooling2d(const V1_0::Operation& operation, +bool ModelToINetworkConverter::ConvertPooling2d(const neuralnetworks::V1_0::Operation& operation, const char* operationName, armnn::PoolingAlgorithm poolType) { @@ -1625,7 +2115,8 @@ const void* ModelToINetworkConverter::GetOperandValueReadOnlyAddress(const Opera return valueStart; } -const Operand* ModelToINetworkConverter::GetInputOperand(const V1_0::Operation& operation, uint32_t inputIndex) const +const Operand* ModelToINetworkConverter::GetInputOperand(const neuralnetworks::V1_0::Operation& operation, + uint32_t inputIndex) const { if (inputIndex >= operation.inputs.size()) { @@ -1637,7 +2128,8 @@ const Operand* ModelToINetworkConverter::GetInputOperand(const V1_0::Operation& return &m_Model.operands[operation.inputs[inputIndex]]; } -const Operand* ModelToINetworkConverter::GetOutputOperand(const V1_0::Operation& operation, uint32_t outputIndex) const +const Operand* ModelToINetworkConverter::GetOutputOperand(const neuralnetworks::V1_0::Operation& operation, + uint32_t outputIndex) const { if (outputIndex >= operation.outputs.size()) { @@ -1650,7 +2142,7 @@ const Operand* ModelToINetworkConverter::GetOutputOperand(const V1_0::Operation& } template -bool ModelToINetworkConverter::GetInputScalar(const V1_0::Operation& operation, uint32_t inputIndex, +bool ModelToINetworkConverter::GetInputScalar(const neuralnetworks::V1_0::Operation& operation, uint32_t inputIndex, OperandType type, T& outValue) const { const Operand* operand = GetInputOperand(operation, inputIndex); @@ -1681,33 +2173,75 @@ bool ModelToINetworkConverter::GetInputScalar(const V1_0::Operation& operation, return true; } -bool ModelToINetworkConverter::GetInputInt32(const V1_0::Operation& operation, +bool ModelToINetworkConverter::GetInputInt32(const neuralnetworks::V1_0::Operation& operation, uint32_t inputIndex, int32_t& outValue) const { return GetInputScalar(operation, inputIndex, OperandType::INT32, outValue); } -bool ModelToINetworkConverter::GetInputFloat32(const V1_0::Operation& operation, +bool ModelToINetworkConverter::GetInputFloat32(const neuralnetworks::V1_0::Operation& operation, uint32_t inputIndex, float& outValue) const { return GetInputScalar(operation, inputIndex, OperandType::FLOAT32, outValue); } -bool ModelToINetworkConverter::GetInputActivationFunction(const V1_0::Operation& operation, - uint32_t inputIndex, - ActivationFn& outActivationFunction) const +bool ModelToINetworkConverter::GetInputActivationFunctionImpl(const neuralnetworks::V1_0::Operation& operation, + uint32_t inputIndex, + OperandType type, + ActivationFn& outActivationFunction) const { + if (type != OperandType::INT32 && type != OperandType::TENSOR_INT32) + { + return Fail("%s: unexpected operand type: %s (should be %s or %s)", + __func__, + toString(type).c_str(), + toString(OperandType::INT32).c_str(), + toString(OperandType::TENSOR_INT32).c_str()); + } + int32_t activationFunctionAsInt; - if (!GetInputInt32(operation, inputIndex, activationFunctionAsInt)) + if (!GetInputScalar(operation, inputIndex, type, activationFunctionAsInt)) { return Fail("%s: failed to get activation input value", __func__); } - outActivationFunction = static_cast(activationFunctionAsInt); return true; } -bool ModelToINetworkConverter::GetInputPaddingScheme(const V1_0::Operation& operation, +bool ModelToINetworkConverter::GetInputActivationFunction(const neuralnetworks::V1_0::Operation& operation, + uint32_t inputIndex, + ActivationFn& outActivationFunction) const +{ + return GetInputActivationFunctionImpl(operation, inputIndex, OperandType::INT32, outActivationFunction); +} + +bool ModelToINetworkConverter::GetInputActivationFunctionFromTensor(const neuralnetworks::V1_0::Operation& operation, + uint32_t inputIndex, + ActivationFn& outActivationFunction) const +{ + // This only accepts a 1-D tensor of size 1 + return GetInputActivationFunctionImpl(operation, inputIndex, OperandType::INT32, outActivationFunction); +} + +bool ModelToINetworkConverter::GetOptionalInputActivation(const neuralnetworks::V1_0::Operation& operation, + uint32_t inputIndex, + ActivationFn& activationFunction) const +{ + if (operation.inputs.size() <= inputIndex) + { + activationFunction = ActivationFn::kActivationNone; + } + else + { + if (!GetInputActivationFunction(operation, inputIndex, activationFunction)) + { + return Fail("%s: Operation has invalid inputs", __func__); + } + } + return true; +} + +bool ModelToINetworkConverter::GetInputPaddingScheme(const neuralnetworks::V1_0::Operation& operation, uint32_t inputIndex, android::nn::PaddingScheme& outPaddingScheme) const { @@ -1722,7 +2256,7 @@ bool ModelToINetworkConverter::GetInputPaddingScheme(const V1_0::Operation& oper } LayerInputHandle ModelToINetworkConverter::ConvertToLayerInputHandle( - const V1_0::Operation& operation, + const neuralnetworks::V1_0::Operation& operation, uint32_t inputIndex) { const Operand* operand = GetInputOperand(operation, inputIndex); @@ -1791,22 +2325,22 @@ LayerInputHandle ModelToINetworkConverter::ConvertToLayerInputHandle( } } -ConstTensorPin ModelToINetworkConverter::ConvertOperationInputToConstTensorPin(const V1_0::Operation& operation, - uint32_t inputIndex, const armnn::PermutationVector& dimensionMappings, - const armnn::TensorShape* overrideTensorShape) +ConstTensorPin ModelToINetworkConverter::ConvertOperationInputToConstTensorPin( + const neuralnetworks::V1_0::Operation& operation, + uint32_t inputIndex, const armnn::PermutationVector& dimensionMappings, + const armnn::TensorShape* overrideTensorShape, bool optional) { const Operand* operand = GetInputOperand(operation, inputIndex); if (!operand) { - Fail("%s: failed to get input operand", __func__); + Fail("%s: failed to get input operand: index=%u", __func__, inputIndex); return ConstTensorPin(); } - - return ConvertOperandToConstTensorPin(*operand, dimensionMappings, overrideTensorShape); + return ConvertOperandToConstTensorPin(*operand, dimensionMappings, overrideTensorShape, optional); } ConstTensorPin ModelToINetworkConverter::ConvertOperandToConstTensorPin(const Operand& operand, - const armnn::PermutationVector& dimensionMappings, const armnn::TensorShape* overrideTensorShape) + const armnn::PermutationVector& dimensionMappings, const armnn::TensorShape* overrideTensorShape, bool optional) { if (!IsOperandTypeSupportedForTensors(operand.type)) { @@ -1823,6 +2357,12 @@ ConstTensorPin ModelToINetworkConverter::ConvertOperandToConstTensorPin(const Op const void* const valueStart = GetOperandValueReadOnlyAddress(operand); if (!valueStart) { + if (optional) + { + // optional tensor with no values is not really an error; return it as invalid, but marked as optional + return ConstTensorPin(true); + } + // mandatory tensor with no values Fail("%s: failed to get operand address", __func__); return ConstTensorPin(); } @@ -1919,7 +2459,7 @@ armnn::IConnectableLayer* ModelToINetworkConverter::ProcessActivation(const armn } if (!IsLayerSupported(__func__, armnn::IsActivationSupported, m_Compute, - prevLayer->GetOutputSlot(0).GetTensorInfo(), activationDesc)) + prevLayer->GetOutputSlot(0).GetTensorInfo(), tensorInfo, activationDesc)) { return nullptr; } @@ -1933,19 +2473,21 @@ armnn::IConnectableLayer* ModelToINetworkConverter::ProcessActivation(const armn return activationLayer; } -bool ModelToINetworkConverter::SetupAndTrackLayerOutputSlot(const V1_0::Operation& operation, uint32_t outputIndex, - armnn::IConnectableLayer& layer) +bool ModelToINetworkConverter::SetupAndTrackLayerOutputSlot(const neuralnetworks::V1_0::Operation& operation, + uint32_t operationOutputIndex, + armnn::IConnectableLayer& layer, + uint32_t layerOutputIndex) { - const Operand* outputOperand = GetOutputOperand(operation, outputIndex); + const Operand* outputOperand = GetOutputOperand(operation, operationOutputIndex); - if ((outputOperand == nullptr) || (outputIndex >= layer.GetNumOutputSlots())) + if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots())) { return false; } - armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(outputIndex); + armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex); - const uint32_t operandIndex = operation.outputs[outputIndex]; + const uint32_t operandIndex = operation.outputs[operationOutputIndex]; m_OutputSlotForOperand[operandIndex] = &outputSlot; outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand)); @@ -1953,6 +2495,13 @@ bool ModelToINetworkConverter::SetupAndTrackLayerOutputSlot(const V1_0::Operatio return true; } +bool ModelToINetworkConverter::SetupAndTrackLayerOutputSlot(const neuralnetworks::V1_0::Operation& operation, + uint32_t outputIndex, + armnn::IConnectableLayer& layer) +{ + return SetupAndTrackLayerOutputSlot(operation, outputIndex, layer, outputIndex); +} + bool ModelToINetworkConverter::IsOperationSupported(uint32_t operationIndex) const { std::map::const_iterator it = m_OperationSupported.find(operationIndex); -- cgit v1.2.1