There seem to be a lot of questions on why we designed the proposal with variants and providers, so let me talk about the history of this PEP.
The initial problem is that we want to support GPUs. It’s one of the biggest pain points in Python packaging right, many of our users mention this to us, and it’s also a pain massively felt on the side of those building GPU wheels. This initiative was started by nvidia folks, who reached out to other interested parties. That’s how I came to work the proposal, seeing that someone was building a solution to our biggest user pain. The initiative now includes people from torch, Nvidia, Intel, AMD, Quantsight, Astral, Linux distros and work from many more groups and projects.
The first approach would be extending the wheel filename with another segment, torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64.whl becomes torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64_cuda126.whl. This has some problems: The big one is that the rules around GPU support are way more complex, and change much more frequently. For example, the wheel is built against specific SM, do we need to track this as torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64_cuda126_sm60_sm70_sm80_sm90.whl? Do we need a new PEP when the next CUDA major version changes its compatibility rules? What about TPUs? It also doesn’t handle CPU levels/extensions, and it requires adding each new GPU vendor separately, which unlike CPUs (which have one compact arch tag generally) all have different rules. There was also some concern about wheel filenames become even longer and unwieldy.
The alternative is to introduce variant label: You say torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64-cuda126.whl and add a metadata file explaining what cuda126 means, which SM it entails and how to match it. This metadata file are the variants.json (both in-wheel and on the index) in the PEP. This file could say e.g. that you need to detect the CUDA and the x86_64 level to select the best wheel.
In the wheel filename, Python implementation, CPU and OS are all available through Python std ABIs (with the exception of manylinux). GPUs on the other hand are not available through std, so we have to define something separately, and use non-std code for it .
A variant gets mapped to one or more providers, which explains what you need to detect. Each provider has code for running this detection, and return the result in the shared property format. For the main providers that we want to support out of the box, there will be library that you can call directly to get the properties to provide on out-of-the-box working install experience for GPU enabled packages. The PEP specifies a PEP 517-like interface to install arbitrary providers, so that different providers can be used without redefining an interface each time. But that’s something that’s not crucial, we could also do with just specifying that these providers exist and that they return a list of properties, without putting a universal interface in the PEP or tools.
The goal is that tool authors can use this provider code (through a sharedly maintained library) without having to understand or maintain something as complex as CUDA. In the current version of the PEP, we are very abstract just talking about providers, vendoring and not about governance. I consider this partially my fault, because I pushed for keeping these abstract to not get bogged down in the governance of such a library, or different tools preferring regular dependencies, vendoring or allowlists respectively. We got the feedback from multiple packaging tooling maintainers that they want to have a collection of providers in variantlib , so we’ll update the PEP to specify a variantlib with a Python interface to the default providers (I think Michał has an upcoming post about what specific format we’re aiming for, using “variantlib” here as it’s the evident option). variantlib will be a place of shared maintainership between tool authors, library authors and hardware vendors (who all contributed to this PEP and the prototype providers that come with it). To stay with my favourite example, I don’t think most packaging tooling maintainers don’t understand how manylinux/musllinux detection works internally, and they don’t need to: There’s a library that does all the wicked ctypes stuff, and you call that. Personally, I’ve been thinking about pypa/packaging and manylinux as reference examples where we already have community process for doing updates around a core defined by PEPs.
Coming back to the motivation for variants and providers, after adding a variants file and providers, we realized that this solves more problems than just GPUs and maybe CPU extensions. With variants, we can finally encode BLAS and OpenMP libraries (something numpy currently struggles with) and it looks like we can even encode ABI compatibility (which allows projects such as vllm publish wheels). For me, the amount of additional things variants enable sway me strongly towards variants over a fixed label extension.