siyiweigeHEW opened a new issue, #20185: URL: https://github.com/apache/tvm/issues/20185
### Expected behavior Per the [ONNX Squeeze spec](https://onnx.ai/onnx/operators/onnx__Squeeze.html) (opset 11 and later): > Remove single-dimensional entries from the shape of a tensor. ... If an axis is selected with shape entry not equal to one, an error is raised. So for a valid ONNX model with `data` of shape `(2, 3)` and `axes = [0]` (axis 0 has size 2, not 1), the runtime must **raise an error**. onnxruntime rejects it: ``` [ONNXRuntimeError] : 1 : FAIL : Node () Op (Squeeze) [ShapeInferenceError] Dimension of input 0 must be 1 instead of 2 ``` and `onnx.reference` raises `ValueError: cannot select an axis to squeeze out which has size not equal to one`. The model passes `onnx.checker`. ### Actual behavior `tvm.relax.frontend.onnx.from_onnx` silently ignores the offending axis and returns the input **unchanged** — no error is raised, and the model produces a result that the ONNX spec forbids: ``` m onnxruntime -> ERROR (per spec) m onnx.reference-> ERROR (per spec) m TVM -> shape (2, 3), values [0, 1, 2, 3, 4, 5] # silently accepted ``` Root cause: the converter (`Squeeze._impl_v13`, `python/tvm/relax/frontend/onnx/onnx_frontend.py:1456`) delegates the non-constant path to `relax.op.squeeze(data, axis)`, and the underlying op shape inference (`src/relax/op/tensor/manipulate.cc:1269-1284`) explicitly implements the PyTorch behavior instead of the ONNX contract: ```cpp // If a dimension is not 1, silently skip it (no-op), matching PyTorch behavior. if ((int_len != nullptr && int_len->value == 1) || int_len == nullptr) { axis_removal_mask[axes[i]] = true; } ``` The op even carries a `Todo(relax-team): revisit here for better check on if the axis being squeezed has length 1.` The Relax op is shared by multiple frontends, but the ONNX frontend must enforce the ONNX spec (raise on a non-unit axis), just as it already does on the **constant path**, where `np.squeeze` correctly raises `ValueError` and matches onnxruntime/onnx.reference. ### Environment - OS: Linux - TVM: v0.24.dev0 (commit `262c6d2e04`, built 2026-02) - Python: 3.11 - onnx: 1.20.1 - onnxruntime: 1.24.1 ### Steps to reproduce ```python """Repro: ONNX Squeeze with axes selecting a non-unit dimension is silently accepted by the TVM relax frontend instead of raising an error.""" import numpy as np import onnx, onnxruntime from onnx import helper, TensorProto from onnx.reference import ReferenceEvaluator from tvm.relax.frontend.onnx import from_onnx def build(data_shape, axes, opset=13): inits = [helper.make_tensor( "axes", TensorProto.INT64, [len(axes)], list(axes))] node = helper.make_node("Squeeze", ["data", "axes"], ["Y"]) graph = helper.make_graph( [node], "g", [helper.make_tensor_value_info("data", TensorProto.FLOAT, list(data_shape))], [helper.make_tensor_value_info("Y", TensorProto.FLOAT, list(data_shape))], inits) model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", opset)]) model.ir_version = 8 return model m = build((2, 3), [0]) # axis 0 has size 2 (not 1) -> spec requires error onnx.checker.check_model(m) # valid ONNX model x = np.arange(6, dtype="float32").reshape(2, 3) try: onnxruntime.InferenceSession(m.SerializeToString(), providers=["CPUExecutionProvider"]).run(None, {"data": x}) print("onnxruntime: OK (unexpected)") except Exception as e: print("onnxruntime:", e) try: ReferenceEvaluator(m).run(None, {"data": x}) print("onnx.reference: OK (unexpected)") except Exception as e: print("onnx.reference:", type(e).__name__, e) try: mod = from_onnx(m, shape_dict={"data": [2, 3]}) print("TVM: imported successfully (BUG: no error raised)") except Exception as e: print("TVM:", type(e).__name__, e) ``` Actual output: ``` onnxruntime: [ONNXRuntimeError] : 1 : FAIL : Node () Op (Squeeze) [ShapeInferenceError] Dimension of input 0 must be 1 instead of 2 onnx.reference: ValueError cannot select an axis to squeeze out which has size not equal to one TVM: imported successfully (BUG: no error raised) ``` The same divergence reproduces for negative axes (e.g. `(2, 3)` with `axes=[-1]`) and when the axis list mixes unit and non-unit dims (e.g. `(2, 3, 1)` with `axes=[0, 2]`). The constant-data path is unaffected (it raises the same `ValueError` as `onnx.reference`). ### Note (already fixed upstream) For opset ≤ 11 models, where `axes` is an **attribute** rather than an input, the `_impl_v13` converter at `262c6d2e04` never read `attr["axes"]`, so it squeezed *all* unit dims and produced wrong output shapes (e.g. `(1,2,1,3)` with `axes=[0]` → `(2,3)` instead of `(2,1,3)`). This was already fixed upstream by the added `Squeeze._impl_v1` (reads the attribute). The remaining issue described above (non-unit axis silently accepted) is still present in the current upstream `relax.squeeze`. ### Triage * needs-triage * bug * relax * frontend/onnx -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
