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 396dd34946 [Fix][Relax][ONNX] Preserve ONNX Squeeze axes attribute for 
opset < 13 (#19966)
396dd34946 is described below

commit 396dd349460ffbfd1e46d9308ca7d67997d0ad23
Author: Kryptonite <[email protected]>
AuthorDate: Sat Jul 18 07:14:55 2026 +0300

    [Fix][Relax][ONNX] Preserve ONNX Squeeze axes attribute for opset < 13 
(#19966)
    
    ## Summary
    Before opset 13, ONNX `Squeeze` specifies `axes` as a node attribute
    rather than a tensor input. The Relax ONNX importer only implemented
    `_impl_v13`, which reads axes from the second input, so for opset < 13
    models, the attribute was silently ignored (`axis` defaulted to `None`)
    and the importer squeezed every size-1 dimension instead of only the
    requested one. This produced tensors with the wrong rank, breaking
    downstream ops like `Transpose` whose `perm` no longer matched the
    input's actual rank.
    
    Added `_impl_v1` to read `axes` from the node attribute for opset < 13,
    and factored the existing squeeze logic into a shared `_squeeze` helper
    used by both `_impl_v1` and `_impl_v13`.
    
    ## Test plan
    - Added `test_squeeze_axes_attribute` to
    `tests/python/relax/test_frontend_onnx.py`, covering an opset-11
    `Squeeze` node with `axes` as an attribute.
    - Ran `pytest tests/python/relax/test_frontend_onnx.py -k squeeze`. All
    21 tests pass.
    - Verified against the real-world model that triggers this bug,
    
[PaddlePaddle/PP-OCRv6_tiny_rec_onnx](https://huggingface.co/PaddlePaddle/PP-OCRv6_tiny_rec_onnx)
    (opset 11, uses attribute-based `Squeeze`): import fails on `main` with
    `Transpose: number of axes in perm attribute (3) must equal the number
    of input tensor dimensions (-1)`, and succeeds with this fix.
    
    ## Real-world reproduction
    
    ```python
    import urllib.request
    
    import onnx
    
    from tvm.relax.frontend.onnx import from_onnx
    
    # PaddlePaddle/PP-OCRv6_tiny_rec_onnx (opset 11, uses attribute-based 
Squeeze)
    url = 
"https://huggingface.co/PaddlePaddle/PP-OCRv6_tiny_rec_onnx/resolve/main/inference.onnx";
    path = "pp_ocrv6_tiny_rec.onnx"
    urllib.request.urlretrieve(url, path)
    
    model = onnx.load(path)
    print("opset:", [(o.domain, o.version) for o in model.opset_import])
    
    for node in model.graph.node:
        if node.op_type == "Squeeze":
            axes_attr = [a for a in node.attribute if a.name == "axes"]
            print(node.name, "inputs=", list(node.input), "axes_attr=", 
axes_attr)
    
    # Fails on main with:
    #   ValueError: Transpose: number of axes in perm attribute (3) must equal 
the number of input tensor dimensions (-1)
    # Succeeds with this fix.
    mod = from_onnx(model)
    print("Import succeeded")
    ```
    
    Fixes (partially) #19965. The shape-Gather and dynamic-TopK issues
    reported in that issue are separate and not addressed here.
---
 python/tvm/relax/frontend/onnx/onnx_frontend.py |  9 +++++++
 tests/python/relax/test_frontend_onnx.py        | 36 +++++++++++++++++++++++++
 2 files changed, 45 insertions(+)

diff --git a/python/tvm/relax/frontend/onnx/onnx_frontend.py 
b/python/tvm/relax/frontend/onnx/onnx_frontend.py
index eaad127524..9121018bb9 100644
--- a/python/tvm/relax/frontend/onnx/onnx_frontend.py
+++ b/python/tvm/relax/frontend/onnx/onnx_frontend.py
@@ -2087,13 +2087,22 @@ class CumSum(OnnxOpConverter):
 class Squeeze(OnnxOpConverter):
     """Converts an onnx Squeeze node into an equivalent Relax expression."""
 
+    @classmethod
+    def _impl_v1(cls, bb, inputs, attr, params):
+        # Prior to opset 13, axes is provided as an attribute rather than an 
input.
+        axes = attr.get("axes", None)
+        return cls._squeeze(bb, inputs[0], axes)
+
     @classmethod
     def _impl_v13(cls, bb, inputs, attr, params):
         data = inputs[0]
         axis = get_constant(inputs[1], params)
         if isinstance(axis, relax.Constant):
             axis = tuple([int(x) for x in axis.data.numpy()])
+        return cls._squeeze(bb, data, axis)
 
+    @classmethod
+    def _squeeze(cls, bb, data, axis):
         # If data is constant, perform computation directly.
         if isinstance(data, relax.Constant):
             if isinstance(axis, tuple | type(None)):
diff --git a/tests/python/relax/test_frontend_onnx.py 
b/tests/python/relax/test_frontend_onnx.py
index db9bf18a81..4a5c2f778d 100644
--- a/tests/python/relax/test_frontend_onnx.py
+++ b/tests/python/relax/test_frontend_onnx.py
@@ -3687,6 +3687,42 @@ def test_squeeze():
     verify_squeeze(None, ExpectedSqueezeAll)
 
 
+def test_squeeze_axes_attribute():
+    # Prior to opset 13, ONNX Squeeze takes `axes` as an attribute rather than 
an input.
+    squeeze_node = helper.make_node("Squeeze", ["x"], ["y"], axes=[0, 2])
+    shape = [1, 32, 1, 32]
+
+    graph = helper.make_graph(
+        [squeeze_node],
+        "squeeze_axes_attribute_test",
+        inputs=[
+            helper.make_tensor_value_info("x", TensorProto.FLOAT, shape),
+        ],
+        outputs=[helper.make_tensor_value_info("y", TensorProto.FLOAT, [32, 
32])],
+    )
+
+    model = helper.make_model(
+        graph,
+        producer_name="squeeze_axes_attribute_test",
+        opset_imports=[helper.make_opsetid("", 11)],
+    )
+    tvm_model = from_onnx(model, opset=11, keep_params_in_input=True)
+
+    @I.ir_module
+    class Expected:
+        @R.function
+        def main(x: R.Tensor((1, 32, 1, 32), dtype="float32")) -> R.Tensor(
+            (32, 32), dtype="float32"
+        ):
+            R.func_attr({"num_input": 1})
+            with R.dataflow():
+                gv: R.Tensor((32, 32), dtype="float32") = R.squeeze(x, 
axis=[0, 2])
+                R.output(gv)
+            return gv
+
+    tvm.ir.assert_structural_equal(tvm_model, Expected)
+
+
 def test_squeeze_constant():
     def verify_squeeze_constant(axis, expected):
         shape = [1, 2, 1, 3]

Reply via email to