From 6e36a64e26520e3f169bb2a92972a24e1be915a7 Mon Sep 17 00:00:00 2001 From: Sadik Armagan Date: Tue, 10 Nov 2020 21:18:41 +0000 Subject: IVGCVSW-5389 'TfLiteDelegate: Implement the FullyConnected operator' * Added FullyConnected operator support to delegate Signed-off-by: Sadik Armagan Change-Id: Iae9c0980a4bfd6aa4d90f107f329dfa782baeefe --- delegate/CMakeLists.txt | 4 +- delegate/src/DelegateUtils.hpp | 132 ++++++++++++-- delegate/src/FullyConnected.hpp | 212 +++++++++++++++++++++- delegate/src/armnn_delegate.cpp | 20 +-- delegate/src/test/FullyConnectedTest.cpp | 128 ++++++++++++++ delegate/src/test/FullyConnectedTestHelper.hpp | 232 +++++++++++++++++++++++++ 6 files changed, 696 insertions(+), 32 deletions(-) create mode 100644 delegate/src/test/FullyConnectedTest.cpp create mode 100644 delegate/src/test/FullyConnectedTestHelper.hpp diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt index e05a0baff4..05ec851bf2 100644 --- a/delegate/CMakeLists.txt +++ b/delegate/CMakeLists.txt @@ -95,16 +95,18 @@ list(APPEND armnnDelegate_unittest_sources src/test/ElementwiseBinaryTestHelper.hpp src/test/ElementwiseUnaryTest.cpp src/test/ElementwiseUnaryTestHelper.hpp + src/test/FullyConnectedTest.cpp + src/test/FullyConnectedTestHelper.hpp src/test/Pooling2dTest.cpp src/test/Pooling2dTestHelper.hpp src/test/QuantizationTest.cpp src/test/QuantizationTestHelper.hpp) add_executable(DelegateUnitTests ${armnnDelegate_unittest_sources}) -target_include_directories(DelegateUnitTests PRIVATE src) target_include_directories(DelegateUnitTests PRIVATE third-party) target_link_libraries(DelegateUnitTests armnnDelegate) +target_link_libraries(DelegateUnitTests Armnn::armnnUtils) target_include_directories(DelegateUnitTests PRIVATE diff --git a/delegate/src/DelegateUtils.hpp b/delegate/src/DelegateUtils.hpp index 729a8b4e98..fb3f998283 100644 --- a/delegate/src/DelegateUtils.hpp +++ b/delegate/src/DelegateUtils.hpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include @@ -94,6 +96,11 @@ TfLiteStatus ValidateNumOutputs(TfLiteContext* tfLiteContext, return kTfLiteOk; } +bool IsValid(const TfLiteTensor* tfLiteTensor) +{ + return tfLiteTensor == nullptr ? false : true; +} + bool IsDynamicTensor(const TfLiteTensor& tfLiteTensor) { auto tensorAllocationType = tfLiteTensor.allocation_type; @@ -118,13 +125,15 @@ TfLiteStatus Connect(armnn::IConnectableLayer* layer, TfLiteNode* tfLiteNode, armnnDelegate::DelegateData& data) { - ARMNN_ASSERT(tfLiteNode->inputs->size == layer->GetNumInputSlots()); ARMNN_ASSERT(tfLiteNode->outputs->size == layer->GetNumOutputSlots()); // Connect the input slots for (unsigned int inputIndex = 0; inputIndex < layer->GetNumInputSlots(); ++inputIndex) { - data.m_OutputSlotForNode[tfLiteNode->inputs->data[inputIndex]]->Connect(layer->GetInputSlot(inputIndex)); + if (data.m_OutputSlotForNode[tfLiteNode->inputs->data[inputIndex]] != nullptr) + { + data.m_OutputSlotForNode[tfLiteNode->inputs->data[inputIndex]]->Connect(layer->GetInputSlot(inputIndex)); + } } // Prepare output slots @@ -133,6 +142,7 @@ TfLiteStatus Connect(armnn::IConnectableLayer* layer, armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(outputIndex); data.m_OutputSlotForNode[tfLiteNode->outputs->data[outputIndex]] = &outputSlot; } + return kTfLiteOk; } @@ -299,43 +309,39 @@ TfLiteStatus FusedActivation(TfLiteContext* tfLiteContext, return kTfLiteOk; } -armnn::TensorInfo GetTensorInfoForTfLiteTensor(const TfLiteTensor& tfLiteTensor) +armnn::DataType GetDataType(const TfLiteTensor& tfLiteTensor) { - armnn::DataType type; switch (tfLiteTensor.type) { case kTfLiteBool: - type = armnn::DataType::Boolean; - break; + return armnn::DataType::Boolean; case kTfLiteFloat32: - type = armnn::DataType::Float32; - break; + return armnn::DataType::Float32; case kTfLiteFloat16: - type = armnn::DataType::Float16; - break; + return armnn::DataType::Float16; case kTfLiteUInt8: - type = armnn::DataType::QAsymmU8; - break; + return armnn::DataType::QAsymmU8; case kTfLiteInt8: if (tfLiteTensor.params.zero_point == 0) { - type = armnn::DataType::QSymmS8; + return armnn::DataType::QSymmS8; } else { - type = armnn::DataType::QAsymmS8; + return armnn::DataType::QAsymmS8; } - break; case kTfLiteInt16: - type = armnn::DataType::QSymmS16; - break; + return armnn::DataType::QSymmS16; case kTfLiteInt32: - type = armnn::DataType::Signed32; - break; + return armnn::DataType::Signed32; default: throw armnn::Exception("TfLiteArmnnDelegate: Unsupported data type: " + tfLiteTensor.type); } +} +armnn::TensorInfo GetTensorInfoForTfLiteTensor(const TfLiteTensor& tfLiteTensor) +{ + armnn::DataType type = GetDataType(tfLiteTensor); armnn::TensorInfo ret; auto tensorDimensionSize = tfLiteTensor.dims->size; if (tensorDimensionSize == 0) @@ -391,4 +397,92 @@ armnn::TensorInfo GetTensorInfoForTfLiteTensor(const TfLiteTensor& tfLiteTensor) return ret; } +struct DataHolder +{ +public: + DataHolder() + : m_Fp32Data(nullptr), m_Uint8Data(nullptr), + m_Int8Data(nullptr), m_Int16Data(nullptr), m_Int32Data(nullptr) {} + + DataHolder(std::unique_ptr&& data) + : m_Fp32Data(std::move(data)), m_Uint8Data(nullptr), + m_Int8Data(nullptr), m_Int16Data(nullptr), m_Int32Data(nullptr) {} + + DataHolder(std::unique_ptr&& data) + : m_Fp32Data(nullptr), m_Uint8Data(std::move(data)), + m_Int8Data(nullptr), m_Int16Data(nullptr), m_Int32Data(nullptr) {} + + DataHolder(std::unique_ptr&& data) + : m_Fp32Data(nullptr), m_Uint8Data(nullptr), + m_Int8Data(std::move(data)), m_Int16Data(nullptr), m_Int32Data(nullptr) {} + + DataHolder(std::unique_ptr&& data) + : m_Fp32Data(nullptr), m_Uint8Data(nullptr), + m_Int8Data(nullptr), m_Int16Data(std::move(data)), m_Int32Data(nullptr) {} + + DataHolder(std::unique_ptr&& data) + : m_Fp32Data(nullptr), m_Uint8Data(nullptr), + m_Int8Data(nullptr), m_Int16Data(nullptr), m_Int32Data(std::move(data)) {} + +private: + std::unique_ptr m_Fp32Data; + std::unique_ptr m_Uint8Data; + std::unique_ptr m_Int8Data; + std::unique_ptr m_Int16Data; + std::unique_ptr m_Int32Data; +}; + +template +std::pair CreateConstTensorImpl( + const TfLiteTensor* tensor, + armnn::TensorInfo& tensorInfo, + armnn::Optional permutationVector) +{ + std::unique_ptr data(new T[tensorInfo.GetNumElements()]); + if (permutationVector.has_value() && permutationVector.value().GetSize() > 0) + { + tensorInfo = armnnUtils::Permuted(tensorInfo, permutationVector.value()); + armnnUtils::Permute(tensorInfo.GetShape(), + permutationVector.value(), + reinterpret_cast(tensor->data.raw), data.get(), sizeof(T)); + } + else + { + ::memcpy(data.get(), tensor->data.raw, tensorInfo.GetNumBytes()); + } + + auto constData = std::make_pair(armnn::ConstTensor(tensorInfo, data.get()), std::move(data)); + + DataHolder storedData(std::move(constData.second)); + return std::make_pair(constData.first, std::move(storedData)); +} + +std::pair CreateConstTensor( + const TfLiteTensor* tfLiteTensor, + armnn::TensorInfo& tensorInfo, + armnn::Optional permutationVector) +{ + switch (tensorInfo.GetDataType()) + { + case armnn::DataType::Float32: + return CreateConstTensorImpl(tfLiteTensor, tensorInfo, permutationVector); + case armnn::DataType::QAsymmU8: + return CreateConstTensorImpl(tfLiteTensor, tensorInfo, permutationVector); + case armnn::DataType::QSymmS8: + return CreateConstTensorImpl(tfLiteTensor, tensorInfo, permutationVector); + case armnn::DataType::QAsymmS8: + return CreateConstTensorImpl(tfLiteTensor, tensorInfo, permutationVector); + case armnn::DataType::QSymmS16: + return CreateConstTensorImpl(tfLiteTensor, tensorInfo, permutationVector); + case armnn::DataType::Signed32: + return CreateConstTensorImpl(tfLiteTensor, tensorInfo, permutationVector); + default: + { + throw armnn::Exception( + "TfLiteArmnnDelegate: Unsupported data type when creating const tensor: " + + std::string(armnn::GetDataTypeName(tensorInfo.GetDataType()))); + } + } +} + } // namespace anonymous diff --git a/delegate/src/FullyConnected.hpp b/delegate/src/FullyConnected.hpp index ad981cd63b..f35f4c92b0 100644 --- a/delegate/src/FullyConnected.hpp +++ b/delegate/src/FullyConnected.hpp @@ -5,6 +5,8 @@ #pragma once +#include "DelegateUtils.hpp" + #include #include #include @@ -19,7 +21,213 @@ TfLiteStatus VisitFullyConnectedOperator(DelegateData& delegateData, int nodeIndex, int32_t operatorCode) { - return kTfLiteError; + auto numInputs = tfLiteNode->inputs->size; + if (numInputs < 2) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, "TfLiteArmnnDelegate: Minimum number of inputs (%d != %d) in node #%d", + 2, numInputs, nodeIndex); + return kTfLiteError; + } + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + bool biasEnabled = (numInputs == 3); + + const TfLiteTensor* tfLiteTensors = tfLiteContext->tensors; + const TfLiteTensor& tfLiteInputTensor = tfLiteTensors[tfLiteNode->inputs->data[0]]; + if(!IsValid(&tfLiteTensors[tfLiteNode->inputs->data[0]])) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Invalid input tensor in operator #%d node #%d: ", + operatorCode, nodeIndex); + return kTfLiteError; + } + if (IsDynamicTensor(tfLiteInputTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dynamic input tensors are not supported in node #%d: ", + nodeIndex); + return kTfLiteError; + } + const TfLiteTensor& tfLiteOutputTensor = tfLiteTensors[tfLiteNode->outputs->data[0]]; + if(!IsValid(&tfLiteOutputTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Invalid output tensor in operator #%d node #%d: ", + operatorCode, nodeIndex); + return kTfLiteError; + } + if (IsDynamicTensor(tfLiteOutputTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dynamic output tensors are not supported in node #%d: ", + nodeIndex); + return kTfLiteError; + } + + const TfLiteTensor& tfLiteWeightsTensor = tfLiteTensors[tfLiteNode->inputs->data[1]]; + if(!IsValid(&tfLiteWeightsTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Invalid weights tensor in operator #%d node #%d: ", + operatorCode, nodeIndex); + return kTfLiteError; + } + if (IsDynamicTensor(tfLiteWeightsTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dynamic weight tensors are not supported in node #%d: ", + nodeIndex); + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteInputTensor); + const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteOutputTensor); + + armnn::TensorInfo weightsTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteWeightsTensor); + // Fully Connected Layer accepts two dimensional weights input + int32_t weightsDimension = static_cast(weightsTensorInfo.GetNumDimensions()); + if (weightsDimension != 2) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dimension #$d for Fully Connected weights is not supported by Armnn" + " in operator #%d node #%d: ", weightsDimension, operatorCode, nodeIndex); + return kTfLiteError; + } + + armnn::TensorInfo biasTensorInfo; + if (biasEnabled) + { + const TfLiteTensor& tfLiteBiasTensor = tfLiteTensors[tfLiteNode->inputs->data[2]]; + if(!IsValid(&tfLiteBiasTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Invalid bias tensor in operator #%d node #%d: ", + operatorCode, nodeIndex); + return kTfLiteError; + } + if (IsDynamicTensor(tfLiteBiasTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dynamic bias tensors are not supported in node #%d: ", + nodeIndex); + return kTfLiteError; + } + biasTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteBiasTensor); + } + else + { + biasTensorInfo = armnn::TensorInfo(armnn::TensorShape({1}), GetDataType(tfLiteInputTensor)); + } + + armnn::FullyConnectedDescriptor descriptor; + descriptor.m_TransposeWeightMatrix = true; + descriptor.m_BiasEnabled = biasEnabled; + + bool isSupported = false; + auto validateFunc = [&](const armnn::TensorInfo& outputTensorInfo, bool& isSupported) + { + FORWARD_LAYER_SUPPORT_FUNC(__func__, + tfLiteContext, + IsFullyConnectedSupported, + delegateData.m_Backends, + isSupported, + inputTensorInfo, + outputTensorInfo, + weightsTensorInfo, + biasTensorInfo, + descriptor); + }; + + if (!delegateData.m_Network) + { + validateFunc(outputTensorInfo, isSupported); + return isSupported ? kTfLiteOk : kTfLiteError; + } + + auto weightsTensor = CreateConstTensor(&tfLiteWeightsTensor, + weightsTensorInfo, + armnn::Optional()); + + armnn::IConnectableLayer* layer = nullptr; + if (biasEnabled) + { + const TfLiteTensor& tfLiteBiasTensor = tfLiteTensors[tfLiteNode->inputs->data[2]]; + auto biasTensor = CreateConstTensor(&tfLiteBiasTensor, + biasTensorInfo, + armnn::Optional()); + layer = delegateData.m_Network->AddFullyConnectedLayer(descriptor, + weightsTensor.first, + armnn::Optional(biasTensor.first)); + } + else + { + layer = delegateData.m_Network->AddFullyConnectedLayer(descriptor, + weightsTensor.first, + armnn::EmptyOptional()); + } + ARMNN_ASSERT(layer != nullptr); + + armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + + armnn::IConnectableLayer* reshapeLayer = nullptr; + if (inputTensorInfo.GetNumDimensions() > 2) + { + // Add reshape to flatten to 2D [batch_size, input_size] + std::vector reshapedDimensions(2); + reshapedDimensions[1] = weightsTensorInfo.GetShape()[1]; + reshapedDimensions[0] = inputTensorInfo.GetNumElements() / reshapedDimensions[1]; + + if (inputTensorInfo.GetNumElements() % reshapedDimensions[1] != 0) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Failed to deduce input tensor shape from filter size #%d #%d node #%d: ", + reshapedDimensions[1], operatorCode, nodeIndex); + return kTfLiteError; + } + + armnn::TensorInfo reshapedTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteInputTensor); + reshapedTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() }); + + armnn::ReshapeDescriptor reshapeDescriptor; + reshapeDescriptor.m_TargetShape = reshapedTensorInfo.GetShape(); + reshapeLayer = delegateData.m_Network->AddReshapeLayer(reshapeDescriptor); + ARMNN_ASSERT(reshapeLayer != nullptr); + + reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedTensorInfo); + + // Connect + delegateData.m_OutputSlotForNode[tfLiteNode->inputs->data[0]]->Connect(reshapeLayer->GetInputSlot(0)); + reshapeLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0)); + armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0); + delegateData.m_OutputSlotForNode[tfLiteNode->outputs->data[0]] = &outputSlot; + } + + if (reshapeLayer == nullptr) + { + Connect(layer, tfLiteNode, delegateData); + } + + auto* tfLiteNodeParameters = reinterpret_cast(tfLiteNode->builtin_data); + if (!tfLiteNodeParameters) + { + // No Activation + return kTfLiteOk; + } + + // Check Activation + TfLiteFusedActivation activationType = tfLiteNodeParameters->activation; + return FusedActivation(tfLiteContext, tfLiteNode, activationType, layer, 0, delegateData); } -} // namespace armnnDelegate +} // namespace armnnDelegate \ No newline at end of file diff --git a/delegate/src/armnn_delegate.cpp b/delegate/src/armnn_delegate.cpp index 82cf5732df..69bd4f7350 100644 --- a/delegate/src/armnn_delegate.cpp +++ b/delegate/src/armnn_delegate.cpp @@ -85,7 +85,6 @@ TfLiteStatus DoPrepare(TfLiteContext* tfLiteContext, TfLiteDelegate* tfLiteDeleg { return kTfLiteError; } - return static_cast(tfLiteNode->user_data)->Prepare(tfLiteContext); }, // ArmnnSubgraph Invoke @@ -209,6 +208,11 @@ TfLiteStatus ArmnnSubgraph::AddInputLayer(DelegateData& delegateData, { const int32_t tensorId = inputs->data[i]; const TfLiteTensor tensor = tfLiteContext->tensors[tensorId]; + // Do not create bindings for constant inputs + if (tensor.allocation_type == kTfLiteMmapRo) + { + continue; + } auto bindingId = static_cast((tensorId)); armnn::IConnectableLayer* layer = delegateData.m_Network->AddInputLayer(bindingId); @@ -220,12 +224,9 @@ TfLiteStatus ArmnnSubgraph::AddInputLayer(DelegateData& delegateData, // Store for creating connections delegateData.m_OutputSlotForNode[tensorId] = &outputSlot; - // Do not create bindings for constant inputs - if (tensor.allocation_type != kTfLiteMmapRo) - { - inputBindings.push_back(std::make_pair(bindingId, tensorInfo)); - } + inputBindings.push_back(std::make_pair(bindingId, tensorInfo)); } + return kTfLiteOk; } @@ -244,7 +245,6 @@ TfLiteStatus ArmnnSubgraph::AddOutputLayer(DelegateData& delegateData, armnn::IConnectableLayer* layer = delegateData.m_Network->AddOutputLayer(bindingId); auto tensorInfo = GetTensorInfoForTfLiteTensor(tensor); - ARMNN_ASSERT(delegateData.m_OutputSlotForNode[tensorId] != nullptr); delegateData.m_OutputSlotForNode[tensorId]->Connect(layer->GetInputSlot(0)); outputBindings.push_back(std::make_pair(bindingId, tensorInfo)); @@ -272,7 +272,8 @@ ArmnnSubgraph* ArmnnSubgraph::Create(TfLiteContext* tfLiteContext, armnn::NetworkId networkId; delegateData.m_Network = armnn::INetwork::Create(networkOptions); - delegateData.m_OutputSlotForNode = std::vector(parameters->nodes_to_replace->size, nullptr); + delegateData.m_OutputSlotForNode = std::vector(tfLiteContext->tensors_size, nullptr); + std::vector inputBindings; std::vector outputBindings; @@ -314,8 +315,7 @@ ArmnnSubgraph* ArmnnSubgraph::Create(TfLiteContext* tfLiteContext, armnn::IOptimizedNetworkPtr optNet(nullptr, nullptr); try { - - optNet = armnn::Optimize(*(delegateData.m_Network), + optNet = armnn::Optimize(*(delegateData.m_Network.get()), delegate->m_Options.GetBackends(), delegate->m_Runtime->GetDeviceSpec()); } diff --git a/delegate/src/test/FullyConnectedTest.cpp b/delegate/src/test/FullyConnectedTest.cpp new file mode 100644 index 0000000000..1d33381d6e --- /dev/null +++ b/delegate/src/test/FullyConnectedTest.cpp @@ -0,0 +1,128 @@ +// +// Copyright © 2020 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "FullyConnectedTestHelper.hpp" + +namespace +{ + +TEST_SUITE("FullyConnectedTest") +{ + +void FullyConnectedFp32Test(std::vector& backends) +{ + std::vector inputTensorShape { 1, 4, 1, 1 }; + std::vector weightsTensorShape { 1, 4 }; + std::vector biasTensorShape { 1 }; + std::vector outputTensorShape { 1, 1 }; + + std::vector inputValues = { 10, 20, 30, 40 }; + std::vector weightsData = { 2, 3, 4, 5 }; + + std::vector expectedOutputValues = { (400 + 10) }; + + // bias is set std::vector biasData = { 10 } in the model + FullyConnectedTest(backends, + ::tflite::TensorType_FLOAT32, + tflite::ActivationFunctionType_NONE, + inputTensorShape, + weightsTensorShape, + biasTensorShape, + outputTensorShape, + inputValues, + expectedOutputValues, + weightsData); +} + +void FullyConnectedActicationTest(std::vector& backends) +{ + std::vector inputTensorShape { 1, 4, 1, 1 }; + std::vector weightsTensorShape { 1, 4 }; + std::vector biasTensorShape { 1 }; + std::vector outputTensorShape { 1, 1 }; + + std::vector inputValues = { -10, 20, 30, 40 }; + std::vector weightsData = { 2, 3, 4, -5 }; + + std::vector expectedOutputValues = { 0 }; + + // bias is set std::vector biasData = { 10 } in the model + FullyConnectedTest(backends, + ::tflite::TensorType_FLOAT32, + tflite::ActivationFunctionType_RELU, + inputTensorShape, + weightsTensorShape, + biasTensorShape, + outputTensorShape, + inputValues, + expectedOutputValues, + weightsData); +} + +void FullyConnectedUint8Test(std::vector& backends) +{ + std::vector inputTensorShape { 1, 4, 2, 1 }; + std::vector weightsTensorShape { 1, 4 }; + std::vector biasTensorShape { 1 }; + std::vector outputTensorShape { 2, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 10, 20, 30, 40 }; + std::vector weightsData = { 2, 3, 4, 5 }; + + std::vector expectedOutputValues = { (40 + 10) / 2, (400 + 10) / 2 }; + + // bias is set std::vector biasData = { 10 } in the model + // input and weights quantization scale 1.0f and offset 0 in the model + // output quantization scale 2.0f and offset 0 in the model + FullyConnectedTest(backends, + ::tflite::TensorType_UINT8, + tflite::ActivationFunctionType_NONE, + inputTensorShape, + weightsTensorShape, + biasTensorShape, + outputTensorShape, + inputValues, + expectedOutputValues, + weightsData); +} + +TEST_CASE ("FULLY_CONNECTED_FP32_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc, + armnn::Compute::CpuRef }; + FullyConnectedFp32Test(backends); +} + +TEST_CASE ("FULLY_CONNECTED_FP32_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc, + armnn::Compute::CpuRef }; + FullyConnectedFp32Test(backends); +} + +TEST_CASE ("FULLY_CONNECTED_UINT8_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc, + armnn::Compute::CpuRef }; + FullyConnectedUint8Test(backends); +} + +TEST_CASE ("FULLY_CONNECTED_UINT8_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc, + armnn::Compute::CpuRef }; + FullyConnectedUint8Test(backends); +} + +TEST_CASE ("FULLY_CONNECTED_Activation_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc, + armnn::Compute::CpuRef }; + FullyConnectedActicationTest(backends); +} + +} // End of TEST_SUITE("FullyConnectedTest") + +} // anonymous namespace \ No newline at end of file diff --git a/delegate/src/test/FullyConnectedTestHelper.hpp b/delegate/src/test/FullyConnectedTestHelper.hpp new file mode 100644 index 0000000000..4eed9580f1 --- /dev/null +++ b/delegate/src/test/FullyConnectedTestHelper.hpp @@ -0,0 +1,232 @@ +// +// Copyright © 2020 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + +template +std::vector CreateFullyConnectedTfLiteModel(tflite::TensorType tensorType, + tflite::ActivationFunctionType activationType, + const std::vector & inputTensorShape, + const std::vector & weightsTensorShape, + const std::vector & biasTensorShape, + const std::vector & outputTensorShape, + const std::vector & weightsData, + float quantScale = 1.0f, + int quantOffset = 0, + float outputQuantScale = 2.0f, + int outputQuantOffset = 0) +{ + using namespace tflite; + flatbuffers::FlatBufferBuilder flatBufferBuilder; + std::array, 3> buffers; + buffers[0] = CreateBuffer(flatBufferBuilder, flatBufferBuilder.CreateVector({})); + buffers[1] = CreateBuffer(flatBufferBuilder, + flatBufferBuilder.CreateVector(reinterpret_cast(weightsData.data()), + sizeof(T) * weightsData.size())); + + auto biasTensorType = ::tflite::TensorType_FLOAT32; + if (tensorType == ::tflite::TensorType_UINT8) + { + biasTensorType = ::tflite::TensorType_INT32; + std::vector biasData = { 10 }; + buffers[2] = CreateBuffer(flatBufferBuilder, + flatBufferBuilder.CreateVector(reinterpret_cast(biasData.data()), + sizeof(int32_t) * biasData.size())); + + } + else + { + std::vector biasData = { 10 }; + buffers[2] = CreateBuffer(flatBufferBuilder, + flatBufferBuilder.CreateVector(reinterpret_cast(biasData.data()), + sizeof(float) * biasData.size())); + } + + auto quantizationParameters = + CreateQuantizationParameters(flatBufferBuilder, + 0, + 0, + flatBufferBuilder.CreateVector({ quantScale }), + flatBufferBuilder.CreateVector({ quantOffset })); + + auto outputQuantizationParameters = + CreateQuantizationParameters(flatBufferBuilder, + 0, + 0, + flatBufferBuilder.CreateVector({ outputQuantScale }), + flatBufferBuilder.CreateVector({ outputQuantOffset })); + + std::array, 4> tensors; + tensors[0] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(inputTensorShape.data(), + inputTensorShape.size()), + tensorType, + 0, + flatBufferBuilder.CreateString("input_0"), + quantizationParameters); + tensors[1] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(weightsTensorShape.data(), + weightsTensorShape.size()), + tensorType, + 1, + flatBufferBuilder.CreateString("weights"), + quantizationParameters); + tensors[2] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(biasTensorShape.data(), + biasTensorShape.size()), + biasTensorType, + 2, + flatBufferBuilder.CreateString("bias"), + quantizationParameters); + + tensors[3] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(outputTensorShape.data(), + outputTensorShape.size()), + tensorType, + 0, + flatBufferBuilder.CreateString("output"), + outputQuantizationParameters); + + + // create operator + tflite::BuiltinOptions operatorBuiltinOptionsType = BuiltinOptions_FullyConnectedOptions; + flatbuffers::Offset operatorBuiltinOptions = + CreateFullyConnectedOptions(flatBufferBuilder, + activationType, + FullyConnectedOptionsWeightsFormat_DEFAULT, false).Union(); + + const std::vector operatorInputs{ {0, 1, 2} }; + const std::vector operatorOutputs{ {3} }; + flatbuffers::Offset fullyConnectedOperator = + CreateOperator(flatBufferBuilder, + 0, + flatBufferBuilder.CreateVector(operatorInputs.data(), operatorInputs.size()), + flatBufferBuilder.CreateVector(operatorOutputs.data(), operatorOutputs.size()), + operatorBuiltinOptionsType, operatorBuiltinOptions); + + const std::vector subgraphInputs{ {0, 1, 2} }; + const std::vector subgraphOutputs{ {3} }; + flatbuffers::Offset subgraph = + CreateSubGraph(flatBufferBuilder, + flatBufferBuilder.CreateVector(tensors.data(), tensors.size()), + flatBufferBuilder.CreateVector(subgraphInputs.data(), subgraphInputs.size()), + flatBufferBuilder.CreateVector(subgraphOutputs.data(), subgraphOutputs.size()), + flatBufferBuilder.CreateVector(&fullyConnectedOperator, 1)); + + flatbuffers::Offset modelDescription = + flatBufferBuilder.CreateString("ArmnnDelegate: FullyConnected Operator Model"); + flatbuffers::Offset operatorCode = CreateOperatorCode(flatBufferBuilder, + tflite::BuiltinOperator_FULLY_CONNECTED); + + flatbuffers::Offset 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(flatBufferBuilder.GetBufferPointer(), + flatBufferBuilder.GetBufferPointer() + flatBufferBuilder.GetSize()); +} + +template +void FullyConnectedTest(std::vector& backends, + tflite::TensorType tensorType, + tflite::ActivationFunctionType activationType, + const std::vector & inputTensorShape, + const std::vector & weightsTensorShape, + const std::vector & biasTensorShape, + const std::vector & outputTensorShape, + const std::vector & inputValues, + const std::vector & expectedOutputValues, + const std::vector & weightsData, + float quantScale = 1.0f, + int quantOffset = 0) +{ + using namespace tflite; + + std::vector modelBuffer = CreateFullyConnectedTfLiteModel(tensorType, + activationType, + inputTensorShape, + weightsTensorShape, + biasTensorShape, + outputTensorShape, + weightsData, + quantScale, + quantOffset); + + const Model* tfLiteModel = GetModel(modelBuffer.data()); + // Create TfLite Interpreters + std::unique_ptr armnnDelegateInterpreter; + CHECK(InterpreterBuilder(tfLiteModel, ::tflite::ops::builtin::BuiltinOpResolver()) + (&armnnDelegateInterpreter) == kTfLiteOk); + CHECK(armnnDelegateInterpreter != nullptr); + CHECK(armnnDelegateInterpreter->AllocateTensors() == kTfLiteOk); + + std::unique_ptr 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 + theArmnnDelegate(armnnDelegate::TfLiteArmnnDelegateCreate(delegateOptions), + armnnDelegate::TfLiteArmnnDelegateDelete); + CHECK(theArmnnDelegate != nullptr); + // Modify armnnDelegateInterpreter to use armnnDelegate + CHECK(armnnDelegateInterpreter->ModifyGraphWithDelegate(theArmnnDelegate.get()) == kTfLiteOk); + + // Set input data + auto tfLiteDelegateInputId = tfLiteInterpreter->inputs()[0]; + auto tfLiteDelageInputData = tfLiteInterpreter->typed_tensor(tfLiteDelegateInputId); + for (unsigned int i = 0; i < inputValues.size(); ++i) + { + tfLiteDelageInputData[i] = inputValues[i]; + } + + auto armnnDelegateInputId = armnnDelegateInterpreter->inputs()[0]; + auto armnnDelegateInputData = armnnDelegateInterpreter->typed_tensor(armnnDelegateInputId); + for (unsigned int i = 0; i < inputValues.size(); ++i) + { + armnnDelegateInputData[i] = inputValues[i]; + } + + // Run EnqueWorkload + CHECK(tfLiteInterpreter->Invoke() == kTfLiteOk); + CHECK(armnnDelegateInterpreter->Invoke() == kTfLiteOk); + + // Compare output data + auto tfLiteDelegateOutputId = tfLiteInterpreter->outputs()[0]; + auto tfLiteDelageOutputData = tfLiteInterpreter->typed_tensor(tfLiteDelegateOutputId); + auto armnnDelegateOutputId = armnnDelegateInterpreter->outputs()[0]; + auto armnnDelegateOutputData = armnnDelegateInterpreter->typed_tensor(armnnDelegateOutputId); + for (size_t i = 0; i < expectedOutputValues.size(); i++) + { + CHECK(expectedOutputValues[i] == tfLiteDelageOutputData[i]); + CHECK(expectedOutputValues[i] == armnnDelegateOutputData[i]); + CHECK(tfLiteDelageOutputData[i] == armnnDelegateOutputData[i]); + } +} + +} // anonymous namespace \ No newline at end of file -- cgit v1.2.1