Introduce rounding into depthwise conv reference / test.

PiperOrigin-RevId: 232504607
This commit is contained in:
A. Unique TensorFlower 2019-02-05 09:51:21 -08:00 committed by TensorFlower Gardener
parent 32b84541e4
commit f6a39be9f4
2 changed files with 186 additions and 92 deletions

View File

@ -57,7 +57,8 @@ enum class CoverageExtension {
// The TestParam structure below is the preferred parameterization of tests. A
// tuple version is defined in order to support value-parameterized tests.
typedef std::tuple<DepthwiseConvInvocation, int, bool, bool, bool>
typedef std::tuple<DepthwiseConvInvocation, int, bool, bool, bool,
DepthwiseConvOutputRounding, bool>
TestParamTuple;
struct TestParam {
@ -68,7 +69,9 @@ struct TestParam {
tests_to_run(::testing::get<1>(param_tuple)),
test_stride(::testing::get<2>(param_tuple)),
test_pad(::testing::get<3>(param_tuple)),
test_depth_multiplier(::testing::get<4>(param_tuple)) {}
test_depth_multiplier(::testing::get<4>(param_tuple)),
output_rounding(::testing::get<5>(param_tuple)),
loose_tolerance(::testing::get<6>(param_tuple)) {}
static std::string TestNameSuffix(
const ::testing::TestParamInfo<TestParamTuple>& info) {
@ -84,6 +87,9 @@ struct TestParam {
bool test_stride = false;
bool test_pad = false;
bool test_depth_multiplier = false;
DepthwiseConvOutputRounding output_rounding =
DepthwiseConvOutputRounding::kNone;
bool loose_tolerance = false;
};
inline void DispatchDepthwiseConv(
@ -183,9 +189,30 @@ int TestOneDepthwiseConvWithGivenOutputShift(
op_params.output_offset = output_offset;
op_params.output_multiplier = output_multiplier;
op_params.output_shift = -output_shift;
reference_ops::DepthwiseConv(op_params, input_shape, input_data, filter_shape,
filter_data, bias_shape, bias_data, output_shape,
reference_output_data.data());
switch (test_param.output_rounding) {
case DepthwiseConvOutputRounding::kUpward:
reference_ops::DepthwiseConvBasicKernel<
DepthwiseConvOutputRounding::kAwayFromZero>::Run(op_params,
input_shape,
input_data,
filter_shape,
filter_data,
bias_shape,
bias_data,
output_shape,
reference_output_data
.data());
break;
case DepthwiseConvOutputRounding::kAwayFromZero:
reference_ops::DepthwiseConv(
op_params, input_shape, input_data, filter_shape, filter_data,
bias_shape, bias_data, output_shape, reference_output_data.data());
break;
case DepthwiseConvOutputRounding::kNone:
default:
EXPECT_NE(test_param.output_rounding, DepthwiseConvOutputRounding::kNone);
break;
}
DispatchDepthwiseConv(test_param, op_params, input_shape, input_data,
filter_shape, filter_data, bias_shape, bias_data,
output_shape, output_data.data());
@ -221,10 +248,10 @@ int TestOneDepthwiseConvWithGivenOutputShift(
// Normally we should require bit-for-bit exact results. Unfortunately a bug
// in the Intel arm_neon_sse.h translation header that we use for x86 tests
// causes 1-bit inaccuracy in
// the vqrdmulh_n_s32 intrinsic, which causes off-by-1 errors in quantized
// DepthwiseConv ops. So we have to live with a few off-by-one errors for now,
// yet still ensure that no more than a small minority of values are wrong.
// causes 1-bit inaccuracy in the vqrdmulh_n_s32 intrinsic, which causes
// off-by-1 errors in quantized DepthwiseConv ops. So we have to live with a
// few off-by-one errors for now, yet still ensure that no more than a small
// minority of values are wrong.
EXPECT_LT(std::abs(mean_diff), mean_tolerance);
EXPECT_LT(mean_abs_diff, mean_tolerance);
EXPECT_LE(std::abs(median_diff), diff_median_tolerance);
@ -482,16 +509,21 @@ bool TryTestOneNeonDot3x3(const TestParam& test_param,
dilation_width_factor, dilation_height_factor, padding_type);
}
void TestOneDepthwiseConv(DepthwiseConvInvocation forced_invocation) {
void TestOneDepthwiseConv(DepthwiseConvInvocation forced_invocation,
DepthwiseConvOutputRounding output_rounding) {
TestParam test_param;
test_param.forced_invocation = forced_invocation;
test_param.output_rounding = output_rounding;
while (!TryTestOneDepthwiseConv(test_param, ParamsSpecialization::kNone)) {
}
}
void TestOneDepthwiseConv3x3Filter(DepthwiseConvInvocation forced_invocation) {
void TestOneDepthwiseConv3x3Filter(
DepthwiseConvInvocation forced_invocation,
DepthwiseConvOutputRounding output_rounding) {
TestParam test_param;
test_param.forced_invocation = forced_invocation;
test_param.output_rounding = output_rounding;
while (!TryTestOneDepthwiseConv3x3Filter(test_param,
ParamsSpecialization::kNone)) {
}
@ -505,7 +537,8 @@ void TestOneNeonDot3x3(const TestParam& test_param) {
TEST(TestDepthwiseConv, TestDepthwiseConv) {
const int kTestsToRun = 10 * 1000;
for (int i = 0; i < kTestsToRun; i++) {
TestOneDepthwiseConv(DepthwiseConvInvocation::kNone);
TestOneDepthwiseConv(DepthwiseConvInvocation::kNone,
DepthwiseConvOutputRounding::kAwayFromZero);
}
}
@ -513,14 +546,16 @@ TEST(TestDepthwiseConv, TestDepthwiseConv) {
TEST(TestDepthwiseConv, TestGenericKernel) {
const int kTestsToRun = 10 * 1000;
for (int i = 0; i < kTestsToRun; i++) {
TestOneDepthwiseConv(DepthwiseConvInvocation::kUseGenericKernel);
TestOneDepthwiseConv(DepthwiseConvInvocation::kUseGenericKernel,
DepthwiseConvOutputRounding::kAwayFromZero);
}
}
TEST(TestDepthwiseConv, TestKernel3x3Filter) {
const int kTestsToRun = 1000;
for (int i = 0; i < kTestsToRun; i++) {
TestOneDepthwiseConv3x3Filter(DepthwiseConvInvocation::kNone);
TestOneDepthwiseConv3x3Filter(DepthwiseConvInvocation::kNone,
DepthwiseConvOutputRounding::kAwayFromZero);
}
}
@ -529,7 +564,8 @@ TEST(TestDepthwiseConv, TestKernel3x3Filter) {
TEST(TestDepthwiseConv, TestGenericKernel3x3Filter) {
const int kTestsToRun = 100;
for (int i = 0; i < kTestsToRun; i++) {
TestOneDepthwiseConv3x3Filter(DepthwiseConvInvocation::kUseGenericKernel);
TestOneDepthwiseConv3x3Filter(DepthwiseConvInvocation::kUseGenericKernel,
DepthwiseConvOutputRounding::kAwayFromZero);
}
}
@ -537,7 +573,8 @@ TEST(TestDepthwiseConv, TestGenericKernel3x3Filter) {
TEST(TestDepthwiseConv, TestNeon3x3Filter) {
const int kTestsToRun = 3 * 1000;
for (int i = 0; i < kTestsToRun; i++) {
TestOneDepthwiseConv3x3Filter(DepthwiseConvInvocation::kUseNeon3x3);
TestOneDepthwiseConv3x3Filter(DepthwiseConvInvocation::kUseNeon3x3,
DepthwiseConvOutputRounding::kAwayFromZero);
}
}
#endif
@ -559,7 +596,9 @@ INSTANTIATE_TEST_SUITE_P(
Values(1000), // tests_to_run
Bool(), // test_stride
Values(false), // test_pad
Values(false) // test_depth_multiplier
Values(false), // test_depth_multiplier
Values(DepthwiseConvOutputRounding::kAwayFromZero), // output_rounding
Values(false) // loose_tolerance
),
TestParam::TestNameSuffix);
#endif
@ -574,7 +613,9 @@ INSTANTIATE_TEST_SUITE_P(
Values(100), // tests_to_run
Bool(), // test_stride
Bool(), // test_pad
Bool() // test_depth_multiplier
Bool(), // test_depth_multiplier
Values(DepthwiseConvOutputRounding::kUpward), // output_rounding
Values(false) // loose_tolerance
),
TestParam::TestNameSuffix);

View File

@ -44,6 +44,14 @@ enum class DepthwiseConvInvocation {
kUseIntrinsics3x3DotProduct, // 3x3 kernel using NEON intrinsics.
};
// Category of depthwise convolution output rounding.
enum class DepthwiseConvOutputRounding {
kNone = 0, // Invalid: specific method must be specified.
kAwayFromZero, // Original method: exact halves rounded away from zero.
kUpward, // Halves towards +infinity: adds 0.5 before truncate.
// This is where a future kNearestEven would be placed.
};
// Category of depthwise convolution depth multiplication.
enum class DepthwiseConvDepthMultiplication {
kNoMultiplication = 0, // Depth multiplier = 1.
@ -52,12 +60,41 @@ enum class DepthwiseConvDepthMultiplication {
namespace reference_ops {
inline void DepthwiseConv(
const DepthwiseParams& params, const RuntimeShape& input_shape,
const uint8* input_data, const RuntimeShape& filter_shape,
const uint8* filter_data, const RuntimeShape& bias_shape,
const int32* bias_data, const RuntimeShape& output_shape,
uint8* output_data) {
template <DepthwiseConvOutputRounding output_rounding>
inline int32 DepthwiseConvRound(int32 x, int32 quantized_multiplier,
int shift) {
TFLITE_DCHECK_NE(output_rounding, DepthwiseConvOutputRounding::kNone);
return MultiplyByQuantizedMultiplier(x, quantized_multiplier, shift);
}
template <>
inline int32 DepthwiseConvRound<DepthwiseConvOutputRounding::kAwayFromZero>(
int32 x, int32 quantized_multiplier, int shift) {
return MultiplyByQuantizedMultiplier(x, quantized_multiplier, shift);
}
template <>
inline int32 DepthwiseConvRound<DepthwiseConvOutputRounding::kUpward>(
int32 x, int32 quantized_multiplier, int shift) {
using gemmlowp::SaturatingRoundingDoublingHighMul;
const int left_shift = shift > 0 ? shift : 0;
const int right_shift = shift > 0 ? 0 : -shift;
const int rounding_offset = right_shift > 0 ? 1 << (right_shift - 1) : 0;
return (SaturatingRoundingDoublingHighMul(x * (1 << left_shift),
quantized_multiplier) +
rounding_offset) >>
right_shift;
}
template <DepthwiseConvOutputRounding output_rounding>
struct DepthwiseConvBasicKernel {
static inline void Run(const DepthwiseParams& params,
const RuntimeShape& input_shape,
const uint8* input_data,
const RuntimeShape& filter_shape,
const uint8* filter_data,
const RuntimeShape& bias_shape, const int32* bias_data,
const RuntimeShape& output_shape, uint8* output_data) {
const int stride_width = params.stride_width;
const int stride_height = params.stride_height;
const int dilation_width_factor = params.dilation_width_factor;
@ -100,7 +137,8 @@ inline void DepthwiseConv(
int32 acc = 0;
for (int filter_y = 0; filter_y < filter_height; ++filter_y) {
for (int filter_x = 0; filter_x < filter_width; ++filter_x) {
const int in_x = in_x_origin + dilation_width_factor * filter_x;
const int in_x =
in_x_origin + dilation_width_factor * filter_x;
const int in_y =
in_y_origin + dilation_height_factor * filter_y;
// If the location is outside the bounds of the input image,
@ -111,15 +149,15 @@ inline void DepthwiseConv(
input_data[Offset(input_shape, b, in_y, in_x, ic)];
int32 filter_val = filter_data[Offset(
filter_shape, 0, filter_y, filter_x, oc)];
acc +=
(filter_val + filter_offset) * (input_val + input_offset);
acc += (filter_val + filter_offset) *
(input_val + input_offset);
}
}
}
if (bias_data) {
acc += bias_data[oc];
}
acc = MultiplyByQuantizedMultiplier(acc, output_multiplier,
acc = DepthwiseConvRound<output_rounding>(acc, output_multiplier,
output_shift);
acc += output_offset;
acc = std::max(acc, output_activation_min);
@ -131,9 +169,24 @@ inline void DepthwiseConv(
}
}
}
}
};
inline void DepthwiseConv(
const DepthwiseParams& params, const RuntimeShape& input_shape,
const uint8* input_data, const RuntimeShape& filter_shape,
const uint8* filter_data, const RuntimeShape& bias_shape,
const int32* bias_data, const RuntimeShape& output_shape,
uint8* output_data) {
return DepthwiseConvBasicKernel<
DepthwiseConvOutputRounding::kAwayFromZero>::Run(params, input_shape,
input_data, filter_shape,
filter_data, bias_shape,
bias_data, output_shape,
output_data);
}
} // end namespace reference_ops
} // namespace reference_ops
} // end namespace tflite
#endif // TENSORFLOW_LITE_KERNELS_INTERNAL_REFERENCE_DEPTHWISECONV_UINT8_H_