The GitHub Actions job "Lint" on tvm.git/main has succeeded.
Run started by GitHub user tlopex (triggered by tlopex).

Head commit for run:
bd2df9f605cd86d6eba7b1f51ab3d06216cfcf21 / HuEnwei <[email protected]>
[Fix][Relax][Frontend][ONNX] Support Pad mode="wrap" and axes input for op… 
(#20152)

Fixes: #20150

## Summary

The Relax ONNX frontend rejected legal **opset-18** Pad models using
`mode="wrap"` (circular padding) or the optional `axes` input. Both are
ONNX Pad-18 features, are accepted by `onnx.checker` / `onnx.reference`
/
onnxruntime, and `topi.nn.circular_pad` already implements circular
padding — this is purely a frontend dispatch gap.

## Root cause

Upstream #19827 added `Pad._impl_v19` with `wrap`/`axes` support, but
`get_converter` dispatches on the highest `_impl_v{N}` with `N <=
opset`,
so that method is only reached for models with **opset >= 19**. A model
with **opset 18** — the version that actually introduced `wrap` and
`axes` — still resolves to `_impl_v11`, which:

1. has a whitelist `["constant", "edge", "reflect"]`, so `mode="wrap"`
raises `OpAttributeInvalid("Value wrap ... is invalid for operator
Pad.")`;
2. never reads `inputs[3]` (the `axes` input), so an axes model is
padded
   on the full rank instead of the specified axes and fails with
   `ValueError("Input dimension and pad_before dismatch ...")`.

## Fix

Add `Pad._impl_v18`, mirroring #19827's `_impl_v19` but for opset 18:
expand the `axes` input into full-rank pads via
`_get_known_tensor_rank` / `_normalize_constant_axes`, extend the mode
whitelist to include `"wrap"`, and dispatch `wrap` to
`topi.nn.circular_pad`:

```python
@classmethod
def _impl_v18(cls, bb, inputs, attr, params):
    # ONNX Pad-18 introduces mode="wrap" and the optional axes input ...
    ...
    axes_input = inputs[3] if len(inputs) > 3 else None
    if axes_input is not None:
        ...
        rank = _get_known_tensor_rank(inputs[0])
        axes = _normalize_constant_axes([int(a) for a in axes], rank, "Pad")
        full_before = [0] * rank
        full_after = [0] * rank
        for i, ax in enumerate(axes):
            full_before[ax] = pad_before[i]
            full_after[ax] = pad_after[i]
        pad_before, pad_after = full_before, full_after

    pad_mode = attr.get("mode", b"constant").decode("utf-8")
    if pad_mode not in ["constant", "edge", "reflect", "wrap"]:
        raise tvm.error.OpAttributeInvalid(...)
    ...
    elif pad_mode == "wrap":
        return bb.emit_te(topi.nn.circular_pad, inputs[0], pad_before, 
pad_after)
```

`_impl_v2` (opset 2, pads as attribute) and `_impl_v11` (opset 11-17,
neither `wrap` nor `axes` legal) are left untouched.

## Validation

Differential test (Relax `from_onnx` + `relax.build` + `VirtualMachine`
vs onnxruntime) over 81 legal Pad models: 3 input shapes × all modes ×
positive/negative pads, plus `axes` cases. Verified on the familyfuzz
locked build `262c6d2e0` via runtime monkey-patch
(`results/.../onnx_Pad/verify_patch.py`, no source files modified).

| Category | Cases | Before | After |
|---|---|---|---|
| `constant` / `edge` / `reflect` (opset 11/13) | 41 | match onnxrt |
match onnxrt (no regression) |
| `constant` / `edge` opset-18, no axes | 8 | match | match |
| `wrap` positive pads (opset 18, 19) | 14 | **rejected**
(`OpAttributeInvalid`) | match onnxrt, `max\|diff\| = 0` |
| `constant` + `axes` (opset 18, incl. negative axis) | 5 | **rejected**
(`ValueError`) | match onnxrt, `max\|diff\| = 0` |
| negative pads (crop) | 8 | match | match |
| **Total** | **81** | 59 match / **22 rejected** | **76 match / 0
rejected** / 5 documented deviation |

The 5 documented deviations are `wrap` with **negative pads**, where the
implementations disagree: `onnx.reference` (`np.pad` mode `"wrap"`)
errors
out entirely, onnxruntime uses its own crop-window semantics, and
`topi.nn.circular_pad` follows the ONNX mod formula
(`out[i] = in[(i - pad_before) mod dim]`), matching upstream #19827's
identical implementation. Positive-pad `wrap` (the actual use case)
agrees exactly across onnxrt / onnx.reference / TVM.

Run:

```bash
/home/shenqingchao/miniconda3/envs/tvm23/bin/python3 \
  results/TVM/deepseek-v4-flash/prove_hum/onnx_Pad/verify_patch.py
```

## Files changed

- `python/tvm/relax/frontend/onnx/onnx_frontend.py` — add
`Pad._impl_v18`
  with `wrap`/`axes` support for opset 18 (same handling as #19827's
  `_impl_v19`), closing the opset-18 gap.

---------

Co-authored-by: FFChopon <[email protected]>
Co-authored-by: Claude <[email protected]>

Report URL: https://github.com/apache/tvm/actions/runs/33289991591

With regards,
GitHub Actions via GitBox


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to