/* Copyright 2018 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 #include #include #include "Eigen/Core" // from @eigen_archive #include "tensorflow/lite/core/interpreter.h" #include "tensorflow/lite/kernels/test_util.h" #include "tensorflow/lite/schema/schema_generated.h" namespace tflite { namespace ops { namespace builtin { TfLiteRegistration* Register_DEQUANTIZE(); } // namespace builtin } // namespace ops namespace { using ::testing::ElementsAreArray; class DequantizeOpModel : public SingleOpModel { public: explicit DequantizeOpModel() {} DequantizeOpModel(TensorType type, std::initializer_list shape, float scale, int32_t zero_point, int version) { const TensorData input_tensor_data = {type, shape, 0, 0, scale, zero_point}; input_ = AddInput(input_tensor_data); output_ = AddOutput({TensorType_FLOAT32, shape}); SetBuiltinOp(BuiltinOperator_DEQUANTIZE, BuiltinOptions_DequantizeOptions, CreateDequantizeOptions(builder_).Union()); resolver_ = std::make_unique( BuiltinOperator_DEQUANTIZE, ops::builtin::Register_DEQUANTIZE(), version); BuildInterpreter({GetShape(input_)}); } template void SetInput(std::initializer_list data) { PopulateTensor(input_, data); } template void SetInputInt4(int input, const std::vector data) { auto non_const = *const_cast*>(&data); std::vector data_int8(non_const.size()); std::copy(non_const.begin(), non_const.end(), data_int8.begin()); PopulateTensor4bit(input, 0, data_int8.data(), data_int8.data() + data_int8.size()); } template void SetInputInt2(int input, const std::vector data) { auto non_const = *const_cast*>(&data); std::vector data_int8(non_const.size()); std::copy(non_const.begin(), non_const.end(), data_int8.begin()); PopulateTensor2bit(input, 0, data_int8.data(), data_int8.data() + data_int8.size()); } std::vector GetOutput() { return ExtractVector(output_); } protected: int input_; int output_; }; TEST(DequantizeOpTest, Int4) { // [-3.5, 4] -> scale=0.5, zero_point=1 for INT4 DequantizeOpModel m(TensorType_INT4, {2, 2}, 0.5, -1, 6); m.SetInputInt4(0, {7, 6, -7, -8}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({4, 3.5, -3, -3.5}))); } TEST(DequantizeOpTest, Uint4) { // [0, 7.5] -> scale=0.5, zero_point=0 for UINT4 DequantizeOpModel m(TensorType_UINT4, {2, 2}, 0.5, 0, 8); m.SetInputInt4(0, {15, 14, 1, 0}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({7.5, 7.0, 0.5, 0.0}))); } TEST(DequantizeOpTest, Int2) { DequantizeOpModel m(TensorType_INT2, {1, 4}, 0.5, -1, 6); m.SetInputInt2(0, {1, 0, -1, -2}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({1.0, 0.5, 0.0, -0.5}))); } TEST(DequantizeOpTest, Uint8) { // [-63.5, 64] -> scale=0.5 zero_point=127 for UINT8 DequantizeOpModel m(TensorType_UINT8, {2, 5}, 0.5, 127, 1); m.SetInput({0, 1, 2, 3, 4, 251, 252, 253, 254, 255}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64}))); } TEST(DequantizeOpTest, Int8) { // [-63.5, 64] -> scale=0.5, zero_point=1 for INT8 DequantizeOpModel m(TensorType_INT8, {2, 5}, 0.5, -1, 2); m.SetInput({-128, -127, -126, -125, -124, 123, 124, 125, 126, 127}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64}))); } TEST(DequantizeOpTest, Float16) { DequantizeOpModel m(TensorType_FLOAT16, {2, 3}, 1.0f, 0, 3); std::vector half{Eigen::half{-535.54f}, Eigen::half{-100.0f}, Eigen::half{-1.0f}, Eigen::half{0.f}, Eigen::half{1.0f}, Eigen::half{100.32f}}; m.PopulateTensor(0, 0, reinterpret_cast(half.data()), reinterpret_cast(half.data()) + half.size()); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( {-535.54f, -100.0f, -1.0f, 0.f, 1.0f, 100.32f}, /*max_abs_err=*/0.1f))); } TEST(DequantizeOpTest, Int16) { DequantizeOpModel m(TensorType_INT16, {2, 5}, 0.5, 0, 4); m.SetInput({-129, -126, -125, -124, -123, 124, 125, 126, 127, 131}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( {-64.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 65.5}))); } class DequantizePerChannelOpModel : public DequantizeOpModel { public: DequantizePerChannelOpModel(TensorType type, std::initializer_list shape, std::initializer_list scales, std::initializer_list zero_points, int channel_dim, int version) { std::vector per_channel_scales(scales); std::vector input_offsets(zero_points); const TensorData input_tensor_data = { type, shape, 0, 0, 0.0f, 0, true, per_channel_scales, input_offsets, channel_dim}; input_ = AddInput(input_tensor_data); output_ = AddOutput({TensorType_FLOAT32, shape}); SetBuiltinOp(BuiltinOperator_DEQUANTIZE, BuiltinOptions_DequantizeOptions, CreateDequantizeOptions(builder_).Union()); resolver_ = std::make_unique( BuiltinOperator_DEQUANTIZE, ops::builtin::Register_DEQUANTIZE(), version); BuildInterpreter({GetShape(input_)}); } }; TEST(DequantizePerChannelOpTest, Uint8) { // [-63.5, 64] -> scale=0.5 zero_point=127 for UINT8 DequantizePerChannelOpModel m(TensorType_UINT8, {2, 5}, {0.5, 0.5}, {127, 127}, 0, 5); m.SetInput({0, 1, 2, 3, 4, 251, 252, 253, 254, 255}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64}))); } TEST(DequantizePerChannelOpTest, Int8) { // [-63.5, 64] -> scale=0.5, zero_point=1 for INT8 DequantizePerChannelOpModel m(TensorType_INT8, {2, 5}, {0.5, 0.5}, {-1, -1}, 0, 5); m.SetInput({-128, -127, -126, -125, -124, 123, 124, 125, 126, 127}); ASSERT_EQ(m.Invoke(), kTfLiteOk); EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear( {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64}))); } TEST(DequantizePerChannelOpTest, Int2) { // scales={0.5, 1.0}, zero_points={-1, 0}, channel_dim=0 DequantizePerChannelOpModel m(TensorType_INT2, {2, 2}, {0.5, 1.0}, {-1, 0}, 0, 6); m.SetInputInt2(0, {1, 0, -1, -2}); ASSERT_EQ(m.Invoke(), kTfLiteOk); // Dequantization formula: (val - zp) * scale // Channel 0: scale=0.5, zp=-1. // val=1: (1 - (-1)) * 0.5 = 1.0 // val=0: (0 - (-1)) * 0.5 = 0.5 // Channel 1: scale=1.0, zp=0 // val=-1: (-1 - 0) * 1.0 = -1.0 // val=-2: (-2 - 0) * 1.0 = -2.0 EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({1.0, 0.5, -1.0, -2.0}))); } TEST(DequantizePerChannelOpTest, Uint4) { // scales={0.5, 1.0}, zero_points={0, 1}, channel_dim=0 DequantizePerChannelOpModel m(TensorType_UINT4, {2, 2}, {0.5, 1.0}, {0, 1}, 0, 8); m.SetInputInt4(0, {15, 1, 15, 1}); ASSERT_EQ(m.Invoke(), kTfLiteOk); // Channel 0: scale=0.5, zp=0 // val=15: (15 - 0) * 0.5 = 7.5 // val=1: (1 - 0) * 0.5 = 0.5 // Channel 1: scale=1.0, zp=1 // val=15: (15 - 1) * 1.0 = 14.0 // val=1: (1 - 1) * 1.0 = 0.0 EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({7.5, 0.5, 14.0, 0.0}))); } } // namespace } // namespace tflite