Skip to content

[Fix][Relax][Frontend][ONNX] Squeeze: raise an error when axes select a non-unit dimension - #20211

Closed
siyiweigeHEW wants to merge 2 commits into
apache:mainfrom
siyiweigeHEW:fix/relax-onnx-squeeze-axis-validation
Closed

siyiweigeHEW wants to merge 2 commits into
apache:mainfrom
siyiweigeHEW:fix/relax-onnx-squeeze-axis-validation

Conversation

@siyiweigeHEW

Copy link
Copy Markdown
Contributor

Fixes: #20185

Summary

The Relax ONNX frontend silently accepted a Squeeze whose axes select a
dimension with size not equal to 1, returning the input unchanged with no
error. The ONNX Squeeze spec
(opset 11+ and earlier) requires the opposite: "If an axis is selected with shape
entry not equal to one, an error is raised."
Both onnxruntime and
onnx.reference reject such models; TVM diverged. The converter now validates
statically-known axes against the input shape and raises a ValueError before
delegating to relax.op.squeeze.

Root cause

Squeeze._squeeze (python/tvm/relax/frontend/onnx/onnx_frontend.py) delegates
the non-constant path directly to relax.op.squeeze(data, list(axis)). The
underlying op's shape inference
(src/relax/op/tensor/manipulate.cc:1269-1284) deliberately implements PyTorch
semantics — "If a dimension is not 1, silently skip it (no-op), matching PyTorch
behavior"
(and carries a Todo(relax-team): revisit here for better check on if the axis being squeezed has length 1). So a non-unit selected axis was silently
ignored and the input returned unchanged. The ONNX frontend performed no
dim == 1 check — unlike its constant path, where np.squeeze already raises
the same ValueError as onnx.reference.

# Before: silently accepted e.g. data(2,3) with axes=[0]
if isinstance(axis, tuple):
    return relax.op.squeeze(data, list(axis))

Fix

Validate before delegating: when axis is statically known (a tuple) and the
input shape is statically known, every selected (normalized, deduplicated,
bounds-checked) axis must have size 1:

if isinstance(axis, tuple):
    cls._validate_axes_unit_dims(data, axis)
    return relax.op.squeeze(data, list(axis))

_validate_axes_unit_dims reuses _normalize_constant_axes (negative-axis
normalization, bounds and uniqueness checks) and raises the same ValueError
message as np.squeeze/onnx.reference when a known dim is != 1. Dimensions
that are only known at runtime cannot be validated at import time and keep the
previous behavior — the shared relax.op.squeeze is intentionally left unchanged
because other frontends (e.g. PyTorch) rely on its no-op semantics.

Validation

Differential test: Relax (build + VirtualMachine) vs onnxruntime on the same
model, cross-checked with onnx.reference (which is the reference for the
rejection semantics and for fp64), across valid, invalid-axis, constant, and
out-of-bounds cases.

Case onnxruntime onnx.reference TVM (after fix) Result
(2,3) axes=[0] (non-unit) ERR ERR ValueError OK
(1,2,3) axes=[1] (non-unit) ERR ERR ValueError OK
(1,2,1,3) axes=[1] (only non-unit) ERR ERR ValueError OK
(2,3,1) axes=[0,2] (mixed) ERR ERR ValueError OK
(2,3) axes=[-1] (neg, non-unit) ERR ERR ValueError OK
(1,2,1,3) axes=[0,2] (valid, regression) (2,3) (2,3) (2,3) max|diff|=0 OK
(1,2,1) axes=[-1] (valid neg, regression) (1,2) (1,2) (1,2) max|diff|=0 OK
(1,) axes=[0] (scalar output, regression) () () () max|diff|=0 OK

Totals: 25/25 OK (pre-fix: 6/6 legal models diverged — onnxruntime/ref
rejected, TVM silently returned the input unchanged). Valid axes (incl. negative
axes, scalar output, opset 13/21), the constant-data path, and out-of-bounds
rejection are unchanged. Run:

TVM_LIBRARY_PATH=<tvm23>/build PYTHONPATH=<patched 262c6d2e04 source>/python \
  python results/TVM/deepseek-v4-flash/prove_hum/onnx_Squeeze/5修复_差分验证.py

New in-tree tests cover the raise behavior for both opset 11 (axes attribute) and
opset 13 (axes input), the constant path, and numeric correctness of valid axes
vs onnxruntime.

Files changed

  • python/tvm/relax/frontend/onnx/onnx_frontend.py — Squeeze._squeeze: validate
    that every selected axis has a size-1 dimension (ONNX spec) before delegating to
    relax.op.squeeze.
  • tests/python/relax/test_frontend_onnx.py — add test_squeeze_non_unit_axis_raises,
    test_squeeze_constant_non_unit_axis_raises, test_squeeze_valid_axes_correctness.

…-unit dimension

ONNX Squeeze spec requires an error to be raised when an axis selects a shape
entry not equal to one. The relax ONNX frontend delegated to relax.op.squeeze,
whose shape inference silently skips non-unit dims (PyTorch semantics) and
returned the input unchanged, diverging from onnxruntime and onnx.reference.

Validate statically-known axes against the input shape before delegating and
raise a ValueError (same message as np.squeeze / onnx.reference) for non-unit
dims. Dimensions only known at runtime keep the previous behavior.
@OmarAzizi

Copy link
Copy Markdown
Contributor

Fixed in #20188, sorry I didn't know you were working on it

@tlopex

tlopex commented Aug 29, 2026

Copy link
Copy Markdown
Member

Closed as already fixed. Thanks @siyiweigeHEW @OmarAzizi

@tlopex tlopex closed this Aug 29, 2026
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.

[Bug][Relax][Frontend][ONNX] Squeeze silently ignores axes that select non-unit dimensions instead of raising an error (violates ONNX spec)

3 participants