Skip to content

Lower multi-dimensional reflect/replicate pad at op conversion time - #2701

Open
LeSingh1 wants to merge 1 commit into
apple:mainfrom
LeSingh1:fix/pad-reflect-replicate-3d-validation
Open

LeSingh1 wants to merge 1 commit into
apple:mainfrom
LeSingh1:fix/pad-reflect-replicate-3d-validation

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented May 18, 2026

Copy link
Copy Markdown
Contributor

Problem

reflect and replicate padding fail to convert when padding is applied outside the last two dimensions. The Core ML runtime rejects the resulting op:

Error: Unable to parse ML Program: in operation op_11_cast_fp16:
Padding for more than two dimensions only supports `constant` mode

The restriction is often described as "at most two padded dimensions", but that is not what the runtime enforces. Enumerated against a rank-5 input (identical for reflect and replicate):

pad padded axes result
[0,0, 0,0, 0,0, 0,0, 1,1] 4 OK
[0,0, 0,0, 0,0, 1,1, 1,1] 3, 4 OK
[0,0, 0,0, 1,1, 0,0, 0,0] 2 rejected
[0,0, 0,0, 1,1, 1,1, 0,0] 2, 3 rejected
[0,0, 1,1, 0,0, 0,0, 0,0] 1 rejected

The actual rule is all non-zero non-constant padding must be in the last two dimensions. So a rank-4 F.pad(x, (0,0,1,2,2,1), mode="replicate") pads only two axes and is still rejected.

Fix

Lowered at op conversion time in the torch pad converter, per @TobyRoseman's question on #2697.

reflect and replicate map each output index to an input index one axis at a time, so a multi-axis pad is exactly a sequence of single-axis pads. The converter pads the last two dimensions first, then each remaining axis individually with that axis transposed to the end — so Core ML only ever sees non-constant padding on the final dimension.

No new pass and no pipeline ordering to reason about, and nothing merges the chain back because the emitted pads are never adjacent. Constant mode, dynamic pad values, and already-legal pads emit byte-identical output.

Testing

Same tests against main and against this branch, both backends, both frontends:

main        : 28 failed, 8 passed, 12 skipped
this branch : 36 passed, 12 skipped
full TestPad: 58 passed, 22 skipped
MIL pass tests (-k pad): 215 passed

Coverage added: test_pad_reflect_replicate extended to rank 5 (no neuralnetwork skip needed), test_pad_reflect_replicate_3d for ReflectionPad3d/ReplicationPad3d symmetric and asymmetric, and test_pad_reflect_replicate_leading_dims for the two-leading-axes case.

Numerics verified against eager PyTorch on mlprogram fp32 and neuralnetwork.

)
if padded_dims > 2:
raise NotImplementedError(
f"CoreML's pad operation only supports `{mode}` mode when "

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just stick with the error message from the Core ML Framework, I think it clearer: Padding for more than two dimensions only supports constant mode".

],
)
def test_pad_reflect_replicate_3d_raises(self, mode, torch_module):
# Regression test for issues #2576 and #2571: MIL `mb.pad` only supports

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's update this comment using the language mentioned in my other comment.

@TobyRoseman

Copy link
Copy Markdown
Collaborator

LeSingh1 added a commit to LeSingh1/coremltools that referenced this pull request May 18, 2026
Per @TobyRoseman's review on apple#2701:

- ops.py: replace the verbose multi-line error with the existing Core ML
  Framework wording ('Padding for more than two dimensions only supports
  constant mode') so the conversion-time and runtime errors match.
- test_torch_ops.py: update the docstring to the same language and match
  the new error string in pytest.raises (the previous regex matched the
  mode name, which is no longer in the message).
@LeSingh1

Copy link
Copy Markdown
Contributor Author

Thanks for the review @TobyRoseman! Updated in abb9faa — now raising the Core ML Framework's own wording verbatim ("Padding for more than two dimensions only supports constant mode") and updated the test comment + pytest.raises match to use the same string.

@TobyRoseman

Copy link
Copy Markdown
Collaborator

This may be a better fix: #2697

@LeSingh1

Copy link
Copy Markdown
Contributor Author

Agreed @TobyRoseman#2697's approach is better, since it actually makes >2D reflect/replicate pads work via the new split_non_constant_pads backend pass instead of just surfacing the error sooner. I'm happy to close this PR once #2697 lands, or sooner if you'd prefer. Let me know which is cleaner for you.

@TobyRoseman

Copy link
Copy Markdown
Collaborator

@LeSingh1 - Thanks for confirming. If you don't mind, please review #2697. Does the fix look good to you? Are there any additional tests you recommend adding? Feel free to comment directly in that pull request.

cc: @tritolol.

LeSingh1 added a commit to LeSingh1/coremltools that referenced this pull request Aug 4, 2026
Per @TobyRoseman's review on apple#2701:

- ops.py: replace the verbose multi-line error with the existing Core ML
  Framework wording ('Padding for more than two dimensions only supports
  constant mode') so the conversion-time and runtime errors match.
- test_torch_ops.py: update the docstring to the same language and match
  the new error string in pytest.raises (the previous regex matched the
  mode name, which is no longer in the message).
@LeSingh1
LeSingh1 force-pushed the fix/pad-reflect-replicate-3d-validation branch from 6140a85 to 52ac1ed Compare August 4, 2026 03:04
Core ML only accepts `reflect` / `replicate` padding when every non-zero pad
lies in the last two dimensions of the input, while torch has no such
restriction. `torch.nn.ReflectionPad3d` / `torch.nn.ReplicationPad3d` pad three
dimensions, and `torch.nn.functional.pad` can pad any subset of dimensions, so
conversion succeeded but the resulting model failed to load with

    Error compiling model: "Failed to parse the model specification.
    Error: Unable to parse ML Program: in operation pad_cast_fp16:
    Padding for more than two dimensions only supports `constant` mode".

Both modes map every output index to an input index one axis at a time, so a
multi-axis pad is equivalent to a sequence of single-axis pads. Lower an
unsupported pad into a chain that pads the last two dimensions first and then
each remaining axis on its own, transposed to the end so Core ML only ever sees
padding on the final dimension.

Padding that is already confined to the last two dimensions, `constant` mode,
and dynamic pad values are all emitted exactly as before.

Fixes apple#2571
Fixes apple#2576
@LeSingh1
LeSingh1 force-pushed the fix/pad-reflect-replicate-3d-validation branch from 52ac1ed to c72b1f7 Compare August 4, 2026 03:49
@LeSingh1 LeSingh1 changed the title Raise NotImplementedError at conversion time for reflect/replicate pad >2D (fixes #2576, #2571) Lower multi-dimensional reflect/replicate pad at op conversion time Aug 4, 2026
@LeSingh1

LeSingh1 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@TobyRoseman — following up on your ask to review #2697, and on the question you left there about doing this at op conversion time. I have a full local build now (native libs + predict()), so everything below is measured rather than reasoned about.

I have to correct my own earlier review of #2697 — it does not fix the issue. I read the logic on paper and said it looked right; it does not survive a real run. Applied to current main, ten of its new/extended cases fail:

$ pytest coremltools/converters/mil/frontend/torch/test/test_torch_ops.py::TestPad
10 failed, 30 passed, 32 skipped

E RuntimeError: Error compiling model: "Failed to parse the model specification.
  Error: Unable to parse ML Program: in operation op_11_cast_fp16_split_0:
  Padding for more than two dimensions only supports `constant` mode".

Why: the restriction is not "at most two padded dimensions", it is "all non-zero padding must be in the last two dimensions". Enumerated against a rank-5 input, identical for reflect and replicate:

pad padded axes result
[0,0, 0,0, 0,0, 0,0, 1,1] 4 OK
[0,0, 0,0, 0,0, 1,1, 1,1] 3, 4 OK
[0,0, 0,0, 1,1, 0,0, 0,0] 2 rejected
[0,0, 0,0, 1,1, 1,1, 0,0] 2, 3 rejected
[0,0, 1,1, 0,0, 0,0, 0,0] 1 rejected

Splitting a 3-axis pad into chunks of two arbitrary axes still produces a chunk on axes (2, 3) — that is exactly the ..._split_0 op above. The same table shows the previous version of this PR was under-restrictive in the other direction: a rank-4 F.pad(x, (0,0,1,2,2,1), mode="replicate") pads two axes, passes an "at most two" check, and is still rejected.

To your question: yes, this belongs at op conversion time, and it is straightforward there. reflect and replicate map every output index to an input index one axis at a time, so a multi-axis pad is exactly a sequence of single-axis pads. The converter pads the last two dimensions first, then each remaining axis on its own with that axis transposed to the end, so Core ML only ever sees padding on the final dimension. No new pass, no pipeline ordering to reason about, and nothing merges the chain back because the pads are never adjacent.

I've replaced this PR's contents with that and updated the title and description to match (the previous "raise early" version is at 6140a85 if you'd rather have it back).

Verification — same tests against main and against this branch, both backends, both frontends:

main        : 28 failed, 8 passed, 12 skipped
this branch : 36 passed, 12 skipped
full TestPad: 58 passed, 22 skipped

Coverage added: test_pad_reflect_replicate extended to rank 5 (no neuralnetwork skip needed — it works there too), test_pad_reflect_replicate_3d for ReflectionPad3d/ReplicationPad3d symmetric and asymmetric, and test_pad_reflect_replicate_leading_dims for the two-leading-axes case.

Credit to @tritolol#2697 is where the "a multi-axis non-constant pad is separable" idea comes from; this puts it in the converter and adds the transpose the runtime restriction requires. Happy to close this in favour of an updated #2697 if @tritolol wants to take it from here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants