This is an automated email from the ASF dual-hosted git repository.

cbalint13 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 626a3537e6 [Relax][ONNX] Accept 1-D scalar inputs in NonMaxSuppression 
(#19843)
626a3537e6 is described below

commit 626a3537e64b3b00ec403ca3ea4c11e93ce45442
Author: Guan-Ming (Wesley) Chiu <[email protected]>
AuthorDate: Sun Jun 21 00:21:39 2026 +0800

    [Relax][ONNX] Accept 1-D scalar inputs in NonMaxSuppression (#19843)
    
    ## Related Issue
    
    closes #19693
    
    ## Why
    
    NumPy 2.x raises TypeError on int(np.array([3])), so importing an ONNX
    NonMaxSuppression whose scalar params are 1-D single-element tensors
    (shape[1], common from exporters) crashed.
    
    ## How
    
    - Cast the constant max_output_boxes_per_class / iou_threshold /
    score_threshold via .numpy().item(), which accepts both 0-D and
    1-element tensors (matching the existing param-Var path).
    - Add an import test feeding 1-D scalar constants (the default folding
    path that the existing test_nms misses).
---
 python/tvm/relax/frontend/onnx/onnx_frontend.py | 26 +++++++++++++++++++------
 tests/python/relax/test_frontend_onnx.py        | 26 +++++++++++++++++++++++++
 2 files changed, 46 insertions(+), 6 deletions(-)

diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py 
b/python/tvm/relax/frontend/onnx/onnx_frontend.py
index cdb213f10d..430e7cef7a 100644
--- a/python/tvm/relax/frontend/onnx/onnx_frontend.py
+++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py
@@ -4884,7 +4884,7 @@ class NonMaxSuppression(OnnxOpConverter):
         if max_output_boxes_per_class is not None and isinstance(
             max_output_boxes_per_class, relax.Constant
         ):
-            max_output_boxes_per_class = 
int(max_output_boxes_per_class.data.numpy())
+            max_output_boxes_per_class = 
int(max_output_boxes_per_class.data.numpy().item())
         elif max_output_boxes_per_class is not None and isinstance(
             max_output_boxes_per_class, relax.Var
         ):
@@ -4898,12 +4898,19 @@ class NonMaxSuppression(OnnxOpConverter):
             max_output_boxes_per_class = 0  # Default value
 
         if iou_threshold is not None and isinstance(iou_threshold, 
relax.Constant):
-            iou_threshold = float(iou_threshold.data.numpy())
+            iou_threshold = float(iou_threshold.data.numpy().item())
+        elif iou_threshold is not None and isinstance(iou_threshold, 
relax.Var):
+            var_name = iou_threshold.name_hint
+            if var_name in params[1]:
+                _, param_value = params[1][var_name]
+                iou_threshold = float(param_value.numpy().item())
+            else:
+                iou_threshold = 0.5  # Default value
         else:
             iou_threshold = 0.5  # Default value
 
         if score_threshold is not None and isinstance(score_threshold, 
relax.Constant):
-            score_threshold = float(score_threshold.data.numpy())
+            score_threshold = float(score_threshold.data.numpy().item())
         elif score_threshold is not None and isinstance(score_threshold, 
relax.Var):
             var_name = score_threshold.name_hint
             if var_name in params[1]:
@@ -4973,7 +4980,7 @@ class AllClassNMS(OnnxOpConverter):
         if max_output_boxes_per_class is not None and isinstance(
             max_output_boxes_per_class, relax.Constant
         ):
-            max_output_boxes_per_class = 
int(max_output_boxes_per_class.data.numpy())
+            max_output_boxes_per_class = 
int(max_output_boxes_per_class.data.numpy().item())
         elif max_output_boxes_per_class is not None and isinstance(
             max_output_boxes_per_class, relax.Var
         ):
@@ -4987,12 +4994,19 @@ class AllClassNMS(OnnxOpConverter):
             max_output_boxes_per_class = 0  # Default value
 
         if iou_threshold is not None and isinstance(iou_threshold, 
relax.Constant):
-            iou_threshold = float(iou_threshold.data.numpy())
+            iou_threshold = float(iou_threshold.data.numpy().item())
+        elif iou_threshold is not None and isinstance(iou_threshold, 
relax.Var):
+            var_name = iou_threshold.name_hint
+            if var_name in params[1]:
+                _, param_value = params[1][var_name]
+                iou_threshold = float(param_value.numpy().item())
+            else:
+                iou_threshold = 0.5  # Default value
         else:
             iou_threshold = 0.5  # Default value
 
         if score_threshold is not None and isinstance(score_threshold, 
relax.Constant):
-            score_threshold = float(score_threshold.data.numpy())
+            score_threshold = float(score_threshold.data.numpy().item())
         elif score_threshold is not None and isinstance(score_threshold, 
relax.Var):
             var_name = score_threshold.name_hint
             if var_name in params[1]:
diff --git a/tests/python/relax/test_frontend_onnx.py 
b/tests/python/relax/test_frontend_onnx.py
index 6e9d4c9d95..58004b80ad 100644
--- a/tests/python/relax/test_frontend_onnx.py
+++ b/tests/python/relax/test_frontend_onnx.py
@@ -5120,6 +5120,32 @@ def test_nms():
         )
 
 
+def test_nms_scalar_shape1_constants():
+    """Scalar params given as 1-D single-element constants must import (NumPy 
2.x cast)."""
+    nms_node = helper.make_node(
+        "NonMaxSuppression",
+        ["boxes", "scores", "max_output_boxes_per_class", "iou_threshold", 
"score_threshold"],
+        ["selected_indices"],
+    )
+    graph = helper.make_graph(
+        [nms_node],
+        "nms_scalar_shape1",
+        inputs=[
+            helper.make_tensor_value_info("boxes", TensorProto.FLOAT, [1, 5, 
4]),
+            helper.make_tensor_value_info("scores", TensorProto.FLOAT, [1, 1, 
5]),
+        ],
+        initializer=[
+            helper.make_tensor("max_output_boxes_per_class", 
TensorProto.INT64, [1], [3]),
+            helper.make_tensor("iou_threshold", TensorProto.FLOAT, [1], [0.5]),
+            helper.make_tensor("score_threshold", TensorProto.FLOAT, [1], 
[0.0]),
+        ],
+        outputs=[helper.make_tensor_value_info("selected_indices", 
TensorProto.INT64, [0, 3])],
+    )
+    model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 
18)])
+    # Default import folds initializers to relax.Constant, exercising the 
scalar-cast path.
+    from_onnx(model)
+
+
 @pytest.mark.parametrize("with_explicit_max", [False, True])
 def test_nms_max_output_boxes_per_class_zero(with_explicit_max: bool):
     """ONNX default for max_output_boxes_per_class is 0, yielding empty 
output."""

Reply via email to