From 10741f27dccd858f699cca4433d71bda5878a124 Mon Sep 17 00:00:00 2001 From: cchung100m Date: Thu, 14 May 2026 19:25:48 +0800 Subject: [PATCH 01/11] [Relax][ONNX] Prevent integer divide-by-zero crashes --- .../tvm/relax/frontend/onnx/onnx_frontend.py | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py b/python/tvm/relax/frontend/onnx/onnx_frontend.py index 3f25d2ff3bb5..63272409546e 100644 --- a/python/tvm/relax/frontend/onnx/onnx_frontend.py +++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py @@ -72,6 +72,18 @@ def _relax_dtype_is_floating_point(dtype: str) -> bool: ) +def _const_integer_expr_has_zero(expr: relax.Expr) -> bool | None: + """Return whether a constant integer expression contains a zero value. + + Returns None when expression is not statically inspectable. + """ + + if isinstance(expr, relax.Constant): + return bool(_np.any(expr.data.numpy() == 0)) + + return None + + def get_type(elem_type: str | int) -> str: """Converts onnx integer datatype to numpy datatype""" # If a string was passed instead of a tensor type, it does not need @@ -526,7 +538,38 @@ class Div(BinaryBase): @classmethod def _impl_v7(cls, bb, inputs, attr, params): - return cls.base_impl(bb, inputs, attr, params) + lhs_dtype = inputs[0].struct_info.dtype + rhs_dtype = inputs[1].struct_info.dtype + + try: + lhs_code = DataType(lhs_dtype).type_code + rhs_code = DataType(rhs_dtype).type_code + except (ValueError, TypeError, TVMError): + return cls.base_impl(bb, inputs, attr, params) + + lhs_is_integer = lhs_code == DataTypeCode.INT or lhs_code == DataTypeCode.UINT + rhs_is_integer = rhs_code == DataTypeCode.INT or rhs_code == DataTypeCode.UINT + if not (lhs_is_integer and rhs_is_integer): + return cls.base_impl(bb, inputs, attr, params) + + rhs_has_zero = _const_integer_expr_has_zero(inputs[1]) + if rhs_has_zero: + return ValueError("ONNX Div with integer inputs encountered divisor value 0.") + + if rhs_has_zero is False: + return cls.base_impl(bb, inputs, attr, params) + + rhs_is_zero = bb.normalize(relax.op.equal(inputs[1], relax.const(0, rhs_dtype))) + rhs_is_zero_i64 = bb.normalize(relax.op.astype(rhs_is_zero, "int64")) + zero_count = bb.normalize(relax.op.sum(rhs_is_zero_i64, axis=None, keepdims=False)) + no_zero_divisor = bb.normalize(relax.op.equal(zero_count, relax.const(0, "int64"))) + bb.emit( + relax.op.assert_op( + no_zero_divisor, + format="ONNX Div with integer inputs encountered divisor value 0.", + ) + ) + return cls.relax_op(inputs[0], inputs[1]) # pylint: disable=not-callable class Pow(BinaryBase): From 9e9f78ca5a610edcdee1c5d03d8746a02353e3b4 Mon Sep 17 00:00:00 2001 From: cchung100m Date: Thu, 14 May 2026 21:32:59 +0800 Subject: [PATCH 02/11] [Relax][ONNX] Remove dynamic assert --- python/tvm/relax/frontend/onnx/onnx_frontend.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py b/python/tvm/relax/frontend/onnx/onnx_frontend.py index 63272409546e..40df0a2e78af 100644 --- a/python/tvm/relax/frontend/onnx/onnx_frontend.py +++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py @@ -554,23 +554,12 @@ def _impl_v7(cls, bb, inputs, attr, params): rhs_has_zero = _const_integer_expr_has_zero(inputs[1]) if rhs_has_zero: - return ValueError("ONNX Div with integer inputs encountered divisor value 0.") + raise ValueError("ONNX Div with integer inputs encountered divisor value 0.") if rhs_has_zero is False: return cls.base_impl(bb, inputs, attr, params) - rhs_is_zero = bb.normalize(relax.op.equal(inputs[1], relax.const(0, rhs_dtype))) - rhs_is_zero_i64 = bb.normalize(relax.op.astype(rhs_is_zero, "int64")) - zero_count = bb.normalize(relax.op.sum(rhs_is_zero_i64, axis=None, keepdims=False)) - no_zero_divisor = bb.normalize(relax.op.equal(zero_count, relax.const(0, "int64"))) - bb.emit( - relax.op.assert_op( - no_zero_divisor, - format="ONNX Div with integer inputs encountered divisor value 0.", - ) - ) - return cls.relax_op(inputs[0], inputs[1]) # pylint: disable=not-callable - + return cls.base_impl(bb, inputs, attr, params) class Pow(BinaryBase): """Converts an onnx Pow node into an equivalent Relax expression.""" From 4d4192ff432ae657e2b9d1103c5a1ab554fbd27e Mon Sep 17 00:00:00 2001 From: cchung100m Date: Thu, 14 May 2026 21:34:41 +0800 Subject: [PATCH 03/11] [Relax][ONNX] Remove redundant code --- python/tvm/relax/frontend/onnx/onnx_frontend.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py b/python/tvm/relax/frontend/onnx/onnx_frontend.py index 40df0a2e78af..1e492fac24c3 100644 --- a/python/tvm/relax/frontend/onnx/onnx_frontend.py +++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py @@ -556,9 +556,6 @@ def _impl_v7(cls, bb, inputs, attr, params): if rhs_has_zero: raise ValueError("ONNX Div with integer inputs encountered divisor value 0.") - if rhs_has_zero is False: - return cls.base_impl(bb, inputs, attr, params) - return cls.base_impl(bb, inputs, attr, params) class Pow(BinaryBase): From 9a9567c86ba3291a37377580f8e9672768a7ceff Mon Sep 17 00:00:00 2001 From: cchung100m Date: Thu, 14 May 2026 23:05:13 +0800 Subject: [PATCH 04/11] [Relax][ONNX] Add test case: test_div_integer_constant_zero_divisor_raises_valueerror() --- tests/python/relax/test_frontend_onnx.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/python/relax/test_frontend_onnx.py b/tests/python/relax/test_frontend_onnx.py index 0d1d9f2d7c4b..a0c95fb703c8 100644 --- a/tests/python/relax/test_frontend_onnx.py +++ b/tests/python/relax/test_frontend_onnx.py @@ -591,6 +591,25 @@ def test_binary(op_name: str): verify_binary_scalar(op_name) +def test_div_integer_constant_zero_divisor_raises_valueerror(): + b_init = numpy_helper.from_array(np.array([3, 0, -2, 1], dtype=np.int32), name="b") + node = helper.make_node("Div", ["a", "b"], ["y"]) + graph = helper.make_graph( + [node], + "div_const_zero", + [helper.make_tensor_value_info("a", TensorProto.INT32, [4])], + [helper.make_tensor_value_info("y", TensorProto.INT32, [4])], + initializer=[b_init], + ) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 18)]) + model.ir_version = 9 + + with pytest.raises( + ValueError, match="ONNX Div with integer inputs encountered divisor value 0" + ): + from_onnx(model, opset=18, keep_params_in_input=False) + + @pytest.mark.parametrize("int_mode", [True, False]) def test_mod(int_mode: bool): if int_mode: From a226cbe49aa2cf83ec701c2354bfb5dd31dcaa4d Mon Sep 17 00:00:00 2001 From: cchung100m Date: Thu, 14 May 2026 23:10:33 +0800 Subject: [PATCH 05/11] [Relax][ONNX] Prevent the AttributeError --- python/tvm/relax/frontend/onnx/onnx_frontend.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py b/python/tvm/relax/frontend/onnx/onnx_frontend.py index 1e492fac24c3..45022cf0b307 100644 --- a/python/tvm/relax/frontend/onnx/onnx_frontend.py +++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py @@ -538,13 +538,10 @@ class Div(BinaryBase): @classmethod def _impl_v7(cls, bb, inputs, attr, params): - lhs_dtype = inputs[0].struct_info.dtype - rhs_dtype = inputs[1].struct_info.dtype - try: - lhs_code = DataType(lhs_dtype).type_code - rhs_code = DataType(rhs_dtype).type_code - except (ValueError, TypeError, TVMError): + lhs_code = DataType(inputs[0].struct_info.dtype).type_code + rhs_code = DataType(inputs[1].struct_info.dtype).type_code + except (AttributeError, ValueError, TypeError, TVMError): return cls.base_impl(bb, inputs, attr, params) lhs_is_integer = lhs_code == DataTypeCode.INT or lhs_code == DataTypeCode.UINT From 37f13d89cdf454b6cea6c2fa4c0c99762a16ed45 Mon Sep 17 00:00:00 2001 From: cchung100m Date: Fri, 15 May 2026 08:14:23 +0800 Subject: [PATCH 06/11] [Relax][ONNX] Add test case: test_div_integer_dynamic_nonzero_matches_ort() --- tests/python/relax/test_frontend_onnx.py | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/python/relax/test_frontend_onnx.py b/tests/python/relax/test_frontend_onnx.py index a0c95fb703c8..58458351713c 100644 --- a/tests/python/relax/test_frontend_onnx.py +++ b/tests/python/relax/test_frontend_onnx.py @@ -610,6 +610,32 @@ def test_div_integer_constant_zero_divisor_raises_valueerror(): from_onnx(model, opset=18, keep_params_in_input=False) +def test_div_integer_dynamic_nonzero_matches_ort(): + node = helper.make_node("Div", ["a", "b"], ["y"]) + graph = helper.make_graph( + [node], + "div_dynamic_nonzero", + [ + helper.make_tensor_value_info("a", TensorProto.INT32, [4]), + helper.make_tensor_value_info("b", TensorProto.INT32, [4]), + ], + [helper.make_tensor_value_info("y", TensorProto.INT32, [4])], + ) + model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 18)]) + model.ir_version = 9 + + check_correctness( + model, + inputs={ + "a": np.array([42, 99, -50, 7], dtype=np.int32), + "b": np.array([3, -2, 5, 1], dtype=np.int32), + }, + ir_version=9, + opset=18, + check_dtypes=True, + ) + + @pytest.mark.parametrize("int_mode", [True, False]) def test_mod(int_mode: bool): if int_mode: From cd19e36feced3d8a45101cc57bcd643d316e8964 Mon Sep 17 00:00:00 2001 From: cchung100m Date: Fri, 15 May 2026 08:28:36 +0800 Subject: [PATCH 07/11] [Relax][ONNX] incrementally as the importer improves --- .../relax/test_frontend_onnx_backend.py | 93 +++++++++++++------ 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/tests/python/relax/test_frontend_onnx_backend.py b/tests/python/relax/test_frontend_onnx_backend.py index 3eb63f153598..18ad3b778ec1 100644 --- a/tests/python/relax/test_frontend_onnx_backend.py +++ b/tests/python/relax/test_frontend_onnx_backend.py @@ -80,9 +80,7 @@ def run(self, inputs, **kwargs): if isinstance(output, (tvm.runtime.Tensor, np.ndarray)): return (output.numpy() if hasattr(output, "numpy") else output,) if isinstance(output, (tuple, list)): - return tuple( - o.numpy() if hasattr(o, "numpy") else np.array(o) for o in output - ) + return tuple(o.numpy() if hasattr(o, "numpy") else np.array(o) for o in output) return (np.array(output),) @@ -110,9 +108,7 @@ def prepare(cls, model, device="CPU", **kwargs): func_param_names = [p.name_hint for p in func.params] graph_input_names = [inp.name for inp in model.graph.input] - return TVMRelaxBackendRep( - tvm_model, params, func_param_names, graph_input_names - ) + return TVMRelaxBackendRep(tvm_model, params, func_param_names, graph_input_names) @classmethod def supports_device(cls, device: str) -> bool: @@ -133,32 +129,77 @@ def supports_device(cls, device: str) -> bool: # validated against the ONNX Backend Test Suite. They can be added # incrementally as the importer improves. _INCLUDE_OPS = [ - "abs", "acos", "acosh", "add", "and", "argmax", "argmin", - "averagepool", "bitshift", - "bitwise_and", "bitwise_not", "bitwise_or", "bitwise_xor", - "ceil", "clip", "compress", "concat", - "conv", "cos", "cosh", - "depthtospace", "div", - "einsum", "erf", "exp", - "flatten", "floor", - "gathernd", "gemm", - "globalaveragepool", "globalmaxpool", "greater", "greater_equal", - "hardmax", "hardswish", + "abs", + "acos", + "acosh", + "add", + "and", + "argmax", + "argmin", + "averagepool", + "bitshift", + "bitwise_and", + "bitwise_not", + "bitwise_or", + "bitwise_xor", + "ceil", + "clip", + "compress", + "concat", + "conv", + "cos", + "cosh", + "depthtospace", + "div", + "einsum", + "erf", + "exp", + "flatten", + "floor", + "gathernd", + "gemm", + "globalaveragepool", + "globalmaxpool", + "greater", + "greater_equal", + "hardmax", + "hardswish", "isnan", - "less", "less_equal", "lrn", - "matmul", "matmulinteger", "mean", "min", "mod", "mul", "neg", - "nonzero", "not", + "less", + "less_equal", + "lrn", + "matmul", + "matmulinteger", + "mean", + "min", + "mod", + "mul", + "neg", + "nonzero", + "not", "or", "reciprocal", "round", "scatternd", - "sigmoid", "sign", - "sin", "sinh", "size", "slice", + "sigmoid", + "sign", + "sin", + "sinh", + "size", + "slice", "spacetodepth", - "sqrt", "squeeze", "sub", "sum", - "tan", "tanh", "tile", "transpose", - "unique", "unsqueeze", - "where", "xor", + "sqrt", + "squeeze", + "sub", + "sum", + "tan", + "tanh", + "tile", + "transpose", + "unique", + "unsqueeze", + "where", + "xor", ] for _op in _INCLUDE_OPS: From 67a7a776b4a074cf9997b789d14af93064ee1ba0 Mon Sep 17 00:00:00 2001 From: cchung100m Date: Fri, 15 May 2026 08:29:08 +0800 Subject: [PATCH 08/11] [Relax][ONNX] Add empty line --- python/tvm/relax/frontend/onnx/onnx_frontend.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py b/python/tvm/relax/frontend/onnx/onnx_frontend.py index 45022cf0b307..614024d35547 100644 --- a/python/tvm/relax/frontend/onnx/onnx_frontend.py +++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py @@ -555,6 +555,7 @@ def _impl_v7(cls, bb, inputs, attr, params): return cls.base_impl(bb, inputs, attr, params) + class Pow(BinaryBase): """Converts an onnx Pow node into an equivalent Relax expression.""" From b9ad74dcc918a1d2292d647296908dafc2738f7b Mon Sep 17 00:00:00 2001 From: cchung100m Date: Fri, 15 May 2026 08:31:55 +0800 Subject: [PATCH 09/11] [Relax][ONNX] Fix lint error UP038 --- tests/python/relax/test_frontend_onnx_backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/python/relax/test_frontend_onnx_backend.py b/tests/python/relax/test_frontend_onnx_backend.py index 18ad3b778ec1..301b95f640c4 100644 --- a/tests/python/relax/test_frontend_onnx_backend.py +++ b/tests/python/relax/test_frontend_onnx_backend.py @@ -77,9 +77,9 @@ def run(self, inputs, **kwargs): self._vm.invoke_stateful("main") output = self._vm.get_outputs("main") - if isinstance(output, (tvm.runtime.Tensor, np.ndarray)): + if isinstance(output, tvm.runtime.Tensor | np.ndarray): return (output.numpy() if hasattr(output, "numpy") else output,) - if isinstance(output, (tuple, list)): + if isinstance(output, tuple | list): return tuple(o.numpy() if hasattr(o, "numpy") else np.array(o) for o in output) return (np.array(output),) From db8cc7190dd397dc2a56a2e531bd5efb56454ef7 Mon Sep 17 00:00:00 2001 From: cchung100m Date: Sat, 16 May 2026 22:59:10 +0800 Subject: [PATCH 10/11] [Relax][ONNX] Remove unnecessary helper function --- .../tvm/relax/frontend/onnx/onnx_frontend.py | 17 +++--------- tests/python/relax/test_frontend_onnx.py | 26 ------------------- 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py b/python/tvm/relax/frontend/onnx/onnx_frontend.py index 614024d35547..9e60f5073dab 100644 --- a/python/tvm/relax/frontend/onnx/onnx_frontend.py +++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py @@ -72,18 +72,6 @@ def _relax_dtype_is_floating_point(dtype: str) -> bool: ) -def _const_integer_expr_has_zero(expr: relax.Expr) -> bool | None: - """Return whether a constant integer expression contains a zero value. - - Returns None when expression is not statically inspectable. - """ - - if isinstance(expr, relax.Constant): - return bool(_np.any(expr.data.numpy() == 0)) - - return None - - def get_type(elem_type: str | int) -> str: """Converts onnx integer datatype to numpy datatype""" # If a string was passed instead of a tensor type, it does not need @@ -549,8 +537,9 @@ def _impl_v7(cls, bb, inputs, attr, params): if not (lhs_is_integer and rhs_is_integer): return cls.base_impl(bb, inputs, attr, params) - rhs_has_zero = _const_integer_expr_has_zero(inputs[1]) - if rhs_has_zero: + if isinstance(inputs[1], relax.Constant) and bool( + _np.any(inputs[1].data.numpy() == 0) + ): raise ValueError("ONNX Div with integer inputs encountered divisor value 0.") return cls.base_impl(bb, inputs, attr, params) diff --git a/tests/python/relax/test_frontend_onnx.py b/tests/python/relax/test_frontend_onnx.py index 58458351713c..a0c95fb703c8 100644 --- a/tests/python/relax/test_frontend_onnx.py +++ b/tests/python/relax/test_frontend_onnx.py @@ -610,32 +610,6 @@ def test_div_integer_constant_zero_divisor_raises_valueerror(): from_onnx(model, opset=18, keep_params_in_input=False) -def test_div_integer_dynamic_nonzero_matches_ort(): - node = helper.make_node("Div", ["a", "b"], ["y"]) - graph = helper.make_graph( - [node], - "div_dynamic_nonzero", - [ - helper.make_tensor_value_info("a", TensorProto.INT32, [4]), - helper.make_tensor_value_info("b", TensorProto.INT32, [4]), - ], - [helper.make_tensor_value_info("y", TensorProto.INT32, [4])], - ) - model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 18)]) - model.ir_version = 9 - - check_correctness( - model, - inputs={ - "a": np.array([42, 99, -50, 7], dtype=np.int32), - "b": np.array([3, -2, 5, 1], dtype=np.int32), - }, - ir_version=9, - opset=18, - check_dtypes=True, - ) - - @pytest.mark.parametrize("int_mode", [True, False]) def test_mod(int_mode: bool): if int_mode: From 4bf696409b4b87d384169395a64978cc13343c56 Mon Sep 17 00:00:00 2001 From: cchung100m Date: Sat, 16 May 2026 23:00:47 +0800 Subject: [PATCH 11/11] [Relax][ONNX] Fix lint error --- python/tvm/relax/frontend/onnx/onnx_frontend.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py b/python/tvm/relax/frontend/onnx/onnx_frontend.py index 9e60f5073dab..b42a3a4d9c86 100644 --- a/python/tvm/relax/frontend/onnx/onnx_frontend.py +++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py @@ -537,9 +537,7 @@ def _impl_v7(cls, bb, inputs, attr, params): if not (lhs_is_integer and rhs_is_integer): return cls.base_impl(bb, inputs, attr, params) - if isinstance(inputs[1], relax.Constant) and bool( - _np.any(inputs[1].data.numpy() == 0) - ): + if isinstance(inputs[1], relax.Constant) and bool(_np.any(inputs[1].data.numpy() == 0)): raise ValueError("ONNX Div with integer inputs encountered divisor value 0.") return cls.base_impl(bb, inputs, attr, params)