This is an automated email from the ASF dual-hosted git repository.
mshr-h 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 bc1a904ec1 [Relax][ONNX] Prevent `Div` divide-by-zero crashes (#19566)
bc1a904ec1 is described below
commit bc1a904ec1ad89454ee6577d66cde1268b8f6bc8
Author: Neo Chien <[email protected]>
AuthorDate: Mon May 18 10:50:35 2026 +0800
[Relax][ONNX] Prevent `Div` divide-by-zero crashes (#19566)
Hi Committers,
This PR is trying to fix issues #19541. Any suggestions would be
appreciated if you are available.
### Root cause:
The ONNX `Div` path in the Relax frontend did not separate two
integer-divisor cases: constant zero divisors and dynamic/unknown
divisors. As a result, constant integer zero divisors were not rejected
during import, and dynamic integer divisors could reach runtime without
a guard. When the divisor became zero at runtime, execution could
trigger SIGFPE and terminate the process instead of raising a controlled
error.
### Solution:
This PR applies a minimal, targeted fix in the ONNX frontend `Div`
conversion path. It introduces: import-time validation for constant
integer divisors containing zero, raising ValueError early.
---------
Co-authored-by: cchung100m <[email protected]>
---
python/tvm/relax/frontend/onnx/onnx_frontend.py | 14 ++++
tests/python/relax/test_frontend_onnx.py | 19 +++++
tests/python/relax/test_frontend_onnx_backend.py | 97 +++++++++++++++++-------
3 files changed, 102 insertions(+), 28 deletions(-)
diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py
b/python/tvm/relax/frontend/onnx/onnx_frontend.py
index 3f25d2ff3b..b42a3a4d9c 100644
--- a/python/tvm/relax/frontend/onnx/onnx_frontend.py
+++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py
@@ -526,6 +526,20 @@ class Div(BinaryBase):
@classmethod
def _impl_v7(cls, bb, inputs, attr, params):
+ try:
+ 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
+ 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)
+
+ 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 151ec35e89..26daeff46d 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:
diff --git a/tests/python/relax/test_frontend_onnx_backend.py
b/tests/python/relax/test_frontend_onnx_backend.py
index 3eb63f1535..301b95f640 100644
--- a/tests/python/relax/test_frontend_onnx_backend.py
+++ b/tests/python/relax/test_frontend_onnx_backend.py
@@ -77,12 +77,10 @@ class TVMRelaxBackendRep(BackendRep):
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)):
- return tuple(
- o.numpy() if hasattr(o, "numpy") else np.array(o) for o in
output
- )
+ 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),)
@@ -110,9 +108,7 @@ class TVMRelaxBackend(Backend):
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 @@ backend_test =
onnx.backend.test.BackendTest(TVMRelaxBackend, __name__)
# 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: