gemini-code-assist[bot] commented on code in PR #19522:
URL: https://github.com/apache/tvm/pull/19522#discussion_r3211129611
##########
tests/python/relax/test_frontend_tflite.py:
##########
@@ -4250,5 +4249,115 @@ def main(
tvm.ir.assert_structural_equal(mod, Expected)
+def test_sign():
+ """SIGN → relax.op.sign (unary elemwise, float and int)."""
+
+ class Sign(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(3, 4),
dtype=tf.float32)])
+ def func(self, x):
+ return tf.math.sign(x)
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main(x: R.Tensor((3, 4), dtype="float32")) -> R.Tensor((3, 4),
dtype="float32"):
+ R.func_attr({"num_input": 1})
+ with R.dataflow():
+ gv: R.Tensor((3, 4), dtype="float32") = R.sign(x)
+ R.output(gv)
+ return gv
+
+ verify(Sign, Expected)
+
+ class SignInt(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(5,),
dtype=tf.int32)])
+ def func(self, x):
+ return tf.math.sign(x)
+
+ verify(SignInt)
+
+
+def test_unique():
+ """UNIQUE → relax.op.unique, two-output (values, inverse_indices)."""
+
+ class Unique(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(6,),
dtype=tf.float32)])
+ def func(self, x):
+ y, idx = tf.unique(x)
+ return y, idx
+
+ verify(Unique)
+
+ class UniqueInt(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(8,),
dtype=tf.int32)])
+ def func(self, x):
+ y, idx = tf.unique(x)
+ return y, idx
+
+ verify(UniqueInt)
+
+
+def test_bucketize():
+ """BUCKETIZE → relax.op.bucketize with constant boundaries from
BucketizeOptions."""
+
+ class Bucketize(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(5,),
dtype=tf.float32)])
+ def func(self, x):
+ return tf.raw_ops.Bucketize(input=x, boundaries=[0.0, 1.0, 2.0])
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main(x: R.Tensor((5,), dtype="float32")) -> R.Tensor((5,),
dtype="int32"):
+ R.func_attr({"num_input": 1})
+ with R.dataflow():
+ lv: R.Tensor((3,), dtype="float32") = R.const(
+ np.array([0.0, 1.0, 2.0], dtype="float32"), "float32"
+ )
+ gv: R.Tensor((5,), dtype="int32") = R.bucketize(x, lv,
right=False)
Review Comment:

The expected Relax IR should use `right=True` to correctly reflect TFLite's
`BUCKETIZE` semantics.
```suggestion
gv: R.Tensor((5,), dtype="int32") = R.bucketize(x, lv,
right=True)
```
##########
tests/python/relax/test_frontend_tflite.py:
##########
@@ -4250,5 +4249,115 @@ def main(
tvm.ir.assert_structural_equal(mod, Expected)
+def test_sign():
+ """SIGN → relax.op.sign (unary elemwise, float and int)."""
+
+ class Sign(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(3, 4),
dtype=tf.float32)])
+ def func(self, x):
+ return tf.math.sign(x)
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main(x: R.Tensor((3, 4), dtype="float32")) -> R.Tensor((3, 4),
dtype="float32"):
+ R.func_attr({"num_input": 1})
+ with R.dataflow():
+ gv: R.Tensor((3, 4), dtype="float32") = R.sign(x)
+ R.output(gv)
+ return gv
+
+ verify(Sign, Expected)
+
+ class SignInt(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(5,),
dtype=tf.int32)])
+ def func(self, x):
+ return tf.math.sign(x)
+
+ verify(SignInt)
+
+
+def test_unique():
+ """UNIQUE → relax.op.unique, two-output (values, inverse_indices)."""
+
+ class Unique(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(6,),
dtype=tf.float32)])
+ def func(self, x):
+ y, idx = tf.unique(x)
+ return y, idx
+
+ verify(Unique)
+
+ class UniqueInt(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(8,),
dtype=tf.int32)])
+ def func(self, x):
+ y, idx = tf.unique(x)
+ return y, idx
+
+ verify(UniqueInt)
+
+
+def test_bucketize():
+ """BUCKETIZE → relax.op.bucketize with constant boundaries from
BucketizeOptions."""
+
+ class Bucketize(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(5,),
dtype=tf.float32)])
+ def func(self, x):
+ return tf.raw_ops.Bucketize(input=x, boundaries=[0.0, 1.0, 2.0])
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main(x: R.Tensor((5,), dtype="float32")) -> R.Tensor((5,),
dtype="int32"):
+ R.func_attr({"num_input": 1})
+ with R.dataflow():
+ lv: R.Tensor((3,), dtype="float32") = R.const(
+ np.array([0.0, 1.0, 2.0], dtype="float32"), "float32"
+ )
+ gv: R.Tensor((5,), dtype="int32") = R.bucketize(x, lv,
right=False)
+ R.output(gv)
+ return gv
+
+ verify(Bucketize, Expected)
+
+ class BucketizeEmpty(tf.Module):
+ @tf.function(input_signature=[tf.TensorSpec(shape=(4,),
dtype=tf.float32)])
+ def func(self, x):
+ return tf.raw_ops.Bucketize(input=x, boundaries=[])
+
+ verify(BucketizeEmpty)
+
+
+def test_fake_quant():
Review Comment:

It would be beneficial to add a test case for the degenerate range scenario
(`min == max`) in `FAKE_QUANT`, as this is a specific fix introduced in this
pull request to prevent division by zero.
##########
python/tvm/relax/frontend/tflite/tflite_frontend.py:
##########
@@ -1229,6 +1235,46 @@ def quantize(x):
return out
+ def convert_relu_0_to_1(self, op):
+ """Convert TFLite RELU_0_TO_1 — clips input to [0, 1]."""
+ input_tensors = self.get_input_tensors(op)
+ assert len(input_tensors) == 1, "input tensors length should be 1"
+ input_tensor = input_tensors[0]
+ in_expr = self.get_expr(input_tensor.tensor_idx)
+
+ output_tensors = self.get_output_tensors(op)
+ assert len(output_tensors) == 1, "output tensors length should be 1"
+ output_tensor = output_tensors[0]
+
+ if input_tensor.qnn_params:
+ scale_val =
get_scalar_from_constant(input_tensor.qnn_params["scale"])
+ zero_point_val =
get_scalar_from_constant(input_tensor.qnn_params["zero_point"])
+
+ def quantize(x):
+ return float(round(x / scale_val) + zero_point_val)
Review Comment:

TFLite quantization typically uses "round to nearest, ties up" semantics
(equivalent to `floor(x + 0.5)`). Python's built-in `round()` uses "round to
nearest, ties to even" (Banker's rounding), which can lead to different results
for values exactly at the midpoint. Using `math.floor(x + 0.5)` ensures
consistency with TFLite's expected behavior for quantized clipping bounds.
```suggestion
return float(math.floor(x / scale_val + 0.5) +
zero_point_val)
```
##########
python/tvm/relax/frontend/tflite/tflite_frontend.py:
##########
@@ -2802,6 +2848,32 @@ def convert_broadcast_args(self, op):
relax.op.where(s1_is_one, s0, relax.op.maximum(s0, s1)),
)
+ def convert_bucketize(self, op):
+ """Convert TFLite BUCKETIZE → relax.op.bucketize.
+
+ Boundaries are stored as a repeated float in BucketizeOptions, not as a
+ tensor input, so we materialise them as a compile-time constant.
+ """
+ from tflite.BuiltinOptions import BuiltinOptions
+ from tflite.BucketizeOptions import BucketizeOptions
+
+ input_tensors = self.get_input_tensors(op)
+ assert len(input_tensors) == 1, "input tensors length should be 1"
+ in_expr = self.get_tensor_expr(input_tensors[0])
+
+ assert op.BuiltinOptionsType() == BuiltinOptions.BucketizeOptions
+ op_options = op.BuiltinOptions()
+ bucket_options = BucketizeOptions()
+ bucket_options.Init(op_options.Bytes, op_options.Pos)
+
+ boundaries = [
+ bucket_options.Boundaries(i) for i in
range(bucket_options.BoundariesLength())
+ ]
+ boundaries_const = relax.op.const(np.array(boundaries,
dtype="float32"))
Review Comment:

The use of `relax.op.const` is inconsistent with the rest of the active code
in this file, which uses `relax.const` (e.g., lines 66, 499, 850).
`relax.const` is the standard factory function for creating constants in the
Relax frontend.
```suggestion
boundaries_const = relax.const(np.array(boundaries, dtype="float32"))
```
##########
python/tvm/relax/frontend/tflite/tflite_frontend.py:
##########
@@ -2802,6 +2848,32 @@ def convert_broadcast_args(self, op):
relax.op.where(s1_is_one, s0, relax.op.maximum(s0, s1)),
)
+ def convert_bucketize(self, op):
+ """Convert TFLite BUCKETIZE → relax.op.bucketize.
+
+ Boundaries are stored as a repeated float in BucketizeOptions, not as a
+ tensor input, so we materialise them as a compile-time constant.
+ """
+ from tflite.BuiltinOptions import BuiltinOptions
+ from tflite.BucketizeOptions import BucketizeOptions
+
+ input_tensors = self.get_input_tensors(op)
+ assert len(input_tensors) == 1, "input tensors length should be 1"
+ in_expr = self.get_tensor_expr(input_tensors[0])
+
+ assert op.BuiltinOptionsType() == BuiltinOptions.BucketizeOptions
+ op_options = op.BuiltinOptions()
+ bucket_options = BucketizeOptions()
+ bucket_options.Init(op_options.Bytes, op_options.Pos)
+
+ boundaries = [
+ bucket_options.Boundaries(i) for i in
range(bucket_options.BoundariesLength())
+ ]
+ boundaries_const = relax.op.const(np.array(boundaries,
dtype="float32"))
+
+ out = relax.op.bucketize(in_expr, boundaries_const, right=False)
Review Comment:

TFLite's `BUCKETIZE` operator is defined to return the index of the first
boundary that is **greater than** the input value (equivalent to
`std::upper_bound`). In Relax's `bucketize` operator, this behavior is selected
by setting `right=True`. The current implementation using `right=False`
(equivalent to `std::lower_bound`) will produce incorrect results when an input
value exactly matches a boundary.
```suggestion
out = relax.op.bucketize(in_expr, boundaries_const, right=True)
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]