Fix XNNPACK pooling crashing on default stride and single-element kernel_size - #21489
Open
slipstr34m wants to merge 1 commit into
Open
Fix XNNPACK pooling crashing on default stride and single-element kernel_size#21489slipstr34m wants to merge 1 commit into
slipstr34m wants to merge 1 commit into
Conversation
…nel_size The aten pool2d schemas declare stride with a default of the empty list, meaning kernel_size, and declare kernel_size, stride, padding and dilation as int[2], which accepts a scalar or a 1-element list that broadcasts to both dimensions. The XNNPACK partitioner configs and visitors indexed node.args positionally and assumed a fully populated 2-element list, so F.max_pool2d(x, 2) raised IndexError from inside the partitioner's own constraint check, and a 1-element kernel_size raised while computing the pooling region. normalize_pool2d_args applies the schema defaults in one place, next to the existing normalize_mean_dims which exists for the same reason on mean.dim. Fixes pytorch#10968 Fixes pytorch#10969
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21489
Note: Links to docs will display an error until the docs builds have been completed. This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #21488
Fixes #10968
Fixes #10969
Problem
The aten schemas are
Two schema facts the XNNPACK pooling code does not account for.
stridedefaults to the empty list, meaning "use kernel_size", and may be absent fromnode.argsentirely. Andint[2]accepts a scalar or a 1-element list, which broadcasts to both dimensions.MaxPool2dConfig.check_constraintsindexed positionally:and
AvgPoolingConfig.check_constraintsdidBoth assume a fully populated 2-element list, so three plain uses of the documented API raise
IndexErrorfrom inside the partitioner's own constraint check, before any support decision is reached:The visitors index the same way, so even a node that passed the config would fail during serialization.
Declaring
target_name = "max_pool2d.default"promises the whole schema. The identical models with an explicit 2-element stride lower and run correctly today.Fix
One helper,
normalize_pool2d_args, placed next to the existingnormalize_mean_dimsinbackends/xnnpack/utils/utils.py, which exists for exactly this reason onmean.dimand is already called from both that config and that visitor:It is called from
AvgPoolingConfig.check_constraints,MaxPool2dConfig.check_constraints,MaxPooling2d.define_nodeandAveragePooling2d.define_nodein place of the raw indexing.This is the same normalization five other backends here already do, so "the partitioner may assume canonical args" is contradicted by standing practice in the repo.
backends/arm/_passes/rewrite_max_pool2d_pass.py:82-87is structurally the same helper:backends/samsung/builders/op_max_pool2d.py:51-55does the same, includingstride = cast(List[int], node.args[2]) if len(node.args) > 2 else kernel_size.backends/mlx/ops.pycarries the commentif not stride: # empty list means default to kernel_size.backends/qualcomm/builders/op_max_pool2d.py:63-76andbackends/apple/mps/operators/convolution_ops.py:53-57both broadcast 1-element kernel, stride, padding and dilation.AvgPoolingConfigalready conceded the point internally. It guardslen(args) >= 5,>= 6and>= 7forceil_mode,count_include_padanddivisor_overrideon the lines around the crash. It knows arg tuples arrive short, it just started guarding at index 5 instead of index 2.Two incidental cleanups caused by the change, called out so they are not a surprise in review: the
# pyre-ignore[16]on the stride comparison is dropped becausestrideis now typed, andcast/Listimports that became unused are removed.Effect
Same script run on either side of the change.
delegatedis whether the node reached XNNPACK,maxdiffis deviation from eager.The negative control is the important one. Normalization could have turned "correctly decline" into "delegate something XNNPACK cannot do", and it does not.
Declining these nodes instead of normalizing them would also have fixed the crash, but it is strictly worse. All four cases match eager once they delegate, so XNNPACK does support them, and silently dropping
F.max_pool2d(x, 2)out of the delegate would be a performance regression on one of the most common CNN patterns there is.Blast radius
Only nodes whose pooling args are currently missing or scalar, which is exactly the set that raises today. For fully specified 2-element args the helper returns the same lists, so existing behaviour is unchanged. No serialization format or runtime change; the
.pteschema already carries separate height and width fields.Testing
Four cases added, two per file.
test_maxpool2d.pygets default stride and a 1-elementkernel_size.test_avgpool2d.pygets a 1-elementkernel_size, and a 1-elementkernel_sizewithdivisor_override=5. The second matters because normalizing turns thedivisor_override != pooling_regioncomparison from a crash into a live code path, and it must still decline, since the region is 3 * 3 rather than 5.Verified failing-first by reverting only the four source files and keeping the tests: all four fail with
IndexErroratgeneric_node_configs.py:352,:360and:184.Which spellings are affected, for reference:
nn.MaxPool2dsuppliesstridefromkernel_sizein its own__init__, and a bare int is expanded to[n, n]before the op is called, so the common spelling is safe on both counts and existing coverage never reaches these paths.lintrunnerreports no issues on all six changed files.