siyiweigeHEW opened a new issue, #20151:
URL: https://github.com/apache/tvm/issues/20151
### Expected behavior
A valid ONNX `Reshape` node should follow the ONNX spec's 0-dimension
semantics:
- With default `allowzero=0` (and opset < 14), a `0` in `shape` means "copy
the corresponding dimension from the input". For constant `data` with shape
`(2, 3)` and `shape = [0, 3]`, the output must be `(2, 3)`, unchanged.
- With `allowzero=1` (opset ≥ 14), a `0` in `shape` is a literal zero
dimension. For input `(2, 0)` and `shape = [0, 2]`, the output must be `(0, 2)`.
Both models pass `onnx.checker` and run correctly in onnxruntime and
`onnx.reference`.
### Actual behavior
`tvm.relax.frontend.onnx.from_onnx` mishandles both cases:
1. **Constant-fold path** (`Reshape._impl_v13`,
`python/tvm/relax/frontend/onnx/onnx_frontend.py:979-981`): when both `data`
and `shape` are constants, the frontend calls `np.reshape(data, shape)`, which
treats `0` as a literal zero element (numpy semantics) instead of "copy dim
from input" (ONNX default). A fully-constant model `data=(2,3)`, `shape=[0,3]`
(default `allowzero=0`) — which onnxruntime and `onnx.reference` both accept
and return `(2,3)` — raises:
```
ValueError: cannot reshape array of size 6 into shape (0,3)
```
2. **`allowzero` attribute ignored** (`Reshape._impl_v13`,
`python/tvm/relax/frontend/onnx/onnx_frontend.py:968-985`): the converter never
reads `attr["allowzero"]`. It always passes `0`-copy semantics to
`relax.op.reshape`. A valid empty-tensor model `data=(2,0)`, `shape=[0,2]`,
`allowzero=1` (opset 14) — which onnxruntime and `onnx.reference` return
`(0,2)` — is rejected:
```
InternalError: Reshape expects the new shape to be convertible from the
old shape. However, the old shape ...
```
Both are valid, runnable ONNX models.
### Environment
- OS: Linux
- TVM: v0.24.dev0 (main branch, commit `262c6d2e0`, built 2026-02-11)
- Python: 3.11
- onnx: 1.20.1
- onnxruntime: 1.24.1
### Steps to reproduce
```python
"""Repro: ONNX Reshape 0-dim semantics mishandled by TVM relax frontend."""
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, shape_vals, data_const, out_shape, allowzero=None,
opset=13):
inits, inputs = [], []
if data_const:
d = (np.arange(int(np.prod(data_shape))).reshape(data_shape) +
1).astype("float32")
inits.append(helper.make_tensor("data", TensorProto.FLOAT,
list(data_shape), d.flatten().tolist()))
else:
inputs.append(helper.make_tensor_value_info("data",
TensorProto.FLOAT, list(data_shape)))
inits.append(helper.make_tensor("shape", TensorProto.INT64,
[len(shape_vals)], list(shape_vals)))
node = helper.make_node("Reshape", ["data", "shape"], ["Y"])
if allowzero is not None:
node.attribute.append(helper.make_attribute("allowzero", allowzero))
graph = helper.make_graph(
[node], "g", inputs,
[helper.make_tensor_value_info("Y", TensorProto.FLOAT,
list(out_shape))], inits)
model = helper.make_model(graph, opset_imports=[helper.make_opsetid("",
opset)])
model.ir_version = 8
return model
def try_tvm(m, shape_dict, label):
try:
from_onnx(m, shape_dict=shape_dict)
print(f"{label} TVM: OK")
except Exception as e:
print(f"{label} TVM: {type(e).__name__}: {e}")
# (1) Constant path, default allowzero=0: 0 must copy the input dim
m1 = build((2, 3), [0, 3], data_const=True, out_shape=(2, 3))
onnx.checker.check_model(m1) # valid ONNX
model
print("m1 onnxruntime:",
onnxruntime.InferenceSession(m1.SerializeToString()).run(None, {})[0].shape)
print("m1 onnx.reference:", ReferenceEvaluator(m1).run(None, {})[0].shape)
try_tvm(m1, {}, "m1") # TVM rejects
# (2) allowzero=1 (opset 14): 0 is a literal zero dim
m2 = build((2, 0), [0, 2], data_const=False, out_shape=(0, 2), allowzero=1,
opset=14)
onnx.checker.check_model(m2) # valid ONNX
model
x = np.zeros((2, 0), dtype="float32")
print("m2 onnxruntime:",
onnxruntime.InferenceSession(m2.SerializeToString()).run(None, {"data":
x})[0].shape)
print("m2 onnx.reference:", ReferenceEvaluator(m2).run(None, {"data":
x})[0].shape)
try_tvm(m2, {"data": [2, 0]}, "m2") # TVM rejects
```
Actual output:
```
m1 onnxruntime: (2, 3)
m1 onnx.reference: (2, 3)
Error converting operator Reshape, with inputs:
[metadata["relax.expr.Constant"][0], metadata["relax.expr.Constant"][0]]
m1 TVM: ValueError: cannot reshape array of size 6 into shape (0,3)
File "tvm/relax/frontend/onnx/onnx_frontend.py", line 980, in _impl_v13
out = _np.reshape(data.data.numpy(), new_shape.data.numpy().tolist())
m2 onnxruntime: (0, 2)
m2 onnx.reference: (0, 2)
Error converting operator Reshape, with inputs: [data,
metadata["relax.expr.Constant"][0]]
m2 TVM: InternalError: Reshape expects the new shape to be convertible from
the old shape. However, the old shape is R.shape([2, 0]), with product
T.int64(0), while the new shape is R.shape([2, 2]), with product T.int64(4)
```
### 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]