cmsis-nn: refactor conv to get rid of duplicate code (#2402)

BUG=Some duplicate code in cmsis-nn/conv.cc
diff --git a/tensorflow/lite/micro/kernels/cmsis_nn/conv.cc b/tensorflow/lite/micro/kernels/cmsis_nn/conv.cc
index d3d1552..6628168 100644
--- a/tensorflow/lite/micro/kernels/cmsis_nn/conv.cc
+++ b/tensorflow/lite/micro/kernels/cmsis_nn/conv.cc
@@ -1,4 +1,4 @@
-/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
+/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -159,104 +159,52 @@
   return kTfLiteOk;
 }
 
-TfLiteStatus EvalQuantizedPerChannelInt4(
-    TfLiteContext* context, TfLiteNode* node, const TfLiteConvParams& params,
-    const OpData& data, const TfLiteEvalTensor* input,
-    const TfLiteEvalTensor* filter, const TfLiteEvalTensor* bias,
-    TfLiteEvalTensor* output) {
-  cmsis_nn_conv_params conv_params;
-  conv_params.dilation.h = params.dilation_height_factor;
-  conv_params.dilation.w = params.dilation_width_factor;
-
-  // Initialize cmsis_nn convolution parameters
-  conv_params.input_offset = -data.reference_op_data.input_zero_point;
-  conv_params.output_offset = data.reference_op_data.output_zero_point;
-  conv_params.stride.h = params.stride_height;
-  conv_params.stride.w = params.stride_width;
-  conv_params.padding.h = data.reference_op_data.padding.height;
-  conv_params.padding.w = data.reference_op_data.padding.width;
-  conv_params.activation.min = data.reference_op_data.output_activation_min;
-  conv_params.activation.max = data.reference_op_data.output_activation_max;
-
-  // Initialize cmsis_nn per channel quantization parameters
-  cmsis_nn_per_channel_quant_params quant_params;
-  quant_params.multiplier = const_cast<int32_t*>(
-      data.reference_op_data.per_channel_output_multiplier);
-  quant_params.shift =
-      const_cast<int32_t*>(data.reference_op_data.per_channel_output_shift);
-
-  RuntimeShape filter_shape = tflite::micro::GetTensorShape(filter);
-  RuntimeShape input_shape = tflite::micro::GetTensorShape(input);
-  RuntimeShape output_shape = tflite::micro::GetTensorShape(output);
-  RuntimeShape bias_shape = tflite::micro::GetTensorShape(bias);
-
-  // Consistency check.
-  TFLITE_DCHECK_LE(conv_params.activation.min, conv_params.activation.max);
-  TFLITE_DCHECK_EQ(input_shape.DimensionsCount(), 4);
-  TFLITE_DCHECK_EQ(filter_shape.DimensionsCount(), 4);
-  TFLITE_DCHECK_EQ(output_shape.DimensionsCount(), 4);
-  const int batch_size = MatchingDim(input_shape, 0, output_shape, 0);
-  const int input_depth = MatchingDim(input_shape, 3, filter_shape, 3);
-  const int output_depth = MatchingDim(filter_shape, 0, output_shape, 3);
-  if (tflite::micro::GetOptionalTensorData<int32_t>(bias)) {
-    TFLITE_DCHECK_EQ(bias_shape.FlatSize(), output_depth);
-  }
-
-  // Initialize cmsis_nn dimensions
-  // Input
-  cmsis_nn_dims input_dims;
-  input_dims.n = batch_size;
-  input_dims.h = input_shape.Dims(1);
-  input_dims.w = input_shape.Dims(2);
-  input_dims.c = input_depth;
-
-  // Filter
-  cmsis_nn_dims filter_dims;
-  filter_dims.n = output_depth;
-  filter_dims.h = filter_shape.Dims(1);
-  filter_dims.w = filter_shape.Dims(2);
-  filter_dims.c = input_depth;
-
-  // Bias
-  cmsis_nn_dims bias_dims;
-  bias_dims.n = 1;
-  bias_dims.h = 1;
-  bias_dims.w = 1;
-  bias_dims.c = output_depth;
-
-  // Output
-  cmsis_nn_dims output_dims;
-  output_dims.n = batch_size;
-  output_dims.h = output_shape.Dims(1);
-  output_dims.w = output_shape.Dims(2);
-  output_dims.c = output_depth;
-
-  // Initialize cmsis_nn context
-  cmsis_nn_context ctx;
-  ctx.buf = nullptr;
-  ctx.size = 0;
-
-  if (data.buffer_idx > -1) {
-    ctx.buf = context->GetScratchBuffer(context, data.buffer_idx);
-    // Note: ctx.size is currently not used in cmsis_nn.
-    // The buffer should be allocated in the Prepare function through
-    // arm_convolve_wrapper_s8_get_buffer_size
-  }
-
-  // arm_convolve_wrapper_s4 dispatches the optimized kernel accordingly with
-  // the parameters passed for convolutions with 4 bit weights
-  TFLITE_DCHECK_EQ(
-      arm_convolve_wrapper_s4(
-          &ctx, &conv_params, &quant_params, &input_dims,
-          tflite::micro::GetTensorData<int8_t>(input), &filter_dims,
-          tflite::micro::GetTensorData<int8_t>(filter), &bias_dims,
-          tflite::micro::GetOptionalTensorData<int32_t>(bias), &output_dims,
-          tflite::micro::GetTensorData<int8_t>(output)),
-      ARM_CMSIS_NN_SUCCESS);
-
-  return kTfLiteOk;
+template <class ActType, class BiasType, class WeigthsType>
+arm_cmsis_nn_status convolve_wrapper(
+    const cmsis_nn_context* ctx, const cmsis_nn_conv_params* conv_params,
+    const cmsis_nn_per_channel_quant_params* quant_params,
+    const cmsis_nn_dims* input_dims, const ActType* input,
+    const cmsis_nn_dims* filter_dims, const int8_t* filter,
+    const cmsis_nn_dims* bias_dims, const BiasType* bias,
+    const cmsis_nn_dims* output_dims, ActType* output, WeigthsType weightsT) {
+  return ARM_CMSIS_NN_ARG_ERROR;
 }
 
+template <>
+arm_cmsis_nn_status convolve_wrapper(
+    const cmsis_nn_context* ctx, const cmsis_nn_conv_params* conv_params,
+    const cmsis_nn_per_channel_quant_params* quant_params,
+    const cmsis_nn_dims* input_dims, const int8_t* input,
+    const cmsis_nn_dims* filter_dims, const int8_t* filter,
+    const cmsis_nn_dims* bias_dims, const int32_t* bias,
+    const cmsis_nn_dims* output_dims, int8_t* output, TfLiteType weightsT) {
+  if (weightsT == kTfLiteInt8) {
+    return arm_convolve_wrapper_s8(ctx, conv_params, quant_params, input_dims,
+                                   input, filter_dims, filter, bias_dims, bias,
+                                   output_dims, output);
+  } else if (weightsT == kTfLiteInt4) {
+    return arm_convolve_wrapper_s4(ctx, conv_params, quant_params, input_dims,
+                                   input, filter_dims, filter, bias_dims, bias,
+                                   output_dims, output);
+  } else {
+    return ARM_CMSIS_NN_ARG_ERROR;
+  }
+}
+
+template <>
+arm_cmsis_nn_status convolve_wrapper(
+    const cmsis_nn_context* ctx, const cmsis_nn_conv_params* conv_params,
+    const cmsis_nn_per_channel_quant_params* quant_params,
+    const cmsis_nn_dims* input_dims, const int16_t* input,
+    const cmsis_nn_dims* filter_dims, const int8_t* filter,
+    const cmsis_nn_dims* bias_dims, const int64_t* bias,
+    const cmsis_nn_dims* output_dims, int16_t* output, TfLiteType weightsT) {
+  return arm_convolve_wrapper_s16(ctx, conv_params, quant_params, input_dims,
+                                  input, filter_dims, filter, bias_dims, bias,
+                                  output_dims, output);
+}
+
+template <typename ActType, typename BiasType, TfLiteType type>
 TfLiteStatus EvalQuantizedPerChannel(TfLiteContext* context, TfLiteNode* node,
                                      const TfLiteConvParams& params,
                                      const OpData& data,
@@ -298,7 +246,7 @@
   const int batch_size = MatchingDim(input_shape, 0, output_shape, 0);
   const int input_depth = MatchingDim(input_shape, 3, filter_shape, 3);
   const int output_depth = MatchingDim(filter_shape, 0, output_shape, 3);
-  if (tflite::micro::GetOptionalTensorData<int32_t>(bias)) {
+  if (tflite::micro::GetOptionalTensorData<BiasType>(bias)) {
     TFLITE_DCHECK_EQ(bias_shape.FlatSize(), output_depth);
   }
 
@@ -339,115 +287,19 @@
   if (data.buffer_idx > -1) {
     ctx.buf = context->GetScratchBuffer(context, data.buffer_idx);
     // Note: ctx.size is currently not used in cmsis_nn.
-    // The buffer should be allocated in the Prepare function through
-    // arm_convolve_wrapper_s8_get_buffer_size
+    // The buffer should be allocated in the prepare function through
+    // the corresponding arm_convolve_wrapper_[type]_get_buffer_size
   }
 
-  // arm_convolve_wrapper_s8 dispatches the optimized kernel accordingly with
-  // the parameters passed
+  // arm_convolve_wrapper_[type] dispatches the optimized kernel accordingly
+  // with the parameters passed
   TFLITE_DCHECK_EQ(
-      arm_convolve_wrapper_s8(
+      convolve_wrapper(
           &ctx, &conv_params, &quant_params, &input_dims,
-          tflite::micro::GetTensorData<int8_t>(input), &filter_dims,
+          tflite::micro::GetTensorData<ActType>(input), &filter_dims,
           tflite::micro::GetTensorData<int8_t>(filter), &bias_dims,
-          tflite::micro::GetOptionalTensorData<int32_t>(bias), &output_dims,
-          tflite::micro::GetTensorData<int8_t>(output)),
-      ARM_CMSIS_NN_SUCCESS);
-
-  return kTfLiteOk;
-}
-
-TfLiteStatus EvalQuantizedPerChannel16x8(
-    TfLiteContext* context, TfLiteNode* node, const TfLiteConvParams& params,
-    const OpData& data, const TfLiteEvalTensor* input,
-    const TfLiteEvalTensor* filter, const TfLiteEvalTensor* bias,
-    TfLiteEvalTensor* output) {
-  cmsis_nn_conv_params conv_params;
-  conv_params.dilation.h = params.dilation_height_factor;
-  conv_params.dilation.w = params.dilation_width_factor;
-
-  // Initialize cmsis_nn convolution parameters
-  conv_params.input_offset = -data.reference_op_data.input_zero_point;
-  conv_params.output_offset = data.reference_op_data.output_zero_point;
-  conv_params.stride.h = params.stride_height;
-  conv_params.stride.w = params.stride_width;
-  conv_params.padding.h = data.reference_op_data.padding.height;
-  conv_params.padding.w = data.reference_op_data.padding.width;
-  conv_params.activation.min = data.reference_op_data.output_activation_min;
-  conv_params.activation.max = data.reference_op_data.output_activation_max;
-
-  // Initialize cmsis_nn per channel quantization parameters
-  cmsis_nn_per_channel_quant_params quant_params;
-  quant_params.multiplier = const_cast<int32_t*>(
-      data.reference_op_data.per_channel_output_multiplier);
-  quant_params.shift =
-      const_cast<int32_t*>(data.reference_op_data.per_channel_output_shift);
-
-  RuntimeShape filter_shape = tflite::micro::GetTensorShape(filter);
-  RuntimeShape input_shape = tflite::micro::GetTensorShape(input);
-  RuntimeShape output_shape = tflite::micro::GetTensorShape(output);
-  RuntimeShape bias_shape = tflite::micro::GetTensorShape(bias);
-
-  // Consistency check.
-  TFLITE_DCHECK_LE(conv_params.activation.min, conv_params.activation.max);
-  TFLITE_DCHECK_EQ(input_shape.DimensionsCount(), 4);
-  TFLITE_DCHECK_EQ(filter_shape.DimensionsCount(), 4);
-  TFLITE_DCHECK_EQ(output_shape.DimensionsCount(), 4);
-  const int batch_size = MatchingDim(input_shape, 0, output_shape, 0);
-  const int input_depth = MatchingDim(input_shape, 3, filter_shape, 3);
-  const int output_depth = MatchingDim(filter_shape, 0, output_shape, 3);
-  if (tflite::micro::GetOptionalTensorData<int64_t>(bias)) {
-    TFLITE_DCHECK_EQ(bias_shape.FlatSize(), output_depth);
-  }
-
-  // Initialize cmsis_nn dimensions
-  // Input
-  cmsis_nn_dims input_dims;
-  input_dims.n = batch_size;
-  input_dims.h = input_shape.Dims(1);
-  input_dims.w = input_shape.Dims(2);
-  input_dims.c = input_depth;
-
-  // Filter
-  cmsis_nn_dims filter_dims;
-  filter_dims.n = output_depth;
-  filter_dims.h = filter_shape.Dims(1);
-  filter_dims.w = filter_shape.Dims(2);
-  filter_dims.c = input_depth;
-
-  // Bias
-  cmsis_nn_dims bias_dims;
-  bias_dims.n = 1;
-  bias_dims.h = 1;
-  bias_dims.w = 1;
-  bias_dims.c = output_depth;
-
-  // Output
-  cmsis_nn_dims output_dims;
-  output_dims.n = batch_size;
-  output_dims.h = output_shape.Dims(1);
-  output_dims.w = output_shape.Dims(2);
-  output_dims.c = output_depth;
-
-  // Initialize cmsis_nn context
-  cmsis_nn_context ctx;
-  ctx.buf = nullptr;
-  ctx.size = 0;
-
-  if (data.buffer_idx > -1) {
-    ctx.buf = context->GetScratchBuffer(context, data.buffer_idx);
-    // Note: ctx.size is currently not used in cmsis_nn.
-    // The buffer should be allocated in the Prepare function through
-    // arm_convolve_wrapper_s8_get_buffer_size
-  }
-
-  TFLITE_DCHECK_EQ(
-      arm_convolve_wrapper_s16(
-          &ctx, &conv_params, &quant_params, &input_dims,
-          tflite::micro::GetTensorData<int16_t>(input), &filter_dims,
-          tflite::micro::GetTensorData<int8_t>(filter), &bias_dims,
-          tflite::micro::GetOptionalTensorData<int64_t>(bias), &output_dims,
-          tflite::micro::GetTensorData<int16_t>(output)),
+          tflite::micro::GetOptionalTensorData<BiasType>(bias), &output_dims,
+          tflite::micro::GetTensorData<ActType>(output), type),
       ARM_CMSIS_NN_SUCCESS);
 
   return kTfLiteOk;
@@ -471,8 +323,8 @@
   TFLITE_DCHECK(node->user_data != nullptr);
   const OpData& data = *(static_cast<const OpData*>(node->user_data));
 
-  return EvalQuantizedPerChannelInt4(context, node, params, data, input, filter,
-                                     bias, output);
+  return EvalQuantizedPerChannel<int8_t, int32_t, kTfLiteInt4>(
+      context, node, params, data, input, filter, bias, output);
 }
 
 TfLiteStatus EvalInt8(TfLiteContext* context, TfLiteNode* node) {
@@ -493,8 +345,8 @@
   TFLITE_DCHECK(node->user_data != nullptr);
   const OpData& data = *(static_cast<const OpData*>(node->user_data));
 
-  return EvalQuantizedPerChannel(context, node, params, data, input, filter,
-                                 bias, output);
+  return EvalQuantizedPerChannel<int8_t, int32_t, kTfLiteInt8>(
+      context, node, params, data, input, filter, bias, output);
 }
 
 TfLiteStatus EvalInt16x8(TfLiteContext* context, TfLiteNode* node) {
@@ -516,8 +368,8 @@
   const OpData& data = *(static_cast<const OpData*>(node->user_data));
 
   if (bias == nullptr || bias->type == kTfLiteInt64) {
-    return EvalQuantizedPerChannel16x8(context, node, params, data, input,
-                                       filter, bias, output);
+    return EvalQuantizedPerChannel<int16_t, int64_t, kTfLiteInt16>(
+        context, node, params, data, input, filter, bias, output);
   } else {
     reference_integer_ops::ConvPerChannel(
         ConvParamsQuantized(params, data.reference_op_data),
@@ -580,12 +432,12 @@
     case kTfLiteInt8: {
       switch (filter->type) {
         case kTfLiteInt4: {
-          return EvalQuantizedPerChannelInt4(context, node, params, data, input,
-                                             filter, bias, output);
+          return EvalQuantizedPerChannel<int8_t, int32_t, kTfLiteInt4>(
+              context, node, params, data, input, filter, bias, output);
         }
         case kTfLiteInt8: {
-          return EvalQuantizedPerChannel(context, node, params, data, input,
-                                         filter, bias, output);
+          return EvalQuantizedPerChannel<int8_t, int32_t, kTfLiteInt8>(
+              context, node, params, data, input, filter, bias, output);
         }
         default: {
           MicroPrintf("Filter type %s (%d) not supported.",
@@ -597,8 +449,8 @@
     }
     case kTfLiteInt16: {
       if (bias == nullptr || bias->type == kTfLiteInt64) {
-        return EvalQuantizedPerChannel16x8(context, node, params, data, input,
-                                           filter, bias, output);
+        return EvalQuantizedPerChannel<int16_t, int64_t, kTfLiteInt16>(
+            context, node, params, data, input, filter, bias, output);
       } else if (bias->type == kTfLiteInt32) {
         reference_integer_ops::ConvPerChannel(
             ConvParamsQuantized(params, data.reference_op_data),