Skip to content

[Bug][Relax][Frontend][ONNX] Reshape mishandles 0 in shape: constant-fold path breaks 0-copy semantics (default allowzero=0); allowzero=1 ignored #20151

Description

@siyiweigeHEW

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triagePRs or issues that need to be investigated by maintainers to find the right assignees to address ittype: bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions