From 8f397a1efed11e17e9f8cb12b53a72b7e32ab978 Mon Sep 17 00:00:00 2001 From: Sadik Armagan Date: Fri, 17 Jun 2022 15:38:22 +0100 Subject: IVGCVSW-6989 "Merged experimental/armnn_shim_sl" * Updated Serializer CMakeLists.txt to build armnnSerializerObj * Added constant tensors as input support to SL Signed-off-by: Sadik Armagan Change-Id: I22f6cf50147d99a01f7fe70d7446b114a4c57af3 --- shim/sl/canonical/ConversionUtils.cpp | 1006 +++++++++++++++++++++++++++++++++ 1 file changed, 1006 insertions(+) create mode 100644 shim/sl/canonical/ConversionUtils.cpp (limited to 'shim/sl/canonical/ConversionUtils.cpp') diff --git a/shim/sl/canonical/ConversionUtils.cpp b/shim/sl/canonical/ConversionUtils.cpp new file mode 100644 index 0000000000..fbc5e280d3 --- /dev/null +++ b/shim/sl/canonical/ConversionUtils.cpp @@ -0,0 +1,1006 @@ +// +// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "ConversionUtils.hpp" +#include + +/// +/// Helper classes +/// + +namespace armnn_driver +{ + +LayerInputHandle::LayerInputHandle() + : m_OutputSlot(nullptr) + , m_Valid(false) +{} + +LayerInputHandle::LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo) + : m_OutputSlot(outputSlot) + , m_Valid(valid) + , m_TensorInfo(tensorInfo) +{} + +bool LayerInputHandle::IsValid() const +{ + return m_Valid; +} + +void LayerInputHandle::Connect(armnn::IInputSlot& inputSlot) +{ + ARMNN_ASSERT(IsValid()); + if (m_OutputSlot) + { + m_OutputSlot->Connect(inputSlot); + } +} + +void LayerInputHandle::Disconnect(armnn::IInputSlot& inputSlot) +{ + ARMNN_ASSERT(IsValid()); + if (m_OutputSlot) + { + m_OutputSlot->Disconnect(inputSlot); + } +} + +const armnn::TensorInfo& LayerInputHandle::GetTensorInfo() const +{ + return m_TensorInfo; +} + +void LayerInputHandle::SanitizeQuantizationScale(LayerInputHandle& weight, LayerInputHandle& input) +{ + if (m_OutputSlot) + { + armnn::TensorInfo weightInfo = weight.GetTensorInfo(); + armnn::TensorInfo inputInfo = input.GetTensorInfo(); + armnn::TensorInfo biasInfo = GetTensorInfo(); + + SanitizeBiasQuantizationScale(biasInfo, weightInfo, inputInfo); + + m_TensorInfo = biasInfo; + m_OutputSlot->SetTensorInfo(biasInfo); + } +} + +ConstTensorPin::ConstTensorPin(bool optional) + : m_Optional(optional) +{} + +ConstTensorPin::ConstTensorPin(armnn::TensorInfo& tensorInfo, + const void* valueStart, + uint32_t numBytes, + const armnn::PermutationVector& mappings) + : m_Optional(false) +{ + armnn::IgnoreUnused(numBytes); + if (tensorInfo.GetNumBytes() != numBytes) + { + VLOG(DRIVER) << "The size of ConstTensor does not match its TensorInfo."; + } + + const bool needsSwizzling = (mappings.GetSize() > 0); + if (needsSwizzling) + { + m_SwizzledTensorData.resize(tensorInfo.GetNumBytes()); + SwizzleAndroidNn4dTensorToArmNn(tensorInfo, valueStart, m_SwizzledTensorData.data(), mappings); + + m_ConstTensor = armnn::ConstTensor(tensorInfo, m_SwizzledTensorData.data()); + } + else + { + m_ConstTensor = armnn::ConstTensor(tensorInfo, valueStart); + } +} + +bool ConstTensorPin::IsValid() const +{ + return m_ConstTensor.GetMemoryArea() != nullptr; +} + +bool ConstTensorPin::IsOptional() const +{ + return m_Optional; +} + +const armnn::ConstTensor& ConstTensorPin::GetConstTensor() const +{ + return m_ConstTensor; +} + +const armnn::ConstTensor* ConstTensorPin::GetConstTensorPtr() const +{ + if (IsValid() && m_ConstTensor.GetNumElements() > 0) + { + return &m_ConstTensor; + } + // tensor is either invalid, or has no elements (indicating an optional tensor that was not provided) + return nullptr; +} + +/// +/// Utility functions +/// + +bool IsWeightsValid(const Operation& operation, + uint32_t inputIndex, + const Model& model) +{ + const Operand* operand = GetInputOperand(operation, inputIndex, model); + if (!operand) + { + Fail("%s: failed to get input operand %i", __func__, inputIndex); + return false; + } + + if (operand->lifetime != OperandLifeTime::CONSTANT_COPY + && operand->lifetime != OperandLifeTime::CONSTANT_REFERENCE + && operand->lifetime != OperandLifeTime::NO_VALUE) + { + return false; + } + return true; +} + +ConstTensorPin ConvertOperandToConstTensorPin(const Operand& operand, + const Model& model, + const ConversionData& data, + const armnn::PermutationVector& dimensionMappings, + const armnn::TensorShape* overrideTensorShape, + bool optional) +{ + if (!IsOperandTypeSupportedForTensors(operand.type)) + { + VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor" << operand.type; + return ConstTensorPin(); + } + + if (!optional && !IsOperandConstant(operand)) + { + VLOG(DRIVER) << __func__ << ": lifetime for input tensor: r" << operand.lifetime; + return ConstTensorPin(); + } + + const void* const valueStart = GetOperandValueReadOnlyAddress(operand, model, data, optional); + if (!valueStart) + { + if (optional) + { + // optional tensor with no values is not really an error; return it as invalid, but marked as optional + return ConstTensorPin(true); + } + // mandatory tensor with no values + Fail("%s: failed to get operand address", __func__); + return ConstTensorPin(); + } + + armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand); + + // Make sure isConstant flag is set. + tensorInfo.SetConstant(); + + if (overrideTensorShape != nullptr) + { + tensorInfo.SetShape(*overrideTensorShape); + } + return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings); +} + +LayerInputHandle ConvertToLayerInputHandle(const Operation& operation, + uint32_t inputIndex, + const Model& model, + ConversionData& data, + const armnn::PermutationVector& dimensionMappings) +{ + + const Operand* operand = GetInputOperand(operation, inputIndex, model); + if (!operand) + { + Fail("%s: failed to get input operand %i", __func__, inputIndex); + return LayerInputHandle(); + } + + if (!IsOperandTypeSupportedForTensors(operand->type)) + { + VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor: " << operand->type; + return LayerInputHandle(); + } + + try + { + armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand); + + if (IsDynamicTensor(operandTensorInfo)) + { + data.m_DynamicInputsEncountered = true; + + const uint32_t operandIndex = operation.inputs[inputIndex]; + + // Check if the dynamic input tensors have been inferred by one of the previous layers + // If not we can't support them + if (data.m_OutputSlotForOperand.size() >= operandIndex && data.m_OutputSlotForOperand[operandIndex]) + { + operandTensorInfo = data.m_OutputSlotForOperand[operandIndex]->GetTensorInfo(); + } + else + { + Fail("%s: Type 2 dynamic input tensors are not supported", __func__); + return LayerInputHandle(); + } + } + + switch (operand->lifetime) + { + case OperandLifeTime::SUBGRAPH_INPUT: + { + // NOTE: We must check whether we can support the input tensor on at least one + // of the provided backends; otherwise we cannot convert the operation + bool isInputSupported = false; + FORWARD_LAYER_SUPPORT_FUNC(__func__, + IsInputSupported, + data.m_Backends, + isInputSupported, + operandTensorInfo); + + if (!isInputSupported) + { + Fail("%s: unsupported input tensor", __func__); + return LayerInputHandle(); + } + + [[clang::fallthrough]]; // intentional fallthrough + } + case OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough + case OperandLifeTime::SUBGRAPH_OUTPUT: + { + // The tensor is either an operand internal to the model, or a model input. + // It can be associated with an ArmNN output slot for an existing layer. + + // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted + const uint32_t operandIndex = operation.inputs[inputIndex]; + return LayerInputHandle(true, data.m_OutputSlotForOperand[operandIndex], operandTensorInfo); + } + case OperandLifeTime::CONSTANT_COPY: // intentional fallthrough + case OperandLifeTime::POINTER: + case OperandLifeTime::CONSTANT_REFERENCE: + { + // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer. + ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand, model, data, dimensionMappings); + if (tensorPin.IsValid()) + { + bool isSupported = false; + FORWARD_LAYER_SUPPORT_FUNC(__func__, + IsConstantSupported, + data.m_Backends, + isSupported, + tensorPin.GetConstTensor().GetInfo()); + if (!isSupported) + { + return LayerInputHandle(); + } + + armnn::IConnectableLayer* constantLayer = + data.m_Network->AddConstantLayer(tensorPin.GetConstTensor()); + armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0); + outputSlot.SetTensorInfo(tensorPin.GetConstTensor().GetInfo()); + + return LayerInputHandle(true, &outputSlot, operandTensorInfo); + } + else + { + Fail("%s: invalid operand tensor", __func__); + return LayerInputHandle(); + } + break; + } + default: + { + VLOG(DRIVER) << __func__ << ": unsupported lifetime for input tensor: " << operand->lifetime; + return LayerInputHandle(); + } + } + } + catch (UnsupportedOperand& e) + { + VLOG(DRIVER) << __func__ << ": Operand type: " << e.m_type << " not supported in ArmnnDriver"; + return LayerInputHandle(); + } +} + +bool ConvertPaddings(const Operation& operation, + const Model& model, + ConversionData& data, + unsigned int rank, + armnn::PadDescriptor& padDescriptor) +{ + const Operand* paddingsOperand = GetInputOperand(operation, 1, model); + if (!paddingsOperand) + { + return Fail("%s: Could not read paddings operand", __func__); + } + + armnn::TensorShape paddingsOperandShape = GetTensorShapeForOperand(*paddingsOperand); + if (paddingsOperandShape.GetNumDimensions() != 2 || paddingsOperandShape.GetNumElements() != rank * 2) + { + return Fail("%s: Operation has invalid paddings operand: expected shape [%d, 2]", __func__, rank); + } + + std::vector paddings; + if (!GetTensorInt32Values(*paddingsOperand, paddings, model, data)) + { + return Fail("%s: Operation has invalid or unsupported paddings operand", __func__); + } + + // add padding for each dimension of input tensor. + for (unsigned int i = 0; i < paddings.size() - 1; i += 2) + { + int paddingBeforeInput = paddings[i]; + int paddingAfterInput = paddings[i + 1]; + + if (paddingBeforeInput < 0 || paddingAfterInput < 0) + { + return Fail("%s: Operation has invalid paddings operand, invalid padding values.", __func__); + } + + padDescriptor.m_PadList.emplace_back((unsigned int) paddingBeforeInput, (unsigned int) paddingAfterInput); + } + + return true; +} + + +bool ConvertPooling2d(const Operation& operation, + const char* operationName, + armnn::PoolingAlgorithm poolType, + const Model& model, + ConversionData& data) +{ + + VLOG(DRIVER) << "Converter::ConvertL2Pool2d()"; + + LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data); + if (!input.IsValid()) + { + return Fail("%s: Operation Could not read input 0", operationName); + } + + const Operand* output = GetOutputOperand(operation, 0, model); + if (!output) + { + return Fail("%s: Could not read output 0", __func__); + } + + const armnn::TensorInfo& inputInfo = input.GetTensorInfo(); + const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output); + + armnn::Pooling2dDescriptor desc; + desc.m_PoolType = poolType; + desc.m_OutputShapeRounding = armnn::OutputShapeRounding::Floor; + desc.m_DataLayout = armnn::DataLayout::NHWC; + + ActivationFn activation; + + auto inputSize = operation.inputs.size(); + + if (inputSize >= 10) + { + // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type) + if (!GetInputScalar(operation, 1, OperandType::INT32, desc.m_PadLeft, model, data) || + !GetInputScalar(operation, 2, OperandType::INT32, desc.m_PadRight, model, data) || + !GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadTop, model, data) || + !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadBottom, model, data) || + !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideX, model, data) || + !GetInputScalar(operation, 6, OperandType::INT32, desc.m_StrideY, model, data) || + !GetInputScalar(operation, 7, OperandType::INT32, desc.m_PoolWidth, model, data) || + !GetInputScalar(operation, 8, OperandType::INT32, desc.m_PoolHeight, model, data) || + !GetInputActivationFunction(operation, 9, activation, model, data)) + { + return Fail("%s: Operation has invalid inputs", operationName); + } + + if (Is12OrLaterOperand(*output)) + { + desc.m_DataLayout = OptionalDataLayout(operation, 10, model, data); + } + } + else + { + // one input, 6 parameters (padding, stridex, stridey, width, height, activation type) + ::android::nn::PaddingScheme scheme; + if (!GetInputPaddingScheme(operation, 1, scheme, model, data) || + !GetInputScalar(operation, 2, OperandType::INT32, desc.m_StrideX, model, data) || + !GetInputScalar(operation, 3, OperandType::INT32, desc.m_StrideY, model, data) || + !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PoolWidth, model, data) || + !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PoolHeight, model, data) || + !GetInputActivationFunction(operation, 6, activation, model, data)) + { + return Fail("%s: Operation has invalid inputs", operationName); + } + + if (Is12OrLaterOperand(*output)) + { + desc.m_DataLayout = OptionalDataLayout(operation, 7, model, data); + } + + const armnnUtils::DataLayoutIndexed dataLayout(desc.m_DataLayout); + const unsigned int inputWidth = inputInfo.GetShape()[dataLayout.GetWidthIndex()]; + const unsigned int inputHeight = inputInfo.GetShape()[dataLayout.GetHeightIndex()]; + + CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme); + CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme); + } + + bool isSupported = false; + + auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported) + { + FORWARD_LAYER_SUPPORT_FUNC(__func__, + IsPooling2dSupported, + data.m_Backends, + isSupported, + inputInfo, + outputInfo, + desc); + + }; + + if(IsDynamicTensor(outputInfo)) + { + isSupported = AreDynamicTensorsSupported(); + } + else + { + validateFunc(outputInfo, isSupported); + } + + if (!isSupported) + { + return false; + } + + armnn::IConnectableLayer* pooling2dLayer = data.m_Network->AddPooling2dLayer(desc); + if (!pooling2dLayer) + { + return Fail("%s: AddPooling2dLayer failed", __func__); + } + + input.Connect(pooling2dLayer->GetInputSlot(0)); + + if (!isSupported) + { + return false; + } + + return SetupAndTrackLayerOutputSlot(operation, 0, *pooling2dLayer, model, + data, nullptr, validateFunc, activation); +} + +bool ConvertReduce(const Operation& operation, + const Model& model, + ConversionData& data, + armnn::ReduceOperation reduceOperation) +{ + armnn::ReduceDescriptor descriptor; + descriptor.m_ReduceOperation = reduceOperation; + + LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data); + if (!input.IsValid()) + { + return Fail("%s: Operation has invalid inputs", __func__); + } + const armnn::TensorInfo& inputInfo = input.GetTensorInfo(); + + const Operand* output = GetOutputOperand(operation, 0, model); + if (!output) + { + return Fail("%s: Could not read output 0", __func__); + } + const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output); + + const Operand* axisOperand = GetInputOperand(operation, 1, model); + if (!axisOperand) + { + return Fail("%s: Could not read input 1", __func__); + } + std::vector axis; + if (!GetTensorInt32Values(*axisOperand, axis, model, data)) + { + return Fail("%s: Input 1 has invalid values", __func__); + } + + // Convert the axis to unsigned int and remove duplicates. + unsigned int rank = inputInfo.GetNumDimensions(); + std::set uniqueAxis; + std::transform(axis.begin(), axis.end(), + std::inserter(uniqueAxis, uniqueAxis.begin()), + [rank](int i) -> unsigned int { return (i + rank) % rank; }); + descriptor.m_vAxis.assign(uniqueAxis.begin(), uniqueAxis.end()); + + // Get the "keep dims" flag. + if (!GetInputScalar(operation, 2, OperandType::BOOL, descriptor.m_KeepDims, model, data)) + { + return Fail("%s: Could not read input 2", __func__); + } + + bool isSupported = false; + auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported) + { + FORWARD_LAYER_SUPPORT_FUNC(__func__, + IsReduceSupported, + data.m_Backends, + isSupported, + inputInfo, + outputInfo, + descriptor); + }; + + if(!IsDynamicTensor(outputInfo)) + { + validateFunc(outputInfo, isSupported); + } + else + { + isSupported = AreDynamicTensorsSupported(); + } + + if (!isSupported) + { + return false; + } + + armnn::IConnectableLayer* const layer = data.m_Network->AddReduceLayer(descriptor); + assert(layer != nullptr); + input.Connect(layer->GetInputSlot(0)); + + return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc); +} + + +bool ConvertToActivation(const Operation& operation, + const char* operationName, + const armnn::ActivationDescriptor& activationDesc, + const Model& model, + ConversionData& data) +{ + LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data); + if (!input.IsValid()) + { + return Fail("%s: Input 0 is invalid", operationName); + } + + const Operand* outputOperand = GetOutputOperand(operation, 0, model); + if (!outputOperand) + { + return false; + } + + const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand); + + bool isSupported = false; + + auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported) + { + FORWARD_LAYER_SUPPORT_FUNC(__func__, + IsActivationSupported, + data.m_Backends, + isSupported, + input.GetTensorInfo(), + outInfo, + activationDesc); + }; + + if(IsDynamicTensor(outInfo)) + { + isSupported = AreDynamicTensorsSupported(); + } + else + { + validateFunc(outInfo, isSupported); + } + + if (!isSupported) + { + return false; + } + + armnn::IConnectableLayer* layer = data.m_Network->AddActivationLayer(activationDesc); + ARMNN_ASSERT(layer != nullptr); + input.Connect(layer->GetInputSlot(0)); + + return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc); +} + +DequantizeResult DequantizeIfRequired(size_t operand_index, + const Operation& operation, + const Model& model, + const ConversionData& data) +{ + const Operand* weightsOperand = GetInputOperand(operation, operand_index, model); + if (!weightsOperand) + { + return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::INVALID_OPERAND }; + } + + if (IsOperandConstant(*weightsOperand)) + { + // Weights are already constant + return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::NOT_REQUIRED }; + } + + const size_t weightsInputIndex = operation.inputs[operand_index]; + + // The weights are a non const tensor, this indicates they might be the output of a dequantize op. + // Iterate over the nodes and find the previous operation which should be DEQUANTIZE + for (uint32_t operationIdx = 0; operationIdx < getMainModel(model).operations.size(); ++operationIdx) + { + // Search for the DEQUANTIZE op which has the operand with index equal to operandIndex + const auto& operationIt = getMainModel(model).operations[operationIdx]; + if (operationIt.type != OperationType::DEQUANTIZE) + { + continue; + } + + size_t outOpIndex = weightsInputIndex + 1; + for (size_t i = 0; outOpIndex != weightsInputIndex && i < operationIt.outputs.size(); ++i) + { + outOpIndex = operationIt.outputs[i]; + } + + if (outOpIndex != weightsInputIndex) + { + continue; + } + + const Operand* operand = GetInputOperand(operationIt, 0, model); + ARMNN_ASSERT(operand); + + if (!IsQSymm8(*operand)) + { + // Only supporting dequantize from QSYMM8 to FLOAT + break; + } + + // Allocate a new buffer for the dequantized data and manually dequantize + const void* startValue = GetOperandValueReadOnlyAddress(*operand, model, data); + if (!startValue) + { + // Failed to get the operand address + break; + } + + const uint8_t* quantizedBuffer = reinterpret_cast(startValue); + size_t dequantizedBufferLength = operand->location.length; + const float quantizationScale = operand->scale; + + auto dequantizedBuffer = std::make_unique(dequantizedBufferLength + 1); + for (size_t i = 0; i < dequantizedBufferLength; ++i) + { + float* dstPtr = dequantizedBuffer.get(); + ARMNN_ASSERT(dstPtr); + *dstPtr++ = quantizedBuffer[i] * quantizationScale; + } + + // Construct tensor info for dequantized ConstTensor + armnn::TensorInfo tensorInfo(operand->dimensions.size(), + operand->dimensions.data(), + armnn::DataType::Float32); + + return { std::move(dequantizedBuffer), dequantizedBufferLength * sizeof(float), + std::move(tensorInfo), + DequantizeStatus::SUCCESS }; + } + + return { nullptr, 0, armnn::TensorInfo() , DequantizeStatus::NOT_REQUIRED}; +} + +ConstTensorPin DequantizeAndMakeConstTensorPin(const Operation& operation, + const Model& model, + const ConversionData& data, + size_t operandIndex, + bool optional) +{ + DequantizeResult dequantized = DequantizeIfRequired(operandIndex,operation, model, data); + + DequantizeStatus status = std::get<3>(dequantized); + switch (status) + { + case DequantizeStatus::INVALID_OPERAND: + { + // return invalid const tensor pin + return ConstTensorPin(); + } + case DequantizeStatus::NOT_REQUIRED: + { + return ConvertOperationInputToConstTensorPin( + operation, operandIndex, model, data, g_DontPermute, nullptr, optional); + } + case DequantizeStatus::SUCCESS: + default: + { + return ConstTensorPin( + std::get<2>(dequantized), std::get<0>(dequantized).get(), std::get<1>(dequantized), g_DontPermute); + } + } +} + +bool GetInputPaddingScheme(const Operation& operation, + uint32_t inputIndex, + PaddingScheme& outPaddingScheme, + const Model& model, + const ConversionData& data) +{ + int32_t paddingSchemeAsInt; + if (!GetInputInt32(operation, inputIndex, paddingSchemeAsInt, model, data)) + { + return Fail("%s: failed to get padding scheme input value", __func__); + } + + outPaddingScheme = static_cast<::android::nn::PaddingScheme>(paddingSchemeAsInt); + return true; +} + +const void* GetOperandValueReadOnlyAddress(const Operand& operand, + const Model& model, + const ConversionData& data, + bool optional) +{ + const void* valueStart = nullptr; + switch (operand.lifetime) + { + case OperandLifeTime::CONSTANT_COPY: + { + valueStart = model.operandValues.data() + operand.location.offset; + break; + } + case OperandLifeTime::POINTER: + { + // Pointer specified in the model + valueStart = std::get(operand.location.pointer); + break; + } + case OperandLifeTime::CONSTANT_REFERENCE: + { + // Constant specified via a Memory object + valueStart = GetMemoryFromPool(operand.location, data.m_MemPools); + break; + } + case OperandLifeTime::NO_VALUE: + { + // An optional input tensor with no values is not an error so should not register as a fail + if (optional) + { + valueStart = nullptr; + break; + } + [[fallthrough]]; + } + default: + { + VLOG(DRIVER) << __func__ << ": unsupported/invalid operand lifetime:: " << operand.lifetime; + valueStart = nullptr; + } + } + + return valueStart; +} + +bool GetTensorInt32Values(const Operand& operand, + std::vector& outValues, + const Model& model, + const ConversionData& data) +{ + if (operand.type != OperandType::TENSOR_INT32) + { + VLOG(DRIVER) << __func__ << ": invalid operand type: " << operand.type; + return false; + } + + const void* startAddress = GetOperandValueReadOnlyAddress(operand, model, data); + if (!startAddress) + { + VLOG(DRIVER) << __func__ << ": failed to get operand address " << operand.type; + return false; + } + + // Check number of bytes is sensible + const uint32_t numBytes = operand.location.length; + if (numBytes % sizeof(int32_t) != 0) + { + return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i", + __func__, numBytes, sizeof(int32_t)); + } + + outValues.resize(numBytes / sizeof(int32_t)); + memcpy(outValues.data(), startAddress, numBytes); + return true; +} + +armnn::DataLayout OptionalDataLayout(const Operation& operation, + uint32_t inputIndex, + const Model& model, + ConversionData& data) +{ + const Operand* operand = GetInputOperand(operation, inputIndex, model); + if (!operand) + { + return armnn::DataLayout::NHWC; + } + + if (!IsBool(*operand)) + { + return armnn::DataLayout::NHWC; + } + + const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data); + if (!valueAddress) + { + return armnn::DataLayout::NHWC; + } + + if (*(static_cast(valueAddress))) + { + return armnn::DataLayout::NCHW; + } + else + { + return armnn::DataLayout::NHWC; + } +} + +armnn::IConnectableLayer* ProcessActivation(const armnn::TensorInfo& tensorInfo, + ActivationFn activation, + armnn::IConnectableLayer* prevLayer, + ConversionData& data) +{ + ARMNN_ASSERT(prevLayer->GetNumOutputSlots() == 1); + + prevLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo); + + armnn::IConnectableLayer* activationLayer = prevLayer; + + if (activation != ActivationFn::kActivationNone) + { + armnn::ActivationDescriptor activationDesc; + switch (activation) + { + case ActivationFn::kActivationRelu: + { + activationDesc.m_Function = armnn::ActivationFunction::ReLu; + break; + } + case ActivationFn::kActivationRelu1: + { + activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu; + activationDesc.m_A = 1.0f; + activationDesc.m_B = -1.0f; + break; + } + case ActivationFn::kActivationRelu6: + { + activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu; + activationDesc.m_A = 6.0f; + break; + } + case ActivationFn::kActivationSigmoid: + { + activationDesc.m_Function = armnn::ActivationFunction::Sigmoid; + break; + } + case ActivationFn::kActivationTanh: + { + activationDesc.m_Function = armnn::ActivationFunction::TanH; + activationDesc.m_A = 1.0f; + activationDesc.m_B = 1.0f; + break; + } + default: + { + Fail("%s: Invalid activation enum value %i", __func__, activation); + return nullptr; + } + } + + bool isSupported = false; + FORWARD_LAYER_SUPPORT_FUNC(__func__, + IsActivationSupported, + data.m_Backends, + isSupported, + prevLayer->GetOutputSlot(0).GetTensorInfo(), + tensorInfo, + activationDesc); + if (!isSupported) + { + return nullptr; + } + + activationLayer = data.m_Network->AddActivationLayer(activationDesc); + + prevLayer->GetOutputSlot(0).Connect(activationLayer->GetInputSlot(0)); + activationLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo); + } + + return activationLayer; +} + +bool SetupAndTrackLayerOutputSlot(const Operation& operation, + uint32_t operationOutputIndex, + armnn::IConnectableLayer& layer, + uint32_t layerOutputIndex, + const Model& model, + ConversionData& data, + const armnn::TensorInfo* overrideOutputInfo, + const std::function & validateFunc, + const ActivationFn& activationFunction, + bool inferOutputShapes) +{ + const Operand* outputOperand = GetOutputOperand(operation, operationOutputIndex, model); + if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots())) + { + return false; + } + + armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex); + if (overrideOutputInfo == nullptr) + { + outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand)); + } + else + { + outputSlot.SetTensorInfo(*overrideOutputInfo); + } + + bool isSupported = false; + if (validateFunc && (IsDynamicTensor(outputSlot.GetTensorInfo()) || inferOutputShapes)) + { + // Type one dynamic tensors require the previous layer's output shape for inference + for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex) + { + if(!layer.GetInputSlot(inputSlotIndex).GetConnection()) + { + return false; + } + } + // IsTensorInfoSet will infer the dynamic output shape + outputSlot.IsTensorInfoSet(); + // Once the shape is inferred we can validate it + validateFunc(outputSlot.GetTensorInfo(), isSupported); + + if(!isSupported) + { + for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex) + { + layer.GetInputSlot(inputSlotIndex).GetConnection()->Disconnect(layer.GetInputSlot(inputSlotIndex)); + } + return false; + } + } + + const uint32_t operandIndex = operation.outputs[operationOutputIndex]; + + if (activationFunction != ActivationFn::kActivationNone) + { + const armnn::TensorInfo& activationOutputInfo = outputSlot.GetTensorInfo(); + armnn::IConnectableLayer* const endLayer = ProcessActivation(activationOutputInfo, activationFunction, + &layer, data); + + if (!endLayer) + { + return Fail("%s: ProcessActivation failed", __func__); + } + + armnn::IOutputSlot& activationOutputSlot = endLayer->GetOutputSlot(layerOutputIndex); + data.m_OutputSlotForOperand[operandIndex] = &activationOutputSlot; + } + else + { + data.m_OutputSlotForOperand[operandIndex] = &outputSlot; + } + + return true; +} + +} // namespace armnn_driver -- cgit v1.2.1