Merge pull request #43098 from Tessil:toupstream/16x8_mean_operator
PiperOrigin-RevId: 341637416 Change-Id: Id3f00de59ee8588ee68e9401189a031a073f0628
This commit is contained in:
commit
c0ec1e2525
@ -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`,
|
||||
|
@ -160,7 +160,7 @@ DepthToSpaceOpModel/int8
|
||||
FloatDivOpTest/.+
|
||||
|
||||
# elementwise_test
|
||||
ElementWise/Abs
|
||||
ElementWise/Abs,29
|
||||
ElementWise/Sin,29
|
||||
ElementWise/Log,29
|
||||
ElementWise/Sqrt,29
|
||||
|
@ -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));
|
||||
|
@ -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});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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"},
|
||||
|
Loading…
Reference in New Issue
Block a user