aboutsummaryrefslogtreecommitdiff
path: root/shim/sl/canonical/ConversionUtils.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'shim/sl/canonical/ConversionUtils.hpp')
-rw-r--r--shim/sl/canonical/ConversionUtils.hpp1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/shim/sl/canonical/ConversionUtils.hpp b/shim/sl/canonical/ConversionUtils.hpp
new file mode 100644
index 0000000000..5847d219d4
--- /dev/null
+++ b/shim/sl/canonical/ConversionUtils.hpp
@@ -0,0 +1,1013 @@
+//
+// 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);
+
+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, ...) \
+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) \
+ { \
+ 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;
+ FORWARD_LAYER_SUPPORT_FUNC(__func__,
+ IsReshapeSupported,
+ data.m_Backends,
+ isSupported,
+ smallInfo,
+ reshapedInfo,
+ reshapeDescriptor);
+ if (!isSupported)
+ {
+ return false;
+ }
+
+ ARMNN_ASSERT(data.m_Network != nullptr);
+ armnn::IConnectableLayer& reshapeLayer = AddReshapeLayer(*data.m_Network, smallInputHandle, reshapedInfo);
+
+ 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)
+{
+ 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);
+ 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))
+ {
+ 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;
+ FORWARD_LAYER_SUPPORT_FUNC(__func__,
+ IsTransposeSupported,
+ data.m_Backends,
+ isSupported,
+ inputs[i].GetTensorInfo(),
+ outputTransposeInfo,
+ transposeDesc);
+ if (!isSupported)
+ {
+ return false;
+ }
+
+ }
+ SwizzleInputs(*data.m_Network, inputs, inputShapes, mapping);
+ }
+ 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);
+
+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);
+
+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);
+
+} // namespace armnn_driver