Skip to content

Lower adaptive pooling to output size 1 as a global reduce in 1D - #2787

Open
LeSingh1 wants to merge 1 commit into
apple:mainfrom
LeSingh1:fix-adaptive-pool1d-symbolic
Open

Lower adaptive pooling to output size 1 as a global reduce in 1D#2787
LeSingh1 wants to merge 1 commit into
apple:mainfrom
LeSingh1:fix-adaptive-pool1d-symbolic

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

nn.AdaptiveAvgPool1d(1) over a flexible length — global pooling across a variable-length sequence, the standard head of a 1-D convnet — aborts conversion:

import torch, torch.nn as nn, coremltools as ct

model = nn.AdaptiveAvgPool1d(1).eval()
x = torch.rand(1, 64, 8)
ct.convert(
    torch.jit.trace(model, x),
    inputs=[ct.TensorType(shape=ct.Shape([1, 64, ct.RangeDim(upper_bound=20)]))],
    convert_to="mlprogram",
)
File ".../sympy/core/expr.py", line 375, in __float__
    raise TypeError("Cannot convert expression to float")
TypeError: Cannot convert expression to float

_adaptive_pool1d always slices the input into one window per output element, computing the window boundaries in python from the pooled dimension:

start = _math.floor(i * in_dimension / out_dimension)
end = _math.ceil((i + 1) * in_dimension / out_dimension)

For a flexible input in_dimension is a sympy symbol, so floor / ceil raise out of sympy, from the middle of the converter with no indication of what the user did wrong.

The 2-D equivalent has no such problem: _adaptive_pool2d special-cases output size (1, 1) into a global reduce_mean / reduce_max, which needs no window boundaries, so nn.AdaptiveAvgPool2d((1, 1)) over a flexible input converts today. The 1-D path just never got the same treatment.

Fix

Give _adaptive_pool1d the two cases _adaptive_pool2d already has:

  • output size == 1 → a single global reduce over the last axis. This is exactly what the window loop already computes for that case (its one window spans the whole dimension), so the result is unchanged for static shapes — it just no longer needs to know the size.
  • any other output size with a symbolic input shape → the same clear ValueError _adaptive_pool2d raises, instead of a sympy TypeError.

Tests

  • TestAdaptiveAvgPool::test_adaptive_avg_pool1d_symbolic_inputnn.AdaptiveAvgPool1d(1) with a RangeDim length. Fails with the sympy TypeError without the fix.
  • TestAdaptiveAvgPool::test_adaptive_avg_pool1d_output_size_1 — output size 1 with static shapes over rank 3 and rank 2 input, pinning the new global-reduce path against torch. The existing test_adaptive_max_pool1d parametrizations only use output sizes 3 and 5, so output size 1 was uncovered.

The symbolic test skips the torch.export frontends, which decompose AdaptiveAvgPool1d(1) into a mean op before the converter sees it.

Verification

pytest coremltools/converters/mil/frontend/torch/test/test_torch_ops.py -k "TestAdaptiveAvgPool or TestAdaptiveMaxPool"

618 passed, 2 skipped on macOS / Apple silicon, torch 2.12, across mlprogram + neuralnetwork and the TorchScript / TorchExport / ExecuTorch frontends.

nn.AdaptiveAvgPool1d(1) / nn.AdaptiveMaxPool1d(1) over a flexible length,
i.e. global pooling across a variable-length sequence, aborts conversion with

    File ".../sympy/core/expr.py", line 375, in __float__
    TypeError: Cannot convert expression to float

_adaptive_pool1d always slices the input into per-output-element windows,
whose boundaries it computes in python from the pooled dimension. That
dimension is a sympy symbol for a flexible input, so the floor/ceil of the
window boundaries raises out of sympy.

_adaptive_pool2d already special-cases output size == (1, 1) into a global
reduce_mean / reduce_max, which needs no window boundaries and so works with
a symbolic shape; nn.AdaptiveAvgPool2d((1, 1)) converts today. Give the 1D
path the same treatment, and let the remaining output sizes report the same
clear ValueError that _adaptive_pool2d raises instead of surfacing a sympy
error from the middle of the converter.
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.

1 participant