diff options
Diffstat (limited to 'src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp')
-rw-r--r-- | src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp b/src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp new file mode 100644 index 0000000000..72c7352a65 --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp @@ -0,0 +1,173 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// +// Copyright © 2020, 2023 The TensorFlow Authors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// + +#include <numeric> +#include "ResizeOperator.hpp" + +// This function is paraphrased from: +// tensorflow/compiler/mlir/tosa/transforms/legalize_common.cc from function convertResizeOp +// tensorflow/lite/kernels/internal/reference/resize_utils.h +TosaSerializationBasicBlock* ConvertResizeToTosaOperator(const Layer* layer, + const std::vector<const TensorInfo*>& inputs, + const std::vector<const TensorInfo*>& outputs, + const ResizeDescriptor* resizeDescriptor) +{ + ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( inputs.size() == 1, + "ConvertResizeToTosaOperator: Resize must have only one input." ); + ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( resizeDescriptor->m_DataLayout == DataLayout::NHWC, + "ConvertResizeToTosaOperator: NCHW not supported."); + + ResizeMode mode; + if (resizeDescriptor->m_Method == ResizeMethod::NearestNeighbor) + { + mode = tosa::ResizeMode_NEAREST; + } + else if (resizeDescriptor->m_Method == ResizeMethod::Bilinear) + { + mode = tosa::ResizeMode_BILINEAR; + throw armnn::InvalidArgumentException("ConvertResizeToTosaOperator: Unimplemented Resize method."); + } + else + { + throw armnn::InvalidArgumentException("ConvertResizeToTosaOperator: Unsupported Resize method."); + } + + std::string inputName = std::string("input0_"); + std::string outputName = std::string("output0_"); + std::string blockName = std::string("Op_RESIZE_block_") + GetUniqueTosaMappingID(); + + // If a layer is present then the block will be used for execution, so input and output names need to be determined + // using the previous and following layers so the graph is connected correctly. For validation this doesn't matter. + if(layer != nullptr) + { + // Get the layers connected to the input slots and determine unique tensor names. + Layer& connectedLayer = layer->GetInputSlot(0).GetConnectedOutputSlot()->GetOwningLayer(); + inputName = GenerateUniqueName(connectedLayer, 0); + + // Determine unique output tensor name. + outputName = GenerateUniqueOutputName(*layer, 0); + } + + int32_t inputHeight = static_cast<int32_t>(inputs[0]->GetShape()[1]); + int32_t inputWidth = static_cast<int32_t>(inputs[0]->GetShape()[2]); + + int32_t outputHeight = static_cast<int32_t>(resizeDescriptor->m_TargetHeight); + int32_t outputWidth = static_cast<int32_t>(resizeDescriptor->m_TargetWidth); + bool alignCorners = resizeDescriptor->m_AlignCorners; + bool halfPixel = resizeDescriptor->m_HalfPixelCenters; + + // Go from ArmNN parameters (outputShape, halfPixel and alignedCorners) + // to TOSA parameters (scale, offset and border) + // Align corners sets the scaling ratio to (O - 1)/(I - 1) rather than O / I. + auto preprocessResizeParameters = [&](int inputSize, int outputSize, int& scale_n, int& scale_d, int& offset) + { + // Dimension is length 1, we are just sampling from one value. + if (inputSize == 1) + { + scale_n = outputSize; + scale_d = 1; + offset = 0; + return; + } + + // Apply if aligned and capable to be aligned. + // Align corners sets the scaling ratio to (OH - 1)/(IH - 1) rather than OH / IH. Same for width. + bool applyAligned = alignCorners && (outputSize > 1); + scale_n = applyAligned ? (outputSize - 1) : outputSize; + scale_d = applyAligned ? (inputSize - 1) : inputSize; + + // Simplify the scales, make sure they are even values. + int gcd = std::gcd(scale_n, scale_d); + scale_n = 2 * scale_n / gcd; + scale_d = 2 * scale_d / gcd; + + // If half pixel centers then input and output sampling positions are offset by 1/2 pixel. + offset = halfPixel ? (scale_d / 2 - scale_n / 2) : 0; + + // Reduce the scaling ratio if possible, we know scale_n and scale_d are even + if ((offset & 1) == 0) + { + scale_n /= 2; + scale_d /= 2; + offset /= 2; + } + }; + + int scale_y_n, scale_y_d, offset_y; + int scale_x_n, scale_x_d, offset_x; + preprocessResizeParameters(inputHeight, outputHeight, scale_y_n, scale_y_d, offset_y); + preprocessResizeParameters(inputWidth, outputWidth, scale_x_n, scale_x_d, offset_x); + + int border_y = scale_y_d * (outputHeight - 1) - scale_y_n * (inputHeight - 1) + offset_y; + int border_x = scale_x_d * (outputWidth - 1) - scale_x_n * (inputWidth - 1) + offset_x; + + // [scale_y_n, scale_y_d, scale_x_n, scale_x_d] + std::vector<int16_t> scale = { static_cast<int16_t>(scale_y_n), + static_cast<int16_t>(scale_y_d), + static_cast<int16_t>(scale_x_n), + static_cast<int16_t>(scale_x_d) }; + + // [offset_y, offset_x] + std::vector<int16_t> offset = { static_cast<int16_t>(offset_y), + static_cast<int16_t>(offset_x) }; + // [border_y, border_x] + std::vector<int16_t> border = { static_cast<int16_t>(border_y), + static_cast<int16_t>(border_x) }; + + auto isInt16Range = [](int x) + { + return (x <= std::numeric_limits<int16_t>::max()) && (x >= std::numeric_limits<int16_t>::min()); + }; + + if (inputs[0]->IsQuantized()) + { + // It isn't commonly seen these numbers aren't fit within 16 bits, and won't match TFLite reference. + if (!isInt16Range(scale_y_n) || !isInt16Range(scale_y_d) || + !isInt16Range(scale_x_n) || !isInt16Range(scale_x_d) || + !isInt16Range(offset_y) || !isInt16Range(offset_x) || + !isInt16Range(border_y) || !isInt16Range(border_x)) + { + throw armnn::Exception("ConvertResizeToTosaOperator: stride or offset out of 16 bit range"); + } + } + + TosaResizeAttribute resizeAttribute(scale, offset, border, mode); + + auto* op = new TosaSerializationOperator(Op_RESIZE, + Attribute_ResizeAttribute, + &resizeAttribute, + {inputName}, + {outputName}); + + std::vector<TosaSerializationTensor*> tensors; + + // Only add input tensors if connected layer is an input layer. + // As intermediate or constant tensors will be created separately. + // There also can't be duplicate tensor. + if(inputName.find("input0_") != std::string::npos) + { + std::vector<int32_t> inputShape = GetTosaTensorShape(inputs[0]->GetShape()); + DType inputDType = ArmNNToDType(inputs[0]->GetDataType()); + + tensors.push_back(new TosaSerializationTensor(inputName, inputShape, inputDType, {})); + } + + std::vector<int32_t> outputShape = GetTosaTensorShape(outputs[0]->GetShape()); + DType outputDType = ArmNNToDType(outputs[0]->GetDataType()); + + tensors.push_back(new TosaSerializationTensor(outputName, outputShape, outputDType, {})); + + // operatorInputNames/operatorOutputNames ends up being the same as + // blockInputNames/blockOutputNames for one-to-one ArmNN to TOSA mappings + return new TosaSerializationBasicBlock(blockName, // name + mainName, // region name + {op}, // operators + tensors, // tensors + {inputName}, // inputs + {outputName}); // outputs +}
\ No newline at end of file |