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):