From 0176fd81b3f6a82ddc89e016cb634010f5397425 Mon Sep 17 00:00:00 2001 From: Keith Davis Date: Tue, 1 Jun 2021 17:36:32 +0100 Subject: MLCE-510 Add CpuRef Shape Operator to ArmNN * Add TfLiteParser and delegate support Signed-off-by: Keith Davis Change-Id: Id3219ba7cc7128b5e73de2c7d8d076a40dcce9c5 --- CMakeLists.txt | 1 + delegate/CMakeLists.txt | 3 + delegate/src/Shape.hpp | 86 +++++++++++++++++ delegate/src/armnn_delegate.cpp | 7 ++ delegate/src/test/ShapeTest.cpp | 45 +++++++++ delegate/src/test/ShapeTestHelper.hpp | 171 +++++++++++++++++++++++++++++++++ src/armnnTfLiteParser/TfLiteParser.cpp | 36 +++++++ src/armnnTfLiteParser/TfLiteParser.hpp | 1 + src/armnnTfLiteParser/test/Shape.cpp | 84 ++++++++++++++++ 9 files changed, 434 insertions(+) create mode 100644 delegate/src/Shape.hpp create mode 100644 delegate/src/test/ShapeTest.cpp create mode 100644 delegate/src/test/ShapeTestHelper.hpp create mode 100644 src/armnnTfLiteParser/test/Shape.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 054e4d7096..a6b0367c11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -666,6 +666,7 @@ if(BUILD_UNIT_TESTS) src/armnnTfLiteParser/test/ResizeNearestNeighbor.cpp src/armnnTfLiteParser/test/Softmax.cpp src/armnnTfLiteParser/test/SpaceToBatchND.cpp + src/armnnTfLiteParser/test/Shape.cpp src/armnnTfLiteParser/test/Slice.cpp src/armnnTfLiteParser/test/Split.cpp src/armnnTfLiteParser/test/SplitV.cpp diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt index c7ac4390c5..b43feb7f9c 100644 --- a/delegate/CMakeLists.txt +++ b/delegate/CMakeLists.txt @@ -42,6 +42,7 @@ list(APPEND armnnDelegate_sources src/Reduce.hpp src/Resize.hpp src/Round.hpp + src/Shape.hpp src/Slice.hpp src/Softmax.hpp src/SpaceDepth.hpp @@ -170,6 +171,8 @@ if(BUILD_UNIT_TESTS) src/test/SoftmaxTestHelper.hpp src/test/SpaceDepthTest.cpp src/test/SpaceDepthTestHelper.hpp + src/test/ShapeTest.cpp + src/test/ShapeTestHelper.hpp src/test/SliceTest.cpp src/test/SliceTestHelper.hpp src/test/SplitTest.cpp diff --git a/delegate/src/Shape.hpp b/delegate/src/Shape.hpp new file mode 100644 index 0000000000..b173299a62 --- /dev/null +++ b/delegate/src/Shape.hpp @@ -0,0 +1,86 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "DelegateUtils.hpp" + +#include +#include +#include +#include +#include + +namespace armnnDelegate +{ + +TfLiteStatus VisitShapeOperator(DelegateData& delegateData, + TfLiteContext* tfLiteContext, + TfLiteNode* tfLiteNode, + int nodeIndex, + int32_t operatorCode) +{ + TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + + const TfLiteTensor* tfLiteTensors = tfLiteContext->tensors; + const TfLiteTensor& tfLiteInputTensor = tfLiteTensors[tfLiteNode->inputs->data[0]]; + if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, nodeIndex)) + { + return kTfLiteError; + } + + const TfLiteTensor& tfLiteOutputTensor = tfLiteTensors[tfLiteNode->outputs->data[0]]; + if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex)) + { + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteInputTensor); + const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteTensor(tfLiteOutputTensor); + + auto* shapeParameters = reinterpret_cast(tfLiteNode->builtin_data); + if ( shapeParameters->out_type != kTfLiteInt32 && shapeParameters->out_type != kTfLiteInt64 ) + { + TF_LITE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnDelegate: output_type data type is not supported in operator #%d node #%d: ", + operatorCode, nodeIndex); + return kTfLiteError; + } + + bool isSupported = false; + auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported) + { + FORWARD_LAYER_SUPPORT_FUNC(__func__, + tfLiteContext, + IsShapeSupported, + delegateData.m_Backends, + isSupported, + inputTensorInfo, + outInfo); + }; + + // If the m_Network is a nullptr, this signals that a prerequisite TfLite callback is required to clarify the + // support for the operator + // If supported, VisitShapeOperator will be called again to add the layer to the network as seen further below + if (!delegateData.m_Network) + { + validateFunc(outputTensorInfo, isSupported); + return isSupported ? kTfLiteOk : kTfLiteError; + } + + // Add a Shape layer + armnn::IConnectableLayer* layer = delegateData.m_Network->AddShapeLayer(); + ARMNN_ASSERT(layer != nullptr); + + armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + + // Connect + return Connect(layer, tfLiteNode, delegateData); +} + +} // namespace armnnDelegate diff --git a/delegate/src/armnn_delegate.cpp b/delegate/src/armnn_delegate.cpp index 0c984ecc82..0ac33808b0 100644 --- a/delegate/src/armnn_delegate.cpp +++ b/delegate/src/armnn_delegate.cpp @@ -30,6 +30,7 @@ #include "Reduce.hpp" #include "Resize.hpp" #include "Round.hpp" +#include "Shape.hpp" #include "Slice.hpp" #include "Softmax.hpp" #include "SpaceDepth.hpp" @@ -805,6 +806,12 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, tfLiteNode, nodeIndex, armnn::UnaryOperation::Rsqrt); + case kTfLiteBuiltinShape: + return VisitShapeOperator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + kTfLiteBuiltinShape); case kTfLiteBuiltinSplit: return VisitSplitOperator(delegateData, tfLiteContext, diff --git a/delegate/src/test/ShapeTest.cpp b/delegate/src/test/ShapeTest.cpp new file mode 100644 index 0000000000..b49910adf6 --- /dev/null +++ b/delegate/src/test/ShapeTest.cpp @@ -0,0 +1,45 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "ShapeTestHelper.hpp" + +#include + +namespace armnnDelegate +{ + +void ShapeSimpleTest(std::vector& backends) +{ + std::vector inputShape{ 1, 3, 2, 3 }; + + std::vector inputValues{ 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, }; + + std::vector expectedOutputShape{ 4 }; + std::vector expectedOutputValues{ 1, 3, 2, 3 }; + + ShapeTest(::tflite::TensorType_INT32, + ::tflite::TensorType_INT32, + backends, + inputShape, + inputValues, + expectedOutputValues, + expectedOutputShape); +} + +// SHAPE Test Suite +TEST_SUITE("SHAPE_CpuRefTests") +{ + +TEST_CASE("SHAPE_Simple_CpuRef_Test") +{ + std::vector backends = { armnn::Compute::CpuRef }; + ShapeSimpleTest(backends); +} + +} +// End of SHAPE Test Suite + +} // namespace armnnDelegate \ No newline at end of file diff --git a/delegate/src/test/ShapeTestHelper.hpp b/delegate/src/test/ShapeTestHelper.hpp new file mode 100644 index 0000000000..854c5084aa --- /dev/null +++ b/delegate/src/test/ShapeTestHelper.hpp @@ -0,0 +1,171 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "TestUtils.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +std::vector CreateShapeTfLiteModel(tflite::TensorType inputTensorType, + tflite::TensorType outputTensorType, + const std::vector& inputTensorShape, + const std::vector& outputTensorShape, + 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 })); + + std::array, 2> tensors; + tensors[0] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(inputTensorShape.data(), + inputTensorShape.size()), + inputTensorType, + 0, + flatBufferBuilder.CreateString("input"), + quantizationParameters); + tensors[1] = CreateTensor(flatBufferBuilder, + flatBufferBuilder.CreateVector(outputTensorShape.data(), + outputTensorShape.size()), + outputTensorType, + 0, + flatBufferBuilder.CreateString("output"), + quantizationParameters); + + const std::vector operatorInputs({ 0 }); + const std::vector operatorOutputs({ 1 }); + + flatbuffers::Offset shapeOperator = + CreateOperator(flatBufferBuilder, + 0, + flatBufferBuilder.CreateVector(operatorInputs.data(), + operatorInputs.size()), + flatBufferBuilder.CreateVector(operatorOutputs.data(), + operatorOutputs.size()), + BuiltinOptions_ShapeOptions, + CreateShapeOptions(flatBufferBuilder, outputTensorType).Union()); + + flatbuffers::Offset modelDescription = + flatBufferBuilder.CreateString("ArmnnDelegate: SHAPE Operator Model"); + + flatbuffers::Offset operatorCode = + CreateOperatorCode(flatBufferBuilder, tflite::BuiltinOperator_SHAPE); + + 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(&shapeOperator, 1)); + + 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 ShapeTest(tflite::TensorType inputTensorType, + tflite::TensorType outputTensorType, + std::vector& backends, + std::vector& inputShape, + std::vector& inputValues, + std::vector& expectedOutputValues, + std::vector& expectedOutputShape, + float quantScale = 1.0f, + int quantOffset = 0) +{ + using namespace tflite; + std::vector modelBuffer = CreateShapeTfLiteModel(inputTensorType, + outputTensorType, + inputShape, + expectedOutputShape, + quantScale, + quantOffset); + + const Model* tfLiteModel = GetModel(modelBuffer.data()); + + // Create TfLite Interpreters + std::unique_ptr armnnDelegate; + + CHECK(InterpreterBuilder(tfLiteModel, ::tflite::ops::builtin::BuiltinOpResolver()) + (&armnnDelegate) == kTfLiteOk); + CHECK(armnnDelegate != nullptr); + CHECK(armnnDelegate->AllocateTensors() == kTfLiteOk); + + std::unique_ptr tfLiteDelegate; + + CHECK(InterpreterBuilder(tfLiteModel, ::tflite::ops::builtin::BuiltinOpResolver()) + (&tfLiteDelegate) == kTfLiteOk); + CHECK(tfLiteDelegate != nullptr); + CHECK(tfLiteDelegate->AllocateTensors() == kTfLiteOk); + + // Create the ArmNN Delegate + armnnDelegate::DelegateOptions delegateOptions(backends); + + std::unique_ptr < TfLiteDelegate, decltype(&armnnDelegate::TfLiteArmnnDelegateDelete) > + theArmnnDelegate(armnnDelegate::TfLiteArmnnDelegateCreate(delegateOptions), + armnnDelegate::TfLiteArmnnDelegateDelete); + + CHECK(theArmnnDelegate != nullptr); + + // Modify armnnDelegateInterpreter to use armnnDelegate + CHECK(armnnDelegate->ModifyGraphWithDelegate(theArmnnDelegate.get()) == kTfLiteOk); + + // Set input data + armnnDelegate::FillInput(tfLiteDelegate, 0, inputValues); + armnnDelegate::FillInput(armnnDelegate, 0, inputValues); + + // Run EnqueWorkload + CHECK(tfLiteDelegate->Invoke() == kTfLiteOk); + CHECK(armnnDelegate->Invoke() == kTfLiteOk); + + // Compare output data + armnnDelegate::CompareOutputData(tfLiteDelegate, + armnnDelegate, + expectedOutputShape, + expectedOutputValues, + 0); + + tfLiteDelegate.reset(nullptr); + armnnDelegate.reset(nullptr); +} + +} // anonymous namespace diff --git a/src/armnnTfLiteParser/TfLiteParser.cpp b/src/armnnTfLiteParser/TfLiteParser.cpp index 26c44a9f35..f38f45fcdf 100644 --- a/src/armnnTfLiteParser/TfLiteParser.cpp +++ b/src/armnnTfLiteParser/TfLiteParser.cpp @@ -648,6 +648,7 @@ TfLiteParserImpl::TfLiteParserImpl(const OptionalAddShapeLayer(layerName.c_str()); + ARMNN_ASSERT(layer != nullptr); + + + TensorInfo outputTensorInfo = ToTensorInfo(outputs[0], true); + layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo); + + // Check if output tensor type is Signed32 or Signed64 + if (outputTensorInfo.GetDataType() != armnn::DataType::Signed32 && + outputTensorInfo.GetDataType() != armnn::DataType::Signed64) + { + throw ParseException( + fmt::format( + "Output tensor data type is not supported. (Supported types: Signed32 & Signed64) {}", + CHECK_LOCATION().AsString())); + } + + auto inputTensorIndexes = AsUnsignedVector(GetInputTensorIds(m_Model, subgraphIndex, operatorIndex)); + RegisterInputSlots(subgraphIndex, operatorIndex, layer, {inputTensorIndexes[0]}); + + auto outputTensorIndexes = AsUnsignedVector(GetOutputTensorIds(m_Model, subgraphIndex, operatorIndex)); + RegisterOutputSlots(subgraphIndex, operatorIndex, layer, outputTensorIndexes); +} + void TfLiteParserImpl::ParseSqueeze(size_t subgraphIndex, size_t operatorIndex) { CHECK_MODEL(m_Model, subgraphIndex, operatorIndex); diff --git a/src/armnnTfLiteParser/TfLiteParser.hpp b/src/armnnTfLiteParser/TfLiteParser.hpp index da2ae126da..836c4e8f51 100644 --- a/src/armnnTfLiteParser/TfLiteParser.hpp +++ b/src/armnnTfLiteParser/TfLiteParser.hpp @@ -144,6 +144,7 @@ private: void ParseResizeBilinear(size_t subgraphIndex, size_t operatorIndex); void ParseResizeNearestNeighbor(size_t subgraphIndex, size_t operatorIndex); void ParseRsqrt(size_t subgraphIndex, size_t operatorIndex); + void ParseShape(size_t subgraphIndex, size_t operatorIndex); void ParseSlice(size_t subgraphIndex, size_t operatorIndex); void ParseSoftmax(size_t subgraphIndex, size_t operatorIndex); void ParseSpaceToBatchND(size_t subgraphIndex, size_t operatorIndex); diff --git a/src/armnnTfLiteParser/test/Shape.cpp b/src/armnnTfLiteParser/test/Shape.cpp new file mode 100644 index 0000000000..c82bc4ef57 --- /dev/null +++ b/src/armnnTfLiteParser/test/Shape.cpp @@ -0,0 +1,84 @@ +// +// Copyright © 2021 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "ParserFlatbuffersFixture.hpp" +#include "../TfLiteParser.hpp" + +TEST_SUITE("TensorflowLiteParser_Shape") +{ +struct ShapeFixture : public ParserFlatbuffersFixture +{ + explicit ShapeFixture(const std::string& inputShape, + const std::string& outputShape, + const std::string& inputDataType, + const std::string& outputDataType) + { + m_JsonString = R"( + { + "version": 3, + "operator_codes": [ { "builtin_code": "SHAPE" } ], + "subgraphs": [ { + "tensors": [ + { + "shape": )" + inputShape + R"(, + "type": )" + inputDataType + R"(, + "buffer": 0, + "name": "inputTensor", + "quantization": { + "min": [ 0.0 ], + "max": [ 255.0 ], + "scale": [ 1.0 ], + "zero_point": [ 0 ], + } + }, + { + "shape": )" + outputShape + R"(, + "type": )" + outputDataType + R"(, + "buffer": 1, + "name": "outputTensor", + "quantization": { + "min": [ 0.0 ], + "max": [ 255.0 ], + "scale": [ 1.0 ], + "zero_point": [ 0 ], + } + } + ], + "inputs": [ 0 ], + "outputs": [ 1 ], + "operators": [ + { + "opcode_index": 0, + "inputs": [ 0 ], + "outputs": [ 1 ], + "custom_options_format": "FLEXBUFFERS" + } + ], + } ], + "buffers" : [ {}, {} ] + } + )"; + SetupSingleInputSingleOutput("inputTensor", "outputTensor"); + } +}; + + +struct SimpleShapeFixture : ShapeFixture +{ + SimpleShapeFixture() : ShapeFixture("[ 1, 3, 3, 1 ]", + "[ 4 ]", + "INT32", + "INT32") {} +}; + +TEST_CASE_FIXTURE(SimpleShapeFixture, "SimpleShapeFixture") +{ + RunTest<1, armnn::DataType::Signed32>( + 0, + {{"inputTensor", { 1, 1, 1, 1, 1, 1, 1, 1, 1 }}}, + {{"outputTensor",{ 1, 3, 3, 1 }}}); +} + +} \ No newline at end of file -- cgit v1.2.1