Skip to content

Commit 9651ef5

Browse files
committed
Add missing tests for the pas fix
1 parent 45a231f commit 9651ef5

3 files changed

Lines changed: 179 additions & 2 deletions

File tree

coreai_torch/_utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from ._composite_declaration import generate_composite_decl
4747
from ._type_mapping import (
4848
TORCH_TO_COREAI_DTYPE,
49+
_get_coreai_to_numpy_dtype,
4950
_get_coreai_to_torch_dtype,
5051
)
5152

@@ -1055,11 +1056,13 @@ def replace_pad_with_mode(
10551056
padding[2 * dim] = inverted_padding[i]
10561057
padding[2 * dim + 1] = inverted_padding[i + 1]
10571058

1058-
# padding_value is ignored for non-constant modes, but the op requires one.
1059+
# padding_value is ignored for non-constant modes, but the op requires it to
1060+
# be a constant of the input dtype (a cast op is rejected by the backend).
1061+
np_dtype = _get_coreai_to_numpy_dtype()[x.type.element_type]
10591062
return coreai.pad(
10601063
x,
10611064
np.array(padding, dtype=np.uint32),
1062-
coreai.cast(0.0, x.type.element_type),
1065+
coreai.constant(np.array(0.0, dtype=np_dtype)),
10631066
padding_mode=padding_mode,
10641067
)
10651068

tests/ops/test_ops.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7331,3 +7331,93 @@ def forward(
73317331
remove_decomps=[torch.ops.aten.scaled_dot_product_attention.default],
73327332
**kwargs,
73337333
)
7334+
7335+
7336+
# ndim (number of padded spatial dims) -> the aten op that must be preserved
7337+
# so the reflect/replicate lowering (coreai.pad) is exercised end to end.
7338+
_REFLECT_PAD_OP = {
7339+
1: torch.ops.aten.reflection_pad1d.default,
7340+
2: torch.ops.aten.reflection_pad2d.default,
7341+
3: torch.ops.aten.reflection_pad3d.default,
7342+
}
7343+
_REPLICATE_PAD_OP = {
7344+
1: torch.ops.aten.replication_pad1d.default,
7345+
2: torch.ops.aten.replication_pad2d.default,
7346+
3: torch.ops.aten.replication_pad3d.default,
7347+
}
7348+
7349+
7350+
class _PadModel(nn.Module):
7351+
def __init__(self, pad: tuple[int, ...], mode: str) -> None:
7352+
super().__init__()
7353+
self._pad = pad
7354+
self._mode = mode
7355+
7356+
def forward(self, x: Tensor) -> Tensor:
7357+
return torch.nn.functional.pad(x, self._pad, mode=self._mode)
7358+
7359+
7360+
# (pad, input_shape) pairs valid for BOTH reflect and replicate.
7361+
# For reflect, torch requires every pad entry < the corresponding dim size.
7362+
_PAD_SHARED_CASES = [
7363+
# 1D (pad = (left, right)), input (N, C, W)
7364+
((2, 2), (1, 3, 8)), # symmetric
7365+
((1, 3), (2, 4, 10)), # asymmetric
7366+
((0, 2), (1, 1, 6)), # one-sided
7367+
# 2D (pad = (left, right, top, bottom)), input (N, C, H, W)
7368+
((2, 2, 2, 2), (1, 3, 8, 8)), # symmetric
7369+
((1, 2, 3, 1), (2, 3, 10, 12)), # asymmetric, per-side
7370+
((0, 1, 1, 0), (1, 4, 7, 9)), # mixed zero/one-sided
7371+
# 3D (pad = (l, r, t, b, front, back)), input (N, C, D, H, W)
7372+
((1, 1, 1, 1, 1, 1), (1, 2, 4, 6, 6)), # symmetric
7373+
((2, 1, 0, 2, 1, 1), (1, 2, 5, 7, 7)), # asymmetric
7374+
]
7375+
7376+
7377+
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16])
7378+
@pytest.mark.parametrize("mode", ["reflect", "replicate"])
7379+
@pytest.mark.parametrize("pad, input_shape", _PAD_SHARED_CASES)
7380+
async def test_reflect_replicate_pad(
7381+
pad: tuple[int, ...],
7382+
input_shape: tuple[int, ...],
7383+
mode: str,
7384+
dtype: torch.dtype,
7385+
) -> None:
7386+
ndim = len(pad) // 2
7387+
aten_op = (_REFLECT_PAD_OP if mode == "reflect" else _REPLICATE_PAD_OP)[ndim]
7388+
await validate_numerical_output(
7389+
model=_PadModel(pad, mode).eval(),
7390+
x=torch.rand(*input_shape, dtype=dtype),
7391+
remove_decomps=[aten_op],
7392+
)
7393+
7394+
7395+
@pytest.mark.parametrize("mode", ["reflect", "replicate"])
7396+
async def test_reflect_replicate_pad_dynamic_batch(mode: str) -> None:
7397+
"""Padding amounts and spatial dims are static; only the batch is dynamic."""
7398+
aten_op = (_REFLECT_PAD_OP if mode == "reflect" else _REPLICATE_PAD_OP)[2]
7399+
await validate_numerical_output(
7400+
model=_PadModel((2, 2, 2, 2), mode).eval(),
7401+
x=torch.rand(2, 3, 8, 8),
7402+
dynamic_shapes={"x": {0: torch.export.Dim("batch", min=1)}},
7403+
remove_decomps=[aten_op],
7404+
)
7405+
7406+
7407+
@pytest.mark.parametrize(
7408+
"pad, input_shape",
7409+
[
7410+
((4, 4), (1, 2, 3)), # 1D: pad exceeds the padded dim
7411+
((5, 5, 5, 5), (1, 2, 3, 3)), # 2D: pad exceeds both spatial dims
7412+
],
7413+
)
7414+
async def test_replicate_pad_larger_than_dim(
7415+
pad: tuple[int, ...], input_shape: tuple[int, ...]
7416+
) -> None:
7417+
"""Replicate (unlike reflect) allows padding wider than the input dim."""
7418+
ndim = len(pad) // 2
7419+
await validate_numerical_output(
7420+
model=_PadModel(pad, "replicate").eval(),
7421+
x=torch.rand(*input_shape),
7422+
remove_decomps=[_REPLICATE_PAD_OP[ndim]],
7423+
)

tests/ops/test_ops_ir.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import torch.nn as nn
1111
from torch import Tensor
1212

13+
import coreai_torch
14+
1315
from ..utils import _all_dims_dynamic, filecheck_pattern, get_ir
1416

1517

@@ -7238,3 +7240,85 @@ def body_fn(x):
72387240
// CHECK-NEXT: }
72397241
""",
72407242
)
7243+
7244+
7245+
class _PadModel(nn.Module):
7246+
def __init__(self, pad: tuple[int, ...], mode: str) -> None:
7247+
super().__init__()
7248+
self._pad = pad
7249+
self._mode = mode
7250+
7251+
def forward(self, x: Tensor) -> Tensor:
7252+
return torch.nn.functional.pad(x, self._pad, mode=self._mode)
7253+
7254+
7255+
class TestPadModesIR:
7256+
"""reflect/replicate padding must lower to coreai.pad, not coreai.gather_nd.
7257+
7258+
``remove_decomps`` keeps the pad op in the graph exactly as
7259+
``get_decomp_table()`` does in production, so the direct lowering is
7260+
exercised (otherwise torch decomposes it into index/gather ops).
7261+
"""
7262+
7263+
@pytest.mark.parametrize(
7264+
"mode, aten_op, coreai_mode",
7265+
[
7266+
("reflect", torch.ops.aten.reflection_pad2d.default, "reflect"),
7267+
("replicate", torch.ops.aten.replication_pad2d.default, "replicate"),
7268+
],
7269+
)
7270+
def test_lowers_to_coreai_pad_not_gather(
7271+
self, mode: str, aten_op, coreai_mode: str
7272+
) -> None:
7273+
ir = get_ir(
7274+
_PadModel((2, 2, 2, 2), mode).eval(),
7275+
x=torch.rand(1, 3, 8, 8),
7276+
remove_decomps=[aten_op],
7277+
)
7278+
assert "coreai.pad" in ir, ir
7279+
assert f"mode = <{coreai_mode}>" in ir, ir
7280+
assert "gather_nd" not in ir, ir
7281+
7282+
def test_reflect_pad_ir_structure(self) -> None:
7283+
ir = get_ir(
7284+
_PadModel((2, 2, 2, 2), "reflect").eval(),
7285+
x=torch.rand(1, 3, 8, 8),
7286+
remove_decomps=[torch.ops.aten.reflection_pad2d.default],
7287+
)
7288+
filecheck_pattern(
7289+
ir,
7290+
check_file="""
7291+
// CHECK-LABEL: coreai.graph @main
7292+
// CHECK: %[[PAD:.*]] = coreai.pad %[[X:.*]], %{{.*}}, %{{.*}} mode = <reflect>
7293+
// CHECK-SAME: -> tensor<1x3x12x12xf32>
7294+
// CHECK: coreai.output %[[PAD]]
7295+
""",
7296+
)
7297+
7298+
def test_reflection_pad1d(self) -> None:
7299+
ir = get_ir(
7300+
_PadModel((2, 2), "reflect").eval(),
7301+
x=torch.rand(1, 3, 8),
7302+
remove_decomps=[torch.ops.aten.reflection_pad1d.default],
7303+
)
7304+
assert "coreai.pad" in ir, ir
7305+
assert "mode = <reflect>" in ir, ir
7306+
assert "gather_nd" not in ir, ir
7307+
7308+
7309+
class TestPadDecompTable:
7310+
"""The decomposition table must preserve the pad ops so the handler fires."""
7311+
7312+
@pytest.mark.parametrize(
7313+
"aten_op",
7314+
[
7315+
torch.ops.aten.reflection_pad1d.default,
7316+
torch.ops.aten.reflection_pad2d.default,
7317+
torch.ops.aten.reflection_pad3d.default,
7318+
torch.ops.aten.replication_pad1d.default,
7319+
torch.ops.aten.replication_pad2d.default,
7320+
torch.ops.aten.replication_pad3d.default,
7321+
],
7322+
)
7323+
def test_pad_ops_preserved(self, aten_op) -> None:
7324+
assert aten_op not in coreai_torch.get_decomp_table()

0 commit comments

Comments
 (0)