Add Q16x8 Depthwise Conv Support (#2140) Adds support for 16-bit activations + 8-bit weights for depthwise convolution in the reference kernel. Uses 64-bit bias to match TFLite. Also adds passthrough to the q16x8 reference kernel for Xtensa, CEVA, and ARC (CMSIS already has it's own implementation). Tested: depthwise_conv_test BUG=2141
diff --git a/tensorflow/lite/micro/kernels/arc_mli/depthwise_conv.cc b/tensorflow/lite/micro/kernels/arc_mli/depthwise_conv.cc index 1fa1d19..c2c9cd5 100644 --- a/tensorflow/lite/micro/kernels/arc_mli/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/arc_mli/depthwise_conv.cc
@@ -660,6 +660,30 @@ bias, output); } break; + case kTfLiteInt16: { + switch (filter->type) { + case kTfLiteInt8: { + reference_integer_ops::DepthwiseConvPerChannel( + DepthwiseConvParamsQuantized(params, data), + data.per_channel_output_multiplier, data.per_channel_output_shift, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData<int16_t>(input), + tflite::micro::GetTensorShape(filter), + tflite::micro::GetTensorData<int8_t>(filter), + tflite::micro::GetTensorShape(bias), + tflite::micro::GetOptionalTensorData<int64_t>(bias), + tflite::micro::GetTensorShape(output), + tflite::micro::GetTensorData<int16_t>(output)); + break; + } + default: + MicroPrintf("Filter type %s (%d) for input type %s not supported.", + TfLiteTypeGetName(filter->type), filter->type, + TfLiteTypeGetName(input->type)); + return kTfLiteError; + } + break; + } default: MicroPrintf("Type %s (%d) not supported.", TfLiteTypeGetName(input->type), input->type);
diff --git a/tensorflow/lite/micro/kernels/ceva/depthwise_conv.cc b/tensorflow/lite/micro/kernels/ceva/depthwise_conv.cc index 533014f..eed5d9e 100644 --- a/tensorflow/lite/micro/kernels/ceva/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/ceva/depthwise_conv.cc
@@ -224,6 +224,30 @@ EvalQuantizedPerChannel(context, node, params, data, input, filter, bias, output); break; + case kTfLiteInt16: { + switch (filter->type) { + case kTfLiteInt8: { + reference_integer_ops::DepthwiseConvPerChannel( + DepthwiseConvParamsQuantized(*params, data), + data.per_channel_output_multiplier, data.per_channel_output_shift, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData<int16_t>(input), + tflite::micro::GetTensorShape(filter), + tflite::micro::GetTensorData<int8_t>(filter), + tflite::micro::GetTensorShape(bias), + tflite::micro::GetOptionalTensorData<int64_t>(bias), + tflite::micro::GetTensorShape(output), + tflite::micro::GetTensorData<int16_t>(output)); + break; + } + default: + MicroPrintf("Filter type %s (%d) for input type %s not supported.", + TfLiteTypeGetName(filter->type), filter->type, + TfLiteTypeGetName(input->type)); + return kTfLiteError; + } + break; + } default: MicroPrintf("Type %s (%d) not supported.", TfLiteTypeGetName(input->type), input->type);
diff --git a/tensorflow/lite/micro/kernels/depthwise_conv.cc b/tensorflow/lite/micro/kernels/depthwise_conv.cc index 9290c2d..398f8cd 100644 --- a/tensorflow/lite/micro/kernels/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/depthwise_conv.cc
@@ -101,8 +101,33 @@ break; } default: - MicroPrintf("Filter type %s (%d) not supported.", - TfLiteTypeGetName(filter->type), filter->type); + MicroPrintf("Filter type %s (%d) for input type %s not supported.", + TfLiteTypeGetName(filter->type), filter->type, + TfLiteTypeGetName(input->type)); + return kTfLiteError; + } + break; + } + case kTfLiteInt16: { + switch (filter->type) { + case kTfLiteInt8: { + reference_integer_ops::DepthwiseConvPerChannel( + DepthwiseConvParamsQuantized(params, data), + data.per_channel_output_multiplier, data.per_channel_output_shift, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData<int16_t>(input), + tflite::micro::GetTensorShape(filter), + tflite::micro::GetTensorData<int8_t>(filter), + tflite::micro::GetTensorShape(bias), + tflite::micro::GetOptionalTensorData<int64_t>(bias), + tflite::micro::GetTensorShape(output), + tflite::micro::GetTensorData<int16_t>(output)); + break; + } + default: + MicroPrintf("Filter type %s (%d) for input type %s not supported.", + TfLiteTypeGetName(filter->type), filter->type, + TfLiteTypeGetName(input->type)); return kTfLiteError; } break;
diff --git a/tensorflow/lite/micro/kernels/depthwise_conv_common.cc b/tensorflow/lite/micro/kernels/depthwise_conv_common.cc index 6d5f6c2..52804de 100644 --- a/tensorflow/lite/micro/kernels/depthwise_conv_common.cc +++ b/tensorflow/lite/micro/kernels/depthwise_conv_common.cc
@@ -192,7 +192,8 @@ context, input->type == filter->type || (input->type == kTfLiteInt8 && - (filter->type == kTfLiteInt4 || filter->type == kTfLiteInt8)), + (filter->type == kTfLiteInt4 || filter->type == kTfLiteInt8)) || + (input->type == kTfLiteInt16 && filter->type == kTfLiteInt8), "Hybrid models are not supported on TFLite Micro."); if (filter->type == kTfLiteInt4) {
diff --git a/tensorflow/lite/micro/kernels/depthwise_conv_test.cc b/tensorflow/lite/micro/kernels/depthwise_conv_test.cc index c3b916c..b50b40a 100644 --- a/tensorflow/lite/micro/kernels/depthwise_conv_test.cc +++ b/tensorflow/lite/micro/kernels/depthwise_conv_test.cc
@@ -78,15 +78,15 @@ return kTfLiteOk; } +template <typename T, typename BiasT> void TestDepthwiseConvQuantizedPerChannel( - int* input_dims_data, const float* input_data, int8_t* input_quantized, + int* input_dims_data, const float* input_data, T* input_quantized, float input_scale, int input_zero_point, int* filter_dims_data, const float* filter_data, int8_t* filter_data_quantized, - int* bias_dims_data, const float* bias_data, int32_t* bias_data_quantized, + int* bias_dims_data, const float* bias_data, BiasT* bias_data_quantized, int* output_dims_data, const float* expected_output_data, - int8_t* expected_output_data_quantized, int8_t* output_data, - float output_scale, int output_zero_point, - TfLiteDepthwiseConvParams* conv_params, + T* expected_output_data_quantized, T* output_data, float output_scale, + int output_zero_point, TfLiteDepthwiseConvParams* conv_params, TfLiteType filter_packed_type = kTfLiteNoType) { TfLiteIntArray* input_dims = IntArrayFromInts(input_dims_data); TfLiteIntArray* filter_dims = IntArrayFromInts(filter_dims_data); @@ -147,6 +147,42 @@ 1.0, tensors_size, tensors)); } +void TestDepthwiseConvQuantizedPerChannel( + int* input_dims_data, const float* input_data, int8_t* input_quantized, + float input_scale, int input_zero_point, int* filter_dims_data, + const float* filter_data, int8_t* filter_data_quantized, + int* bias_dims_data, const float* bias_data, int32_t* bias_data_quantized, + int* output_dims_data, const float* expected_output_data, + int8_t* expected_output_data_quantized, int8_t* output_data, + float output_scale, int output_zero_point, + TfLiteDepthwiseConvParams* conv_params, + TfLiteType filter_packed_type = kTfLiteNoType) { + return TestDepthwiseConvQuantizedPerChannel<int8_t, int32_t>( + input_dims_data, input_data, input_quantized, input_scale, + input_zero_point, filter_dims_data, filter_data, filter_data_quantized, + bias_dims_data, bias_data, bias_data_quantized, output_dims_data, + expected_output_data, expected_output_data_quantized, output_data, + output_scale, output_zero_point, conv_params, filter_packed_type); +} + +void TestDepthwiseConvQuantizedPerChannel( + int* input_dims_data, const float* input_data, int16_t* input_quantized, + float input_scale, int input_zero_point, int* filter_dims_data, + const float* filter_data, int8_t* filter_data_quantized, + int* bias_dims_data, const float* bias_data, int64_t* bias_data_quantized, + int* output_dims_data, const float* expected_output_data, + int16_t* expected_output_data_quantized, int16_t* output_data, + float output_scale, int output_zero_point, + TfLiteDepthwiseConvParams* conv_params, + TfLiteType filter_packed_type = kTfLiteNoType) { + return TestDepthwiseConvQuantizedPerChannel<int16_t, int64_t>( + input_dims_data, input_data, input_quantized, input_scale, + input_zero_point, filter_dims_data, filter_data, filter_data_quantized, + bias_dims_data, bias_data, bias_data_quantized, output_dims_data, + expected_output_data, expected_output_data_quantized, output_data, + output_scale, output_zero_point, conv_params, filter_packed_type); +} + // Xtensa kernels do not support float activations., and the corresponding tests // are disabled. As a result, helper functions that are only needed for float // kernel tests also need to be ifdef'd out to avoid build errors due to unused @@ -989,4 +1025,47 @@ output_scale, output_zero_point, &conv_params); } +TF_LITE_MICRO_TEST(SimpleTestQuantizedPerChannelInt16InputInt8Filter) { + const int input_elements = 12; + int input_shape[] = {4, 1, 3, 2, 2}; + const float input_values[] = {-547, 108, -682, 540, -161, -539, 9, -482, + -859, 84, 153, -726, 523, 702, -172, -936}; + const int filter_elements = 16; + int filter_shape[] = {4, 1, 2, 2, 4}; + const float filter_values[] = {1, 2, 3, 4, -9, 10, -11, 12, + 5, 6, 7, 8, 13, -14, 15, -16}; + const int bias_elements = 4; + int bias_shape[] = {4, 1, 1, 1, 4}; + const int output_elements = 8; + const float bias_values[] = {1, 2, 3, 4}; + const float golden[] = { + 4894, -9009, -16596, 10268, -2564, -7483, -6599, 4356, + }; + int output_shape[] = {4, 1, 2, 1, 4}; + const int output_dims_count = 8; + int16_t output_data[output_dims_count]; + + const float input_scale = 0.5; + const float output_scale = 1.0f; + const int input_zero_point = 0; + const int output_zero_point = 0; + + int16_t input_quantized[input_elements]; + int8_t filter_quantized[filter_elements]; + int64_t bias_quantized[bias_elements]; + int16_t golden_quantized[output_elements]; + + TfLiteDepthwiseConvParams conv_params; + conv_params.activation = kTfLiteActNone; + conv_params.dilation_width_factor = 1; + conv_params.dilation_height_factor = 1; + conv_params.stride_height = 1; + conv_params.stride_width = 1; + + tflite::testing::TestDepthwiseConvQuantizedPerChannel( + input_shape, input_values, input_quantized, input_scale, input_zero_point, + filter_shape, filter_values, filter_quantized, bias_shape, bias_values, + bias_quantized, output_shape, golden, golden_quantized, output_data, + output_scale, output_zero_point, &conv_params); +} TF_LITE_MICRO_TESTS_END
diff --git a/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc b/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc index 02ea871..f3acb8d 100644 --- a/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/xtensa/depthwise_conv.cc
@@ -48,6 +48,18 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_OK(context, DepthwiseConvPrepare(context, node)); + MicroContext* micro_context = GetMicroContext(context); + TfLiteTensor* input = + micro_context->AllocateTempInputTensor(node, kConvInputTensor); + TF_LITE_ENSURE(context, input != nullptr); + + // For int16 input, only fallback to the reference kernel is used + // so there is no need to prepare the Hifi/Vision kernel. + if (input->type == kTfLiteInt16) { + micro_context->DeallocateTempTfLiteTensor(input); + return kTfLiteOk; + } + micro_context->DeallocateTempTfLiteTensor(input); #if defined(HIFI4) || defined(HIFI5) TF_LITE_ENSURE_OK(context, DepthwiseConvPrepareHifi(context, node)); @@ -114,6 +126,31 @@ } break; } + case kTfLiteInt16: { + switch (filter->type) { + case kTfLiteInt8: { + reference_integer_ops::DepthwiseConvPerChannel( + DepthwiseConvParamsQuantized(params, op_data.reference_op_data), + op_data.reference_op_data.per_channel_output_multiplier, + op_data.reference_op_data.per_channel_output_shift, + tflite::micro::GetTensorShape(input), + tflite::micro::GetTensorData<int16_t>(input), + tflite::micro::GetTensorShape(filter), + tflite::micro::GetTensorData<int8_t>(&filter_int8), + tflite::micro::GetTensorShape(bias), + tflite::micro::GetOptionalTensorData<int64_t>(bias), + tflite::micro::GetTensorShape(output), + tflite::micro::GetTensorData<int16_t>(output)); + break; + } + default: + MicroPrintf("Filter type %s (%d) for input type %s not supported.", + TfLiteTypeGetName(filter->type), filter->type, + TfLiteTypeGetName(input->type)); + return kTfLiteError; + } + break; + } default: MicroPrintf("Type %s (%d) not supported.", TfLiteTypeGetName(input->type), input->type);