aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backends/backendsCommon/test/DepthwiseConvolution2dEndToEndTests.hpp3
-rw-r--r--src/backends/tosaCommon/TosaMappings.cpp19
-rw-r--r--src/backends/tosaCommon/operatorMappings/CMakeLists.txt2
-rw-r--r--src/backends/tosaCommon/operatorMappings/ConstantOperator.cpp29
-rw-r--r--src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp5
-rw-r--r--src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.cpp182
-rw-r--r--src/backends/tosaCommon/operatorMappings/DepthwiseConv2dOperator.hpp17
-rw-r--r--src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp1
-rw-r--r--src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp3
-rw-r--r--src/backends/tosaReference/TosaRefLayerSupport.cpp13
-rw-r--r--src/backends/tosaReference/test/TosaRefEndToEndTests.cpp8
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")