Re-add support/wheels for python 3.10
I work on a library that makes pretty heavy use of arb (context). Currently, we bundle arb/flint/mpfr/gmp binaries into our wheels, but we'd like to stop doing that and depend on python-flint instead. Unfortunately we both require features that were only introduced in 0.7.0, and are not yet ready to drop support for python 3.10 (whereas y'all stopped publishing wheels for it in 0.7.0). Ideally, we'd love it if python-flint could start publishing binaries for python 3.10 again, either by adding them to the existing set, or building wheels that are not python-version-dependent.
It is possible to use python-flint with Python < 3.11 but no wheels are provided and you would have to manually edit requires-python in pyproject.toml. The requires-python>=3.11 is just set so that tools like pip would backtrack to an older version of python-flint on an older version of Python rather than trying to build from source when wheels are unavailable.
After gh-337 it is possible to build stable ABI wheels with Cython 3.2 (currently in beta stage). That makes it possible to build a single wheel that works with a range of Python versions so then providing wheels for more Python versions does not mean multiplying the number of wheels and the total upload size for PyPI.
What I am not sure about is the speed implications of using the stable ABI rather than building wheels for each Python version. Even if it is slower though at least it would be better to have a stable ABI wheel as a fallback for Python versions that we don't publish separate wheels for.
A stable ABI wheel can be built with:
pip install cython==3.2.0b3
pip wheel . --no-build-isolation \
-Csetup-args=-Dflint_version_check=false \
-Csetup-args=-Dpython.allow_limited_api=True \
-Csetup-args=-Dlimited_api_version=3.10
(See https://github.com/mesonbuild/meson-python/issues/787 for why this is configured so awkwardly.)
Using Cython with the stable ABI is a new feature added in Cython 3.1 but because of a bug https://github.com/cython/cython/issues/7144 we need Cython 3.2 for python-flint.
Using Cython with the stable ABI is a new feature added in Cython 3.1 but because of a bug cython/cython#7144 we need Cython 3.2 for python-flint.
The fix should be in Cython 3.1.4 I believe (although given that 3.2 is very near, maybe that doesn't matter)
Thanks, I just tried Cython 3.1.6 and it all seems to be working.
I've been playing around with the stable abi wheels, and they seem to work. In particular, I built a python 3.10 stable ABI wheel (using Cython 3.2, since that's out now), and then tested in on pythons 3.10-3.13. The full test suite works on every python version I tried.
From what I've read, I wouldn't expect the stable ABI to have a big performance impact here because most of the work happens in C functions. I tried timing 100 runs of the test suite using both stable ABI and regular wheels, and the difference looks pretty minor:
| Python Version | Regular time | Stable ABI Time | % change |
|---|---|---|---|
| 3.10 | 82.179 | 85.4944 | +4% |
| 3.11 | 80.1139 | 82.1283 | +2.5% |
| 3.12 | 91.1226 | 89.282 | -2% |
| 3.13 | 86.6954 | 88.7888 | +2.4% |
Obviously this is only one machine (an arm mac) - it might be worth checking it on other platforms to see if that holds up.
We have a two main options for how to incorporate stable ABI wheels:
- We could replace all the regular cpython wheels with stable ABI wheels. Currently there are 20 of those ( [mac x68, mac arm, linux aarch64, linux x86, windows x86] X [3.11, 3.12, 3.13, 3.14] ), and we could reduce that to 5 (1 wheel per architecture). Free-threading pythons would still need separate wheels, as cython doesn't support stable ABI wheels for them yet. That should knock 100-150 MB off each release, and allow support for python 3.10, but it means that every user has to pay the performance penalty.
- We could add stable ABI wheels to the existing selection, and remove some (but not all) of the existing wheels. The most obvious choice there might be to replace the 3.11 wheels with stable ABI, which would add 3.10 support, roughly maintain the size of each release, and mean that anyone who uses 3.12 or newer doesn't pay the performance cost. But you could also do something intermediate, where we keep the 1-2 most recent python versions, and everything older is covered by stable ABI wheels (somewhat reducing the release size, and somewhat increasing the number of users paying the performance penalty).
Given that there seems to be a relatively small performance penalty, I would suggest option 1.
The main thing that needs investigating is what the performance impact is and where it is (or is not) noticeable. For some types a small amount of overhead can be important e.g. for fmpz, nmod, arb. It might be that the performance differences associated with stable ABI are not relevant for them.
Option 1 is much simpler and easier to implement than option 2 but I'm not sure yet what the tradeoffs are.
Sounds like we would ideally like a set of benchmarks broken down by type, but I don't think I have enough of a sense of how most of the types are used to write them (I could maybe do arbs).
A simpler approach would be to use the test functions as benchmarks, but break them down by type (rather than just taking the total time to run the test suite, as I did above). I would be happy to put something together, if that seems useful.
First thing to check is just basic operations like:
In [6]: from flint import fmpz as Z
In [7]: a = Z(1)
In [8]: b = Z(2)
In [9]: %timeit a*b
140 ns ± 2.16 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
If the stable ABI doesn't make much different to that sort of operation then any performance implications are likely not relevant for python-flint. These sort of measurement need to be done carefully though with release mode build settings and so on.
The Cython docs also suggest that setting the limited API version to 3.12 or newer might give better performance so there could be a case for having cp310-abi3 wheels and then also cp312-abi3 wheels. That is to do with vectorcall though and I don't know if that is relevant here.
Okay, I can put together some simple benchmarks along those lines - I'll send them as a PR and you can see if you think I've missed anything.
When you say "release mode build settings", I assume you mean building with -Dbuildtype=release, and maybe also using cibuildwheel? Is there anything else?
Yes -Dbuildtype=release is needed for release mode build settings. The default build settings are release with meson-python (unlike meson more generally). The coverage build is also off by default and should be off for any benchmarking.