blob: a636161300f1d0a32ee420b317e876c2a37d0b16 [file] [log] [blame]
//
// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//
#pragma once
#include <armnn_delegate.hpp>
#include <DelegateUtils.hpp>
#include <armnn/ArmNN.hpp>
#include <armnn/BackendHelper.hpp>
#include <armnn/TypesUtils.hpp>
#include <armnn/utility/Assert.hpp>
#include <armnn/utility/NumericCast.hpp>
#include <armnnUtils/Permute.hpp>
#include <armnnUtils/TensorUtils.hpp>
#include <tensorflow/lite/builtin_ops.h>
#include <tensorflow/lite/c/builtin_op_data.h>
#include <tensorflow/lite/c/common.h>
#include <tensorflow/lite/c/c_api_opaque.h>
#include <tensorflow/lite/minimal_logging.h>
#include <tensorflow/lite/kernels/kernel_util.h>
#include <fmt/format.h>
namespace
{
std::string GetName(armnn::ActivationFunction function, int nodeIndex)
{
return fmt::format("{}:{}", GetActivationFunctionAsCString(function), nodeIndex);
}
std::string GetName(armnn::ArgMinMaxFunction function, int nodeIndex)
{
return fmt::format("{}:{}", GetArgMinMaxFunctionAsCString(function), nodeIndex);
}
std::string GetName(armnn::BinaryOperation opType, int nodeIndex)
{
return fmt::format("{}:{}", GetBinaryOperationAsCString(opType), nodeIndex);
}
std::string GetName(armnn::ComparisonOperation layerType, int nodeIndex)
{
return fmt::format("{}:{}", GetComparisonOperationAsCString(layerType), nodeIndex);
}
std::string GetName(armnn::LogicalBinaryOperation operation, int nodeIndex)
{
return fmt::format("{}:{}", GetLogicalBinaryOperationAsCString(operation), nodeIndex);
}
std::string GetName(armnn::UnaryOperation opType, int nodeIndex)
{
return fmt::format("{}:{}", GetUnaryOperationAsCString(opType), nodeIndex);
}
std::string GetName(armnn::LayerType layerType, int nodeIndex, std::string subname = "")
{
return fmt::format("{}{}:{}", GetLayerTypeAsCString(layerType), subname, nodeIndex);
}
// Macro to call an Is<layer_name>Supported function and log caller name together with reason for lack of support
#define FORWARD_LAYER_OPAQUE_SUPPORT_FUNC(opName, tfLiteContext, func, backends, supported, setBackend, ...) \
try \
{ \
for (auto&& backendId : backends) \
{ \
auto layerSupportObject = armnn::GetILayerSupportByBackendId(backendId); \
if (layerSupportObject.IsBackendRegistered()) \
{ \
std::string reasonIfUnsupported; \
supported = \
layerSupportObject.func(__VA_ARGS__, armnn::Optional<std::string&>(reasonIfUnsupported)); \
if (supported) \
{ \
setBackend = backendId; \
break; \
} \
else \
{ \
if (reasonIfUnsupported.size() > 0) \
{ \
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING, \
"%s: not supported by armnn: %s", opName, reasonIfUnsupported.c_str()); \
} \
else \
{ \
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING, \
"%s: not supported by armnn", opName); \
} \
} \
} \
else \
{ \
TF_LITE_OPAQUE_KERNEL_LOG(tfLiteContext, "%s: backend not registered: %s", \
opName, backendId.Get().c_str()); \
} \
} \
if (!supported) \
{ \
TF_LITE_OPAQUE_KERNEL_LOG(tfLiteContext, "%s: not supported by any specified backend", opName); \
} \
} \
catch (const armnn::InvalidArgumentException &e) \
{ \
throw armnn::InvalidArgumentException(e, "Failed to check layer support", CHECK_LOCATION()); \
}
TfLiteStatus ValidateNumInputs(TfLiteOpaqueContext* tfLiteContext,
TfLiteOpaqueNode* tfLiteNode,
const unsigned int expectedSize,
int nodeIndex)
{
int numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode);
if (static_cast<unsigned int>(numInputs) != expectedSize)
{
TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unexpected number of inputs (%d != %d) in node #%d",
numInputs, expectedSize, nodeIndex);
return kTfLiteError;
}
return kTfLiteOk;
}
TfLiteStatus ValidateNumOutputs(TfLiteOpaqueContext* tfLiteContext,
TfLiteOpaqueNode* tfLiteNode,
const unsigned int expectedSize,
int nodeIndex)
{
auto numOutputs = TfLiteOpaqueNodeNumberOfOutputs(tfLiteNode);
if (static_cast<unsigned int>(numOutputs) != expectedSize)
{
TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unexpected number of outputs (%d != %d) in node #%d",
numOutputs, expectedSize, nodeIndex);
return kTfLiteError;
}
return kTfLiteOk;
}
bool IsConstantTensor(const TfLiteOpaqueTensor* tfLiteTensor)
{
auto tensorAllocationType = TfLiteOpaqueTensorGetAllocationType(tfLiteTensor);
if (tensorAllocationType == kTfLiteMmapRo)
{
return true;
}
return false;
}
bool IsDynamicTensor(const TfLiteOpaqueTensor* tfLiteTensor)
{
auto tensorAllocationType = TfLiteOpaqueTensorGetAllocationType(tfLiteTensor);
if (tensorAllocationType == kTfLiteDynamic)
{
return true;
}
return false;
}
bool IsValid(const TfLiteOpaqueTensor* tfLiteTensor)
{
return tfLiteTensor == nullptr ? false : true;
}
bool IsValid(TfLiteOpaqueContext* tfLiteContext,
const TfLiteOpaqueTensor* tfLiteTensor,
int32_t operatorCode,
int32_t nodeIndex)
{
if(!IsValid(tfLiteTensor))
{
TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
tfLiteContext,
"TfLiteArmnnOpaqueDelegate: Invalid TfLite tensor in operator #%d node #%d: ",
operatorCode, nodeIndex);
return false;
}
if (IsDynamicTensor(tfLiteTensor))
{
TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
tfLiteContext,
"TfLiteArmnnOpaqueDelegate: Dynamic tensors are not supported in operator #%d node #%d: ",
operatorCode, nodeIndex);
return false;
}
return true;
}
bool IsAffineQuantization(const TfLiteOpaqueTensor& tfLiteTensor)
{
auto quantizationInfo = TfLiteOpaqueTensorGetQuantization(&tfLiteTensor);
if (quantizationInfo.type == kTfLiteAffineQuantization)
{
return true;
}
return false;
}
// Connects the layer to the graph
TfLiteStatus Connect(armnn::IConnectableLayer* layer,
TfLiteOpaqueContext* tfLiteContext,
TfLiteOpaqueNode* tfLiteNode,
armnnOpaqueDelegate::DelegateData& data)
{
// Get array of input indices, inputIndexArray is set from the TfLiteOpaqueNodeInputs function
// This function turns inputIndexArray into an int array of indices. These indices point to the index of the
// tensors for each input slot in the node.
const int* inputIndexArray;
int numInputs;
if(TfLiteOpaqueNodeInputs(tfLiteNode, &inputIndexArray, &numInputs) != kTfLiteOk)
{
return kTfLiteError;
}
// We can't validate the number of inputs vs the layer->GetNumOutputSlots() as some operators differ.
// An example is Mean where the number of TFLite inputs is 2, but number of Arm NN inputs is 1,
// as we store the axis within the descriptor.
// Connect the input slots.
// For each input slot, get the index of the opaque tensor that was allocated for it.
for (unsigned int inputIndex = 0; inputIndex < layer->GetNumInputSlots(); ++inputIndex)
{
if (data.m_OutputSlotForNode[inputIndexArray[inputIndex]] != nullptr)
{
data.m_OutputSlotForNode[inputIndexArray[inputIndex]]->Connect(layer->GetInputSlot(inputIndex));
}
}
// Get array of output indices, outputIndexArray is set from the TfLiteOpaqueNodeOutputs function
// This function turns outputIndexArray into an int array of indices. These indices point to the tensors for
// each output slot in the node.
const int* outputIndexArray;
int numOutputs;
if(TfLiteOpaqueNodeOutputs(tfLiteNode, &outputIndexArray, &numOutputs) != kTfLiteOk)
{
return kTfLiteError;
}
// numOutputs is set from TfLiteOpaqueNodeOutputs.
if(numOutputs != static_cast<int>(layer->GetNumOutputSlots()))
{
ARMNN_LOG(error) << "Layer: " << layer->GetName() << ": Expected number of output slots does not match actual "
"number of output slots.";
return kTfLiteError;
}
// Prepare output slots
for (unsigned int outputIndex = 0; outputIndex < layer->GetNumOutputSlots(); ++outputIndex)
{
armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(outputIndex);
data.m_OutputSlotForNode[static_cast<unsigned long>(outputIndexArray[outputIndex])] = &outputSlot;
}
return kTfLiteOk;
}
TfLiteStatus FusedActivation(TfLiteOpaqueContext* tfLiteContext,
TfLiteOpaqueNode* tfLiteNode,
TfLiteFusedActivation activationType,
armnn::IConnectableLayer* prevLayer,
unsigned int outputSlotIndex,
armnnOpaqueDelegate::DelegateData& data,
int nodeIndex)
{
const armnn::TensorInfo& activationOutputInfo = prevLayer->GetOutputSlot(outputSlotIndex).GetTensorInfo();
armnn::ActivationDescriptor activationDesc;
switch (activationType)
{
case kTfLiteActNone:
{
// No Activation
return kTfLiteOk;
}
case kTfLiteActRelu:
{
activationDesc.m_Function = armnn::ActivationFunction::ReLu;
break;
}
case kTfLiteActReluN1To1:
{
activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
activationDesc.m_A = 1.0f;
activationDesc.m_B = -1.0f;
break;
}
case kTfLiteActRelu6:
{
activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
activationDesc.m_A = 6.0f;
activationDesc.m_B = 0.0f;
break;
}
case kTfLiteActSigmoid:
{
activationDesc.m_Function = armnn::ActivationFunction::Sigmoid;
break;
}
case kTfLiteActTanh:
{
activationDesc.m_Function = armnn::ActivationFunction::TanH;
activationDesc.m_A = 1.0f;
activationDesc.m_B = 1.0f;
break;
}
default:
return kTfLiteError;
}
bool isSupported = false;
armnn::BackendId setBackend;
FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("ACTIVATION",
tfLiteContext,
IsActivationSupported,
data.m_Backends,
isSupported,
setBackend,
activationOutputInfo,
activationOutputInfo,
activationDesc);
if (!isSupported)
{
return kTfLiteError;
}
auto layerName = GetName(activationDesc.m_Function, nodeIndex);
armnn::IConnectableLayer* activationLayer = data.m_Network->AddActivationLayer(activationDesc, layerName.c_str());
activationLayer->SetBackendId(setBackend);
ARMNN_ASSERT(activationLayer != nullptr);
activationLayer->GetOutputSlot(0).SetTensorInfo(activationOutputInfo);
// Get array of output indices, outputIndexArray is set from the TfLiteOpaqueNodeOutputs function
// This function turns outputIndexArray into an int array of indices. These indices point to the tensors for
// each output slot in the node.
const int* outputIndexArray;
int numOutputs;
TfLiteStatus outputStatus = TfLiteOpaqueNodeOutputs(tfLiteNode, &outputIndexArray, &numOutputs);
if(outputStatus != kTfLiteOk)
{
return kTfLiteError;
}
// Connect and prepare output slots
for (unsigned int outputIndex = 0; outputIndex < activationLayer->GetNumOutputSlots(); ++outputIndex)
{
data.m_OutputSlotForNode[static_cast<unsigned long>(
outputIndexArray[outputIndex])]->Connect(activationLayer->GetInputSlot(0));
armnn::IOutputSlot& outputSlot = activationLayer->GetOutputSlot(outputIndex);
data.m_OutputSlotForNode[static_cast<unsigned long>(outputIndexArray[outputIndex])] = &outputSlot;
}
return kTfLiteOk;
}
armnn::IConnectableLayer* AddReshapeLayer(TfLiteOpaqueContext* tfLiteContext,
TfLiteOpaqueNode* tfLiteNode,
armnn::IConnectableLayer* prevLayer,
armnn::TensorInfo reshapedOutputTensorInfo,
armnn::TensorInfo outputTensorInfo,
armnnOpaqueDelegate::DelegateData& data,
int nodeIndex)
{
armnn::ReshapeDescriptor desc;
desc.m_TargetShape = outputTensorInfo.GetShape();
bool isSupported = false;
armnn::BackendId setBackend;
FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("RESHAPE",
tfLiteContext,
IsReshapeSupported,
data.m_Backends,
isSupported,
setBackend,
reshapedOutputTensorInfo,
outputTensorInfo,
desc);
if (!isSupported)
{
return nullptr;
}
auto layerName = GetName(armnn::LayerType::Reshape, nodeIndex);
armnn::IConnectableLayer* reshapeLayer = data.m_Network->AddReshapeLayer(desc, layerName.c_str());
reshapeLayer->SetBackendId(setBackend);
ARMNN_ASSERT(reshapeLayer != nullptr);
prevLayer->GetOutputSlot(0).SetTensorInfo(reshapedOutputTensorInfo);
reshapeLayer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
// Gather array of indices and it's length, replaces node->outputs->data[i]
const int* outputIndices = nullptr;
int numOutputs = 0;
TfLiteStatus status = TfLiteOpaqueNodeOutputs(tfLiteNode, &outputIndices, &numOutputs);
if(status != kTfLiteOk)
{
throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Unable to gather output information from node.");
}
if (static_cast<unsigned int>(numOutputs) != reshapeLayer->GetNumOutputSlots())
{
throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Unexpected number of outputs (" +
std::to_string(numOutputs) +
"!= " +
std::to_string(reshapeLayer->GetNumOutputSlots()) +
") in node.");
}
// Connect and prepare output slots
for (unsigned int outputIndex = 0; outputIndex < reshapeLayer->GetNumOutputSlots(); ++outputIndex)
{
data.m_OutputSlotForNode[static_cast<unsigned long>(
outputIndices[outputIndex])]->Connect(reshapeLayer->GetInputSlot(0));
armnn::IOutputSlot& outputSlot = reshapeLayer->GetOutputSlot(outputIndex);
data.m_OutputSlotForNode[static_cast<unsigned long>(outputIndices[outputIndex])] = &outputSlot;
}
return reshapeLayer;
}
armnn::DataType GetDataType(const TfLiteOpaqueTensor* tfLiteTensor)
{
switch (TfLiteOpaqueTensorType(tfLiteTensor))
{
case kTfLiteBool:
return armnn::DataType::Boolean;
case kTfLiteFloat32:
return armnn::DataType::Float32;
case kTfLiteFloat16:
return armnn::DataType::Float16;
case kTfLiteUInt8:
return armnn::DataType::QAsymmU8;
case kTfLiteInt8:
{
auto quantizationInfo = TfLiteOpaqueTensorGetQuantization(tfLiteTensor);
if (quantizationInfo.type == kTfLiteAffineQuantization)
{
auto* quantization =
reinterpret_cast<TfLiteAffineQuantization*>(quantizationInfo.params);
if (quantization->zero_point != nullptr && quantization->zero_point->size == 1)
{
return armnn::DataType::QAsymmS8;
}
else
{
return armnn::DataType::QSymmS8;
}
}
else
{
return armnn::DataType::QAsymmS8;
}
}
case kTfLiteInt16:
return armnn::DataType::QSymmS16;
case kTfLiteInt32:
return armnn::DataType::Signed32;
case kTfLiteInt64:
return armnn::DataType::Signed64;
default:
throw armnn::Exception(
&"TfLiteArmnnOpaqueDelegate: Unsupported data type: " [ TfLiteOpaqueTensorType(tfLiteTensor) ]);
}
}
armnn::TensorInfo GetTensorInfoForTfLiteOpaqueTensor(const TfLiteOpaqueTensor* tfLiteTensor, bool isOutput = false)
{
armnn::DataType type = GetDataType(tfLiteTensor);
armnn::TensorInfo ret;
auto tensorDimensionSize = TfLiteOpaqueTensorNumDims(tfLiteTensor);
if (tensorDimensionSize == 0)
{
// If input tensor does not have a shape
// assuming that it has 1D tensor
if (!isOutput)
{
std::vector<unsigned int> safeShape = { 1 };
bool dimensionsSpecificity[1] = { true };
armnn::TensorShape tensorShape(safeShape.size(),
safeShape.data(),
dimensionsSpecificity);
ret = armnn::TensorInfo(tensorShape, type);
if(IsConstantTensor(tfLiteTensor))
{
ret.SetConstant(true);
}
}
else
{
armnn::TensorShape tensorShape(armnn::Dimensionality::NotSpecified);
ret = armnn::TensorInfo(tensorShape, type);
}
}
else
{
std::vector<unsigned int> tensorDims(tensorDimensionSize);
std::vector<unsigned char> dimensionsSpecificity(tensorDimensionSize, true);
for (int32_t i = 0; i < tensorDimensionSize; ++i)
{
int32_t dim = TfLiteOpaqueTensorDim(tfLiteTensor, i);
if (dim <= 0)
{
dimensionsSpecificity[i] = false;
}
tensorDims[i] = static_cast<unsigned int>(dim);
}
armnn::TensorShape tensorShape(tensorDimensionSize,
tensorDims.data(),
reinterpret_cast<const bool *>(dimensionsSpecificity.data()));
if (IsConstantTensor(tfLiteTensor))
{
ret = armnn::TensorInfo(tensorShape, type);
ret.SetConstant(true);
}
else
{
ret = armnn::TensorInfo(tensorShape, type);
}
}
auto quantizationInfo = TfLiteOpaqueTensorGetQuantization(tfLiteTensor);
if (quantizationInfo.type == kTfLiteAffineQuantization)
{
// get per-channel quantization parameters
const auto* affineQuantization =
reinterpret_cast<TfLiteAffineQuantization*>(quantizationInfo.params);
if (affineQuantization->scale->size > 1)
{
std::vector<float> quantizationScales;
for (unsigned int i = 0; i < static_cast<unsigned int>(affineQuantization->scale->size); ++i)
{
quantizationScales.push_back(affineQuantization->scale->data[i]);
}
ret.SetQuantizationScales(quantizationScales);
ret.SetQuantizationDim(armnn::numeric_cast<unsigned int>(affineQuantization->quantized_dimension));
}
else
{
ret.SetQuantizationScale(affineQuantization->scale->data[0]);
ret.SetQuantizationOffset(affineQuantization->zero_point->data[0]);
}
}
return ret;
}
armnn::ConstTensor CreateConstTensor(const TfLiteOpaqueTensor* tfLiteTensor,
const armnn::TensorInfo& tensorInfo)
{
auto allocType = TfLiteOpaqueTensorGetAllocationType(tfLiteTensor);
if (allocType != kTfLiteMmapRo)
{
throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Not constant allocation type: " + std::to_string(allocType));
}
return armnn::ConstTensor(tensorInfo, TfLiteOpaqueTensorData(tfLiteTensor));
}
armnn::ConstTensor* GetConstTensorForTfLiteTensor(const TfLiteOpaqueContext* tfLiteContext,
TfLiteOpaqueNode* tfLiteNode,
int index)
{
const TfLiteOpaqueTensor* tfLiteTensor = TfLiteOpaqueNodeGetInput(tfLiteContext, tfLiteNode, index);
armnn::TensorInfo tensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteTensor);
return new armnn::ConstTensor(tensorInfo, TfLiteOpaqueTensorData(tfLiteTensor));
}
bool IsOptionalOperandPresent(TfLiteOpaqueNode* tfLiteNode, const int operandIndex)
{
// Get array of input indices, inputIndexArray is set from the TfLiteOpaqueNodeInputs function
// This function turns inputIndexArray into an int array of indices. These indices point to the index of the
// tensors for each input slot in the node.
const int* inputIndexArray;
int numInputs = 0;
TfLiteStatus status = TfLiteOpaqueNodeInputs(tfLiteNode, &inputIndexArray, &numInputs);
if(status != kTfLiteOk)
{
throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Unable to gather input information from node.");
}
// If the inputs array has fewer than operandIndex entries or if the entry at operandIndex has a value of -1 or
// less then the input is not present.
if (numInputs > operandIndex && inputIndexArray[operandIndex] >= 0)
{
return true;
}
return false;
}
TfLiteStatus ProcessInputs(armnn::IConnectableLayer* layer,
armnnOpaqueDelegate::DelegateData& delegateData,
TfLiteOpaqueContext* tfLiteContext,
TfLiteOpaqueNode* tfLiteNode,
int nodeIndex)
{
// Get array of input indices, inputIndexArray is set from the TfLiteOpaqueNodeInputs function
// This function turns inputIndexArray into an int array of indices. These indices point to the index of the
// tensors for each input slot in the node.
const int* inputIndexArray;
int numInputs = 0;
TfLiteStatus status = TfLiteOpaqueNodeInputs(tfLiteNode, &inputIndexArray, &numInputs);
if(status != kTfLiteOk)
{
throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Unable to gather input information from node.");
}
// Process input tensors
// If input tensor is a Constant tensor create a constant layer and connect it to the network
for (int32_t inputIndex = 0; inputIndex < static_cast<int32_t>(layer->GetNumInputSlots()); ++inputIndex)
{
const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueNodeGetInput(tfLiteContext, tfLiteNode, inputIndex);
if (IsConstantTensor(tfLiteInputTensor))
{
armnn::TensorInfo inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
bool isSupported = false;
armnn::BackendId setBackend;
FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("CONSTANT",
tfLiteContext,
IsConstantSupported,
delegateData.m_Backends,
isSupported,
setBackend,
inputTensorInfo);
if (!isSupported)
{
return kTfLiteError;
}
auto constantInput = CreateConstTensor(tfLiteInputTensor, inputTensorInfo);
auto layerName = GetName(armnn::LayerType::Constant, nodeIndex);
armnn::IConnectableLayer* constantLayer = delegateData.m_Network->AddConstantLayer(constantInput,
layerName.c_str());
constantLayer->SetBackendId(setBackend);
armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
outputSlot.SetTensorInfo(inputTensorInfo);
delegateData.m_OutputSlotForNode[inputIndexArray[inputIndex]] = &outputSlot;
}
}
return kTfLiteOk;
}
} // namespace anonymous