diff --git a/RELEASE.md b/RELEASE.md index 90356204cce..755c2eb3c10 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -46,7 +46,7 @@ * Use `NnApiDelegate()` and related delegate configuration methods directly. * 16 bits quantization - * Added int16x8 support for REDUCE_MIN and REDUCE_MAX operators. + * Added int16x8 support for ABS, REDUCE_MAX and REDUCE_MIN operators. * TF Core: * Corrected higher-order gradients of control flow constructs (`tf.cond`, diff --git a/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc b/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc index 5af67a2e122..240448799b7 100644 --- a/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc +++ b/tensorflow/lite/delegates/nnapi/acceleration_test_list.cc @@ -160,7 +160,7 @@ DepthToSpaceOpModel/int8 FloatDivOpTest/.+ # elementwise_test -ElementWise/Abs +ElementWise/Abs,29 ElementWise/Sin,29 ElementWise/Log,29 ElementWise/Sqrt,29 diff --git a/tensorflow/lite/kernels/elementwise.cc b/tensorflow/lite/kernels/elementwise.cc index 40f13002b8e..94d2b255324 100644 --- a/tensorflow/lite/kernels/elementwise.cc +++ b/tensorflow/lite/kernels/elementwise.cc @@ -58,7 +58,7 @@ bool IsLogicalSupportedType(const TfLiteType type) { } bool IsAbsSupportedType(const TfLiteType type) { - return type == kTfLiteFloat32 || type == kTfLiteInt8; + return type == kTfLiteFloat32 || type == kTfLiteInt8 || type == kTfLiteInt16; } typedef bool (*IsSupportedType)(TfLiteType); @@ -83,7 +83,7 @@ TfLiteStatus AbsPrepare(TfLiteContext* context, TfLiteNode* node) { context, (GenericPrepare(context, node)), kTfLiteOk); const TfLiteTensor* input = GetInput(context, node, 0); - if (input->type == kTfLiteInt8) { + if (input->type == kTfLiteInt8 || input->type == kTfLiteInt16) { TfLiteTensor* output = GetOutput(context, node, 0); auto* op_data = static_cast(node->user_data); TF_LITE_ENSURE_EQ(context, input->quantization.type, @@ -104,6 +104,10 @@ TfLiteStatus AbsPrepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE(context, output_params->zero_point->size > 0); op_data->input_offset = input_params->zero_point->data[0]; op_data->output_offset = output_params->zero_point->data[0]; + if (input->type == kTfLiteInt16) { + TF_LITE_ENSURE_EQ(context, op_data->input_offset, 0); + TF_LITE_ENSURE_EQ(context, op_data->output_offset, 0); + } const float input_scale = input_params->scale->data[0]; const float output_scale = output_params->scale->data[0]; double scale = input_scale / output_scale; @@ -148,26 +152,34 @@ void AbsFree(TfLiteContext* context, void* buffer) { delete static_cast(buffer); } +template +TfLiteStatus AbsEvalQuantized(TfLiteContext* context, TfLiteNode* node, + TfLiteType type) { + const auto* op_data = static_cast(node->user_data); + const int kMin = std::numeric_limits::min(); + const int kMax = std::numeric_limits::max(); + + std::function func = [&](T i) { + const int32_t value = std::abs(i - op_data->input_offset); + const int32_t output = MultiplyByQuantizedMultiplier( + value, op_data->multiplier, op_data->shift) + + op_data->output_offset; + + return static_cast(std::min(std::max(output, kMin), kMax)); + }; + + return EvalImpl(context, node, func, type); +} + TfLiteStatus AbsEval(TfLiteContext* context, TfLiteNode* node) { const TfLiteType type = GetInput(context, node, 0)->type; switch (type) { case kTfLiteFloat32: return EvalImpl(context, node, std::abs, type); - case kTfLiteInt8: { - const auto* op_data = static_cast(node->user_data); - const int kMinInt8 = std::numeric_limits::min(); - const int kMaxInt8 = std::numeric_limits::max(); - std::function func = [&](int8_t i) { - const int32_t value = std::abs(i - op_data->input_offset); - return std::min( - std::max(op_data->output_offset + - MultiplyByQuantizedMultiplier( - value, op_data->multiplier, op_data->shift), - kMinInt8), - kMaxInt8); - }; - return EvalImpl(context, node, func, type); - } + case kTfLiteInt8: + return AbsEvalQuantized(context, node, type); + case kTfLiteInt16: + return AbsEvalQuantized(context, node, type); default: TF_LITE_KERNEL_LOG(context, "Current data type %s is not supported.", TfLiteTypeGetName(type)); diff --git a/tensorflow/lite/kernels/elementwise_test.cc b/tensorflow/lite/kernels/elementwise_test.cc index e0f198f8f9b..f03c4cefd24 100644 --- a/tensorflow/lite/kernels/elementwise_test.cc +++ b/tensorflow/lite/kernels/elementwise_test.cc @@ -47,12 +47,12 @@ class ElementWiseOpFloatModel : public ElementWiseOpBaseModel { } }; -class ElementWiseOpInt8Model : public ElementWiseOpBaseModel { +class ElementWiseOpQuantizedModel : public ElementWiseOpBaseModel { public: - ElementWiseOpInt8Model(BuiltinOperator op, TensorData input_tensor_data, - TensorData output_tensor_data) { - input_ = AddInput(input_tensor_data); - output_ = AddOutput(output_tensor_data); + ElementWiseOpQuantizedModel(BuiltinOperator op, TensorData input_tensor_data, + TensorData output_tensor_data) { + input_ = AddInput(SymmetricInt16Scaling(input_tensor_data)); + output_ = AddOutput(SymmetricInt16Scaling(output_tensor_data)); SetBuiltinOp(op, BuiltinOptions_NONE, 0); BuildInterpreter({input_tensor_data.shape}); } @@ -83,6 +83,24 @@ class ElementWiseOpInt8Model : public ElementWiseOpBaseModel { } return output; } + + private: + TensorData& SymmetricInt16Scaling(TensorData& tensor) { + // Symmetric range and null zero-point is required for INT16 tensors. As + // SingleOpModel::QuantizationParams calculates the scale on an asymmetric + // base [int_type::min, int_type::max], manually calculate the scale on a + // symmetric range [int_type::min+1, int_type::max] to ensure a null + // zero-point. + if (tensor.type == TensorType_INT16) { + CHECK_EQ(std::abs(tensor.min), tensor.max); + tensor.scale = tensor.max / std::numeric_limits::max(); + tensor.zero_point = 0; + tensor.min = 0; + tensor.max = 0; + } + + return tensor; + } }; class ElementWiseOpBoolModel : public ElementWiseOpBaseModel { @@ -96,6 +114,13 @@ class ElementWiseOpBoolModel : public ElementWiseOpBaseModel { } }; +template +float GetQuantizationStep(float min, float max) { + const float kQuantizedStep = (max - min) / (std::numeric_limits::max() - + std::numeric_limits::min()); + return kQuantizedStep; +} + TEST(ElementWise, Sin) { ElementWiseOpFloatModel m(BuiltinOperator_SIN, {1, 1, 4, 1}); m.PopulateTensor(m.input(), {0, 3.1415926, -3.1415926, 1}); @@ -123,7 +148,7 @@ TEST(ElementWise, Log) { EXPECT_THAT(m.GetTensorShape(m.output()), ElementsAreArray({1, 1, 4, 1})); } -TEST(FloatActivationsOpTest, Abs) { +TEST(ElementWise, Abs) { ElementWiseOpFloatModel m(BuiltinOperator_ABS, {1, 2, 4, 1}); m.PopulateTensor(m.input(), { 0.f, -6.2f, 2.f, 4.f, // @@ -136,7 +161,7 @@ TEST(FloatActivationsOpTest, Abs) { })); } -TEST(FloatActivationsOpTest, AbsInt8) { +TEST(ElementWise, AbsInt8) { std::vector data = {15., 46., 78., -142., -1., -17., -49., 113.}; std::vector abs_data(data.size()); for (int i = 0; i < abs_data.size(); i++) { @@ -148,7 +173,7 @@ TEST(FloatActivationsOpTest, AbsInt8) { const float kOutputScale = abs_max / 255.0; const int input_zero_point = 127 - *minmax.second; const int output_zero_point = -128; - ElementWiseOpInt8Model m( + ElementWiseOpQuantizedModel m( BuiltinOperator_ABS, {TensorType_INT8, {1, 8}, @@ -166,6 +191,22 @@ TEST(FloatActivationsOpTest, AbsInt8) { ElementsAreArray(ArrayFloatNear(abs_data, kInputScale))); } +TEST(ElementWise, AbsInt16) { + const float kQuantizedTolerance = GetQuantizationStep(-150, 150); + std::vector data = {15., 46., 78., -142., -1., -17., -49., 113.}; + std::vector abs_data(data.size()); + for (int i = 0; i < abs_data.size(); i++) { + abs_data[i] = std::abs(data[i]); + } + ElementWiseOpQuantizedModel m(BuiltinOperator_ABS, + {TensorType_INT16, {1, 8}, -142, 142}, + {TensorType_INT16, {1, 8}, -150, 150}); + m.QuantizeAndPopulate(m.input(), data); + m.Invoke(); + EXPECT_THAT(m.ExtractDequantVector(m.output()), + ElementsAreArray(ArrayFloatNear(abs_data, kQuantizedTolerance))); +} + TEST(ElementWise, Sqrt) { ElementWiseOpFloatModel m(BuiltinOperator_SQRT, {1, 1, 4, 1}); m.PopulateTensor(m.input(), {0, 1, 2, 4}); diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index 890d60160e0..cd0c297a545 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -35,7 +35,7 @@ namespace builtin { BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_ABS, Register_ABS(), /* min_version = */ 1, - /* max_version = */ 2); + /* max_version = */ 3); AddBuiltin(BuiltinOperator_HARD_SWISH, Register_HARD_SWISH()); AddBuiltin(BuiltinOperator_RELU, Register_RELU(), /* min_version = */ 1, /* max_version = */ 3); diff --git a/tensorflow/lite/kernels/register_ref.cc b/tensorflow/lite/kernels/register_ref.cc index 8ae7b2fd290..95727501f0d 100644 --- a/tensorflow/lite/kernels/register_ref.cc +++ b/tensorflow/lite/kernels/register_ref.cc @@ -191,7 +191,7 @@ const TfLiteRegistration* BuiltinRefOpResolver::FindOp(const char* op, BuiltinRefOpResolver::BuiltinRefOpResolver() { AddBuiltin(BuiltinOperator_ABS, Register_ABS(), /* min_version = */ 1, - /* max_version = */ 2); + /* max_version = */ 3); AddBuiltin(BuiltinOperator_HARD_SWISH, Register_HARD_SWISH_REF()); AddBuiltin(BuiltinOperator_RELU, Register_RELU(), /* min_version = */ 1, /* max_version = */ 3); diff --git a/tensorflow/lite/tools/versioning/op_version.cc b/tensorflow/lite/tools/versioning/op_version.cc index 9b271d33aaf..b14bc7cd3a0 100644 --- a/tensorflow/lite/tools/versioning/op_version.cc +++ b/tensorflow/lite/tools/versioning/op_version.cc @@ -375,6 +375,7 @@ int GetBuiltinOperatorVersion(const OpSignature& op_sig) { } return 1; + case BuiltinOperator_ABS: case BuiltinOperator_RELU: if (op_sig.input_types.at(0) == TensorType_INT16) { return 3; @@ -385,12 +386,6 @@ int GetBuiltinOperatorVersion(const OpSignature& op_sig) { } return 1; - case BuiltinOperator_ABS: - if (op_sig.input_types.at(0) == TensorType_INT8 || - op_sig.input_types.at(0) == TensorType_UINT8) { - return 2; - } - return 1; case BuiltinOperator_STRIDED_SLICE: if (op_sig.options.single_input_op.num_dims > 4) { return 4; diff --git a/tensorflow/lite/tools/versioning/op_version_test.cc b/tensorflow/lite/tools/versioning/op_version_test.cc index b846318e956..008df41c20e 100644 --- a/tensorflow/lite/tools/versioning/op_version_test.cc +++ b/tensorflow/lite/tools/versioning/op_version_test.cc @@ -756,10 +756,18 @@ TEST(OpVersionTest, VersioningAbsTest) { // int8 input is version 2. fake_op_sig = { - .op = BuiltinOperator_RESIZE_NEAREST_NEIGHBOR, + .op = BuiltinOperator_ABS, .input_types = std::vector{TensorType_INT8}, .output_types = std::vector{TensorType_INT8}, }; EXPECT_EQ(GetBuiltinOperatorVersion(fake_op_sig), 2); + + // int16 input is version 3. + fake_op_sig = { + .op = BuiltinOperator_ABS, + .input_types = std::vector{TensorType_INT16}, + .output_types = std::vector{TensorType_INT16}, + }; + EXPECT_EQ(GetBuiltinOperatorVersion(fake_op_sig), 3); } } // namespace tflite diff --git a/tensorflow/lite/tools/versioning/runtime_version.cc b/tensorflow/lite/tools/versioning/runtime_version.cc index bfa51ae30ab..2e71882f469 100644 --- a/tensorflow/lite/tools/versioning/runtime_version.cc +++ b/tensorflow/lite/tools/versioning/runtime_version.cc @@ -320,6 +320,7 @@ std::string FindMinimumRuntimeVersionForOp(tflite::BuiltinOperator op_code, {{BuiltinOperator_ZEROS_LIKE, 1}, "1.12.0"}, {{BuiltinOperator_ABS, 1}, "1.13.0"}, {{BuiltinOperator_ABS, 2}, "2.4.0"}, + {{BuiltinOperator_ABS, 3}, kPendingReleaseVersion}, {{BuiltinOperator_HARD_SWISH, 1}, "1.15.0"}, {{BuiltinOperator_FILL, 1}, "1.13.0"}, {{BuiltinOperator_FILL, 2}, "2.3.0"},