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:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   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:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   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:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   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:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   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:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   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]

Reply via email to