From 81ec994a3ebc8ad02c4a622846cf64b70e1182bd Mon Sep 17 00:00:00 2001 From: Matthew Sloyan Date: Tue, 12 Oct 2021 10:26:30 +0100 Subject: IVGCVSW-6166 Add Support for Conv3d to TFLite Delegate * Conv3d is only correctly supported for external delegates from TF v2.6, as there was a breaking bug in v2.5. Signed-off-by: Matthew Sloyan Change-Id: Ib7941307f4c7b0d3dbb7deaa5a90aceb63c1162f --- delegate/src/test/Convolution3dTest.cpp | 273 ++++++++++++++++++++++++++++ delegate/src/test/ConvolutionTestHelper.hpp | 258 ++++++++++++++++++++++++++ 2 files changed, 531 insertions(+) create mode 100644 delegate/src/test/Convolution3dTest.cpp (limited to 'delegate/src/test') diff --git a/delegate/src/test/Convolution3dTest.cpp b/delegate/src/test/Convolution3dTest.cpp new file mode 100644 index 0000000000..6caa7ea18f --- /dev/null +++ b/delegate/src/test/Convolution3dTest.cpp @@ -0,0 +1,273 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "ConvolutionTestHelper.hpp" + +#include + +#include +#include +#include +#include +#include + +#include + +namespace armnnDelegate +{ + +// Conv3d is currently only supports Float32 inputs, filter, bias and outputs in TFLite. +// Conv3d is only correctly supported for external delegates from TF Lite v2.6, as there was a breaking bug in v2.5. +#if defined(ARMNN_POST_TFLITE_2_5) + +// Create a vector from 0 to size divided to create smaller floating point values. +template +std::vector CreateFloatData(int32_t size, float divisor) +{ + std::vector data; + for (int32_t i = 0; i < size; ++i) + { + float value = static_cast(i); + data.push_back(value/divisor); + } + return data; +} + +void Conv3DWithBiasesSimpleWithPaddingFp32Test(std::vector& backends) +{ + // Set input data + std::vector inputShape { 1, 2, 2, 2, 1 }; + std::vector filterShape { 2, 2, 2, 1, 1 }; + std::vector biasShape { 1 }; + std::vector outputShape { 1, 2, 2, 2, 1 }; + + static std::vector inputValues = + { + 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f + }; + + std::vector filterValues = + { + 2.f,1.f, 1.f,0.f, 0.f,1.f, 1.f,1.f + }; + + std::vector biasValues = { 5.f }; + + std::vector expectedOutputValues = + { + 33.f, 21.f, 23.f, 13.f, 28.f, 25.f, 27.f, 21.f + }; + + Convolution3dTest(tflite::BuiltinOperator_CONV_3D, + ::tflite::TensorType_FLOAT32, + { 1, 1, 1 }, // strideX, strideY, strideZ + { 1, 1, 1 }, // dilationX, dilationY, dilationZ + tflite::Padding_SAME, + tflite::ActivationFunctionType_NONE, + backends, + inputShape, + filterShape, + outputShape, + inputValues, + filterValues, + expectedOutputValues, + biasShape, + biasValues); +} + +void Conv3DWithBiasesStridesFp32Test(std::vector& backends) +{ + std::vector inputShape { 1, 3, 10, 10, 1 }; + std::vector filterShape { 3, 5, 5, 1, 1 }; + std::vector biasShape { 1 }; + std::vector outputShape { 1, 1, 3, 3, 1 }; + + std::vector inputValues = CreateFloatData(300, 1.0f); + + std::vector filterValues = + { + 1.f, 1.f, 1.f, 1.f, 1.f, + 1.f, 1.f, 1.f, 1.f, 1.f, + 1.f, 1.f, 1.f, 1.f, 1.f, + 1.f, 1.f, 1.f, 1.f, 1.f, + 1.f, 1.f, 1.f, 1.f, 1.f, + + 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, + + 2.f, 2.f, 2.f, 2.f, 2.f, + 2.f, 2.f, 2.f, 2.f, 2.f, + 2.f, 2.f, 2.f, 2.f, 2.f, + 2.f, 2.f, 2.f, 2.f, 2.f, + 2.f, 2.f, 2.f, 2.f, 2.f + }; + + std::vector biasValues = { 10.f }; + + std::vector expectedOutputValues = + { + 11660.f, 11810.f, 11960.f, + + 13160.f, 13310.f, 13460.f, + + 14660.f, 14810.f, 14960.f + }; + + Convolution3dTest(tflite::BuiltinOperator_CONV_3D, + ::tflite::TensorType_FLOAT32, + { 2, 2, 2 }, // strideX, strideY, strideZ + { 1, 1, 1 }, // dilationX, dilationY, dilationZ + tflite::Padding_VALID, + tflite::ActivationFunctionType_NONE, + backends, + inputShape, + filterShape, + outputShape, + inputValues, + filterValues, + expectedOutputValues, + biasShape, + biasValues); +} + + +void Conv3DWithBiasesDilationFp32Test(std::vector& backends) +{ + std::vector inputShape { 1, 5, 5, 5, 2 }; + std::vector filterShape { 2, 2, 2, 2, 2 }; + std::vector biasShape { 2 }; + std::vector outputShape { 1, 2, 2, 2, 2 }; + + std::vector inputValues = CreateFloatData(250, 1.0f); + + std::vector filterValues = + { + -1.f, -1.f, -1.f, -1.f, -1.f, -1.f, -1.f, -1.f, -1.f, -1.f, -1.f, 1.f, 1.f, 1.f, -1.f, -1.f, + 1.f, 1.f, -1.f, 1.f, -1.f, 1.f, -1.f, 1.f, -1.f, -1.f, -1.f, 1.f, -1.f, 1.f, -1.f, 1.f, + }; + + std::vector biasValues = { 0.f, 2.f }; + + // Since the dilation rate is 3 this will dilate the kernel to be 4x4, + // therefore the output will be 2x2 + std::vector expectedOutputValues = + { + -1124.f, 976.f, + -1148.f, 980.f, + + -1244.f, 996.f, + -1268.f, 1000.f, + + -1724.f, 1076.f, + -1748.f, 1080.f, + + -1844.f, 1096.f, + -1868.f, 1100.f + }; + + Convolution3dTest(tflite::BuiltinOperator_CONV_3D, + ::tflite::TensorType_FLOAT32, + { 1, 1, 1 }, // strideX, strideY, strideZ + { 3, 3, 3 }, // dilationX, dilationY, dilationZ + tflite::Padding_VALID, + tflite::ActivationFunctionType_NONE, + backends, + inputShape, + filterShape, + outputShape, + inputValues, + filterValues, + expectedOutputValues, + biasShape, + biasValues); +} + +void Conv3DFp32SmallTest(std::vector& backends) +{ + std::vector inputShape { 1, 3, 10, 10, 1 }; + std::vector filterShape { 3, 3, 3, 1, 1 }; + std::vector biasShape { 1 }; + std::vector outputShape { 1, 1, 4, 4, 1 }; + + std::vector inputValues = CreateFloatData(300, 100.0f); + + std::vector filterValues = + { + 0.125977f, 0.150391f, 0.101562f, + 0.0585938f, 0.0864258f, 0.043457f, + 0.034668f, 0.0322266f, 0.0385742f, + + 0.125977f, 0.150391f, -0.101562f, + -0.0585938f,-0.0864258f,-0.043457f, + -0.0104630f, 0.0154114f, 0.0013768f, + + 0.0344238f, 0.035644f, 0.0495605f, + 0.0683594f, 0.099121f, -0.0461426f, + -0.0996094f,-0.126953f, -0.043457f, + }; + + std::vector biasValues = { 0 }; + + std::vector expectedOutputValues = + { + -0.08156067f, -0.06891209f, -0.05589598f, -0.04310101f, + 0.04584253f, 0.05855697f, 0.07129729f, 0.08325434f, + 0.17304349f, 0.18521416f, 0.19818866f, 0.21096253f, + 0.29965734f, 0.312698f, 0.32547557f, 0.33818722f + }; + + Convolution3dTest(tflite::BuiltinOperator_CONV_3D, + ::tflite::TensorType_FLOAT32, + { 2, 2, 2 }, // strideX, strideY, strideZ + { 1, 1, 1 }, // dilationX, dilationY, dilationZ + tflite::Padding_VALID, + tflite::ActivationFunctionType_NONE, + backends, + inputShape, + filterShape, + outputShape, + inputValues, + filterValues, + expectedOutputValues, + biasShape, + biasValues); +} + +TEST_SUITE("Convolution3dTest_CpuRefTests") +{ + +TEST_CASE ("Conv3DWithBiasesSimpleWithPadding_Fp32_CpuRef_Test") +{ + std::vector backends = {armnn::Compute::CpuRef}; + Conv3DWithBiasesSimpleWithPaddingFp32Test(backends); +} + +TEST_CASE ("Conv3DWithBiasesStrides_Fp32_CpuRef_Test") +{ + std::vector backends = {armnn::Compute::CpuRef}; + Conv3DWithBiasesStridesFp32Test(backends); +} + +TEST_CASE ("Conv3DWithBiasesDilation_Fp32_CpuRef_Test") +{ + std::vector backends = {armnn::Compute::CpuRef}; + Conv3DWithBiasesDilationFp32Test(backends); +} + +TEST_CASE ("Conv3DFp32Small_Fp32_CpuRef_Test") +{ + std::vector backends = {armnn::Compute::CpuRef}; + Conv3DFp32SmallTest(backends); +} + + +} //End of TEST_SUITE("Convolution3dTest_CpuRefTests") + +#endif + +} // namespace armnnDelegate \ No newline at end of file diff --git a/delegate/src/test/ConvolutionTestHelper.hpp b/delegate/src/test/ConvolutionTestHelper.hpp index 1b33c1d74d..ce1f951d21 100644 --- a/delegate/src/test/ConvolutionTestHelper.hpp +++ b/delegate/src/test/ConvolutionTestHelper.hpp @@ -5,6 +5,8 @@ #pragma once +#include "TestUtils.hpp" + #include #include @@ -221,6 +223,7 @@ void ConvolutionTest(tflite::BuiltinOperator convolutionOperatorCode, using namespace tflite; std::vector modelBuffer; + modelBuffer = CreateConv2dTfLiteModel(convolutionOperatorCode, tensorType, strideX, @@ -301,6 +304,261 @@ void ConvolutionTest(tflite::BuiltinOperator convolutionOperatorCode, } } +// Conv3d is only correctly supported for external delegates from TF Lite v2.6, as there was a breaking bug in v2.5. +#if defined(ARMNN_POST_TFLITE_2_5) +template +std::vector CreateConv3dTfLiteModel(tflite::BuiltinOperator convolutionOperatorCode, + tflite::TensorType tensorType, + std::vector strides, + std::vector dilation, + tflite::Padding padding, + tflite::ActivationFunctionType fused_activation_function, + const std::vector& inputTensorShape, + const std::vector& filterTensorShape, + const std::vector& biasTensorShape, + const std::vector& outputTensorShape, + const std::vector& filterData, + const std::vector& biasData, + const std::vector biasScales = {1.0f}, + const std::vector biasOffsets = {0}, + const std::vector filterScales = {1.0f}, + const std::vector filterOffsets = {0}, + float outputQuantScale = 2.0f, + int outputQuantOffset = 0, + float quantScale = 1.0f, + int quantOffset = 0, + int32_t depth_multiplier = 1, + int32_t filterQuantizationDim = 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(filterData.data()), + sizeof(T) * filterData.size())); + + buffers[2] = CreateBuffer(flatBufferBuilder, + flatBufferBuilder.CreateVector(reinterpret_cast(biasData.data()), + sizeof(B) * 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 })); + + auto filterQuantizationParameters = + CreateQuantizationParameters(flatBufferBuilder, + 0, + 0, + flatBufferBuilder.CreateVector(filterScales), + flatBufferBuilder.CreateVector(filterOffsets), + tflite::QuantizationDetails_NONE, + 0, + filterQuantizationDim); + + auto biasQuantizationParameters = + CreateQuantizationParameters(flatBufferBuilder, + 0, + 0, + flatBufferBuilder.CreateVector(biasScales), + flatBufferBuilder.CreateVector(biasOffsets)); + + std::array, 4> tensors; + tensors[0] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(inputTensorShape.data(), + inputTensorShape.size()), + tensorType, + 0, + flatBufferBuilder.CreateString("input"), + quantizationParameters); + tensors[1] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(filterTensorShape.data(), + filterTensorShape.size()), + tensorType, + 1, + flatBufferBuilder.CreateString("filter"), + filterQuantizationParameters); + + auto biasTensorType = ::tflite::TensorType_FLOAT32; + if (tensorType == ::tflite::TensorType_INT8 || tensorType == ::tflite::TensorType_UINT8) + { + biasTensorType = ::tflite::TensorType_INT32; + } + tensors[2] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(biasTensorShape.data(), biasTensorShape.size()), + biasTensorType, + 2, + flatBufferBuilder.CreateString("bias"), + biasQuantizationParameters); + tensors[3] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(outputTensorShape.data(), + outputTensorShape.size()), + tensorType, + 0, + flatBufferBuilder.CreateString("output"), + outputQuantizationParameters); + + tflite::BuiltinOptions operatorBuiltinOptionsType = tflite::BuiltinOptions_Conv3DOptions; + flatbuffers::Offset operatorBuiltinOptions = CreateConv3DOptions(flatBufferBuilder, + padding, + strides[2], // Depth + strides[0], // Width + strides[1], // Height + fused_activation_function, + dilation[2], + dilation[0], + dilation[1]).Union(); + + // Create operator + const std::vector operatorInputs{0, 1, 2}; + const std::vector operatorOutputs{3}; + flatbuffers::Offset convolutionOperator = + 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(&convolutionOperator, 1)); + + flatbuffers::Offset modelDescription = + flatBufferBuilder.CreateString("ArmnnDelegate: Convolution 3d Operator Model"); + + // If using an operator with a code greater than 127 then the enum value should be passed as the fifth + // parameter rather than the second like in other tests. + flatbuffers::Offset operatorCode = + CreateOperatorCode(flatBufferBuilder, 0, 0, 1, tflite::BuiltinOperator_CONV_3D); + + 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 Convolution3dTest(tflite::BuiltinOperator convolutionOperatorCode, + tflite::TensorType tensorType, + std::vector strides, + std::vector dilation, + tflite::Padding padding, + tflite::ActivationFunctionType fused_activation_function, + std::vector& backends, + std::vector& inputShape, + std::vector& filterShape, + std::vector& outputShape, + std::vector& inputValues, + std::vector& filterValues, + std::vector& expectedOutputValues, + const std::vector& biasShape = {}, + const std::vector& biasValues = {}, + const std::vector biasScales = {1.0f}, + const std::vector biasOffsets = {0}, + const std::vector filterScales = {1.0f}, + const std::vector filterOffsets = {0}, + float outputQuantScale = 2.0f, + int outputQuantOffset = 0, + float quantScale = 1.0f, + int quantOffset = 0, + int32_t depth_multiplier = 1, + int32_t filterQuantizationDim = 3) +{ + using namespace tflite; + + std::vector modelBuffer; + modelBuffer = CreateConv3dTfLiteModel(convolutionOperatorCode, + tensorType, + strides, + dilation, + padding, + fused_activation_function, + inputShape, + filterShape, + biasShape, + outputShape, + filterValues, + biasValues, + biasScales, + biasOffsets, + filterScales, + filterOffsets, + outputQuantScale, + outputQuantOffset, + quantScale, + quantOffset, + depth_multiplier, + filterQuantizationDim); + + 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 + armnnDelegate::FillInput(tfLiteInterpreter, 0, inputValues); + armnnDelegate::FillInput(armnnDelegateInterpreter, 0, inputValues); + + // Run EnqueueWorkload + CHECK(tfLiteInterpreter->Invoke() == kTfLiteOk); + CHECK(armnnDelegateInterpreter->Invoke() == kTfLiteOk); + + // Compare output data + auto tfLiteDelegateOutputId = tfLiteInterpreter->outputs()[0]; + auto tfLiteDelagateOutputData = tfLiteInterpreter->typed_tensor(tfLiteDelegateOutputId); + auto armnnDelegateOutputId = armnnDelegateInterpreter->outputs()[0]; + auto armnnDelegateOutputData = armnnDelegateInterpreter->typed_tensor(armnnDelegateOutputId); + + armnnDelegate::CompareData(expectedOutputValues.data(), armnnDelegateOutputData, expectedOutputValues.size(), 1); + armnnDelegate::CompareData(expectedOutputValues.data(), tfLiteDelagateOutputData, expectedOutputValues.size(), 1); + armnnDelegate::CompareData(tfLiteDelagateOutputData, armnnDelegateOutputData, expectedOutputValues.size(), 1); +} +#endif + template std::vector CreateTransposeConvTfLiteModel(tflite::TensorType tensorType, uint32_t strideX, -- cgit v1.2.1