aboutsummaryrefslogtreecommitdiff
path: root/src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp')
-rw-r--r--src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp173
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