blob: 91a8e3080ca8b0ca8331659f61a27628dd6982a0 [file] [log] [blame]
//
// Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//
#pragma once
#include "CanonicalUtils.hpp"
#include <armnn/ArmNN.hpp>
#include <armnn/BackendHelper.hpp>
#include <armnn/utility/Assert.hpp>
#include <armnn/utility/IgnoreUnused.hpp>
#include <armnn/utility/NumericCast.hpp>
#include <armnnUtils/DataLayoutIndexed.hpp>
#include <armnnUtils/Transpose.hpp>
#include <ActivationFunctor.h>
#include <CpuExecutor.h>
#include <OperationsUtils.h>
#include <armnnUtils/FloatingPointComparison.hpp>
#include <log/log.h>
#include <vector>
inline const android::nn::Model::Subgraph& getMainModel(const android::nn::Model& model) { return model.main; }
namespace armnn_driver
{
///
/// Helper classes
///
#include <nnapi/OperandTypes.h>
#include <nnapi/Result.h>
#include <nnapi/TypeUtils.h>
#include <nnapi/Types.h>
#include <nnapi/Validation.h>
using Model = ::android::nn::Model;
using Operand = ::android::nn::Operand;
using OperandLifeTime = ::android::nn::Operand::LifeTime;
using OperandType = ::android::nn::OperandType;
using Operation = ::android::nn::Operation;
using OperationType = ::android::nn::OperationType;
using ErrorStatus = ::android::nn::ErrorStatus;
struct ConversionData
{
ConversionData(const std::vector<armnn::BackendId>& backends)
: m_Backends(backends)
, m_Network(nullptr, nullptr)
, m_DynamicInputsEncountered(false)
{}
const std::vector<armnn::BackendId> m_Backends;
armnn::INetworkPtr m_Network;
std::vector<armnn::IOutputSlot*> m_OutputSlotForOperand;
std::vector<::android::nn::RunTimePoolInfo> m_MemPools;
bool m_DynamicInputsEncountered;
};
class LayerInputHandle
{
public:
LayerInputHandle();
LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo);
bool IsValid() const;
void Connect(armnn::IInputSlot& inputSlot);
void Disconnect(armnn::IInputSlot& inputSlot);
const armnn::TensorInfo& GetTensorInfo() const;
void SanitizeQuantizationScale(LayerInputHandle& weight, LayerInputHandle& input);
armnn::IOutputSlot* GetOutputSlot() const;
private:
armnn::IOutputSlot* m_OutputSlot;
bool m_Valid;
armnn::TensorInfo m_TensorInfo;
};
class ConstTensorPin
{
public:
// Creates an invalid tensor pin (can be used to signal errors)
// The optional flag can be set to indicate the tensor values were missing, but it was otherwise valid
ConstTensorPin(bool optional = false);
// @param tensorInfo TensorInfo associated with the tensor.
// @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with
// the model being converted.
// @param numBytes Number of bytes for the tensor data.
ConstTensorPin(armnn::TensorInfo& tensorInfo, const void* valueStart, uint32_t numBytes,
const armnn::PermutationVector& mappings);
ConstTensorPin(const ConstTensorPin& other) = delete;
ConstTensorPin(ConstTensorPin&& other) = default;
bool IsValid() const;
bool IsOptional() const;
const armnn::ConstTensor& GetConstTensor() const;
const armnn::ConstTensor* GetConstTensorPtr() const;
private:
armnn::ConstTensor m_ConstTensor;
// Owned memory for swizzled tensor data, only required if the tensor needed
// swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of
// the pools associated with the model being converted.
std::vector<uint8_t> m_SwizzledTensorData;
// optional flag to indicate that an invalid tensor pin is not an error, but the optional values were not given
bool m_Optional;
};
enum class ConversionResult
{
Success,
ErrorMappingPools,
UnsupportedFeature
};
} // namespace armnn_driver
///
/// Utility functions
///
namespace
{
using namespace armnn_driver;
// Convenience function to log the reason for failing to convert a model.
// @return Always returns false (so that it can be used by callers as a quick way to signal an error and return)
template<class... Args>
static bool Fail(const char* formatStr, Args&&... args)
{
ALOGD(formatStr, std::forward<Args>(args)...);
return false;
}
// Convenience macro to call an Is*Supported function and log caller name together with reason for lack of support.
// Called as: FORWARD_LAYER_SUPPORT_FUNC(__func__, Is*Supported, backends, a, b, c, d, e)
#define FORWARD_LAYER_SUPPORT_FUNC(funcName, 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) \
{ \
VLOG(DRIVER) << funcName << ": not supported by armnn: " << reasonIfUnsupported.c_str(); \
} \
else \
{ \
VLOG(DRIVER) << funcName << ": not supported by armnn"; \
} \
} \
} \
else \
{ \
VLOG(DRIVER) << funcName << ": backend not registered: " << backendId.Get().c_str(); \
} \
} \
if (!supported) \
{ \
VLOG(DRIVER) << funcName << ": not supported by any specified backend"; \
} \
} \
catch (const armnn::InvalidArgumentException &e) \
{ \
throw armnn::InvalidArgumentException(e, "Failed to check layer support", CHECK_LOCATION()); \
}
inline armnn::TensorShape GetTensorShapeForOperand(const Operand& operand)
{
return armnn::TensorShape(operand.dimensions.size(), operand.dimensions.data());
}
// Support within the 1.3 driver for specific tensor data types
inline bool IsOperandTypeSupportedForTensors(OperandType type)
{
return type == OperandType::BOOL ||
type == OperandType::TENSOR_BOOL8 ||
type == OperandType::TENSOR_FLOAT16 ||
type == OperandType::TENSOR_FLOAT32 ||
type == OperandType::TENSOR_QUANT8_ASYMM ||
type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
type == OperandType::TENSOR_QUANT8_SYMM ||
type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL ||
type == OperandType::TENSOR_QUANT16_SYMM ||
type == OperandType::TENSOR_INT32;
}
inline bool IsBool(Operand operand)
{
return operand.type == OperandType::BOOL;
}
inline bool Is12OrLaterOperand(Operand)
{
return true;
}
template<typename LayerHandleType>
armnn::IConnectableLayer& AddReshapeLayer(armnn::INetwork& network,
LayerHandleType& inputLayer,
armnn::TensorInfo reshapeInfo)
{
armnn::ReshapeDescriptor reshapeDescriptor;
reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape();
armnn::IConnectableLayer* reshapeLayer = network.AddReshapeLayer(reshapeDescriptor);
ARMNN_ASSERT(reshapeLayer != nullptr);
// Attach the input layer to the reshape layer
inputLayer.Connect(reshapeLayer->GetInputSlot(0));
reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapeInfo);
return *reshapeLayer;
}
armnn::TensorShape FlattenFullyConnectedInput(const armnn::TensorShape& inputShape,
const armnn::TensorShape& weightsShape)
{
if (inputShape.GetNumDimensions() > 2U)
{
unsigned int totalInputElements = inputShape.GetNumElements();
unsigned int inputSize = weightsShape[1];
unsigned int batchSize = totalInputElements / inputSize;
if(totalInputElements % batchSize != 0)
{
throw std::runtime_error("Failed to deduce tensor shape");
}
return armnn::TensorShape({batchSize, inputSize});
}
else
{
return inputShape;
}
}
inline bool VerifyFullyConnectedShapes(const armnn::TensorShape& inputShape,
const armnn::TensorShape& weightsShape,
const armnn::TensorShape& outputShape,
bool transposeWeightMatrix)
{
unsigned int dimIdx = transposeWeightMatrix ? 0 : 1;
return (inputShape[0] == outputShape[0] && weightsShape[dimIdx] == outputShape[1]);
}
bool BroadcastTensor(LayerInputHandle& input0,
LayerInputHandle& input1,
armnn::IConnectableLayer* startLayer,
ConversionData& data)
{
ARMNN_ASSERT(startLayer != nullptr);
const armnn::TensorInfo& inputInfo0 = input0.GetTensorInfo();
const armnn::TensorInfo& inputInfo1 = input1.GetTensorInfo();
unsigned int inputDimensions0 = inputInfo0.GetNumDimensions();
unsigned int inputDimensions1 = inputInfo1.GetNumDimensions();
if (inputDimensions0 == inputDimensions1)
{
// The inputs have the same number of dimensions, simply connect them to the given layer as they are
input0.Connect(startLayer->GetInputSlot(0));
input1.Connect(startLayer->GetInputSlot(1));
return true;
}
// Since the number of dimensions do not match then we need to add degenerate dimensions
// to the "smaller" tensor using a reshape, while keeping the order of the inputs.
unsigned int maxInputDimensions = std::max(inputDimensions0, inputDimensions1);
unsigned int sizeDifference = std::abs(armnn::numeric_cast<int>(inputDimensions0) -
armnn::numeric_cast<int>(inputDimensions1));
bool input0IsSmaller = inputDimensions0 < inputDimensions1;
LayerInputHandle& smallInputHandle = input0IsSmaller ? input0 : input1;
const armnn::TensorInfo& smallInfo = smallInputHandle.GetTensorInfo();
const armnn::TensorShape& smallShape = smallInfo.GetShape();
std::vector<unsigned int> reshapedDimensions(maxInputDimensions, 1);
for (unsigned int i = sizeDifference; i < maxInputDimensions; i++)
{
reshapedDimensions[i] = smallShape[i - sizeDifference];
}
armnn::TensorInfo reshapedInfo = smallInfo;
reshapedInfo.SetShape(armnn::TensorShape{ armnn::numeric_cast<unsigned int>(reshapedDimensions.size()),
reshapedDimensions.data() });
// RehsapeDescriptor that is ignored in the IsReshapeSupported function
armnn::ReshapeDescriptor reshapeDescriptor;
bool isSupported = false;
armnn::BackendId setBackend;
FORWARD_LAYER_SUPPORT_FUNC(__func__,
IsReshapeSupported,
data.m_Backends,
isSupported,
setBackend,
smallInfo,
reshapedInfo,
reshapeDescriptor);
if (!isSupported)
{
return false;
}
ARMNN_ASSERT(data.m_Network != nullptr);
armnn::IConnectableLayer& reshapeLayer = AddReshapeLayer(*data.m_Network, smallInputHandle, reshapedInfo);
reshapeLayer.SetBackendId(setBackend);
if (input0IsSmaller)
{
// Input0 is the "smaller" tensor, connect the reshape layer as follows:
//
// Input0 Input1
// | |
// Reshape |
// \ /
// StartLayer
reshapeLayer.GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
input1.Connect(startLayer->GetInputSlot(1));
}
else
{
// Input1 is the "smaller" tensor, connect the reshape layer as follows:
//
// Input0 Input1
// | |
// | Reshape
// \ /
// StartLayer
input0.Connect(startLayer->GetInputSlot(0));
reshapeLayer.GetOutputSlot(0).Connect(startLayer->GetInputSlot(1));
}
return true;
}
void CalcPadding(uint32_t input,
uint32_t kernel,
uint32_t stride,
uint32_t& outPadHead,
uint32_t& outPadTail,
PaddingScheme scheme)
{
int32_t padHead;
int32_t padTail;
calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail);
outPadHead = armnn::numeric_cast<uint32_t>(padHead);
outPadTail = armnn::numeric_cast<uint32_t>(padTail);
}
void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t dilation, uint32_t& outPadHead,
uint32_t& outPadTail, ::android::nn::PaddingScheme scheme)
{
int32_t padHead;
int32_t padTail;
calculateExplicitPadding(input, stride, dilation, kernel, scheme, &padHead, &padTail);
outPadHead = armnn::numeric_cast<uint32_t>(padHead);
outPadTail = armnn::numeric_cast<uint32_t>(padTail);
}
inline void CalcPaddingTransposeConv(uint32_t output, uint32_t kernel, int32_t stride, int32_t& outPadHead,
int32_t& outPadTail, ::android::nn::PaddingScheme scheme)
{
calculateExplicitPaddingTransposeConv(output, stride, kernel, scheme, &outPadHead, &outPadTail);
}
Shape GetOperandShape(const Operand& operand)
{
Shape shape;
shape.type = OperandType(operand.type);
shape.dimensions = operand.dimensions;
shape.scale = operand.scale;
shape.offset = operand.zeroPoint;
return shape;
}
// ArmNN requires the bias scale to be equal to the product of the weight and input scales, which is also
// what AndroidNN requires. However for some of the AndroidNN tests the values don't exactly match so
// we accept some tolerance. We don't want ArmNN itself to accept these inconsistencies as it is up to the
// user (us, in this case) to ensure they match.
void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo,
const armnn::TensorInfo& weightInfo,
const armnn::TensorInfo& inputInfo)
{
if (weightInfo.HasPerAxisQuantization())
{
// NOTE: Bias scale is always set to 0 for per-axis quantization and
// it needs to be calculated: scale[i] = input_scale * weight_scale[i]
auto UpdateBiasScaleValue = [&inputInfo](float biasScale) -> float
{
return biasScale * inputInfo.GetQuantizationScale();
};
std::vector<float> biasScales(weightInfo.GetQuantizationScales());
std::transform(biasScales.begin(), biasScales.end(), biasScales.begin(), UpdateBiasScaleValue);
biasInfo.SetQuantizationScales(biasScales);
// bias is expected to be a 1d tensor, set qdim=0
biasInfo.SetQuantizationDim(0);
VLOG(DRIVER) << "Bias quantization params have been updated for per-axis quantization";
}
else
{
const float expectedBiasScale = weightInfo.GetQuantizationScale() * inputInfo.GetQuantizationScale();
if (biasInfo.GetQuantizationScale() != expectedBiasScale)
{
if (armnnUtils::within_percentage_tolerance(biasInfo.GetQuantizationScale(), expectedBiasScale, 1.0f))
{
VLOG(DRIVER) << "Bias quantization scale has been modified to match input * weights";
biasInfo.SetQuantizationScale(expectedBiasScale);
}
}
}
}
// 4D Tensor Permutations
const armnn::PermutationVector IdentityPermutation4D({ 0U, 1U, 2U, 3U });
const armnn::PermutationVector IdentityPermutation3D({ 0U, 1U, 2U });
const armnn::PermutationVector SwapDim1And2({ 0U, 2U, 1U, 3U });
// 3D Permutation Vectors
const armnn::PermutationVector RotateTensorLeft({ 1U, 2U, 0U });
const armnn::PermutationVector RotateTensorRight({ 2U, 0U, 1U });
template<typename OSlot>
armnn::IConnectableLayer& AddTransposeLayer(armnn::INetwork& network, OSlot& input,
const armnn::PermutationVector& mappings)
{
// Add swizzle layer
armnn::IConnectableLayer* const layer = network.AddTransposeLayer(mappings);
ARMNN_ASSERT(layer != nullptr);
// Connect input to swizzle layer
input.Connect(layer->GetInputSlot(0));
// Setup swizzled output
const armnn::TensorInfo outInfo = armnnUtils::TransposeTensorShape(input.GetTensorInfo(), mappings);
layer->GetOutputSlot(0).SetTensorInfo(outInfo);
return *layer;
}
bool ValidateConcatOutputShape(const std::vector<armnn::TensorShape> & inputShapes,
const armnn::TensorShape & outputShape,
uint32_t concatDim)
{
// Validate the output shape is correct given the input shapes (which have just been validated)
unsigned int numDimensions = inputShapes[0].GetNumDimensions();
if (outputShape.GetNumDimensions() != numDimensions)
{
return Fail("%s: Output shape has wrong number of dimensions", __func__);
}
unsigned int outputSizeAlongConcatenatedDimension = 0;
for (unsigned int i = 0; i < inputShapes.size(); i++)
{
outputSizeAlongConcatenatedDimension += inputShapes[i][concatDim];
}
for (unsigned int i = 0; i < numDimensions; ++i)
{
if (i == concatDim)
{
if (outputShape[i] != outputSizeAlongConcatenatedDimension)
{
return Fail(
"%s: Invalid output shape for dimension %d (%d != %d)",
__func__,
i,
outputShape[i],
outputSizeAlongConcatenatedDimension);
}
}
else
{
if (outputShape[i] != inputShapes[0][i])
{
return Fail("%s: Invalid output shape", __func__);
}
}
}
return true;
}
inline bool RequiresReshape(armnn::TensorShape & inputShape)
{
return inputShape.GetNumDimensions() < 3;
}
inline void SwizzleInputs(armnn::INetwork& network,
std::vector<LayerInputHandle>& inputs,
std::vector<armnn::TensorShape>& inputShapes,
const armnn::PermutationVector& mapping,
std::vector<armnn::BackendId>& setBackends)
{
if (!mapping.IsEqual(IdentityPermutation4D))
{
size_t nInputs = inputs.size();
for (size_t i=0; i<nInputs; ++i)
{
// add swizzle layer
armnn::IConnectableLayer& swizzleLayer = AddTransposeLayer(network, inputs[i], mapping);
swizzleLayer.SetBackendId(setBackends[i]);
auto& outputSlot = swizzleLayer.GetOutputSlot(0);
auto& outputInfo = outputSlot.GetTensorInfo();
// replace inputs with the swizzled ones
inputs[i] = LayerInputHandle(true, &outputSlot, outputInfo);
inputShapes[i] = inputs[i].GetTensorInfo().GetShape();
}
}
}
bool TransposeInputTensors(ConversionData& data,
std::vector<LayerInputHandle>& inputs,
std::vector<armnn::TensorShape>& inputShapes,
const armnn::PermutationVector& mapping)
{
// If we have a IdentityPermutation4D or IdentityPermutation3D then we are not permuting
if (!mapping.IsEqual(IdentityPermutation4D) && !mapping.IsEqual(IdentityPermutation3D))
{
std::vector<armnn::BackendId> setBackendsVec;
armnn::TensorInfo outputTransposeInfo;
size_t nInputs = inputs.size();
for (size_t i=0; i<nInputs; ++i)
{
// check permute layer
armnn::TransposeDescriptor transposeDesc;
transposeDesc.m_DimMappings = mapping;
outputTransposeInfo = armnnUtils::TransposeTensorShape(inputs[i].GetTensorInfo(), mapping);
bool isSupported = false;
armnn::BackendId setBackend;
FORWARD_LAYER_SUPPORT_FUNC(__func__,
IsTransposeSupported,
data.m_Backends,
isSupported,
setBackend,
inputs[i].GetTensorInfo(),
outputTransposeInfo,
transposeDesc);
setBackendsVec.push_back(setBackend);
if (!isSupported)
{
return false;
}
}
SwizzleInputs(*data.m_Network, inputs, inputShapes, mapping, setBackendsVec);
}
return true;
}
bool CreateConcatPermutationParameters(const unsigned int numberOfDimensions,
int32_t & concatDimension,
std::pair<armnn::PermutationVector, armnn::PermutationVector> & permutationPair)
{
bool needPermute = false;
ARMNN_ASSERT(numberOfDimensions >= 3);
// ArmNN uses Compute Library subtensors to perform concatenation
// This only works when concatenating along dimension 0, 1 or 3 for a 4-D tensor,
// or along dimension 0 or 2 for a 3-D tensor.
if (numberOfDimensions == 4 && concatDimension == 2)
{
concatDimension = 1;
permutationPair = std::make_pair(SwapDim1And2, SwapDim1And2);
needPermute = true;
}
else if (numberOfDimensions == 3 && concatDimension == 1)
{
concatDimension = 0;
permutationPair = std::make_pair(RotateTensorLeft, RotateTensorRight);
needPermute = true;
}
// If the tensor is 3-D and the concat dimension is 2 then we don't need to permute but we do need to change the
// permutation identity to only have 3 dimensions
else if (numberOfDimensions == 3 && concatDimension == 2)
{
permutationPair = std::make_pair(IdentityPermutation3D, IdentityPermutation3D);
}
return needPermute;
}
} // anonymous namespace
namespace armnn_driver
{
using namespace android::nn;
//// Creates an ArmNN activation layer and connects it to the given layer, if the
//// passed in AndroidNN activation function requires so.
//// @return The end layer of the sequence of layers built for the given AndroidNN
//// activation function or nullptr if an error occurred (e.g. unsupported activation).
//// Note that the end layer matches the input layer if no activation is required
//// (the sequence of layers has length 1).
armnn::IConnectableLayer* ProcessActivation(const armnn::TensorInfo& tensorInfo,
ActivationFn activation,
armnn::IConnectableLayer* prevLayer,
ConversionData& data);
inline const Operand* GetInputOperand(const Operation& operation,
uint32_t inputIndex,
const Model& model,
bool failOnIndexOutOfBounds = true)
{
if (inputIndex >= operation.inputs.size())
{
if (failOnIndexOutOfBounds)
{
Fail("%s: invalid input index: %i out of %i", __func__, inputIndex, operation.inputs.size());
}
return nullptr;
}
// Model should have been validated beforehand
ARMNN_ASSERT(operation.inputs[inputIndex] < getMainModel(model).operands.size());
return &getMainModel(model).operands[operation.inputs[inputIndex]];
}
inline const Operand* GetOutputOperand(const Operation& operation,
uint32_t outputIndex,
const Model& model)
{
if (outputIndex >= operation.outputs.size())
{
Fail("%s: invalid output index: %i out of %i", __func__, outputIndex, operation.outputs.size());
return nullptr;
}
// Model should have been validated beforehand
ARMNN_ASSERT(operation.outputs[outputIndex] < getMainModel(model).operands.size());
return &getMainModel(model).operands[operation.outputs[outputIndex]];
}
const void* GetOperandValueReadOnlyAddress(const Operand& operand,
const Model& model,
const ConversionData& data,
bool optional = false);
inline bool GetOperandType(const Operation& operation,
uint32_t inputIndex,
const Model& model,
OperandType& type)
{
const Operand* operand = GetInputOperand(operation, inputIndex, model);
if (!operand)
{
return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
}
type = operand->type;
return true;
}
inline bool IsOperandConstant(const Operand& operand)
{
OperandLifeTime lifetime = operand.lifetime;
return lifetime == OperandLifeTime::CONSTANT_COPY ||
lifetime == OperandLifeTime::CONSTANT_REFERENCE ||
lifetime == OperandLifeTime::POINTER ||
lifetime == OperandLifeTime::NO_VALUE;
}
bool IsWeightsValid(const Operation& operation, uint32_t inputIndex, const Model& model);
ConstTensorPin ConvertOperandToConstTensorPin(const Operand& operand,
const Model& model,
const ConversionData& data,
const armnn::PermutationVector& dimensionMappings = g_DontPermute,
const armnn::TensorShape* overrideTensorShape = nullptr,
bool optional = false,
const armnn::DataType* overrideDataType = nullptr);
inline ConstTensorPin ConvertOperationInputToConstTensorPin(
const Operation& operation,
uint32_t inputIndex,
const Model& model,
const ConversionData& data,
const armnn::PermutationVector& dimensionMappings = g_DontPermute,
const armnn::TensorShape* overrideTensorShape = nullptr,
bool optional = false)
{
const Operand* operand = GetInputOperand(operation, inputIndex, model);
if (!operand)
{
Fail("%s: failed to get input operand: index=%u", __func__, inputIndex);
return ConstTensorPin();
}
return ConvertOperandToConstTensorPin(*operand,
model,
data,
dimensionMappings,
overrideTensorShape,
optional);
}
template <typename OutputType>
bool GetInputScalar(const Operation& operation,
uint32_t inputIndex,
OperandType type,
OutputType& outValue,
const Model& model,
const ConversionData& data,
bool optional = false)
{
const Operand* operand = GetInputOperand(operation, inputIndex, model);
if (!optional && !operand)
{
return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
}
if (!optional && operand->type != type)
{
VLOG(DRIVER) << __func__ << ": unexpected operand type: " << operand->type << " should be: " << type;
return false;
}
if (!optional && operand->location.length != sizeof(OutputType))
{
return Fail("%s: incorrect operand location length: %i (should be %i)",
__func__, operand->location.length, sizeof(OutputType));
}
const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
if (!optional && !valueAddress)
{
return Fail("%s: failed to get address for operand", __func__);
}
if(!optional)
{
outValue = *(static_cast<const OutputType*>(valueAddress));
}
return true;
}
inline bool GetInputInt32(const Operation& operation,
uint32_t inputIndex,
int32_t& outValue,
const Model& model,
const ConversionData& data)
{
return GetInputScalar(operation, inputIndex, OperandType::INT32, outValue, model, data);
}
inline bool GetInputFloat32(const Operation& operation,
uint32_t inputIndex,
float& outValue,
const Model& model,
const ConversionData& data)
{
return GetInputScalar(operation, inputIndex, OperandType::FLOAT32, outValue, model, data);
}
inline bool GetInputActivationFunctionImpl(const Operation& operation,
uint32_t inputIndex,
OperandType type,
ActivationFn& outActivationFunction,
const Model& model,
const ConversionData& data)
{
if (type != OperandType::INT32 && type != OperandType::TENSOR_INT32)
{
VLOG(DRIVER) << __func__ << ": unexpected operand type: " << type
<< " should be OperandType::INT32 or OperandType::TENSOR_INT32";
return false;
}
int32_t activationFunctionAsInt;
if (!GetInputScalar(operation, inputIndex, type, activationFunctionAsInt, model, data))
{
return Fail("%s: failed to get activation input value", __func__);
}
outActivationFunction = static_cast<ActivationFn>(activationFunctionAsInt);
return true;
}
inline bool GetInputActivationFunction(const Operation& operation,
uint32_t inputIndex,
ActivationFn& outActivationFunction,
const Model& model,
const ConversionData& data)
{
return GetInputActivationFunctionImpl(operation,
inputIndex,
OperandType::INT32,
outActivationFunction,
model,
data);
}
inline bool GetInputActivationFunctionFromTensor(const Operation& operation,
uint32_t inputIndex,
ActivationFn& outActivationFunction,
const Model& model,
const ConversionData& data)
{
// This only accepts a 1-D tensor of size 1
return GetInputActivationFunctionImpl(operation,
inputIndex,
OperandType::INT32,
outActivationFunction,
model,
data);
}
inline bool GetOptionalInputActivation(const Operation& operation,
uint32_t inputIndex,
ActivationFn& activationFunction,
const Model& model,
const ConversionData& data)
{
if (operation.inputs.size() <= inputIndex)
{
activationFunction = ActivationFn::kActivationNone;
}
else
{
if (!GetInputActivationFunction(operation, inputIndex, activationFunction, model, data))
{
return Fail("%s: Operation has invalid inputs", __func__);
}
}
return true;
}
template<typename ConvolutionDescriptor>
bool GetOptionalConvolutionDilationParams(const Operation& operation,
uint32_t dilationXIndex,
ConvolutionDescriptor& descriptor,
const Model& model,
const ConversionData& data)
{
bool success = true;
if (operation.inputs.size() >= dilationXIndex + 2)
{
success &= GetInputScalar(operation,
dilationXIndex,
OperandType::INT32,
descriptor.m_DilationX,
model,
data);
success &= GetInputScalar(operation,
dilationXIndex + 1,
OperandType::INT32,
descriptor.m_DilationY,
model,
data);
}
return success;
}
inline bool GetOptionalBool(const Operation& operation,
uint32_t inputIndex,
const Model& model,
const ConversionData& data)
{
const Operand* operand = GetInputOperand(operation, inputIndex, model);
if (!operand)
{
return false;
}
if (!IsBool(*operand))
{
return false;
}
const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
if (!valueAddress)
{
return false;
}
return *(static_cast<const bool*>(valueAddress));
}
bool GetTensorInt32Values(const Operand& operand,
std::vector<int32_t>& outValues,
const Model& model,
const ConversionData& data);
bool GetInputPaddingScheme(const Operation& operation,
uint32_t inputIndex,
PaddingScheme& outPaddingScheme,
const Model& model,
const ConversionData& data);
LayerInputHandle ConvertToLayerInputHandle(const Operation& operation,
uint32_t inputIndex,
const Model& model,
ConversionData& data,
const armnn::PermutationVector& dimensionMappings = g_DontPermute,
const LayerInputHandle* inputHandle = nullptr);
bool SetupAndTrackLayerOutputSlot(const Operation& operation,
uint32_t operationOutputIndex,
armnn::IConnectableLayer& layer,
uint32_t layerOutputIndex,
const Model& model,
ConversionData& data,
const armnn::TensorInfo* overrideOutputInfo = nullptr,
const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc = nullptr,
const ActivationFn& activationFunction = ActivationFn::kActivationNone,
bool inferOutputShapes = false);
armnn::DataLayout OptionalDataLayout(const Operation& operation,
uint32_t inputIndex,
const Model& model,
ConversionData& data);
inline bool SetupAndTrackLayerOutputSlot(
const Operation& operation,
uint32_t outputIndex,
armnn::IConnectableLayer& layer,
const Model& model,
ConversionData& data,
const armnn::TensorInfo* overrideOutputInfo = nullptr,
const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc = nullptr,
const ActivationFn& activationFunction = ActivationFn::kActivationNone)
{
return SetupAndTrackLayerOutputSlot(operation,
outputIndex,
layer,
outputIndex,
model,
data,
overrideOutputInfo,
validateFunc,
activationFunction);
}
bool ConvertToActivation(const Operation& operation,
const char* operationName,
const armnn::ActivationDescriptor& activationDesc,
const Model& model,
ConversionData& data);
bool ConvertPaddings(const Operation& operation,
const Model& model,
ConversionData& data,
unsigned int rank,
armnn::PadDescriptor& padDescriptor);
bool ConvertReduce(const Operation& operation,
const Model& model,
ConversionData& data,
armnn::ReduceOperation reduceOperation);
bool ConvertPooling2d(const Operation& operation,
const char* operationName,
armnn::PoolingAlgorithm poolType,
const Model& model,
ConversionData& data);
inline bool IsQSymm8(const Operand& operand)
{
return operand.type == OperandType::TENSOR_QUANT8_SYMM;
}
enum class DequantizeStatus
{
SUCCESS,
NOT_REQUIRED,
INVALID_OPERAND
};
using DequantizeResult = std::tuple<std::unique_ptr<float[]>, size_t, armnn::TensorInfo, DequantizeStatus>;
DequantizeResult DequantizeIfRequired(size_t operand_index,
const Operation& operation,
const Model& model,
const ConversionData& data);
ConstTensorPin DequantizeAndMakeConstTensorPin(const Operation& operation,
const Model& model,
const ConversionData& data,
size_t operandIndex,
bool optional = false);
bool IsConnectedToDequantize(armnn::IOutputSlot* ioutputSlot);
} // namespace armnn_driver