Skip to content

Commit d08e63b

Browse files
committed
[Relax][Frontend][ONNX] Add Pad wrap mode
1 parent e7b87fe commit d08e63b

2 files changed

Lines changed: 79 additions & 5 deletions

File tree

python/tvm/relax/frontend/onnx/onnx_frontend.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2759,6 +2759,63 @@ def _impl_v11(cls, bb, inputs, attr, params):
27592759
# edge mode - replicate border values
27602760
return bb.emit_te(topi.nn.replicate_pad, inputs[0], pad_before, pad_after)
27612761

2762+
@classmethod
2763+
def _impl_v19(cls, bb, inputs, attr, params):
2764+
pads = get_constant(inputs[1], params)
2765+
constant_value = get_constant(inputs[2], params)
2766+
if constant_value is not None:
2767+
constant_value = constant_value.data.numpy().item()
2768+
else:
2769+
constant_value = 0.0
2770+
2771+
if isinstance(pads, relax.Constant):
2772+
pad_before, pad_after = _np.split(pads.data.numpy(), 2)
2773+
pad_before = _np.ndarray.tolist(pad_before)
2774+
pad_after = _np.ndarray.tolist(pad_after)
2775+
else:
2776+
raise ValueError("Dynamic pads are not supported yet.")
2777+
2778+
axes_input = inputs[3] if len(inputs) > 3 else None
2779+
if axes_input is not None:
2780+
axes_const = get_constant(axes_input, params)
2781+
if not isinstance(axes_const, relax.Constant):
2782+
raise ValueError("Dynamic axes are not supported for Pad yet.")
2783+
2784+
axes = axes_const.data.numpy().tolist()
2785+
if len(pad_before) != len(axes):
2786+
raise ValueError(
2787+
f"Pad expects pads length 2 * len(axes), got "
2788+
f"{len(pad_before) + len(pad_after)} pads and {len(axes)} axes."
2789+
)
2790+
2791+
rank = _get_known_tensor_rank(inputs[0])
2792+
if rank is None:
2793+
raise ValueError("Pad with axes requires a statically known input rank.")
2794+
2795+
axes = _normalize_constant_axes([int(a) for a in axes], rank, "Pad")
2796+
full_before = [0] * rank
2797+
full_after = [0] * rank
2798+
for i, ax in enumerate(axes):
2799+
full_before[ax] = pad_before[i]
2800+
full_after[ax] = pad_after[i]
2801+
pad_before, pad_after = full_before, full_after
2802+
2803+
pad_mode = attr.get("mode", b"constant").decode("utf-8")
2804+
if pad_mode not in ["constant", "edge", "reflect", "wrap"]:
2805+
raise tvm.error.OpAttributeInvalid(
2806+
"Value " + pad_mode + ' in attribute "mode" is invalid for operator Pad.'
2807+
)
2808+
2809+
if pad_mode == "constant":
2810+
return bb.emit_te(topi.nn.pad, inputs[0], pad_before, pad_after, constant_value)
2811+
elif pad_mode == "reflect":
2812+
return bb.emit_te(topi.nn.mirror_pad, inputs[0], pad_before, pad_after, "REFLECT")
2813+
elif pad_mode == "wrap":
2814+
return bb.emit_te(topi.nn.circular_pad, inputs[0], pad_before, pad_after)
2815+
else:
2816+
# edge mode - replicate border values
2817+
return bb.emit_te(topi.nn.replicate_pad, inputs[0], pad_before, pad_after)
2818+
27622819

27632820
class Tile(OnnxOpConverter):
27642821
"""Converts an onnx Tile node into an equivalent Relax expression."""

tests/python/relax/test_frontend_onnx.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3656,23 +3656,37 @@ def test_pad(dynamic):
36563656
if dynamic:
36573657
pytest.skip("Dynamic pad not supported")
36583658

3659-
def verify_pad(input_shape, pads, mode="constant", value=0.0):
3659+
def verify_pad(input_shape, pads, mode="constant", value=0.0, opset=14, axes=None):
36603660
indata = np.random.normal(size=input_shape).astype(np.float32)
36613661
# numpy expect result
36623662
len_dim = len(pads) // 2
36633663
np_pads = [(pads[i], pads[i + len_dim]) for i in range(len_dim)]
36643664
pads = np.array(pads)
36653665
# onnx graph
3666-
if mode in ["edge", "reflect"]:
3666+
if mode in ["edge", "reflect", "wrap"]:
3667+
if axes is not None:
3668+
rank = len(input_shape)
3669+
full_pads = [(0, 0)] * rank
3670+
for i, ax in enumerate(axes):
3671+
full_pads[ax if ax >= 0 else ax + rank] = np_pads[i]
3672+
np_pads = full_pads
3673+
36673674
outdata = np.pad(indata, pad_width=np_pads, mode=mode)
3668-
node = helper.make_node("Pad", inputs=["input", "pads"], outputs=["output"], mode=mode)
3675+
node_inputs = ["input", "pads"] if axes is None else ["input", "pads", "", "axes"]
3676+
node = helper.make_node("Pad", inputs=node_inputs, outputs=["output"], mode=mode)
3677+
initializer = [helper.make_tensor("pads", TensorProto.INT64, (len(pads),), pads)]
3678+
if axes is not None:
3679+
axes_arr = np.array(axes, dtype=np.int64)
3680+
initializer.append(
3681+
helper.make_tensor("axes", TensorProto.INT64, (len(axes_arr),), axes_arr)
3682+
)
36693683
graph = helper.make_graph(
36703684
[node],
36713685
"pad_test",
36723686
inputs=[
36733687
helper.make_tensor_value_info("input", TensorProto.FLOAT, list(indata.shape))
36743688
],
3675-
initializer=[helper.make_tensor("pads", TensorProto.INT64, (len(pads),), pads)],
3689+
initializer=initializer,
36763690
outputs=[
36773691
helper.make_tensor_value_info("output", TensorProto.FLOAT, list(outdata.shape))
36783692
],
@@ -3700,14 +3714,17 @@ def verify_pad(input_shape, pads, mode="constant", value=0.0):
37003714
],
37013715
)
37023716
model = helper.make_model(graph, producer_name="pad_test")
3703-
check_correctness(model)
3717+
check_correctness(model, opset=opset)
37043718

37053719
verify_pad((2, 2), [0, 1, 0, 0], "constant", 0.0)
37063720
verify_pad((2, 3), [1, 0, 0, 1], "constant", 0.0)
37073721
verify_pad((3, 2), [0, 0, 1, 0], "constant", 5.0)
37083722
verify_pad((1, 3, 4, 5), [0, 1, 1, 1, 0, 0, 1, 1], "reflect")
37093723
verify_pad((2, 3), [1, 1, 1, 1], "edge")
37103724
verify_pad((1, 3, 4, 5), [0, 1, 1, 1, 0, 0, 1, 1], "edge")
3725+
verify_pad((1, 3, 4, 5), [0, 1, 1, 1, 0, 0, 1, 1], "wrap", opset=19)
3726+
verify_pad((1, 3, 4), [2, 2], "wrap", opset=19, axes=[2])
3727+
verify_pad((1, 3, 4, 5), [1, 1, 1, 1], "wrap", opset=19, axes=[1, 3])
37113728

37123729

37133730
@pytest.mark.parametrize("dynamic", [True, False])

0 commit comments

Comments
 (0)