From ce65588484ed1e553bdebf24123a30b5575f1bce Mon Sep 17 00:00:00 2001 From: Teresa Charlin Date: Tue, 21 Nov 2023 15:44:13 +0000 Subject: Add Resize Nearest Neighbour support to TOSA Reference Backend * Add support for quantized data in TosaRefPreCompiledWorkloadGetOutput. * Remove extra includes from all TOSA operators headers. * Added positive and negative unit tests for resize. * Resolves: IVGCVSW-7346 Signed-off-by: Teresa Charlin Change-Id: Ib6e30d018a7a1bf26b380fc794560aae108b26c3 --- AUTHORS-TensorFlow | 11 ++ src/backends/backendsCommon/WorkloadData.cpp | 1 + .../backendsCommon/test/ResizeEndToEndTestImpl.hpp | 62 ++++++-- src/backends/reference/RefLayerSupport.cpp | 3 +- src/backends/reference/test/RefEndToEndTests.cpp | 41 +++++ src/backends/tosaCommon/TosaMappings.cpp | 5 + .../tosaCommon/operatorMappings/CMakeLists.txt | 2 + .../tosaCommon/operatorMappings/ConcatOperator.hpp | 6 +- .../operatorMappings/ConstantOperator.hpp | 6 +- .../tosaCommon/operatorMappings/Conv2dOperator.hpp | 6 +- .../operatorMappings/ElementwiseBinaryOperator.hpp | 6 +- .../operatorMappings/ElementwiseUnaryOperator.hpp | 3 - .../operatorMappings/Pooling2DOperator.hpp | 6 +- .../operatorMappings/ReshapeOperator.hpp | 6 +- .../tosaCommon/operatorMappings/ResizeOperator.cpp | 173 +++++++++++++++++++++ .../tosaCommon/operatorMappings/ResizeOperator.hpp | 16 ++ .../tosaCommon/operatorMappings/SliceOperator.hpp | 6 +- .../operatorMappings/TosaCommonOperators.hpp | 1 + .../operatorMappings/TransposeConv2dOperator.hpp | 6 +- .../operatorMappings/TransposeOperator.hpp | 6 +- .../tosaCommon/test/OneToOneMappingTests.cpp | 142 +++++++++++++++++ src/backends/tosaCommon/test/TosaTestUtils.hpp | 57 ++++++- src/backends/tosaReference/TosaRefLayerSupport.cpp | 1 + .../tosaReference/test/TosaRefEndToEndTests.cpp | 42 +++++ .../test/TosaRefLayerSupportTests.cpp | 24 +++ .../workloads/TosaRefPreCompiledWorkload.cpp | 41 ++++- .../workloads/TosaRefPreCompiledWorkload.hpp | 6 + 27 files changed, 619 insertions(+), 66 deletions(-) create mode 100644 AUTHORS-TensorFlow create mode 100644 src/backends/tosaCommon/operatorMappings/ResizeOperator.cpp create mode 100644 src/backends/tosaCommon/operatorMappings/ResizeOperator.hpp diff --git a/AUTHORS-TensorFlow b/AUTHORS-TensorFlow new file mode 100644 index 0000000000..f4f92e15cb --- /dev/null +++ b/AUTHORS-TensorFlow @@ -0,0 +1,11 @@ +# This is the official list of TensorFlow authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. + +Google Inc. +Yuan Tang +Arm Ltd \ No newline at end of file diff --git a/src/backends/backendsCommon/WorkloadData.cpp b/src/backends/backendsCommon/WorkloadData.cpp index 021435ea40..2d7a5fdffc 100644 --- a/src/backends/backendsCommon/WorkloadData.cpp +++ b/src/backends/backendsCommon/WorkloadData.cpp @@ -1592,6 +1592,7 @@ void ResizeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const DataType::Float32, DataType::QAsymmS8, DataType::QAsymmU8, + DataType::QSymmS8, DataType::QSymmS16 }; diff --git a/src/backends/backendsCommon/test/ResizeEndToEndTestImpl.hpp b/src/backends/backendsCommon/test/ResizeEndToEndTestImpl.hpp index f8d17a89b7..071daa7a31 100644 --- a/src/backends/backendsCommon/test/ResizeEndToEndTestImpl.hpp +++ b/src/backends/backendsCommon/test/ResizeEndToEndTestImpl.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2017 Arm Ltd. All rights reserved. +// Copyright © 2017, 2019-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // #pragma once @@ -37,7 +37,9 @@ armnn::INetworkPtr CreateResizeNetwork(const armnn::ResizeDescriptor& descriptor template void ResizeEndToEnd(const std::vector& backends, armnn::DataLayout dataLayout, - armnn::ResizeMethod resizeMethod) + armnn::ResizeMethod resizeMethod, + bool alignCorners = false, + bool halfPixel = false) { using namespace armnn; using T = ResolveType; @@ -64,6 +66,11 @@ void ResizeEndToEnd(const std::vector& backends, 7.f, 8.f, 9.f }; + if (alignCorners && halfPixel) + { + throw InvalidArgumentException("alignCorners and halfPixel cannot be true simultaneously "); + } + std::vector expectedOutputData; switch(resizeMethod) { @@ -81,14 +88,42 @@ void ResizeEndToEnd(const std::vector& backends, } case ResizeMethod::NearestNeighbor: { - expectedOutputData = + if (alignCorners) { - 1.f, 1.f, 2.f, 2.f, 3.f, - 1.f, 1.f, 2.f, 2.f, 3.f, - 4.f, 4.f, 5.f, 5.f, 6.f, - 4.f, 4.f, 5.f, 5.f, 6.f, - 7.f, 7.f, 8.f, 8.f, 9.f - }; + expectedOutputData = + { + 1.f, 2.f, 2.f, 3.f, 3.f, + 4.f, 5.f, 5.f, 6.f, 6.f, + 4.f, 5.f, 5.f, 6.f, 6.f, + 7.f, 8.f, 8.f, 9.f, 9.f, + 7.f, 8.f, 8.f, 9.f, 9.f + }; + } + else + { + if (halfPixel) + { + expectedOutputData = + { + 1.f, 1.f, 2.f, 3.f, 3.f, + 1.f, 1.f, 2.f, 3.f, 3.f, + 4.f, 4.f, 5.f, 6.f, 6.f, + 7.f, 7.f, 8.f, 9.f, 9.f, + 7.f, 7.f, 8.f, 9.f, 9.f + }; + } + else + { + expectedOutputData = + { + 1.f, 1.f, 2.f, 2.f, 3.f, + 1.f, 1.f, 2.f, 2.f, 3.f, + 4.f, 4.f, 5.f, 5.f, 6.f, + 4.f, 4.f, 5.f, 5.f, 6.f, + 7.f, 7.f, 8.f, 8.f, 9.f + }; + } + } break; } default: @@ -102,6 +137,9 @@ void ResizeEndToEnd(const std::vector& backends, descriptor.m_TargetHeight = outputHeight; descriptor.m_Method = resizeMethod; descriptor.m_DataLayout = dataLayout; + descriptor.m_AlignCorners = alignCorners; + descriptor.m_HalfPixelCenters = halfPixel; + // swizzle data if needed if (dataLayout == armnn::DataLayout::NHWC) @@ -137,7 +175,9 @@ void ResizeBilinearEndToEnd(const std::vector& backends, template void ResizeNearestNeighborEndToEnd(const std::vector& backends, - armnn::DataLayout dataLayout) + armnn::DataLayout dataLayout, + bool alignCorners = false, + bool halfPixel = false) { - ResizeEndToEnd(backends, dataLayout, armnn::ResizeMethod::NearestNeighbor); + ResizeEndToEnd(backends, dataLayout, armnn::ResizeMethod::NearestNeighbor, alignCorners, halfPixel); } diff --git a/src/backends/reference/RefLayerSupport.cpp b/src/backends/reference/RefLayerSupport.cpp index 2be227ae8f..40d243e10a 100644 --- a/src/backends/reference/RefLayerSupport.cpp +++ b/src/backends/reference/RefLayerSupport.cpp @@ -2381,13 +2381,14 @@ bool RefLayerSupport::IsResizeSupported(const TensorInfo& input, { IgnoreUnused(descriptor); bool supported = true; - std::array supportedTypes = + std::array supportedTypes = { DataType::BFloat16, DataType::Float32, DataType::Float16, DataType::QAsymmS8, DataType::QAsymmU8, + DataType::QSymmS8, DataType::QSymmS16 }; diff --git a/src/backends/reference/test/RefEndToEndTests.cpp b/src/backends/reference/test/RefEndToEndTests.cpp index 13995f5c6f..7e07c658fa 100644 --- a/src/backends/reference/test/RefEndToEndTests.cpp +++ b/src/backends/reference/test/RefEndToEndTests.cpp @@ -1274,6 +1274,11 @@ TEST_CASE("RefResizeBilinearEndToEndUint8NchwTest") ResizeBilinearEndToEnd(defaultBackends, armnn::DataLayout::NCHW); } +TEST_CASE("RefResizeBilinearEndToEndInt8NchwTest") +{ + ResizeBilinearEndToEnd(defaultBackends, armnn::DataLayout::NCHW); +} + TEST_CASE("RefResizeBilinearEndToEndInt16NchwTest") { ResizeBilinearEndToEnd(defaultBackends, armnn::DataLayout::NCHW); @@ -1289,6 +1294,11 @@ TEST_CASE("RefResizeBilinearEndToEndUint8NhwcTest") ResizeBilinearEndToEnd(defaultBackends, armnn::DataLayout::NHWC); } +TEST_CASE("RefResizeBilinearEndToEndInt8NhwcTest") +{ + ResizeBilinearEndToEnd(defaultBackends, armnn::DataLayout::NHWC); +} + TEST_CASE("RefResizeBilinearEndToEndInt16NhwcTest") { ResizeBilinearEndToEnd(defaultBackends, armnn::DataLayout::NHWC); @@ -1305,6 +1315,11 @@ TEST_CASE("RefResizeNearestNeighborEndToEndUint8NchwTest") ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NCHW); } +TEST_CASE("RefResizeNearestNeighborEndToEndInt8NchwTest") +{ + ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NCHW); +} + TEST_CASE("RefResizeNearestNeighborEndToEndInt16NchwTest") { ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NCHW); @@ -1320,11 +1335,37 @@ TEST_CASE("RefResizeNearestNeighborEndToEndUint8NhwcTest") ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NHWC); } +TEST_CASE("RefResizeNearestNeighborEndToEndInt8NhwcTest") +{ + ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NHWC); +} + TEST_CASE("RefResizeNearestNeighborEndToEndInt16NhwcTest") { ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NHWC); } +TEST_CASE("RefResizeNearestNeighborEndToEndFloatAlignCornersNhwcTest") +{ + ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NHWC, true, false); +} + +TEST_CASE("RefResizeNearestNeighborEndToEndFloatHalfPixelNhwcTest") +{ + ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NHWC, false, true); +} + +TEST_CASE("RefResizeNearestNeighborEndToEndInt8AlignCornersNhwcTest") +{ + ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NHWC, true, false); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndInt8HalfPixelNhwcTest") +{ + ResizeNearestNeighborEndToEnd(defaultBackends, armnn::DataLayout::NHWC, false, true); +} + + // ReverseV2 TEST_CASE("RefReverseV2EndToEndFloat16Test") { diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp index a998996f19..6567026de0 100644 --- a/src/backends/tosaCommon/TosaMappings.cpp +++ b/src/backends/tosaCommon/TosaMappings.cpp @@ -74,6 +74,11 @@ TosaSerializationBasicBlock* GetTosaMapping(const Layer* layer, auto reshapeDesc = PolymorphicDowncast(&descriptor); return ConvertReshapeToTosaOperator(layer, inputs, outputs, reshapeDesc); } + case LayerType::Resize: + { + auto resizeDesc = PolymorphicDowncast(&descriptor); + return ConvertResizeToTosaOperator(layer, inputs, outputs, resizeDesc); + } case LayerType::Slice: { auto sliceDesc = PolymorphicDowncast(&descriptor); diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt index 26f51b643f..c864544241 100644 --- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt +++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt @@ -20,6 +20,8 @@ list(APPEND armnnTosaBackendOperators_sources Pooling2DOperator.cpp ReshapeOperator.hpp ReshapeOperator.cpp + ResizeOperator.hpp + ResizeOperator.cpp SliceOperator.hpp SliceOperator.cpp TosaOperatorUtils.hpp diff --git a/src/backends/tosaCommon/operatorMappings/ConcatOperator.hpp b/src/backends/tosaCommon/operatorMappings/ConcatOperator.hpp index e6094ce03a..cbd7aca6e8 100644 --- a/src/backends/tosaCommon/operatorMappings/ConcatOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/ConcatOperator.hpp @@ -1,14 +1,10 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // #pragma once -#include - -#include - #include "TosaOperatorUtils.hpp" using namespace armnn; diff --git a/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp b/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp index df158aca3d..598e041232 100644 --- a/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/ConstantOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/operatorMappings/Conv2dOperator.hpp b/src/backends/tosaCommon/operatorMappings/Conv2dOperator.hpp index 909151b9ac..f22a8d2933 100644 --- a/src/backends/tosaCommon/operatorMappings/Conv2dOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/Conv2dOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/operatorMappings/ElementwiseBinaryOperator.hpp b/src/backends/tosaCommon/operatorMappings/ElementwiseBinaryOperator.hpp index 86031c6e06..4966ed1659 100644 --- a/src/backends/tosaCommon/operatorMappings/ElementwiseBinaryOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/ElementwiseBinaryOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/operatorMappings/ElementwiseUnaryOperator.hpp b/src/backends/tosaCommon/operatorMappings/ElementwiseUnaryOperator.hpp index d13428ca5a..635abd6f3c 100644 --- a/src/backends/tosaCommon/operatorMappings/ElementwiseUnaryOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/ElementwiseUnaryOperator.hpp @@ -6,9 +6,6 @@ #pragma once #include "TosaOperatorUtils.hpp" -#include - -#include using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/operatorMappings/Pooling2DOperator.hpp b/src/backends/tosaCommon/operatorMappings/Pooling2DOperator.hpp index cc9ec097f9..1323abc6a3 100644 --- a/src/backends/tosaCommon/operatorMappings/Pooling2DOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/Pooling2DOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/operatorMappings/ReshapeOperator.hpp b/src/backends/tosaCommon/operatorMappings/ReshapeOperator.hpp index 4f363df052..007d2dfbb1 100644 --- a/src/backends/tosaCommon/operatorMappings/ReshapeOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/ReshapeOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; 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 +#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& inputs, + const std::vector& 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(inputs[0]->GetShape()[1]); + int32_t inputWidth = static_cast(inputs[0]->GetShape()[2]); + + int32_t outputHeight = static_cast(resizeDescriptor->m_TargetHeight); + int32_t outputWidth = static_cast(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 scale = { static_cast(scale_y_n), + static_cast(scale_y_d), + static_cast(scale_x_n), + static_cast(scale_x_d) }; + + // [offset_y, offset_x] + std::vector offset = { static_cast(offset_y), + static_cast(offset_x) }; + // [border_y, border_x] + std::vector border = { static_cast(border_y), + static_cast(border_x) }; + + auto isInt16Range = [](int x) + { + return (x <= std::numeric_limits::max()) && (x >= std::numeric_limits::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 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 inputShape = GetTosaTensorShape(inputs[0]->GetShape()); + DType inputDType = ArmNNToDType(inputs[0]->GetDataType()); + + tensors.push_back(new TosaSerializationTensor(inputName, inputShape, inputDType, {})); + } + + std::vector 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 diff --git a/src/backends/tosaCommon/operatorMappings/ResizeOperator.hpp b/src/backends/tosaCommon/operatorMappings/ResizeOperator.hpp new file mode 100644 index 0000000000..881e7c79ad --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/ResizeOperator.hpp @@ -0,0 +1,16 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "TosaOperatorUtils.hpp" + +using namespace armnn; +using namespace tosa; + +TosaSerializationBasicBlock* ConvertResizeToTosaOperator(const Layer* inputSize, + const std::vector& outputSize, + const std::vector& scale_n, + const ResizeDescriptor* scale_d); diff --git a/src/backends/tosaCommon/operatorMappings/SliceOperator.hpp b/src/backends/tosaCommon/operatorMappings/SliceOperator.hpp index 127cc81255..69a4df5d4f 100644 --- a/src/backends/tosaCommon/operatorMappings/SliceOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/SliceOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp index 66dc1b342a..369b37f35e 100644 --- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp +++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp @@ -13,6 +13,7 @@ #include "ElementwiseUnaryOperator.hpp" #include "Pooling2DOperator.hpp" #include "ReshapeOperator.hpp" +#include "ResizeOperator.hpp" #include "SliceOperator.hpp" #include "TransposeConv2dOperator.hpp" #include "TransposeOperator.hpp" diff --git a/src/backends/tosaCommon/operatorMappings/TransposeConv2dOperator.hpp b/src/backends/tosaCommon/operatorMappings/TransposeConv2dOperator.hpp index eb911a1195..c94795cef2 100644 --- a/src/backends/tosaCommon/operatorMappings/TransposeConv2dOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/TransposeConv2dOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/operatorMappings/TransposeOperator.hpp b/src/backends/tosaCommon/operatorMappings/TransposeOperator.hpp index 3d1e2acd14..19c97cf176 100644 --- a/src/backends/tosaCommon/operatorMappings/TransposeOperator.hpp +++ b/src/backends/tosaCommon/operatorMappings/TransposeOperator.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -7,10 +7,6 @@ #include "TosaOperatorUtils.hpp" -#include - -#include - using namespace armnn; using namespace tosa; diff --git a/src/backends/tosaCommon/test/OneToOneMappingTests.cpp b/src/backends/tosaCommon/test/OneToOneMappingTests.cpp index 0cf8002cd1..267c9fb49d 100644 --- a/src/backends/tosaCommon/test/OneToOneMappingTests.cpp +++ b/src/backends/tosaCommon/test/OneToOneMappingTests.cpp @@ -553,6 +553,148 @@ TEST_CASE("GetTosaMappingFromLayer_ReshapeLayer") LayerType::Reshape); } + +TEST_CASE("GetTosaMapping_ResizeLayer") +{ + TensorInfo inputInfo = TensorInfo({ 1, 2, 3, 3 }, DataType::Float32); + TensorInfo outputInfo = TensorInfo({ 1, 4, 6, 3 }, DataType::Float32); + + std::vector> inputShape = {{ 1, 2, 3, 3 }}; + std::vector> outputShape = {{ 1, 4, 6, 3 }}; + + ResizeDescriptor descriptor; + descriptor.m_DataLayout = DataLayout::NHWC; + descriptor.m_TargetHeight = 4; + descriptor.m_TargetWidth = 6; + + TosaSerializationBasicBlock* basicBlock = GetTosaMapping(nullptr, + LayerType::Resize, + {&inputInfo}, + {&outputInfo}, + descriptor); + AssertTosaOneToOneMappingBasicBlock(basicBlock, + inputShape, + outputShape, + Op_RESIZE, + Attribute_ResizeAttribute, + descriptor, + LayerType::Resize); +} + +TEST_CASE("GetTosaMappingFromLayer_ResizeLayer") +{ + IRuntime::CreationOptions options; + IRuntimePtr runtime(IRuntime::Create(options)); + + // Builds up the structure of the network. + INetworkPtr net(INetwork::Create()); + + ResizeDescriptor descriptor; + descriptor.m_DataLayout = DataLayout::NHWC; + descriptor.m_TargetHeight = 2; + descriptor.m_TargetWidth = 3; + + IConnectableLayer* input = net->AddInputLayer(0, "input"); + IConnectableLayer* resize = net->AddResizeLayer(descriptor, "resize"); + IConnectableLayer* output = net->AddOutputLayer(0, "output"); + + input->GetOutputSlot(0).Connect(resize->GetInputSlot(0)); + resize->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + TensorInfo inputInfo = TensorInfo({ 1, 4, 6, 3 }, DataType::Float32); + TensorInfo outputInfo = TensorInfo({ 1, 2, 3, 3 }, DataType::Float32); + + input->GetOutputSlot(0).SetTensorInfo(inputInfo); + resize->GetOutputSlot(0).SetTensorInfo(outputInfo); + + std::vector> inputShape = {{ 1, 4, 6, 3 }}; + std::vector> outputShape = {{ 1, 2, 3, 3 }}; + + TosaSerializationBasicBlock* basicBlock = GetTosaMappingFromLayer(PolymorphicDowncast(resize)); + AssertTosaOneToOneMappingBasicBlock(basicBlock, + inputShape, + outputShape, + Op_RESIZE, + Attribute_ResizeAttribute, + descriptor, + LayerType::Resize); +} + +TEST_CASE("UNSUPPORTED_GetTosaMapping_ResizeLayer_NCHW") +{ + TensorInfo inputInfo = TensorInfo({ 1, 3, 2, 3 }, DataType::Float32); + TensorInfo outputInfo = TensorInfo({ 1, 3, 4, 6 }, DataType::Float32); + + std::vector> inputShape = {{ 1, 3, 2, 3 }}; + std::vector> outputShape = {{ 1, 3, 4, 6 }}; + + ResizeDescriptor descriptor; + descriptor.m_DataLayout = DataLayout::NCHW; + descriptor.m_TargetHeight = 4; + descriptor.m_TargetWidth = 6; + + try + { + TosaSerializationBasicBlock* basicBlock = GetTosaMapping(nullptr, + LayerType::Resize, + {&inputInfo}, + {&outputInfo}, + descriptor); + + AssertTosaOneToOneMappingBasicBlock(basicBlock, + inputShape, + outputShape, + Op_RESIZE, + Attribute_ResizeAttribute, + descriptor, + LayerType::Resize); + + FAIL("An exception should have been thrown"); + } + catch (const armnn::InvalidArgumentException& e) + { + CHECK(strcmp(e.what(), "ConvertResizeToTosaOperator: NCHW not supported.") == 0); + } +} + +TEST_CASE("UNSUPPORTED_GetTosaMapping_ResizeLayer_Bilinear") +{ + TensorInfo inputInfo = TensorInfo({ 1, 2, 3, 3 }, DataType::Float32); + TensorInfo outputInfo = TensorInfo({ 1, 4, 6, 3 }, DataType::Float32); + + std::vector> inputShape = {{ 1, 2, 3, 3 }}; + std::vector> outputShape = {{ 1, 4, 6, 3 }}; + + ResizeDescriptor descriptor; + descriptor.m_Method = ResizeMethod::Bilinear; + descriptor.m_DataLayout = DataLayout::NHWC; + descriptor.m_TargetHeight = 4; + descriptor.m_TargetWidth = 6; + + try + { + TosaSerializationBasicBlock* basicBlock = GetTosaMapping(nullptr, + LayerType::Resize, + {&inputInfo}, + {&outputInfo}, + descriptor); + + AssertTosaOneToOneMappingBasicBlock(basicBlock, + inputShape, + outputShape, + Op_RESIZE, + Attribute_ResizeAttribute, + descriptor, + LayerType::Resize); + + FAIL("An exception should have been thrown"); + } + catch (const armnn::InvalidArgumentException& e) + { + CHECK(strcmp(e.what(), "ConvertResizeToTosaOperator: Unimplemented Resize method.") == 0); + } +} + TEST_CASE("GetTosaMapping_SliceLayer") { TensorInfo inputInfo = TensorInfo({ 3, 2, 3 }, DataType::Float32); diff --git a/src/backends/tosaCommon/test/TosaTestUtils.hpp b/src/backends/tosaCommon/test/TosaTestUtils.hpp index e24055371f..87ff5ff532 100644 --- a/src/backends/tosaCommon/test/TosaTestUtils.hpp +++ b/src/backends/tosaCommon/test/TosaTestUtils.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // @@ -125,16 +125,65 @@ inline void VerifyTosaAttribute(const BaseDescriptor& descriptor, break; } + case LayerType::Resize: + { + auto resizeDesc = PolymorphicDowncast(&descriptor); + TosaResizeAttribute resizeAttribute(attribute); + + // Check output shape + uint32_t outputHeight = resizeDesc->m_TargetHeight; + uint32_t outputWidth = resizeDesc->m_TargetWidth; + + CHECK((outputShape.size() == 4)); + if (resizeDesc->m_DataLayout == DataLayout::NHWC) + { + //Check output is not dynamic + CHECK((outputShape[1] > 0)); + CHECK((outputShape[2] > 0)); + + CHECK((outputHeight == static_cast(outputShape[1]))); + CHECK((outputWidth == static_cast(outputShape[2]))); + } + else if (resizeDesc->m_DataLayout == DataLayout::NCHW) + { + //Check output is not dynamic + CHECK((outputShape[2] > 0)); + CHECK((outputShape[3] > 0)); + + CHECK((outputHeight == static_cast(outputShape[2]))); + CHECK((outputWidth == static_cast(outputShape[3]))); + } + else + { + throw armnn::Exception("VerifyTosaAttribute: Invalid DataLayout in Resize."); + } + + // Check Resize mode/method + if (resizeDesc->m_Method == ResizeMethod::NearestNeighbor) + { + CHECK((resizeAttribute.mode() == tosa::ResizeMode_NEAREST)); + } + else if (resizeDesc->m_Method == ResizeMethod::Bilinear) + { + CHECK((resizeAttribute.mode() == tosa::ResizeMode_BILINEAR)); + } + else + { + throw armnn::Exception("VerifyTosaAttribute: Unsupported Resize method."); + } + + break; + } case LayerType::Slice: { auto sliceDesc = PolymorphicDowncast(&descriptor); - TosaSliceAttribute reshapeAttribute(attribute); + TosaSliceAttribute sliceAttribute(attribute); std::vector begin(sliceDesc->m_Begin.begin(), sliceDesc->m_Begin.end()); std::vector size(sliceDesc->m_Size.begin(), sliceDesc->m_Size.end()); - CHECK(begin == reshapeAttribute.start()); - CHECK(size == reshapeAttribute.size()); + CHECK(begin == sliceAttribute.start()); + CHECK(size == sliceAttribute.size()); CHECK(begin.size() == inputShape.size()); CHECK(size.size() == inputShape.size()); diff --git a/src/backends/tosaReference/TosaRefLayerSupport.cpp b/src/backends/tosaReference/TosaRefLayerSupport.cpp index e1c349feb4..04be52d25b 100644 --- a/src/backends/tosaReference/TosaRefLayerSupport.cpp +++ b/src/backends/tosaReference/TosaRefLayerSupport.cpp @@ -71,6 +71,7 @@ bool TosaRefLayerSupport::IsLayerSupported(const LayerType& type, case LayerType::ElementwiseUnary: case LayerType::Pooling2d: case LayerType::Reshape: + case LayerType::Resize: case LayerType::Slice: case LayerType::Transpose: inputInfos.push_back(&infos[0]); diff --git a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp index 26cadd22db..b35dacb1a1 100644 --- a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp +++ b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp @@ -11,6 +11,7 @@ #include "backendsCommon/test/MultiplicationEndToEndTestImpl.hpp" #include "backendsCommon/test/Pooling2dEndToEndTestImpl.hpp" #include "backendsCommon/test/ReshapeEndToEndTestImpl.hpp" +#include "backendsCommon/test/ResizeEndToEndTestImpl.hpp" #include "backendsCommon/test/ElementwiseUnaryEndToEndTestImpl.hpp" #include "backendsCommon/test/SliceEndToEndTestImpl.hpp" #include "backendsCommon/test/SubtractionEndToEndTestImpl.hpp" @@ -145,6 +146,47 @@ TEST_CASE("TosaRefRsqrtEndtoEndTestFloat32") UnaryOperation::Rsqrt); } +// Resize +TEST_CASE("TosaRefResizeNearestNeighborEndToEndFloat32AlignCornersNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, true, false); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndFloat32HalfPixelNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, false, true); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndFloat16AlignCornersNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, true, false); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndFloat16HalfPixelNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, false, true); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndInt8AlignCornersNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, true, false); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndInt8HalfPixelNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, false, true); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndInt16AlignCornersNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, true, false); +} + +TEST_CASE("TosaRefResizeNearestNeighborEndToEndInt16HalfPixelNhwcTest") +{ + ResizeNearestNeighborEndToEnd(tosaDefaultBackends, armnn::DataLayout::NHWC, false, true); +} + // Slice TEST_CASE("TosaRefSliceEndtoEndTestFloat32") { diff --git a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp index e32894f0b6..fb4c84f07c 100644 --- a/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp +++ b/src/backends/tosaReference/test/TosaRefLayerSupportTests.cpp @@ -352,6 +352,30 @@ TEST_CASE("IsLayerSupportedTosaReferenceReshape") CHECK(supported); } +TEST_CASE("IsLayerSupportedTosaReferenceResize") +{ + TensorShape inShape = { 1, 720, 1280, 3 }; + TensorShape outShape = { 1, 1080, 1920, 3 }; + TensorInfo in(inShape, DataType::Float32); + TensorInfo out(outShape, DataType::Float32); + + ResizeDescriptor descriptor; + descriptor.m_DataLayout = armnn::DataLayout::NHWC; + descriptor.m_TargetHeight = 1080; + descriptor.m_TargetWidth = 1920; + + TosaRefLayerSupport supportChecker; + std::string reasonIfNotSupported; + auto supported = supportChecker.IsLayerSupported(LayerType::Resize, + {in, out}, + descriptor, + EmptyOptional(), + EmptyOptional(), + reasonIfNotSupported); + + CHECK(supported); +} + TEST_CASE("IsLayerSupportedTosaReferenceReshapeUnsupported") { TensorShape inShape = {3,4}; diff --git a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp index 8b08f01b23..5e4103aed1 100644 --- a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp +++ b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.cpp @@ -50,9 +50,15 @@ void TosaRefPreCompiledWorkload::Execute() const SetInput(runner, inputNames[inputSlotIdx], inputSlotIdx); break; case DataType::QAsymmU8: + SetInput(runner, inputNames[inputSlotIdx], inputSlotIdx); + break; case DataType::QAsymmS8: case DataType::QSymmS8: + SetInput(runner, inputNames[inputSlotIdx], inputSlotIdx); + break; case DataType::QSymmS16: + SetInput(runner, inputNames[inputSlotIdx], inputSlotIdx); + break; case DataType::Signed32: SetInput(runner, inputNames[inputSlotIdx], inputSlotIdx); break; @@ -87,9 +93,15 @@ void TosaRefPreCompiledWorkload::Execute() const GetOutput(runner, outputNames[outputSlotIdx], outputSlotIdx); break; case DataType::QAsymmU8: + GetOutput(runner, outputNames[outputSlotIdx], outputSlotIdx); + break; case DataType::QAsymmS8: case DataType::QSymmS8: + GetOutput(runner, outputNames[outputSlotIdx], outputSlotIdx); + break; case DataType::QSymmS16: + GetOutput(runner, outputNames[outputSlotIdx], outputSlotIdx); + break; case DataType::Signed32: GetOutput(runner, outputNames[outputSlotIdx], outputSlotIdx); break; @@ -106,14 +118,27 @@ void TosaRefPreCompiledWorkload::Execute() const } template +void TosaRefPreCompiledWorkload::SetInput(TosaReference::IModelRunner& runner, + std::string inputName, + uint32_t inputIndex) const +{ + SetInput(runner, inputName, inputIndex); +} + +template void TosaRefPreCompiledWorkload::SetInput(TosaReference::IModelRunner& runner, std::string inputName, uint32_t inputIndex) const { std::vector inputData(m_Data.m_Inputs[inputIndex]->GetShape().GetNumElements()); + std::vector inputDataRunner(m_Data.m_Inputs[inputIndex]->GetShape().GetNumElements()); + m_Data.m_Inputs[inputIndex]->CopyOutTo(inputData.data()); - runner.setInput(inputName, inputData); + std::transform(inputData.begin(), inputData.end(), + inputDataRunner.begin(), [](T x) { return static_cast(x);}); + + runner.setInput(inputName, inputDataRunner); } template @@ -121,7 +146,19 @@ void TosaRefPreCompiledWorkload::GetOutput(TosaReference::IModelRunner& runner, std::string outputName, uint32_t outputIndex) const { - std::vector actualOutputs = runner.getOutput(outputName); + GetOutput(runner, outputName, outputIndex); +} + +template +void TosaRefPreCompiledWorkload::GetOutput(TosaReference::IModelRunner& runner, + std::string outputName, + uint32_t outputIndex) const +{ + std::vector actualOutputsRunner = runner.getOutput(outputName); + std::vector actualOutputs (actualOutputsRunner.size()); + + std::transform(actualOutputsRunner.begin(), actualOutputsRunner.end(), + actualOutputs.begin(), [](Trunner x) { return static_cast(x);}); m_Data.m_Outputs[outputIndex]->CopyInFrom(actualOutputs.data()); } diff --git a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp index 2b3a314888..337e8f9572 100644 --- a/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp +++ b/src/backends/tosaReference/workloads/TosaRefPreCompiledWorkload.hpp @@ -42,9 +42,15 @@ private: this->m_Data.m_Outputs[slot] = tensorHandle; } + template + void SetInput(TosaReference::IModelRunner& runner, std::string inputName, uint32_t inputIndex) const; + template void SetInput(TosaReference::IModelRunner& runner, std::string inputName, uint32_t inputIndex) const; + template + void GetOutput(TosaReference::IModelRunner& runner, std::string outputName, uint32_t outputIndex) const; + template void GetOutput(TosaReference::IModelRunner& runner, std::string outputName, uint32_t outputIndex) const; -- cgit v1.2.1