Skip to content

Commit e53cfe1

Browse files
authored
[Frontend][TFLite] Fix undefined symbols and Relay API remnants in TFLite frontend (#18929)
The TFLite frontend was ported from Relay but contains several undefined symbols and Relay-specific APIs that cause runtime errors. This PR cleans up these issues so that working code paths are clean and broken paths fail with clear `NotImplementedError` instead of `NameError`.
1 parent ff883db commit e53cfe1

2 files changed

Lines changed: 100 additions & 109 deletions

File tree

python/tvm/relax/frontend/tflite/tflite_frontend.py

Lines changed: 72 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
# pylint: disable=import-outside-toplevel, use-list-literal
1919
# pylint: disable=no-value-for-parameter, unused-variable
2020
# pylint: disable=unexpected-keyword-arg, unused-import, too-many-function-args
21-
# ruff: noqa: RUF005, F821, F841
21+
# ruff: noqa: RUF005
22+
# F821: _qnn and _expr references are in unreachable code paths (guarded by NotImplementedError)
23+
# and will be resolved when quantization and vision op support are added.
24+
# ruff: noqa: F821
2225
"""Tensorflow lite frontend."""
2326

2427
import functools
@@ -468,7 +471,9 @@ def get_tensors(self, tensors_idx_list):
468471
qnn_params = dict()
469472
qnn_params["scale"] = relax.const(scale, "float32")
470473
qnn_params["zero_point"] = relax.const(zero_point, "int32")
471-
raise NotImplementedError("Quantized operators not supported now")
474+
raise NotImplementedError(
475+
"Quantized TFLite models are not yet supported in the Relax frontend"
476+
)
472477
return_list.append(TensorWrapper(tensor_idx, tensor, buffer, qnn_params))
473478
return return_list
474479

@@ -530,20 +535,14 @@ def get_tensor_type_str(self, tensor_type):
530535
return "bool"
531536
raise NotImplementedError(f"Tensor type {tensor_type!s} is currently not supported")
532537

533-
def flatten_to_nd(self, x, x_shape, nd=3):
538+
def flatten_to_nd(self, x, nd=3):
534539
"""Flatten input tensor to nd rank"""
535-
ndims = self._infer_shape(x_shape)[0]
540+
shape = x.struct_info.shape
541+
ndims = len(shape)
536542
if ndims == nd:
537543
return x
538-
newshape = relax.op.concat(
539-
[
540-
relax.const([-1], dtype=self._infer_type(x_shape).checked_type.dtype),
541-
relax.op.strided_slice(x_shape, [ndims - nd + 1], [ndims]),
542-
],
543-
0,
544-
)
545-
out = relax.op.reshape(x, self._fold_constant(newshape))
546-
return out
544+
new_shape = [-1] + [int(shape[i]) for i in range(ndims - nd + 1, ndims)]
545+
return relax.op.reshape(x, new_shape)
547546

548547
def has_same_qnn_params(self, lhs_tensor, rhs_tensor):
549548
lhs_scale = lhs_tensor.qnn_params["scale"]
@@ -709,7 +708,7 @@ def _convert_resize(self, method, op):
709708

710709
# ResizeNearestNeighborOptions was added in tflite v1.13
711710
tflite_ver = 1120
712-
if "ResizeNearestNeighborOptions" in dir(tflite.BuiltinOptions):
711+
if hasattr(BuiltinOptions, "ResizeNearestNeighborOptions"):
713712
tflite_ver = 1130
714713

715714
input_tensors = self.get_input_tensors(op)
@@ -947,8 +946,7 @@ def convert_shape(self, op):
947946
shape_options = ShapeOptions()
948947
shape_options.Init(op_options.Bytes, op_options.Pos)
949948

950-
out_type = self.get_tensor_type_str(shape_options.OutType())
951-
out = shape_of(self.get_tensor_expr(input_tensors[0]), dtype=out_type)
949+
out = relax.op.shape_of(self.get_tensor_expr(input_tensors[0]))
952950

953951
return out
954952

@@ -1428,6 +1426,7 @@ def convert_gather(self, op):
14281426

14291427
from tflite.BuiltinOptions import BuiltinOptions
14301428
from tflite.GatherOptions import GatherOptions
1429+
from tflite.TensorType import TensorType
14311430

14321431
input_tensors = self.get_input_tensors(op)
14331432
assert len(input_tensors) == 2, "input tensors length should be 2"
@@ -2804,115 +2803,66 @@ def convert_batch_matmul(self, op):
28042803

28052804
assert len(input_tensors) == 2, "two input tensor arguments expected"
28062805

2806+
if self.is_quantized(op):
2807+
raise NotImplementedError(
2808+
"Quantized BATCH_MATMUL is not yet supported in the Relax frontend"
2809+
)
2810+
28072811
batch_matmul_options = BatchMatMulOptions()
28082812
op_options = op.BuiltinOptions()
28092813
batch_matmul_options.Init(op_options.Bytes, op_options.Pos)
28102814

28112815
input_a = self.get_expr(input_tensors[0].tensor_idx)
28122816
input_b = self.get_expr(input_tensors[1].tensor_idx)
28132817

2814-
shape_a = shape_of(input_a)
2815-
shape_b = shape_of(input_b)
2816-
rank_a = self._infer_shape(shape_a)[0]
2817-
rank_b = self._infer_shape(shape_b)[0]
2818+
shape_a = list(input_a.struct_info.shape)
2819+
shape_b = list(input_b.struct_info.shape)
2820+
rank_a = len(shape_a)
2821+
rank_b = len(shape_b)
28182822

28192823
if rank_a > 2 or rank_b > 2:
2820-
# Determine the output batch dimension
2821-
new_a_shape = shape_a
2822-
new_b_shape = shape_b
2823-
if rank_a > rank_b:
2824-
rank_diff = rank_a - rank_b
2825-
new_b_shape = relax.op.concat(
2826-
[
2827-
relax.const(
2828-
[1] * rank_diff, dtype=self._infer_type(new_b_shape).checked_type.dtype
2829-
),
2830-
shape_b,
2831-
],
2832-
0,
2833-
)
2834-
elif rank_a < rank_b:
2835-
rank_diff = rank_b - rank_a
2836-
new_a_shape = relax.op.concat(
2837-
[
2838-
relax.const(
2839-
[1] * rank_diff, dtype=self._infer_type(new_a_shape).checked_type.dtype
2840-
),
2841-
shape_a,
2842-
],
2843-
0,
2844-
)
2845-
else:
2846-
pass
2824+
# Broadcast batch dimensions
2825+
new_a_shape = [1] * max(0, rank_b - rank_a) + [int(s) for s in shape_a]
2826+
new_b_shape = [1] * max(0, rank_a - rank_b) + [int(s) for s in shape_b]
2827+
max_rank = max(rank_a, rank_b)
28472828

2848-
out_batch = relax.op.concat(
2849-
[
2850-
relax.op.maximum(
2851-
relax.op.strided_slice(new_b_shape, [i], [i + 1]),
2852-
relax.op.strided_slice(new_a_shape, [i], [i + 1]),
2853-
)
2854-
for i in range(max(rank_a, rank_b) - 2)
2855-
],
2856-
0,
2857-
)
2829+
batch_shape = [
2830+
max(new_a_shape[i], new_b_shape[i]) for i in range(max_rank - 2)
2831+
]
28582832

2859-
a_broadcasted_shape = _fold_constant(
2860-
_op.concat([out_batch, _op.strided_slice(shape_a, [rank_a - 2], [rank_a])], 0)
2861-
)
2862-
b_broadcasted_shape = _fold_constant(
2863-
_op.concat([out_batch, _op.strided_slice(shape_b, [rank_b - 2], [rank_b])], 0)
2864-
)
2865-
if not tvm.ir.structural_equal(shape_a, a_broadcasted_shape):
2866-
input_a = relax.op.transform.broadcast_to(input_a, a_broadcasted_shape)
2867-
if not tvm.ir.structural_equal(shape_b, b_broadcasted_shape):
2868-
input_b = relax.op.transform.broadcast_to(input_b, b_broadcasted_shape)
2833+
a_broadcast = batch_shape + [int(shape_a[-2]), int(shape_a[-1])]
2834+
b_broadcast = batch_shape + [int(shape_b[-2]), int(shape_b[-1])]
28692835

2870-
input_a = self.flatten_to_nd(input_a, shape_a, 3)
2871-
input_b = self.flatten_to_nd(input_b, shape_b, 3)
2836+
if [int(s) for s in shape_a] != a_broadcast:
2837+
input_a = relax.op.broadcast_to(input_a, a_broadcast)
2838+
if [int(s) for s in shape_b] != b_broadcast:
2839+
input_b = relax.op.broadcast_to(input_b, b_broadcast)
28722840

2873-
if batch_matmul_options.AdjX():
2841+
input_a = self.flatten_to_nd(input_a, 3)
2842+
input_b = self.flatten_to_nd(input_b, 3)
2843+
2844+
adj_x = batch_matmul_options.AdjX()
2845+
adj_y = batch_matmul_options.AdjY()
2846+
2847+
if adj_x:
28742848
input_a = relax.op.permute_dims(input_a, [0, 2, 1])
2875-
if not batch_matmul_options.AdjY():
2849+
if adj_y:
28762850
input_b = relax.op.permute_dims(input_b, [0, 2, 1])
28772851

2878-
if self.is_quantized(op):
2879-
output = _qnn.op.batch_matmul(
2880-
input_a,
2881-
input_b,
2882-
relax.const(0, "int32"),
2883-
relax.const(0, "int32"),
2884-
relax.const(1.0, "float32"),
2885-
relax.const(1.0, "float32"),
2886-
)
2887-
else:
2888-
output = relax.op.nn.batch_matmul(input_a, input_b)
2852+
output = relax.op.matmul(input_a, input_b)
28892853

2890-
# Reshape output to original dimensions.
2891-
output_shape = shape_of(output)
2854+
# Compute output matmul dims from original shapes
2855+
m_dim = int(shape_a[-1]) if adj_x else int(shape_a[-2])
2856+
n_dim = int(shape_b[-2]) if adj_y else int(shape_b[-1])
2857+
final_shape = [int(s) for s in shape_a[: rank_a - 2]] + [m_dim, n_dim]
2858+
return relax.op.reshape(output, final_shape)
28922859

2893-
rank_out = self._infer_shape(output_shape)[0]
2894-
2895-
final_shape = relax.op.concat(
2896-
[
2897-
relax.op.strided_slice(shape_a, [0], [rank_a - 2]),
2898-
relax.op.strided_slice(output_shape, [rank_out - 2], [rank_out]),
2899-
],
2900-
0,
2901-
)
2902-
2903-
reshape = relax.op.reshape(output, self._fold_constant(final_shape))
2904-
# qnn batch matmul returns a int32 tensor so we need to requantize
2905-
if self.is_quantized(op):
2906-
return _qnn.op.requantize(
2907-
reshape,
2908-
relax.const(1.0, "float32"),
2909-
relax.const(0, "int32"),
2910-
relax.const(1.0, "float32"),
2911-
relax.const(0, "int32"),
2912-
out_dtype="int8",
2913-
)
2914-
else:
2915-
return reshape
2860+
# rank <= 2: use matmul directly
2861+
if batch_matmul_options.AdjX():
2862+
input_a = relax.op.permute_dims(input_a)
2863+
if batch_matmul_options.AdjY():
2864+
input_b = relax.op.permute_dims(input_b)
2865+
return relax.op.matmul(input_a, input_b)
29162866

29172867
def convert_space_to_batch_nd(self, op):
29182868
"""space_to_batch_nd implementation."""
@@ -2974,6 +2924,7 @@ def convert_space_to_depth(self, op):
29742924

29752925
def convert_sparse_to_dense(self, op):
29762926
"""Convert TFLite SPARSE_TO_DENSE"""
2927+
from tflite.TensorType import TensorType
29772928

29782929
input_tensors = self.get_input_tensors(op)
29792930
assert len(input_tensors) == 4, "input tensors length should be 4"
@@ -3029,6 +2980,7 @@ def convert_transpose_conv(self, op):
30292980

30302981
from tflite.BuiltinOptions import BuiltinOptions
30312982
from tflite.Padding import Padding
2983+
from tflite.TensorType import TensorType
30322984
from tflite.TransposeConvOptions import TransposeConvOptions
30332985

30342986
input_tensors = self.get_input_tensors(op)
@@ -3226,6 +3178,7 @@ def convert_quantize(self, op):
32263178

32273179
def convert_dequantize(self, op):
32283180
"""Convert TFLite Dequantize"""
3181+
from tflite.TensorType import TensorType
32293182

32303183
input_tensors = self.get_input_tensors(op)
32313184
assert len(input_tensors) == 1, "input tensors length should be 1"
@@ -3251,6 +3204,11 @@ def convert_dequantize(self, op):
32513204

32523205
def convert_detection_postprocess(self, op):
32533206
"""Convert TFLite_Detection_PostProcess"""
3207+
raise NotImplementedError(
3208+
"DETECTION_POSTPROCESS requires vision ops (multibox_transform_loc, "
3209+
"non_max_suppression, get_valid_counts) not yet available in Relax. "
3210+
"See https://github.com/apache/tvm/issues/XXXX"
3211+
)
32543212
flexbuffer = op.CustomOptionsAsNumpy().tobytes()
32553213
custom_options = FlexBufferDecoder(flexbuffer).decode()
32563214

@@ -3381,6 +3339,11 @@ def convert_detection_postprocess(self, op):
33813339
def convert_nms_v5(self, op):
33823340
"""Convert TFLite NonMaxSuppressionV5"""
33833341
# https://www.tensorflow.org/api_docs/cc/class/tensorflow/ops/non-max-suppression-v5
3342+
raise NotImplementedError(
3343+
"NON_MAX_SUPPRESSION_V5 requires vision ops (get_valid_counts, "
3344+
"non_max_suppression) not yet available in Relax. "
3345+
"See https://github.com/apache/tvm/issues/XXXX"
3346+
)
33843347

33853348
input_tensors = self.get_input_tensors(op)
33863349
assert len(input_tensors) == 6, "input tensor length should be 6"
@@ -3843,7 +3806,7 @@ def _def_prepare_dense_matrix_from_sparse(indices, level, prev_idx):
38433806

38443807
def get_scalar_from_constant(expr):
38453808
"""Returns scalar value from Relax constant scalar."""
3846-
assert isinstance(expr, _expr.Constant) and not expr.data.shape, (
3809+
assert isinstance(expr, relax.Constant) and not expr.data.shape, (
38473810
"Expr is not a constant scalar."
38483811
)
38493812
value = expr.data.numpy()
@@ -4091,7 +4054,7 @@ def func(self, data):
40914054

40924055
with bb.function("main"):
40934056
input_list = []
4094-
with bb.dataflow() as df: # pylint: disable=invalid-name, unused-variable
4057+
with bb.dataflow() as df: # noqa: F841 # pylint: disable=invalid-name, unused-variable
40954058
exp_tab = ExprTable()
40964059
for model_input in model_inputs:
40974060
model_input_name = get_tensor_name(subgraph, model_input)

tests/python/relax/test_frontend_tflite.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,5 +825,33 @@ def func(self, data):
825825
verify(concrete_func)
826826

827827

828+
def test_batch_matmul():
829+
class BatchMatMul(tf.Module):
830+
@tf.function(
831+
input_signature=[
832+
tf.TensorSpec(shape=(2, 3, 4), dtype=tf.float32),
833+
tf.TensorSpec(shape=(2, 4, 5), dtype=tf.float32),
834+
]
835+
)
836+
def func(self, x, y):
837+
return tf.matmul(x, y)
838+
839+
verify(BatchMatMul)
840+
841+
842+
def test_batch_matmul_adj():
843+
class BatchMatMulAdj(tf.Module):
844+
@tf.function(
845+
input_signature=[
846+
tf.TensorSpec(shape=(2, 4, 3), dtype=tf.float32),
847+
tf.TensorSpec(shape=(2, 5, 4), dtype=tf.float32),
848+
]
849+
)
850+
def func(self, x, y):
851+
return tf.matmul(x, y, transpose_a=True, transpose_b=True)
852+
853+
verify(BatchMatMulAdj)
854+
855+
828856
if __name__ == "__main__":
829857
pytest.main(["-s", __file__])

0 commit comments

Comments
 (0)