From 5742350cb05c03b250648ceefaa9b637de233390 Mon Sep 17 00:00:00 2001 From: Thibaut Goetghebuer-Planchon Date: Mon, 7 Sep 2020 16:15:23 +0100 Subject: [PATCH 1/2] Add int16x8 support for ABS operator --- tensorflow/lite/kernels/elementwise.cc | 46 +++++++++------ tensorflow/lite/kernels/elementwise_test.cc | 57 ++++++++++++++++--- tensorflow/lite/kernels/register.cc | 2 +- .../lite/tools/versioning/op_version.cc | 9 +++ .../lite/tools/versioning/op_version_test.cc | 10 +++- .../lite/tools/versioning/runtime_version.cc | 1 + 6 files changed, 98 insertions(+), 27 deletions(-) diff --git a/tensorflow/lite/kernels/elementwise.cc b/tensorflow/lite/kernels/elementwise.cc index d23cdedc6c8..9212aeb6cd8 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); @@ -81,7 +81,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, @@ -102,6 +102,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; @@ -144,26 +148,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 e020298fc8f..27ae7da7970 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 = */ 2); diff --git a/tensorflow/lite/tools/versioning/op_version.cc b/tensorflow/lite/tools/versioning/op_version.cc index 7edf459eb90..46b0bb06ceb 100644 --- a/tensorflow/lite/tools/versioning/op_version.cc +++ b/tensorflow/lite/tools/versioning/op_version.cc @@ -369,6 +369,15 @@ int GetBuiltinOperatorVersion(const OpSignature& op_sig) { return 1; case BuiltinOperator_ABS: + if (op_sig.input_types.at(0) == TensorType_INT16) { + return 3; + } + if (op_sig.input_types.at(0) == TensorType_INT8 || + op_sig.input_types.at(0) == TensorType_UINT8) { + return 2; + } + return 1; + case BuiltinOperator_RELU: if (op_sig.input_types.at(0) == TensorType_INT8 || op_sig.input_types.at(0) == TensorType_UINT8) { diff --git a/tensorflow/lite/tools/versioning/op_version_test.cc b/tensorflow/lite/tools/versioning/op_version_test.cc index 82ebad701cd..aa834790091 100644 --- a/tensorflow/lite/tools/versioning/op_version_test.cc +++ b/tensorflow/lite/tools/versioning/op_version_test.cc @@ -721,10 +721,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 a656356b84c..dd7cb89f020 100644 --- a/tensorflow/lite/tools/versioning/runtime_version.cc +++ b/tensorflow/lite/tools/versioning/runtime_version.cc @@ -306,6 +306,7 @@ std::string FindMinimumRuntimeVersionForOp(tflite::BuiltinOperator op_code, {{BuiltinOperator_ZEROS_LIKE, 1}, "1.12.0"}, {{BuiltinOperator_ABS, 1}, "1.13.0"}, {{BuiltinOperator_ABS, 2}, kPendingReleaseVersion}, + {{BuiltinOperator_ABS, 3}, kPendingReleaseVersion}, {{BuiltinOperator_HARD_SWISH, 1}, "1.15.0"}, {{BuiltinOperator_FILL, 1}, "1.13.0"}, {{BuiltinOperator_FILL, 2}, "2.3.0"}, From c03abbfd76cc85a9956cbd1bb68f8e99d1043347 Mon Sep 17 00:00:00 2001 From: Thibaut Goetghebuer-Planchon Date: Fri, 18 Sep 2020 12:24:16 +0100 Subject: [PATCH 2/2] Update ABS operator version supported by the reference kernel --- tensorflow/lite/kernels/register_ref.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/lite/kernels/register_ref.cc b/tensorflow/lite/kernels/register_ref.cc index c8fb46adb96..45b8e36b655 100644 --- a/tensorflow/lite/kernels/register_ref.cc +++ b/tensorflow/lite/kernels/register_ref.cc @@ -190,7 +190,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 = */ 2);