Merge pull request from Tessil:toupstream/16x8_mean_operator

PiperOrigin-RevId: 341637416
Change-Id: Id3f00de59ee8588ee68e9401189a031a073f0628
This commit is contained in:
TensorFlower Gardener 2020-11-10 09:53:03 -08:00
commit c0ec1e2525
9 changed files with 93 additions and 36 deletions

View File

@ -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`,

View File

@ -160,7 +160,7 @@ DepthToSpaceOpModel/int8
FloatDivOpTest/.+
# elementwise_test
ElementWise/Abs
ElementWise/Abs,29
ElementWise/Sin,29
ElementWise/Log,29
ElementWise/Sqrt,29

View File

@ -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<IsAbsSupportedType, kAbsName>(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<OpData*>(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<OpData*>(buffer);
}
template <typename T>
TfLiteStatus AbsEvalQuantized(TfLiteContext* context, TfLiteNode* node,
TfLiteType type) {
const auto* op_data = static_cast<const OpData*>(node->user_data);
const int kMin = std::numeric_limits<T>::min();
const int kMax = std::numeric_limits<T>::max();
std::function<T(T)> 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<T>(std::min(std::max(output, kMin), kMax));
};
return EvalImpl<T>(context, node, func, type);
}
TfLiteStatus AbsEval(TfLiteContext* context, TfLiteNode* node) {
const TfLiteType type = GetInput(context, node, 0)->type;
switch (type) {
case kTfLiteFloat32:
return EvalImpl<float>(context, node, std::abs<float>, type);
case kTfLiteInt8: {
const auto* op_data = static_cast<const OpData*>(node->user_data);
const int kMinInt8 = std::numeric_limits<int8_t>::min();
const int kMaxInt8 = std::numeric_limits<int8_t>::max();
std::function<int8_t(int8_t)> 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<int8_t>(context, node, func, type);
}
case kTfLiteInt8:
return AbsEvalQuantized<int8_t>(context, node, type);
case kTfLiteInt16:
return AbsEvalQuantized<int16_t>(context, node, type);
default:
TF_LITE_KERNEL_LOG(context, "Current data type %s is not supported.",
TfLiteTypeGetName(type));

View File

@ -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<int16_t>::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 <typename T>
float GetQuantizationStep(float min, float max) {
const float kQuantizedStep = (max - min) / (std::numeric_limits<T>::max() -
std::numeric_limits<T>::min());
return kQuantizedStep;
}
TEST(ElementWise, Sin) {
ElementWiseOpFloatModel m(BuiltinOperator_SIN, {1, 1, 4, 1});
m.PopulateTensor<float>(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<float>(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<float> data = {15., 46., 78., -142., -1., -17., -49., 113.};
std::vector<float> 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<int16_t>(-150, 150);
std::vector<float> data = {15., 46., 78., -142., -1., -17., -49., 113.};
std::vector<float> 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<int16_t>(m.input(), data);
m.Invoke();
EXPECT_THAT(m.ExtractDequantVector<int16_t>(m.output()),
ElementsAreArray(ArrayFloatNear(abs_data, kQuantizedTolerance)));
}
TEST(ElementWise, Sqrt) {
ElementWiseOpFloatModel m(BuiltinOperator_SQRT, {1, 1, 4, 1});
m.PopulateTensor<float>(m.input(), {0, 1, 2, 4});

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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>{TensorType_INT8},
.output_types = std::vector<TensorType>{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>{TensorType_INT16},
.output_types = std::vector<TensorType>{TensorType_INT16},
};
EXPECT_EQ(GetBuiltinOperatorVersion(fake_op_sig), 3);
}
} // namespace tflite

View File

@ -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"},