Skip to content

Transforms: reorder a broadcast constant by permuting, not reinterpreting - #22165

Draft
rascani wants to merge 1 commit into
pytorch:mainfrom
rascani:transforms-constant-permute-fix
Draft

Transforms: reorder a broadcast constant by permuting, not reinterpreting#22165
rascani wants to merge 1 commit into
pytorch:mainfrom
rascani:transforms-constant-permute-fix

Conversation

@rascani

@rascani rascani commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

When RemovePermutesAroundElementwiseOps cancels a permute across a region, a broadcast constant of lower rank than the activation has to be reordered by the same permutation. The pass always emitted a view_copy for that. A view reinterprets strides rather than moving elements, so it is only a stand-in for the reorder when nothing but unit extents move. On a (4, 8, 8) bias under [0, 2, 3, 1] the two disagree elementwise at the same shape:

bias.view(8, 8, 4)[0, 0, :4]     -> [0, 1, 2, 3]
bias.permute(1, 2, 0)[0, 0, :4]  -> [0, 64, 128, 192]

Keep the view where it is exact. Broadcasting widens the constant to the region's rank before the permutation applies, so compute the widened target shape and compare its non-unit extents with the original: if their order is unchanged the reorder is a pure reshape, and a single view still expresses it at no cost. Only when a non-unit extent actually moves does the constant get a widening view plus a permute.

A per-channel constant, the ordinary case, stays a lone view_copy. The rank-3 constant above is the one that gains a permute, because there a view is wrong.

The old shape arithmetic also sliced the permuted shape back down to the constant's rank, which is not where the surviving axes necessarily are. A rank-1 per-channel constant under [0, 2, 3, 1] wants (1, 1, C, 1) and the slice asked for (1,), so export failed outright; it now lowers.

Cadence is the exposed backend: it runs this pass twice in its default pipeline and has no rank-matching pass. Arm reaches the pass at equal rank almost always, because MatchArgRanksPass widens broadcast operands beforehand.

Authored with Claude Code.

@pytorch-bot

pytorch-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22165

Note: Links to docs will display an error until the docs builds have been completed.

✅ You can merge normally! (1 Unrelated Failure)

As of commit 517882b with merge base 469debd (image):

FLAKY - The following job failed but was likely due to flakiness present on trunk:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 25, 2026
@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

When RemovePermutesAroundElementwiseOps cancels the permutes around a region,
an operand that is a constant has no permute of its own to cancel, so the pass
compensates the constant directly: it applies the region's permutation to it.
For a constant of lower rank than the activation the pass emitted a view_copy.
Both ops copy, but they copy differently -- view_copy preserves flat order and
permute_copy reorders -- so a reshape stands in for the permutation only when
no non-unit axis moves:

    b = torch.arange(4 * 8 * 8).reshape(4, 8, 8)
    torch.ops.aten.view_copy.default(b, [8, 8, 4])[0, 0, :4]     # [0, 1, 2, 3]
    torch.ops.aten.permute_copy.default(b, [1, 2, 0])[0, 0, :4]  # [0, 64, 128, 192]

Same shape, different values.

Broadcasting widens the constant to the region's rank before the permutation
applies, so widen it and ask where each non-unit axis lands. If those
destinations stay in order the permutation only shuffles unit axes, and a
single view_copy still expresses it -- the ordinary per-channel constant takes
this path and is unchanged. Otherwise the constant is widened and permuted.
Comparing extents rather than axis order would be wrong: two axes of the same
size can swap without the extents changing.

The old arithmetic also sliced the permuted shape back down to the constant's
rank, which is not where the surviving axes necessarily are. A rank-1
per-channel constant under [0, 2, 3, 1] wants (1, 1, C, 1) and the slice asked
for (1,), so export failed outright; it now lowers.

Checked by enumeration over every region rank 2..5, every permutation, every
constant rank below it and every extent combination in {1,2,3}: 15414
configurations, no disagreement with reshaping to region rank and permuting,
6810 of them still taking the single-view path.

Cadence is the exposed backend: it runs this pass twice in its default pipeline
and has no rank-matching pass. Arm reaches the pass at equal rank almost always,
because MatchArgRanksPass widens broadcast operands beforehand.

Authored with Claude Code.
@rascani
rascani force-pushed the transforms-constant-permute-fix branch from d2f0e8b to 517882b Compare August 25, 2026 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant