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

tlopex 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 545c3325ad [Relax][Frontend][TFLite] Fix bool 
`REDUCE_ANY`/`REDUCE_ALL` compile failure (#19415)
545c3325ad is described below

commit 545c3325ade19502bdae8767a33d6c58f201b8b8
Author: Soowon Jeong <[email protected]>
AuthorDate: Sat Apr 18 11:47:58 2026 +0900

    [Relax][Frontend][TFLite] Fix bool `REDUCE_ANY`/`REDUCE_ALL` compile 
failure (#19415)
    
    ## Problem
    
    #19413 registered `REDUCE_ANY` / `REDUCE_ALL` as `_convert_reduce` with
    `relax.op.max` / `relax.op.min`. These TFLite ops are bool-only (per TFL
    op schema: `TFL_ReduceAnyOp` / `TFL_ReduceAllOp` take and return
    `TFL_BoolTensor`), and `relax.op.max` / `relax.op.min` are not defined
    on bool, so any real model using these ops fails at compile time with:
    
    ```
    Cannot decide min_value for type bool
    Cannot decide max_value for type bool
    ```
    
    The existing structural-equality test passed because it never attempted
    to compile the converted module (E2E is gated on `CI_ENV_NIGHTLY`).
    
    ## Fix
    
    Introduce a dedicated `_convert_reduce_bool` handler that casts the
    input to int8, reduces with max/min, and casts back to bool. Update the
    test to compile the expected module so this lowering is exercised
    without `CI_ENV_NIGHTLY`.
    
    ## Testing
    
    Verified compile + VM-run (TF converter → Relax → LLVM) across the full
    shape / axes / keepdims matrix from `test_reduction_bool_ops`: 12 cases,
    all PASS.
    
    Follow-up to #19413.
---
 .../tvm/relax/frontend/tflite/tflite_frontend.py   | 34 ++++++++++++++++++++--
 tests/python/relax/test_frontend_tflite.py         | 21 ++++++++++++-
 2 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/python/tvm/relax/frontend/tflite/tflite_frontend.py 
b/python/tvm/relax/frontend/tflite/tflite_frontend.py
index 6d57dd5653..334021b903 100644
--- a/python/tvm/relax/frontend/tflite/tflite_frontend.py
+++ b/python/tvm/relax/frontend/tflite/tflite_frontend.py
@@ -195,8 +195,8 @@ class OperatorConverter:
             "PRELU": self.convert_prelu,
             "RANGE": self.convert_range,
             "QUANTIZE": self.convert_quantize,
-            "REDUCE_ALL": functools.partial(self._convert_reduce, 
relax_op=_op.min),
-            "REDUCE_ANY": functools.partial(self._convert_reduce, 
relax_op=_op.max),
+            "REDUCE_ALL": functools.partial(self._convert_reduce_bool, 
relax_op=_op.min),
+            "REDUCE_ANY": functools.partial(self._convert_reduce_bool, 
relax_op=_op.max),
             "REDUCE_MAX": functools.partial(self._convert_reduce, 
relax_op=_op.max),
             "REDUCE_MIN": functools.partial(self._convert_reduce, 
relax_op=_op.min),
             "REDUCE_PROD": functools.partial(self._convert_reduce, 
relax_op=_op.prod),
@@ -1787,6 +1787,36 @@ class OperatorConverter:
 
         return out
 
+    def _convert_reduce_bool(self, relax_op, op):
+        """Convert TFLite REDUCE_ANY / REDUCE_ALL (bool-only ops).
+
+        Relax max/min are undefined on bool, so cast through int8.
+        """
+        from tflite.BuiltinOptions import BuiltinOptions
+        from tflite.ReducerOptions import ReducerOptions
+
+        input_tensors = self.get_input_tensors(op)
+        assert len(input_tensors) == 2, "input tensors length should be 2"
+
+        input_tensor = input_tensors[0]
+        in_expr = self.get_expr(input_tensor.tensor_idx)
+
+        axis_value = self.get_tensor_value(input_tensors[1])
+        axis = tuple(axis_value) if len(axis_value.shape) > 0 else 
tuple((axis_value.item(),))
+
+        if op.BuiltinOptionsType():
+            assert op.BuiltinOptionsType() == BuiltinOptions.ReducerOptions
+            reduce_options = ReducerOptions()
+            op_options = op.BuiltinOptions()
+            reduce_options.Init(op_options.Bytes, op_options.Pos)
+            keep_dims = reduce_options.KeepDims()
+        else:
+            keep_dims = False
+
+        in_expr = relax.op.astype(in_expr, "int8")
+        out = relax_op(in_expr, axis, keep_dims)
+        return relax.op.astype(out, "bool")
+
     def _convert_arg_min_max(self, op, relax_op):
         """Generic method converting TFLite arg_min_max"""
 
diff --git a/tests/python/relax/test_frontend_tflite.py 
b/tests/python/relax/test_frontend_tflite.py
index 4fff2340de..92080634e2 100644
--- a/tests/python/relax/test_frontend_tflite.py
+++ b/tests/python/relax/test_frontend_tflite.py
@@ -1977,6 +1977,22 @@ def test_reduction_ops(tf_op, relax_op, input_shape, 
axes, keepdims, dtype):
     verify(ReduceModule, expected)
 
 
+def _make_reduce_bool_expected(relax_op, input_shape, axes, keepdims):
+    if axes is None:
+        axes = list(range(len(input_shape)))
+    bb = relax.BlockBuilder()
+    x = relax.Var("x", relax.TensorStructInfo(input_shape, "bool"))
+    with bb.function("main", [x]):
+        with bb.dataflow():
+            cast_in = bb.emit(relax.op.astype(x, "int8"))
+            reduced = bb.emit(relax_op(cast_in, axis=axes, keepdims=keepdims))
+            gv = bb.emit_output(relax.op.astype(reduced, "bool"))
+        bb.emit_func_output(gv)
+    mod = bb.get()
+    mod["main"] = mod["main"].with_attr("num_input", 1)
+    return mod
+
+
 @pytest.mark.parametrize(
     "tf_op, relax_op",
     [
@@ -2002,9 +2018,12 @@ def test_reduction_bool_ops(tf_op, relax_op, 
input_shape, axes, keepdims):
         def func(self, x):
             return tf_op(x, axis=axes, keepdims=keepdims)
 
-    expected = _make_reduce_expected(relax_op, input_shape, axes, keepdims, 
"bool")
+    expected = _make_reduce_bool_expected(relax_op, input_shape, axes, 
keepdims)
     verify(ReduceBoolModule, expected)
 
+    # Regression guard: compile to catch a bool max/min lowering path.
+    tvm.compile(expected, tvm.target.Target("llvm"))
+
 
 def test_pad():
     class Pad(tf.Module):

Reply via email to