From 36d94ef824c516f27fad8f17a96e2123565e6f9f Mon Sep 17 00:00:00 2001 From: Francis Murtagh Date: Fri, 28 Apr 2023 14:05:43 +0100 Subject: IVGCVSW-7602 IVGCVSW-7602 Implement opaque delegate for Quantize + Dequantize operator Signed-off-by: Francis Murtagh Change-Id: I318cb25f526dfe0f7aa6afcf77971afd8d437209 --- delegate/CMakeLists.txt | 2 + delegate/opaque/src/Quantization.hpp | 191 +++++++++++++++++++++++++++++++++ delegate/opaque/src/armnn_delegate.cpp | 12 +++ 3 files changed, 205 insertions(+) diff --git a/delegate/CMakeLists.txt b/delegate/CMakeLists.txt index 081b3f32a0..b0145ca919 100644 --- a/delegate/CMakeLists.txt +++ b/delegate/CMakeLists.txt @@ -304,6 +304,8 @@ if(BUILD_UNIT_TESTS) test/PreluTestHelper.hpp test/RoundTest.cpp test/RoundTestHelper.hpp + test/QuantizationTest.cpp + test/QuantizationTestHelper.hpp test/ShapeTest.cpp test/ShapeTestHelper.hpp test/TestUtils.hpp diff --git a/delegate/opaque/src/Quantization.hpp b/delegate/opaque/src/Quantization.hpp index e16969768e..7a1dd6fd17 100644 --- a/delegate/opaque/src/Quantization.hpp +++ b/delegate/opaque/src/Quantization.hpp @@ -2,3 +2,194 @@ // Copyright © 2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // +#pragma once + +#include + +namespace armnnOpaqueDelegate +{ + +TfLiteStatus VisitDequantizeOperator(DelegateData& delegateData, + TfLiteOpaqueContext* tfLiteContext, + TfLiteOpaqueNode* tfLiteNode, + int nodeIndex, + int32_t operatorCode) +{ + TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + + // Gather input indices and use to get input tensor. + const int* inputTensors; + auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode); + 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; + } + + const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[0]); + if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, 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; + } + + const TfLiteOpaqueTensor* tfLiteOutputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]); + if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex)) + { + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor); + armnn::TensorInfo outputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true); + + UpdateConstantTensorOutputs(inputTensorInfo, outputTensorInfo); + + bool isSupported = false; + armnn::BackendId setBackend; + auto validateFunc = [&](const armnn::TensorInfo& outputTensorInfo, bool& isSupported) + { + FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("DEQUANTIZE", + tfLiteContext, + IsDequantizeSupported, + delegateData.m_Backends, + isSupported, + setBackend, + inputTensorInfo, + outputTensorInfo); + }; + + if (!delegateData.m_Network) + { + validateFunc(outputTensorInfo, isSupported); + return isSupported ? kTfLiteOk : kTfLiteError; + } + + armnn::IConnectableLayer* dequantizeLayer = delegateData.m_Network->AddDequantizeLayer(); + dequantizeLayer->SetBackendId(setBackend); + ARMNN_ASSERT(dequantizeLayer != nullptr); + + armnn::IOutputSlot& outputSlot = dequantizeLayer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + + auto inputsTensorsProcess = ProcessInputs(dequantizeLayer, + delegateData, + tfLiteContext, + tfLiteNode); + if (inputsTensorsProcess == kTfLiteError) + { + return inputsTensorsProcess; + } + + return Connect(dequantizeLayer, tfLiteContext, tfLiteNode, delegateData); +} + +TfLiteStatus VisitQuantizeOperator(DelegateData& delegateData, + TfLiteOpaqueContext* tfLiteContext, + TfLiteOpaqueNode* tfLiteNode, + int nodeIndex, + int32_t operatorCode) +{ + TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex)); + + // Gather input indices and use to get input tensor. + const int* inputTensors; + auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode); + 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; + } + + const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[0]); + if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, 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; + } + + const TfLiteOpaqueTensor* tfLiteOutputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]); + if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex)) + { + return kTfLiteError; + } + + // Only affine per-layer quantization is supported. + if (!IsAffineQuantization(*tfLiteOutputTensor)) + { + TF_LITE_OPAQUE_MAYBE_KERNEL_LOG( + tfLiteContext, + "TfLiteArmnnOpaqueDelegate: Only affine per-layer quantization is supported in operator #%d node #%d: ", + operatorCode, nodeIndex); + return kTfLiteError; + } + + const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor); + const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true); + + bool isSupported = false; + armnn::BackendId setBackend; + auto validateFunc = [&](const armnn::TensorInfo& outputTensorInfo, bool& isSupported) + { + FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("QUANTIZE", + tfLiteContext, + IsQuantizeSupported, + delegateData.m_Backends, + isSupported, + setBackend, + inputTensorInfo, + outputTensorInfo); + }; + + if (!delegateData.m_Network) + { + validateFunc(outputTensorInfo, isSupported); + return isSupported ? kTfLiteOk : kTfLiteError; + } + + armnn::IConnectableLayer* quantizeLayer = delegateData.m_Network->AddQuantizeLayer(); + quantizeLayer->SetBackendId(setBackend); + ARMNN_ASSERT(quantizeLayer != nullptr); + + armnn::IOutputSlot& outputSlot = quantizeLayer->GetOutputSlot(0); + outputSlot.SetTensorInfo(outputTensorInfo); + + // try to connect the Constant Inputs if there are any + if(ProcessInputs(quantizeLayer,delegateData, tfLiteContext, tfLiteNode) != kTfLiteOk ) + { + return kTfLiteError; + } + + return Connect(quantizeLayer, tfLiteContext, tfLiteNode, delegateData); +} + +} // namespace armnnOpaqueDelegate \ No newline at end of file diff --git a/delegate/opaque/src/armnn_delegate.cpp b/delegate/opaque/src/armnn_delegate.cpp index b38b528317..8c3ddfaeea 100644 --- a/delegate/opaque/src/armnn_delegate.cpp +++ b/delegate/opaque/src/armnn_delegate.cpp @@ -726,6 +726,12 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, tfLiteNode, nodeIndex, kTfLiteBuiltinDepthwiseConv2d); + case kTfLiteBuiltinDequantize: + return VisitDequantizeOperator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + kTfLiteBuiltinDequantize); case kTfLiteBuiltinDiv: return VisitElementwiseBinaryOperator(delegateData, tfLiteContext, @@ -942,6 +948,12 @@ TfLiteStatus ArmnnSubgraph::VisitNode(DelegateData& delegateData, tfLiteNode, nodeIndex, kTfLiteBuiltinPrelu); + case kTfLiteBuiltinQuantize: + return VisitQuantizeOperator(delegateData, + tfLiteContext, + tfLiteNode, + nodeIndex, + kTfLiteBuiltinQuantize); case kTfLiteBuiltinRelu: return VisitActivationOperator(delegateData, tfLiteContext, -- cgit v1.2.1