From 185dd2376bea59385e631e91255ad01cee1d1bac Mon Sep 17 00:00:00 2001 From: Sachin Joglekar Date: Mon, 9 Mar 2020 15:52:47 -0700 Subject: [PATCH] Adds kernel impl for dequantizing per-channel quantized tensors to float PiperOrigin-RevId: 299955689 Change-Id: I03c7b9693a2b86518b4b8dd9303b231fc4c025ee --- tensorflow/lite/kernels/internal/BUILD | 11 ++ .../internal/per_channel_dequantize_test.cc | 121 ++++++++++++++++++ .../kernels/internal/reference/dequantize.h | 31 +++++ tensorflow/lite/kernels/internal/types.h | 6 + 4 files changed, 169 insertions(+) create mode 100644 tensorflow/lite/kernels/internal/per_channel_dequantize_test.cc diff --git a/tensorflow/lite/kernels/internal/BUILD b/tensorflow/lite/kernels/internal/BUILD index c7a76d79e48..50e5c1805f3 100644 --- a/tensorflow/lite/kernels/internal/BUILD +++ b/tensorflow/lite/kernels/internal/BUILD @@ -1028,6 +1028,17 @@ cc_test( ], ) +cc_test( + name = "per_channel_dequantize_test", + srcs = ["per_channel_dequantize_test.cc"], + deps = [ + ":reference_base", + ":types", + "//tensorflow/lite/kernels:test_util", + "@com_google_googletest//:gtest_main", + ], +) + exports_files(["optimized/eigen_tensor_reduced_instantiations_oss.h"]) filegroup( diff --git a/tensorflow/lite/kernels/internal/per_channel_dequantize_test.cc b/tensorflow/lite/kernels/internal/per_channel_dequantize_test.cc new file mode 100644 index 00000000000..3ad125b86e4 --- /dev/null +++ b/tensorflow/lite/kernels/internal/per_channel_dequantize_test.cc @@ -0,0 +1,121 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +#include +#include + +#include +#include "tensorflow/lite/kernels/internal/reference/dequantize.h" +#include "tensorflow/lite/kernels/internal/types.h" +#include "tensorflow/lite/kernels/test_util.h" + +namespace tflite { +namespace { + +using ::testing::ElementsAreArray; + +TEST(PerChannelDequantize, TestInt8ToFloat_2D) { + const std::vector scales = {0.5, 0.25}; + const std::vector zero_points = {-1, -1}; + const int quantized_dimension = 0; + + const RuntimeShape shape({2, 5}); + + const std::vector input = {-128, -127, -126, -125, -124, + 123, 124, 125, 126, 127}; + std::vector output(10, -1); + + PerChannelDequantizationParams op_params; + op_params.zero_point = zero_points.data(); + op_params.scale = scales.data(); + op_params.quantized_dimension = quantized_dimension; + reference_ops::PerChannelDequantize(op_params, shape, input.data(), shape, + output.data()); + EXPECT_THAT(output, + ElementsAreArray(ArrayFloatNear({-63.5, -63, -62.5, -62, -61.5, + 31, 31.25, 31.5, 31.75, 32}))); +} + +TEST(PerChannelDequantize, TestInt8ToFloat_3D) { + const std::vector scales = {0.5, 0.25, 0.5, 0.25, 1.0}; + const std::vector zero_points = {-1, 1, -1, 1, 0}; + const int quantized_dimension = 2; + + const RuntimeShape shape({1, 2, 5}); + + const std::vector input = {-128, -127, -126, -125, -124, + 123, 124, 125, 126, 127}; + std::vector output(10, -1); + + PerChannelDequantizationParams op_params; + op_params.zero_point = zero_points.data(); + op_params.scale = scales.data(); + op_params.quantized_dimension = quantized_dimension; + reference_ops::PerChannelDequantize(op_params, shape, input.data(), shape, + output.data()); + EXPECT_THAT(output, + ElementsAreArray(ArrayFloatNear({-63.5, -32, -62.5, -31.5, -124, + 62, 30.75, 63, 31.25, 127}))); +} + +TEST(PerChannelDequantize, TestInt8ToFloat_4DDim0) { + const std::vector scales = {0.5, 0.25}; + const std::vector zero_points = {-1, 1}; + const int quantized_dimension = 0; + + RuntimeShape shape({2, 2, 5, 1}); + + const std::vector input = {-128, -127, -126, -125, -124, 123, 124, + 125, 126, 127, -128, -127, -126, -125, + -124, 123, 124, 125, 126, 127}; + std::vector output(20, -1); + + PerChannelDequantizationParams op_params; + op_params.zero_point = zero_points.data(); + op_params.scale = scales.data(); + op_params.quantized_dimension = quantized_dimension; + reference_ops::PerChannelDequantize(op_params, shape, input.data(), shape, + output.data()); + EXPECT_THAT(output, ElementsAreArray(ArrayFloatNear( + {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, + 63, 63.5, 64, -32.25, -32, -31.75, -31.5, + -31.25, 30.5, 30.75, 31, 31.25, 31.5}))); +} + +TEST(PerChannelDequantize, TestInt8ToFloat_4DDim3) { + const std::vector scales = {0.5, 0.25, 0.5, 0.25, 1.0}; + const std::vector zero_points = {-1, 1, -1, 1, 0}; + const int quantized_dimension = 3; + + RuntimeShape shape({1, 2, 2, 5}); + + const std::vector input = {-128, -127, -126, -125, -124, 123, 124, + 125, 126, 127, -128, -127, -126, -125, + -124, 123, 124, 125, 126, 127}; + std::vector output(20, -1); + + PerChannelDequantizationParams op_params; + op_params.zero_point = zero_points.data(); + op_params.scale = scales.data(); + op_params.quantized_dimension = quantized_dimension; + reference_ops::PerChannelDequantize(op_params, shape, input.data(), shape, + output.data()); + EXPECT_THAT(output, ElementsAreArray(ArrayFloatNear( + {-63.5, -32, -62.5, -31.5, -124, 62, 30.75, + 63, 31.25, 127, -63.5, -32, -62.5, -31.5, + -124, 62, 30.75, 63, 31.25, 127}))); +} + +} // namespace +} // namespace tflite diff --git a/tensorflow/lite/kernels/internal/reference/dequantize.h b/tensorflow/lite/kernels/internal/reference/dequantize.h index 6bc338c8e06..6bedcba1044 100644 --- a/tensorflow/lite/kernels/internal/reference/dequantize.h +++ b/tensorflow/lite/kernels/internal/reference/dequantize.h @@ -17,6 +17,8 @@ limitations under the License. #include +#include + #include "tensorflow/lite/kernels/internal/common.h" #include "tensorflow/lite/kernels/internal/types.h" @@ -60,6 +62,35 @@ inline void DequantizeInteger(const tflite::DequantizationParams& op_params, } } +// Dequantizes per-channel quantized tensor to float. +template +inline void PerChannelDequantize( + const tflite::PerChannelDequantizationParams& op_params, + const RuntimeShape& input_shape, const T* input_data, + const RuntimeShape& output_shape, float* output_data) { + // Ensure flat size is same. + MatchingFlatSize(input_shape, output_shape); + + const int32* zero_point = op_params.zero_point; + const float* scale = op_params.scale; + const int32 quantized_dimension = op_params.quantized_dimension; + const int32 num_dims = input_shape.DimensionsCount(); + const int32* dims_data = input_shape.DimsData(); + std::vector current_dim(num_dims, 0); + + do { + size_t offset = + ReducedOutputOffset(num_dims, reinterpret_cast(dims_data), + current_dim.data(), 0, nullptr); + const int channel = current_dim[quantized_dimension]; + const int32 val = input_data[offset]; + const float result = + static_cast(scale[channel] * (val - zero_point[channel])); + output_data[offset] = result; + } while (NextIndex(num_dims, reinterpret_cast(dims_data), + current_dim.data())); +} + } // namespace reference_ops } // namespace tflite diff --git a/tensorflow/lite/kernels/internal/types.h b/tensorflow/lite/kernels/internal/types.h index 5abd679d9bd..9b93e8156e5 100644 --- a/tensorflow/lite/kernels/internal/types.h +++ b/tensorflow/lite/kernels/internal/types.h @@ -863,6 +863,12 @@ struct DequantizationParams { int32 zero_point; }; +struct PerChannelDequantizationParams { + const float* scale; + const int32* zero_point; + int32 quantized_dimension; +}; + struct FakeQuantParams { MinMax minmax; int32 num_bits;