This is an automated email from the ASF dual-hosted git repository.

lukhut pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new f88a10fb00 [TFLite] Add support to int16 data type in TFLite frontend 
(#10915)
f88a10fb00 is described below

commit f88a10fb00419c51a116a63f931a98d8286b23de
Author: Leandro Nunes <[email protected]>
AuthorDate: Wed May 18 14:04:24 2022 +0100

    [TFLite] Add support to int16 data type in TFLite frontend (#10915)
    
    * [TFLite] Add support to int16 data type in TFLite frontend
    
    Add support for int16 data type and int64 biases/accumulators in
    the TFLite frontend.
    
    Adjusts TFLite tests to cover int16 convolutions and element-wise;
    Fixes a minor typo negtive->negative in the element-wise tests.
    
    * Update src/relay/qnn/op/convolution.cc
    
    Co-authored-by: Elen Kalda <[email protected]>
    
    Co-authored-by: Elen Kalda <[email protected]>
---
 python/tvm/relay/frontend/tflite.py          |  11 +-
 src/relay/qnn/op/convolution.cc              |  48 +++--
 src/relay/qnn/op/dequantize.cc               |   4 +-
 src/relay/qnn/op/quantize.cc                 |   4 +-
 src/relay/qnn/op/requantize.cc               |   8 +-
 tests/python/frontend/tflite/test_forward.py | 257 ++++++++++++++++++++-------
 6 files changed, 235 insertions(+), 97 deletions(-)

diff --git a/python/tvm/relay/frontend/tflite.py 
b/python/tvm/relay/frontend/tflite.py
index 8d18cc2962..b696bd6d05 100644
--- a/python/tvm/relay/frontend/tflite.py
+++ b/python/tvm/relay/frontend/tflite.py
@@ -390,6 +390,7 @@ class OperatorConverter(object):
             return {
                 TensorType.UINT8: np.uint8,
                 TensorType.INT8: np.int8,
+                TensorType.INT16: np.int16,
                 TensorType.FLOAT16: np.float16,
                 TensorType.FLOAT32: np.float32,
                 TensorType.INT32: np.int32,
@@ -430,6 +431,8 @@ class OperatorConverter(object):
 
         if tensor_type == TensorType.INT8:
             return "int8"
+        if tensor_type == TensorType.INT16:
+            return "int16"
         if tensor_type == TensorType.UINT8:
             return "uint8"
         if tensor_type == TensorType.FLOAT16:
@@ -2149,7 +2152,9 @@ class OperatorConverter(object):
             qnn_conv2d_params = dict(params)
             qnn_conv2d_params["input_zero_point"] = 
input_tensor.qnn_params["zero_point"]
             qnn_conv2d_params["kernel_zero_point"] = 
weight_tensor.qnn_params["zero_point"]
-            qnn_conv2d_params["out_dtype"] = "int32"
+            qnn_conv2d_params["out_dtype"] = (
+                "int64" if output_tensor_type_str == "int16" else "int32"
+            )
             qnn_conv2d_params["input_scale"] = input_tensor.qnn_params["scale"]
             qnn_conv2d_params["kernel_scale"] = 
weight_tensor.qnn_params["scale"]
             out = _qnn.op.conv2d(in_expr, weight_expr, **qnn_conv2d_params)
@@ -2160,8 +2165,8 @@ class OperatorConverter(object):
         if len(input_tensors) == 3:
             bias_tensor = input_tensors[2]
             bias_tensor_type = bias_tensor.tensor.Type()
-            # bias tensor type should be INT32 (quantization) or FLOAT32
-            assert bias_tensor_type in (TensorType.INT32, TensorType.FLOAT32)
+            # bias tensor type should be INT32 (int8 qnn) or INT64 (int16 qnn) 
or FLOAT32
+            assert bias_tensor_type in (TensorType.INT32, TensorType.INT64, 
TensorType.FLOAT32)
             bias_tensor_type_str = self.get_tensor_type_str(bias_tensor_type)
             if self.has_expr(bias_tensor.tensor_idx):
                 bias_expr = self.get_expr(bias_tensor.tensor_idx)
diff --git a/src/relay/qnn/op/convolution.cc b/src/relay/qnn/op/convolution.cc
index 8a7521e8ee..42e4540f0f 100644
--- a/src/relay/qnn/op/convolution.cc
+++ b/src/relay/qnn/op/convolution.cc
@@ -50,12 +50,14 @@ bool QnnConv2DRel(const Array<Type>& types, int num_inputs, 
const Attrs& attrs,
   if (data == nullptr || weight == nullptr) return false;
   const auto* param = attrs.as<Conv2DAttrs>();
   ICHECK(param != nullptr) << "Conv2DAttrs cannot be nullptr.";
-  ICHECK(data->dtype == DataType::Int(8) || data->dtype == DataType::UInt(8))
-      << "Expected qnn conv2d type(int8, uint8) for input but was " << 
data->dtype;
+  ICHECK(data->dtype == DataType::Int(8) || data->dtype == DataType::UInt(8) ||
+         data->dtype == DataType::Int(16))
+      << "Expected qnn conv2d type(int8, uint8, int16) for input but was " << 
data->dtype;
   ICHECK(weight->dtype == DataType::Int(8) || weight->dtype == 
DataType::UInt(8))
       << "Expected qnn conv2d type(int8, uint8) for weight but was " << 
weight->dtype;
-  ICHECK(param->out_dtype == DataType::Int(16) || param->out_dtype == 
DataType::Int(32))
-      << "Expected qnn conv2d type(int32, int16) for output but was " << 
param->out_dtype;
+  ICHECK(param->out_dtype == DataType::Int(16) || param->out_dtype == 
DataType::Int(32) ||
+         param->out_dtype == DataType::Int(64))
+      << "Expected qnn conv2d type(int16, int32, int64) for output but was " 
<< param->out_dtype;
   ICHECK(param->out_dtype.bits() > 0) << "Output dtype bits should be greater 
than 0.";
 
   // Check the types of scale and zero points.
@@ -190,19 +192,21 @@ WorkloadType GetWorkload(const Array<tvm::relay::Type>& 
arg_types, const Conv2DA
  */
 Expr Conv2DFallBack(const Expr& data, const Expr& weight, const Expr& 
input_zero_point,
                     const Expr& kernel_zero_point, const Conv2DAttrs* param) {
-  // Upcast the zero point to Int16.
-  auto zp_data = Cast(input_zero_point, DataType::Int(16));
-  auto zp_kernel = Cast(kernel_zero_point, DataType::Int(16));
+  // Upcast the parameters to be at least int32 to avoid overflow
+  auto upcast_bits = param->out_dtype.bits() < 32 ? 32 : 
param->out_dtype.bits();
 
-  auto shifted_data = Cast(data, DataType::Int(16));
-  auto zero_scalar = MakeConstantScalar(DataType::Int(32), 0);
+  auto zp_data = Cast(input_zero_point, DataType::Int(upcast_bits));
+  auto zp_kernel = Cast(kernel_zero_point, DataType::Int(upcast_bits));
+
+  auto shifted_data = Cast(data, DataType::Int(upcast_bits));
+  auto zero_scalar = MakeConstantScalar(DataType::Int(upcast_bits), 0);
   if (!IsEqualScalar(input_zero_point, zero_scalar)) {
-    shifted_data = Subtract(Cast(data, DataType::Int(16)), zp_data);
+    shifted_data = Subtract(Cast(data, DataType::Int(upcast_bits)), zp_data);
   }
 
-  auto shifted_kernel = Cast(weight, DataType::Int(16));
+  auto shifted_kernel = Cast(weight, DataType::Int(upcast_bits));
   if (!IsEqualScalar(kernel_zero_point, zero_scalar)) {
-    shifted_kernel = Subtract(Cast(weight, DataType::Int(16)), zp_kernel);
+    shifted_kernel = Subtract(Cast(weight, DataType::Int(upcast_bits)), 
zp_kernel);
   }
 
   return Conv2D(shifted_data, shifted_kernel, param->strides, param->padding, 
param->dilation,
@@ -557,6 +561,7 @@ Expr Conv2DThirdTerm(const Expr& weight, const Expr& 
input_zero_point, const Con
  * \param in_channels The number of input channels.
  * \param kernel_h The height of kernel.
  * \param kernel_w The width of kernel.
+ * \param param The qnn conv2d attributes.
  * \return The sequence of Relay operators for term4.
  * \note The term4 looks like this
  *
@@ -564,10 +569,11 @@ Expr Conv2DThirdTerm(const Expr& weight, const Expr& 
input_zero_point, const Con
  *
  */
 Expr Conv2DFourthTerm(int input_zero_point_int, int kernel_zero_point_int, int 
in_channels,
-                      int kernel_h, int kernel_w) {
+                      int kernel_h, int kernel_w, const Conv2DAttrs* param) {
+  auto upcast_bits = param->out_dtype.bits() < 32 ? 32 : 
param->out_dtype.bits();
   int scalar_term4 =
       input_zero_point_int * kernel_zero_point_int * in_channels * kernel_h * 
kernel_w;
-  return MakeConstantScalar(DataType::Int(32), scalar_term4);
+  return MakeConstantScalar(DataType::Int(upcast_bits), scalar_term4);
 }
 
 /*
@@ -578,6 +584,7 @@ Expr Conv2DFourthTerm(int input_zero_point_int, int 
kernel_zero_point_int, int i
  * \param in_channels The number of input channels.
  * \param kernel_h The height of kernel.
  * \param kernel_w The width of kernel.
+ * \param param The qnn conv2d attributes.
  * \return The sequence of Relay operators for term4.
  * \note The term4 looks like this
  *
@@ -585,8 +592,10 @@ Expr Conv2DFourthTerm(int input_zero_point_int, int 
kernel_zero_point_int, int i
  *
  */
 Expr Conv2DFourthTerm(const Expr& input_zero_point, const Expr& 
kernel_zero_point, int in_channels,
-                      int kernel_h, int kernel_w) {
-  Expr scalar_term4 = MakeConstantScalar(DataType::Int(32), in_channels * 
kernel_h * kernel_w);
+                      int kernel_h, int kernel_w, const Conv2DAttrs* param) {
+  auto upcast_bits = param->out_dtype.bits() < 32 ? 32 : 
param->out_dtype.bits();
+  Expr scalar_term4 =
+      MakeConstantScalar(DataType::Int(upcast_bits), in_channels * kernel_h * 
kernel_w);
   Expr variable_term4 = Multiply(input_zero_point, kernel_zero_point);
   return Multiply(scalar_term4, variable_term4);
 }
@@ -791,10 +800,11 @@ Expr QnnConv2DCanonicalize(const Attrs& attrs, const 
Array<Expr>& new_args,
   auto term3 = Conv2DThirdTerm(weight, input_zero_point, param, out_channels);
   Expr term4;
   if (dynamic_zp) {
-    term4 = Conv2DFourthTerm(input_zero_point, kernel_zero_point, in_channels, 
kernel_h, kernel_w);
+    term4 = Conv2DFourthTerm(input_zero_point, kernel_zero_point, in_channels, 
kernel_h, kernel_w,
+                             param);
   } else {
     term4 = Conv2DFourthTerm(input_zero_point_int, kernel_zero_point_int, 
in_channels, kernel_h,
-                             kernel_w);
+                             kernel_w, param);
   }
   return Conv2DCombineTerms(term1, term2, term3, term4, input_zero_point_int,
                             kernel_zero_point_int);
@@ -829,7 +839,7 @@ This operator convolves quantized weight with quantized 
data. The scale of the
 output quantized tensor is the product of the weight_scale and input_scale of
 the input quantized tensors. The zero point of the output quantized tensor is
 0. By default, the dtype of output is int32. Please also refer to Requantize
-operator to understand how to scale back the int32 output to (u)int8.
+operator to understand how to scale back the int32 output to (u)int8 or 
(u)int16.
 - **data**: This depends on the `layout` parameter. Input is 4D array of shape
             (batch_size, in_channels, height, width) if `layout` is `NCHW`.
 - **weight**: (channels, in_channels, kernel_size[0], kernel_size[1])
diff --git a/src/relay/qnn/op/dequantize.cc b/src/relay/qnn/op/dequantize.cc
index 9a9c60d9ea..1ddcde8123 100644
--- a/src/relay/qnn/op/dequantize.cc
+++ b/src/relay/qnn/op/dequantize.cc
@@ -47,8 +47,8 @@ bool DequantizeRel(const Array<Type>& types, int num_inputs, 
const Attrs& attrs,
 
   const auto input_dtype = data->dtype;
   ICHECK(input_dtype == DataType::Int(8) || input_dtype == DataType::UInt(8) ||
-         input_dtype == DataType::Int(32))
-      << "Input type should be one of the quantized types [unit8, int8, int32] 
but was "
+         input_dtype == DataType::Int(16) || input_dtype == DataType::Int(32))
+      << "Input type should be one of the quantized types [unit8, int8, int16, 
int32] but was "
       << input_dtype;
 
   const auto* dequantize_attrs = attrs.as<DequantizeAttrs>();
diff --git a/src/relay/qnn/op/quantize.cc b/src/relay/qnn/op/quantize.cc
index 1a4c853d89..06a73ee91c 100644
--- a/src/relay/qnn/op/quantize.cc
+++ b/src/relay/qnn/op/quantize.cc
@@ -76,8 +76,8 @@ bool QuantizeRel(const Array<Type>& types, int num_inputs, 
const Attrs& attrs,
   const Array<tvm::PrimExpr> oshape = data->shape;
   const DataType out_dtype = quantize_attrs->out_dtype;
   ICHECK(out_dtype == DataType::Int(8) || out_dtype == DataType::UInt(8) ||
-         out_dtype == DataType::Int(32))
-      << "Output type should be one of [int8, unit8, int32] but was " << 
out_dtype;
+         out_dtype == DataType::Int(16) || out_dtype == DataType::Int(32))
+      << "Output type should be one of [int8, unit8, int16, int32] but was " 
<< out_dtype;
   // assign output type
   reporter->Assign(types[3], TensorType(oshape, out_dtype));
   return true;
diff --git a/src/relay/qnn/op/requantize.cc b/src/relay/qnn/op/requantize.cc
index ea143fe417..8601264f53 100644
--- a/src/relay/qnn/op/requantize.cc
+++ b/src/relay/qnn/op/requantize.cc
@@ -480,8 +480,8 @@ bool RequantizeRel(const Array<Type>& types, int 
num_inputs, const Attrs& attrs,
   }
   const auto in_dtype = data->dtype;
   ICHECK(in_dtype == DataType::Int(8) || in_dtype == DataType::UInt(8) ||
-         in_dtype == DataType::Int(32))
-      << "Input type should be one of [int8, uint8, int32] but was " << 
in_dtype;
+         in_dtype == DataType::Int(32) || in_dtype == DataType::Int(64))
+      << "Input type should be one of [int8, uint8, int32, int64] but was " << 
in_dtype;
 
   const RequantizeAttrs* requantize_attrs = attrs.as<RequantizeAttrs>();
   int axis = requantize_attrs->axis;
@@ -507,8 +507,8 @@ bool RequantizeRel(const Array<Type>& types, int 
num_inputs, const Attrs& attrs,
   // assign output type
   auto out_dtype = requantize_attrs->out_dtype;
   ICHECK(out_dtype == DataType::Int(8) || out_dtype == DataType::UInt(8) ||
-         out_dtype == DataType::Int(32))
-      << "Output type should be one of [int8, uint8, int32] but was " << 
out_dtype;
+         out_dtype == DataType::Int(16) || out_dtype == DataType::Int(32))
+      << "Output type should be one of [int8, uint8, int16, int32] but was " 
<< out_dtype;
   reporter->Assign(types[5], TensorType(oshape, out_dtype));
   return true;
 }
diff --git a/tests/python/frontend/tflite/test_forward.py 
b/tests/python/frontend/tflite/test_forward.py
index 80cdcf327f..8c8ca0eab2 100644
--- a/tests/python/frontend/tflite/test_forward.py
+++ b/tests/python/frontend/tflite/test_forward.py
@@ -139,19 +139,38 @@ def vmobj_to_list(o):
 
 
 def _quantize_keras_model(
-    keras_model, representative_data_gen, is_float_input=False, 
is_float_output=False
+    keras_model,
+    representative_data_gen,
+    is_float_input=False,
+    is_float_output=False,
+    int_quant_dtype=tf.int8,
 ):
     """Utility function to quantize a Keras model using TFLite converter."""
     converter = 
interpreter_wrapper.TFLiteConverter.from_keras_model(keras_model)
-    converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
-    converter.representative_dataset = representative_data_gen
-    converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
+    if int_quant_dtype == tf.int8:
+        converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
+        converter.representative_dataset = representative_data_gen
+        converter.target_spec.supported_ops = 
[tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
+        inference_dtype = tf.uint8
+    elif int_quant_dtype == tf.int16:
+        converter.optimizations = [tf.lite.Optimize.DEFAULT]
+        converter.representative_dataset = representative_data_gen
+        converter.target_spec.supported_ops = [
+            
tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8
+        ]
+        inference_dtype = tf.uint16
+    else:
+        raise RuntimeError(
+            f"Invalid quantized dtype {int_quant_dtype}. Supported types: 
int8, int16."
+        )
+
     # NOTE: If representative dataset is provided, and inference input type is 
not set,
     #       then converter will self add quant & dequant Op accordingly.
     if not is_float_input:
-        converter.inference_input_type = tf.uint8
+        converter.inference_input_type = inference_dtype
     if not is_float_output:
-        converter.inference_output_type = tf.uint8
+        converter.inference_output_type = inference_dtype
+
     return converter.convert()
 
 
@@ -271,6 +290,7 @@ def compare_tflite_with_tvm(
     mode="graph_executor",
     experimental_new_converter=False,
     fp16_quantized=False,
+    int_quant_dtype=tf.int8,
 ):
     """Generic function to generate and compare TFLite and TVM output"""
     in_data = convert_to_list(in_data)
@@ -287,7 +307,15 @@ def compare_tflite_with_tvm(
         converter = tf.lite.TFLiteConverter.from_session(sess, input_tensors, 
output_tensors)
         converter.experimental_new_converter = experimental_new_converter
         if quantized:
-            converter.inference_type = tf.lite.constants.QUANTIZED_UINT8
+            if int_quant_dtype == tf.int16:
+                converter.optimizations = [tf.lite.Optimize.DEFAULT]
+                converter.target_spec.supported_ops = [
+                    
tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8
+                ]
+            else:
+                # default to int8 quantization
+                converter.inference_type = tf.lite.constants.QUANTIZED_UINT8
+
             input_arrays = converter.get_input_arrays()
             input_stats = {}
             # calculate the mean and quantization scale for every input tensor,
@@ -875,7 +903,7 @@ def test_forward_l2_pool2d():
 
 
 def _test_tflite2_quantized_convolution(
-    input_shape, kernel_shape, dilations, strides, padding, data_format
+    input_shape, kernel_shape, filters, padding="valid", data_format=None, 
int_quant_dtype=tf.int8
 ):
     """One iteration of TFLite2 quantized convolution with given shapes and 
attributes"""
     data_format = "channels_last" if "NHWC" else "channels_first"
@@ -884,23 +912,26 @@ def _test_tflite2_quantized_convolution(
 
     data_in = tf.keras.layers.Input(shape=data.shape[1:])
     conv = tf.keras.layers.Conv2D(
-        filters=kernel_shape[3],
+        filters=filters,
         kernel_size=(kernel_shape[0], kernel_shape[1]),
-        strides=strides,
+        activation=tf.nn.relu,
         padding=padding,
         data_format=data_format,
-        activation="relu",
-        use_bias=False,
     )(data_in)
     keras_model = tf.keras.models.Model(data_in, conv)
-    keras_model.layers[1].set_weights([kernel])
 
     # To create quantized values with dynamic range of activations, needs 
representative dataset
     def representative_data_gen():
         for i in range(1):
             yield [data]
 
-    tflite_model_quant = _quantize_keras_model(keras_model, 
representative_data_gen)
+    tflite_model_quant = _quantize_keras_model(
+        keras_model,
+        representative_data_gen,
+        is_float_input=True,
+        is_float_output=True,
+        int_quant_dtype=int_quant_dtype,
+    )
 
     tflite_output = run_tflite_graph(tflite_model_quant, data)
     tvm_output = run_tvm_graph(tflite_model_quant, data, 
data_in.name.replace(":0", ""))
@@ -909,6 +940,25 @@ def _test_tflite2_quantized_convolution(
     )
 
 
+def test_forward_quantized_convolution():
+    for int_quant_dtype in [tf.int8, tf.int16]:
+        _test_tflite2_quantized_convolution(
+            (1, 28, 28, 1),
+            (1, 1),
+            12,
+            data_format="NHWC",
+            int_quant_dtype=int_quant_dtype,
+        )
+
+        _test_tflite2_quantized_convolution(
+            (1, 1, 28, 28),
+            (1, 1),
+            12,
+            data_format="NCWH",
+            int_quant_dtype=int_quant_dtype,
+        )
+
+
 def _test_tflite2_quantized_depthwise_convolution(
     input_shape, kernel_shape, dilations, strides, padding, data_format, 
depth_multiplier
 ):
@@ -1046,7 +1096,6 @@ def _test_convolution(
                     quantized=quantized,
                     input_range=input_range,
                     experimental_new_converter=True,
-                    fp16_quantized=fp16_quantized,
                 )
         else:
             data_array = np.reshape(data_array, 
tensor_in_sizes).astype("float32")
@@ -1765,7 +1814,7 @@ def test_forward_concatenation():
 # --------------
 
 
-def _test_unary_elemwise(math_op, data, quantized, quant_range=[-6, 6]):
+def _test_unary_elemwise(math_op, data, quantized, quant_range=[-6, 6], 
int_quant_dtype=tf.int8):
     """One iteration of unary elemwise"""
     if quantized:
         with tf.Graph().as_default():
@@ -1787,6 +1836,7 @@ def _test_unary_elemwise(math_op, data, quantized, 
quant_range=[-6, 6]):
                 quantized=True,
                 input_range=input_range,
                 experimental_new_converter=True,
+                int_quant_dtype=int_quant_dtype,
             )
     else:
         with tf.Graph().as_default():
@@ -1795,14 +1845,20 @@ def _test_unary_elemwise(math_op, data, quantized, 
quant_range=[-6, 6]):
             compare_tflite_with_tvm(data, ["in:0"], [in_data], [out])
 
 
-def _unary_elewise_create_model(math_op, data, offset=0):
+def _unary_elewise_create_model(math_op, data, offset=0, 
int_quant_dtype=tf.int8):
     class Model(tf.Module):
         @tf.function
         def tf_function(self, x):
             op = math_op(x)
             return op
 
-    dtype = "int8"
+    if int_quant_dtype in (tf.int8, tf.uint8):
+        dtype = "int8"
+    elif int_quant_dtype in (tf.int16, tf.uint16):
+        dtype = "int16"
+    else:
+        raise Exception(f"Unsupported dtype '{int_quant_dtype}' for unary 
elementwise test.")
+
     model = Model()
 
     # Save the model
@@ -1824,9 +1880,17 @@ def _unary_elewise_create_model(math_op, data, offset=0):
     converter = tf.lite.TFLiteConverter.from_saved_model(export_dir)
     converter.optimizations = [tf.lite.Optimize.DEFAULT]
     converter.representative_dataset = representative_dataset
-    converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
-    converter.inference_input_type = tf.int8
-    converter.inference_output_type = tf.int8
+
+    if int_quant_dtype in (tf.int16, tf.uint16):
+        converter.target_spec.supported_ops = [
+            
tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8
+        ]
+    else:
+        converter.target_spec.supported_ops = 
[tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
+
+    converter.inference_input_type = int_quant_dtype
+    converter.inference_output_type = int_quant_dtype
+
     tflite_model = converter.convert()
     return tflite_model
 
@@ -1836,24 +1900,28 @@ def _unary_elewise_create_model(math_op, data, 
offset=0):
 # ----
 
 
-def _test_abs(data, quantized):
+def _test_abs(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of abs"""
     if quantized:
-        tflite_model_quant = _unary_elewise_create_model(tf.math.abs, data, 
offset=1)
+        tflite_model_quant = _unary_elewise_create_model(
+            tf.math.abs, data, offset=1, int_quant_dtype=int_quant_dtype
+        )
         tflite_output = run_tflite_graph(tflite_model_quant, data)
 
         # TFLite 2.6.x upgrade support
         if tf.__version__ < LooseVersion("2.6.1"):
             in_node = ["serving_default_input_int8"]
         else:
-            in_node = ["tfl.quantize"]
+            in_node = (
+                ["serving_default_input_int16"] if int_quant_dtype == tf.int16 
else ["tfl.quantize"]
+            )
 
         tvm_output = run_tvm_graph(tflite_model_quant, data, in_node)
         tvm.testing.assert_allclose(
             np.squeeze(tvm_output[0]), np.squeeze(tflite_output[0]), 
rtol=1e-5, atol=1e-2
         )
     else:
-        return _test_unary_elemwise(math_ops.abs, data, quantized)
+        return _test_unary_elemwise(math_ops.abs, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1861,14 +1929,18 @@ def _test_abs(data, quantized):
 # ----
 
 
-def _test_rsqrt(data, quantized):
+def _test_rsqrt(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of rsqrt"""
 
     # tensorflow version upgrade support
     if tf.__version__ < LooseVersion("2.6.1") or not quantized:
-        return _test_unary_elemwise(math_ops.rsqrt, data, quantized, 
quant_range=[1, 6])
+        return _test_unary_elemwise(
+            math_ops.rsqrt, data, quantized, quant_range=[1, 6], 
int_quant_dtype=int_quant_dtype
+        )
     else:
-        tflite_model_quant = _unary_elewise_create_model(tf.math.rsqrt, data)
+        tflite_model_quant = _unary_elewise_create_model(
+            tf.math.rsqrt, data, int_quant_dtype=int_quant_dtype
+        )
         tflite_output = run_tflite_graph(tflite_model_quant, data)
         in_node = ["tfl.quantize"]
 
@@ -1883,9 +1955,9 @@ def _test_rsqrt(data, quantized):
 # ----
 
 
-def _test_ceil(data, quantized):
+def _test_ceil(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of ceil"""
-    return _test_unary_elemwise(math_ops.ceil, data, quantized)
+    return _test_unary_elemwise(math_ops.ceil, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1893,9 +1965,9 @@ def _test_ceil(data, quantized):
 # -----
 
 
-def _test_floor(data, quantized):
+def _test_floor(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of floor"""
-    return _test_unary_elemwise(math_ops.floor, data, quantized)
+    return _test_unary_elemwise(math_ops.floor, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1903,9 +1975,9 @@ def _test_floor(data, quantized):
 # -----
 
 
-def _test_round(data, quantized):
+def _test_round(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of round"""
-    return _test_unary_elemwise(math_ops.round, data, quantized)
+    return _test_unary_elemwise(math_ops.round, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1913,9 +1985,9 @@ def _test_round(data, quantized):
 # ---
 
 
-def _test_exp(data, quantized):
+def _test_exp(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of exp"""
-    return _test_unary_elemwise(math_ops.exp, data, quantized)
+    return _test_unary_elemwise(math_ops.exp, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1923,9 +1995,11 @@ def _test_exp(data, quantized):
 # ---
 
 
-def _test_log(data, quantized):
+def _test_log(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of log"""
-    return _test_unary_elemwise(math_ops.log, data, quantized, quant_range=[1, 
6])
+    return _test_unary_elemwise(
+        math_ops.log, data, quantized, quant_range=[1, 6], 
int_quant_dtype=int_quant_dtype
+    )
 
 
 #######################################################################
@@ -1933,9 +2007,9 @@ def _test_log(data, quantized):
 # ---
 
 
-def _test_sin(data, quantized):
+def _test_sin(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of sin"""
-    return _test_unary_elemwise(math_ops.sin, data, quantized)
+    return _test_unary_elemwise(math_ops.sin, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1943,10 +2017,12 @@ def _test_sin(data, quantized):
 # ---
 
 
-def _test_cos(data, quantized):
+def _test_cos(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of cos"""
     if quantized:
-        tflite_model_quant = _unary_elewise_create_model(tf.math.cos, data)
+        tflite_model_quant = _unary_elewise_create_model(
+            tf.math.cos, data, int_quant_dtype=int_quant_dtype
+        )
         tflite_output = run_tflite_graph(tflite_model_quant, data)
         in_node = ["tfl.quantize"]
         tvm_output = run_tvm_graph(tflite_model_quant, data, in_node)
@@ -1962,9 +2038,9 @@ def _test_cos(data, quantized):
 # ---
 
 
-def _test_tan(data, quantized):
+def _test_tan(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of tan"""
-    return _test_unary_elemwise(math_ops.tan, data, quantized)
+    return _test_unary_elemwise(math_ops.tan, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1972,9 +2048,9 @@ def _test_tan(data, quantized):
 # ------
 
 
-def _test_square(data, quantized):
+def _test_square(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of square"""
-    return _test_unary_elemwise(math_ops.square, data, quantized)
+    return _test_unary_elemwise(math_ops.square, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
@@ -1982,19 +2058,21 @@ def _test_square(data, quantized):
 # ------
 
 
-def _test_neg(data, quantized):
+def _test_neg(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of neg"""
-    return _test_unary_elemwise(math_ops.neg, data, quantized)
+    return _test_unary_elemwise(math_ops.neg, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
 #######################################################################
-# Neg
+# Sqrt
 # ------
 
 
-def _test_sqrt(data, quantized):
+def _test_sqrt(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of sqrt"""
-    return _test_unary_elemwise(math_ops.sqrt, data, quantized, 
quant_range=[1, 6])
+    return _test_unary_elemwise(
+        math_ops.sqrt, data, quantized, quant_range=[1, 6], 
int_quant_dtype=int_quant_dtype
+    )
 
 
 #######################################################################
@@ -2002,28 +2080,29 @@ def _test_sqrt(data, quantized):
 # ---
 
 
-def _test_elu(data, quantized):
+def _test_elu(data, quantized, int_quant_dtype=tf.int8):
     """One iteration of elu"""
-    return _test_unary_elemwise(nn_ops.elu, data, quantized)
+    return _test_unary_elemwise(nn_ops.elu, data, quantized, 
int_quant_dtype=int_quant_dtype)
 
 
-def _test_forward_unary_elemwise(test_op, quant_dtype=None, quantized=True, 
negtive=True):
+def _test_forward_unary_elemwise(test_op, int_quant_dtype=None, 
quantized=True, negative=True):
     # input data
     in_data, inq_data = [], []
 
+    np_dtype = int_quant_dtype.as_numpy_dtype if int_quant_dtype else np.uint8
+
     # quantized input data
     if quantized:
-        quant_dtype = quant_dtype or np.uint8
-        inq_data.append(np.arange(1, 240, 40, dtype=quant_dtype))
-        inq_data.append(np.arange(1, 240, 40, dtype=quant_dtype).reshape((2, 
1, 3)))
-        if quant_dtype == np.int8:
+        inq_data.append(np.arange(1, 240, 40, dtype=np_dtype))
+        inq_data.append(np.arange(1, 240, 40, dtype=np_dtype).reshape((2, 1, 
3)))
+        if int_quant_dtype == np.int8:
             inq_data.append(np.arange(-128, 127, 45, dtype=np.int8))
 
     for data in inq_data:
-        test_op(data, quantized=True)
+        test_op(data, quantized=True, int_quant_dtype=int_quant_dtype)
 
     # normal input data
-    if negtive:
+    if negative:
         in_data.append(np.arange(-2.0, 4.0, dtype=np.float32))
         in_data.append(np.arange(-2.0, 4.0, dtype=np.float32).reshape((2, 1, 
3)))
     else:
@@ -2031,30 +2110,31 @@ def _test_forward_unary_elemwise(test_op, 
quant_dtype=None, quantized=True, negt
         in_data.append(np.arange(1.0, 7.0, dtype=np.float32).reshape((2, 1, 
3)))
 
     for data in in_data:
-        test_op(data, quantized=False)
+        test_op(data, quantized=False, int_quant_dtype=int_quant_dtype)
 
 
 def test_all_unary_elemwise():
-    _test_forward_unary_elemwise(_test_abs, quant_dtype=np.int8)
+    _test_forward_unary_elemwise(_test_abs, int_quant_dtype=tf.int8)
+    _test_forward_unary_elemwise(_test_abs, int_quant_dtype=tf.int16)
     _test_forward_unary_elemwise(_test_floor)
     _test_forward_unary_elemwise(_test_exp)
-    _test_forward_unary_elemwise(_test_log, negtive=False)
+    _test_forward_unary_elemwise(_test_log, negative=False)
     _test_forward_unary_elemwise(_test_square)
     _test_forward_unary_elemwise(_test_sin)
     _test_forward_unary_elemwise(_test_neg)
-    _test_forward_unary_elemwise(_test_sqrt, negtive=False)
+    _test_forward_unary_elemwise(_test_sqrt, negative=False)
     # tensorflow version upgrade support
     if tf.__version__ < LooseVersion("2.6.1"):
-        _test_forward_unary_elemwise(_test_rsqrt, negtive=False, 
quant_dtype=np.uint8)
+        _test_forward_unary_elemwise(_test_rsqrt, negative=False, 
int_quant_dtype=tf.uint8)
     else:
-        _test_forward_unary_elemwise(_test_rsqrt, negtive=False, 
quant_dtype=np.int8)
+        _test_forward_unary_elemwise(_test_rsqrt, negative=False, 
int_quant_dtype=tf.int8)
     # ceil and cos come with TFLite 1.14.0.post1 fbs schema
     if package_version.parse(tf.VERSION) >= package_version.parse("1.14.0"):
         _test_forward_unary_elemwise(_test_ceil)
         if tf.__version__ < LooseVersion("2.6.1"):
             _test_forward_unary_elemwise(_test_cos, quantized=False)
         else:
-            _test_forward_unary_elemwise(_test_cos, quant_dtype=np.int8)
+            _test_forward_unary_elemwise(_test_cos, int_quant_dtype=tf.int8)
         _test_forward_unary_elemwise(_test_round)
         # This fails with TF and Tflite 1.15.2, this could not have been tested
         # in CI or anywhere else. The failure mode is that we see a backtrace
@@ -4572,6 +4652,47 @@ def test_forward_tflite_float16():
     tvm.testing.assert_allclose(tvm_sorted_labels, tflite_sorted_labels)
 
 
+def test_forward_mobilenet_int16():
+    """Test int16 quantized model"""
+    # MobilenetV2
+    model_file = tf_testing.get_workload_official(
+        
"https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_2018_02_22/mobilenet_v1_0.25_128.tgz";,
+        "mobilenet_v1_0.25_128_frozen.pb",
+    )
+
+    # Test image. Checking the labels because the requantize implementation is 
different between
+    # TFLite and Relay. This cause final output numbers to mismatch. So, 
testing accuracy via
+    # labels. Also, giving a real image, instead of random inputs.
+    #
+    # According to TFLite documentation, despite the quantization being done 
to make this model
+    # use int16 types, inputs and outputs are kept float32 by default.
+    # 
https://www.tensorflow.org/lite/performance/post_training_integer_quant_16x8
+    data = get_real_image(128, 128, quantized=False)
+
+    converter = tf.lite.TFLiteConverter.from_frozen_graph(
+        model_file, ["input"], ["MobilenetV1/Predictions/Reshape_1"]
+    )
+
+    def representative_dataset():
+        for _ in range(1):
+            yield [data]
+
+    converter.optimizations = [tf.lite.Optimize.DEFAULT]
+    converter.target_spec.supported_ops = [
+        
tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8
+    ]
+    converter.representative_dataset = representative_dataset
+    tflite_model_buf = converter.convert()
+
+    tflite_output = run_tflite_graph(tflite_model_buf, data)
+    tflite_predictions = np.squeeze(tflite_output)
+    tflite_sorted_labels = tflite_predictions.argsort()[-3:][::-1]
+    tvm_output = run_tvm_graph(tflite_model_buf, data, "input")
+    tvm_predictions = np.squeeze(tvm_output)
+    tvm_sorted_labels = tvm_predictions.argsort()[-3:][::-1]
+    tvm.testing.assert_allclose(tvm_sorted_labels, tflite_sorted_labels)
+
+
 #######################################################################
 # Quantized SSD Mobilenet
 # -----------------------
@@ -4867,3 +4988,5 @@ if __name__ == "__main__":
     test_forward_tflite2_qnn_mobilenet_v2()
 
     test_forward_tflite_float16()
+
+    test_forward_tflite_int16()

Reply via email to