From 57480cdc5b2b3a88b6fca40089d0fb7521d832b2 Mon Sep 17 00:00:00 2001 From: Ryan OShea Date: Fri, 10 Jun 2022 14:49:11 +0100 Subject: IVGCVSW-6946 Add Pool3D to tflite delegate * Add new test and test helper for Pool3d * Add new custom operator to switch in armnn_delegate.cpp * Add new pool3d function to pooling.hpp * Update doxygen Signed-off-by: Ryan OShea Change-Id: I77a541bf423b337c749e70c564cdd727efe2fd05 --- delegate/CMakeLists.txt | 4 +- delegate/src/Pooling.hpp | 176 +++++++++++- delegate/src/armnn_delegate.cpp | 34 ++- delegate/src/test/Pooling3dTest.cpp | 431 ++++++++++++++++++++++++++++++ delegate/src/test/Pooling3dTestHelper.hpp | 295 ++++++++++++++++++++ docs/05_03_delegate.dox | 4 + 6 files changed, 933 insertions(+), 11 deletions(-) create mode 100644 delegate/src/test/Pooling3dTest.cpp create mode 100644 delegate/src/test/Pooling3dTestHelper.hpp diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt index d488de4c9c..523214bb90 100644 --- a/delegate/CMakeLists.txt +++ b/delegate/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright © 2020 Arm Ltd and Contributors. All rights reserved. +# Copyright © 2022 Arm Ltd and Contributors. All rights reserved. # SPDX-License-Identifier: MIT # @@ -177,6 +177,8 @@ if(BUILD_UNIT_TESTS) src/test/PadTestHelper.hpp src/test/Pooling2dTest.cpp src/test/Pooling2dTestHelper.hpp + src/test/Pooling3dTest.cpp + src/test/Pooling3dTestHelper.hpp src/test/PreluTest.cpp src/test/PreluTestHelper.hpp src/test/QuantizationTest.cpp diff --git a/delegate/src/Pooling.hpp b/delegate/src/Pooling.hpp index 4095ac4ac2..df8c3db727 100644 --- a/delegate/src/Pooling.hpp +++ b/delegate/src/Pooling.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2020 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -11,15 +11,16 @@ #include #include #include +#include namespace armnnDelegate { -TfLiteStatus VisitPoolingOperator(DelegateData& delegateData, - TfLiteContext* tfLiteContext, - TfLiteNode* tfLiteNode, - int nodeIndex, - int32_t tfLitePoolingOperatorCode) +TfLiteStatus VisitPooling2dOperator(DelegateData& delegateData, + TfLiteContext* tfLiteContext, + TfLiteNode* tfLiteNode, + int nodeIndex, + int32_t tfLitePoolingOperatorCode) { TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); @@ -113,4 +114,167 @@ TfLiteStatus VisitPoolingOperator(DelegateData& delegateData, return FusedActivation(tfLiteContext, tfLiteNode, activationType, poolingLayer, 0, delegateData); } +TfLiteStatus VisitPooling3dOperator(DelegateData& delegateData, + TfLiteContext* tfLiteContext, + TfLiteNode* tfLiteNode, + int nodeIndex, + std::string customOperatorName) +{ + 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 (IsDynamicTensor(tfLiteInputTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dynamic input tensors are not supported in operator #%d node #%d: ", + customOperatorName.c_str(), nodeIndex); + return kTfLiteError; + } + + const TfLiteTensor& tfLiteOutputTensor = tfLiteTensors[tfLiteNode->outputs->data[0]]; + if (IsDynamicTensor(tfLiteOutputTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dynamic output tensors are not supported in operator #%d node #%d: ", + customOperatorName.c_str(), nodeIndex); + return kTfLiteError; + } + // Set the input and output info + const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteInputTensor); + const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteOutputTensor); + + // Custom Operators are defined by the name string associated to the operator. Use this to determine + // which pooling algorithm to create the armnn operator with. L2 Pooling3D is unsupported in TfLite. + armnn::PoolingAlgorithm poolingAlgorithm; + if (customOperatorName == "MaxPool3D") + { + poolingAlgorithm = armnn::PoolingAlgorithm::Max; + } + else if (customOperatorName == "AveragePool3D") + { + poolingAlgorithm = armnn::PoolingAlgorithm::Average; + } + else + { + return kTfLiteError; + } + // Create the armnn pool3d descriptor and set the algorithm parsed above. + armnn::Pooling3dDescriptor descriptor; + descriptor.m_PoolType = poolingAlgorithm; + + // custom_initial_data and custom_initial_data_size are void* variables defined in the tflite registration + // used to access the custom option buffer for the operator. + auto custom_data = tfLiteNode->custom_initial_data; + auto custom_data_size = tfLiteNode->custom_initial_data_size; + // Reinterpret the void* to a byte buffer to access the options data in the flexbuffers map. + const flexbuffers::Map& m = flexbuffers::GetRoot(reinterpret_cast(custom_data), + custom_data_size).AsMap(); + // poolDims is a vector of [ 1, Depth, Height, Width, 1 ] + const auto poolDims = m["ksize"].AsTypedVector(); + descriptor.m_PoolWidth = poolDims[3].AsInt32(); + descriptor.m_PoolHeight = poolDims[2].AsInt32(); + descriptor.m_PoolDepth = poolDims[1].AsInt32(); + + // strideDimes is a vector of [ 1, Z, Y, X, 1] + const auto strideDims = m["strides"].AsTypedVector(); + descriptor.m_StrideX = strideDims[3].AsInt32(); + descriptor.m_StrideY = strideDims[2].AsInt32(); + descriptor.m_StrideZ = strideDims[1].AsInt32(); + descriptor.m_DataLayout = armnn::DataLayout::NDHWC; + + unsigned int inputDepth = inputTensorInfo.GetShape()[1]; + unsigned int inputHeight = inputTensorInfo.GetShape()[2]; + unsigned int inputWidth = inputTensorInfo.GetShape()[3]; + + // CalcPadding expects a TfLitePadding type. Parse flexbuffers to extract padding string and create TfLitePadding. + std::string paddingStr = m["padding"].AsString().str(); + TfLitePadding padding; + if (paddingStr == "VALID") + { + padding = kTfLitePaddingValid; + } + else if (paddingStr == "SAME") + { + padding = kTfLitePaddingSame; + } + else + { + padding = kTfLitePaddingUnknown; + } + // Calculates padding for each pooling dimension separately + CalcPadding(inputHeight, descriptor.m_PoolHeight, descriptor.m_StrideY, 1u, + descriptor.m_PadTop, descriptor.m_PadBottom, padding); + CalcPadding(inputWidth, descriptor.m_PoolWidth, descriptor.m_StrideX, 1u, + descriptor.m_PadLeft, descriptor.m_PadRight, padding); + CalcPadding(inputDepth, descriptor.m_PoolDepth, descriptor.m_StrideZ, 1u, + descriptor.m_PadFront, descriptor.m_PadBack, padding); + + // Validate the output info. + bool isSupported = false; + auto validateFunc = [&](const armnn::TensorInfo& outputTensorInfo, bool& isSupported) { + FORWARD_LAYER_SUPPORT_FUNC("POOLING_3D", + tfLiteContext, + IsPooling3dSupported, + delegateData.m_Backends, + isSupported, + inputTensorInfo, + outputTensorInfo, + descriptor); + }; + + if (!delegateData.m_Network) + { + validateFunc(outputTensorInfo, isSupported); + return isSupported ? kTfLiteOk : kTfLiteError; + } + + // Create the Layer + armnn::IConnectableLayer* poolingLayer = delegateData.m_Network->AddPooling3dLayer(descriptor); + ARMNN_ASSERT(poolingLayer != nullptr); + + // Create and set output slots + armnn::IOutputSlot& outputSlot = poolingLayer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + Connect(poolingLayer, tfLiteNode, delegateData); + + // Check activation by parsing the string from the flexbuffer map + std::string activationTypeStr = m["activation"].AsString().str(); + TfLiteFusedActivation activationType; + + if (activationTypeStr == "kTfLiteActRelu") + { + activationType = kTfLiteActRelu; + } + else if (activationTypeStr == "kTfLiteActReluN1To1") + { + activationType = kTfLiteActReluN1To1; + } + else if (activationTypeStr == "kTfLiteActRelu6") + { + activationType = kTfLiteActRelu6; + } + else if (activationTypeStr == "kTfLiteActTanh") + { + activationType = kTfLiteActTanh; + } + else if (activationTypeStr == "kTfLiteActSignBit") + { + activationType = kTfLiteActSignBit; + } + else if (activationTypeStr == "kTfLiteActSigmoid") + { + activationType = kTfLiteActSigmoid; + } + else + { + activationType = kTfLiteActNone; + } + + return FusedActivation(tfLiteContext, tfLiteNode, activationType, poolingLayer, 0, delegateData); +} + } // namespace armnnDelegate diff --git a/delegate/src/armnn_delegate.cpp b/delegate/src/armnn_delegate.cpp index 6e1a91f9e4..bb2f3c319a 100644 --- a/delegate/src/armnn_delegate.cpp +++ b/delegate/src/armnn_delegate.cpp @@ -1,5 +1,5 @@ // -// Copyright © 2020 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -495,6 +495,32 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, { switch (tfLiteRegistration->builtin_code) { + case kTfLiteBuiltinCustom: + { +#if defined(ARMNN_POST_TFLITE_2_5) + // Custom operators are defined by the name rather than the builtin code. + // Parse the custom_name param in the registration to point to the correct visitor function. + std::string customOperatorName = tfLiteRegistration->custom_name; + if ( customOperatorName == "AveragePool3D" ) + { + return VisitPooling3dOperator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + customOperatorName); + } + else if (customOperatorName == "MaxPool3D") + { + return VisitPooling3dOperator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + customOperatorName); + } +#endif + // Invalid or unsupported custom operator + return kTfLiteError; + } case kTfLiteBuiltinAbs: return VisitElementwiseUnaryOperator(delegateData, tfLiteContext, @@ -520,7 +546,7 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, nodeIndex, kTfLiteBuiltinArgMin); case kTfLiteBuiltinAveragePool2d: - return VisitPoolingOperator(delegateData, + return VisitPooling2dOperator(delegateData, tfLiteContext, tfLiteNode, nodeIndex, @@ -667,7 +693,7 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, nodeIndex, kTfLiteBuiltinL2Normalization); case kTfLiteBuiltinL2Pool2d: - return VisitPoolingOperator(delegateData, + return VisitPooling2dOperator(delegateData, tfLiteContext, tfLiteNode, nodeIndex, @@ -729,7 +755,7 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, nodeIndex, kTfLiteBuiltinLstm); case kTfLiteBuiltinMaxPool2d: - return VisitPoolingOperator(delegateData, + return VisitPooling2dOperator(delegateData, tfLiteContext, tfLiteNode, nodeIndex, diff --git a/delegate/src/test/Pooling3dTest.cpp b/delegate/src/test/Pooling3dTest.cpp new file mode 100644 index 0000000000..c0a186210e --- /dev/null +++ b/delegate/src/test/Pooling3dTest.cpp @@ -0,0 +1,431 @@ +// +// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "Pooling3dTestHelper.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace armnnDelegate +{ + +// Pool3D custom op was only added in tflite r2.6. +#if defined(ARMNN_POST_TFLITE_2_5) + +void MaxPool3dFP32PaddingValidTest(std::vector& backends) +{ + // Set input and expected output data + std::vector inputShape = { 1, 2, 3, 4, 1 }; + std::vector outputShape = { 1, 1, 2, 3, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 6, 6, 4 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kMax"; + TfLitePadding padding = kTfLitePaddingValid; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 1, + 1, + 1, + 2, + 2, + 2); +} + +void MaxPool3dFP32PaddingSameTest(std::vector& backends) +{ + // Set input data and expected output data + std::vector inputShape = { 1, 2, 3, 4, 1 }; + std::vector outputShape = { 1, 2, 3, 4, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 6, 6, 4, 4, 6, 6, 6, 6, 4, 5, 6, 6, 6, 6, 4, 4 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kMax"; + TfLitePadding padding = kTfLitePaddingSame; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 1, + 1, + 1, + 2, + 2, + 2); +} + +void MaxPool3dFP32H1Test(std::vector& backends) +{ + // Set input data and expected output data + std::vector inputShape = { 1, 2, 3, 4, 1 }; + std::vector outputShape = { 1, 1, 3, 3, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 2, 3 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kMax"; + TfLitePadding padding = kTfLitePaddingValid; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 1, + 1, + 1, + 2, + 1, + 2); +} + +void MaxPool3dFP32Test(std::vector& backends) +{ + // Set input data and expected output data + std::vector inputShape = { 1, 2, 3, 4, 1 }; + std::vector outputShape = { 1, 2, 3, 4, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 6, 6 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kMax"; + TfLitePadding padding = kTfLitePaddingUnknown; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 1, + 1, + 1, + 2, + 2, + 2); +} + +void AveragePool3dFP32PaddingValidTest(std::vector& backends) +{ + // Set input data and expected output data. + std::vector inputShape = { 1, 2, 3, 4, 1 }; + std::vector outputShape = { 1, 1, 2, 3, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 3.5, 3, 2.5 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kAverage"; + TfLitePadding padding = kTfLitePaddingValid; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 1, + 1, + 1, + 2, + 2, + 2); +} + +void AveragePool3dFP32PaddingSameTest(std::vector& backends) +{ + // Set input data and expected output data + std::vector inputShape = { 4, 2, 3, 1, 1 }; + std::vector outputShape = { 4, 2, 3, 1, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 3, 4, 4.5, 4.5, 5.5, 6, 3, 4, 4.5, 4.5, 5.5, 6, 3, 4, 4.5, 4.5 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kAverage"; + TfLitePadding padding = kTfLitePaddingSame; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 1, + 1, + 1, + 2, + 2, + 2); +} + +void AveragePool3dFP32H1Test(std::vector& backends) +{ + // Set input data and expected output data + std::vector inputShape = { 1, 2, 3, 4, 1 }; + std::vector outputShape = { 1, 1, 2, 2, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 1.5, 3.5 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kAverage"; + TfLitePadding padding = kTfLitePaddingUnknown; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 2, + 2, + 2, + 2, + 1, + 2); +} + +void AveragePool3dFP32Test(std::vector& backends) +{ + // Set input data and expected output data + std::vector inputShape = { 4, 3, 2, 1, 1 }; + std::vector outputShape = { 1, 2, 2, 4, 1 }; + + std::vector inputValues = { 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6, + 1, 2, 3, 4, 5, 6 }; + std::vector expectedOutputValues = { 3.125, 4.25 }; + + // poolType string required to create the correct pooling operator + // Padding type required to create the padding in custom options + std::string poolType = "kMax"; + TfLitePadding padding = kTfLitePaddingUnknown; + + Pooling3dTest(poolType, + ::tflite::TensorType_FLOAT32, + backends, + inputShape, + outputShape, + inputValues, + expectedOutputValues, + padding, + 2, + 2, + 2, + 2, + 2, + 2); +} + +TEST_SUITE("Pooling3d_GpuAccTests") +{ + +TEST_CASE ("MaxPooling3d_FP32_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc }; + MaxPool3dFP32Test(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_PaddingValid_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc }; + MaxPool3dFP32PaddingValidTest(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_PaddingSame_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc }; + MaxPool3dFP32PaddingSameTest(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_H1_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc }; + MaxPool3dFP32H1Test(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_PaddingValid_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc }; + AveragePool3dFP32PaddingValidTest(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_PaddingSame_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc }; + AveragePool3dFP32PaddingSameTest(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_H1_GpuAcc_Test") +{ + std::vector backends = { armnn::Compute::GpuAcc }; + AveragePool3dFP32H1Test(backends); +} + +} // TEST_SUITE("Pooling3d_GpuAccTests") + +TEST_SUITE("Pooling3d_CpuAccTests") +{ + +TEST_CASE ("MaxPooling3d_FP32_PaddingValid_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc }; + MaxPool3dFP32PaddingValidTest(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_PaddingSame_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc }; + MaxPool3dFP32PaddingSameTest(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc }; + MaxPool3dFP32Test(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_H1_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc }; + MaxPool3dFP32H1Test(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_PaddingValid_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc }; + AveragePool3dFP32PaddingValidTest(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_PaddingSame_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc }; + AveragePool3dFP32PaddingSameTest(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_H1_CpuAcc_Test") +{ + std::vector backends = { armnn::Compute::CpuAcc }; + AveragePool3dFP32H1Test(backends); +} + +} // TEST_SUITE("Pooling3d_CpuAccTests") + +TEST_SUITE("Pooling3d_CpuRefTests") +{ +TEST_CASE ("MaxPooling3d_FP32_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + MaxPool3dFP32Test(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_PaddingValid_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + MaxPool3dFP32PaddingValidTest(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_PaddingSame_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + MaxPool3dFP32PaddingSameTest(backends); +} + +TEST_CASE ("MaxPooling3d_FP32_H1_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + MaxPool3dFP32H1Test(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_PaddingValid_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + AveragePool3dFP32PaddingValidTest(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_PaddingSame_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + AveragePool3dFP32PaddingSameTest(backends); +} + +TEST_CASE ("AveragePooling3d_FP32_H1_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + AveragePool3dFP32H1Test(backends); +} + +} // TEST_SUITE("Pooling3d_CpuRefTests") + +#endif + +} \ No newline at end of file diff --git a/delegate/src/test/Pooling3dTestHelper.hpp b/delegate/src/test/Pooling3dTestHelper.hpp new file mode 100644 index 0000000000..f5f5cc3809 --- /dev/null +++ b/delegate/src/test/Pooling3dTestHelper.hpp @@ -0,0 +1,295 @@ +// +// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "TestUtils.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +#if defined(ARMNN_POST_TFLITE_2_5) + +std::vector CreateCustomOptions(int, int, int, int, int, int, TfLitePadding); + +std::vector CreatePooling3dTfLiteModel( + std::string poolType, + tflite::TensorType tensorType, + const std::vector& inputTensorShape, + const std::vector& outputTensorShape, + TfLitePadding padding = kTfLitePaddingSame, + int32_t strideWidth = 0, + int32_t strideHeight = 0, + int32_t strideDepth = 0, + int32_t filterWidth = 0, + int32_t filterHeight = 0, + int32_t filterDepth = 0, + tflite::ActivationFunctionType fusedActivation = tflite::ActivationFunctionType_NONE, + float quantScale = 1.0f, + int quantOffset = 0) +{ + using namespace tflite; + flatbuffers::FlatBufferBuilder flatBufferBuilder; + + std::vector> buffers; + buffers.push_back(CreateBuffer(flatBufferBuilder, flatBufferBuilder.CreateVector({}))); + + auto quantizationParameters = + CreateQuantizationParameters(flatBufferBuilder, + 0, + 0, + flatBufferBuilder.CreateVector({ quantScale }), + flatBufferBuilder.CreateVector({ quantOffset })); + + // Create the input and output tensors + std::array, 2> tensors; + tensors[0] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(inputTensorShape.data(), + inputTensorShape.size()), + tensorType, + 0, + flatBufferBuilder.CreateString("input"), + quantizationParameters); + + tensors[1] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(outputTensorShape.data(), + outputTensorShape.size()), + tensorType, + 0, + flatBufferBuilder.CreateString("output"), + quantizationParameters); + + // Create the custom options from the function below + std::vector customOperatorOptions = CreateCustomOptions(strideHeight, strideWidth, strideDepth, + filterHeight, filterWidth, filterDepth, padding); + // opCodeIndex is created as a uint8_t to avoid map lookup + uint8_t opCodeIndex = 0; + // Set the operator name based on the PoolType passed in from the test case + std::string opName = ""; + if (poolType == "kMax") + { + opName = "MaxPool3D"; + } + else + { + opName = "AveragePool3D"; + } + // To create a custom operator code you pass in the builtin code for custom operators and the name of the custom op + flatbuffers::Offset operatorCode = CreateOperatorCodeDirect(flatBufferBuilder, + tflite::BuiltinOperator_CUSTOM, + opName.c_str()); + + // Create the Operator using the opCodeIndex and custom options. Also sets builtin options to none. + const std::vector operatorInputs{ 0 }; + const std::vector operatorOutputs{ 1 }; + flatbuffers::Offset poolingOperator = + CreateOperator(flatBufferBuilder, + opCodeIndex, + flatBufferBuilder.CreateVector(operatorInputs.data(), operatorInputs.size()), + flatBufferBuilder.CreateVector(operatorOutputs.data(), operatorOutputs.size()), + tflite::BuiltinOptions_NONE, + 0, + flatBufferBuilder.CreateVector(customOperatorOptions), + tflite::CustomOptionsFormat_FLEXBUFFERS); + + // Create the subgraph using the operator created above. + const std::vector subgraphInputs{ 0 }; + const std::vector subgraphOutputs{ 1 }; + 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(&poolingOperator, 1)); + + flatbuffers::Offset modelDescription = + flatBufferBuilder.CreateString("ArmnnDelegate: Pooling3d Operator Model"); + + // Create the model using operatorCode and the subgraph. + 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 Pooling3dTest(std::string poolType, + tflite::TensorType tensorType, + std::vector& backends, + std::vector& inputShape, + std::vector& outputShape, + std::vector& inputValues, + std::vector& expectedOutputValues, + TfLitePadding padding = kTfLitePaddingSame, + int32_t strideWidth = 0, + int32_t strideHeight = 0, + int32_t strideDepth = 0, + int32_t filterWidth = 0, + int32_t filterHeight = 0, + int32_t filterDepth = 0, + tflite::ActivationFunctionType fusedActivation = tflite::ActivationFunctionType_NONE, + float quantScale = 1.0f, + int quantOffset = 0) +{ + using namespace tflite; + // Create the single op model buffer + std::vector modelBuffer = CreatePooling3dTfLiteModel(poolType, + tensorType, + inputShape, + outputShape, + padding, + strideWidth, + strideHeight, + strideDepth, + filterWidth, + filterHeight, + filterDepth, + fusedActivation, + quantScale, + quantOffset); + + const Model* tfLiteModel = GetModel(modelBuffer.data()); + CHECK(tfLiteModel != nullptr); + // Create TfLite Interpreters + std::unique_ptr armnnDelegateInterpreter; + + // Custom ops need to be added to the BuiltinOp resolver before the interpreter is created + // Based on the poolType from the test case add the custom operator using the name and the tflite + // registration function + tflite::ops::builtin::BuiltinOpResolver armnn_op_resolver; + if (poolType == "kMax") + { + armnn_op_resolver.AddCustom("MaxPool3D", tflite::ops::custom::Register_MAX_POOL_3D()); + } + else + { + armnn_op_resolver.AddCustom("AveragePool3D", tflite::ops::custom::Register_AVG_POOL_3D()); + } + + CHECK(InterpreterBuilder(tfLiteModel, armnn_op_resolver) + (&armnnDelegateInterpreter) == kTfLiteOk); + CHECK(armnnDelegateInterpreter != nullptr); + CHECK(armnnDelegateInterpreter->AllocateTensors() == kTfLiteOk); + + std::unique_ptr tfLiteInterpreter; + + // Custom ops need to be added to the BuiltinOp resolver before the interpreter is created + // Based on the poolType from the test case add the custom operator using the name and the tflite + // registration function + tflite::ops::builtin::BuiltinOpResolver tflite_op_resolver; + if (poolType == "kMax") + { + tflite_op_resolver.AddCustom("MaxPool3D", tflite::ops::custom::Register_MAX_POOL_3D()); + } + else + { + tflite_op_resolver.AddCustom("AveragePool3D", tflite::ops::custom::Register_AVG_POOL_3D()); + } + + CHECK(InterpreterBuilder(tfLiteModel, tflite_op_resolver) + (&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 tfLiteDelegateInputData = tfLiteInterpreter->typed_tensor(tfLiteDelegateInputId); + for (unsigned int i = 0; i < inputValues.size(); ++i) + { + tfLiteDelegateInputData[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 EnqueueWorkload + CHECK(tfLiteInterpreter->Invoke() == kTfLiteOk); + CHECK(armnnDelegateInterpreter->Invoke() == kTfLiteOk); + + armnnDelegate::CompareOutputData(tfLiteInterpreter, armnnDelegateInterpreter, outputShape, expectedOutputValues); +} + +// Function to create the flexbuffer custom options for the custom pooling3d operator. +std::vector CreateCustomOptions(int strideHeight, int strideWidth, int strideDepth, + int filterHeight, int filterWidth, int filterDepth, TfLitePadding padding) +{ + auto flex_builder = std::make_unique(); + size_t map_start = flex_builder->StartMap(); + flex_builder->String("data_format", "NDHWC"); + // Padding is created as a key and padding type. Only VALID and SAME supported + if (padding == kTfLitePaddingValid) + { + flex_builder->String("padding", "VALID"); + } + else + { + flex_builder->String("padding", "SAME"); + } + + // Vector of filter dimensions in order ( 1, Depth, Height, Width, 1 ) + auto start = flex_builder->StartVector("ksize"); + flex_builder->Add(1); + flex_builder->Add(filterDepth); + flex_builder->Add(filterHeight); + flex_builder->Add(filterWidth); + flex_builder->Add(1); + // EndVector( start, bool typed, bool fixed) + flex_builder->EndVector(start, true, false); + + // Vector of stride dimensions in order ( 1, Depth, Height, Width, 1 ) + auto stridesStart = flex_builder->StartVector("strides"); + flex_builder->Add(1); + flex_builder->Add(strideDepth); + flex_builder->Add(strideHeight); + flex_builder->Add(strideWidth); + flex_builder->Add(1); + // EndVector( stridesStart, bool typed, bool fixed) + flex_builder->EndVector(stridesStart, true, false); + + flex_builder->EndMap(map_start); + flex_builder->Finish(); + + return flex_builder->GetBuffer(); +} +#endif +} // anonymous namespace + + + + diff --git a/docs/05_03_delegate.dox b/docs/05_03_delegate.dox index 625b253992..d1c41fe213 100644 --- a/docs/05_03_delegate.dox +++ b/docs/05_03_delegate.dox @@ -43,6 +43,8 @@ The Arm NN SDK TensorFlow Lite delegate currently supports the following operato - AVERAGE_POOL_2D, Supported Fused Activation: RELU , RELU6 , TANH, NONE +- AVERAGE_POOL_3D + - BATCH_TO_SPACE_ND - CAST @@ -107,6 +109,8 @@ The Arm NN SDK TensorFlow Lite delegate currently supports the following operato - MAX_POOL_2D, Supported Fused Activation: RELU , RELU6 , TANH, NONE +- MAX_POOL_3D + - MEAN - MINIMUM -- cgit v1.2.1