diff options
Diffstat (limited to 'delegate')
-rw-r--r-- | delegate/CMakeLists.txt | 2 | ||||
-rw-r--r-- | delegate/TensorFlowLiteDelegateSupport.md | 4 | ||||
-rw-r--r-- | delegate/src/Normalization.hpp | 137 | ||||
-rw-r--r-- | delegate/src/armnn_delegate.cpp | 20 | ||||
-rw-r--r-- | delegate/src/test/NormalizationTest.cpp | 166 | ||||
-rw-r--r-- | delegate/src/test/NormalizationTestHelper.hpp | 181 |
6 files changed, 487 insertions, 23 deletions
diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt index ba8ba6de00..2862faf9e6 100644 --- a/delegate/CMakeLists.txt +++ b/delegate/CMakeLists.txt @@ -131,6 +131,8 @@ if(BUILD_UNIT_TESTS) src/test/GatherTestHelper.hpp src/test/LogicalTest.cpp src/test/LogicalTestHelper.hpp + src/test/NormalizationTest.cpp + src/test/NormalizationTestHelper.hpp src/test/PadTest.cpp src/test/PadTestHelper.hpp src/test/Pooling2dTest.cpp diff --git a/delegate/TensorFlowLiteDelegateSupport.md b/delegate/TensorFlowLiteDelegateSupport.md index dd4cbace7b..ed1124a1ff 100644 --- a/delegate/TensorFlowLiteDelegateSupport.md +++ b/delegate/TensorFlowLiteDelegateSupport.md @@ -46,6 +46,8 @@ The Arm NN SDK TensorFlow Lite delegate currently supports the following operato * LESS_OR_EQUAL +* LOCAL_RESPONSE_NORMALIZATION + * LOGICAL_AND * LOGICAL_NOT @@ -56,6 +58,8 @@ The Arm NN SDK TensorFlow Lite delegate currently supports the following operato * LOG_SOFTMAX +* L2_NORMALIZATION + * L2_POOL_2D * MAXIMUM diff --git a/delegate/src/Normalization.hpp b/delegate/src/Normalization.hpp index 4c18b364cc..68ff3af32d 100644 --- a/delegate/src/Normalization.hpp +++ b/delegate/src/Normalization.hpp @@ -5,8 +5,6 @@ #pragma once -#include <armnn/utility/IgnoreUnused.hpp> - #include <tensorflow/lite/builtin_ops.h> #include <tensorflow/lite/c/builtin_op_data.h> #include <tensorflow/lite/c/common.h> @@ -15,19 +13,132 @@ namespace armnnDelegate { -TfLiteStatus VisitNormalizationOperator(DelegateData& delegateData, - TfLiteContext* tfLiteContext, - TfLiteNode* tfLiteNode, - int nodeIndex, - int32_t normalizationOperatorCode) +TfLiteStatus VisitL2NormalizationOperator(DelegateData& delegateData, + TfLiteContext* tfLiteContext, + TfLiteNode* tfLiteNode, + int nodeIndex, + int32_t operatorCode) +{ + TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + + const TfLiteTensor* tfLiteTensors = tfLiteContext->tensors; + const TfLiteTensor& tfLiteInputTensor = tfLiteTensors[tfLiteNode->inputs->data[0]]; + if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, nodeIndex)) + { + return kTfLiteError; + } + + const TfLiteTensor& tfLiteOutputTensor = tfLiteTensors[tfLiteNode->outputs->data[0]]; + if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex)) + { + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteInputTensor); + const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteOutputTensor); + + armnn::L2NormalizationDescriptor descriptor; + descriptor.m_DataLayout = armnn::DataLayout::NHWC; + + bool isSupported = false; + auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported) + { + FORWARD_LAYER_SUPPORT_FUNC(__func__, + tfLiteContext, + IsL2NormalizationSupported, + delegateData.m_Backends, + isSupported, + inputTensorInfo, + outInfo, + descriptor); + }; + + if (!delegateData.m_Network) + { + validateFunc(outputTensorInfo, isSupported); + return isSupported ? kTfLiteOk : kTfLiteError; + } + + // Add a L2Normalization layer + armnn::IConnectableLayer* layer = delegateData.m_Network->AddL2NormalizationLayer(descriptor); + ARMNN_ASSERT(layer != nullptr); + + armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + + // Connect + return Connect(layer, tfLiteNode, delegateData); +} + + +TfLiteStatus VisitLocalResponseNormalizationOperator(DelegateData& delegateData, + TfLiteContext* tfLiteContext, + TfLiteNode* tfLiteNode, + int nodeIndex, + int32_t normalizationOperatorCode) { - armnn::IgnoreUnused(delegateData, - tfLiteContext, - tfLiteNode, - nodeIndex, - normalizationOperatorCode); + TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + + const TfLiteTensor* tfLiteTensors = tfLiteContext->tensors; + const TfLiteTensor& tfLiteInputTensor = tfLiteTensors[tfLiteNode->inputs->data[0]]; + if (!IsValid(tfLiteContext, tfLiteInputTensor, normalizationOperatorCode, nodeIndex)) + { + return kTfLiteError; + } + + const TfLiteTensor& tfLiteOutputTensor = tfLiteTensors[tfLiteNode->outputs->data[0]]; + if (!IsValid(tfLiteContext, tfLiteOutputTensor, normalizationOperatorCode, nodeIndex)) + { + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteInputTensor); + const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteOutputTensor); + + armnn::NormalizationDescriptor descriptor; + descriptor.m_DataLayout = armnn::DataLayout::NHWC; + descriptor.m_NormChannelType = armnn::NormalizationAlgorithmChannel::Across; + descriptor.m_NormMethodType = armnn::NormalizationAlgorithmMethod::LocalBrightness; + + auto* params = reinterpret_cast<TfLiteLocalResponseNormParams*>(tfLiteNode->builtin_data); + descriptor.m_NormSize = params->radius; + descriptor.m_K = params->bias; + descriptor.m_Alpha = params->alpha; + descriptor.m_Beta = params->beta; + + // ArmNN expects normSize to be the full size of the normalization window + descriptor.m_NormSize = 1 + (2 * descriptor.m_NormSize); + + bool isSupported = false; + auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported) + { + FORWARD_LAYER_SUPPORT_FUNC(__func__, + tfLiteContext, + IsNormalizationSupported, + delegateData.m_Backends, + isSupported, + inputTensorInfo, + outInfo, + descriptor); + }; + + if (!delegateData.m_Network) + { + validateFunc(outputTensorInfo, isSupported); + return isSupported ? kTfLiteOk : kTfLiteError; + } + + // Add a Normalization layer + armnn::IConnectableLayer* layer = delegateData.m_Network->AddNormalizationLayer(descriptor); + ARMNN_ASSERT(layer != nullptr); + + armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); - return kTfLiteError; + // Connect + return Connect(layer, tfLiteNode, delegateData); } } // namespace armnnDelegate diff --git a/delegate/src/armnn_delegate.cpp b/delegate/src/armnn_delegate.cpp index 6dba890509..6f72d864b9 100644 --- a/delegate/src/armnn_delegate.cpp +++ b/delegate/src/armnn_delegate.cpp @@ -575,11 +575,11 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, nodeIndex, kTfLiteBuiltinHardSwish); case kTfLiteBuiltinL2Normalization: - return VisitNormalizationOperator(delegateData, - tfLiteContext, - tfLiteNode, - nodeIndex, - kTfLiteBuiltinL2Normalization); + return VisitL2NormalizationOperator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + kTfLiteBuiltinL2Normalization); case kTfLiteBuiltinL2Pool2d: return VisitPoolingOperator(delegateData, tfLiteContext, @@ -599,11 +599,11 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, nodeIndex, kTfLiteBuiltinLessEqual); case kTfLiteBuiltinLocalResponseNormalization: - return VisitNormalizationOperator(delegateData, - tfLiteContext, - tfLiteNode, - nodeIndex, - kTfLiteBuiltinLocalResponseNormalization); + return VisitLocalResponseNormalizationOperator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + kTfLiteBuiltinLocalResponseNormalization); case kTfLiteBuiltinLogicalAnd: return VisitLogicalBinaryOperator(delegateData, tfLiteContext, diff --git a/delegate/src/test/NormalizationTest.cpp b/delegate/src/test/NormalizationTest.cpp new file mode 100644 index 0000000000..058394edb7 --- /dev/null +++ b/delegate/src/test/NormalizationTest.cpp @@ -0,0 +1,166 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "NormalizationTestHelper.hpp" + +#include <armnn_delegate.hpp> + +#include <flatbuffers/flatbuffers.h> +#include <tensorflow/lite/schema/schema_generated.h> + +#include <doctest/doctest.h> + +namespace armnnDelegate +{ + +void L2NormalizationTest(std::vector<armnn::BackendId>& backends) +{ + // Set input data + std::vector<int32_t> inputShape { 1, 1, 1, 10 }; + std::vector<int32_t> outputShape { 1, 1, 1, 10 }; + + std::vector<float> inputValues + { + 1.0f, + 2.0f, + 3.0f, + 4.0f, + 5.0f, + 6.0f, + 7.0f, + 8.0f, + 9.0f, + 10.0f + }; + + const float approxInvL2Norm = 0.050964719f; + std::vector<float> expectedOutputValues + { + 1.0f * approxInvL2Norm, + 2.0f * approxInvL2Norm, + 3.0f * approxInvL2Norm, + 4.0f * approxInvL2Norm, + 5.0f * approxInvL2Norm, + 6.0f * approxInvL2Norm, + 7.0f * approxInvL2Norm, + 8.0f * approxInvL2Norm, + 9.0f * approxInvL2Norm, + 10.0f * approxInvL2Norm + }; + + NormalizationTest<float>(tflite::BuiltinOperator_L2_NORMALIZATION, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues); +} + +void LocalResponseNormalizationTest(std::vector<armnn::BackendId>& backends, + int32_t radius, + float bias, + float alpha, + float beta) +{ + // Set input data + std::vector<int32_t> inputShape { 2, 2, 2, 1 }; + std::vector<int32_t> outputShape { 2, 2, 2, 1 }; + + std::vector<float> inputValues + { + 1.0f, 2.0f, + 3.0f, 4.0f, + 5.0f, 6.0f, + 7.0f, 8.0f + }; + + std::vector<float> expectedOutputValues + { + 0.5f, 0.400000006f, 0.300000012f, 0.235294119f, + 0.192307696f, 0.16216217f, 0.140000001f, 0.123076923f + }; + + NormalizationTest<float>(tflite::BuiltinOperator_LOCAL_RESPONSE_NORMALIZATION, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + radius, + bias, + alpha, + beta); +} + + +TEST_SUITE("L2Normalization_CpuRefTests") +{ + +TEST_CASE ("L2NormalizationFp32Test_CpuRef_Test") +{ + std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef }; + L2NormalizationTest(backends); +} + +} // TEST_SUITE("L2Normalization_CpuRefTests") + +TEST_SUITE("L2Normalization_CpuAccTests") +{ + +TEST_CASE ("L2NormalizationFp32Test_CpuAcc_Test") +{ + std::vector<armnn::BackendId> backends = { armnn::Compute::CpuAcc }; + L2NormalizationTest(backends); +} + +} // TEST_SUITE("L2NormalizationFp32Test_CpuAcc_Test") + +TEST_SUITE("L2Normalization_GpuAccTests") +{ + +TEST_CASE ("L2NormalizationFp32Test_GpuAcc_Test") +{ + std::vector<armnn::BackendId> backends = { armnn::Compute::GpuAcc }; + L2NormalizationTest(backends); +} + +} // TEST_SUITE("L2Normalization_GpuAccTests") + +TEST_SUITE("LocalResponseNormalization_CpuRefTests") +{ + +TEST_CASE ("LocalResponseNormalizationTest_CpuRef_Test") +{ + std::vector<armnn::BackendId> backends = { armnn::Compute::CpuRef }; + LocalResponseNormalizationTest(backends, 3, 1.f, 1.f, 1.f); +} + +} // TEST_SUITE("LocalResponseNormalization_CpuRefTests") + +TEST_SUITE("LocalResponseNormalization_CpuAccTests") +{ + +TEST_CASE ("LocalResponseNormalizationTest_CpuAcc_Test") +{ + std::vector<armnn::BackendId> backends = { armnn::Compute::CpuAcc }; + LocalResponseNormalizationTest(backends, 3, 1.f, 1.f, 1.f); +} + +} // TEST_SUITE("LocalResponseNormalization_CpuAccTests") + +TEST_SUITE("LocalResponseNormalization_GpuAccTests") +{ + +TEST_CASE ("LocalResponseNormalizationTest_GpuAcc_Test") +{ + std::vector<armnn::BackendId> backends = { armnn::Compute::GpuAcc }; + LocalResponseNormalizationTest(backends, 3, 1.f, 1.f, 1.f); +} + +} // TEST_SUITE("LocalResponseNormalization_GpuAccTests") + +} // namespace armnnDelegate
\ No newline at end of file diff --git a/delegate/src/test/NormalizationTestHelper.hpp b/delegate/src/test/NormalizationTestHelper.hpp new file mode 100644 index 0000000000..26286b1c88 --- /dev/null +++ b/delegate/src/test/NormalizationTestHelper.hpp @@ -0,0 +1,181 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "TestUtils.hpp" + +#include <armnn_delegate.hpp> + +#include <flatbuffers/flatbuffers.h> +#include <tensorflow/lite/interpreter.h> +#include <tensorflow/lite/kernels/register.h> +#include <tensorflow/lite/model.h> +#include <tensorflow/lite/schema/schema_generated.h> +#include <tensorflow/lite/version.h> + +#include <doctest/doctest.h> + +namespace +{ + +std::vector<char> CreateNormalizationTfLiteModel(tflite::BuiltinOperator normalizationOperatorCode, + tflite::TensorType tensorType, + const std::vector<int32_t>& inputTensorShape, + const std::vector<int32_t>& outputTensorShape, + int32_t radius, + float bias, + float alpha, + float beta, + float quantScale = 1.0f, + int quantOffset = 0) +{ + using namespace tflite; + flatbuffers::FlatBufferBuilder flatBufferBuilder; + + auto quantizationParameters = + CreateQuantizationParameters(flatBufferBuilder, + 0, + 0, + flatBufferBuilder.CreateVector<float>({ quantScale }), + flatBufferBuilder.CreateVector<int64_t>({ quantOffset })); + + auto inputTensor = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector<int32_t>(inputTensorShape.data(), + inputTensorShape.size()), + tensorType, + 0, + flatBufferBuilder.CreateString("input"), + quantizationParameters); + + auto outputTensor = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector<int32_t>(outputTensorShape.data(), + outputTensorShape.size()), + tensorType, + 1, + flatBufferBuilder.CreateString("output"), + quantizationParameters); + + std::vector<flatbuffers::Offset<Tensor>> tensors = { inputTensor, outputTensor }; + + std::vector<flatbuffers::Offset<tflite::Buffer>> buffers; + buffers.push_back(CreateBuffer(flatBufferBuilder, flatBufferBuilder.CreateVector({}))); + buffers.push_back(CreateBuffer(flatBufferBuilder, flatBufferBuilder.CreateVector({}))); + + std::vector<int32_t> operatorInputs = {{ 0 }}; + std::vector<int> subgraphInputs = {{ 0 }}; + + tflite::BuiltinOptions operatorBuiltinOptionsType = BuiltinOptions_L2NormOptions; + flatbuffers::Offset<void> operatorBuiltinOptions = CreateL2NormOptions(flatBufferBuilder, + tflite::ActivationFunctionType_NONE).Union(); + + if (normalizationOperatorCode == tflite::BuiltinOperator_LOCAL_RESPONSE_NORMALIZATION) + { + operatorBuiltinOptionsType = BuiltinOptions_LocalResponseNormalizationOptions; + operatorBuiltinOptions = + CreateLocalResponseNormalizationOptions(flatBufferBuilder, radius, bias, alpha, beta).Union(); + } + + // create operator + const std::vector<int32_t> operatorOutputs{{ 1 }}; + flatbuffers::Offset <Operator> normalizationOperator = + CreateOperator(flatBufferBuilder, + 0, + flatBufferBuilder.CreateVector<int32_t>(operatorInputs.data(), operatorInputs.size()), + flatBufferBuilder.CreateVector<int32_t>(operatorOutputs.data(), operatorOutputs.size()), + operatorBuiltinOptionsType, + operatorBuiltinOptions); + + const std::vector<int> subgraphOutputs{{ 1 }}; + flatbuffers::Offset <SubGraph> subgraph = + CreateSubGraph(flatBufferBuilder, + flatBufferBuilder.CreateVector(tensors.data(), tensors.size()), + flatBufferBuilder.CreateVector<int32_t>(subgraphInputs.data(), subgraphInputs.size()), + flatBufferBuilder.CreateVector<int32_t>(subgraphOutputs.data(), subgraphOutputs.size()), + flatBufferBuilder.CreateVector(&normalizationOperator, 1)); + + flatbuffers::Offset <flatbuffers::String> modelDescription = + flatBufferBuilder.CreateString("ArmnnDelegate: Normalization Operator Model"); + flatbuffers::Offset <OperatorCode> operatorCode = CreateOperatorCode(flatBufferBuilder, + normalizationOperatorCode); + + flatbuffers::Offset <Model> flatbufferModel = + CreateModel(flatBufferBuilder, + TFLITE_SCHEMA_VERSION, + flatBufferBuilder.CreateVector(&operatorCode, 1), + flatBufferBuilder.CreateVector(&subgraph, 1), + modelDescription, + flatBufferBuilder.CreateVector(buffers.data(), buffers.size())); + + flatBufferBuilder.Finish(flatbufferModel); + + return std::vector<char>(flatBufferBuilder.GetBufferPointer(), + flatBufferBuilder.GetBufferPointer() + flatBufferBuilder.GetSize()); +} + +template <typename T> +void NormalizationTest(tflite::BuiltinOperator normalizationOperatorCode, + tflite::TensorType tensorType, + const std::vector<armnn::BackendId>& backends, + const std::vector<int32_t>& inputShape, + std::vector<int32_t>& outputShape, + std::vector<T>& inputValues, + std::vector<T>& expectedOutputValues, + int32_t radius = 0, + float bias = 0.f, + float alpha = 0.f, + float beta = 0.f, + float quantScale = 1.0f, + int quantOffset = 0) +{ + using namespace tflite; + std::vector<char> modelBuffer = CreateNormalizationTfLiteModel(normalizationOperatorCode, + tensorType, + inputShape, + outputShape, + radius, + bias, + alpha, + beta, + quantScale, + quantOffset); + + const Model* tfLiteModel = GetModel(modelBuffer.data()); + CHECK(tfLiteModel != nullptr); + + std::unique_ptr<Interpreter> armnnDelegateInterpreter; + CHECK(InterpreterBuilder(tfLiteModel, ::tflite::ops::builtin::BuiltinOpResolver()) + (&armnnDelegateInterpreter) == kTfLiteOk); + CHECK(armnnDelegateInterpreter != nullptr); + CHECK(armnnDelegateInterpreter->AllocateTensors() == kTfLiteOk); + + std::unique_ptr<Interpreter> tfLiteInterpreter; + CHECK(InterpreterBuilder(tfLiteModel, ::tflite::ops::builtin::BuiltinOpResolver()) + (&tfLiteInterpreter) == kTfLiteOk); + CHECK(tfLiteInterpreter != nullptr); + CHECK(tfLiteInterpreter->AllocateTensors() == kTfLiteOk); + + // Create the ArmNN Delegate + armnnDelegate::DelegateOptions delegateOptions(backends); + std::unique_ptr<TfLiteDelegate, decltype(&armnnDelegate::TfLiteArmnnDelegateDelete)> + theArmnnDelegate(armnnDelegate::TfLiteArmnnDelegateCreate(delegateOptions), + armnnDelegate::TfLiteArmnnDelegateDelete); + CHECK(theArmnnDelegate != nullptr); + // Modify armnnDelegateInterpreter to use armnnDelegate + CHECK(armnnDelegateInterpreter->ModifyGraphWithDelegate(theArmnnDelegate.get()) == kTfLiteOk); + + // Set input data + armnnDelegate::FillInput<T>(tfLiteInterpreter, 0, inputValues); + armnnDelegate::FillInput<T>(armnnDelegateInterpreter, 0, inputValues); + + // Run EnqueueWorkload + CHECK(tfLiteInterpreter->Invoke() == kTfLiteOk); + CHECK(armnnDelegateInterpreter->Invoke() == kTfLiteOk); + + // Compare output data + armnnDelegate::CompareOutputData(tfLiteInterpreter, armnnDelegateInterpreter, outputShape, expectedOutputValues); +} + +} // anonymous namespace
\ No newline at end of file |