Remove permutes around fused_quant elementwise ops - #21481
Conversation
Summary: `ConvToChannelsLast` wraps every conv in `permute(NCHW->NHWC) -> conv -> permute(NHWC->NCHW)`. When convs are joined by elementwise fused_quant ops (residual add/mul, activations), permutes end up threaded through the surrounding region and were never removed: the existing Cadence `RemovePermutesAroundElementwiseOps` only recognizes aten/cadence elementwise ops, not the SAS fused_quant ops -- and it would also wrongly treat their lifted scale/zero_point operands as constants to be permuted. This adds a fused_quant-aware permute-removal pass and wires it into the edge optimization group: - Extend the shared ExecuTorch `RemovePermutesAroundElementwiseOps` with a small overridable seam (`_permute_relevant_inputs`) so a subclass can hide operands from layout propagation. Behavior-preserving for existing users. - New `RemovePermutesAroundFusedQuantElementwiseOps` (SAS) subclasses it, adding `fused_quant.add`/`mul` and the activation ops as permutable and exposing only their tensor operands, so the lifted scale/zero_point placeholders are never permuted/compensated (which would break lowering). Ops with per-channel qparams are skipped (not permutation-invariant). - Replace the two Cadence permute passes (which no-op pre-Lower) in the optimization group with this single pass. - Teach the shared subgraph engine about "permutation-sink" flattens: a `view_copy` whose input has <=1 non-unit dim (e.g. the `[1, C, 1, 1] -> [1, C]` after a global pool) is layout-invariant, so a permutation flowing into it simply dies. The region can terminate cleanly there with no compensating permute -- which lets the residual-block permutes collapse across the avgpool -> flatten -> classifier head instead of being stranded by it. Note: fused_quant is currently SAS-specific, NOT yet a generic cross-backend dialect, so the fused_quant knowledge deliberately stays in the SAS subclass rather than the shared ExecuTorch pass. When fused_quant graduates to a shared dialect, this can fold into the base pass via `extra_permutable_ops` + the seam. (The permutation-sink flatten handling is generic and correctly lives in the shared pass.) On resnet18 the optimized graph goes from 83 permutes down to a single one (the model-input boundary); every permute around the residual add/relu blocks and across the global-pool flatten is removed. Differential Revision: D113424191
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21481
Note: Links to docs will display an error until the docs builds have been completed. ⏳ No Failures, 65 PendingAs of commit 4a3b871 with merge base a1d4d35 ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@mcremon-meta has exported this pull request. If you are a Meta employee, you can view the originating Diff in D113424191. |
This PR needs a
|
Summary:
ConvToChannelsLastwraps every conv inpermute(NCHW->NHWC) -> conv -> permute(NHWC->NCHW). When convs are joined by elementwise fused_quant ops(residual add/mul, activations), permutes end up threaded through the
surrounding region and were never removed: the existing Cadence
RemovePermutesAroundElementwiseOpsonly recognizes aten/cadence elementwiseops, not the SAS fused_quant ops -- and it would also wrongly treat their
lifted scale/zero_point operands as constants to be permuted.
This adds a fused_quant-aware permute-removal pass and wires it into the edge
optimization group:
RemovePermutesAroundElementwiseOpswith asmall overridable seam (
_permute_relevant_inputs) so a subclass can hideoperands from layout propagation. Behavior-preserving for existing users.
RemovePermutesAroundFusedQuantElementwiseOps(SAS) subclasses it,adding
fused_quant.add/muland the activation ops as permutable andexposing only their tensor operands, so the lifted scale/zero_point
placeholders are never permuted/compensated (which would break lowering).
Ops with per-channel qparams are skipped (not permutation-invariant).
optimization group with this single pass.
view_copywhose input has <=1 non-unit dim (e.g. the[1, C, 1, 1] -> [1, C]after a global pool) is layout-invariant, so a permutation flowing into it
simply dies. The region can terminate cleanly there with no compensating
permute -- which lets the residual-block permutes collapse across the
avgpool -> flatten -> classifier head instead of being stranded by it.
Note: fused_quant is currently SAS-specific, NOT yet a generic cross-backend
dialect, so the fused_quant knowledge deliberately stays in the SAS subclass
rather than the shared ExecuTorch pass. When fused_quant graduates to a shared
dialect, this can fold into the base pass via
extra_permutable_ops+ theseam. (The permutation-sink flatten handling is generic and correctly lives in
the shared pass.)
On resnet18 the optimized graph goes from 83 permutes down to a single one (the
model-input boundary); every permute around the residual add/relu blocks and
across the global-pool flatten is removed.
Differential Revision: D113424191
cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani