diff options
Diffstat (limited to 'src/backends')
11 files changed, 274 insertions, 8 deletions
diff --git a/src/backends/backendsCommon/test/DepthwiseConvolution2dEndToEndTests.hpp b/src/backends/backendsCommon/test/DepthwiseConvolution2dEndToEndTests.hpp index a2c369b692..778b4823c3 100644 --- a/src/backends/backendsCommon/test/DepthwiseConvolution2dEndToEndTests.hpp +++ b/src/backends/backendsCommon/test/DepthwiseConvolution2dEndToEndTests.hpp @@ -45,7 +45,7 @@ armnn::INetworkPtr CreateDepthwiseConvolution2dNetwork(const armnn::DepthwiseCon } // anonymous namespace -template<armnn::DataType ArmnnType, armnn::DataType ArmnnBType> +template<armnn::DataType ArmnnType, armnn::DataType ArmnnBType = ArmnnType> void DepthwiseConvolution2dEndToEnd(const std::vector<armnn::BackendId>& backends, armnn::DataLayout dataLayout) { @@ -168,6 +168,7 @@ void DepthwiseConvolution2dEndToEnd(const std::vector<armnn::BackendId>& backend { PermuteTensorNhwcToNchw(inputInfo, inputData); PermuteTensorNhwcToNchw(outputInfo, expectedOutputData); + PermuteTensorNhwcToNchw(weightsInfo, weightsData); } // Quantize data diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp index 0e44d54aab..81391f8213 100644 --- a/src/backends/tosaCommon/TosaMappings.cpp +++ b/src/backends/tosaCommon/TosaMappings.cpp @@ -58,13 +58,30 @@ TosaSerializationBasicBlock* GetTosaMapping(const Layer* layer, } case LayerType::Constant: { - return ConvertConstantToTosaOperator(layer, outputs); + bool isDepthwiseConv2dWeights = false; + if(layer) + { + // The difference in layout of weights in Tensorflow/ArmNN and the layout + // described in TOSA means we must permute the weights from [1, H, W, C * M] to [H, W, C, M]. + unsigned int slotIdx = layer->GetOutputSlot().GetConnection(0)->GetSlotIndex(); + LayerType type = layer->GetOutputSlot().GetConnection(0)->GetOwningLayer().GetType(); + if(type == LayerType::DepthwiseConvolution2d && slotIdx == 1) + { + isDepthwiseConv2dWeights = true; + } + } + return ConvertConstantToTosaOperator(layer, outputs, isDepthwiseConv2dWeights); } case LayerType::Convolution2d: { auto conv2dDesc = PolymorphicDowncast<const Convolution2dDescriptor*>(&descriptor); return ConvertConv2dToTosaOperator(layer, inputs, outputs, conv2dDesc); } + case LayerType::DepthwiseConvolution2d: + { + auto conv2dDesc = PolymorphicDowncast<const DepthwiseConvolution2dDescriptor*>(&descriptor); + return ConvertDepthwiseConv2dToTosaOperator(layer, inputs, outputs, conv2dDesc); + } case LayerType::Pooling2d: { auto poolDesc = PolymorphicDowncast<const Pooling2dDescriptor*>(&descriptor); diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt index 58a64574d6..eba9011c56 100644 --- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt +++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt @@ -14,6 +14,8 @@ list(APPEND armnnTosaBackendOperators_sources ConstantOperator.cpp Conv2dOperator.hpp Conv2dOperator.cpp + DepthwiseConv2dOperator.hpp + DepthwiseConv2dOperator.cpp ElementwiseBinaryOperator.hpp ElementwiseBinaryOperator.cpp ElementwiseUnaryOperator.cpp diff --git a/src/backends/tosaCommon/operatorMappings/ConstantOperator.cpp b/src/backends/tosaCommon/operatorMappings/ConstantOperator.cpp index c7cd7d7969..f5920fe45e 100644 --- a/src/backends/tosaCommon/operatorMappings/ConstantOperator.cpp +++ b/src/backends/tosaCommon/operatorMappings/ConstantOperator.cpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022, 2024 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -8,7 +8,8 @@ #include <layers/ConstantLayer.hpp> TosaSerializationBasicBlock* ConvertConstantToTosaOperator(const Layer* layer, - const std::vector<const TensorInfo*>& outputs) + const std::vector<const TensorInfo*>& outputs, + bool isDepthwiseConv2dWeights = false) { std::string outputName = std::string("constant_"); std::string blockName = std::string("Op_CONST_block_") + GetUniqueTosaMappingID(); @@ -30,7 +31,29 @@ TosaSerializationBasicBlock* ConvertConstantToTosaOperator(const Layer* layer, auto* op = new TosaSerializationOperator(Op_CONST, Attribute_NONE, nullptr, {}, {outputName}); - std::vector<int32_t> outputShape0 = GetTosaTensorShape(outputs[0]->GetShape()); + std::vector<int32_t> outputShape0; + + if(isDepthwiseConv2dWeights) + { + // Constant weights are connected to a depthwise conv2d layer. From this get the depthwise conv2d input shape. + TensorShape inputShape = + layer->GetOutputSlot().GetConnection(0)->GetOwningLayer().GetInputSlot(0).GetTensorInfo().GetShape(); + + unsigned int multiplier = outputs[0]->GetShape()[3]/inputShape[3]; + + // TOSA requires depthwise conv2d kernel to be converted from [1, H, W, C * M] to layout [H, W, C, M] + outputShape0 = { + static_cast<int32_t>(outputs[0]->GetShape()[1]), + static_cast<int32_t>(outputs[0]->GetShape()[2]), + static_cast<int32_t>(inputShape[3]), + static_cast<int32_t>(multiplier) + }; + } + else + { + outputShape0 = GetTosaTensorShape(outputs[0]->GetShape()); + } + DType outputDType0 = ArmNNToDType(outputs[0]->GetDataType()); // Setup output tensor with constant tensor data if available. diff --git a/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp b/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp index 598e041232..934d24db83 100644 --- a/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -11,5 +11,6 @@ using namespace armnn; using namespace tosa; TosaSerializationBasicBlock* ConvertConstantToTosaOperator(const Layer* layer, - const std::vector<const TensorInfo*>& outputs); + const std::vector<const TensorInfo*>& outputs, + bool isDepthwiseConv2dWeights); diff --git a/src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.cpp b/src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.cpp new file mode 100644 index 0000000000..4fc64ee492 --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.cpp @@ -0,0 +1,182 @@ +// +// Copyright © 2024 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "DepthwiseConv2dOperator.hpp" +#include "TosaRescaleOperatorUtils.hpp" +#include <ResolveType.hpp> + +TosaSerializationBasicBlock* ConvertDepthwiseConv2dToTosaOperator( + const Layer* layer, + const std::vector<const TensorInfo*>& inputs, + const std::vector<const TensorInfo*>& outputs, + const DepthwiseConvolution2dDescriptor* conv2dDescriptor) +{ + std::vector<std::string> inputNames; + std::string outputName = std::string("output0_"); + std::string blockName = std::string("Op_DEPTHWISE_CONV2D_block_") + GetUniqueTosaMappingID(); + + DType inputDType0 = ArmNNToDType(inputs[0]->GetDataType()); + DType outputDType0 = ArmNNToDType(outputs[0]->GetDataType()); + + // Set input names for validation purposes only. + if(layer == nullptr) + { + inputNames.emplace_back("input_0"); + inputNames.emplace_back("input_1"); + if(conv2dDescriptor->m_BiasEnabled) + { + inputNames.emplace_back("input_2"); + } + } + // If a layer is present then the block will be used for execution, so input and output names need to be + // determined using the previous and following layers so the graph is connected correctly. + // For validation this doesn't matter. + else + { + // Get the layer connected to the input slot and determine unique tensor names. + for (uint32_t i = 0; i < inputs.size(); ++i) + { + std::string inputName = GenerateUniqueInputName(layer->GetInputSlot(i)); + inputNames.push_back(inputName); + } + + // Determine unique output tensor name. + outputName = GenerateUniqueOutputName(*layer); + } + + std::vector<TosaSerializationTensor*> tensors; + std::vector<TosaSerializationOperator*> operators; + + // Setup input Tensor + // Only add tensor if connected layer is an input layer. + // As intermediate or constant tensors will be created separately. + // There also can't be duplicate tensors. + if(inputNames[0].find("input_") != std::string::npos) + { + std::vector<int32_t> inputShape0 = GetTosaTensorShape(inputs[0]->GetShape()); + tensors.push_back(new TosaSerializationTensor(inputNames[0], inputShape0, inputDType0, {})); + } + + // Only add input tensors if weights and bias are not constant or if running validation. + // Constant tensors will be created in the ConvertConstantToTosaOperator function. + if(!inputs[1]->IsConstant() || layer == nullptr) + { + std::vector<int32_t> inputShape0 = GetTosaTensorShape(inputs[0]->GetShape()); + std::vector<int32_t> inputShape1 = GetTosaTensorShape(inputs[1]->GetShape()); + + int32_t multiplier = inputShape1[3]/inputShape0[3]; + + // TOSA requires depthwise conv2d kernel to be converted from from [1, H, W, C * M] to [H, W, C, M] + std::vector<int32_t> inputShapeHWCM = { + inputShape1[1], inputShape1[2], inputShape0[3], multiplier + }; + + DType inputDType1 = ArmNNToDType(inputs[1]->GetDataType()); + + tensors.push_back(new TosaSerializationTensor(inputNames[1], inputShapeHWCM, inputDType1, {})); + } + + if(conv2dDescriptor->m_BiasEnabled) + { + if(!inputs[2]->IsConstant() || layer == nullptr) + { + std::vector<int32_t> inputShape2 = GetTosaTensorShape(inputs[2]->GetShape()); + DType inputDType2 = ArmNNToDType(inputs[2]->GetDataType()); + + tensors.push_back(new TosaSerializationTensor(inputNames[2], inputShape2, inputDType2, {})); + } + } + else + { + // If bias is disabled, create a constant bias of 0 as three inputs are required. + std::string constantName = std::string("constant_") + GetUniqueTosaMappingID(); + + operators.push_back(new TosaSerializationOperator(Op_CONST, Attribute_NONE, nullptr, {}, {constantName})); + + // The size of the bias must match the channels dimension, so get the correct index. + unsigned int index = (conv2dDescriptor->m_DataLayout == DataLayout::NHWC) ? 3 : 1; + + const DType dType = (inputDType0 == DType_INT8) ? DType_INT32 : outputDType0; + std::vector<float> data(outputs[0]->GetShape()[index], 0); + + std::vector<uint8_t> uint8Data; + TosaSerializationHandler::ConvertF32toU8(data, uint8Data); + + tensors.push_back(new TosaSerializationTensor(constantName, + {static_cast<int32_t>(outputs[0]->GetShape()[index])}, + dType, + uint8Data)); + inputNames.emplace_back(constantName); + } + + // Setup Output Tensor + std::vector<int32_t> outputShape0 = {GetTosaTensorShape(outputs[0]->GetShape())}; + std::string outputConv2dName; + bool isInputInt8 = (inputDType0 == DType_INT8); + if (isInputInt8) + { + outputConv2dName = std::string("intermediate0_") + GetUniqueTosaMappingID(); + tensors.push_back(new TosaSerializationTensor(outputConv2dName, outputShape0, DType_INT32, {})); + } + else + { + tensors.push_back(new TosaSerializationTensor(outputName, outputShape0, outputDType0, {})); + } + + // Set up CONV2D operator + std::vector<int> pad = {static_cast<int>(conv2dDescriptor->m_PadTop), + static_cast<int>(conv2dDescriptor->m_PadBottom), + static_cast<int>(conv2dDescriptor->m_PadLeft), + static_cast<int>(conv2dDescriptor->m_PadRight)}; + std::vector<int> stride = {static_cast<int>(conv2dDescriptor->m_StrideY), + static_cast<int>(conv2dDescriptor->m_StrideX)}; + std::vector<int> dilation = {static_cast<int>(conv2dDescriptor->m_DilationY), + static_cast<int>(conv2dDescriptor->m_DilationX)}; + TosaConvAttribute attribute(pad, stride, dilation, + inputs[0]->GetQuantizationOffset(), // input_zp + inputs[1]->GetQuantizationOffset(), // weight_zp + false); // local_bound + + std::string& convOutStr = isInputInt8 ? outputConv2dName : outputName; + auto* conv2d_op = new TosaSerializationOperator(Op_DEPTHWISE_CONV2D, + Attribute_ConvAttribute, + &attribute, + inputNames, + {convOutStr}); + operators.push_back(conv2d_op); + + if (isInputInt8) + { + int32_t output_zp = outputs[0]->GetQuantizationOffset(); + double output_scale = outputs[0]->GetQuantizationScales()[0]; + double input_scale = inputs[0]->GetQuantizationScales()[0]; + const std::vector<float>& weight_scales = inputs[1]->GetQuantizationScales(); + + TosaSerializationOperator* rescaleOp = nullptr; + CreateRescaleTosaOperatorPerChannel(outputConv2dName, + outputName, + 0, + output_zp, + true, + true, + input_scale, + output_scale, + weight_scales, + &rescaleOp); + operators.push_back(rescaleOp); + tensors.push_back(new TosaSerializationTensor(outputName, + outputShape0, + DType_INT8, {})); + } + + // operatorInputNames/operatorOutputNames ends up being the same as + // blockInputNames/blockOutputNames for one-to-one ArmNN to TOSA mappings + return new TosaSerializationBasicBlock(blockName, // name + mainName, // region name + operators, // operators + tensors, // tensors + inputNames, // inputs + {outputName}); // outputs +}
\ No newline at end of file diff --git a/src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.hpp b/src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.hpp new file mode 100644 index 0000000000..2282330b31 --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.hpp @@ -0,0 +1,17 @@ +// +// Copyright © 2024 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "TosaOperatorUtils.hpp" + +using namespace armnn; +using namespace tosa; + +TosaSerializationBasicBlock* ConvertDepthwiseConv2dToTosaOperator( + const Layer* layer, + const std::vector<const TensorInfo*>& inputs, + const std::vector<const TensorInfo*>& outputs, + const DepthwiseConvolution2dDescriptor* conv2dDescriptor); diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp index fd0574f30d..a1a90812cd 100644 --- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp +++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp @@ -10,6 +10,7 @@ #include "ConcatOperator.hpp" #include "ConstantOperator.hpp" #include "Conv2dOperator.hpp" +#include "DepthwiseConv2dOperator.hpp" #include "ElementwiseBinaryOperator.hpp" #include "ElementwiseUnaryOperator.hpp" #include "Pooling2DOperator.hpp" diff --git a/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp b/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp index c37d6519bb..942872e5ed 100644 --- a/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp +++ b/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp @@ -208,8 +208,9 @@ inline void CreateRescaleTosaOperatorPerChannel(const std::string& inputName, op_tensor_shifts.push_back(shift); } + bool per_channel = weight_scales.size() == 1 ? false : true; CreateRescaleTosaOperator(inputName, outputName, op_tensor_multipliers, op_tensor_shifts, - input_zp, output_zp, double_round, scale32, true, op); + input_zp, output_zp, double_round, scale32, per_channel, op); } inline void CreateFromInt32RescaleTosaOperator(const std::string& inputName, diff --git a/src/backends/tosaReference/TosaRefLayerSupport.cpp b/src/backends/tosaReference/TosaRefLayerSupport.cpp index dac06676bf..38fd01b93c 100644 --- a/src/backends/tosaReference/TosaRefLayerSupport.cpp +++ b/src/backends/tosaReference/TosaRefLayerSupport.cpp @@ -73,6 +73,19 @@ bool TosaRefLayerSupport::IsLayerSupported(const LayerType& type, } break; } + case LayerType::DepthwiseConvolution2d: + { + inputInfos.push_back(&infos[0]); // input + outputInfos.push_back(&infos[1]); // output + inputInfos.push_back(&infos[2]); // weights + + auto conv2dDesc = PolymorphicDowncast<const DepthwiseConvolution2dDescriptor*>(&descriptor); + if(conv2dDesc->m_BiasEnabled) + { + inputInfos.push_back(&infos[3]); // bias + } + break; + } case LayerType::ElementwiseUnary: case LayerType::Pooling2d: case LayerType::Quantize: diff --git a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp index f86edd52f4..f5da79c04a 100644 --- a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp +++ b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp @@ -9,6 +9,7 @@ #include "backendsCommon/test/AdditionEndToEndTestImpl.hpp" #include "backendsCommon/test/Convolution2dEndToEndTestImpl.hpp" #include "backendsCommon/test/ConcatEndToEndTestImpl.hpp" +#include "backendsCommon/test/DepthwiseConvolution2dEndToEndTests.hpp" #include "backendsCommon/test/ElementwiseBinaryEndToEndTestImpl.hpp" #include "backendsCommon/test/ElementwiseUnaryEndToEndTestImpl.hpp" #include "backendsCommon/test/MultiplicationEndToEndTestImpl.hpp" @@ -129,6 +130,13 @@ TEST_CASE("TosaRefConv2dWithoutBiasEndtoEndTestInt8") armnn::DataType::Signed32>(tosaDefaultBackends, armnn::DataLayout::NHWC, false); } +// DepthwiseConv2d +TEST_CASE("TosaRefDepthwiseConv2dEndtoEndTestInt8") +{ + DepthwiseConvolution2dEndToEnd<armnn::DataType::QSymmS8, + armnn::DataType::Signed32>(tosaDefaultBackends, armnn::DataLayout::NHWC); +} + // Elementwise Binary //Add TEST_CASE("TosaRefAddEndtoEndTestInt32") |