Skip to content

[Bug][Relax][Frontend][ONNX] Pad mode="wrap" (opset 18+) and axes input rejected by from_onnx: OpAttributeInvalid #20150

Description

@siyiweigeHEW

Expected behavior

A valid ONNX Pad model using the opset-18+ featuresmode="wrap" (circular padding) and/or the optional axes input (pad only the specified dimensions) — should be imported successfully by tvm.relax.frontend.onnx.from_onnx. Both were added to the ONNX Pad spec in opset 18 (see ONNX Pad spec), are accepted by onnx.checker, and run correctly in onnxruntime and onnx.reference.

Actual behavior

from_onnx rejects both features:

  1. mode="wrap" raises OpAttributeInvalid:

    OpAttributeInvalid: Value wrap in attribute "mode" is invalid for operator Pad.
    

    The frontend's mode whitelist only accepts ["constant", "edge", "reflect"] (raised at python/tvm/relax/frontend/onnx/onnx_frontend.py:1919 in Pad._impl_v2 and :1949 in Pad._impl_v11, class at line 1899), so a legal circular-padding model is misdiagnosed as invalid.

  2. axes input is silently ignored — the pads are applied to the full-rank shape instead of the specified axes, so a model such as X: (3, 4), pads: [1, 2], axes: [1] (which pads only dim 1, output (3, 7)) fails with ValueError: Input dimension and pad_before dismatch : 2 vs 1 because the frontend tries to apply the 1-axis pads to dim 0.

Both failures are frontend coverage gaps, not invalid models: onnx.checker and onnx.reference accept them, onnxruntime runs them correctly, and topi.nn.circular_pad already implements circular padding — the frontend simply never dispatches to it.

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: valid ONNX Pad (opset 18) with mode="wrap" and/or the axes input is
rejected by the TVM relax ONNX frontend, while onnxruntime runs it correctly."""
import numpy as np
import onnx, onnxruntime
from onnx import helper, TensorProto
from tvm.relax.frontend.onnx import from_onnx

def make_v18_model(x_shape, pads, mode, axes=None):
    X = helper.make_tensor_value_info("X", TensorProto.FLOAT, list(x_shape))
    Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, None)
    inits = [helper.make_tensor("pads", TensorProto.INT64, [len(pads)], pads)]
    node_in = ["X", "pads"]
    if axes is not None:  # axes is positional input #3; provide constant_value too
        inits.append(helper.make_tensor("value", TensorProto.FLOAT, [], [0.0]))
        inits.append(helper.make_tensor("axes", TensorProto.INT64, [len(axes)], axes))
        node_in += ["value", "axes"]
    g = helper.make_graph([helper.make_node("Pad", node_in, ["Y"], mode=mode)], "g", [X], [Y], inits)
    m = helper.make_model(g, opset_imports=[helper.make_opsetid("", 18)])
    m.ir_version = 8
    return m

x = np.arange(12, dtype="float32").reshape(3, 4)

# (1) mode="wrap" (opset 18+ circular padding)
m1 = make_v18_model((3, 4), [1, 1, 2, 2], "wrap")
onnx.checker.check_model(m1)                                        # valid
r = onnxruntime.InferenceSession(m1.SerializeToString()).run(None, {"X": x})[0]
print("onnxruntime wrap ->", r.shape)                               # (6, 7)
from_onnx(m1, shape_dict={"X": [3, 4]})                             # rejected

# (2) axes input: pads only dim 1 of X(3,4)
m2 = make_v18_model((3, 4), [1, 2], "constant", axes=[1])
r = onnxruntime.InferenceSession(m2.SerializeToString()).run(None, {"X": x})[0]
print("onnxruntime constant+axes ->", r.shape)                      # (3, 7)
from_onnx(m2, shape_dict={"X": [3, 4]})                             # rejected

Actual output:

onnxruntime wrap -> (6, 7)
Traceback (most recent call last):
  ...
  File "tvm/relax/frontend/onnx/onnx_frontend.py", line 1949, in _impl_v11
    raise tvm.error.OpAttributeInvalid(
tvm.error.OpAttributeInvalid: Value wrap in attribute "mode" is invalid for operator Pad.
onnxruntime constant+axes -> (3, 7)
Traceback (most recent call last):
  ...
  File "tvm/relax/frontend/onnx/onnx_frontend.py", line 1954, in _impl_v11
    return bb.emit_te(topi.nn.pad, inputs[0], pad_before, pad_after, constant_value)
  File "tvm/topi/nn/pad.py", line 88, in pad
    raise ValueError(f"Input dimension and pad_before dismatch : {n} vs {len(pad_before)}")
ValueError: Input dimension and pad_before dismatch : 2 vs 1

Additional context

  • The ONNX Pad spec (opset 18+) defines mode ∈ {constant, reflect, edge, wrap}, where wrap pads by wrapping around (circular), and adds an optional axes input restricting which dimensions are padded. Opset ≤ 17 has neither.
  • topi.nn.circular_pad already implements circular padding in TVM's core, so the capability exists — this is purely a frontend dispatch gap (mirroring the earlier ConvTranspose3d case where topi.nn.conv3d_transpose existed but from_onnx raised NotImplementedError).
  • A broader differential survey (3 input shapes × all modes × positive/negative pads, plus axes cases): of 81 legal Pad models accepted by onnxruntime / onnx.checker, TVM rejects 22mode="wrap" (19) and constant + axes (3). The 59 accepted models match onnxruntime exactly (max|diff| = 0), including negative pads (crop), so the gap is purely import coverage. Dynamic pads (graph input) are also rejected ("Dynamic pads are not supported yet."), an unrelated known limitation.
  • A fix was upstreamed in apache/tvm #19827 "[Relax][Frontend][ONNX] Add support for Pad mode=wrap for opset 19" (merged 2026-07-09): it dispatches mode="wrap" to topi.nn.circular_pad and expands the axes input into full-rank pads. Its motivating case is exactly the OpAttributeInvalid: Value wrap ... is invalid failure described here — confirming the bug is real. This report covers the unfixed behavior in the v0.24.dev0 build at commit 262c6d2e0, which predates that fix.

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