Add MaxPool1D decomposition pass support (#17022) - #21446
Conversation
Summary: Implement DecomposeMaxPool1dPass to enable MaxPool1D support on ARM backend by decomposing max_pool1d into unsqueeze_copy → max_pool2d → squeeze_copy. ## Implementation Strategy ### Decomposition Approach (Optimal for TOSA/Vela) The pass decomposes max_pool1d into max_pool2d via unsqueeze_copy/squeeze_copy operations: 1. unsqueeze_copy(dim=2): (N, C, L) → (N, C, 1, L) - add height dimension 2. max_pool2d: with adapted params [k]→[1,k], [s]→[1,s], [p]→[0,p], [d]→[1,d] 3. squeeze_copy(dims=[2]): (N, C, 1, L_out) → (N, C, L_out) - remove height dimension ### Why This Approach is Optimal 1. **unsqueeze_copy and squeeze_copy map to TOSA RESHAPE** which is zero-cost in Vela: - Classified as memory_only_ops (Reshape, Squeeze, ExpandDims, Identity) - Bypassed entirely when conditions met (NPU-produced, single consumer) - Tensor equivalence enables memory aliasing (same address) 2. **TFA Pipeline Placement (before quantization)**: - unsqueeze_copy.default is in _one_to_one_shared_input_qspec - squeeze_copy.dims is added to _one_to_one_shared_input_qspec - max_pool2d is in _one_to_one_shared_input_or_input_act_qspec - All get proper SharedQuantizationSpec from the annotator automatically 3. **Quantization Handling**: - Clear qparams on intermediate unsqueeze_copy and squeeze_copy ops (let annotator fill them) - Preserve original meta on max_pool2d for proper tracing - MAX_POOL2D doesn't need zero-point handling (unlike AVG_POOL2D) ### TOSA/Vela Constraints Validated - U55: Stride ≤3 ✓, Kernel ≤256x256 ✓ - U85: Extended stride support via accumulator save/restore - Dilation: Handled by separate DecomposeMaxPool2dPass if needed Reviewed By: 3l1 Differential Revision: D91760459
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21446
Note: Links to docs will display an error until the docs builds have been completed. ❌ 2 New Failures, 2 Unrelated FailuresAs of commit 501e2ea with merge base 32f56be ( NEW FAILURES - The following jobs have failed:
FLAKY - The following jobs failed but were likely due to flakiness present on trunk:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
There was a problem hiding this comment.
Pull request overview
This PR adds Arm-backend support for aten.max_pool1d by introducing a decomposition pass that rewrites MaxPool1D into an equivalent MaxPool2D sequence (unsqueeze_copy → max_pool2d → squeeze_copy) and wires it into the Arm pass pipelines and quantization annotator so the resulting intermediate ops are properly annotated.
Changes:
- Add
DecomposeMaxPool1dPassto rewriteaten.max_pool1dinto 2D pooling plus view-like reshapes. - Register the new pass in the Arm pass manager and
_passespackage exports. - Update Arm quantization annotation to treat
aten.squeeze_copy.dimsas a shared-input qspec op; enable the previously xfailed TOSA INT MaxPool1D test.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| backends/arm/test/ops/test_max_pool1d.py | Un-xfails the TOSA INT MaxPool1D decomposition test. |
| backends/arm/quantizer/quantization_annotator.py | Adds squeeze_copy.dims to shared-input qspec ops to support the new decomposition intermediates. |
| backends/arm/_passes/decompose_max_pool1d_pass.py | Introduces the MaxPool1D→MaxPool2D decomposition pass. |
| backends/arm/_passes/arm_pass_manager.py | Inserts the new decomposition pass into relevant Arm pipelines. |
| backends/arm/_passes/init.py | Exports DecomposeMaxPool1dPass from the passes package. |
Comments suppressed due to low confidence (1)
backends/arm/_passes/decompose_max_pool1d_pass.py:54
- This decomposition assumes the max_pool1d input is 3D (N, C, L). However, max_pool1d can also be called with 2D inputs (C, L); in that case
unsqueeze_copy(dim=2)produces a rank-3 tensor andmax_pool2dwill fail due to incorrect rank. Consider guarding on input rank (or explicitly handling 2D) before applying the rewrite.
# Extract and normalize arguments
x = args[0]
kernel_size = _normalize_to_list(args[1])
stride = _normalize_to_list(
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _normalize_to_list( | ||
| value: Optional[Union[int, List[int], tuple]], | ||
| default: Optional[List[int]] = None, | ||
| ) -> List[int]: | ||
| """Normalize parameter to list: handle None, int, tuple, list.""" | ||
| if value is None: | ||
| if default is None: | ||
| raise ValueError("Value cannot be None without a default") | ||
| return default | ||
| if isinstance(value, int): | ||
| return [value] | ||
| return list(value) |
Summary:
Implement DecomposeMaxPool1dPass to enable MaxPool1D support on ARM backend
by decomposing max_pool1d into unsqueeze_copy → max_pool2d → squeeze_copy.
Implementation Strategy
Decomposition Approach (Optimal for TOSA/Vela)
The pass decomposes max_pool1d into max_pool2d via unsqueeze_copy/squeeze_copy
operations:
Why This Approach is Optimal
unsqueeze_copy and squeeze_copy map to TOSA RESHAPE which is zero-cost in Vela:
TFA Pipeline Placement (before quantization):
Quantization Handling:
TOSA/Vela Constraints Validated
Reviewed By: 3l1
Differential Revision: D91760459
cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani