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 5d6c972674 [BugFix][Relax] Fix ONNX `get_converter` index underflow 
for old opsets (#19376)
5d6c972674 is described below

commit 5d6c9726748b0a5c0f19addbc183f18e994c1f6a
Author: Soowon Jeong <[email protected]>
AuthorDate: Fri Apr 10 07:31:19 2026 +0900

    [BugFix][Relax] Fix ONNX `get_converter` index underflow for old opsets 
(#19376)
    
    ## Description
    
    `OnnxOpConverter.get_converter()` has an index underflow bug: when a
    model's opset is below all implemented converter versions, the selection
    index wraps to -1 (Python negative indexing), silently picking the
    **latest** implementation instead of the earliest.
    
    ### Root cause
    
    ```python
    versions = sorted(versions + [opset])
    version = versions[max([i for i, v in enumerate(versions) if v == opset]) - 
1]
    #                                                                          
^^^
    #         opset=11, versions=[11, 13, 18] → index=0 → 0-1=-1 → 
versions[-1]=18
    ```
    
    ### Impact
    
    14 operators affected. For opset 11-12 models, these ops silently
    produce wrong results:
    
    | Operator | Impl versions | Opset 11 dispatches to | Correct |
    |----------|:---:|:---:|:---:|
    | ReduceMean | [13, 18] | **v18** | v13 |
    | ReduceL1/L2 | [13, 18] | **v18** | v13 |
    | ReduceLogSum | [13, 18] | **v18** | v13 |
    | ReduceLogSumExp | [13, 18] | **v18** | v13 |
    | ReduceProd | [13, 18] | **v18** | v13 |
    | ReduceSumSquare | [13, 18] | **v18** | v13 |
    | ReduceMax/Min | [11, 18] | correct | correct |
    | Pad, Scatter, ScatterND, RoiAlign | various | **wrong** | — |
    
    The v18 implementations read `axes` from **inputs**, but opset 11-12
    passes `axes` as **attributes** — so axes becomes None and the op
    reduces over all dimensions.
    
    Example: `ReduceMean(axes=[2,3])` on shape `(2,3,4,4)`:
    - Before fix: output shape `(1,1,1,1)` (wrong, all-axis reduction)
    - After fix: output shape `(2,3,1,1)` (correct)
    
    ### Fix
    
    Replace the index arithmetic with an explicit filter:
    ```python
    candidates = [v for v in impl_versions if v <= opset]
    version = max(candidates) if candidates else impl_versions[0]
    ```
    
    ### Testing
    
    Added opset 11 test cases for 7 Reduce operators in
    `test_all_reduce_funcs_axes_attr`. All 20 new tests pass. Existing tests
    unaffected (570 pass, 14 pre-existing failures in axes_input/topk
    unrelated to this change).
    
    ```bash
    pytest tests/python/relax/test_frontend_onnx.py -k "axes_attr and 11" -v
    ```
---
 python/tvm/relax/frontend/onnx/onnx_frontend.py |  8 +++++---
 tests/python/relax/test_frontend_onnx.py        | 14 ++++++++++++--
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py 
b/python/tvm/relax/frontend/onnx/onnx_frontend.py
index 940022de41..2707f6ff1c 100644
--- a/python/tvm/relax/frontend/onnx/onnx_frontend.py
+++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py
@@ -302,9 +302,11 @@ class OnnxOpConverter:
         converter, which should be `_impl_vx`. Number x is the biggest
             number smaller than or equal to opset belongs to all support 
versions.
         """
-        versions = [int(d.replace("_impl_v", "")) for d in dir(cls) if 
"_impl_v" in d]
-        versions = sorted(versions + [opset])
-        version = versions[max([i for i, v in enumerate(versions) if v == 
opset]) - 1]
+        impl_versions = sorted(int(d.replace("_impl_v", "")) for d in dir(cls) 
if "_impl_v" in d)
+        # Select the largest implemented version that is <= opset.
+        # If opset is below all implementations, fall back to the smallest.
+        candidates = [v for v in impl_versions if v <= opset]
+        version = max(candidates) if candidates else impl_versions[0]
         if hasattr(cls, f"_impl_v{version}"):
             return getattr(cls, f"_impl_v{version}")
         raise NotImplementedError(f"opset version {version} of {cls.__name__} 
not implemented")
diff --git a/tests/python/relax/test_frontend_onnx.py 
b/tests/python/relax/test_frontend_onnx.py
index 534161ce6d..db7c3da25a 100644
--- a/tests/python/relax/test_frontend_onnx.py
+++ b/tests/python/relax/test_frontend_onnx.py
@@ -2123,6 +2123,15 @@ def create_reduce_test_parameters_axes_attr():
         output.append(("ReduceLogSumExp", value, 13))
         output.append(("ReduceL1", value, 13))
         output.append(("ReduceL2", value, 13))
+        # Opset 11-12 axes-as-attr: verifies get_converter does not
+        # underflow to the v18 (axes-as-input) implementation.
+        output.append(("ReduceMean", value, 11))
+        output.append(("ReduceProd", value, 11))
+        output.append(("ReduceSumSquare", value, 11))
+        output.append(("ReduceLogSum", value, 11))
+        output.append(("ReduceLogSumExp", value, 11))
+        output.append(("ReduceL1", value, 11))
+        output.append(("ReduceL2", value, 11))
     return output
 
 
@@ -3837,7 +3846,7 @@ def test_concat_from_sequence_invalid_new_axis():
         outputs=[helper.make_tensor_value_info("output", TensorProto.FLOAT, 
[32, 8])],
     )
     model = helper.make_model(graph, 
producer_name="test_concat_from_sequence_invalid_new_axis")
-    
+
     with pytest.raises(ValueError, match="ConcatFromSequence only supports 
new_axis in"):
         from_onnx(model, opset=11)
 
@@ -5528,7 +5537,7 @@ def test_split_to_sequence_keepdims_0(axis: int):
 
     split_to_seq_node = helper.make_node(
         "SplitToSequence",
-        ["data"],          # no split input — keepdims applies here
+        ["data"],  # no split input — keepdims applies here
         ["output"],
         axis=axis,
         keepdims=0,
@@ -5568,6 +5577,7 @@ def 
test_split_to_sequence_keepdims_ignored_when_split_provided():
     model.ir_version = 8
     # Cannot use check_correctness here as ORT deviates from the spec for this 
case
     from tvm.relax.frontend.onnx import from_onnx
+
     tvm_model = from_onnx(model, opset=11, keep_params_in_input=True)
     assert tvm_model is not None
 

Reply via email to