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 fc21cd6ede [Fix][Relax][TFLite] Use astype for frontend casts (#19932)
fc21cd6ede is described below
commit fc21cd6ede10fe23b96644748b07b5e2a89cff43
Author: Hongyi Wu <[email protected]>
AuthorDate: Mon Jul 13 05:24:16 2026 +0800
[Fix][Relax][TFLite] Use astype for frontend casts (#19932)
## Summary
Fix TFLite Relax frontend cast paths that still used removed/nonexistent
cast
APIs.
- Use `relax.op.astype` for FLOAT16 `DEQUANTIZE` constants.
- Use `relax.op.astype` around the existing quantized `AVERAGE_POOL_2D`
converter path.
## Design
This PR only replaces invalid frontend API calls with `relax.op.astype`.
The quantized avgpool regression test calls the converter path directly
because
the top-level TFLite importer still rejects quantized `AVERAGE_POOL_2D`
before
conversion. Enabling that operator globally is out of scope.
## Tests
Added:
- `test_dequantize_float16_uses_astype`
- `test_quantized_avg_pool2d_uses_astype`
Validated with:
```bash
python -m ruff format \
python/tvm/relax/frontend/tflite/tflite_frontend.py \
tests/python/relax/test_frontend_tflite.py
python -m ruff check \
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 "dequantize or avg_pool" -q
```
Result:
```text
ruff format: 2 files left unchanged
ruff check: All checks passed
targeted dequantize/avg_pool tests: 9 passed, 551 deselected
```
I also ran the full TFLite frontend file:
```text
tests/python/relax/test_frontend_tflite.py: 559 passed, 1 failed
```
The remaining failure is unrelated to this PR:
`test_broadcast_to` expects `R.multiply(..., ones)` while the importer
emits
`R.broadcast_to(...)`.
---
.../tvm/relax/frontend/tflite/tflite_frontend.py | 6 +-
tests/python/relax/test_frontend_tflite.py | 148 +++++++++++++++++++++
2 files changed, 151 insertions(+), 3 deletions(-)
diff --git a/python/tvm/relax/frontend/tflite/tflite_frontend.py
b/python/tvm/relax/frontend/tflite/tflite_frontend.py
index 6918c600f6..a2db135466 100644
--- a/python/tvm/relax/frontend/tflite/tflite_frontend.py
+++ b/python/tvm/relax/frontend/tflite/tflite_frontend.py
@@ -5627,9 +5627,9 @@ class OperatorConverter:
"TFLite avg_pool2dreshape requires input and output scale"
"and zero points to be equal"
)
- out = relax.op.cast(in_expr, dtype="int32")
+ out = relax.op.astype(in_expr, "int32")
out = relax.op.nn.avg_pool2d(out, **params)
- out = relax.op.cast(out, dtype=output_tensor_type_str)
+ out = relax.op.astype(out, output_tensor_type_str)
else:
out = relax.op.nn.avg_pool2d(in_expr, **params)
elif pool_type == "max":
@@ -7327,7 +7327,7 @@ class OperatorConverter:
in_expr = self.exp_tab.new_const(
input_value, dtype=dtype,
source_name=input_tensor.tensor.Name()
)
- out = relax.cast(in_expr, dtype="float32")
+ out = relax.op.astype(in_expr, "float32")
return out
in_expr = self.get_expr(input_tensor.tensor_idx)
diff --git a/tests/python/relax/test_frontend_tflite.py
b/tests/python/relax/test_frontend_tflite.py
index c756227fe0..dab0b4b103 100644
--- a/tests/python/relax/test_frontend_tflite.py
+++ b/tests/python/relax/test_frontend_tflite.py
@@ -4907,6 +4907,7 @@ _tfl_int32_vector =
_get_tflite_schema_module("Int32Vector")
_tfl_model = _get_tflite_schema_module("Model")
_tfl_operator = _get_tflite_schema_module("Operator")
_tfl_operator_code = _get_tflite_schema_module("OperatorCode")
+_tfl_pool2d_options = _get_tflite_schema_module("Pool2DOptions")
_tfl_quantization_parameters =
_get_tflite_schema_module("QuantizationParameters")
_tfl_sparsity_parameters = _get_tflite_schema_module("SparsityParameters")
_tfl_subgraph = _get_tflite_schema_module("SubGraph")
@@ -11188,6 +11189,153 @@ def test_dequantize_op_uses_relax_dequantize():
tvm.ir.assert_structural_equal(mod, Expected)
+def test_dequantize_float16_uses_astype():
+ """TFLite DEQUANTIZE float16 -> float32 uses R.astype."""
+ builder = flatbuffers.Builder(1024)
+
+ input_data = np.array([1.5, -2.0], dtype=np.float16)
+
+ input_tensor = _build_tensor(builder, 0, [2],
tensor_type=_tfl_tensor_type.FLOAT16)
+ output_tensor = _build_tensor(builder, 1, [2],
tensor_type=_tfl_tensor_type.FLOAT32)
+
+ dequantize_op = _build_operator(builder, 0, [0], [1])
+ subgraph = _build_subgraph(
+ builder,
+ tensors=[input_tensor, output_tensor],
+ operators=[dequantize_op],
+ inputs=[],
+ outputs=[1],
+ )
+ operator_codes = [_build_operator_code(builder,
_tfl_builtin_operator.DEQUANTIZE)]
+ input_buffer = _build_buffer(builder, input_data.tobytes())
+ output_buffer = _build_buffer(builder)
+ buf = _finish_tflite_model(
+ builder,
+ subgraph=subgraph,
+ operator_codes=operator_codes,
+ buffers=[input_buffer, output_buffer],
+ )
+
+ if hasattr(tflite.Model, "Model"):
+ tflite_model = tflite.Model.Model.GetRootAsModel(buf, 0)
+ else:
+ tflite_model = tflite.Model.GetRootAsModel(buf, 0)
+ mod = from_tflite(tflite_model)
+ mod["main"] = mod["main"].without_attr("params")
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main() -> R.Tensor((2,), dtype="float32"):
+ R.func_attr({"num_input": 0})
+ with R.dataflow():
+ gv: R.Tensor((2,), dtype="float32") = R.astype(
+ R.const(np.array([1.5, -2.0], dtype=np.float16)),
dtype="float32"
+ )
+ R.output(gv)
+ return gv
+
+ tvm.ir.assert_structural_equal(mod, Expected)
+
+
+def test_quantized_avg_pool2d_uses_astype():
+ """Quantized AVERAGE_POOL_2D casts through int32 with R.astype."""
+ builder = flatbuffers.Builder(1024)
+
+ qparams = _build_quantization_parameters(
+ builder, scale=[0.5], zero_point=[3], quantized_dimension=0
+ )
+ input_tensor = _build_tensor(
+ builder,
+ 0,
+ [1, 2, 2, 1],
+ tensor_type=_tfl_tensor_type.INT8,
+ quantization=qparams,
+ )
+ output_tensor = _build_tensor(
+ builder,
+ 1,
+ [1, 1, 1, 1],
+ tensor_type=_tfl_tensor_type.INT8,
+ quantization=qparams,
+ )
+
+ _tfl_pool2d_options.Pool2DOptionsStart(builder)
+ _tfl_pool2d_options.Pool2DOptionsAddPadding(builder, _tfl_padding.VALID)
+ _tfl_pool2d_options.Pool2DOptionsAddStrideH(builder, 1)
+ _tfl_pool2d_options.Pool2DOptionsAddStrideW(builder, 1)
+ _tfl_pool2d_options.Pool2DOptionsAddFilterHeight(builder, 2)
+ _tfl_pool2d_options.Pool2DOptionsAddFilterWidth(builder, 2)
+ _tfl_pool2d_options.Pool2DOptionsAddFusedActivationFunction(builder,
_tfl_activation_fn.NONE)
+ pool_opts = _tfl_pool2d_options.Pool2DOptionsEnd(builder)
+
+ avg_pool_op = _build_operator(
+ builder,
+ 0,
+ [0],
+ [1],
+ builtin_options_type=_tfl_builtin_options.Pool2DOptions,
+ builtin_options=pool_opts,
+ )
+ subgraph = _build_subgraph(
+ builder,
+ tensors=[input_tensor, output_tensor],
+ operators=[avg_pool_op],
+ inputs=[0],
+ outputs=[1],
+ )
+ operator_codes = [_build_operator_code(builder,
_tfl_builtin_operator.AVERAGE_POOL_2D)]
+ buf = _finish_tflite_model(
+ builder,
+ subgraph=subgraph,
+ operator_codes=operator_codes,
+ buffers=[_build_buffer(builder), _build_buffer(builder)],
+ )
+
+ if hasattr(tflite.Model, "Model"):
+ tflite_model = tflite.Model.Model.GetRootAsModel(buf, 0)
+ else:
+ tflite_model = tflite.Model.GetRootAsModel(buf, 0)
+
+ subgraph = tflite_model.Subgraphs(0)
+ bb = relax.BlockBuilder()
+ exp_tab = tflite_frontend.ExprTable()
+ input_var = relax.Var("tvmgen_tensor_0", relax.TensorType((1, 2, 2, 1),
dtype="int8"))
+ exp_tab.set_expr("tvmgen_tensor_0", input_var)
+ converter = tflite_frontend.OperatorConverter(tflite_model, subgraph,
exp_tab, bb)
+ with bb.function("main", [input_var]):
+ with bb.dataflow():
+ output = converter.convert_pool2d(subgraph.Operators(0), "average")
+ gv = bb.emit_output(output)
+ bb.emit_func_output(gv)
+ mod = bb.get()
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main(tvmgen_tensor_0: R.Tensor((1, 2, 2, 1), dtype="int8")) ->
R.Tensor(
+ (1, 1, 1, 1), dtype="int8"
+ ):
+ with R.dataflow():
+ lv: R.Tensor((1, 2, 2, 1), dtype="int32") =
R.astype(tvmgen_tensor_0, dtype="int32")
+ lv1: R.Tensor((1, 1, 1, 1), dtype="int32") = R.nn.avg_pool2d(
+ lv,
+ pool_size=[2, 2],
+ strides=[1, 1],
+ dilation=[1, 1],
+ padding=[0, 0, 0, 0],
+ ceil_mode=False,
+ count_include_pad=False,
+ layout="NHWC",
+ out_layout="NHWC",
+ )
+ gv: R.Tensor((1, 1, 1, 1), dtype="int8") = R.astype(lv1,
dtype="int8")
+ R.output(gv)
+ return gv
+
+ tvm.ir.assert_structural_equal(mod, Expected)
+
+
def test_quantized_conv2d_per_tensor_uses_qdq():
"""Quantized Conv2D with per-tensor quantization uses DQ -> conv2d -> Q."""
builder = flatbuffers.Builder(2048)