// // 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