/* 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 #include #include #include #include #include #include #include #include "absl/memory/memory.h" #include "tensorflow/lite/core/interpreter.h" #include "tensorflow/lite/kernels/test_util.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/string_type.h" namespace tflite { namespace ops { namespace builtin { TfLiteRegistration* Register_TRANSPOSECONV_REF(); TfLiteRegistration* Register_TRANSPOSECONV_GENERIC_OPT(); } // namespace builtin } // namespace ops namespace { using ::testing::ElementsAreArray; enum class TestType { kConst = 0, kDynamic = 1, }; template class BaseTransposeConvOpModel : public SingleOpModel { public: BaseTransposeConvOpModel(TfLiteRegistration* registration, std::initializer_list output_shape_data, const TensorData& filter, std::initializer_list filter_data, const TensorData& input, const TensorData& output, Padding padding, int stride_w, int stride_h, tflite::ActivationFunctionType fused_activation, TestType test_type, int version = 1, const TensorType& bias_type = TensorType_INT32) { // Just to be confusing, transpose_conv has an _input_ named "output_shape" // that sets the shape of the output tensor of the op :). It must always be // an int32 1D four element tensor. if (test_type == TestType::kDynamic) { output_shape_ = AddInput({TensorType_INT32, {4}}); filter_ = AddInput(filter); } else { output_shape_ = AddConstInput(TensorType_INT32, output_shape_data, {4}); filter_ = AddConstInput(filter, filter_data); } input_ = AddInput(input); output_ = AddOutput(output); SetBuiltinOp( BuiltinOperator_TRANSPOSE_CONV, BuiltinOptions_TransposeConvOptions, CreateTransposeConvOptions(builder_, padding, stride_w, stride_h, fused_activation, bias_type) .Union()); resolver_ = std::make_unique( BuiltinOperator_TRANSPOSE_CONV, registration, version); BuildInterpreter( {GetShape(output_shape_), GetShape(filter_), GetShape(input_)}); if (test_type == TestType::kDynamic) { PopulateTensor(output_shape_, output_shape_data); if (!std::is_same::value && !std::is_same::value) { PopulateTensor(filter_, filter_data); } } } void SetInput(std::initializer_list data) { if (std::is_same::value) { QuantizeAndPopulate(input_, data); } else if (std::is_same::value) { QuantizeAndPopulate(input_, data); } else if (std::is_same::value) { QuantizeAndPopulate(input_, data); } else { PopulateTensor(input_, data); } } std::vector GetOutputShape() { return GetTensorShape(output_); } protected: int output_shape_; int filter_; int input_; int output_; }; class TransposeConvOpModel : public BaseTransposeConvOpModel { public: using BaseTransposeConvOpModel::BaseTransposeConvOpModel; std::vector GetOutput() { return ExtractVector(output_); } }; template class PrepareOnlyTransposeConvOpModel : public SingleOpModel { public: PrepareOnlyTransposeConvOpModel( TfLiteRegistration* registration, std::initializer_list output_shape_data, const TensorData& filter, const TensorData& input, const TensorData& output, Padding padding, int stride_w, int stride_h, tflite::ActivationFunctionType fused_activation, int version = 1, const TensorType& bias_type = TensorType_INT32) { output_shape_ = AddConstInput(TensorType_INT32, output_shape_data, {4}); filter_ = AddInput(filter); input_ = AddInput(input); output_ = AddOutput(output); SetBuiltinOp( BuiltinOperator_TRANSPOSE_CONV, BuiltinOptions_TransposeConvOptions, CreateTransposeConvOptions(builder_, padding, stride_w, stride_h, fused_activation, bias_type) .Union()); resolver_ = std::make_unique( BuiltinOperator_TRANSPOSE_CONV, registration, version); BuildInterpreter( {GetShape(output_shape_), GetShape(filter_), GetShape(input_)}, /*num_threads=*/1, /*allow_fp32_relax_to_fp16=*/false, /*apply_delegate=*/false, /*allocate_and_delegate=*/false); } private: int output_shape_; int filter_; int input_; int output_; }; const auto kKernelMap = new std::map({ {"Reference", ops::builtin::Register_TRANSPOSECONV_REF()}, {"GenericOptimized", ops::builtin::Register_TRANSPOSECONV_GENERIC_OPT()}, }); class TransposeConvOpTest : public ::testing::TestWithParam> { public: TfLiteRegistration* GetRegistration() { return kKernelMap->at(std::get<0>(GetParam())); } TestType GetTestType() { return std::get<1>(GetParam()); } }; TEST(TransposeConvPrepareSecurityTest, RejectsCol2ImOverflow) { constexpr int kHugeDim = 46341; PrepareOnlyTransposeConvOpModel m( ops::builtin::Register_TRANSPOSECONV_GENERIC_OPT(), {1, 1, 1, 0}, {TensorType_UINT8, {1, 1, 1, 1}, 0.0f, 255.0f}, {TensorType_UINT8, {1, kHugeDim, kHugeDim, 1}, 0.0f, 255.0f}, {TensorType_UINT8, {}}, Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, ActivationFunctionType_NONE); EXPECT_EQ(m.AllocateTensors(), kTfLiteError); } TEST(TransposeConvPrepareSecurityTest, RejectsHybridInputOverflow) { if (sizeof(void*) <= 4) { GTEST_SKIP() << "Interpreter construction overflows before kernel Prepare " "on 32-bit."; } constexpr int kHugeDim = 46341; PrepareOnlyTransposeConvOpModel m( ops::builtin::Register_TRANSPOSECONV_GENERIC_OPT(), {1, 1, 1, 1}, {TensorType_INT8, {1, 1, 1, 1}, -1.0f, 1.0f}, {TensorType_FLOAT32, {kHugeDim, kHugeDim, 1, 1}}, {TensorType_FLOAT32, {}}, Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, ActivationFunctionType_NONE); EXPECT_EQ(m.AllocateTensors(), kTfLiteError); } // Test case: // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 4, 4, 1 ]), // tf.constant(np.arange(1, 10), shape=[ 3, 3, 1, 1 ], dtype=tf.float32), // tf.constant(np.arange(1, 17), shape=[ 1, 4, 4, 1 ], dtype=tf.float32), // [1, 1, 1, 1 ], // "SAME") TEST_P(TransposeConvOpTest, SimpleTest) { TransposeConvOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {TensorType_FLOAT32, {1, 4, 4, 1}}, {TensorType_FLOAT32, {}}, Padding_SAME, 1, 1, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetOutput(), Pointwise(FloatingPointEq(), {29, 62, 83, 75, 99, 192, 237, 198, 207, 372, 417, 330, 263, 446, 485, 365})); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } // Test case: // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 4, 4, 1 ]), // tf.constant(np.arange(1, 10), shape=[ 3, 3, 1, 1 ], dtype=tf.float32), // tf.constant(np.arange(1, 17), shape=[ 1, 4, 4, 1 ], dtype=tf.float32), // [1, 1, 1, 1 ], // "SAME") TEST_P(TransposeConvOpTest, fusedRELUTest) { // NNAPI can not support for the new kernel behaviors at the moment. if (SingleOpModel::GetForceUseNnapi()) { return; } TransposeConvOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {TensorType_FLOAT32, {1, 4, 4, 1}}, {TensorType_FLOAT32, {}}, Padding_SAME, 1, 1, ActivationFunctionType_RELU, GetTestType()); model.SetInput({1, 2, -3, -4, 5, 6, -7, -8, 9, 10, -11, -12, 13, 14, 15, 16}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), Pointwise(FloatingPointEq(), {29, 24, 0, 0, 99, 72, 0, 0, 207, 186, 0, 0, 263, 292, 141, 0})); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } // Test case: // filter = tf.constant(np.arange(1, 19), // shape=[ 3, 3, 1, 2 ], // dtype=tf.float32) // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 4, 4, 1 ]), // filter, // tf.constant(np.arange(1, 33), shape=[ 1, 4, 4, 2 ], dtype=tf.float32), // [1, 1, 1, 1 ], // "SAME") // And filter value is derived by: // filter = tf.reshape(tf.transpose(filter, perm=[3, 0, 1, 2]), shape=[18, 1]) TEST_P(TransposeConvOpTest, TwoFiltersTest) { TransposeConvOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 2}}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}, {TensorType_FLOAT32, {1, 4, 4, 2}}, {TensorType_FLOAT32, {}}, Padding_SAME, 1, 1, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), Pointwise(FloatingPointEq(), {184, 412, 568, 528, 678, 1347, 1689, 1434, 1494, 2715, 3057, 2442, 1968, 3352, 3652, 2760})); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } // Test case: // filter = tf.constant(np.arange(1, 19), // shape=[ 3, 3, 1, 2 ], // dtype=tf.float32) // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 6, 6, 1 ]), // filter, // tf.constant(np.arange(1, 33), shape=[ 1, 4, 4, 2 ], dtype=tf.float32), // [1, 1, 1, 1 ], // "VALID") // And filter value is derived by: // filter = tf.reshape(tf.transpose(filter, perm=[3, 0, 1, 2]), shape=[1, 18]) TEST_P(TransposeConvOpTest, PaddingValidTest) { TransposeConvOpModel model( GetRegistration(), {1, 6, 6, 1}, {TensorType_FLOAT32, {1, 3, 3, 2}}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}, {TensorType_FLOAT32, {1, 4, 4, 2}}, {TensorType_FLOAT32, {}}, Padding_VALID, 1, 1, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetOutput(), Pointwise(FloatingPointEq(), {5, 22, 59, 101, 114, 83, 52, 184, 412, 568, 528, 344, 237, 678, 1347, 1689, 1434, 879, 597, 1494, 2715, 3057, 2442, 1431, 856, 1968, 3352, 3652, 2760, 1548, 689, 1534, 2543, 2729, 2010, 1103})); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 6, 6, 1})); } // Test case: // filter = tf.constant(np.arange(1, 10), // shape=[ 3, 3, 1, 1 ], // dtype=tf.float32) // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 5, 5, 1 ]), // filter, // tf.constant(np.arange(1, 5), shape=[ 1, 2, 2, 1 ], dtype=tf.float32), // [1, 2, 2, 1 ], // "VALID") TEST_P(TransposeConvOpTest, StrideValidTest) { TransposeConvOpModel model( GetRegistration(), {1, 5, 5, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {TensorType_FLOAT32, {1, 2, 2, 1}}, {TensorType_FLOAT32, {}}, Padding_VALID, 2, 2, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), Pointwise(FloatingPointEq(), {1, 2, 5, 4, 6, 4, 5, 14, 10, 12, 10, 14, 36, 24, 30, 12, 15, 34, 20, 24, 21, 24, 55, 32, 36})); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 1})); } // Test case: // filter = tf.constant(np.arange(1, 19), // shape=[ 3, 3, 2, 1 ], // dtype=tf.float32) // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 5, 5, 2 ]), // filter, // tf.constant(np.arange(1, 5), shape=[ 1, 2, 2, 1 ], dtype=tf.float32), // [1, 2, 2, 1 ], // "VALID") TEST_P(TransposeConvOpTest, MultiChannelTest) { TransposeConvOpModel model( GetRegistration(), {1, 5, 5, 2}, {TensorType_FLOAT32, {2, 3, 3, 1}}, {1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18}, {TensorType_FLOAT32, {1, 2, 2, 1}}, {TensorType_FLOAT32, {}}, Padding_VALID, 2, 2, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), Pointwise(FloatingPointEq(), {1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9, 10, 25, 28, 18, 20, 22, 24, 16, 20, 24, 28, 62, 72, 42, 48, 54, 60, 21, 24, 27, 30, 61, 68, 36, 40, 44, 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72})); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2})); } // Test case: // filter = tf.constant(np.random.randint(1, 10, size=9), // shape=[ 3, 3, 1, 1 ], // dtype=tf.float32) // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 3, 4, 1 ]), // filter, // tf.constant([323, 521], shape=[ 1, 1, 2, 1], dtype=tf.float32), // [1, 3, 3, 1 ], // "SAME") // And filter value is derived by: // filter = tf.reshape(tf.transpose(filter, perm=[3, 0, 1, 2]), shape=[-1]) TEST_P(TransposeConvOpTest, AccuracyTest) { TransposeConvOpModel model( GetRegistration(), {1, 3, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}}, {9, 5, 6, 9, 8, 5, 3, 1, 4}, {TensorType_FLOAT32, {1, 1, 2, 1}}, {TensorType_FLOAT32, {}}, Padding_SAME, 3, 3, ActivationFunctionType_NONE, GetTestType()); model.SetInput({323, 521}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), ElementsAreArray( ArrayFloatNear({1615., 1938., 4689., 2605., 2584., 1615., 4689., 4168., 323., 1292., 1563., 521.}))); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 3, 4, 1})); } // Test case: // filter = tf.constant(np.random.randint(1, 10, size=9), // shape=[ 3, 3, 1, 1 ], // dtype=tf.float32) // output = tf.nn.conv2d_backprop_input( // tf.constant([ 1, 3, 4, 1 ]), // filter, // tf.constant([323, 521], shape=[ 1, 1, 2, 1], dtype=tf.float32), // [1, 3, 3, 1 ], // "SAME") // And filter value is derived by: // filter = tf.reshape(tf.transpose(filter, perm=[3, 0, 1, 2]), shape=[-1]) TEST_P(TransposeConvOpTest, AccuracyWithFusedActivationTest) { // NNAPI can not support for the new kernel behaviors at the moment. if (SingleOpModel::GetForceUseNnapi()) { return; } TransposeConvOpModel model( GetRegistration(), {1, 3, 4, 1}, {TensorType_FLOAT32, {1, 3, 3, 1}}, {9, 5, 6, 9, 8, 5, 3, 1, 4}, {TensorType_FLOAT32, {1, 1, 2, 1}}, {TensorType_FLOAT32, {}}, Padding_SAME, 3, 3, ActivationFunctionType_RELU, GetTestType()); model.SetInput({323, -521}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), ElementsAreArray(ArrayFloatNear( {1615, 1938, 0, 0, 2584, 1615, 0, 0, 323, 1292, 0, 0}))); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 3, 4, 1})); } class QuantizedTransposeConvOpModel : public BaseTransposeConvOpModel { public: using BaseTransposeConvOpModel::BaseTransposeConvOpModel; std::vector GetDequantizedOutput() { return Dequantize(ExtractVector(output_), GetScale(output_), GetZeroPoint(output_)); } }; TEST_P(TransposeConvOpTest, SimpleTestQuantized) { // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9} std::initializer_list filter_data = {129, 131, 133, 135, 137, 139, 141, 143, 145}; QuantizedTransposeConvOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 1}, -63, 64}, filter_data, {TensorType_UINT8, {1, 4, 4, 1}, -63, 64}, {TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear({36, 72, 96, 84, 116, 216, 268, 220, 232, 412, 464, 360, 284, 480, 512, 388}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } TEST_P(TransposeConvOpTest, SimpleTestWithFusedActivationQuantized) { // NNAPI can not support for the new kernel behaviors at the moment. if (SingleOpModel::GetForceUseNnapi()) { return; } // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9} std::initializer_list filter_data = {129, 131, 133, 135, 137, 139, 141, 143, 145}; QuantizedTransposeConvOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 1}, -63, 64}, filter_data, {TensorType_UINT8, {1, 4, 4, 1}, -63, 64}, {TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1, ActivationFunctionType_RELU, GetTestType()); model.SetInput({1, 2, -3, -4, 5, 6, -7, -8, 9, 10, -11, -12, 13, 14, 15, 16}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear({36, 24, 0, 0, 116, 76, 0, 0, 232, 212, 0, 0, 284, 316, 156, 0}))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } TEST_P(TransposeConvOpTest, TwoFiltersTestQuantized) { // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, // 18} std::initializer_list filter_data = {129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163}; QuantizedTransposeConvOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 2}, -63, 64}, filter_data, {TensorType_UINT8, {1, 4, 4, 2}, -63, 64}, {TensorType_UINT8, {}, -4064, 4096}, Padding_SAME, 1, 1, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {224, 448, 608, 576, 736, 1440, 1792, 1504, 1600, 2880, 3232, 2560, 2048, 3456, 3776, 2848}, 1e-5))); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } TEST_P(TransposeConvOpTest, PaddingValidTestQuantized) { // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, // 18} std::initializer_list filter_data = {129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163}; QuantizedTransposeConvOpModel model( GetRegistration(), {1, 6, 6, 1}, {TensorType_UINT8, {1, 3, 3, 2}, -63, 64}, filter_data, {TensorType_UINT8, {1, 4, 4, 2}, -63, 64}, {TensorType_UINT8, {}, -4064, 4096}, Padding_VALID, 1, 1, ActivationFunctionType_NONE, GetTestType()); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {0, 32, 64, 128, 128, 96, 64, 224, 448, 608, 576, 352, 256, 736, 1440, 1792, 1504, 928, 640, 1600, 2880, 3232, 2560, 1504, 896, 2048, 3456, 3776, 2848, 1600, 704, 1568, 2592, 2784, 2048, 1120}, 1e-5))); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 6, 6, 1})); } class PerChannelQuantizedTransposeConvOpModel : public BaseTransposeConvOpModel { public: using BaseTransposeConvOpModel::BaseTransposeConvOpModel; std::vector GetDequantizedOutput() { return Dequantize(ExtractVector(output_), GetScale(output_), GetZeroPoint(output_)); } void SetFilter(const std::initializer_list& data) { PerChannelSymmetricQuantizeAndPopulate(filter_, data); } }; TEST_P(TransposeConvOpTest, SimpleTestQuantizedPerChannelSingleChannel) { const std::initializer_list filter_data = {1, 2, 3, 4, 5, 6, 7, 8, 9}; const std::initializer_list const_filter_data = {14, 28, 42, 56, 71, 85, 99, 113, 127}; PerChannelQuantizedTransposeConvOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0}, const_filter_data, {TensorType_INT8, {1, 4, 4, 1}, 0, 0, 16.0 / 127, -128}, {TensorType_INT8, {}, 0, 0, 2, -128}, Padding_SAME, 1, 1, ActivationFunctionType_NONE, GetTestType(), /* version */ 2); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); if (GetTestType() == TestType::kDynamic) { model.SetFilter(filter_data); } ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear({30, 62, 84, 76, 100, 192, 238, 198, 206, 372, 416, 330, 262, 446, 484, 366}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } // Test data copied from the float multi-channel test above. TEST_P(TransposeConvOpTest, TestQuantizedPerChannelMultiChannel) { const std::initializer_list filter_data = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18}; const std::initializer_list const_filter_data = { 7, 22, 37, 52, 67, 82, 97, 112, 127, 14, 28, 42, 56, 71, 85, 99, 113, 127}; PerChannelQuantizedTransposeConvOpModel model( GetRegistration(), {1, 5, 5, 2}, {TensorType_INT8, {2, 3, 3, 1}, 0, 0, 0, 0, true, {17.0 / 127, 18.0 / 127}, {0, 0}, 0}, const_filter_data, {TensorType_INT8, {1, 2, 2, 1}, 0, 0, 4.0 / 255, -128}, {TensorType_INT8, {}, 0, 0, 1, -128}, Padding_VALID, 2, 2, ActivationFunctionType_NONE, GetTestType(), /* version */ 2); model.SetInput({1, 2, 3, 4}); if (GetTestType() == TestType::kDynamic) { model.SetFilter(filter_data); } ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9, 10, 25, 28, 18, 20, 22, 24, 16, 20, 24, 28, 62, 72, 42, 48, 54, 60, 21, 24, 27, 30, 61, 68, 36, 40, 44, 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2})); } // Test data copied from the float multi-channel test above. TEST_P(TransposeConvOpTest, TestQuantizedPerTensorMultiChannel) { const std::initializer_list filter_data = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18}; const std::initializer_list const_filter_data = { 7, 21, 35, 49, 64, 78, 92, 106, 120, 14, 28, 42, 56, 71, 85, 99, 113, 127}; PerChannelQuantizedTransposeConvOpModel model( GetRegistration(), {1, 5, 5, 2}, {TensorType_INT8, {2, 3, 3, 1}, 0, 0, 0, 0, true, {18.0 / 127, 18.0 / 127}, {0, 0}, 0}, const_filter_data, {TensorType_INT8, {1, 2, 2, 1}, 0, 0, 4.0 / 255, -128}, {TensorType_INT8, {}, 0, 0, 1, -128}, Padding_VALID, 2, 2, ActivationFunctionType_NONE, GetTestType(), /* version */ 2); model.SetInput({1, 2, 3, 4}); if (GetTestType() == TestType::kDynamic) { model.SetFilter(filter_data); } ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {1, 2, 3, 4, 7, 10, 6, 8, 10, 12, 7, 8, 9, 10, 25, 28, 18, 20, 22, 24, 16, 20, 24, 28, 62, 72, 42, 48, 54, 60, 21, 24, 27, 30, 61, 68, 36, 40, 44, 48, 39, 42, 45, 48, 103, 110, 60, 64, 68, 72}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2})); } class PerChannelQuantizedTransposeConvOpModel16x8 : public BaseTransposeConvOpModel { public: using BaseTransposeConvOpModel::BaseTransposeConvOpModel; std::vector GetDequantizedOutput() { return Dequantize(ExtractVector(output_), GetScale(output_), GetZeroPoint(output_)); } void SetFilter(const std::initializer_list& data) { PerChannelSymmetricQuantizeAndPopulate(filter_, data); } }; TEST_P(TransposeConvOpTest, SimpleTestQuantizedPerChannel16x8NoBiasInt32) { const std::initializer_list filter_data = { // [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel] 1, 2, // out channel = 0, y = 0, x = 0 3, 4, // out channel = 0, y = 0, x = 1 3, 4, // out channel = 0, y = 1, x = 0 5, 6, // out channel = 0, y = 1, x = 1 7, 8, // out channel = 1, y = 0, x = 0 5, 6, // out channel = 1, y = 0, x = 1 3, 4, // out channel = 1, y = 1, x = 0 1, 2, // out channel = 1, y = 1, x = 1 }; PerChannelQuantizedTransposeConvOpModel16x8 model( GetRegistration(), /*output_shape_data=*/{1, 2, 3, 2}, /*filter=*/ {TensorType_INT8, /*shape=*/{2, 2, 2, 2}, /*min=*/-64, /*max=*/64, /*scale=*/0, /*zero_point=*/0, /*per_channel_quantization=*/true, /*per_channel_quantization_scales=*/{7.0 / 127, 8.0 / 127}, /*per_channel_quantization_offsets=*/{0, 0}, /*channel_index=*/0}, /*filter_data=*/{}, /*input=*/ {TensorType_INT16, /*shape=*/{1, 2, 3, 2}, /*min=*/0, /*max=*/0, /*scale=*/4.0 / 127, /*zero_point=*/0}, /*output=*/ {TensorType_INT16, /*shape=*/{}, /*min=*/0, /*max=*/0, /*scale=*/1.0, /*zero_point=*/0}, /*padding=*/Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, /*fused_activation=*/ActivationFunctionType_NONE, GetTestType(), /*bias_type=*/TensorType_INT32); model.SetInput({ // [1 * 2 * 3 * 2] as [batch, y, x, input_channel] 3, 2, // batch = 0, y = 0, x = 0 1, -1, // batch = 0, y = 0, x = 1 -2, -3, // batch = 0, y = 0, x = 2 4, 3, // batch = 0, y = 1, x = 0 2, -2, // batch = 0, y = 1, x = 1 -3, -4, // batch = 0, y = 1, x = 2 }); model.SetFilter(filter_data); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {7, 37, 16, 26, -9, -39, 27, 69, 48, 42, -32, -74}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 3, 2})); } TEST_P(TransposeConvOpTest, SimpleTestWithFusedActivationQuantizedPerChannel16x8) { // NNAPI can not support for the new kernel behaviors at the moment. if (SingleOpModel::GetForceUseNnapi()) { return; } const std::initializer_list filter_data = { // [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel] 1, 2, // out channel = 0, y = 0, x = 0 3, 4, // out channel = 0, y = 0, x = 1 3, 4, // out channel = 0, y = 1, x = 0 5, 6, // out channel = 0, y = 1, x = 1 7, 8, // out channel = 1, y = 0, x = 0 5, 6, // out channel = 1, y = 0, x = 1 3, 4, // out channel = 1, y = 1, x = 0 1, 2, // out channel = 1, y = 1, x = 1 }; PerChannelQuantizedTransposeConvOpModel16x8 model( GetRegistration(), /*output_shape_data=*/{1, 2, 3, 2}, /*filter=*/ {TensorType_INT8, /*shape=*/{2, 2, 2, 2}, /*min=*/-64, /*max=*/64, /*scale=*/0, /*zero_point=*/0, /*per_channel_quantization=*/true, /*per_channel_quantization_scales=*/{7.0 / 127, 8.0 / 127}, /*per_channel_quantization_offsets=*/{0, 0}, /*channel_index=*/0}, /*filter_data=*/{}, /*input=*/ {TensorType_INT16, /*shape=*/{1, 2, 3, 2}, /*min=*/0, /*max=*/0, /*scale=*/4.0 / 127, /*zero_point=*/0}, /*output=*/ {TensorType_INT16, /*shape=*/{}, /*min=*/0, /*max=*/0, /*scale=*/1.0, /*zero_point=*/0}, /*padding=*/Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, /*fused_activation=*/ActivationFunctionType_RELU, GetTestType()); model.SetInput({ // [1 * 2 * 3 * 2] as [batch, y, x, input_channel] 3, 2, // batch = 0, y = 0, x = 0 1, -1, // batch = 0, y = 0, x = 1 -2, -3, // batch = 0, y = 0, x = 2 4, 3, // batch = 0, y = 1, x = 0 2, -2, // batch = 0, y = 1, x = 1 -3, -4, // batch = 0, y = 1, x = 2 }); model.SetFilter(filter_data); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {7, 37, 16, 26, 0, 0, 27, 69, 48, 42, 0, 0}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 3, 2})); } TEST_P(TransposeConvOpTest, SimpleTestQuantizedPerChannel16x8NoBiasInt64) { // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9} const std::initializer_list filter_data = { // [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel] 1, 2, // out channel = 0, y = 0, x = 0 3, 4, // out channel = 0, y = 0, x = 1 3, 4, // out channel = 0, y = 1, x = 0 5, 6, // out channel = 0, y = 1, x = 1 7, 8, // out channel = 1, y = 0, x = 0 5, 6, // out channel = 1, y = 0, x = 1 3, 4, // out channel = 1, y = 1, x = 0 1, 2, // out channel = 1, y = 1, x = 1 }; PerChannelQuantizedTransposeConvOpModel16x8 model( GetRegistration(), /*output_shape_data=*/{1, 2, 3, 2}, /*filter=*/ {TensorType_INT8, /*shape=*/{2, 2, 2, 2}, /*min=*/-64, /*max=*/64, /*scale=*/0, /*zero_point=*/0, /*per_channel_quantization=*/true, /*per_channel_quantization_scales=*/{7.0 / 127, 8.0 / 127}, /*per_channel_quantization_offsets=*/{0, 0}, /*channel_index=*/0}, /*filter_data=*/{}, /*input=*/ {TensorType_INT16, /*shape=*/{1, 2, 3, 2}, /*min=*/0, /*max=*/0, /*scale=*/4.0 / 127, /*zero_point=*/0}, /*output=*/ {TensorType_INT16, /*shape=*/{}, /*min=*/0, /*max=*/0, /*scale=*/1.0, /*zero_point=*/0}, /*padding=*/Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, /*fused_activation=*/ActivationFunctionType_NONE, GetTestType(), /*version=*/1, /*bias_type=*/TensorType_INT64); model.SetInput({ // [1 * 2 * 3 * 2] as [batch, y, x, input_channel] 3, 2, // batch = 0, y = 0, x = 0 1, -1, // batch = 0, y = 0, x = 1 -2, -3, // batch = 0, y = 0, x = 2 4, 3, // batch = 0, y = 1, x = 0 2, -2, // batch = 0, y = 1, x = 1 -3, -4, // batch = 0, y = 1, x = 2 }); model.SetFilter(filter_data); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {7, 37, 16, 26, -9, -39, 27, 69, 48, 42, -32, -74}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 3, 2})); } template class BaseTransposeConvBiasOpModel : public SingleOpModel { public: BaseTransposeConvBiasOpModel( TfLiteRegistration* registration, std::initializer_list output_shape_data, const TensorData& filter, std::initializer_list filter_data, const TensorData& input, const TensorData& output, Padding padding, int stride_w, int stride_h, tflite::ActivationFunctionType fused_activation, TestType test_type, int version = 3, const TensorType& bias_type = TensorType_INT32) { bias_type_ = bias_type; if (test_type == TestType::kDynamic) { output_shape_ = AddInput({TensorType_INT32, {4}}); filter_ = AddInput(filter); } else { output_shape_ = AddConstInput(TensorType_INT32, output_shape_data, {4}); filter_ = AddConstInput(filter, filter_data); } input_ = AddInput(input); int bias_size = GetShape(filter_)[0]; if (input.type == TensorType_FLOAT32) { bias_type_ = TensorType_FLOAT32; bias_ = AddInput({TensorType_FLOAT32, {bias_size}}); } else { if (filter.per_channel_quantization) { // per channel quantization. std::vector bias_scale( filter.per_channel_quantization_scales.size()); std::vector bias_zero_points( filter.per_channel_quantization_scales.size()); for (size_t i = 0; i < filter.per_channel_quantization_scales.size(); ++i) { bias_scale[i] = input.scale * filter.per_channel_quantization_scales[i]; bias_zero_points[i] = 0; } TensorData bias{bias_type, {bias_size}, /*min=*/0, /*max=*/0, /*scale=*/0, /*zero_point=*/0, true, /*per_channel_quantization_scales=*/bias_scale, /*per_channel_quantization_offsets=*/bias_zero_points, /*channel_index==*/0}; bias_ = AddInput(bias); } else { // per tensor quantization. auto bias_scale = GetScale(input_) * GetScale(filter_); TensorData bias{bias_type, {bias_size}, 0, 0, bias_scale}; bias_ = AddInput(bias); } } output_ = AddOutput(output); SetBuiltinOp( BuiltinOperator_TRANSPOSE_CONV, BuiltinOptions_TransposeConvOptions, CreateTransposeConvOptions(builder_, padding, stride_w, stride_h, fused_activation, bias_type) .Union()); resolver_ = std::make_unique( BuiltinOperator_TRANSPOSE_CONV, registration, version); BuildInterpreter({GetShape(output_shape_), GetShape(filter_), GetShape(input_), GetShape(bias_)}); if (test_type == TestType::kDynamic) { PopulateTensor(output_shape_, output_shape_data); if (!std::is_same::value && !std::is_same::value) { PopulateTensor(filter_, filter_data); } } } void SetInput(std::initializer_list data) { if (std::is_same::value) { QuantizeAndPopulate(input_, data); } else if (std::is_same::value) { QuantizeAndPopulate(input_, data); } else if (std::is_same::value) { QuantizeAndPopulate(input_, data); } else { PopulateTensor(input_, data); } } void SetBias(std::initializer_list bias) { if (std::is_same::value) { QuantizeAndPopulate(bias_, bias); } else if (std::is_same::value) { PerChannelQuantizeBias(bias_, bias); } else { PopulateTensor(bias_, bias); } } std::vector GetOutputShape() { return GetTensorShape(output_); } protected: int output_shape_; int filter_; int input_; int bias_; int output_; TensorType bias_type_; }; class TransposeConvOpBiasModel : public BaseTransposeConvBiasOpModel { public: using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel; std::vector GetOutput() { return ExtractVector(output_); } }; // Test case: // input_data = np.arange(1, 5).reshape(1,2,2,1).astype(np.float32) // filter_data = np.arange(1, 19).reshape(3,3,2,1).astype(np.float32) // bias_data = np.array([3,4]) // input = tf.keras.layers.Input(shape=(2, 2, 1)) // output = tf.keras.layers.Convolution2DTranspose(filters=2, // kernel_size=[3, 3], // strides=[2, 2], // padding="valid")(input) // model = tf.keras.models.Model(input, output) // model.layers[1].set_weights([filter_data, bias_data]) // output = model.predict(input_data) TEST_P(TransposeConvOpTest, MultiChannelBiasTest) { TransposeConvOpBiasModel model( GetRegistration(), /*output_shape=*/{1, 5, 5, 2}, /*filter=*/{TensorType_FLOAT32, {2, 3, 3, 1}}, /*filter_data=*/ {1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18}, /*input=*/{TensorType_FLOAT32, {1, 2, 2, 1}}, /*output=*/{TensorType_FLOAT32, {}}, Padding_VALID, /*stride_w=*/2, /*stride_h=*/2, /*fused_activation=*/ActivationFunctionType_NONE, GetTestType(), /* version */ 3); model.SetInput({1, 2, 3, 4}); model.SetBias({3, 4}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), Pointwise(FloatingPointEq(), {4, 6, 6, 8, 10, 14, 9, 12, 13, 16, 10, 12, 12, 14, 28, 32, 21, 24, 25, 28, 19, 24, 27, 32, 65, 76, 45, 52, 57, 64, 24, 28, 30, 34, 64, 72, 39, 44, 47, 52, 42, 46, 48, 52, 106, 114, 63, 68, 71, 76})); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2})); } // Test case: // input_data = np.arange(1, 5).reshape(1,2,2,1).astype(np.float32) // filter_data = np.arange(1, 19).reshape(3,3,2,1).astype(np.float32) // bias_data = np.array([3,4]) // input = tf.keras.layers.Input(shape=(2, 2, 1)) // output = tf.keras.layers.Convolution2DTranspose(filters=2, // kernel_size=[3, 3], // strides=[2, 2], // padding="valid")(input) // model = tf.keras.models.Model(input, output) // model.layers[1].set_weights([filter_data, bias_data]) // output = model.predict(input_data) TEST_P(TransposeConvOpTest, MultiChannelBiasWithFusedActivationTest) { // NNAPI can not support for the new kernel behaviors at the moment. if (SingleOpModel::GetForceUseNnapi()) { return; } TransposeConvOpBiasModel model( GetRegistration(), /*output_shape=*/{1, 5, 5, 2}, /*filter=*/{TensorType_FLOAT32, {2, 3, 3, 1}}, /*filter_data=*/ {1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18}, /*input=*/{TensorType_FLOAT32, {1, 2, 2, 1}}, /*output=*/{TensorType_FLOAT32, {}}, Padding_VALID, /*stride_w=*/2, /*stride_h=*/2, /*fused_activation=*/ActivationFunctionType_RELU, GetTestType(), /* version */ 3); model.SetInput({1, 2, -3, 4}); model.SetBias({3, 4}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetOutput(), Pointwise(FloatingPointEq(), {4, 6, 6, 8, 10, 14, 9, 12, 13, 16, 10, 12, 12, 14, 28, 32, 21, 24, 25, 28, 13, 12, 9, 8, 35, 40, 45, 52, 57, 64, 0, 0, 0, 0, 0, 0, 39, 44, 47, 52, 0, 0, 0, 0, 4, 6, 63, 68, 71, 76})); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2})); } class QuantizedTransposeConvBiasOpModel : public BaseTransposeConvBiasOpModel { public: using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel; std::vector GetDequantizedOutput() { return Dequantize(ExtractVector(output_), GetScale(output_), GetZeroPoint(output_)); } }; TEST_P(TransposeConvOpTest, SimpleBiasTestQuantized) { // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9} std::initializer_list filter_data = {129, 131, 133, 135, 137, 139, 141, 143, 145}; QuantizedTransposeConvBiasOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 1}, -63.5, 64}, filter_data, {TensorType_UINT8, {1, 4, 4, 1}, -63.5, 64}, {TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1, ActivationFunctionType_NONE, GetTestType(), /* version */ 3); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); model.SetBias({1}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear({32, 64, 84, 76, 100, 192, 240, 200, 208, 372, 420, 332, 264, 448, 488, 368}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } TEST_P(TransposeConvOpTest, SimpleBiasWithFusedActivationTestQuantized) { // NNAPI can not support for the new kernel behaviors at the moment. if (SingleOpModel::GetForceUseNnapi()) { return; } // Float would be {1, 2, 3, 4, 5, 6, 7, 8, 9} std::initializer_list filter_data = {129, 131, 133, 135, 137, 139, 141, 143, 145}; QuantizedTransposeConvBiasOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_UINT8, {1, 3, 3, 1}, -63.5, 64}, filter_data, {TensorType_UINT8, {1, 4, 4, 1}, -63.5, 64}, {TensorType_UINT8, {}, -508, 512}, Padding_SAME, 1, 1, ActivationFunctionType_RELU, GetTestType(), /* version */ 3); model.SetInput( {1, 2, -3, -4, 5, 6, -7, -8, 9, 10, -11, -12, 13, 14, -15, -16}); model.SetBias({1}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {32, 24, 0, 0, 100, 72, 0, 0, 208, 156, 0, 0, 264, 172, 0, 0}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } class PerChannelQuantizedTransposeConvBiasOpModel : public BaseTransposeConvBiasOpModel { public: using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel; std::vector GetDequantizedOutput() { return Dequantize(ExtractVector(output_), GetScale(output_), GetZeroPoint(output_)); } void SetInput(const std::initializer_list& data) { QuantizeAndPopulate(input_, data); } void SetFilter(const std::initializer_list& data) { PerChannelSymmetricQuantizeAndPopulate(filter_, data); } }; TEST_P(TransposeConvOpTest, SimpleBiasTestQuantizedPerChannelSingleChannel) { const std::initializer_list filter_data = {1, 2, 3, 4, 5, 6, 7, 8, 9}; const std::initializer_list const_filter_data = {14, 28, 42, 56, 71, 85, 99, 113, 127}; PerChannelQuantizedTransposeConvBiasOpModel model( GetRegistration(), {1, 4, 4, 1}, {TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0}, const_filter_data, {TensorType_INT8, {1, 4, 4, 1}, 0, 0, 16.0 / 255, -128}, {TensorType_INT8, {}, 0, 0, 2, -128}, Padding_SAME, 1, 1, ActivationFunctionType_NONE, GetTestType(), /* version */ 3); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); if (GetTestType() == TestType::kDynamic) { model.SetFilter(filter_data); } model.SetBias({1}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT( model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear({30, 62, 84, 76, 100, 194, 238, 200, 208, 372, 418, 330, 264, 446, 486, 366}, 1e-5))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } class PerChannel16x8TransposeConvBiasOpModel : public BaseTransposeConvBiasOpModel { public: using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel; std::vector GetDequantizedOutput() { return Dequantize(ExtractVector(output_), GetScale(output_), GetZeroPoint(output_)); } void SetFilter(const std::initializer_list& data) { PerChannelSymmetricQuantizeAndPopulate(filter_, data); } }; TEST_P(TransposeConvOpTest, SimpleBiasTestQuantizedPerChannel16x8Bias32) { const float scale = 128.0 / 65536; const std::initializer_list filter_data = { // [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel] 1, 2, // out channel = 0, y = 0, x = 0 3, 4, // out channel = 0, y = 0, x = 1 3, 4, // out channel = 0, y = 1, x = 0 5, 6, // out channel = 0, y = 1, x = 1 7, 8, // out channel = 1, y = 0, x = 0 5, 6, // out channel = 1, y = 0, x = 1 3, 4, // out channel = 1, y = 1, x = 0 1, 2, // out channel = 1, y = 1, x = 1 }; PerChannel16x8TransposeConvBiasOpModel model( GetRegistration(), /*output_shape_data=*/{1, 2, 3, 2}, /*filter=*/ {TensorType_INT8, /*shape=*/{2, 2, 2, 2}, /*min=*/-64, /*max=*/64, /*scale=*/0, /*zero_point=*/0, /*per_channel_quantization=*/true, /*per_channel_quantization_scales=*/{7.0 / 127, 8.0 / 127}, /*per_channel_quantization_offsets=*/{0, 0}, /*channel_index=*/0}, /*filter_data=*/{}, /*input=*/ {TensorType_INT16, /*shape=*/{1, 2, 3, 2}, /*min=*/0, /*max=*/0, /*scale=*/4.0 / 127, /*zero_point=*/0}, /*output=*/ {TensorType_INT16, /*shape=*/{}, /*min=*/0, /*max=*/0, /*scale=*/scale, /*zero_point=*/0}, /*padding=*/Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, /*fused_activation=*/ActivationFunctionType_NONE, GetTestType(), /*bias_type=*/TensorType_INT32); model.SetInput({ // [1 * 2 * 3 * 2] as [batch, y, x, input_channel] 3, 2, // batch = 0, y = 0, x = 0 1, -1, // batch = 0, y = 0, x = 1 -2, -3, // batch = 0, y = 0, x = 2 4, 3, // batch = 0, y = 1, x = 0 2, -2, // batch = 0, y = 1, x = 1 -3, -4, // batch = 0, y = 1, x = 2 }); model.SetFilter(filter_data); model.SetBias({3, -2}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {10, 35, 19, 24, -6, -41, 30, 64, 51, 40, -29, -64}, 0.19))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 3, 2})); } TEST_P(TransposeConvOpTest, SimpleBiasTestQuantizedPerChannel16x8Bias64) { const float scale = 128.0 / 65536; const std::initializer_list filter_data = { // [2 * 2 * 2 * 2] as [output_channel, y, x, input_channel] 1, 2, // out channel = 0, y = 0, x = 0 3, 4, // out channel = 0, y = 0, x = 1 3, 4, // out channel = 0, y = 1, x = 0 5, 6, // out channel = 0, y = 1, x = 1 7, 8, // out channel = 1, y = 0, x = 0 5, 6, // out channel = 1, y = 0, x = 1 3, 4, // out channel = 1, y = 1, x = 0 1, 2, // out channel = 1, y = 1, x = 1 }; PerChannel16x8TransposeConvBiasOpModel model( GetRegistration(), /*output_shape_data=*/{1, 2, 3, 2}, /*filter=*/ {TensorType_INT8, /*shape=*/{2, 2, 2, 2}, /*min=*/-64, /*max=*/64, /*scale=*/0, /*zero_point=*/0, /*per_channel_quantization=*/true, /*per_channel_quantization_scales=*/{7.0 / 127, 8.0 / 127}, /*per_channel_quantization_offsets=*/{0, 0}, /*channel_index=*/0}, /*filter_data=*/{}, /*input=*/ {TensorType_INT16, /*shape=*/{1, 2, 3, 2}, /*min=*/0, /*max=*/0, /*scale=*/4.0 / 127, /*zero_point=*/0}, /*output=*/ {TensorType_INT16, /*shape=*/{}, /*min=*/0, /*max=*/0, /*scale=*/scale, /*zero_point=*/0}, /*padding=*/Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, /*fused_activation=*/ActivationFunctionType_NONE, GetTestType(), /*bias_type=*/TensorType_INT64); model.SetInput({ // [1 * 2 * 3 * 2] as [batch, y, x, input_channel] 3, 2, // batch = 0, y = 0, x = 0 1, -1, // batch = 0, y = 0, x = 1 -2, -3, // batch = 0, y = 0, x = 2 4, 3, // batch = 0, y = 1, x = 0 2, -2, // batch = 0, y = 1, x = 1 -3, -4, // batch = 0, y = 1, x = 2 }); model.SetFilter(filter_data); model.SetBias({3, -2}); ASSERT_EQ(model.Invoke(), kTfLiteOk); EXPECT_THAT(model.GetDequantizedOutput(), ElementsAreArray(ArrayFloatNear( {10, 35, 19, 24, -6, -41, 30, 64, 51, 40, -29, -64}, 0.19))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 3, 2})); } class HybridTransposeConvOpModel : public BaseTransposeConvBiasOpModel { public: using BaseTransposeConvBiasOpModel::BaseTransposeConvBiasOpModel; void SetFilter(std::initializer_list f) { PerChannelSymmetricQuantizeAndPopulate(filter_, f); } void SetBias(std::initializer_list b) { PopulateTensor(bias_, b); } std::vector GetOutput() { return ExtractVector(output_); } }; TEST_P(TransposeConvOpTest, SimpleTestHybridInt8) { const std::initializer_list filter_data = {1, 2, 3, 4, 5, 6, 7, 8, 9}; const std::initializer_list const_filter_data = {14, 28, 42, 56, 71, 85, 99, 113, 127}; HybridTransposeConvOpModel model( /*registration=*/GetRegistration(), /*output_shape_data=*/{1, 4, 4, 1}, /*filter=*/ {TensorType_INT8, {1, 3, 3, 1}, 0, 0, 0, 0, true, {9.0 / 127}, {0}, 0}, /*filter_data=*/const_filter_data, /*input=*/{TensorType_FLOAT32, {1, 4, 4, 1}}, /*output=*/{TensorType_FLOAT32, {}}, /*padding=*/Padding_SAME, /*stride_w=*/1, /*stride_h=*/1, /*fused_activation=*/ActivationFunctionType_NONE, /*test_type=*/GetTestType(), /*version=*/3, /*bias_type=*/TensorType_FLOAT32); model.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); if (GetTestType() == TestType::kDynamic) { model.SetFilter(filter_data); } model.SetBias({1}); ASSERT_EQ(model.Invoke(), kTfLiteOk); // The values are taken from float model "SimpleTest". EXPECT_THAT(model.GetOutput(), ElementsAreArray(ArrayFloatNear( {30, 63, 84, 76, 100, 193, 238, 199, 208, 373, 417.5, 331, 263.7, 447, 486, 366.5}, 0.19))); // GetOutputShape() should always be same as model.SetOutputShape(...); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1})); } TEST_P(TransposeConvOpTest, SimpleTestHybridInt8MultiChannel) { const std::initializer_list filter_data = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 2, 4, 6, 8, 10, 12, 14, 16, 18}; const std::initializer_list const_filter_data = { 7, 22, 37, 52, 67, 82, 97, 112, 127, 14, 28, 42, 56, 71, 85, 99, 113, 127}; HybridTransposeConvOpModel model( /*registration=*/GetRegistration(), /*output_shape_data=*/{1, 5, 5, 2}, /*filter=*/ {TensorType_INT8, {2, 3, 3, 1}, 0, 0, 0, 0, true, {17.0 / 127, 18.0 / 127}, {0, 0}, 0}, /*filter_data=*/const_filter_data, /*input=*/{TensorType_FLOAT32, {1, 2, 2, 1}}, /*output=*/{TensorType_FLOAT32, {}}, /*padding=*/Padding_VALID, /*stride_w=*/2, /*stride_h=*/2, /*fused_activation=*/ActivationFunctionType_NONE, /*test_type=*/GetTestType(), /*version=*/3, /*bias_type=*/TensorType_FLOAT32); model.SetInput({1, 2, 3, 4}); if (GetTestType() == TestType::kDynamic) { model.SetFilter(filter_data); } model.SetBias({3, 4}); ASSERT_EQ(model.Invoke(), kTfLiteOk); // The values are taken from float model "MultiChannelBiasTest". EXPECT_THAT( model.GetOutput(), ElementsAreArray(ArrayFloatNear( {4, 6, 6, 8, 10, 14, 9, 12, 13, 16, 10, 12, 12, 14, 28, 32, 21, 24, 25, 28, 19, 24, 27, 32, 64.5, 76, 44.5, 52, 56.5, 63.5, 24, 28, 30, 34, 63.5, 72, 39, 44, 47, 52, 42, 46, 48, 52, 106, 114, 63, 68, 71, 76}, 0.26))); EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 5, 5, 2})); } INSTANTIATE_TEST_SUITE_P( TransposeConvOpTest, TransposeConvOpTest, ::testing::Combine( ::testing::ValuesIn(SingleOpTest::GetKernelTags(*kKernelMap)), ::testing::Values(TestType::kConst, TestType::kDynamic))); } // namespace } // namespace tflite