Expected behavior
A valid ONNX Pad model using the opset-18+ features — mode="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:
-
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.
-
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 22 — mode="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
Expected behavior
A valid ONNX
Padmodel using the opset-18+ features —mode="wrap"(circular padding) and/or the optionalaxesinput (pad only the specified dimensions) — should be imported successfully bytvm.relax.frontend.onnx.from_onnx. Both were added to the ONNX Pad spec in opset 18 (see ONNX Pad spec), are accepted byonnx.checker, and run correctly in onnxruntime and onnx.reference.Actual behavior
from_onnxrejects both features:mode="wrap"raisesOpAttributeInvalid:The frontend's mode whitelist only accepts
["constant", "edge", "reflect"](raised atpython/tvm/relax/frontend/onnx/onnx_frontend.py:1919inPad._impl_v2and:1949inPad._impl_v11, class at line 1899), so a legal circular-padding model is misdiagnosed as invalid.axesinput is silently ignored — the pads are applied to the full-rank shape instead of the specified axes, so a model such asX: (3, 4),pads: [1, 2],axes: [1](which pads only dim 1, output(3, 7)) fails withValueError: Input dimension and pad_before dismatch : 2 vs 1because the frontend tries to apply the 1-axis pads to dim 0.Both failures are frontend coverage gaps, not invalid models:
onnx.checkerandonnx.referenceaccept them, onnxruntime runs them correctly, andtopi.nn.circular_padalready implements circular padding — the frontend simply never dispatches to it.Environment
262c6d2e0, built 2026-02-11)Steps to reproduce
Actual output:
Additional context
mode∈ {constant,reflect,edge,wrap}, wherewrappads by wrapping around (circular), and adds an optionalaxesinput restricting which dimensions are padded. Opset ≤ 17 has neither.topi.nn.circular_padalready implements circular padding in TVM's core, so the capability exists — this is purely a frontend dispatch gap (mirroring the earlierConvTranspose3dcase wheretopi.nn.conv3d_transposeexisted butfrom_onnxraisedNotImplementedError).axescases): of 81 legal Pad models accepted by onnxruntime /onnx.checker, TVM rejects 22 —mode="wrap"(19) andconstant+axes(3). The 59 accepted models match onnxruntime exactly (max|diff| = 0), including negative pads (crop), so the gap is purely import coverage. Dynamicpads(graph input) are also rejected ("Dynamic pads are not supported yet."), an unrelated known limitation.mode="wrap"totopi.nn.circular_padand expands theaxesinput into full-rank pads. Its motivating case is exactly theOpAttributeInvalid: Value wrap ... is invalidfailure described here — confirming the bug is real. This report covers the unfixed behavior in the v0.24.dev0 build at commit262c6d2e0, which predates that fix.Triage