aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTracy Narine <tracy.narine@arm.com>2023-11-28 11:55:08 +0000
committerTracy Narine <tracy.narine@arm.com>2024-01-17 20:19:09 +0000
commit10403ec9f214d4fec2b85866882a9bc707852412 (patch)
treecf1bef9015d228f9c84cada5386a085781d38f57
parent8ded1788e93886c687c86c1136e34cf0ed7acf47 (diff)
downloadarmnn-10403ec9f214d4fec2b85866882a9bc707852412.tar.gz
IVGCVSW-7344 Add LeakyRelu Activation support to TOSA Reference Backend
* Adding a one to many FP32 tosa mapping for Leaky Relu * Added a few utilities that are needed * Added new tests Signed-off-by: Tracy Narine <tracy.narine@arm.com> Change-Id: If1d7c57a523961581777a244416a7346a9310803
-rw-r--r--src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp31
-rw-r--r--src/backends/reference/test/RefEndToEndTests.cpp28
-rw-r--r--src/backends/tosaCommon/TosaMappings.cpp14
-rw-r--r--src/backends/tosaCommon/operatorMappings/ActivationOperator.cpp284
-rw-r--r--src/backends/tosaCommon/operatorMappings/ActivationOperator.hpp20
-rw-r--r--src/backends/tosaCommon/operatorMappings/CMakeLists.txt5
-rw-r--r--src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp3
-rw-r--r--src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp12
-rw-r--r--src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp130
-rw-r--r--src/backends/tosaCommon/test/OneToManyMappingTests.cpp54
-rw-r--r--src/backends/tosaReference/TosaRefLayerSupport.cpp6
-rw-r--r--src/backends/tosaReference/test/TosaRefEndToEndTests.cpp2
-rw-r--r--src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp42
13 files changed, 616 insertions, 15 deletions
diff --git a/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp b/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp
index c6d49b1df6..996e7605d2 100644
--- a/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp
+++ b/src/backends/backendsCommon/test/ActivationEndToEndTestImpl.hpp
@@ -1,5 +1,5 @@
//
-// Copyright © 2020-2021,2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2020-2021,2023-2024 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//
#pragma once
@@ -171,4 +171,31 @@ void HardSwishEndToEndTest(const std::vector<BackendId>& backends)
descriptor);
}
-} // anonymous namespace \ No newline at end of file
+/** Executes an end to end test for Leaky Relu activation with specific input and expected-output data
+ *
+ * @tparam ArmnnType The armnn data type for the input and expected-output data
+ * @param backends The backends on which to run the test
+ */
+template<armnn::DataType ArmnnType>
+void LeakyReluEndToEndTest(const std::vector<BackendId>& backends, const float qScale=1.0f, const int32_t qOffset=0)
+{
+ std::vector<float> floatInputData{ -2.0f, -1.0f, -0.0f, 0.0f,
+ 1.0f, 2.0f, 3.0f, 4.0f };
+
+ std::vector<float> floatExpectedOutputData{ -0.02f, -0.01f, -0.0f, 0.0f,
+ 1.0f, 2.0f, 3.0f, 4.0f };
+
+ armnn::TensorInfo inputInfo({ 2, 2, 2, 1 }, ArmnnType, qScale, qOffset, true);
+ armnn::TensorInfo outputInfo({ 2, 2, 2, 1 }, ArmnnType, qScale, qOffset);
+
+ armnn::ActivationDescriptor descriptor(ActivationFunction::LeakyReLu, static_cast<float>(0.01));
+
+ ActivationEndToEndImpl<ArmnnType>(backends,
+ floatInputData,
+ floatExpectedOutputData,
+ inputInfo,
+ outputInfo,
+ descriptor);
+}
+
+} // anonymous namespace
diff --git a/src/backends/reference/test/RefEndToEndTests.cpp b/src/backends/reference/test/RefEndToEndTests.cpp
index 78852bc645..63f76b17b1 100644
--- a/src/backends/reference/test/RefEndToEndTests.cpp
+++ b/src/backends/reference/test/RefEndToEndTests.cpp
@@ -1,5 +1,5 @@
//
-// Copyright © 2017-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2017-2024 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//
@@ -1044,6 +1044,32 @@ TEST_CASE("RefHardSwishEndToEndTestQSymmS16")
HardSwishEndToEndTest<armnn::DataType::QSymmS16>(defaultBackends);
}
+// LeakyRelu
+TEST_CASE("RefLeakyReluActivationFloat32")
+{
+ LeakyReluEndToEndTest<DataType::Float32>(defaultBackends);
+}
+
+TEST_CASE("RefLeakyReluActivationFloat16")
+{
+ LeakyReluEndToEndTest<DataType::Float16>(defaultBackends, 0.3f, 5);
+}
+
+TEST_CASE("RefLeakyReluActivationInt8")
+{
+ LeakyReluEndToEndTest<DataType::QAsymmS8>(defaultBackends, 0.6f, 7);
+}
+
+TEST_CASE("RefLeakyReluActivationUInt8")
+{
+ LeakyReluEndToEndTest<DataType::QAsymmU8>(defaultBackends, 0.12f, 15);
+}
+
+TEST_CASE("RefLeakyReluActivationInt16")
+{
+ LeakyReluEndToEndTest<DataType::QSymmS16>(defaultBackends, 0.15f, 55);
+}
+
// LogSoftmax
TEST_CASE("RefLogSoftmaxEndToEndTest")
{
diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp
index 6c6bff4087..1ebb68b3c9 100644
--- a/src/backends/tosaCommon/TosaMappings.cpp
+++ b/src/backends/tosaCommon/TosaMappings.cpp
@@ -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
//
@@ -23,6 +23,18 @@ TosaSerializationBasicBlock* GetTosaMapping(const Layer* layer,
{
switch (type)
{
+ case LayerType::Activation:
+ {
+ auto activationDesc = PolymorphicDowncast<const ActivationDescriptor*>(&descriptor);
+ if (activationDesc->m_Function == ActivationFunction::LeakyReLu)
+ {
+ return ConvertActivationToTosaOperator(layer, inputs, outputs, activationDesc);
+ }
+ else
+ {
+ return CreateEmptyTosaSerializationBasicBlock();
+ }
+ }
case LayerType::Addition:
case LayerType::Multiplication:
case LayerType::Subtraction:
diff --git a/src/backends/tosaCommon/operatorMappings/ActivationOperator.cpp b/src/backends/tosaCommon/operatorMappings/ActivationOperator.cpp
new file mode 100644
index 0000000000..4009ae834a
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/ActivationOperator.cpp
@@ -0,0 +1,284 @@
+//
+// Copyright © 2024 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+//
+// Copyright © 2020 The TensorFlow Authors. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include "ActivationOperator.hpp"
+#include "TosaRescaleOperatorUtils.hpp"
+
+#include <layers/ActivationLayer.hpp>
+
+// This function is paraphrased from:
+// tensorflow/compiler/mlir/tosa/transforms/legalize_tfl.cc from function ConvertTFLLeakyReluOp
+TosaSerializationBasicBlock* ConvertActivationToTosaOperator(const Layer* layer,
+ const std::vector<const TensorInfo*>& inputs,
+ const std::vector<const TensorInfo*>& outputs,
+ const ActivationDescriptor* activationDescriptor)
+{
+ if (inputs.size() != 1)
+ {
+ throw armnn::Exception("ConvertActivationToTosaOperator: 1 input tensors required.");
+ }
+
+ if (outputs.size() != 1)
+ {
+ throw armnn::Exception("ConvertActivationToTosaOperator: 1 output tensor required.");
+ }
+
+ std::string inputName = std::string("input0_");
+ std::string outputNameAlpha = std::string("intermediate1_") + GetUniqueTosaMappingID();
+ std::string outputNameMul = std::string("intermediate2_") + GetUniqueTosaMappingID();
+ std::string outputName = std::string("output0_");
+ std::string blockName = std::string("Op_ACTIVATION_block_") + GetUniqueTosaMappingID();
+
+ // 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.
+ if (layer != nullptr)
+ {
+ // Get the layers connected to the input slots and determine unique tensors names.
+ Layer& connectedInputLayer = layer->GetInputSlot(0).GetConnectedOutputSlot()->GetOwningLayer();
+ inputName = GenerateUniqueName(connectedInputLayer, 0);
+
+ // Determine unique output tensor name.
+ outputName = GenerateUniqueOutputName(*layer, 0);
+ }
+
+ std::vector<TosaSerializationTensor*> tensors;
+
+ // Only add input tensors if connected layer is an input layer.
+ // As intermediate or constant tensors will be created separately.
+ // There also can't be duplicate tensor.
+ std::vector<int32_t> inputShape0;
+ DType inputDType0 = DType::DType_UNKNOWN;
+ if(inputName.find("input0_") != std::string::npos)
+ {
+ inputShape0 = GetTosaTensorShape(inputs[0]->GetShape());
+ inputDType0 = ArmNNToDType(inputs[0]->GetDataType());
+ tensors.push_back(new TosaSerializationTensor(inputName, inputShape0, inputDType0, {}));
+ }
+
+ std::vector<int32_t> outputShape0 = GetTosaTensorShape(outputs[0]->GetShape());
+ DType outputDType0 = ArmNNToDType(outputs[0]->GetDataType());
+ tensors.push_back(new TosaSerializationTensor(outputName, outputShape0, outputDType0, {}));
+
+#if TOSA_FWD_COMPAT_VERSION(0, 60, 0)
+ std::string outputNameMAXMIN= std::string("intermediate3_") + GetUniqueTosaMappingID();
+
+ if (inputDType0 == DType::DType_FP32)
+ {
+ // const_alpha
+ TosaSerializationOperator* alphaOp = nullptr;
+ TosaSerializationTensor* alphaTensor = nullptr;
+ CreateConstTosaOperator<float>(outputNameAlpha,
+ activationDescriptor->m_A,
+ inputDType0,
+ inputShape0,
+ alphaOp,
+ alphaTensor);
+ tensors.push_back(alphaTensor);
+
+ // mul
+ int32_t shift = 0;
+ TosaMulAttribute mulAttribute(shift);
+ TosaSerializationOperator* mulOp = new TosaSerializationOperator(Op_MUL,
+ Attribute_MulAttribute,
+ &mulAttribute,
+ {inputName, outputNameAlpha},
+ {outputNameMul});
+ tensors.push_back(new TosaSerializationTensor(outputNameMul, inputShape0, inputDType0, {}));
+
+ TosaSerializationOperator* op = nullptr;
+ if (activationDescriptor->m_A <= 1.0)
+ {
+ op = new TosaSerializationOperator(Op_MAXIMUM,
+ Attribute_NONE,
+ nullptr,
+ {inputName, outputNameMul},
+ {outputName});
+ }
+ else
+ {
+ op = new TosaSerializationOperator(Op_MINIMUM,
+ Attribute_NONE,
+ nullptr,
+ {inputName, outputNameMul},
+ {outputName});
+
+ }
+
+ // 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
+ {alphaOp, mulOp, op}, // operators
+ tensors, // tensors
+ {inputName}, // inputs
+ {outputName}); // outputs
+ }
+ else
+ {
+ std::string outputNameRescaleAlpha = std::string("intermediate3_") + GetUniqueTosaMappingID();
+ std::string outputNameRescaleIdentity = std::string("intermediate4_") + GetUniqueTosaMappingID();
+ std::string outputNameRescaleMaxMin = std::string("intermediate5_") + GetUniqueTosaMappingID();
+
+ DType rescale_type = DType::DType_INT32;
+ float alpha = activationDescriptor->m_A;
+ double scale_alpha = inputs[0]->GetQuantizationScale() * alpha / outputs[0]->GetQuantizationScale();
+ double scale_identity = inputs[0]->GetQuantizationScale() / outputs[0]->GetQuantizationScale();
+ int32_t input_zp = inputs[0]->GetQuantizationOffset();
+ int32_t output_zp = outputs[0]->GetQuantizationOffset();
+
+ // Value op_rescale_alpha_in =
+ // buildRescale(rewriter, op, rescale_type, input, scale_alpha,
+ // input_qtype.getZeroPoint(), 0, true, true);
+ TosaSerializationOperator* rescaleAlphaOp = nullptr;
+ TosaSerializationTensor* rescaleAlphaTensor = nullptr;
+ CreateRescaleTosaOperator(inputName,
+ outputNameRescaleAlpha,
+ rescale_type,
+ inputShape0,
+ scale_alpha,
+ input_zp,
+ 0,
+ true,
+ true,
+ &rescaleAlphaOp,
+ &rescaleAlphaTensor);
+ tensors.push_back(rescaleAlphaTensor);
+
+ // Value op_rescale_identity_in =
+ // buildRescale(rewriter, op, rescale_type, input, scale_identity,
+ // input_qtype.getZeroPoint(), 0, true, true);
+ TosaSerializationOperator* rescaleIdentityOp = nullptr;
+ TosaSerializationTensor* rescaleIdentityTensor = nullptr;
+ CreateRescaleTosaOperator(inputName,
+ outputNameRescaleIdentity,
+ rescale_type,
+ inputShape0,
+ scale_identity,
+ input_zp,
+ 0,
+ true,
+ true,
+ &rescaleIdentityOp,
+ &rescaleIdentityTensor);
+ tensors.push_back(rescaleIdentityTensor);
+
+ // Value result_int32;
+ // if (alpha <= 1.0) {
+ // auto max_op = CreateOpAndInfer<tosa::MaximumOp>(
+ // rewriter, op->getLoc(), rescale_type, op_rescale_identity_in,
+ // op_rescale_alpha_in);
+ // result_int32 = max_op.getResult();
+ // } else {
+ // auto min_op = CreateOpAndInfer<tosa::MinimumOp>(
+ // rewriter, op->getLoc(), rescale_type, op_rescale_identity_in,
+ // op_rescale_alpha_in);
+ // result_int32 = min_op.getResult();
+ // }
+ TosaSerializationOperator* op = nullptr;
+ if (alpha <= 1.0)
+ {
+ op = new TosaSerializationOperator(Op_MAXIMUM,
+ Attribute_NONE,
+ nullptr,
+ {outputNameRescaleAlpha, outputNameRescaleIdentity},
+ {outputNameRescaleMaxMin});
+ }
+ else
+ {
+ op = new TosaSerializationOperator(Op_MINIMUM,
+ Attribute_NONE,
+ nullptr,
+ {outputNameRescaleAlpha, outputNameRescaleIdentity},
+ {outputNameRescaleMaxMin});
+
+ }
+ tensors.push_back(new TosaSerializationTensor(outputNameRescaleMaxMin, inputShape0, rescale_type, {}));
+
+ // Value output = buildRescaleFromInt32(rewriter, op, output_type, result_int32,
+ // 1.0, output_qtype.getZeroPoint());
+ TosaSerializationOperator* rescaleOutputOp = nullptr;
+ CreateFromInt32RescaleTosaOperator(outputNameRescaleMaxMin,
+ outputName,
+ outputDType0,
+ outputShape0,
+ 1.0,
+ output_zp,
+ &rescaleOutputOp,
+ nullptr);
+
+ // 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
+ {rescaleAlphaOp, rescaleIdentityOp, op, rescaleOutputOp}, // operators
+ tensors, // tensors
+ {inputName}, // inputs
+ {outputName}); // outputs
+ }
+#else
+ std::string outputNameZero = std::string("intermediate3_") + GetUniqueTosaMappingID();
+ std::string outputNameGE = std::string("intermediate4_") + GetUniqueTosaMappingID();
+
+ // const_zero
+ TosaSerializationOperator* zeroOp = nullptr;
+ TosaSerializationTensor* zeroTensor = nullptr;
+ CreateConstTosaOperator<float>(outputNameZero,
+ 0.0f,
+ inputDType0,
+ inputShape0,
+ zeroOp,
+ zeroTensor);
+ tensors.push_back(zeroTensor);
+
+ // const_alpha
+ TosaSerializationOperator* alphaOp = nullptr;
+ TosaSerializationTensor* alphaTensor = nullptr;
+ CreateConstTosaOperator<float>(outputNameAlpha,
+ activationDescriptor->m_A,
+ inputDType0,
+ inputShape0,
+ alphaOp,
+ alphaTensor);
+ tensors.push_back(alphaTensor);
+
+ // mul
+ int32_t shift = 0;
+ TosaMulAttribute mulAttribute(shift);
+ TosaSerializationOperator* mulOp = new TosaSerializationOperator(Op_MUL,
+ Attribute_MulAttribute,
+ &mulAttribute,
+ {inputName, outputNameAlpha},
+ {outputNameMul});
+ tensors.push_back(new TosaSerializationTensor(outputNameMul, inputShape0, inputDType0, {}));
+
+ // greater_equal
+ TosaSerializationOperator* geOp = new TosaSerializationOperator(Op_GREATER_EQUAL,
+ Attribute_NONE,
+ nullptr,
+ {inputName, outputNameZero},
+ {outputNameGE});
+ tensors.push_back(new TosaSerializationTensor(outputNameGE, outputShape0, DType::DType_BOOL, {}));
+
+ // select
+ TosaSerializationOperator* selOp = new TosaSerializationOperator(Op_SELECT,
+ Attribute_NONE,
+ nullptr,
+ {outputNameGE, inputName, outputNameMul},
+ {outputName});
+
+ // 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
+ {zeroOp, alphaOp, mulOp, geOp, selOp}, // operators
+ tensors, // tensors
+ {inputName}, // inputs
+ {outputName}); // outputs
+#endif
+}
diff --git a/src/backends/tosaCommon/operatorMappings/ActivationOperator.hpp b/src/backends/tosaCommon/operatorMappings/ActivationOperator.hpp
new file mode 100644
index 0000000000..7519f0c155
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/ActivationOperator.hpp
@@ -0,0 +1,20 @@
+//
+// Copyright © 2024 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <Layer.hpp>
+
+#include <tosa_serialization_handler.h>
+
+#include "TosaOperatorUtils.hpp"
+
+using namespace armnn;
+using namespace tosa;
+
+TosaSerializationBasicBlock* ConvertActivationToTosaOperator(const Layer* layer,
+ const std::vector<const TensorInfo*>& inputs,
+ const std::vector<const TensorInfo*>& outputs,
+ const ActivationDescriptor* activationDescriptor);
diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
index b69463487b..58a64574d6 100644
--- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
+++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt
@@ -1,9 +1,11 @@
#
-# Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+# Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
# SPDX-License-Identifier: MIT
#
list(APPEND armnnTosaBackendOperators_sources
+ ActivationOperator.hpp
+ ActivationOperator.cpp
AvgPool2DIgnoreValueOperator.hpp
AvgPool2DIgnoreValueOperator.cpp
ConcatOperator.hpp
@@ -29,6 +31,7 @@ list(APPEND armnnTosaBackendOperators_sources
SplitOperator.hpp
SplitOperator.cpp
TosaOperatorUtils.hpp
+ TosaRescaleOperatorUtils.hpp
TransposeConv2dOperator.hpp
TransposeConv2dOperator.cpp
TransposeOperator.hpp
diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
index 3d88b6563f..fd0574f30d 100644
--- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
+++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp
@@ -1,10 +1,11 @@
//
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//
#pragma once
+#include "ActivationOperator.hpp"
#include "AvgPool2DIgnoreValueOperator.hpp"
#include "ConcatOperator.hpp"
#include "ConstantOperator.hpp"
diff --git a/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp b/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp
index e43f6ca027..05ccef4a9c 100644
--- a/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp
+++ b/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.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
//
@@ -12,6 +12,7 @@
#include "common/include/ProfilingGuid.hpp"
#include <tosa_serialization_handler.h>
+#include <version.h>
using namespace armnn;
using namespace tosa;
@@ -355,7 +356,6 @@ inline std::vector<uint8_t> ConvertConstantTensorDataToBuffer(const std::shared_
return uint8Data;
}
-
inline std::vector<uint8_t> CreateConstTosaData(const void* value,
DType dtype,
const std::vector<int32_t>& shape)
@@ -452,4 +452,10 @@ inline void CreateConstTosaOperator(const std::string& outputName,
tensor = new TosaSerializationTensor(outputName, shape, dtype, uint8Data);
ARMNN_THROW_MSG_IF_FALSE(tensor, armnn::Exception, "CreateConstTosaOperator: failed to created tensor");
-} \ No newline at end of file
+}
+
+// Macro to conditionally compile Tosa code
+#define TOSA_FWD_COMPAT_VERSION(_major, _minor, _patch) \
+ (TOSA_REFERENCE_MODEL_VERSION_MAJOR >= _major) && \
+ (TOSA_REFERENCE_MODEL_VERSION_MINOR >= _minor) && \
+ (TOSA_REFERENCE_MODEL_VERSION_PATCH >= _patch)
diff --git a/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp b/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp
new file mode 100644
index 0000000000..a0432846f5
--- /dev/null
+++ b/src/backends/tosaCommon/operatorMappings/TosaRescaleOperatorUtils.hpp
@@ -0,0 +1,130 @@
+//
+// Copyright © 2024 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include <armnn/Exceptions.hpp>
+
+#pragma once
+
+inline void CreateRescaleTosaOperator(const std::string& inputName,
+ const std::string& outputName,
+ DType output_type,
+ const std::vector<int32_t>& shape,
+ int32_t scale_multiplier,
+ int32_t scale_shift,
+ int32_t input_zp,
+ int32_t output_zp,
+ bool double_round,
+ bool scale32,
+ TosaSerializationOperator** op,
+ TosaSerializationTensor** tensor)
+{
+ if (!op)
+ {
+ throw armnn::Exception("CreateRescaleTosaOperator: nullptr op");
+ }
+
+ std::vector<int32_t> multipliers{scale_multiplier};
+ std::vector<int32_t> shifts{scale_shift};
+ TosaRescaleAttribute attribute(input_zp,
+ output_zp,
+ multipliers,
+ shifts,
+ scale32,
+ double_round,
+ false);
+
+ // op
+ *op = new TosaSerializationOperator(Op_RESCALE, Attribute_RescaleAttribute, &attribute, {inputName}, {outputName});
+ if (!(*op))
+ {
+ throw armnn::Exception("CreateRescaleTosaOperator: failed to created operator");
+ }
+ if (tensor != nullptr)
+ {
+ // tensor
+ *tensor = new TosaSerializationTensor(outputName, shape, output_type, {});
+ if (! (*tensor))
+ {
+ throw armnn::Exception("CreateRescaleTosaOperator: failed to created tensor");
+ }
+ }
+}
+
+inline void CreateRescaleTosaOperator(const std::string& inputName,
+ const std::string& outputName,
+ DType output_type,
+ const std::vector<int32_t>& shape,
+ double scale,
+ int32_t input_zp,
+ int32_t output_zp,
+ bool double_round,
+ bool scale32,
+ TosaSerializationOperator** op,
+ TosaSerializationTensor** tensor)
+{
+ // The code that follows is based on the behaviour specified in
+ // https://www.mlplatform.org/tosa/tosa_spec.html#_precision_scaling
+
+ auto GetScaleParams = [](double scale, double& m, int32_t& n)
+ {
+ m = 0;
+ n = 0;
+
+ double lastErr = 1e06;
+
+ const int32_t numExponents = 62;
+ const double start = 1.0;
+ const double end = 2.0;
+
+ // Slow iterative approach but running in Reference only
+ for (int32_t i = 0; i < numExponents; ++i)
+ {
+ double exp = 1.0 / (1 << i);
+ double currentM = scale / exp; // Find current m given value = currentM * exp
+ if ((currentM >= start) && (currentM < end))
+ {
+ double value = currentM * exp;
+ double err = std::abs(scale - value);
+ if (err < lastErr)
+ {
+ // Take the m, n that minimize the error
+ n = i;
+ m = currentM;
+ lastErr = err;
+ }
+ }
+ }
+ };
+
+ auto GetMultiplierShiftByScale = [GetScaleParams](bool scale32, double scale, int32_t& multiplier, int32_t& shift)
+ {
+ double m = 0;
+ int32_t n = 0;
+
+ GetScaleParams(scale, m, n);
+
+ multiplier = (scale32) ? (1 << 30) * static_cast<int32_t>(m) : (1 << 14) * static_cast<int32_t>(m);
+ shift = (scale32) ? (30 + n) : (14 + n);
+ };
+
+ int32_t multiplier;
+ int32_t shift;
+ GetMultiplierShiftByScale(scale32, scale, multiplier, shift);
+ CreateRescaleTosaOperator(inputName, outputName, output_type, shape, multiplier, shift,
+ input_zp, output_zp, double_round, scale32, op, tensor);
+}
+
+inline void CreateFromInt32RescaleTosaOperator(const std::string& inputName,
+ const std::string& outputName,
+ DType output_type,
+ const std::vector<int32_t>& shape,
+ double output_scale,
+ int32_t output_zp,
+ TosaSerializationOperator** op,
+ TosaSerializationTensor** tensor)
+{
+ CreateRescaleTosaOperator(inputName, outputName, output_type, shape,
+ output_scale, 0, output_zp, true, true, op, tensor);
+}
diff --git a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
index f439b044a4..dde4d79392 100644
--- a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
+++ b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp
@@ -1,12 +1,12 @@
//
-// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved.
+// Copyright © 2022-2024 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//
#include "AvgPool2DIgnoreValueChecker.hpp"
#include "QuantizeChecker.hpp"
#include "SplitChecker.hpp"
-
+#include <backendsCommon/test/ActivationEndToEndTestImpl.hpp>
#include <armnn/IRuntime.hpp>
using namespace armnn;
@@ -190,4 +190,52 @@ TEST_CASE("GetTosaMappingFromLayer_SplitLayer")
outShape,
descriptor);
}
-} \ No newline at end of file
+
+// Activation
+
+static std::vector<BackendId> tosaDefaultBackends = { "TosaRef" };
+
+TEST_CASE("GetTosaMapping_ActivationFloat32")
+{
+ LeakyReluEndToEndTest<DataType::Float32>(tosaDefaultBackends);
+}
+
+TEST_CASE("GetTosaMapping_ActivationFloat16")
+{
+ try
+ {
+ LeakyReluEndToEndTest<DataType::Float16>(tosaDefaultBackends);
+ }
+ catch (armnn::Exception& e)
+ {
+ CHECK_EQ(std::string(e.what()), "Failed to assign a backend to each layer");
+ }
+}
+
+TEST_CASE("GetTosaMapping_ActivationInt32")
+{
+ LeakyReluEndToEndTest<DataType::Signed32>(tosaDefaultBackends, 0.15f, 0);
+}
+
+TEST_CASE("GetTosaMapping_ActivationInt16")
+{
+ LeakyReluEndToEndTest<DataType::QSymmS16>(tosaDefaultBackends, 0.35f, 0);
+}
+
+TEST_CASE("GetTosaMapping_ActivationInt8")
+{
+ LeakyReluEndToEndTest<DataType::QSymmS8>(tosaDefaultBackends, 0.75f, 0);
+}
+
+TEST_CASE("GetTosaMapping_ActivationUInt8")
+{
+ try
+ {
+ LeakyReluEndToEndTest<DataType::QAsymmU8>(tosaDefaultBackends);
+ }
+ catch (armnn::Exception& e)
+ {
+ CHECK_EQ(std::string(e.what()), "Failed to assign a backend to each layer");
+ }
+}
+}
diff --git a/src/backends/tosaReference/TosaRefLayerSupport.cpp b/src/backends/tosaReference/TosaRefLayerSupport.cpp
index a38c431e09..dac06676bf 100644
--- a/src/backends/tosaReference/TosaRefLayerSupport.cpp
+++ b/src/backends/tosaReference/TosaRefLayerSupport.cpp
@@ -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
//
@@ -34,6 +34,10 @@ bool TosaRefLayerSupport::IsLayerSupported(const LayerType& type,
switch (type)
{
+ case LayerType::Activation:
+ inputInfos.push_back(&infos[0]);
+ outputInfos.push_back(&infos[1]);
+ break;
case LayerType::Input:
case LayerType::Output:
return true;
diff --git a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
index 63fa69d8a9..7a3edaf212 100644
--- a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp
@@ -25,7 +25,7 @@
TEST_SUITE("TosaRefEndToEnd")
{
-std::vector<BackendId> tosaDefaultBackends = { "TosaRef" };
+static std::vector<BackendId> tosaDefaultBackends = { "TosaRef" };
// Addition
TEST_CASE("TosaRefAdditionEndtoEndTestFloat32")
diff --git a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
index 6f038ab69b..2da2875a76 100644
--- a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
+++ b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp
@@ -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
//
@@ -287,6 +287,46 @@ TEST_CASE("IsLayerSupportedTosaReferenceAvgPooling2d_IgnoreValue")
CHECK(supported);
}
+TEST_CASE("IsLayerSupportedTosaReferenceLeakyReLuActivation")
+{
+ TensorInfo inputInfo1({1,1,3,4}, DataType::Float32);
+ TensorInfo inputInfo2({1,1,3,4}, DataType::Float32);
+ TensorInfo outputInfo({1,1,3,4}, DataType::Float32);
+
+ TosaRefLayerSupport supportChecker;
+ std::string reasonIfNotSupported;
+ ActivationDescriptor descriptor;
+ descriptor.m_Function = ActivationFunction::LeakyReLu;
+ auto supported = supportChecker.IsLayerSupported(LayerType::Activation,
+ {inputInfo1, inputInfo2, outputInfo},
+ descriptor,
+ EmptyOptional(),
+ EmptyOptional(),
+ reasonIfNotSupported);
+
+ CHECK(supported);
+}
+
+TEST_CASE("IsLayerSupportedTosaReferenceActivationUnsupported")
+{
+ TensorInfo inputInfo1({1,1,3,4}, DataType::Float32);
+ TensorInfo inputInfo2({1,1,3,4}, DataType::Float32);
+ TensorInfo outputInfo({1,1,3,4}, DataType::Float32);
+
+ TosaRefLayerSupport supportChecker;
+ std::string reasonIfNotSupported;
+ ActivationDescriptor descriptor;
+ descriptor.m_Function = ActivationFunction::HardSwish;
+ auto supported = supportChecker.IsLayerSupported(LayerType::Activation,
+ {inputInfo1, inputInfo2, outputInfo},
+ descriptor,
+ EmptyOptional(),
+ EmptyOptional(),
+ reasonIfNotSupported);
+
+ CHECK(!supported);
+}
+
TEST_CASE("IsLayerSupportedTosaReferenceMaxPooling2dUnsupported")
{
TensorShape inShape = {1,1,3,4};