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]

Reply via email to