From ca5a23a7cbe46b8da8de432d80889c47a745ca4c Mon Sep 17 00:00:00 2001 From: Teresa Charlin Date: Fri, 15 Dec 2023 14:20:47 +0000 Subject: Add Quantize Support to TOSA Ref Backend * Adding a one to many tosa mapping for Quantize * Added tests * Resolves IVGCVSW-7175 Signed-off-by: John Mcloughlin Signed-off-by: Teresa Charlin Change-Id: Ia0852fefb618b4a29c2601b9de8b6b2731229801 --- src/armnn/Network.cpp | 1 - src/backends/backendsCommon/test/CMakeLists.txt | 1 + .../test/DequantizeEndToEndTestImpl.hpp | 2 +- .../test/QuantizationEndToEndTestImpl.hpp | 108 ++++++++++++++++ src/backends/reference/test/RefEndToEndTests.cpp | 49 ++++++-- src/backends/tosaCommon/TosaMappings.cpp | 4 + .../tosaCommon/operatorMappings/CMakeLists.txt | 2 + .../operatorMappings/QuantizeOperator.cpp | 139 +++++++++++++++++++++ .../operatorMappings/QuantizeOperator.hpp | 16 +++ .../operatorMappings/TosaCommonOperators.hpp | 1 + .../operatorMappings/TosaOperatorUtils.hpp | 101 ++++++++++++++- .../tosaCommon/test/OneToManyMappingTests.cpp | 42 +++++++ src/backends/tosaCommon/test/QuantizeChecker.hpp | 105 ++++++++++++++++ src/backends/tosaReference/TosaRefLayerSupport.cpp | 1 + .../tosaReference/test/TosaRefEndToEndTests.cpp | 32 +++++ 15 files changed, 593 insertions(+), 11 deletions(-) create mode 100644 src/backends/backendsCommon/test/QuantizationEndToEndTestImpl.hpp create mode 100644 src/backends/tosaCommon/operatorMappings/QuantizeOperator.cpp create mode 100644 src/backends/tosaCommon/operatorMappings/QuantizeOperator.hpp create mode 100644 src/backends/tosaCommon/test/QuantizeChecker.hpp (limited to 'src') diff --git a/src/armnn/Network.cpp b/src/armnn/Network.cpp index 4f82f20aa2..2582403247 100644 --- a/src/armnn/Network.cpp +++ b/src/armnn/Network.cpp @@ -2004,7 +2004,6 @@ IOptimizedNetworkPtr Optimize(const Graph& inGraph, FuseBatchNormIntoDepthwiseConvolution2DFloat32(), FuseBatchNormIntoDepthwiseConvolution2DFloat16())); - // Initialize backend settings BackendSettings backendSettings(backendPreferences, deviceSpec); auto availablePreferredBackends = backendSettings.GetAvailablePreferredBackends(); diff --git a/src/backends/backendsCommon/test/CMakeLists.txt b/src/backends/backendsCommon/test/CMakeLists.txt index ed95bcf399..264381d171 100644 --- a/src/backends/backendsCommon/test/CMakeLists.txt +++ b/src/backends/backendsCommon/test/CMakeLists.txt @@ -48,6 +48,7 @@ list(APPEND armnnBackendsCommonUnitTests_sources PreluEndToEndTestImpl.hpp QLstmEndToEndTestImpl.cpp QLstmEndToEndTestImpl.hpp + QuantizationEndToEndTestImpl.hpp QuantizedLstmEndToEndTestImpl.cpp QuantizedLstmEndToEndTestImpl.hpp RankEndToEndTestImpl.hpp diff --git a/src/backends/backendsCommon/test/DequantizeEndToEndTestImpl.hpp b/src/backends/backendsCommon/test/DequantizeEndToEndTestImpl.hpp index 82fceb81aa..a3c3a82602 100644 --- a/src/backends/backendsCommon/test/DequantizeEndToEndTestImpl.hpp +++ b/src/backends/backendsCommon/test/DequantizeEndToEndTestImpl.hpp @@ -1,5 +1,5 @@ // -// Copyright © 2019,2021,2023 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2017,2019,2021,2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // diff --git a/src/backends/backendsCommon/test/QuantizationEndToEndTestImpl.hpp b/src/backends/backendsCommon/test/QuantizationEndToEndTestImpl.hpp new file mode 100644 index 0000000000..f5c2eea601 --- /dev/null +++ b/src/backends/backendsCommon/test/QuantizationEndToEndTestImpl.hpp @@ -0,0 +1,108 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include + +#include + +#include + +#include + +namespace +{ + +armnn::INetworkPtr CreateQuantizationNetwork(const armnn::TensorInfo& inputInfo, + const armnn::TensorInfo& outputInfo) +{ + using namespace armnn; + + INetworkPtr network(INetwork::Create()); + + IConnectableLayer *input= network->AddInputLayer(0, "input"); + IConnectableLayer *quantization = network->AddQuantizeLayer("quantization"); + IConnectableLayer *output = network->AddOutputLayer(0, "output"); + + Connect(input, quantization, inputInfo, 0, 0); + Connect(quantization, output, outputInfo, 0, 0); + + return network; +} + +template, typename Tout = armnn::ResolveType> +void QuantizeEndToEndLayerTestImpl(const std::vector& backends, + const armnn::TensorShape& tensorShape, + const std::vector& input, + const std::vector& expectedOutput, + float scale, + int32_t offset) +{ + using namespace armnn; + + TensorInfo inputInfo(tensorShape, ArmnnIType); + TensorInfo outputInfo(tensorShape, ArmnnOType, scale, offset); + + inputInfo.SetConstant(true); + + // Builds up the structure of the network + INetworkPtr net = CreateQuantizationNetwork(inputInfo, outputInfo); + + CHECK(net); + + const std::map> inputTensorData = { { 0, input } }; + const std::map> expectedOutputData = { { 0, expectedOutput } }; + + EndToEndLayerTestImpl(std::move(net), inputTensorData, expectedOutputData, backends); +} + +template> +void QuantizationEndToEndFloat32(const std::vector& backends) +{ + using namespace armnn; + + const TensorShape tensorShape({ 1, 1, 1, 5 }); + + std::vector inputData = { 63.5f, 49.5f, 14.0f, 0.0f, 50.0f }; + + float qScale = 0.5f; + int32_t qOffset = 127; + std::vector expectedOutputData = armnnUtils::QuantizedVector(inputData, qScale, qOffset); + + QuantizeEndToEndLayerTestImpl(backends, + tensorShape, + inputData, + expectedOutputData, + qScale, + qOffset); +}; + +template> +void QuantizationEndToEndFloat16(const std::vector& backends) +{ + using namespace armnn; + using namespace half_float::literal; + using Half = half_float::half; + + const TensorShape tensorShape({ 1, 1, 1, 5 }); + + std::vector floatInputData = { 63.f, 49.f, 14.f, 0.f, 50.f }; + std::vector inputData = { 63._h, 49._h, 14._h, 0._h, 50._h }; + + float qScale = 0.25f; + int32_t qOffset = 1; + std::vector expectedOutputData = armnnUtils::QuantizedVector(floatInputData, qScale, qOffset); + + QuantizeEndToEndLayerTestImpl(backends, + tensorShape, + inputData, + expectedOutputData, + qScale, + qOffset); +}; + +} \ No newline at end of file diff --git a/src/backends/reference/test/RefEndToEndTests.cpp b/src/backends/reference/test/RefEndToEndTests.cpp index 8de763e07e..143d85ac9b 100644 --- a/src/backends/reference/test/RefEndToEndTests.cpp +++ b/src/backends/reference/test/RefEndToEndTests.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -792,42 +793,42 @@ TEST_CASE("RefGatherNdMultiDimInt16Test") } // DepthToSpace -TEST_CASE("DephtToSpaceEndToEndNchwFloat32") +TEST_CASE("DepthToSpaceEndToEndNchwFloat32") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NCHW); } -TEST_CASE("DephtToSpaceEndToEndNchwFloat16") +TEST_CASE("DepthToSpaceEndToEndNchwFloat16") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NCHW); } -TEST_CASE("DephtToSpaceEndToEndNchwUint8") +TEST_CASE("DepthToSpaceEndToEndNchwUint8") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NCHW); } -TEST_CASE("DephtToSpaceEndToEndNchwInt16") +TEST_CASE("DepthToSpaceEndToEndNchwInt16") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NCHW); } -TEST_CASE("DephtToSpaceEndToEndNhwcFloat32") +TEST_CASE("DepthToSpaceEndToEndNhwcFloat32") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NHWC); } -TEST_CASE("DephtToSpaceEndToEndNhwcFloat16") +TEST_CASE("DepthToSpaceEndToEndNhwcFloat16") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NHWC); } -TEST_CASE("DephtToSpaceEndToEndNhwcUint8") +TEST_CASE("DepthToSpaceEndToEndNhwcUint8") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NHWC); } -TEST_CASE("DephtToSpaceEndToEndNhwcInt16") +TEST_CASE("DepthToSpaceEndToEndNhwcInt16") { DepthToSpaceEndToEnd(defaultBackends, armnn::DataLayout::NHWC); } @@ -1064,6 +1065,38 @@ TEST_CASE("RefPreluEndToEndTestQSymm16") PreluEndToEndPositiveTest(defaultBackends); } +// Quantization +TEST_CASE("QuantizationEndToEndFloat32_U8Test") +{ + QuantizationEndToEndFloat32(defaultBackends); +} + +TEST_CASE("QuantizationEndToEndFloat32_I8Test") +{ + QuantizationEndToEndFloat32(defaultBackends); +} + +TEST_CASE("QuantizationEndToEndFloat32_S16Test") +{ + QuantizationEndToEndFloat32(defaultBackends); +} + +TEST_CASE("QuantizationEndToEndFloat16_U8Test") +{ + QuantizationEndToEndFloat16(defaultBackends); +} + +TEST_CASE("QuantizationEndToEndFloat16_I8Test") +{ + QuantizationEndToEndFloat16(defaultBackends); +} + +TEST_CASE("QuantizationEndToEndFloat16_S16Test") +{ + QuantizationEndToEndFloat16(defaultBackends); +} + +// SpaceToDepth TEST_CASE("RefSpaceToDepthNhwcEndToEndTest1") { SpaceToDepthNhwcEndToEndTest1(defaultBackends); diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp index dff266d793..6c6bff4087 100644 --- a/src/backends/tosaCommon/TosaMappings.cpp +++ b/src/backends/tosaCommon/TosaMappings.cpp @@ -74,6 +74,10 @@ TosaSerializationBasicBlock* GetTosaMapping(const Layer* layer, return ConvertPooling2DToTosaOperator(layer, inputs, outputs, poolDesc); } } + case LayerType::Quantize: + { + return ConvertQuantizeToTosaOperator(layer, inputs, outputs); + } case LayerType::Reshape: { auto reshapeDesc = PolymorphicDowncast(&descriptor); diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt index 279b193db7..b69463487b 100644 --- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt +++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt @@ -18,6 +18,8 @@ list(APPEND armnnTosaBackendOperators_sources ElementwiseUnaryOperator.hpp Pooling2DOperator.hpp Pooling2DOperator.cpp + QuantizeOperator.hpp + QuantizeOperator.cpp ReshapeOperator.hpp ReshapeOperator.cpp ResizeOperator.hpp diff --git a/src/backends/tosaCommon/operatorMappings/QuantizeOperator.cpp b/src/backends/tosaCommon/operatorMappings/QuantizeOperator.cpp new file mode 100644 index 0000000000..1107add6e9 --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/QuantizeOperator.cpp @@ -0,0 +1,139 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// +// Copyright © 2020 The TensorFlow Authors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// + +#include "QuantizeOperator.hpp" + +// This function is paraphrased from: +// tensorflow/compiler/mlir/tosa/transforms/legalize_common.cc from function convertQuantizeOp +TosaSerializationBasicBlock* ConvertQuantizeToTosaOperator(const Layer* layer, + const std::vector& inputs, + const std::vector& outputs) +{ + ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( inputs.size() == 1, + "ConvertQuantizeToTosaOperator: Quantize must have only one input" ); + ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( outputs.size() == 1, + "ConvertQuantizeToTosaOperator: Quantize must have only one output" ); + + std::string inputName = std::string("input0_"); + std::string outputNameZeroPoint = std::string("intermediate0_") + GetUniqueTosaMappingID(); + std::string outputNameScale = std::string("intermediate1_") + GetUniqueTosaMappingID(); + std::string outputNameMul = std::string("intermediate2_") + GetUniqueTosaMappingID(); + std::string outputNameAdd = std::string("intermediate3_") + GetUniqueTosaMappingID(); + std::string outputName = std::string("output0_"); + std::string blockName = std::string("Op_QUANTIZE_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); + } + + const TensorInfo inputInfo = *inputs[0]; + const TensorInfo outputInfo = *outputs[0]; + + // Extract quantization detail from Tensor + float zeroPoint = static_cast(outputInfo.GetQuantizationOffset()); + // No per axis support in Tensorflow TOSA code + float scale = outputInfo.GetQuantizationScale(); + + // As per the Tensorflow quantization specification + // Tensorflow TOSA code calculates quantization using multiplication by scale + // Armnn code calculates quantization using division by scale + // Invert scale factor passed from Armnn for tf TOSA code + scale = (scale != 0) ? (1 / scale) : scale; + + 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. + std::vector inputShape0; + DType inputDType0 = DType::DType_UNKNOWN; + if(inputName.find("input0_") != std::string::npos) + { + inputShape0 = GetTosaTensorShape(inputInfo.GetShape()); + inputDType0 = ArmNNToDType(inputInfo.GetDataType()); + ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( inputDType0 == DType::DType_FP16 || inputDType0 == DType::DType_FP32, + "ConvertQuantizeToTosaOperator: Quantize input must be of type Float" ); + tensors.push_back(new TosaSerializationTensor(inputName, inputShape0, inputDType0, {})); + } + + std::vector outputShape0 = GetTosaTensorShape(outputInfo.GetShape()); + DType outputDType0 = ArmNNToDType(outputInfo.GetDataType()); + + // quantize: + // const_zeroPoint = constant(zeroPoint) + // const_scale = constant(scale) + // out_mul = mul(input, const_scale) + // out_add = add(out_mul, const_zeroPoint) + // output = cast(out_add) + + // const_zeroPoint + TosaSerializationOperator* zeroPointOp = nullptr; + TosaSerializationTensor* zeroPointTensor = nullptr; + CreateConstTosaOperator(outputNameZeroPoint, + zeroPoint, + inputDType0, + inputShape0, + zeroPointOp, + zeroPointTensor); + tensors.push_back(zeroPointTensor); + + // const_scale + TosaSerializationOperator *scaleOp = nullptr; + TosaSerializationTensor* scaleTensor = nullptr; + CreateConstTosaOperator(outputNameScale, + scale, + inputDType0, + inputShape0, + scaleOp, + scaleTensor); + tensors.push_back(scaleTensor); + + // mul + int32_t shift = 0; + TosaMulAttribute mulAttribute(shift); + TosaSerializationOperator* mulOp = new TosaSerializationOperator(Op_MUL, + Attribute_MulAttribute, + &mulAttribute, + {inputName, outputNameScale}, + {outputNameMul}); + tensors.push_back(new TosaSerializationTensor(outputNameMul, inputShape0, inputDType0, {})); + + // add + TosaSerializationOperator* addOp = new TosaSerializationOperator(Op_ADD, + Attribute_NONE, + nullptr, + {outputNameMul, outputNameZeroPoint}, + {outputNameAdd}); + tensors.push_back(new TosaSerializationTensor(outputNameAdd, inputShape0, inputDType0, {})); + + // cast + TosaSerializationOperator* castOp = new TosaSerializationOperator(Op_CAST, + Attribute_NONE, + nullptr, + {outputNameAdd}, + {outputName}); + + tensors.push_back(new TosaSerializationTensor(outputName, outputShape0, outputDType0, {})); + + // 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 + {zeroPointOp, scaleOp, mulOp, addOp, castOp}, // operators + tensors, // tensors + {inputName}, // inputs + {outputName}); // outputs +} diff --git a/src/backends/tosaCommon/operatorMappings/QuantizeOperator.hpp b/src/backends/tosaCommon/operatorMappings/QuantizeOperator.hpp new file mode 100644 index 0000000000..895c091391 --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/QuantizeOperator.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* ConvertQuantizeToTosaOperator(const Layer* layer, + const std::vector& inputs, + const std::vector& outputs); \ No newline at end of file diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp index 749e8764aa..3d88b6563f 100644 --- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp +++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp @@ -12,6 +12,7 @@ #include "ElementwiseBinaryOperator.hpp" #include "ElementwiseUnaryOperator.hpp" #include "Pooling2DOperator.hpp" +#include "QuantizeOperator.hpp" #include "ReshapeOperator.hpp" #include "ResizeOperator.hpp" #include "SliceOperator.hpp" diff --git a/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp b/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp index 3e106e1fd5..e43f6ca027 100644 --- a/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.hpp +++ b/src/backends/tosaCommon/operatorMappings/TosaOperatorUtils.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 // @@ -354,3 +354,102 @@ inline std::vector ConvertConstantTensorDataToBuffer(const std::shared_ tensorHandle->Unmap(); return uint8Data; } + + +inline std::vector CreateConstTosaData(const void* value, + DType dtype, + const std::vector& shape) +{ + std::vector uint8Data; + tosa_err_t error = tosa_err_t::TOSA_OK; + + unsigned int numElements = 1; + for (auto s : shape) + { + if (s < 0) + { + throw armnn::Exception("CreateConstTosaData: negative shape elements unhandled."); + } + numElements = numElements * static_cast(s); + } + + switch (dtype) + { + case DType::DType_FP32: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertF32toU8(data, uint8Data); + break; + } + case DType::DType_FP16: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertF16toU8(data, uint8Data); + break; + } + case DType::DType_INT48: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertI48toU8(data, uint8Data); + break; + } + case DType::DType_INT32: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertI32toU8(data, uint8Data); + break; + } + case DType::DType_INT16: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertI16toU8(data, uint8Data); + break; + } + case DType::DType_INT8: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertI8toU8(data, uint8Data); + break; + } + case DType::DType_INT4: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertI4toU8(data, uint8Data); + break; + } + case DType::DType_BOOL: + { + std::vector data(numElements, *static_cast(value)); + error = TosaSerializationHandler::ConvertBooltoU8(data, uint8Data); + break; + } + default: + { + throw armnn::Exception("CreateConstTosaData: An unsupported data type was encountered."); + } + } + + if(error != tosa_err_t::TOSA_OK) + { + throw armnn::Exception("CreateConstTosaData: An error occurred when converting constant data"); + } + + return uint8Data; +} + +template +inline void CreateConstTosaOperator(const std::string& outputName, + const T value, + DType dtype, + const std::vector& shape, + TosaSerializationOperator*& op, + TosaSerializationTensor*& tensor) +{ + std::vector uint8Data = CreateConstTosaData(static_cast(&value), dtype, shape); + + op = new TosaSerializationOperator(Op_CONST, Attribute_NONE, nullptr, {}, {outputName}); + ARMNN_THROW_MSG_IF_FALSE(op, armnn::Exception, "CreateConstTosaOperator: failed to created operator"); + + tensor = new TosaSerializationTensor(outputName, shape, dtype, uint8Data); + ARMNN_THROW_MSG_IF_FALSE(tensor, armnn::Exception, "CreateConstTosaOperator: failed to created tensor"); +} \ No newline at end of file diff --git a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp index 94dd537a30..f439b044a4 100644 --- a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp +++ b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp @@ -4,7 +4,9 @@ // #include "AvgPool2DIgnoreValueChecker.hpp" +#include "QuantizeChecker.hpp" #include "SplitChecker.hpp" + #include using namespace armnn; @@ -84,6 +86,46 @@ TEST_CASE("GetTosaMappingFromLayer_AvgPool2DIgnoreValueLayer") descriptor); } +TEST_CASE("GetTosaMapping_QuantizeLayer") +{ + NullDescriptor descriptor; + DataType outputDataType = DataType::Signed32; + + TensorInfo inputTensorInfo({ 1, 3, 3, 1 }, DataType::Float32); + TensorInfo outputTensorInfo({ 1, 3, 3, 1 }, outputDataType); + std::vector shape = { 1, 3, 3, 1 }; + + TosaSerializationBasicBlock* basicBlock = + GetTosaMapping(nullptr, LayerType::Quantize, {&inputTensorInfo}, {&outputTensorInfo}, descriptor); + VerifyQuantize(basicBlock, shape, ArmNNToDType(DataType::Float32), ArmNNToDType(outputDataType)); +} +TEST_CASE("GetTosaMappingFromLayer_QuantizeLayer") +{ + IRuntime::CreationOptions options; + IRuntimePtr runtime(IRuntime::Create(options)); + // Builds up the structure of the network. + INetworkPtr net(INetwork::Create()); + NullDescriptor descriptor; + DataType outputDataType = DataType::Signed32; + + IConnectableLayer* input0 = net->AddInputLayer(0, "input0"); + IConnectableLayer* quantize = net->AddQuantizeLayer("quantize"); + IConnectableLayer* output = net->AddOutputLayer(0, "output"); + + input0->GetOutputSlot(0).Connect(quantize->GetInputSlot(0)); + quantize->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + armnn::TensorInfo inputTensorInfo({ 1, 3, 3, 1 }, DataType::Float32); + armnn::TensorInfo outputTensorInfo({ 1, 3, 3, 1 }, outputDataType); + std::vector shape = { 1, 3, 3, 1 }; + + input0->GetOutputSlot(0).SetTensorInfo(inputTensorInfo); + quantize->GetOutputSlot(0).SetTensorInfo(outputTensorInfo); + + TosaSerializationBasicBlock* basicBlock = GetTosaMappingFromLayer(PolymorphicDowncast(quantize)); + VerifyQuantize(basicBlock, shape, ArmNNToDType(DataType::Float32), ArmNNToDType(outputDataType)); +} + TEST_CASE("GetTosaMapping_SplitLayer") { const unsigned int numViews = 3; diff --git a/src/backends/tosaCommon/test/QuantizeChecker.hpp b/src/backends/tosaCommon/test/QuantizeChecker.hpp new file mode 100644 index 0000000000..1a35903934 --- /dev/null +++ b/src/backends/tosaCommon/test/QuantizeChecker.hpp @@ -0,0 +1,105 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "TosaTestUtils.hpp" + +using namespace armnn; +using namespace tosa; + +void VerifyQuantize(TosaSerializationBasicBlock* quantizeBlock, + std::vector shape, + DType inputDataType = DType_FP32, + DType outputDataType = DType_FP32) +{ + std::string blockStr = "Op_QUANTIZE_block_"; + CHECK(quantizeBlock->GetName().find(blockStr) != std::string::npos); + CHECK(quantizeBlock->GetInputs().size() == 1); + CHECK(quantizeBlock->GetOutputs().size() == 1); + CHECK(quantizeBlock->GetOperators().size() == 5); // MUL, CONST, ADD, CONST, CAST + CHECK(quantizeBlock->GetTensors().size() == 6); + + std::basic_string blockInputName = quantizeBlock->GetInputs()[0]; + std::basic_string blockOutputName = quantizeBlock->GetOutputs()[0]; + + // + // Verify Constants + // + TosaSerializationOperator* constZeroPointOp = quantizeBlock->GetOperators().at(0); + CHECK(constZeroPointOp->GetAttributeType() == Attribute_NONE); + CHECK(constZeroPointOp->GetOp() == tosa::Op_CONST); + + TosaSerializationOperator* constScaleOp = quantizeBlock->GetOperators().at(1); + CHECK(constScaleOp->GetAttributeType() == Attribute_NONE); + CHECK(constScaleOp->GetOp() == tosa::Op_CONST); + + // + // Verify Multiplication + // + ElementwiseBinaryDescriptor mulDescriptor(BinaryOperation::Mul); + TosaSerializationOperator* mulOp = quantizeBlock->GetOperators().at(2); + CHECK(mulOp->GetAttributeType() == tosa::Attribute_MulAttribute); + CHECK(mulOp->GetOp() == tosa::Op_MUL); + + CHECK(mulOp->GetInputTensorNames().size() == 2); + std::basic_string mulInputName0 = mulOp->GetInputTensorNames()[0]; + std::basic_string mulInputName1 = mulOp->GetInputTensorNames()[1]; + + CHECK(blockInputName == mulInputName0); + + TosaSerializationTensor* mulInputTensor0 = quantizeBlock->GetTensorByName(mulInputName0); + CHECK(mulInputTensor0->GetDtype() == inputDataType); + CHECK(mulInputTensor0->GetData().size() == 0); + CHECK(mulInputTensor0->GetShape() == shape); + + TosaSerializationTensor* mulInputTensor1 = quantizeBlock->GetTensorByName(mulInputName1); + CHECK(mulInputTensor1->GetShape() == shape); + + // + // Verify Addition + // + ElementwiseBinaryDescriptor addDescriptor(BinaryOperation::Add); + TosaSerializationOperator* addOp = quantizeBlock->GetOperators().at(3); + CHECK(addOp->GetAttributeType() == Attribute_NONE); + CHECK(addOp->GetOp() == tosa::Op_ADD); + + CHECK(addOp->GetInputTensorNames().size() == 2); + std::basic_string addInputName0 = addOp->GetInputTensorNames()[0]; + std::basic_string addInputName1 = addOp->GetInputTensorNames()[1]; + + TosaSerializationTensor* addInputTensor0 = quantizeBlock->GetTensorByName(addInputName0); + CHECK(addInputTensor0->GetDtype() == inputDataType); + CHECK(addInputTensor0->GetData().size() == 0); + CHECK(addInputTensor0->GetShape() == shape); + + TosaSerializationTensor* addInputTensor1 = quantizeBlock->GetTensorByName(addInputName1); + CHECK(addInputTensor1->GetShape() == shape); + + // + // Verify Cast + // + TosaSerializationOperator* castOp = quantizeBlock->GetOperators().at(4); + CHECK(castOp->GetAttributeType() == Attribute_NONE); + CHECK(castOp->GetOp() == tosa::Op_CAST); + + CHECK(castOp->GetInputTensorNames().size() == 1); + CHECK(castOp->GetOutputTensorNames().size() == 1); + + std::basic_string castInputName = castOp->GetInputTensorNames()[0]; + std::basic_string castOutputName = castOp->GetOutputTensorNames()[0]; + + TosaSerializationTensor* castInputTensor = quantizeBlock->GetTensorByName(castInputName); + CHECK(castInputTensor->GetDtype() == inputDataType); + CHECK(castInputTensor->GetData().size() == 0); + CHECK(castInputTensor->GetShape() == shape); + + TosaSerializationTensor* castOutputTensor = quantizeBlock->GetTensorByName(castOutputName); + CHECK(castOutputTensor->GetDtype() == outputDataType); + CHECK(castOutputTensor->GetData().size() == 0); + CHECK(castOutputTensor->GetShape() == shape); + + CHECK(blockOutputName == castOutputName); + + +} \ No newline at end of file diff --git a/src/backends/tosaReference/TosaRefLayerSupport.cpp b/src/backends/tosaReference/TosaRefLayerSupport.cpp index 60d0f7c6d6..a38c431e09 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::Quantize: case LayerType::Reshape: case LayerType::Resize: case LayerType::Slice: diff --git a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp index 05d4114382..914df766eb 100644 --- a/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp +++ b/src/backends/tosaReference/test/TosaRefEndToEndTests.cpp @@ -12,6 +12,7 @@ #include "backendsCommon/test/ElementwiseUnaryEndToEndTestImpl.hpp" #include "backendsCommon/test/MultiplicationEndToEndTestImpl.hpp" #include "backendsCommon/test/Pooling2dEndToEndTestImpl.hpp" +#include "backendsCommon/test/QuantizationEndToEndTestImpl.hpp" #include "backendsCommon/test/ReshapeEndToEndTestImpl.hpp" #include "backendsCommon/test/ResizeEndToEndTestImpl.hpp" #include "backendsCommon/test/SliceEndToEndTestImpl.hpp" @@ -133,6 +134,37 @@ TEST_CASE("TosaRefMaxPool2DIgnoreValueEndtoEndTestFloat32") MaxPool2dEndToEnd(tosaDefaultBackends, PaddingMethod::IgnoreValue); } +// Quantization +TEST_CASE("TosaRefQuantizeFromFloat32ToInt8") +{ + QuantizationEndToEndFloat32(tosaDefaultBackends); +} + +TEST_CASE("TosaRefQuantizeFromFloat32ToInt16") +{ + QuantizationEndToEndFloat32(tosaDefaultBackends); +} + +TEST_CASE("TosaRefQuantizeFromFloat32ToInt32") +{ + QuantizationEndToEndFloat32(tosaDefaultBackends); +} + +TEST_CASE("TosaRefQuantizeFromFloat16ToInt8") +{ + QuantizationEndToEndFloat16(tosaDefaultBackends); +} + +TEST_CASE("TosaRefQuantizeFromFloat16ToInt16") +{ + QuantizationEndToEndFloat16(tosaDefaultBackends); +} + +TEST_CASE("TosaRefQuantizeFromFloat16ToInt32") +{ + QuantizationEndToEndFloat16(tosaDefaultBackends); +} + // Reshape TEST_CASE("TosaRefReshapeEndtoEndTestFloat32") { -- cgit v1.2.1