From 7306bbef8b06cb9689108ff56bd67036d02ca79d Mon Sep 17 00:00:00 2001 From: Tracy Narine Date: Mon, 17 Jul 2023 16:06:26 +0100 Subject: IVGCVSW-7834 Add REVERSE_V2 to classic and opaque delegates * Adding support for ReverseV2 in the classic and opaque delegates * CMake files updated * Tests added * Gpu/Cpu Acc tests compiled out until functionality is written Signed-off-by: Tracy Narine Change-Id: I8b41b7e71f2e28e5ea8dddbd00657900e6d7ab9a --- delegate/CMakeLists.txt | 2 + delegate/classic/CMakeLists.txt | 1 + delegate/classic/src/ReverseV2.hpp | 154 +++++++++++++++++++++++++++ delegate/classic/src/armnn_delegate.cpp | 7 ++ delegate/opaque/CMakeLists.txt | 1 + delegate/opaque/src/ReverseV2.hpp | 174 ++++++++++++++++++++++++++++++ delegate/opaque/src/armnn_delegate.cpp | 7 ++ delegate/test/ReverseV2Test.cpp | 183 ++++++++++++++++++++++++++++++++ delegate/test/ReverseV2TestHelper.hpp | 148 ++++++++++++++++++++++++++ docs/05_03_delegate.dox | 2 + 10 files changed, 679 insertions(+) create mode 100644 delegate/classic/src/ReverseV2.hpp create mode 100644 delegate/opaque/src/ReverseV2.hpp create mode 100644 delegate/test/ReverseV2Test.cpp create mode 100644 delegate/test/ReverseV2TestHelper.hpp diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt index bb552d33bb..e46ac04092 100644 --- a/delegate/CMakeLists.txt +++ b/delegate/CMakeLists.txt @@ -182,6 +182,8 @@ if(BUILD_UNIT_TESTS) test/ReshapeTest.cpp test/ResizeTest.cpp test/ResizeTestHelper.hpp + test/ReverseV2Test.cpp + test/ReverseV2TestHelper.hpp test/RoundTest.cpp test/RoundTestHelper.hpp test/SoftmaxTest.cpp diff --git a/delegate/classic/CMakeLists.txt b/delegate/classic/CMakeLists.txt index 54e00e1c9f..8f872d6adc 100644 --- a/delegate/classic/CMakeLists.txt +++ b/delegate/classic/CMakeLists.txt @@ -34,6 +34,7 @@ list(APPEND armnnClassicDelegateObject_sources src/Redefine.hpp src/Reduce.hpp src/Resize.hpp + src/ReverseV2.hpp src/Round.hpp src/Shape.hpp src/SharedFunctions.hpp diff --git a/delegate/classic/src/ReverseV2.hpp b/delegate/classic/src/ReverseV2.hpp new file mode 100644 index 0000000000..d49d20b5c1 --- /dev/null +++ b/delegate/classic/src/ReverseV2.hpp @@ -0,0 +1,154 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include + +namespace armnnDelegate +{ + + + +TfLiteStatus ValidateReverseV2Operator(DelegateData& delegateData, + TfLiteContext* tfLiteContext, + const armnn::TensorInfo& inputInfo0, + const armnn::TensorInfo& inputInfo1, + const armnn::TensorInfo& outputInfo) +{ + bool isSupported = false; + FORWARD_LAYER_SUPPORT_FUNC("REVERSEV2", + tfLiteContext, + IsReverseV2Supported, + delegateData.m_Backends, + isSupported, + armnn::BackendId(), + inputInfo0, + inputInfo1, + outputInfo); + + return isSupported ? kTfLiteOk : kTfLiteError; +} + +TfLiteStatus VisitReverseV2Operator(DelegateData& delegateData, + TfLiteContext* tfLiteContext, + TfLiteNode* tfLiteNode, + int nodeIndex, + int32_t reverseV2OperatorCode) +{ + TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 2, nodeIndex)); + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + + const TfLiteTensor* tfLiteTensors = tfLiteContext->tensors; + + // The first input contains the data that should be reversed + 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: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + // The second input contains an axis tensor. + const TfLiteTensor& tfLiteAxisTensor = tfLiteTensors[tfLiteNode->inputs->data[1]]; + if (IsDynamicTensor(tfLiteAxisTensor)) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: Dynamic input tensors are not supported in operator #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + // Get the output tensor + 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: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo0 = GetTensorInfoForTfLiteTensor(tfLiteInputTensor); + const armnn::TensorInfo& inputTensorInfo1 = GetTensorInfoForTfLiteTensor(tfLiteAxisTensor); + const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteOutputTensor, true); + + if (inputTensorInfo0.GetNumDimensions() != outputTensorInfo.GetNumDimensions()) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: input tensor dimension and output tensor dimension differ #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + for (unsigned i=0; i < inputTensorInfo0.GetNumDimensions(); i++) + { + if (inputTensorInfo0.GetShape()[i] != outputTensorInfo.GetShape()[i]) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: input tensor dimension and output tensor differ #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + } + + std::string layerName("ReverseV2"); + + const auto maxDimension = 4; + + const auto axisTensorNumValues = static_cast(tfLiteAxisTensor.dims->size); + if (axisTensorNumValues > maxDimension) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: The Axis-Input-Tensor of the ReverseV2 operation requires a " + "dimension of <= %d but a tensor with a dimension of %d was given. " + "Operator: #%d node #%d: ", + maxDimension, axisTensorNumValues, reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + // No network pointer indicates that only support for this operator should be checked + if (!delegateData.m_Network) + { + return ValidateReverseV2Operator(delegateData, + tfLiteContext, + inputTensorInfo0, + inputTensorInfo1, + outputTensorInfo); + } + + armnn::IConnectableLayer* reverseV2Layer = delegateData.m_Network->AddReverseV2Layer(layerName.c_str()); + + armnn::IOutputSlot& outputSlot = reverseV2Layer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + + // Try to connect the Constant Inputs if there are any + if(ProcessInputs(reverseV2Layer, delegateData, tfLiteContext, tfLiteNode) != kTfLiteOk ) + { + return kTfLiteError; + } + + ARMNN_ASSERT(reverseV2Layer != nullptr); + + return Connect(reverseV2Layer, tfLiteNode, delegateData); +} + +} // namespace armnnDelegate diff --git a/delegate/classic/src/armnn_delegate.cpp b/delegate/classic/src/armnn_delegate.cpp index e597d13fb2..0f9e8a624c 100644 --- a/delegate/classic/src/armnn_delegate.cpp +++ b/delegate/classic/src/armnn_delegate.cpp @@ -31,6 +31,7 @@ #include "Redefine.hpp" #include "Reduce.hpp" #include "Resize.hpp" +#include "ReverseV2.hpp" #include "Round.hpp" #include "Shape.hpp" #include "Slice.hpp" @@ -949,6 +950,12 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, tfLiteNode, nodeIndex, kTfLiteBuiltinResizeNearestNeighbor); + case kTfLiteBuiltinReverseV2: + return VisitReverseV2Operator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + kTfLiteBuiltinReverseV2); case kTfLiteBuiltinRsqrt: return VisitElementwiseUnaryOperator(delegateData, tfLiteContext, diff --git a/delegate/opaque/CMakeLists.txt b/delegate/opaque/CMakeLists.txt index 1e00709f01..787046d80c 100644 --- a/delegate/opaque/CMakeLists.txt +++ b/delegate/opaque/CMakeLists.txt @@ -31,6 +31,7 @@ list(APPEND armnnOpaqueDelegateObject_sources src/Redefine.hpp src/Reduce.hpp src/Resize.hpp + src/ReverseV2.hpp src/Round.hpp src/Shape.hpp src/SharedFunctions.cpp diff --git a/delegate/opaque/src/ReverseV2.hpp b/delegate/opaque/src/ReverseV2.hpp new file mode 100644 index 0000000000..e5714f4576 --- /dev/null +++ b/delegate/opaque/src/ReverseV2.hpp @@ -0,0 +1,174 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include + +namespace armnnOpaqueDelegate +{ + +TfLiteStatus ValidateReverseV2Operator(DelegateData& delegateData, + TfLiteOpaqueContext* tfLiteContext, + const armnn::TensorInfo& inputInfo0, + const armnn::TensorInfo& inputInfo1, + const armnn::TensorInfo& outputInfo) +{ + bool isSupported = false; + FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("REVERSEV2", + tfLiteContext, + IsReverseV2Supported, + delegateData.m_Backends, + isSupported, + armnn::BackendId(), + inputInfo0, + inputInfo1, + outputInfo); + + return isSupported ? kTfLiteOk : kTfLiteError; +} + +TfLiteStatus VisitReverseV2Operator(DelegateData& delegateData, + TfLiteOpaqueContext* tfLiteContext, + TfLiteOpaqueNode* tfLiteNode, + int nodeIndex, + int32_t reverseV2OperatorCode) +{ + TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 2, nodeIndex)); + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + + // Gather input indices and use to get input tensor. + auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode); + const int* inputTensors; + if (TfLiteOpaqueNodeInputs(tfLiteNode, &inputTensors, &numInputs) != kTfLiteOk) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: Unable to gather input tensor indices from node #%d: ", + nodeIndex); + return kTfLiteError; + } + + // The first input contains the data to be reversed + const TfLiteOpaqueTensor* tfLiteInputTensor = + TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[0]); + if (IsDynamicTensor(tfLiteInputTensor)) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: Dynamic input tensors are not supported in operator #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + // The second input contains the axis tensor + const TfLiteOpaqueTensor* tfLiteAxisTensor = + TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[1]); + if (IsDynamicTensor(tfLiteAxisTensor)) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: Dynamic input tensors are not supported in operator #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + // Gather output indices and use to get output tensors. + int numOutputs = 0; + const int* outputTensors; + if (TfLiteOpaqueNodeOutputs(tfLiteNode, &outputTensors, &numOutputs) != kTfLiteOk) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: Unable to gather output tensor indices from node #%d: ", + nodeIndex); + return kTfLiteError; + } + + // Get the output tensor + const TfLiteOpaqueTensor* tfLiteOutputTensor = + TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]); + if (IsDynamicTensor(tfLiteOutputTensor)) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: Dynamic output tensors are not supported in operator #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo0 = + GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor); + const armnn::TensorInfo& inputTensorInfo1 = + GetTensorInfoForTfLiteOpaqueTensor(tfLiteAxisTensor); + const armnn::TensorInfo& outputTensorInfo = + GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true); + + if (inputTensorInfo0.GetNumDimensions() != outputTensorInfo.GetNumDimensions()) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: input tensor dimension and output tensor dimension differ #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + for (unsigned i=0; i < inputTensorInfo0.GetNumDimensions(); i++) + { + if (inputTensorInfo0.GetShape()[i] != outputTensorInfo.GetShape()[i]) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: input tensor dimension and output tensor differ #%d node #%d: ", + reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + } + + std::string layerName("ReverseV2"); + + // Get axis tensor data + auto axisTensorNumValues = static_cast(TfLiteOpaqueTensorDim(tfLiteAxisTensor,0)); + + const auto maxDimension = 4; + + if (axisTensorNumValues > maxDimension) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: The Axis-Input-Tensor of the ReverseV2 operation requires a " + "dimension of <= %d but a tensor with a dimension of %d was given. " + "Operator: #%d node #%d: ", + maxDimension, axisTensorNumValues, reverseV2OperatorCode, nodeIndex); + return kTfLiteError; + } + + // No network pointer indicates that only support for this operator should be checked + if (!delegateData.m_Network) + { + return ValidateReverseV2Operator(delegateData, + tfLiteContext, + inputTensorInfo0, + inputTensorInfo1, + outputTensorInfo); + } + + armnn::IConnectableLayer* reverseV2Layer = delegateData.m_Network->AddReverseV2Layer(layerName.c_str()); + + armnn::IOutputSlot& outputSlot = reverseV2Layer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + + // try to connect the Constant Inputs if there are any + if(ProcessInputs(reverseV2Layer,delegateData, tfLiteContext, tfLiteNode) != kTfLiteOk ) + { + return kTfLiteError; + } + + ARMNN_ASSERT(reverseV2Layer != nullptr); + + return Connect(reverseV2Layer, tfLiteContext, tfLiteNode, delegateData); +} + +} // namespace armnnOpaqueDelegate diff --git a/delegate/opaque/src/armnn_delegate.cpp b/delegate/opaque/src/armnn_delegate.cpp index f32a6f43b8..510352eae9 100644 --- a/delegate/opaque/src/armnn_delegate.cpp +++ b/delegate/opaque/src/armnn_delegate.cpp @@ -30,6 +30,7 @@ #include "Redefine.hpp" #include "Reduce.hpp" #include "Resize.hpp" +#include "ReverseV2.hpp" #include "Round.hpp" #include "Shape.hpp" #include "Slice.hpp" @@ -1032,6 +1033,12 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, tfLiteNode, nodeIndex, kTfLiteBuiltinResizeBilinear); + case kTfLiteBuiltinReverseV2: + return VisitReverseV2Operator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + kTfLiteBuiltinReverseV2); case kTfLiteBuiltinRsqrt: return VisitElementwiseUnaryOperator(delegateData, tfLiteContext, diff --git a/delegate/test/ReverseV2Test.cpp b/delegate/test/ReverseV2Test.cpp new file mode 100644 index 0000000000..b261474e99 --- /dev/null +++ b/delegate/test/ReverseV2Test.cpp @@ -0,0 +1,183 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "ReverseV2TestHelper.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace armnnDelegate +{ + + void ReverseV2Float32Test(std::vector& backends) + { + // Set input data + std::vector inputValues = + { + 1.0f, 2.0f, 3.0f, + 4.0f, 5.0f, 6.0f, + 7.0f, 8.0f, 9.0f, + + 11.0f, 12.0f, 13.0f, + 14.0f, 15.0f, 16.0f, + 17.0f, 18.0f, 19.0f, + + 21.0f, 22.0f, 23.0f, + 24.0f, 25.0f, 26.0f, + 27.0f, 28.0f, 29.0f + }; + + // The output data + std::vector expectedOutputValues = + { + 3.0f, 2.0f, 1.0f, + 6.0f, 5.0f, 4.0f, + 9.0f, 8.0f, 7.0f, + + 13.0f, 12.0f, 11.0f, + 16.0f, 15.0f, 14.0f, + 19.0f, 18.0f, 17.0f, + + 23.0f, 22.0f, 21.0f, + 26.0f, 25.0f, 24.0f, + 29.0f, 28.0f, 27.0f + }; + + // The axis to reverse + const std::vector axisValues = {2}; + + // Shapes + const std::vector inputShape = {3, 3, 3}; + const std::vector axisShapeDims = {1}; + const std::vector expectedOutputShape = {3, 3, 3}; + + ReverseV2FP32TestImpl(tflite::BuiltinOperator_REVERSE_V2, + backends, + inputValues, + inputShape, + axisValues, + axisShapeDims, + expectedOutputValues, + expectedOutputShape); + } + + void ReverseV2NegativeAxisFloat32Test(std::vector& backends) + { + // Set input data + std::vector inputValues = + { + 1.0f, 2.0f, 3.0f, + 4.0f, 5.0f, 6.0f, + 7.0f, 8.0f, 9.0f, + + 11.0f, 12.0f, 13.0f, + 14.0f, 15.0f, 16.0f, + 17.0f, 18.0f, 19.0f, + + 21.0f, 22.0f, 23.0f, + 24.0f, 25.0f, 26.0f, + 27.0f, 28.0f, 29.0f + }; + + // The output data + std::vector expectedOutputValues = + { + 7.0f, 8.0f, 9.0f, + 4.0f, 5.0f, 6.0f, + 1.0f, 2.0f, 3.0f, + + 17.0f, 18.0f, 19.0f, + 14.0f, 15.0f, 16.0f, + 11.0f, 12.0f, 13.0f, + + 27.0f, 28.0f, 29.0f, + 24.0f, 25.0f, 26.0f, + 21.0f, 22.0f, 23.0f + }; + + // The axis to reverse + const std::vector axisValues = {-2}; + + // Shapes + const std::vector inputShape = {3, 3, 3}; + const std::vector axisShapeDims = {1}; + const std::vector expectedOutputShape = {3, 3, 3}; + + ReverseV2FP32TestImpl(tflite::BuiltinOperator_REVERSE_V2, + backends, + inputValues, + inputShape, + axisValues, + axisShapeDims, + expectedOutputValues, + expectedOutputShape); + } + +#if defined(REVERSEV2_GPUACC) + TEST_SUITE("ReverseV2Tests_GpuAccTests") + { + + TEST_CASE ("ReverseV2_Float32_GpuAcc_Test") + { + std::vector backends = { armnn::Compute::GpuAcc }; + ReverseV2Float32Test(backends); + } + + TEST_CASE ("ReverseV2_NegativeAxis_Float32_GpuAcc_Test") + { + std::vector backends = { armnn::Compute::GpuAcc }; + ReverseV2NegativeAxisFloat32Test(backends); + } + + } // TEST_SUITE("ReverseV2Tests_GpuAccTests") +#endif + + +#if defined(REVERSEV2_CPUACC) + TEST_SUITE("ReverseV2Tests_CpuAccTests") + { + + TEST_CASE ("ReverseV2_Float32_CpuAcc_Test") + { + std::vector backends = { armnn::Compute::CpuAcc }; + ReverseV2Float32Test(backends); + } + + TEST_CASE ("ReverseV2_NegativeAxis_Float32_CpuAcc_Test") + { + std::vector backends = { armnn::Compute::CpuAcc }; + ReverseV2NegativeAxisFloat32Test(backends); + } + + } // TEST_SUITE("ReverseV2Tests_CpuAccTests") +#endif + + + TEST_SUITE("ReverseV2Tests_CpuRefTests") + { + + TEST_CASE ("ReverseV2_Float32_CpuRef_Test") + { + std::vector backends = { armnn::Compute::CpuRef }; + ReverseV2Float32Test(backends); + } + + TEST_CASE ("ReverseV2_NegativeAxis_Float32_CpuRef_Test") + { + std::vector backends = { armnn::Compute::CpuRef }; + ReverseV2NegativeAxisFloat32Test(backends); + } + + } // TEST_SUITE("ReverseV2Tests_CpuRefTests") + +} // namespace armnnDelegate diff --git a/delegate/test/ReverseV2TestHelper.hpp b/delegate/test/ReverseV2TestHelper.hpp new file mode 100644 index 0000000000..8c4acd1a7e --- /dev/null +++ b/delegate/test/ReverseV2TestHelper.hpp @@ -0,0 +1,148 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "TestUtils.hpp" + +#include +#include + +#include +#include +#include + +#include + +#include + +namespace +{ + std::vector CreateReverseV2TfLiteModel(tflite::BuiltinOperator operatorCode, + tflite::TensorType inputTensorType, + const std::vector & inputTensorShape, + const std::vector & axisTensorData, + const std::vector & axisTensorShape, + const std::vector & outputTensorShape) + { + using namespace tflite; + flatbuffers::FlatBufferBuilder flatBufferBuilder; + + std::vector> buffers; + buffers.push_back(CreateBuffer(flatBufferBuilder)); + buffers.push_back(CreateBuffer(flatBufferBuilder)); + buffers.push_back(CreateBuffer(flatBufferBuilder, + flatBufferBuilder.CreateVector( + reinterpret_cast(axisTensorData.data()), + sizeof(int32_t) * axisTensorData.size()))); + buffers.push_back(CreateBuffer(flatBufferBuilder)); + + std::array, 3> tensors; + tensors[0] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(inputTensorShape.data(), + inputTensorShape.size()), + inputTensorType, + 1, + flatBufferBuilder.CreateString("input_tensor")); + + tensors[1] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(axisTensorShape.data(), + axisTensorShape.size()), + TensorType_INT32, + 2, + flatBufferBuilder.CreateString("axis_input_tensor")); + + tensors[2] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(outputTensorShape.data(), + outputTensorShape.size()), + inputTensorType, + 3, + flatBufferBuilder.CreateString("output_tensor")); + + // Create Operator + tflite::BuiltinOptions operatorBuiltinOptionsType = tflite::BuiltinOptions_NONE; + flatbuffers::Offset operatorBuiltinOption = 0; + + const std::vector operatorInputs{0, 1}; + const std::vector operatorOutputs{2}; + flatbuffers::Offset reverseV2Operator = + CreateOperator(flatBufferBuilder, + 0, + flatBufferBuilder.CreateVector(operatorInputs.data(), operatorInputs.size()), + flatBufferBuilder.CreateVector(operatorOutputs.data(), operatorOutputs.size()), + operatorBuiltinOptionsType, + operatorBuiltinOption); + + const std::vector subgraphInputs{0, 1}; + const std::vector subgraphOutputs{2}; + 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(&reverseV2Operator, 1)); + + flatbuffers::Offset modelDescription = + flatBufferBuilder.CreateString("ArmnnDelegate: ReverseV2 Operator Model"); + flatbuffers::Offset opCode = CreateOperatorCode(flatBufferBuilder, operatorCode); + + flatbuffers::Offset flatbufferModel = + CreateModel(flatBufferBuilder, + TFLITE_SCHEMA_VERSION, + flatBufferBuilder.CreateVector(&opCode, 1), + flatBufferBuilder.CreateVector(&subgraph, 1), + modelDescription, + flatBufferBuilder.CreateVector(buffers.data(), buffers.size())); + + flatBufferBuilder.Finish(flatbufferModel, armnnDelegate::FILE_IDENTIFIER); + + return std::vector(flatBufferBuilder.GetBufferPointer(), + flatBufferBuilder.GetBufferPointer() + flatBufferBuilder.GetSize()); + } + + void ReverseV2FP32TestImpl(tflite::BuiltinOperator operatorCode, + std::vector& backends, + std::vector& inputValues, + std::vector inputShape, + std::vector axisValues, + std::vector axisShapeDims, + std::vector& expectedOutputValues, + std::vector expectedOutputShape) + { + using namespace delegateTestInterpreter; + + std::vector modelBuffer = CreateReverseV2TfLiteModel(operatorCode, + ::tflite::TensorType_FLOAT32, + inputShape, + axisValues, + axisShapeDims, + expectedOutputShape); + + // Setup interpreter with just TFLite Runtime. + auto tfLiteInterpreter = DelegateTestInterpreter(modelBuffer); + CHECK(tfLiteInterpreter.AllocateTensors() == kTfLiteOk); + CHECK(tfLiteInterpreter.FillInputTensor(inputValues, 0) == kTfLiteOk); + CHECK(tfLiteInterpreter.FillInputTensor(axisValues, 1) == kTfLiteOk); + CHECK(tfLiteInterpreter.Invoke() == kTfLiteOk); + std::vector tfLiteOutputValues = tfLiteInterpreter.GetOutputResult(0); + std::vector tfLiteOutputShape = tfLiteInterpreter.GetOutputShape(0); + + // Setup interpreter with Arm NN Delegate applied. + auto armnnInterpreter = DelegateTestInterpreter(modelBuffer, backends); + CHECK(armnnInterpreter.AllocateTensors() == kTfLiteOk); + CHECK(armnnInterpreter.FillInputTensor(inputValues, 0) == kTfLiteOk); + CHECK(armnnInterpreter.FillInputTensor(axisValues, 1) == kTfLiteOk); + CHECK(armnnInterpreter.Invoke() == kTfLiteOk); + std::vector armnnOutputValues = armnnInterpreter.GetOutputResult(0); + std::vector armnnOutputShape = armnnInterpreter.GetOutputShape(0); + + armnnDelegate::CompareOutputData(tfLiteOutputValues, armnnOutputValues, expectedOutputValues); + armnnDelegate::CompareOutputShape(tfLiteOutputShape, armnnOutputShape, expectedOutputShape); + + tfLiteInterpreter.Cleanup(); + armnnInterpreter.Cleanup(); + } + +} // anonymous namespace diff --git a/docs/05_03_delegate.dox b/docs/05_03_delegate.dox index 78bc3ea0b8..632afa0cf0 100644 --- a/docs/05_03_delegate.dox +++ b/docs/05_03_delegate.dox @@ -167,6 +167,8 @@ The Arm NN SDK TensorFlow Lite delegate currently supports the following operato - RESIZE_NEAREST_NEIGHBOR +- REVERSEV2 + - RSQRT - SHAPE -- cgit v1.2.1