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 0d70112300 [Relax][Frontend][TFLite] Add REDUCE_WINDOW support (#19637)
0d70112300 is described below

commit 0d7011230040835a6739d32c4f04dba44b2b10af
Author: Sun <[email protected]>
AuthorDate: Fri May 29 14:12:35 2026 +0800

    [Relax][Frontend][TFLite] Add REDUCE_WINDOW support (#19637)
    
    ## Summary
    Add Relax TFLite frontend support for the builtin `REDUCE_WINDOW`
    operator.
    This covers the ordinary TFLite op only, not `STABLEHLO_REDUCE_WINDOW`.
    
    The converter parses `ReduceWindowOptions` from `BuiltinOptions2`,
    validates
    the static window attributes, and lowers supported reduce functions
    through
    `topi.sliding_window` plus Relax reductions.
    
    Supported modes:
    - `ADD`
    - `MUL`
    - `MINIMUM`
    - `MAXIMUM`
    - `ALL`
    - `ANY`
    
    Empty output shapes are handled directly with `relax.op.zeros`.
    Quantized
    `REDUCE_WINDOW`, dynamic window attributes, and unsupported reduce
    functions
    remain rejected with explicit errors.
    
    ## Testing
    - `python -m py_compile
    python/tvm/relax/frontend/tflite/tflite_frontend.py
    tests/python/relax/test_frontend_tflite.py`
    - `python -m pytest tests/python/relax/test_frontend_tflite.py -k
    reduce_window -q -p no:tvm.testing.plugin`
    - `python -m pytest tests/python/relax/test_frontend_tflite.py -k
    "reduce_window or reduction_ops" -q -p no:tvm.testing.plugin`
    - `conda run -n test python -m ruff check
    python/tvm/relax/frontend/tflite/tflite_frontend.py
    tests/python/relax/test_frontend_tflite.py`
    
    ## Related
    Related to #19519.
---
 .../tvm/relax/frontend/tflite/tflite_frontend.py   | 166 +++++++++
 tests/python/relax/test_frontend_tflite.py         | 406 +++++++++++++++++++++
 2 files changed, 572 insertions(+)

diff --git a/python/tvm/relax/frontend/tflite/tflite_frontend.py 
b/python/tvm/relax/frontend/tflite/tflite_frontend.py
index 8183f64f73..65c0faadc2 100644
--- a/python/tvm/relax/frontend/tflite/tflite_frontend.py
+++ b/python/tvm/relax/frontend/tflite/tflite_frontend.py
@@ -281,6 +281,7 @@ class OperatorConverter:
             "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),
+            "REDUCE_WINDOW": self.convert_reduce_window,
             "RELU": self.convert_relu,
             "RELU6": self.convert_relu6,
             "RELU_N1_TO_1": self.convert_relu_n1_to_1,
@@ -3445,6 +3446,171 @@ class OperatorConverter:
 
         return out
 
+    def convert_reduce_window(self, op):
+        """Convert TFLite REDUCE_WINDOW."""
+
+        from tflite.BuiltinOptions2 import BuiltinOptions2
+        from tflite.ReduceWindowFunction import ReduceWindowFunction
+        from tflite.ReduceWindowOptions import ReduceWindowOptions
+
+        input_tensors = self.get_input_tensors(op)
+        output_tensors = self.get_output_tensors(op)
+        if len(input_tensors) != 5:
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW requires 5 input tensors."
+            )
+        if len(output_tensors) != 1:
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW requires 1 output tensor."
+            )
+
+        if op.BuiltinOptions2Type() != BuiltinOptions2.ReduceWindowOptions:
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW requires ReduceWindowOptions."
+            )
+
+        (
+            input_tensor,
+            init_tensor,
+            window_shape_tensor,
+            window_strides_tensor,
+            window_dilations_tensor,
+        ) = input_tensors
+        output_tensor = output_tensors[0]
+
+        if any(
+            self.has_expr(tensor.tensor_idx)
+            for tensor in [window_shape_tensor, window_strides_tensor, 
window_dilations_tensor]
+        ):
+            raise tvm.error.OpNotImplemented(
+                "TFLite REDUCE_WINDOW requires constant window_shape, "
+                "window_strides, and window_dilations."
+            )
+
+        input_shape = to_int_list(self.get_tensor_shape(input_tensor))
+        output_shape = to_int_list(self.get_tensor_shape(output_tensor))
+        input_dtype = self.get_tensor_type_str(input_tensor.tensor.Type())
+        output_dtype = self.get_tensor_type_str(output_tensor.tensor.Type())
+
+        if input_tensor.qnn_params or output_tensor.qnn_params:
+            raise tvm.error.OpNotImplemented(
+                "Quantized TFLite REDUCE_WINDOW is not yet supported in the 
Relax frontend."
+            )
+
+        if input_dtype != output_dtype:
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW requires input and output dtypes to 
match."
+            )
+
+        init_shape = to_int_list(self.get_tensor_shape(init_tensor))
+        if math.prod(init_shape) != 1:
+            raise tvm.error.OpNotImplemented(
+                "TFLite REDUCE_WINDOW requires init_value to contain exactly 
one element."
+            )
+
+        options = ReduceWindowOptions()
+        op_options = op.BuiltinOptions2()
+        options.Init(op_options.Bytes, op_options.Pos)
+        reduce_function = options.ReduceFunction()
+
+        if reduce_function == ReduceWindowFunction.UNSUPPORTED:
+            raise tvm.error.OpNotImplemented(
+                "TFLite REDUCE_WINDOW with UNSUPPORTED reduce_function is not 
supported."
+            )
+
+        window_shape = to_int_list(self.get_tensor_value(window_shape_tensor))
+        window_strides = 
to_int_list(self.get_tensor_value(window_strides_tensor))
+        window_dilations = 
to_int_list(self.get_tensor_value(window_dilations_tensor))
+        rank = len(input_shape)
+
+        if not (len(window_shape) == len(window_strides) == 
len(window_dilations) == rank):
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW window_shape, window_strides, and 
window_dilations "
+                "must match input rank."
+            )
+
+        if any(value <= 0 for value in window_shape + window_strides + 
window_dilations):
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW window dimensions, strides, and 
dilations must be positive."
+            )
+
+        dilated_window_shape = [
+            (window_dim - 1) * dilation + 1
+            for window_dim, dilation in zip(window_shape, window_dilations)
+        ]
+        expected_output_shape = [
+            0 if input_dim < dilated_dim else (input_dim - dilated_dim) // 
stride + 1
+            for input_dim, dilated_dim, stride in zip(
+                input_shape, dilated_window_shape, window_strides
+            )
+        ]
+
+        numeric_reduce_functions = {
+            ReduceWindowFunction.ADD: (relax.op.sum, relax.op.add),
+            ReduceWindowFunction.MUL: (relax.op.prod, relax.op.multiply),
+            ReduceWindowFunction.MINIMUM: (relax.op.min, relax.op.minimum),
+            ReduceWindowFunction.MAXIMUM: (relax.op.max, relax.op.maximum),
+        }
+        bool_reduce_functions = {
+            ReduceWindowFunction.ALL: (relax.op.min, relax.op.logical_and),
+            ReduceWindowFunction.ANY: (relax.op.max, relax.op.logical_or),
+        }
+
+        if reduce_function in numeric_reduce_functions and input_dtype == 
"bool":
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW numeric reductions expect numeric input."
+            )
+        if reduce_function in bool_reduce_functions and input_dtype != "bool":
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW boolean reductions expect bool input."
+            )
+
+        if output_shape != expected_output_shape:
+            raise tvm.error.OpAttributeUnImplemented(
+                "TFLite REDUCE_WINDOW output shape does not match input/window 
parameters."
+            )
+
+        if any(output_dim == 0 for output_dim in output_shape):
+            return relax.op.zeros(output_shape, output_dtype)
+
+        data = self.get_tensor_expr(input_tensor)
+        init_value = self.get_tensor_expr(init_tensor)
+        if len(init_shape) != 0:
+            init_value = relax.op.reshape(init_value, [])
+
+        windowed = relax.op.call_dps_packed(
+            "topi.sliding_window",
+            (
+                data,
+                0,
+                relax.ShapeExpr(dilated_window_shape),
+                relax.ShapeExpr(window_strides),
+            ),
+            out_sinfo=relax.TensorStructInfo(output_shape + 
dilated_window_shape, input_dtype),
+        )
+
+        if any(dilation != 1 for dilation in window_dilations):
+            windowed = relax.op.strided_slice(
+                windowed,
+                axes=list(range(rank, 2 * rank)),
+                begin=[0] * rank,
+                end=dilated_window_shape,
+                strides=window_dilations,
+            )
+
+        reduce_axes = list(range(rank, 2 * rank))
+        if reduce_function in numeric_reduce_functions:
+            reduce_op, combine_op = numeric_reduce_functions[reduce_function]
+            return combine_op(reduce_op(windowed, axis=reduce_axes), 
init_value)
+        if reduce_function in bool_reduce_functions:
+            reduce_op, combine_op = bool_reduce_functions[reduce_function]
+            reduced = reduce_op(relax.op.astype(windowed, "int8"), 
axis=reduce_axes)
+            return combine_op(relax.op.astype(reduced, "bool"), init_value)
+
+        raise tvm.error.OpNotImplemented(
+            f"TFLite REDUCE_WINDOW reduce_function {reduce_function} is not 
supported."
+        )
+
     def _convert_reduce_bool(self, relax_op, op):
         """Convert TFLite REDUCE_ANY / REDUCE_ALL (bool-only ops).
 
diff --git a/tests/python/relax/test_frontend_tflite.py 
b/tests/python/relax/test_frontend_tflite.py
index f1abacec27..9e91c09c2d 100644
--- a/tests/python/relax/test_frontend_tflite.py
+++ b/tests/python/relax/test_frontend_tflite.py
@@ -3705,6 +3705,7 @@ _tfl_model = _get_tflite_schema_module("Model")
 _tfl_operator = _get_tflite_schema_module("Operator")
 _tfl_operator_code = _get_tflite_schema_module("OperatorCode")
 _tfl_quantization_parameters = 
_get_tflite_schema_module("QuantizationParameters")
+_tfl_reduce_window_options = _get_tflite_schema_module("ReduceWindowOptions")
 _tfl_sparsity_parameters = _get_tflite_schema_module("SparsityParameters")
 _tfl_subgraph = _get_tflite_schema_module("SubGraph")
 _tfl_tensor = _get_tflite_schema_module("Tensor")
@@ -3717,6 +3718,7 @@ _tfl_activation_fn = 
_get_tflite_schema_enum("ActivationFunctionType")
 _tfl_dimension_type = _get_tflite_schema_enum("DimensionType")
 _tfl_fc_weights_format = 
_get_tflite_schema_enum("FullyConnectedOptionsWeightsFormat")
 _tfl_padding = _get_tflite_schema_enum("Padding")
+_tfl_reduce_window_function = _get_tflite_schema_enum("ReduceWindowFunction")
 _tfl_sparse_index_vector = _get_tflite_schema_enum("SparseIndexVector")
 _tfl_tensor_type = _get_tflite_schema_enum("TensorType")
 
@@ -3951,6 +3953,410 @@ def _load_model_from_buffer(model_bytes):
     return mod
 
 
+def _build_reduce_window_options(builder, reduce_function):
+    _tfl_reduce_window_options.ReduceWindowOptionsStart(builder)
+    _tfl_reduce_window_options.ReduceWindowOptionsAddReduceFunction(builder, 
reduce_function)
+    return _tfl_reduce_window_options.ReduceWindowOptionsEnd(builder)
+
+
+def _reduce_window_output_shape(input_shape, window_shape, window_strides, 
window_dilations):
+    output_shape = []
+    for input_dim, window_dim, stride, dilation in zip(
+        input_shape, window_shape, window_strides, window_dilations
+    ):
+        dilated_window = (window_dim - 1) * dilation + 1
+        if stride <= 0:
+            output_shape.append(0)
+        elif input_dim < dilated_window:
+            output_shape.append(0)
+        else:
+            output_shape.append((input_dim - dilated_window) // stride + 1)
+    return tuple(output_shape)
+
+
+def _build_reduce_window_model(
+    *,
+    input_shape,
+    init_value,
+    init_shape=(),
+    window_shape,
+    window_strides,
+    window_dilations,
+    output_shape=None,
+    reduce_function,
+    tensor_type=None,
+    value_dtype=np.float32,
+):
+    builder = flatbuffers.Builder(1024)
+    if tensor_type is None:
+        tensor_type = _tfl_tensor_type.FLOAT32
+
+    input_tensor_idx = 0
+    init_tensor_idx = 1
+    window_shape_tensor_idx = 2
+    window_strides_tensor_idx = 3
+    window_dilations_tensor_idx = 4
+    output_tensor_idx = 5
+
+    if output_shape is None:
+        output_shape = _reduce_window_output_shape(
+            input_shape, window_shape, window_strides, window_dilations
+        )
+
+    input_tensor = _build_tensor(builder, 1, input_shape, 
tensor_type=tensor_type)
+    init_tensor = _build_tensor(builder, 2, init_shape, 
tensor_type=tensor_type)
+    window_shape_tensor = _build_tensor(
+        builder, 3, [len(window_shape)], tensor_type=_tfl_tensor_type.INT64
+    )
+    window_strides_tensor = _build_tensor(
+        builder, 4, [len(window_strides)], tensor_type=_tfl_tensor_type.INT64
+    )
+    window_dilations_tensor = _build_tensor(
+        builder, 5, [len(window_dilations)], tensor_type=_tfl_tensor_type.INT64
+    )
+    output_tensor = _build_tensor(builder, 6, output_shape, 
tensor_type=tensor_type)
+
+    reduce_window_opts = _build_reduce_window_options(builder, reduce_function)
+    reduce_window_op = _build_operator(
+        builder,
+        0,
+        [
+            input_tensor_idx,
+            init_tensor_idx,
+            window_shape_tensor_idx,
+            window_strides_tensor_idx,
+            window_dilations_tensor_idx,
+        ],
+        [output_tensor_idx],
+        builtin_options2_type=_tfl_builtin_options2.ReduceWindowOptions,
+        builtin_options2=reduce_window_opts,
+    )
+
+    subgraph = _build_subgraph(
+        builder,
+        tensors=[
+            input_tensor,
+            init_tensor,
+            window_shape_tensor,
+            window_strides_tensor,
+            window_dilations_tensor,
+            output_tensor,
+        ],
+        operators=[reduce_window_op],
+        inputs=[input_tensor_idx],
+        outputs=[output_tensor_idx],
+    )
+    operator_codes = [_build_operator_code(builder, 
_tfl_builtin_operator.REDUCE_WINDOW)]
+
+    buffers = [
+        _build_buffer(builder),
+        _build_buffer(builder),
+        _build_buffer(builder, np.asarray([init_value], 
dtype=value_dtype).tobytes()),
+        _build_buffer(builder, np.asarray(window_shape, 
dtype=np.int64).tobytes()),
+        _build_buffer(builder, np.asarray(window_strides, 
dtype=np.int64).tobytes()),
+        _build_buffer(builder, np.asarray(window_dilations, 
dtype=np.int64).tobytes()),
+        _build_buffer(builder),
+    ]
+
+    return _finish_tflite_model(
+        builder, subgraph=subgraph, operator_codes=operator_codes, 
buffers=buffers
+    )
+
+
+def _from_reduce_window_model(**kwargs):
+    return _load_model_from_buffer(_build_reduce_window_model(**kwargs))
+
+
+def _reduce_window_dilated_shape(window_shape, window_dilations):
+    return [
+        (window_dim - 1) * dilation + 1
+        for window_dim, dilation in zip(window_shape, window_dilations)
+    ]
+
+
+def _make_reduce_window_numeric_expected(
+    *,
+    input_shape,
+    init_value,
+    init_shape=(),
+    window_shape,
+    window_strides,
+    window_dilations,
+    reduce_op,
+    combine_op,
+    dtype="float32",
+):
+    output_shape = _reduce_window_output_shape(
+        input_shape, window_shape, window_strides, window_dilations
+    )
+    dilated_window_shape = _reduce_window_dilated_shape(window_shape, 
window_dilations)
+    rank = len(input_shape)
+
+    bb = relax.BlockBuilder()
+    x = relax.Var("tvmgen_tensor_0", relax.TensorStructInfo(input_shape, 
dtype))
+    with bb.function("main", [x]):
+        with bb.dataflow():
+            windowed = bb.emit(
+                relax.op.call_dps_packed(
+                    "topi.sliding_window",
+                    (
+                        x,
+                        0,
+                        relax.ShapeExpr(dilated_window_shape),
+                        relax.ShapeExpr(window_strides),
+                    ),
+                    out_sinfo=relax.TensorStructInfo(
+                        output_shape + tuple(dilated_window_shape), dtype
+                    ),
+                )
+            )
+            if any(dilation != 1 for dilation in window_dilations):
+                windowed = bb.emit(
+                    relax.op.strided_slice(
+                        windowed,
+                        axes=list(range(rank, 2 * rank)),
+                        begin=[0] * rank,
+                        end=dilated_window_shape,
+                        strides=window_dilations,
+                    )
+                )
+            reduced = bb.emit(reduce_op(windowed, axis=list(range(rank, 2 * 
rank))))
+            init = relax.const(np.asarray([init_value], 
dtype=dtype).reshape(init_shape), dtype)
+            if len(init_shape) != 0:
+                init = relax.op.reshape(init, [])
+            gv = bb.emit_output(combine_op(reduced, init))
+        bb.emit_func_output(gv)
+
+    mod = bb.get()
+    mod["main"] = mod["main"].with_attr("num_input", 1)
+    return mod
+
+
+def _make_reduce_window_bool_expected(
+    *,
+    input_shape,
+    init_value,
+    window_shape,
+    window_strides,
+    window_dilations,
+    reduce_op,
+    combine_op,
+):
+    output_shape = _reduce_window_output_shape(
+        input_shape, window_shape, window_strides, window_dilations
+    )
+    dilated_window_shape = _reduce_window_dilated_shape(window_shape, 
window_dilations)
+    rank = len(input_shape)
+
+    bb = relax.BlockBuilder()
+    x = relax.Var("tvmgen_tensor_0", relax.TensorStructInfo(input_shape, 
"bool"))
+    with bb.function("main", [x]):
+        with bb.dataflow():
+            windowed = bb.emit(
+                relax.op.call_dps_packed(
+                    "topi.sliding_window",
+                    (
+                        x,
+                        0,
+                        relax.ShapeExpr(dilated_window_shape),
+                        relax.ShapeExpr(window_strides),
+                    ),
+                    out_sinfo=relax.TensorStructInfo(
+                        output_shape + tuple(dilated_window_shape), "bool"
+                    ),
+                )
+            )
+            cast_windowed = bb.emit(relax.op.astype(windowed, "int8"))
+            reduced = bb.emit(reduce_op(cast_windowed, axis=list(range(rank, 2 
* rank))))
+            reduced_bool = bb.emit(relax.op.astype(reduced, "bool"))
+            gv = bb.emit_output(combine_op(reduced_bool, 
relax.const(init_value, "bool")))
+        bb.emit_func_output(gv)
+
+    mod = bb.get()
+    mod["main"] = mod["main"].with_attr("num_input", 1)
+    return mod
+
+
+def _make_reduce_window_empty_expected(*, input_shape, output_shape, 
dtype="float32"):
+    bb = relax.BlockBuilder()
+    x = relax.Var("tvmgen_tensor_0", relax.TensorStructInfo(input_shape, 
dtype))
+    with bb.function("main", [x]):
+        with bb.dataflow():
+            gv = bb.emit_output(relax.op.zeros(output_shape, dtype))
+        bb.emit_func_output(gv)
+
+    mod = bb.get()
+    mod["main"] = mod["main"].with_attr("num_input", 1)
+    return mod
+
+
+def test_reduce_window_unsupported_function():
+    with pytest.raises(tvm.error.OpNotImplemented, match="UNSUPPORTED 
reduce_function"):
+        _from_reduce_window_model(
+            input_shape=(4,),
+            init_value=0.0,
+            window_shape=[2],
+            window_strides=[1],
+            window_dilations=[1],
+            reduce_function=_tfl_reduce_window_function.UNSUPPORTED,
+        )
+
+
[email protected](
+    "reduce_function, reduce_op, combine_op",
+    [
+        (_tfl_reduce_window_function.ADD, relax.op.sum, relax.op.add),
+        (_tfl_reduce_window_function.MUL, relax.op.prod, relax.op.multiply),
+        (_tfl_reduce_window_function.MINIMUM, relax.op.min, relax.op.minimum),
+        (_tfl_reduce_window_function.MAXIMUM, relax.op.max, relax.op.maximum),
+    ],
+)
+def test_reduce_window_numeric_modes(reduce_function, reduce_op, combine_op):
+    input_shape = (4, 5)
+    init_value = 1.0
+    window_shape = [2, 2]
+    window_strides = [1, 2]
+    window_dilations = [2, 1]
+    mod = _from_reduce_window_model(
+        input_shape=input_shape,
+        init_value=init_value,
+        window_shape=window_shape,
+        window_strides=window_strides,
+        window_dilations=window_dilations,
+        reduce_function=reduce_function,
+    )
+    expected = _make_reduce_window_numeric_expected(
+        input_shape=input_shape,
+        init_value=init_value,
+        window_shape=window_shape,
+        window_strides=window_strides,
+        window_dilations=window_dilations,
+        reduce_op=reduce_op,
+        combine_op=combine_op,
+    )
+    tvm.ir.assert_structural_equal(mod, expected)
+
+
+def test_reduce_window_one_element_init_tensor():
+    input_shape = (4,)
+    init_value = 1.0
+    init_shape = (1,)
+    window_shape = [2]
+    window_strides = [1]
+    window_dilations = [1]
+    mod = _from_reduce_window_model(
+        input_shape=input_shape,
+        init_value=init_value,
+        init_shape=init_shape,
+        window_shape=window_shape,
+        window_strides=window_strides,
+        window_dilations=window_dilations,
+        reduce_function=_tfl_reduce_window_function.ADD,
+    )
+    expected = _make_reduce_window_numeric_expected(
+        input_shape=input_shape,
+        init_value=init_value,
+        init_shape=init_shape,
+        window_shape=window_shape,
+        window_strides=window_strides,
+        window_dilations=window_dilations,
+        reduce_op=relax.op.sum,
+        combine_op=relax.op.add,
+    )
+    tvm.ir.assert_structural_equal(mod, expected)
+
+
[email protected](
+    "reduce_function, reduce_op, combine_op, init_value",
+    [
+        (_tfl_reduce_window_function.ALL, relax.op.min, relax.op.logical_and, 
True),
+        (_tfl_reduce_window_function.ANY, relax.op.max, relax.op.logical_or, 
False),
+    ],
+)
+def test_reduce_window_bool_modes(reduce_function, reduce_op, combine_op, 
init_value):
+    input_shape = (5,)
+    window_shape = [3]
+    window_strides = [2]
+    window_dilations = [1]
+    mod = _from_reduce_window_model(
+        input_shape=input_shape,
+        init_value=init_value,
+        window_shape=window_shape,
+        window_strides=window_strides,
+        window_dilations=window_dilations,
+        reduce_function=reduce_function,
+        tensor_type=_tfl_tensor_type.BOOL,
+        value_dtype=np.bool_,
+    )
+    expected = _make_reduce_window_bool_expected(
+        input_shape=input_shape,
+        init_value=init_value,
+        window_shape=window_shape,
+        window_strides=window_strides,
+        window_dilations=window_dilations,
+        reduce_op=reduce_op,
+        combine_op=combine_op,
+    )
+    tvm.ir.assert_structural_equal(mod, expected)
+
+
+def test_reduce_window_empty_output_dimension():
+    input_shape = (2,)
+    window_shape = [3]
+    window_strides = [1]
+    window_dilations = [1]
+    mod = _from_reduce_window_model(
+        input_shape=input_shape,
+        init_value=0.0,
+        window_shape=window_shape,
+        window_strides=window_strides,
+        window_dilations=window_dilations,
+        reduce_function=_tfl_reduce_window_function.ADD,
+    )
+    expected = _make_reduce_window_empty_expected(
+        input_shape=input_shape,
+        output_shape=(0,),
+    )
+    tvm.ir.assert_structural_equal(mod, expected)
+
+
+def test_reduce_window_mismatched_window_rank():
+    with pytest.raises(tvm.error.OpAttributeUnImplemented, match="must match 
input rank"):
+        _from_reduce_window_model(
+            input_shape=(4, 5),
+            init_value=0.0,
+            window_shape=[2],
+            window_strides=[1],
+            window_dilations=[1],
+            reduce_function=_tfl_reduce_window_function.ADD,
+        )
+
+
+def test_reduce_window_non_positive_stride():
+    with pytest.raises(tvm.error.OpAttributeUnImplemented, match="must be 
positive"):
+        _from_reduce_window_model(
+            input_shape=(4,),
+            init_value=0.0,
+            window_shape=[2],
+            window_strides=[0],
+            window_dilations=[1],
+            reduce_function=_tfl_reduce_window_function.ADD,
+        )
+
+
+def test_reduce_window_inconsistent_output_shape():
+    with pytest.raises(tvm.error.OpAttributeUnImplemented, match="output 
shape"):
+        _from_reduce_window_model(
+            input_shape=(5,),
+            init_value=0.0,
+            window_shape=[2],
+            window_strides=[1],
+            window_dilations=[1],
+            output_shape=(3,),
+            reduce_function=_tfl_reduce_window_function.ADD,
+        )
+
+
 def _get_builtin_operator(builtin_name):
     if not hasattr(_tfl_builtin_operator, builtin_name):
         pytest.skip(f"TFLite schema does not provide 
BuiltinOperator.{builtin_name}")

Reply via email to