Updating SPEC 0 for NumPy 2.0 transition
The current version of SPEC 0 says that in Q3 2025, NumPy 1.26 can be dropped. This will make NumPy 2.0 the minimum supported version just over a year after its release in June 2024. Given that the transition throughout the ecosystem to support NumPy 2.0 at all isn't yet complete even for well-known open source packages (xref https://github.com/numpy/numpy/issues/26191 e.g., Catboost became available only today, PySpark, Basemap, scikit-sparse and sagemaker-python-sdk don't yet have a compatible release), this is way too early.
The problem is to some extent the same as gh-190, which links to the example of Matplotlib 1.5 to 2.0 transition where there was a larger gap in the release cycle. There is an additional problem though, in that the NumPy 2.0 transition was quite a bit larger and longer than the Matplotlib one, and it requires a wider support window. As a result of the slow availability of open source packages, many companies and institutions are still blocked from upgrading to NumPy 2.0 today. New releases of other projects appearing that already start dropping numpy 1.x support is very much undesirable, it makes the puzzle of what package versions go together too complex.
The window for regular NumPy releases is roughly 2 years into the past, 1.5 years into the future - i.e., a 3.5 year wide sliding window. For NumPy 2.0, it was not possible to do the "into the future" part; the first releases with support for NumPy 2.0 from other projects that depend on NumPy's C A APIs started being made in April 2024. The sequence of chained releases to add support for NumPy 2.0 is very long. So I think we're going to need a 2 year window forward from that at least, so NumPy 1.26.4 support should not be dropped before April 2026.
so NumPy 1.26.4 support should not be dropped before April 2026.
Would it be sufficient to extend the period over which bug-fix releases are provided? That is, feature releases after Q3 2025 could drop support for NumPy 1.26 as scheduled, but the last feature release to support 1.26 should continue to receive bug-fixes until at least April 2026?
No, that isn't sufficient. Also not very useful, since doing extra releases would significantly increase the overall maintenance burden on projects rather than slightly decrease it by dropping older versions.
Thanks for your patience. I'm trying to understand this part.
Also not very useful, since doing extra releases would significantly increase the overall maintenance burden on projects rather than slightly decrease it by dropping older versions.
I think "since doing extra releases would significantly increase the overall maintenance burden on projects" means that doing long-term support bug-fix releases for version $X$ (which supports NumPy 1.26.4), in addition to feature releases for version $X+1$ and beyond (which do not), is more work than the status quo recommended by SPEC 0. Whether this would be useful overall, considering that it eases other aspects of development (e.g. by not requiring simultaneous consideration of pre- and post- NEP 50 promotion rules), is not obvious, and I respect your opinion that it is not.
But "rather than slightly decrease it by dropping older versions" doesn't sounds like it corresponds with the proposal to retain support for older versions than recommended by SPEC 0. It sounds like this would also increase maintenance burden beyond the status quo. Could you clarify?
Also, regarding:
No, that isn't sufficient.
Would you be willing to expand with some of the assumptions and reasoning that leads to that conclusion?
But "rather than slightly decrease it by dropping older versions" doesn't sounds like it corresponds with the proposal to retain support for older versions than recommended by SPEC 0. It sounds like this would also increase maintenance burden beyond the status quo. Could you clarify?
You're right, I could have phrased that better. Compared to the status quo in SPEC 0 today, it's indeed "not dropping older versions" which slightly increases maintenance burden. Even that is not always the case (something least work is to not do the work to drop a version and the associated cleanups), but either way, what I meant to say is that the effort involved here is quite small compared to doing extra releases.
Would you be willing to expand with some of the assumptions and reasoning that leads to that conclusion?
It doesn't address the problem I'm seeing coming, which is finding compatible versions of a stack of packages. If say SciPy would do extra 1.15.x bug fix releases for the next year, the support window for numpy<2.0 still cuts off at scipy<1.16.
The way I'm thinking about this is as moving windows of version ranges. Before the major release of NumPy came into the picture, that windows was always about 7 minor versions of numpy (example: scipy 1.10.1 supports numpy>=1.19.5,<1.27.0). Some projects will do a bit more, but that's about typical. It means that there's a lot of flexibility for users, distro packagers, and for the resolvers of tools like pip to find package versions that "line up". If one package in a dependency chain is a year older (e.g., because it didn't make a new release, or the user requests a specific version), there is usually no problem to still find versions that work together.
Now with numpy 2.0, we first had to tell everyone to add <2.0 constraints, because lots of things were likely to break. And then we asked them to do releases with 2.x compatibility. As a result, we have a set of releases that are only compatible with a smaller window, like 2 years (numpy>=1.23,<1.27 or so) and with a hard "break" in the middle rather than a smoothly moving 2 year window. And then we grew it again over the past 10 months, with more and more packages supporting a range like >=1.24,<2.3. There are still packages whose latest version is <2. So if we're now starting to get packages that start dropping 1.x, we get >=2.0,<2.6. What's critical is that it's then likely that we then get dependency chains for which the latest versions either:
- (a) both
<2and>=2.0(worst case, actually incompatible) - (b) only a single version of some packages that supports
2.0,2.1and/or2.2
Case (a) will be less common, but it will happen - this is already quite problematic, especially for distros. Case (b) will be fairly common if we start dropping numpy 1.x support soon. This will cause problems too, probably even more than case (a), because resolvers aren't great at finding a set of compatible versions if the window to find them in is very narrow. And even if they do manage to find a solution, it requires extra "backtracking" for pip (i.e., finding that the latest version isn't compatible, dropping that version, and trying an older one). That makes installs slower, downloads extra sdists/wheels, etc. even if it succeeds in the end (not guaranteed).
Monorepos are another problem. Think of them similar to Linux distros: you must have a single version of every package, and that has to be compatible with every other package in the monorepo/distro. So if you have some laggard package that isn't yet 2.0-compatible, you get the choice between delaying upgrading to 2.0, or temporarily removing the laggard package. Both not great. I have a decent amount of insight into how corporate deployments work, and there's a high percentage of companies that haven't upgraded their deployments yet to include numpy 2.0. That's going to take quite a while, because of the <2 bounds present in many packages. It's helpful for such use cases to continue being able to upgrade other packages. If other packages drop <2 compatibility too early, upgrades of those packages can't happen anymore, leaving all the upgrade work bunched to the time when a numpy 2.0 upgrade has happened - not ideal.
what I meant to say is that the effort involved here is quite small compared to doing extra releases.
👍 I haven't done releases of a big project, but I appreciate that they are a lot of work. If it were sufficient, I wanted to put the option on the table so that the cost/benefit decision could be explicit.
A few particularly important points from above, I think, with some commentary in parentheses.
- "The current version of SPEC 0 says that in Q3 2025, NumPy 1.26 can be dropped."
- "the first releases with support for NumPy 2.0 from other projects that depend on NumPy's C A APIs started being made in April 2024" (e.g. SciPy 1.13.0)
- "There are still packages whose latest version is
<2. So if we're now starting to get packages that start dropping1.x, we get>=2.0,<2.6." (For instance, according to SPEC 0, a mid-2025 SciPy 1.16.0 release would still have support for NumPy <2.0, but a late 2025/early 2026 SciPy 1.17.0 release would not support NumPy <2.0.)
So let me see if I can boil down one portion of the argument. To express the thoughts succinctly, I find it useful to refer to packages that have had several NumPy 2.0-compatible releases in the past year as "SciPy-like" and those that have not as "Catboost-like". No judgment is implied; I just find it easier to express the thoughts succinctly with these groups/labels.
If SciPy-like packages drop NumPy
<2.0support according to the SPEC 0 schedule in Q3 2025, it will be challenging (or impossible) to produce environments with the latest version of SciPy-like packages and any version of Catboost-like packages.
We are in this situation now because:
Developing for NumPy 2.0 was not easy until NumPy 2.0 and SciPy-like packages with NumPy 2.0 support were released in Q2 2024. Consequently, SPEC 0 effectively gave Catboost-like projects only a year and a few months to develop a NumPy 2.0-compatible release before SciPy-like projects drop NumPy <2.0 support in Q3 2025.
The conclusion is:
If we want to ameliorate the situation, all projects should continue to support NumPy <2.0 for long enough that Catboost-like projects have a few versions that support NumPy >= 2.0. This will make it (possible and) easier to produce environments with the latest releases of all projects.
Is that close enough to shared understanding to discuss further?
Yep, that's a pretty good summary I think.
Maybe useful to add this bit, since it's easy to forget how long things take and dependency chains are: projects that depend on Catboost-like projects can only start testing with and upgrading to NumPy 2.0 now.
Is the request that we adjust the SPEC dates for this specific NumPy 2.0 situation, or that we incorporate language that handle such cases more generally?
projects that depend on Catboost-like projects can only start testing with and upgrading to NumPy 2.0 now.
👍
But this emphasizes that even if "SciPy-like" projects take on the extra burden of extended support for NumPy <2.0 to compensate, we can't really avoid the problems, just reduce the likelihood/severity?
This suggests that we are looking for a sweet spot between two conflicting goals. Is it practical to quantify this? If not, is it just going to be a matter of reaching (lazy) consensus of where that sweet spot is based on personal opinions? (Probably yes, as usual. If so, I just want to be explicit about it, because some may hold the opinion that the extension from Q3 2025 to Q2 2026 would move us away from the sweet spot.)
Is the request that we adjust the SPEC dates for this specific NumPy 2.0 situation
It sounds like that was the request, but I've been thinking that part of what made NumPy 2.0 special (besides the difficulty of supporting it) was the duration between release of NumPy 1.26 and NumPy 2.0. Consider the situation if NumPy 2.0 had been released a full two years after 1.26. Then, according to SPEC 0, there would have been zero time for Catboost-like projects to develop NumPy 2.0-compatible releases to avoid the problem. I seem to remember SPEC 0 explicitly avoiding stating assumptions about release schedules, but maybe a change in the criterion would get us closer to what we really want to control while still being sufficiently simple/practical: drop support for version $X$ based on the release date of $X + \Delta$ (the next feature release after $X$) rather than the release date of $X$ itself?
drop support for version $X$ based on the release date of $X + \Delta$ (the next feature release after $X$) rather than the release date of $X$ itself?
This is along the lines of one of the differences with the schedule scikit-learn has converged on. cc @lesteve
pure Python dependencies: a bit more conservative than SPEC0. Our minimum supported version is at least 2 years old, whereas SPEC0 says you can drop support for a version if it's more than 2 years old. This also depends on our dependencies release cadence. joblib and threadpoolctl tends to not have very regular release cadences, compared to scikit-learn or numpy (roughly 2 minor releases a year). This makes SPEC0 too aggressive compared to what we have been doing in scikit-learn. For example for scikit-learn 1.8 release (scheduled 2025-12-01), SPEC0 would recommend to drop joblib 1.3 (1.3.0 is a bit more than 2 years old at the time of release), but the next minor joblib version 1.4.0 happened in April 2024 so it would be less than one year old in December 2025, which seems way too recent compared to what we have been doing historically (minimum version is roughly 2 years old).
https://github.com/scikit-learn/scikit-learn/issues/30888#issuecomment-2766112386
(Note that their rule for a compiled dependency like NumPy is instead to track support for their oldest supported Python)
Is the request that we adjust the SPEC dates for this specific NumPy 2.0 situation, or that we incorporate language that handle such cases more generally?
Specific to NumPy 2.0. In addition, yes it'd be good to also take care of gh-190, which is the "X + Δ" thing discussed above.
compensate, we can't really avoid the problems, just reduce the likelihood/severity?
Well it's packaging & releases of O(100,000) packages, so yes there will always be some problems in the long tail of that very large set of packages.
This suggests that we are looking for a sweet spot between two conflicting goals.
I really encourage people to not think of dropping a version of some dependency as a goal in itself. Just like deprecating things or expiring deprecations more aggressively isn't a (good) goal. The goal is to limit overall maintenance effort - i.e., the amount of wheels that need to be built, the number of CI configs that need to be run, the number of changes that need to be made to new PRs to make them work with old versions of a dependency, etc. In this case, keeping NumPy 1.26.4 support for 9 more months yields no extra wheels, no extra CI configs, and probably some amount of tweaking needed for new PRs.
I'd say that that is a pretty minor extra maintenance burden. If there's a counter-example for some specific package where the extra burden is not small, that would be useful to discuss.
is it just going to be a matter of reaching (lazy) consensus of where that sweet spot is based on personal opinions? (Probably yes, as usual.
Yes, correct.
but I've been thinking that part of what made NumPy 2.0 special (besides the difficulty of supporting it) was the duration between release of NumPy 1.26 and NumPy 2.0
That's a part of it, but the impact of the <2 pins is the larger part.
The obvious comparison to me here is Python 2 -> Python 3 and how py2.7 stuck around for longer than a normal Python version would. I am 👍🏻 on adding a similar special case for numpy 1.26 of "support until XX" to make this transition easier.
Something that was in NEP29 that seems to have gotten lost is moving to SEPC0 was a minimum of 2 Python minor versions and 3 minor Numpy versions. I suspect that a similar addition to SPEC0 would ameliorate the concerns about non-regular releases.
Also, I'll apologize for the mpl 1.5 -> 2.0 gap. "just change the defaults" ended up being more work than anticipated.
I really encourage people to not think of dropping a version of some dependency as a goal in itself.
? Of course. The conflicting goals in question were 1) reducing maintenance/development burden and 2) ensuring that the latest versions of packages can be installed in the same environment.
The conflicting goals in question were 1) reducing maintenance/development burden
Yes, in the general case that's what the SPEC tries to do. It's up to interpretation of course: I do not expect the maintenance burden to go down when dropping numpy 1.26 soon. It'll probably yield more issues and other support requests for the project itself, in addition to extra costs for users. It's certainly not a given that supporting ever fewer versions of dependencies keeps yielding gains in maintainability. So I'm saying that the conflicting goals don't exist in this case.
So I'm saying that the conflicting goals don't exist in this case.
Thank you for rephrasing. I see that dropping NumPy 1.26 support might lead to an uptick in support requests, so perhaps the overall burden of dropping it is higher. But I agree that "It's up to interpretation" because the overall balance is hard to measure objectively. There may also be pain points associated with supporting NumPy <2.0 and >2.0 simultaneously. Have you experienced any?