Use Requantize after per-channel Conv only if tensor min/max is different from RELU min/max beyond a limit. Also rectify RELU1 bound to (-1, 1)
PiperOrigin-RevId: 317162909 Change-Id: Ice90226436cccf49507bd17877222da755d22644
This commit is contained in:
parent
b0418130b4
commit
07d5aa2309
@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "tensorflow/lite/c/builtin_op_data.h"
|
||||
@ -197,7 +198,7 @@ TfLiteStatus Conv2dOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
|
||||
conv_output_min = 0;
|
||||
conv_output_max = 6;
|
||||
} else if (activation == kTfLiteActRelu1) {
|
||||
conv_output_min = 0;
|
||||
conv_output_min = -1;
|
||||
conv_output_max = 1;
|
||||
} else if (activation == kTfLiteActRelu) {
|
||||
conv_output_min = 0;
|
||||
@ -351,8 +352,12 @@ TfLiteStatus Conv2dOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs,
|
||||
output_max_tensor = AddOutput(sizeof(float), 4, kScalarShape);
|
||||
}
|
||||
|
||||
// Requantize if activation was not None.
|
||||
if (activation != kTfLiteActNone) {
|
||||
// Requantize if activation was not None & the TFLite tensor's min/max is
|
||||
// different (diff > 1e-2) from the RELU bounds.
|
||||
const float min_bound_diff = std::abs(conv_output_min - output_min);
|
||||
const float max_bound_diff = std::abs(conv_output_max - output_max);
|
||||
if (activation != kTfLiteActNone &&
|
||||
(min_bound_diff > 0.01 || max_bound_diff > 0.01)) {
|
||||
auto* requantized_min_const = graph_builder_->AddConstNodeWithData(
|
||||
kScalarShape, reinterpret_cast<char*>(&output_min), sizeof(output_min));
|
||||
auto* requantized_max_const = graph_builder_->AddConstNodeWithData(
|
||||
|
@ -207,6 +207,43 @@ TEST(QuantizedConvolutionOpModel, SimpleConvTestReLU6Activation) {
|
||||
1e-5)));
|
||||
}
|
||||
|
||||
// Same as above, but the output min/max matches the RELU bounds.
|
||||
// Therefore, a Requantize node will not get added after Supernode.
|
||||
TEST(QuantizedConvolutionOpModel,
|
||||
SimpleConvTestReLU6Activation_NoRequantizeRequired) {
|
||||
QuantizedConvolutionOpModel m(
|
||||
BuiltinOperator_CONV_2D, {TensorType_UINT8, {2, 2, 4, 1}, -63.5, 64},
|
||||
{TensorType_UINT8, {3, 2, 2, 1}, -63.5, 64}, {TensorType_UINT8, {}, 0, 6},
|
||||
Padding_VALID, /**dilation_factor**/ 1,
|
||||
/**stride**/ 2, ActivationFunctionType_RELU6);
|
||||
m.SetInput({
|
||||
// First batch
|
||||
1, 1, 1, 1, // row = 1
|
||||
2, 2, 2, 2, // row = 2
|
||||
// Second batch
|
||||
1, 2, 3, 4, // row = 1
|
||||
1, 2, 3, 4, // row = 2
|
||||
});
|
||||
m.SetFilter({
|
||||
1, 2, 3, 4, // first 2x2 filter
|
||||
-1, 1, -1, 1, // second 2x2 filter
|
||||
-1, -1, 1, 1, // third 2x2 filter
|
||||
});
|
||||
m.SetBias({1, 2, 3});
|
||||
|
||||
m.ApplyDelegateAndInvoke();
|
||||
|
||||
EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
|
||||
ElementsAreArray(ArrayFloatNear(
|
||||
{
|
||||
6, 2, 5, // first batch, left
|
||||
6, 2, 5, // first batch, right
|
||||
6, 4, 3, // second batch, left
|
||||
6, 4, 3, // second batch, right
|
||||
},
|
||||
2e-2)));
|
||||
}
|
||||
|
||||
TEST(QuantizedConvolutionOpModel, SimplePerTensor_Int8) {
|
||||
QuantizedConvolutionOpModel m(
|
||||
BuiltinOperator_CONV_2D,
|
||||
@ -512,6 +549,53 @@ TEST(QuantizedConvolutionOpModel, DepthwiseConvSimplePerTensor_Int8) {
|
||||
ElementsAreArray(ArrayFloatNear({43, 48, 40, 52, 3, -4, 4, 4}, 0.6f)));
|
||||
}
|
||||
|
||||
TEST(QuantizedConvolutionOpModel, DepthwiseConvSimplePerTensor_Int8_RELU1) {
|
||||
QuantizedConvolutionOpModel m(
|
||||
BuiltinOperator_DEPTHWISE_CONV_2D,
|
||||
{TensorType_INT8, {1, 2, 3, 1}, -63.5, 64, 0.5, -1},
|
||||
{TensorType_INT8,
|
||||
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
|
||||
{1, 2, 2, 4},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
/*per_channel_quantization=*/true,
|
||||
/*per_channel_quantization_scales=*/{0.1, 2, 3, 0.4},
|
||||
/*per_channel_quantization_offsets=*/{0, 0, 0, 0},
|
||||
/*channel_index=*/3},
|
||||
{TensorType_INT8, {}, -63.5, 64, 0.5, -1}, Padding_VALID,
|
||||
/**dilation_factor**/ 1,
|
||||
/**stride**/ 1, ActivationFunctionType_RELU_N1_TO_1);
|
||||
m.SetInt8Input({
|
||||
// [1 * 2 * 3 * 1] as [batch, y, x, input_channel]
|
||||
3, // batch = 0, y = 0, x = 0
|
||||
1, // batch = 0, y = 0, x = 1
|
||||
-2, // batch = 0, y = 0, x = 2
|
||||
4, // batch = 0, y = 1, x = 0
|
||||
2, // batch = 0, y = 1, x = 1
|
||||
-4, // batch = 0, y = 1, x = 2
|
||||
});
|
||||
m.SetPerChannelQuantizedFilter({
|
||||
// [1 * 2 * 2 * 4] as [input_channel, y, x, output_channel]
|
||||
// depth multiplier = 2
|
||||
1, 2, 3, 4, // y = 0, x = 0
|
||||
3, 4, 5, 6, // y = 0, x = 1
|
||||
7, 8, 5, 6, // y = 1, x = 0
|
||||
3, 4, 1, 2, // y = 1, x = 1
|
||||
});
|
||||
m.SetPerChannelQuantizedBias({3, -2, 4, 6});
|
||||
|
||||
// Reference output.
|
||||
m.Invoke();
|
||||
auto reference_output = m.GetDequantizedOutput<int8_t>();
|
||||
|
||||
m.ApplyDelegateAndInvoke();
|
||||
|
||||
EXPECT_THAT(m.GetDequantizedOutput<int8_t>(),
|
||||
ElementsAreArray(ArrayFloatNear(reference_output, 1e-2)));
|
||||
}
|
||||
|
||||
TEST(QuantizedConvolutionOpModel, DepthwiseConvSimplePerAxis_Int8) {
|
||||
QuantizedConvolutionOpModel m(
|
||||
BuiltinOperator_DEPTHWISE_CONV_2D,
|
||||
|
Loading…
x
Reference in New Issue
Block a user