Skip to content

[Fix] Preserve ONNX Squeeze axes attribute for opset < 13#19966

Open
OmarAzizi wants to merge 1 commit into
apache:mainfrom
OmarAzizi:fix-onnx-squeeze-axes-attribute
Open

[Fix] Preserve ONNX Squeeze axes attribute for opset < 13#19966
OmarAzizi wants to merge 1 commit into
apache:mainfrom
OmarAzizi:fix-onnx-squeeze-axes-attribute

Conversation

@OmarAzizi

Copy link
Copy Markdown
Contributor

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 (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

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for ONNX Squeeze operators prior to opset 13 (where axes is an attribute rather than an input) by introducing _impl_v1 and refactoring the squeeze logic into a helper method _squeeze. A test case is also added to verify this behavior. The reviewer suggests simplifying _impl_v1 by removing the redundant conversion of axes to a tuple of integers, as it is already parsed as a tuple of integers by _parse_attr.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1942 to +1944
axes = attr.get("axes", None)
axis = tuple(int(x) for x in axes) if axes is not None else None
return cls._squeeze(bb, inputs[0], axis)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since the axes attribute is already parsed as a tuple of integers (or None if not present) by _parse_attr, the explicit conversion tuple(int(x) for x in axes) is redundant. We can directly pass axes to _squeeze.

Suggested change
axes = attr.get("axes", None)
axis = tuple(int(x) for x in axes) if axes is not None else None
return cls._squeeze(bb, inputs[0], axis)
axes = attr.get("axes", None)
return cls._squeeze(bb, inputs[0], axes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant