diff --git a/tensorflow/lite/kernels/internal/reference/reference_ops.h b/tensorflow/lite/kernels/internal/reference/reference_ops.h index fe7fde50aef..5ffb9d33db5 100644 --- a/tensorflow/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/reference_ops.h @@ -1659,14 +1659,14 @@ inline void ComputeInterpolationValues(const int32 value, const int32 scale_10, std::min((*scaled_value + (1 << 10) - 1) / (1 << 10), input_size - 1); } -// Same as above but takes int8 as input and output. -inline void ResizeBilinear(const tflite::ResizeBilinearParams& op_params, - const RuntimeShape& unextended_input_shape, - const int8_t* input_data, - const RuntimeShape& unextended_output_size_shape, - const int32* output_size_data, - const RuntimeShape& unextended_output_shape, - int8_t* output_data) { +// Same as above but doesn't use any floating-point for the resize +template <typename T> +inline void ResizeBilinearInteger( + const tflite::ResizeBilinearParams& op_params, + const RuntimeShape& unextended_input_shape, const T* input_data, + const RuntimeShape& unextended_output_size_shape, + const int32* output_size_data, const RuntimeShape& unextended_output_shape, + T* output_data) { // If half_pixel_centers is True, align_corners must be False. TFLITE_DCHECK(!op_params.half_pixel_centers || !op_params.align_corners); TFLITE_DCHECK_LE(unextended_input_shape.DimensionsCount(), 4); @@ -1741,8 +1741,8 @@ inline void ResizeBilinear(const tflite::ResizeBilinearParams& op_params, const int64_t output_20 = output_20_ll + output_20_lu + output_20_rl + output_20_ru; const int64_t round = (output_20 > 0) ? (1 << 19) : -(1 << 19); - const int8_t interpolation = - static_cast<int8_t>((output_20 + round) / (1 << 20)); + const T interpolation = + static_cast<T>((output_20 + round) / (1 << 20)); output_data[Offset(output_shape, b, y, x, c)] = interpolation; } } diff --git a/tensorflow/lite/kernels/internal/resize_bilinear_test.cc b/tensorflow/lite/kernels/internal/resize_bilinear_test.cc index a29082dbf9a..f460e302033 100644 --- a/tensorflow/lite/kernels/internal/resize_bilinear_test.cc +++ b/tensorflow/lite/kernels/internal/resize_bilinear_test.cc @@ -289,12 +289,12 @@ TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x4) { } } -TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x6_Int8) { +template <typename T> +void TestResizeBilinearHalfPixelCenters_2x2to4x6() { // Input: 2x2 RuntimeShape input_dims_inference({1, 2, 2, 1}); // clang-format off - std::vector<int8> input_data = {127, -128, - 64, 0}; + std::vector<T> input_data = {127, -128, 64, 0}; // clang-format on // Output: 4x6 @@ -302,7 +302,7 @@ TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x6_Int8) { // Initialize the output data with something other than zero, so we can catch // issue with kernels failing to initialize the output. const int output_buffer_size = output_dims_inference.FlatSize(); - std::vector<int8> output_data(output_buffer_size, 3); + std::vector<T> output_data(output_buffer_size, 3); RuntimeShape output_size_dims({1, 1, 1, 2}); std::vector<int32> output_size_data = {4, 6}; @@ -312,11 +312,11 @@ TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x6_Int8) { op_params.half_pixel_centers = false; // Test with half_pixel_centers = false. - reference_ops::ResizeBilinear( + reference_ops::ResizeBilinearInteger( op_params, input_dims_inference, input_data.data(), output_size_dims, output_size_data.data(), output_dims_inference, output_data.data()); // clang-format off - std::vector<int8> reference_half_pixel_centers_false = + std::vector<T> reference_half_pixel_centers_false = { 127, 42, -43, -128, -128, -128, 96, 42, -11, -64, -64, -64, 64, 43, 21, 0, 0, 0, @@ -329,17 +329,17 @@ TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x6_Int8) { // clang-format on for (int i = 0; i < output_buffer_size; i++) { - EXPECT_EQ(static_cast<int8>(output_data[i]), - static_cast<int8>(reference_half_pixel_centers_false[i])); + EXPECT_EQ(static_cast<T>(output_data[i]), + static_cast<T>(reference_half_pixel_centers_false[i])); } // Test with half_pixel_centers = true. op_params.half_pixel_centers = true; - reference_ops::ResizeBilinear( + reference_ops::ResizeBilinearInteger( op_params, input_dims_inference, input_data.data(), output_size_dims, output_size_data.data(), output_dims_inference, output_data.data()); // clang-format off - std::vector<int8> reference_half_pixel_centers_true = + std::vector<T> reference_half_pixel_centers_true = { 127, 127, 42, -43, -128, -128, 111, 111, 42, -27, -96, -96, 80, 80, 43, 5, -32, -32, @@ -352,10 +352,18 @@ TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x6_Int8) { // clang-format on for (int i = 0; i < output_buffer_size; i++) { - EXPECT_EQ(static_cast<int8>(output_data[i]), - static_cast<int8>(reference_half_pixel_centers_true[i])); + EXPECT_EQ(static_cast<T>(output_data[i]), + static_cast<T>(reference_half_pixel_centers_true[i])); } } +TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x6_Int8) { + TestResizeBilinearHalfPixelCenters_2x2to4x6<int8_t>(); +} + +TEST(ResizeBilinear, TestResizeBilinearHalfPixelCenters_2x2to4x6_Int16) { + TestResizeBilinearHalfPixelCenters_2x2to4x6<int16_t>(); +} + } // namespace } // namespace tflite diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index 9aa14e579d4..c1bfea187a6 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -121,7 +121,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_RESHAPE, Register_RESHAPE()); AddBuiltin(BuiltinOperator_RESIZE_BILINEAR, Register_RESIZE_BILINEAR(), /* min_version = */ 1, - /* max_version = */ 3); + /* max_version = */ 4); AddBuiltin(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR, Register_RESIZE_NEAREST_NEIGHBOR(), /* min_version = */ 1, diff --git a/tensorflow/lite/kernels/register_ref.cc b/tensorflow/lite/kernels/register_ref.cc index 232ae967ad1..08b6b8a0f35 100644 --- a/tensorflow/lite/kernels/register_ref.cc +++ b/tensorflow/lite/kernels/register_ref.cc @@ -286,7 +286,7 @@ BuiltinRefOpResolver::BuiltinRefOpResolver() { AddBuiltin(BuiltinOperator_RESHAPE, Register_RESHAPE()); AddBuiltin(BuiltinOperator_RESIZE_BILINEAR, Register_RESIZE_BILINEAR_REF(), /* min_version = */ 1, - /* max_version = */ 3); + /* max_version = */ 4); AddBuiltin(BuiltinOperator_RESIZE_NEAREST_NEIGHBOR, Register_RESIZE_NEAREST_NEIGHBOR_REF(), /* min_version = */ 1, diff --git a/tensorflow/lite/kernels/resize_bilinear.cc b/tensorflow/lite/kernels/resize_bilinear.cc index 5978a78411c..6db268ad866 100644 --- a/tensorflow/lite/kernels/resize_bilinear.cc +++ b/tensorflow/lite/kernels/resize_bilinear.cc @@ -114,30 +114,32 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { } if (output->type == kTfLiteFloat32) { -#define TF_LITE_RESIZE_BILINEAR(type, datatype) \ - tflite::ResizeBilinearParams op_params; \ - op_params.align_corners = params->align_corners; \ - op_params.half_pixel_centers = params->half_pixel_centers; \ - type::ResizeBilinear(op_params, GetTensorShape(input), \ - GetTensorData<datatype>(input), GetTensorShape(size), \ - GetTensorData<int32>(size), GetTensorShape(output), \ - GetTensorData<datatype>(output)) +#define TF_LITE_RESIZE_BILINEAR(type, opname, datatype) \ + tflite::ResizeBilinearParams op_params; \ + op_params.align_corners = params->align_corners; \ + op_params.half_pixel_centers = params->half_pixel_centers; \ + type::opname(op_params, GetTensorShape(input), \ + GetTensorData<datatype>(input), GetTensorShape(size), \ + GetTensorData<int32>(size), GetTensorShape(output), \ + GetTensorData<datatype>(output)) if (kernel_type == kReference) { - TF_LITE_RESIZE_BILINEAR(reference_ops, float); + TF_LITE_RESIZE_BILINEAR(reference_ops, ResizeBilinear, float); } if (kernel_type == kGenericOptimized || kernel_type == kNeonOptimized) { - TF_LITE_RESIZE_BILINEAR(optimized_ops, float); + TF_LITE_RESIZE_BILINEAR(optimized_ops, ResizeBilinear, float); } } else if (output->type == kTfLiteUInt8) { if (kernel_type == kReference) { - TF_LITE_RESIZE_BILINEAR(reference_ops, uint8_t); + TF_LITE_RESIZE_BILINEAR(reference_ops, ResizeBilinear, uint8_t); } if (kernel_type == kGenericOptimized || kernel_type == kNeonOptimized) { - TF_LITE_RESIZE_BILINEAR(optimized_ops, uint8_t); + TF_LITE_RESIZE_BILINEAR(optimized_ops, ResizeBilinear, uint8_t); } } else if (output->type == kTfLiteInt8) { - TF_LITE_RESIZE_BILINEAR(reference_ops, int8_t); + TF_LITE_RESIZE_BILINEAR(reference_ops, ResizeBilinearInteger, int8_t); + } else if (output->type == kTfLiteInt16) { + TF_LITE_RESIZE_BILINEAR(reference_ops, ResizeBilinearInteger, int16_t); #undef TF_LITE_RESIZE_BILINEAR } else { context->ReportError(context, "Output type is %d, requires float.", diff --git a/tensorflow/lite/kernels/resize_bilinear_test.cc b/tensorflow/lite/kernels/resize_bilinear_test.cc index 6dedc0d169d..9afdca9b7da 100644 --- a/tensorflow/lite/kernels/resize_bilinear_test.cc +++ b/tensorflow/lite/kernels/resize_bilinear_test.cc @@ -104,6 +104,17 @@ TEST_P(ResizeBilinearOpTest, HorizontalResizeInt8) { ElementsAreArray(ArrayFloatNear({3, 5, 6}))); } +TEST_P(ResizeBilinearOpTest, HorizontalResizeInt16) { + if (SingleOpModel::GetForceUseNnapi()) { + return; + } + ResizeBilinearOpModel m({TensorType_INT16, {1, 1, 2, 1}}, {1, 3}, GetParam()); + m.SetInput<int16_t>({3, 6}); + m.Invoke(); + EXPECT_THAT(m.GetOutput<int16_t>(), + ElementsAreArray(ArrayFloatNear({3, 5, 6}))); +} + TEST_P(ResizeBilinearOpTest, VerticalResize) { ResizeBilinearOpModel m({TensorType_FLOAT32, {1, 2, 1, 1}}, {3, 1}, GetParam()); @@ -129,6 +140,17 @@ TEST_P(ResizeBilinearOpTest, VerticalResizeInt8) { ElementsAreArray(ArrayFloatNear({3, 7, 9}))); } +TEST_P(ResizeBilinearOpTest, VerticalResizeInt16) { + if (SingleOpModel::GetForceUseNnapi()) { + return; + } + ResizeBilinearOpModel m({TensorType_INT16, {1, 2, 1, 1}}, {3, 1}, GetParam()); + m.SetInput<int16_t>({3, 9}); + m.Invoke(); + EXPECT_THAT(m.GetOutput<int16_t>(), + ElementsAreArray(ArrayFloatNear({3, 7, 9}))); +} + TEST_P(ResizeBilinearOpTest, TwoDimensionalResize) { ResizeBilinearOpModel m({TensorType_FLOAT32, {1, 2, 2, 1}}, {3, 3}, GetParam()); @@ -172,6 +194,23 @@ TEST_P(ResizeBilinearOpTest, TwoDimensionalResizeInt8) { }))); } +TEST_P(ResizeBilinearOpTest, TwoDimensionalResizeInt16) { + if (SingleOpModel::GetForceUseNnapi()) { + return; + } + ResizeBilinearOpModel m({TensorType_INT16, {1, 2, 2, 1}}, {3, 3}, GetParam()); + m.SetInput<int16_t>({ + 3, 6, // + 9, 12 // + }); + m.Invoke(); + EXPECT_THAT(m.GetOutput<int16_t>(), ElementsAreArray(ArrayFloatNear({ + 3, 5, 6, // + 7, 9, 10, // + 9, 11, 12, // + }))); +} + TEST_P(ResizeBilinearOpTest, TwoDimensionalResizeWithTwoBatches) { ResizeBilinearOpModel m({TensorType_FLOAT32, {2, 2, 2, 1}}, {3, 3}, GetParam()); @@ -295,6 +334,30 @@ TEST_P(ResizeBilinearOpTest, TwoDimensionalResizeWithTwoBatchesInt8) { /*max_abs_error=*/1))); } +TEST_P(ResizeBilinearOpTest, TwoDimensionalResizeWithTwoBatchesInt16) { + if (SingleOpModel::GetForceUseNnapi()) { + return; + } + ResizeBilinearOpModel m({TensorType_INT16, {2, 2, 2, 1}}, {3, 3}, GetParam()); + m.SetInput<int16_t>({ + 3, 6, // + 9, 12, // + 4, 10, // + 12, 16 // + }); + m.Invoke(); + EXPECT_THAT(m.GetOutput<int16_t>(), ElementsAreArray(ArrayFloatNear( + { + 3, 5, 6, // + 7, 9, 10, // + 9, 11, 12, // + 4, 8, 10, // + 9, 12, 13, // + 12, 14, 16, // + }, + /*max_abs_error=*/1))); +} + TEST_P(ResizeBilinearOpTest, ThreeDimensionalResizeUInt8) { ResizeBilinearOpModel m({TensorType_UINT8, {1, 2, 2, 2}}, {3, 3}, GetParam()); m.SetInput<uint8>({ @@ -327,6 +390,52 @@ TEST_P(ResizeBilinearOpTest, ThreeDimensionalResizeInt8) { /*max_abs_error=*/1))); } +TEST_P(ResizeBilinearOpTest, ThreeDimensionalResizeInt16) { + if (SingleOpModel::GetForceUseNnapi()) { + return; + } + ResizeBilinearOpModel m({TensorType_INT16, {1, 2, 2, 2}}, {3, 3}, GetParam()); + m.SetInput<int16_t>({ + 3, 4, 6, 10, // + 10, 12, 14, 16, // + }); + m.Invoke(); + EXPECT_THAT(m.GetOutput<int16_t>(), ElementsAreArray(ArrayFloatNear( + { + 3, 4, 5, 8, 6, 10, // + 7, 9, 10, 12, 11, 13, // + 10, 12, 12, 14, 14, 16, // + }, + /*max_abs_error=*/1))); +} + +TEST_P(ResizeBilinearOpTest, HorizontalResizeExtremeValuesUInt8) { + ResizeBilinearOpModel m({TensorType_UINT8, {1, 1, 2, 1}}, {1, 3}, GetParam()); + m.SetInput<uint8_t>({253, 255}); + m.Invoke(); + EXPECT_THAT(m.GetOutput<uint8>(), + ElementsAreArray(ArrayFloatNear({253, 254, 255}))); +} + +TEST_P(ResizeBilinearOpTest, HorizontalResizeExtremeValuesInt8) { + ResizeBilinearOpModel m({TensorType_INT8, {1, 1, 2, 1}}, {1, 3}, GetParam()); + m.SetInput<int8_t>({125, 127}); + m.Invoke(); + EXPECT_THAT(m.GetOutput<int8_t>(), + ElementsAreArray(ArrayFloatNear({125, 126, 127}))); +} + +TEST_P(ResizeBilinearOpTest, HorizontalResizeExtremeValuesInt16) { + if (SingleOpModel::GetForceUseNnapi()) { + return; + } + ResizeBilinearOpModel m({TensorType_INT16, {1, 1, 2, 1}}, {1, 3}, GetParam()); + m.SetInput<int16_t>({32765, 32767}); + m.Invoke(); + EXPECT_THAT(m.GetOutput<int16_t>(), + ElementsAreArray(ArrayFloatNear({32765, 32766, 32767}))); +} + INSTANTIATE_TEST_SUITE_P(ResizeBilinearOpTest, ResizeBilinearOpTest, testing::Values(TestType::kConst, TestType::kDynamic)); diff --git a/tensorflow/lite/tools/optimize/operator_property.cc b/tensorflow/lite/tools/optimize/operator_property.cc index 2c993945ea1..ae442505689 100644 --- a/tensorflow/lite/tools/optimize/operator_property.cc +++ b/tensorflow/lite/tools/optimize/operator_property.cc @@ -879,12 +879,6 @@ OperatorProperty GetOperatorProperty(const ModelT* model, int subgraph_index, property.version = 1; break; case BuiltinOperator_RESIZE_BILINEAR: - property.inputs = {{0, {}}}; - property.outputs = {{0, {}}}; - property.restrict_same_input_output_scale = true; - property.version = 2; - property.quantizable_int16 = false; - break; case BuiltinOperator_RESIZE_NEAREST_NEIGHBOR: property.inputs = {{0, {}}}; property.outputs = {{0, {}}}; diff --git a/tensorflow/lite/tools/versioning/op_version.cc b/tensorflow/lite/tools/versioning/op_version.cc index 1f84c261cdb..1526921149d 100644 --- a/tensorflow/lite/tools/versioning/op_version.cc +++ b/tensorflow/lite/tools/versioning/op_version.cc @@ -407,7 +407,9 @@ int GetBuiltinOperatorVersion(const OpSignature& op_sig) { } return 1; case BuiltinOperator_RESIZE_BILINEAR: - if (op_sig.options.resize.half_pixel_centers) { + if (op_sig.input_types.at(0) == TensorType_INT16) { + return 4; + } else if (op_sig.options.resize.half_pixel_centers) { return 3; } else if (op_sig.input_types.at(0) == TensorType_INT8) { return 2; diff --git a/tensorflow/lite/tools/versioning/op_version_test.cc b/tensorflow/lite/tools/versioning/op_version_test.cc index 008df41c20e..8a34a45a584 100644 --- a/tensorflow/lite/tools/versioning/op_version_test.cc +++ b/tensorflow/lite/tools/versioning/op_version_test.cc @@ -705,6 +705,15 @@ TEST(OpVersionTest, VersioningResizeBilinearTest) { fake_op_sig.options.resize.half_pixel_centers = true; EXPECT_EQ(GetBuiltinOperatorVersion(fake_op_sig), 3); + + // int16 input is version 4. + fake_op_sig = { + .op = BuiltinOperator_RESIZE_BILINEAR, + .input_types = + std::vector<TensorType>{TensorType_INT16, TensorType_INT32}, + .output_types = std::vector<TensorType>{TensorType_INT16}, + }; + EXPECT_EQ(GetBuiltinOperatorVersion(fake_op_sig), 4); } TEST(OpVersionTest, VersioningResizeNearestNeighborTest) { // Default. diff --git a/tensorflow/lite/tools/versioning/runtime_version.cc b/tensorflow/lite/tools/versioning/runtime_version.cc index fa0b01fc939..27735e6c725 100644 --- a/tensorflow/lite/tools/versioning/runtime_version.cc +++ b/tensorflow/lite/tools/versioning/runtime_version.cc @@ -198,6 +198,7 @@ std::string FindMinimumRuntimeVersionForOp(tflite::BuiltinOperator op_code, {{BuiltinOperator_RESIZE_BILINEAR, 1}, "1.7.0"}, {{BuiltinOperator_RESIZE_BILINEAR, 2}, "1.14.0"}, {{BuiltinOperator_RESIZE_BILINEAR, 3}, "2.2.0"}, + {{BuiltinOperator_RESIZE_BILINEAR, 4}, kPendingReleaseVersion}, {{BuiltinOperator_RESIZE_NEAREST_NEIGHBOR, 1}, "1.13.1"}, {{BuiltinOperator_RESIZE_NEAREST_NEIGHBOR, 2}, "1.14.0"}, {{BuiltinOperator_RESIZE_NEAREST_NEIGHBOR, 3}, "2.3.0"},