From f81a8256fc4372ad1f166afa359a7f5f768ea144 Mon Sep 17 00:00:00 2001 From: Jonathan DEKHTIAR Date: Wed, 10 Dec 2025 16:31:12 -0500 Subject: [PATCH 01/13] PEP 817: Wheel Variants: Beyond Platform Tags --- .github/CODEOWNERS | 2 + peps/pep-0817.rst | 2494 +++++++++++++++++ .../appendix-variant-metadata-json-schema.rst | 11 + peps/pep-0817/avx512_gromacs_benchmark.png | Bin 0 -> 50433 bytes peps/pep-0817/pytorch_variant_selector.png | Bin 0 -> 30716 bytes peps/pep-0817/variant_schema.json | 163 ++ 6 files changed, 2670 insertions(+) create mode 100644 peps/pep-0817.rst create mode 100644 peps/pep-0817/appendix-variant-metadata-json-schema.rst create mode 100644 peps/pep-0817/avx512_gromacs_benchmark.png create mode 100644 peps/pep-0817/pytorch_variant_selector.png create mode 100644 peps/pep-0817/variant_schema.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a0a6ab363d9..41561368204 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -691,6 +691,8 @@ peps/pep-0811.rst @sethmlarson @gpshead peps/pep-0814.rst @vstinner @corona10 peps/pep-0815.rst @emmatyping peps/pep-0816.rst @brettcannon +peps/pep-0817.rst @DEKHTIARJonathan @mgorny @konstin @rgommers @atalman @charliermarsh @msarahan @seemethere @warsaw @dstufft @aterrel +peps/pep-0817/ @DEKHTIARJonathan @mgorny @konstin @rgommers @atalman @charliermarsh @msarahan @seemethere @warsaw @dstufft @aterrel # ... peps/pep-2026.rst @hugovk # ... diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst new file mode 100644 index 00000000000..a93be6f023f --- /dev/null +++ b/peps/pep-0817.rst @@ -0,0 +1,2494 @@ +PEP: 817 +Title: Wheel Variants: Beyond Platform Tags +Author: Jonathan Dekhtiar , + Michał Górny , + Konstantin Schütze , + Ralf Gommers , + Andrey Talman , + Charlie Marsh , + Michael Sarahan , + Eli Uriegas , + Barry Warsaw , + Donald Stufft , + Andy R. Terrel +Discussions-To: Pending +Status: Draft +Type: Standards Track +Topic: Packaging +Created: 10-Dec-2025 +Post-History: + + +Abstract +======== + +The Python wheel packaging format uses +:doc:`packaging:specifications/platform-compatibility-tags` to specify +a given wheel’s supported environments. These tags are unable to express +features of modern hardware configurations, such as the availability of +GPU acceleration, or to provide custom package variants, such as builds +against different dependency ABIs. This is particularly challenging for +the scientific computing, artificial intelligence (AI), machine learning +(ML), and high-performance computing (HPC) communities. + +This PEP proposes “Wheel Variants,” an extension to the +:doc:`packaging:specifications/binary-distribution-format`. This +extension introduces a mechanism for package maintainers to declare +multiple build variants for the same package version, while allowing +installers to automatically select the most appropriate variant based on +system hardware and software characteristics. More specifically, it +proposes: + +- An evolution of the wheel format called **Wheel Variant** that allows + wheels to be distinguished by hardware or software attributes. + +- A **variant provider plugin** interface that allows installers to dynamically detect + platform attributes and select the most suitable wheel. + +The goal is for a familiar ``[uv] pip install `` to provide +the best user experience. + + +Definitions +=========== + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, +“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this +document are to be interpreted as described in :rfc:`2119`. + + +Motivation +========== + +The Python packaging ecosystem has evolved to support increasingly +diverse computing environments. The current software ecosystem often +relies on platform-specific features to determine which binaries are +compatible with a particular machine. Unfortunately the current wheel +format cannot adequately express the diversity of features present +in modern hardware. + +For example, packages such as `PyTorch `_ need to +be built for specific CUDA or ROCm versions, and that information cannot +currently be included in the wheel tag. Having to build multiple wheels +targeting very different hardware configurations forces maintainers into +various distribution strategies that are suboptimal, and create friction +for users and authors of other software who wish to depend on the +package in question. + +A few existing approaches are explored in the subsequent subsections. +These include maintaining separate package indexes for different +hardware configurations, bundling all potential variants into a single +wheel of considerable size, or using separate package names +(``mypackage-gpu``, ``mypackage-cpu``, etc.). Each of these approaches +has significant drawbacks. + +According to the `2024 Python Developers +Survey `__, +a significant portion of respondents over the last years have been +successively using Python for scientific computing purposes, covering +such areas as Data analysis (steadily over 40% respondents), Machine +learning (grown to 40% in 2024) and Data engineering (around 30%). +Many of these use cases are directly impacted by suboptimal +packaging. + +This issue is often crossing the boundaries of scientific computing. +Wheels currently cannot express more specific CPU requirements, forcing +the maintainers to build and ship only for the oldest CPU baseline they wish to +support. Newer CPU features are sometimes utilized through runtime +dispatching, but for example this does not let software benefit from the +performance improvements related to auto-vectorization for x86-64-v4 +CPUs. + +As illustrated by `archspec `_, +the ability to optimize a package for a specific architecture can lead to +significant performance improvement. + + .. figure:: pep-0817/avx512_gromacs_benchmark.png + :alt: A bar graph comparing GROMACS performance (in ns/day) with + various targets. The first two bars are labeled "yum (2018.8)" + and "generic (SSE2)", reach about 1.0 ns/day and are both + marked as "SSE2". The next bar is labeled "ivybridge" ("AVX") + and reaches almost 1.5 ns/day. Two following bars are labeled + "haswell" and "broadwell" (both "AVX2") and exceed 1.5 ns/day + slightly. The last two bars are labeled "skylake_avx512" and + "cascadelake" (both "AVX512") and reach almost 2.0 ns/day. + + Performance of GROMACS 2020.1 built for different generations of + CPUs. Vertical axis shows performance expressed in ns/day, a + GROMACS-specific measure of simulation speed (higher is better). + + “Compiling `GROMACS `_ for architectures + that can exploit the AVX-512 instructions supported by the Intel + Cascade Lake microarchitecture gives an additional 18% performance + improvement relative to using AVX2 instructions, with a speedup of + about 70% compared to a generic GROMACS installation with only SSE2.” + + — `archspec: A library for detecting, labeling, and reasoning about + microarchitectures `__ + + +The limitations of platform compatibility tags +---------------------------------------------- + +The current wheel format encodes compatibility through three platform +tags: + +1. **Python tag**: encoding the minimum Python version and optionally + restricting Python distributions (e.g., ``py3`` for any Python 3, + ``py313`` for Python 3.13 or newer, ``cp313`` for specifically + CPython, 3.13 or newer). +2. **ABI tag**: encoding the Python ABI required by any extension + modules (e.g., ``none`` for no requirement, ``abi3`` for the CPython + stable ABI, ``cp313`` for extensions requiring CPython 3.13 ABI). +3. **Platform tag**: currently encoding the operating system, + architecture and core system libraries (e.g., ``any`` for any + platform, ``manylinux_2_34_x86_64`` for x86-64 Linux system with + glibc 2.34 or newer, ``macosx_14_0_arm64`` for arm64 macOS 14.0 + or newer system. + +While these tags effectively handle traditional compatibility +dimensions, they cannot express modern requirements: + +**GPU Accelerated Frameworks:** A wheel filename like +``torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64.whl`` provides no +indication whether it supports NVIDIA CUDA, AMD ROCm, +Intel XPU, or is CPU-only. Users cannot determine compatibility +with their GPU hardware or drivers. + +**CPU Instruction Sets:** A wheel filename like +``numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl`` +provides no indication whether it uses optimized instructions +for modern processors with AVX512, SHA-NI, and other specialized +instruction sets. Packages cannot indicate whether they +require specific CPU baseline. This forces maintainers to either use +inconvenient runtime dispatching, or rely on the lowest common +denominator, leaving performance improvements on the table. + +**Runtime Dependencies:** Scientific computing packages often depend on +specific BLAS implementations (OpenBLAS vs Intel MKL), MPI providers +(OpenMPI vs MPICH), or other system libraries that affect both +functionality and performance. The current wheel format is not able to +encode that dependency. + +This lack of flexibility has led many projects to find sub-optimal - yet +necessary - workarounds, such as the manual installation command selector +provided by the PyTorch team. This complexity represents a fundamental +scalability issue with the current tag system. + +This problem is not unique to PyTorch. Projects like `JAX +`_, `NumPy `_, +`SciPy `_, `scikit-learn +`_ and many others in the +scientific Python ecosystem face +similar hurdles. The core issue is that wheel tags are not extensible +enough to handle the combinatorial complexity of build options. Wheel +variants can capture GPU compatibility in wheel metadata and select +the correct wheel at install time. + + +Current workarounds and their limitations +----------------------------------------- + +Separate package indexes as variants +'''''''''''''''''''''''''''''''''''' + +Projects such as `PyTorch `__ +and `RAPIDS `__ +currently distribute packages that approximate “variants” through +separate package indexes with custom URLs. We will use the +example of PyTorch, while the problem, the workarounds and the impact for +users also apply to other packages. + +.. figure:: pep-0817/pytorch_variant_selector.png + :alt: A grid-based selector for PyTorch versions. Individual rows + provide the choice of PyTorch Build (stable or nightly), + operating system (Linux, Mac, Windows), package (Pip, + LibTorch, Source), language (Python, C++ / Java), and Compute + Platform (CUDA 12.6, CUDA 12.8, CUDA 13.0, ROCM 6.4, CPU). + Below these rows, the pip install command for the selected + variant is provided, utilizing the --index-url parameter. + + The PyTorch install selector (https://pytorch.org/get-started/locally/, + captured 22-Aug-2025) + +PyTorch uses a combination of index URLs per accelerator type and local +version segments as accelerator tag (such as ``+cu130``, ``+rocm6.4`` or +``+cpu``) . Users need to first determine the correct index URL for their +system, and add an index specifically for PyTorch. + +.. code:: bash + + pip install torch --index-url https://download.pytorch.org/whl/cu129 + +Tools need to implement special handling for the way PyTorch uses local +version segments. These requirements break the pattern that packages +are usually installed with. Problems with installing PyTorch +are a very common point of user confusion. To quantify this, on +2025-12-05, 552 out of 8136, or 6.8%, of issues on the `uv's issue +tracker `__ contained the term +"torch". + +Wheel variants remove this special casing and make GPU and TPU packages work +just as well as regular packages with native code. They also reduce the burden +on the maintainers to run separate infrastructure and to find and use +non-standard features such as local version segments present on an index. + +**Induced Security Risk:** This approach has unfortunately led to supply +chain attacks - more details on the `PyTorch +Blog `__. It’s +a non-trivial problem to address which has forced the PyTorch team to create +a complete mirror of all their dependencies, and is one of the core +motivations behind :pep:`766`. + +The complexity of configuration often leads to projects providing ad-hoc +installation instructions rather than covering permanent settings. This +can lead to users being unable to cleanly upgrade the packages, or the +upgraded packages being reverted to the default variant on subsequent upgrades. + + +Package names as variants +''''''''''''''''''''''''' + +Packages such as `XGBoost +`__ use different +package names to approximate variants: + +.. code:: bash + + pip install xgboost # NVIDIA GPU variant + pip install xgboost-cpu # CPU-only variant + +Maintainers of other software cannot express that they depend on either +of the available variants being selected. They need to +either depend on a specific variant, provide multiple alternative +dependency sets using extras, or even publish their own software using +multiple package names matching upstream variants. + +Commonly, these packages install overlapping files. Since Python +packaging does not support expressing that two packages are mutually +exclusive, installers can install both of them to the same environment, +with the package installed second overwriting files from the one +installed first. This leads to runtime errors, and +the possibility of incidentally switching between variants depending on +the way package upgrades are ordered. + +An additional limitation of this approach is that publishing a new release +synchronously across multiple package names is not currently possible. +:pep:`694` proposes adding such a mechanism for multiple wheels within a +single package, but extending it to multiple packages is not a goal. + +**Induced Security Risk:** proliferation of suffixed variant packages +leads users to expect these suffixes in other packages, making name +squatting much easier. For example, one could create a malicious +``numpy-cuda`` package that users will be lead to believe it’s a CUDA +variant of NumPy. + +`CuPy `_ had to build +a total of 52 different packages - all with different names - which +clearly highlights the limit of such an approach. End users need to +carefully read the CuPy installation documentation to figure out +which package they need. Continuously having to create new PyPI +packages, request increasing limits and keep the build infrastructure +and documentation in sync, requires significant effort from software +maintainers. + +.. code:: text + + cupy-cuda100 cupy-cuda101 cupy-cuda102 cupy-cuda110 cupy-cuda111 cupy-cuda112 cupy-cuda113 cupy-cuda114 cupy-cuda115 + cupy-cuda116 cupy-cuda117 cupy-cuda118 cupy-cuda119 cupy-cuda11x cupy-cuda120 cupy-cuda121 cupy-cuda122 cupy-cuda123 + cupy-cuda124 cupy-cuda125 cupy-cuda126 cupy-cuda127 cupy-cuda128 cupy-cuda129 cupy-cuda12x cupy-cuda13x cupy-cuda70 + cupy-cuda75 cupy-cuda80 cupy-cuda90 cupy-cuda91 cupy-cuda92 cupy-rocm-4-0 cupy-rocm-4-1 cupy-rocm-4-2 cupy-rocm-4-3 + cupy-rocm-4-4 cupy-rocm-4-5 cupy-rocm-5-0 cupy-rocm-5-1 cupy-rocm-5-2 cupy-rocm-5-3 cupy-rocm-5-4 cupy-rocm-5-5 + cupy-rocm-5-6 cupy-rocm-5-7 cupy-rocm-5-8 cupy-rocm-5-9 cupy-rocm-6-0 cupy-rocm-6-1 cupy-rocm-6-2 cupy-rocm-6-3 + + +Extra-Dependency as variants +'''''''''''''''''''''''''''' + +`JAX `__ uses a +plugin-based approach. The central ``jax`` package provides a number of +extras that can be used to install additional plugins, +e.g. ``jax[cuda12]`` or ``jax[tpu]``. This is far from ideal as +``pip install jax`` (with no extra) leads to a nonfunctional +installation, and consequently dependency chains, a fundamental expected +behavior in the Python ecosystem, are dysfunctional. + +JAX includes 12 extra selectors to cover all use cases - many of which +overlap and could be misleading to users if they don’t read the +documentation in detail. + +It should be noted that most of these “extras” are technically mutually +exclusive, though it is currently impossible to correctly express this +within the package metadata. + +.. code:: email + + Provides-Extra: minimum-jaxlib + Provides-Extra: cpu + Provides-Extra: ci + Provides-Extra: tpu + Provides-Extra: cuda + Provides-Extra: cuda12 + Provides-Extra: cuda13 + Provides-Extra: cuda12-local + Provides-Extra: cuda13-local + Provides-Extra: rocm + Provides-Extra: k8s + Provides-Extra: xprof + + +Bundled universal packages - monolithic builds +'''''''''''''''''''''''''''''''''''''''''''''' + +Including all possible variants in a single wheel is another option, but +this leads to excessively large artifacts, wasting bandwidth and leading to slower +installation times for users who only need one specific variant. In some +cases, such artifacts cannot be hosted on PyPI because they exceed its +size limits. + + +Wheel variant selection via source distribution +''''''''''''''''''''''''''''''''''''''''''''''' + +`FlashAttention `__ does +not publish wheels on PyPI at all, but instead publishes a customized +source distribution that performs platform detection, downloads the +appropriate wheel from an upstream server, and then provides it to the +installer. This approach can select the optimal variant automatically, +but it prevents binary-only installs from working, requires a slow and +error-prone build via a source distribution, and breaks common caching +assumptions tied to the wheel filename. It also requires a specially +prepared build environment that contains the ``torch`` package matching +the version that the software will run against, which requires building +without build isolation. On the project side, it requires hosting wheels +separately. + +**Induced Security Risk:** Similar to regular source builds, this +model requires running arbitrary code at install time. The wheels +are downloaded entirely outside package manager's control, extending +the attack surface to two separate wheel download implementations and +preventing proper provenance tracking. + + +Ecosystem fragmentation +''''''''''''''''''''''' + +The lack of standardized variant support has led to ecosystem +fragmentation: + +**Inconsistent User Experience**: Each package uses different +installation methods, creating confusion and reducing discoverability. + +**Development Tool Complications**: Installation tools, IDEs, and CI/CD +systems struggle to handle non-standard installation requirements. + +**Error-proneness**: The established workarounds are often error-prone, +and in the past they have lead to issues such as downloading incorrect +artifacts. + + +Impact on scientific computing and AI/ML workflows +-------------------------------------------------- + +The packaging limitations particularly affect scientific computing and +AI/ML applications where performance optimization is critical: + + The current wheel format’s lack of hardware awareness creates a + suboptimal experience for hardware-dependent packages. While plugins + help with smaller and well scoped packages, users must currently + manually identify the correct variant (e.g., ``jax[cuda13]``) to + avoid generic defaults or incompatible combinations. We need a system + where ``pip install jax`` automatically selects packages matching the + user’s hardware, unless explicitly overridden. + + Wheel variants are a clear step in the right direction in this regard. + + — Michael Hudgins, JAX_ Developer Infrastructure Lead + +They affect everyone from package authors to end users of all skill +levels, including students, scientists and engineers: + + Accessing compute to run models and process large datasets has been + a pain point in scientific computing for over a decade. Today, + researchers and data scientists still spend hours to days installing + core tools like PyTorch before they can begin their work. This + complexity is a significant barrier to entry for users who want to + use Python in their daily work. The WheelNext Wheel Variants + proposal offers a pathway to address persistent installation and + compute-access problems within the broader packaging ecosystem + without creating another, new and separate solution. Let's focus on + the big picture of enhancing user experience - it will make a real + difference. + + — Leah Wasser, Executive Director and Founder of `pyOpenSci + `_ + + +Heterogeneous computing environments +'''''''''''''''''''''''''''''''''''' + +Research institutions and cloud providers often manage heterogeneous +computing clusters with different architectures (CPU, Hardware +accelerators, ASICS, etc.). The current system requires +environment-specific installation procedures, making reproducible +deployment difficult. This situation also contributes to making +“scientific papers” difficult to reproduce. Application authors focused +on improving that are hindered by the packaging hurdles too: + + We've been developing a package manager for Spyder, a Python IDE for + scientists, engineers and data analysts, with three main aims. + First, to make our users' life easier by allowing them to create + environments and install packages using a GUI instead of introducing + arcane commands in a terminal. Second, to make their research code + reproducible, so they can share it and its dependencies with their + peers. And third, to allow users to transfer their code to machines + in HPC clusters or the cloud with no hassle, so they can leverage + the vast compute resources available there. With the improvements + proposed by this PEP, we'd be able to make that a reality for all + PyPI users because installing widely used scientific libraries (like + PyTorch and CuPy) for the right GPU and instruction set and would be + straightforward and transparent for tools built on top of uv/pip. + + — Carlos Córdoba, lead developer of the `Spyder IDE + `_ + + +Artificial intelligence, machine learning, and deep learning +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +The recent advances in modern AI workflows increasingly rely on GPU +acceleration, but the current packaging system makes deployment complex +and adds a significant burden on open source developers of the entire +tool stack (from build backends to installers, not forgetting the +package maintainers). + + PyTorch’s extensive wheel support was always state of the art and + provided hardware accelerator support from day zero via our `package + selector `__. We believe + this was always a superpower of PyTorch to get things working out of + the box for our users. Unfortunately, the infrastructure supporting + these is very complex, hard to maintain and inefficient (for us, our + users and package repositories). + + With the number of hardware we support growing rapidly again, we are + very supportive of the wheel variants efforts that will allow us to + get PyTorch install instructions to be what our users have been + expecting since PyTorch was first released: ``pip install torch`` + + — The PyTorch_ Core Maintainers + +The lead maintainer of `XGBoost `_ enumerates a number of problems XGBoost +has that he expects will be addressed by wheel variants: + + * Large download size, due to the use of "fat binaries" for multiple + SMs [GPU targets]. Currently, XGBoost builds for 11 different SMs. + + * The need for a separate packaging name for CPU-only package. + Currently we ship a separate package named ``xgboost-cpu``, + requiring users to maintain separate ``requirements.txt`` files. + See `xgboost#11632 + `_ for an example. + + * Complex dispatching logic for multiple CUDA versions. Some + features of XGBoost require new CUDA versions (12.5 or 12.8), + while the XGBoost wheel targets 12.0. As a result, we maintain a + fairly complex dispatching logic to detect CUDA and driver + versions at runtime. Such dispatching logic should be best + implemented in a dedicated piece of software like the NVIDIA + provider plugin, so that the XGBoost project can focus on its core + mission. + + * Undefined behavior due to presence of multiple OpenMP runtimes. + XGBoost is installed in a variety of systems with different OpenMP + runtimes (or none at all). So far, XGBoost has been vendoring a + copy of OpenMP runtime, but this is increasingly untenable. Users + get undefined behavior such as crashes or hangs when multiple + incompatible versions of OpenMP runtimes are present in the + system. (This problem was particularly bad on MacOS, so much so + that the MacOS wheel for XGBoost no longer bundles OpenMP.) + + — Philip Hyunsu Cho, a lead maintainer of XGBoost_ + +The potential for improvement can be summarized as: + + This PEP is a significant step forward in improving the deployment + challenges of the Python ecosystem in the face of increasingly + complex and varied hardware configurations. By enabling multiple + deployment targets for the same libraries in a standard way, it will + consolidate and simplify many awkward and time-consuming + work-arounds developers have been pursuing to support the rapidly + growing AI/ML and scientific computing worlds. + + — Travis Oliphant, the author of NumPy_ and SciPy_ and Chief AI + Architect at OpenTeams + + +Motivation summary +------------------ + +As highlighted in the previous section, the current Python packaging +system cannot adequately serve the needs of modern heterogeneous +computing environments. These aforementioned limitations force package +authors into complex workarounds that create friction for users, +increase maintenance burden, and fragment the ecosystem. + +**Wheel Variants provide a standardized solution that:** + +- Enables automatic hardware-appropriate package selection. +- Maintains full backward compatibility with existing tools (i.e., non-variant aware installers, tools, and + indexes remain unaffected). +- Simplifies package maintenance by offering a unified and flexible + solution to the challenge of managing multiple platform-specific + package builds and distributions. +- Provides a seamless and predictable experience for users, with little-to-no user input required. +- Supports the full spectrum of modern computing hardware. +- Provides a future-proof and flexible system that can evolve with the + ecosystem and future use cases. + + +Out-of-scope features +--------------------- + +This PEP tries to present the minimal scope required and leaves aspects +to tools to evolve. A non-exhaustive list: + +- The format of a static file to select variants deterministically or include + variants in a ``pylock.toml`` file, +- The list of variant providers that are vendored or re-implemented by installers, +- The specific opt-in mechanisms and UX for allowing an installer to run + non-vendored variant providers, +- How to instruct build backends to emit variants through the :pep:`517` + mechanism. + + +Prior art +--------- + +This problem is not unique to the Python ecosystem, different groups and +ecosystems have come up with various answers to that very problem. This +section will focus on highlighting the strengths and weaknesses of the +different approaches taken by various communities. + + +Conda - conda-forge +''''''''''''''''''' + +`Conda `__ is a binary-only package ecosystem +that uses aggregated metadata indexes for resolution rather than +filename parsing. Unlike the +:doc:`packaging:specifications/simple-repository-api`, conda’s +resolution relies on `repodata indexes per platform +`__ +containing full metadata, making filenames purely identifiers with no +parsing requirements. + +**Variant System**: In +`2016-2017 `__, +conda-build introduced variants to differentiate packages with identical +name/version but different dependencies. + +.. code:: bash + + pytorch-2.8.0-cpu_mkl_py313_he1d8d61_100.conda # CPU + MKL variant + pytorch-2.8.0-cuda128_mkl_py313_hf206996_300.conda # CUDA 12.8 + MKL variant + pytorch-2.8.0-cuda129_mkl_py313_he100a2c_300.conda # CUDA 12.9 + MKL variant + +A hash (computed from variant metadata) prevents filename collisions; +actual variant selection happens via standard dependency constraints in +the solver. No special metadata parsing is needed—installers simply +resolve dependencies like: + +.. code:: bash + + conda install pytorch mkl + +**Mutex Metapackages**: Python metadata and conda metadata do not have +good ways to express ideas like “this package conflicts with that one.” +The main mechanism for enforcement is sharing a common package name - +only one package with a given name can exist at one time. Mutex +metapackages are sets of packages with the same name, but different +build string. Packages depend on specific mutex builds (e.g., +``blas=*=openblas`` vs ``blas=*=mkl``) to avoid problems with related +packages using different dependency libraries, such as NumPy_ using +`OpenBLAS `_ and SciPy_ using +`MKL +`_. + +**Example software variants**: +`BLAS `__, +`MPI `__, +`OpenMP `__, +`noarch vs +native `__ + +**Virtual Packages**: `Introduced in +2019 `__, virtual packages +inject system detection (CUDA version, glibc, CPU features) as solver +constraints. Built packages express dependencies like ``__cuda >=12.8``, +and the installer verifies compatibility at install time. Current +virtual packages include ``archspec`` (CPU capabilities), OS/system +libraries, and CUDA driver version. Detection logic is tool-specific +(`rattler `__, +`mamba `__). + + +Spack / Archspec +'''''''''''''''' + +`archspec `_ is a library for +detecting, labeling, and reasoning about CPU microarchitecture variants, +developed for the `Spack `_ package manager. + +**Variant Model:** CPU Microarchitectures (e.g., ``haswell``, +``skylake``, ``zen2``, ``armv8.1a``) form a `Directed Acyclic Graph +(DAG) encoding binary +compatibility `__, +which helps at resolve to express that ``packageB`` depends on +``packageA``. The ordering is partial because (1) separate ISA families +are incomparable, and (2) contemporary designs may have incompatible +feature sets—cascadelake and cannonlake are incomparable despite both +descending from skylake, as each has unique AVX-512 extensions. + +**Implementation:** A language-agnostic JSON database stores +microarchitecture metadata (features, compatibility relationships, +compiler-specific optimization flags). Language bindings provide +detection (queries ``/proc/cpuinfo``, matches to microarchitecture with +largest compatible feature subset) and compatibility comparison +operators. + +**Package Manager Integration:** Spack records target microarchitecture +as package provenance (``spack install fftw target=broadwell``), +automatically selects compiler flags, and enables +microarchitecture-aware binary caching. The `European Environment for +Scientific Software Installations +(EESSI) `__ +distributes optimized builds in separate subdirectories per +microarchitecture (e.g., ``x86_64``, ``armv8.1a``, ``haswell``); +runtime initialization uses ``archspec`` to select best compatible build +when no exact match exists. + + +Gentoo Linux +'''''''''''' + +`Gentoo Linux `_ is a source-first distribution +with support for extensive package customization. This is primarily +achieved via `USE +flags `__: +boolean flags exposed by individual packages and permitting fine-tuning +the enabled features, optional dependencies and some build parameters +(e.g. ``jpegxl`` for JPEG XL image format support, +``cpu_flags_x86_avx2`` for AVX2 instruction set use). Flags can be toggled +individually, and separate binary packages can be built for different +sets of flags. The package manager can either pick a binary package with +matching configuration or build from source. + +API and ABI matching is primarily done through use of +`slotting `__. +Slots are generally used to provide multiple versions or variants of +given package that can be installed alongside (e.g. different major GTK+ +or LLVM versions, or GTK+3 and GTK4 builds of WebKitGTK), whereas +subslots are used to group versions within a slot, usually +corresponding to the library ABI version. Packages can then declare +dependencies bound to the slot and subslot used at build time. Again, +separate binary packages can be built against different dependency +slots. When installing a dependency version falling into a different +slot or subslot, the package manager may either replace the package +needing that dependency with a binary packages built against the new +slot, or rebuild it from source. + +Normally, the use of slots assumes that upgrading to the newest version +possible is desirable. When more fine-grained control is desired, slots +are used in conjunction with USE flags. For example, +``llvm_slot_{major}`` flags are used to select a LLVM major version to +build against. + + +Rationale +========= + +Modified wheel filename +----------------------- + +One of the core requirements of the design is to ensure that installers +predating this PEP will ignore wheel variant files. This makes it +possible to publish both variant wheels and non-variant wheels on a +single index, with installers that do not support variants securely +ignoring the former, and falling back to the latter. + +A variant label component is added to the filename for the twofold +purpose of providing a unique mapping from the filename to a set of +variant properties, and providing a human-readable identification for +the variant. The label is kept short and lowercase to avoid issues with +different filesystems. It is added as a ``-``-separated component at the +end to ensure that the existing filename validation algorithms reject +it: + +- If both the build tag and the variant label are present, the filename + contains too many components. + + Example: + ``numpy-2.3.2-1-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl`` + +- If only the variant label is present, the Python tag at third position + will be misinterpreted as a build number. Since the build number must + start with a digit and no Python tags at the time start with digits, + the filename is considered invalid. + + Example: + ``numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl`` + +This behavior was confirmed for a number of existing tools: +`auditwheel `__, +`packaging `__, +`pdm `__, +`pip `__, +`poetry `__, +`uv `__. + + +Variant properties system +------------------------- + +Variant properties serve the purpose of expressing the characteristics +of the variant. Unlike platform compatibility tags, they are stored +in the variant metadata and therefore do not affect the wheel filename +length. They follow a hierarchical key-value design, with the key +further broken into a namespace and a feature name. +Namespaces are used to group features +defined by a single provider, and to avoid conflicts should multiple +providers define a feature with the same name. This permits independent +governance and evolution of every namespace. + +The keys are restricted to lowercase letters, digits and underscores. +Uppercase characters are disallowed to avoid different spellings of the +same name. The character set for values is more relaxed, to permit +values resembling versions. + +Variant features can be declared as allowing multiple values to be +present within a single variant wheel. If that is +the case, these values are matched as a logical disjunction, i.e. only a +single value needs to be compatible with the system for the wheel to be +considered supported. On the other hand, features are treated conjunctively, +i.e. all of them need to be compatible. This provides some flexibility in +designating variant compatibility while avoiding having to implement a +complete boolean logic. + +Variant properties are serialized into a structured 3-tuple format +inspired by Trove Classifiers in :pep:`301`: + +:: + + {namespace} :: {feature_name} :: {feature_value} + + +Null variant +------------ + +The concept of a null variant makes it possible to distinguish a +fallback wheel variant from a regular wheel published for backwards +compatibility. For example, a package that features optional GPU support +could publish the following wheels during the transition period: + +- One or more wheel variants built for specific hardware, that will be + installed on systems with an installer supporting variants and + suitable hardware. + +- A CPU-only null variant, that will be installed on systems with an + installer supporting variants, but without hardware suitable for any + other variants. + +- A GPU+CPU regular wheel, that will be installed on systems without an + installer supporting variants. + +Notably, this makes it possible to publish a smaller null variant +for systems that do not feature suitable hardware, with a fallback +regular wheel with support for CPU and all GPUs for systems where +variants are not supported and therefore GPU support cannot be +determined. + +Publishing a null variant is optional. If one is published, a wheel +variant-enabled installer will prefer it over the non-variant wheel. If +it is not, it will fall back to the non-variant wheel instead. The +non-variant wheel is also used if variant support is explicitly disabled +by an installer flag. + +The null variant uses a reserved ``null`` label to make it clearly +distinguishable from regular variants. + + +Install-time and Ahead-of-Time providers +---------------------------------------- + +The specification covers two related use cases for package variants: + +1. Variants that need to query the user system to determine their + compatibility with it. For example, these are the variants utilizing + GPUs or requiring CPU instruction sets beyond what platform tags + provide. + +2. Variants that can always be installed, and therefore only provide + a choice between different installation options. For example, software built + against different BLAS / LAPACK providers or debug builds of packages. + +The first class of variants requires querying provider plugins to +determine package compatibility, and therefore involves the potential +risks highlighted in the `security implications`_ section. The proposed +mitigations incur a cost on installer implementations, making it +desirable to avoid plugin use at install time for the second class of variants, +where it is not strictly necessary. + +To account for this, the specification proposes two classes of +providers: + +a. Install-time providers that require executing code to determine + the wheel compatibility, and therefore satisfy the variants needing + that. + +b. Ahead-of-Time (AoT) providers that entirely rely on static metadata + for the purpose of wheel selection, and that can be used for the + other class of variants to avoid the additional problems posed + by install-time providers. + +For user convenience and better consistency across different packages, +AoT providers can also feature plugin packages, but they are only used +at build time (where executing code from additional packages is already +a risk) to verify the variant properties specified by the maintainer +and automatically generate the static metadata. + +Both kinds of provider plugins expose the same API. However, an AoT +provider must always consider all valid variant properties supported, +and it must always return the same ordered list of supported properties +irrespective of the user system. All AoT providers can be technically +used as install-time providers, but not the other way around. + + +Plugin stability and versioning +------------------------------- + +Given that calling provider plugins may be necessary while installing +variant wheels published in the past, it is important that provider +plugin behavior remain stable within their lifetime. Ideally, no +properties previously supported should ever be removed. + +If a breaking change needs to be performed, it is recommended to either +introduce a new provider package for that, or add a new plugin API +endpoint to the existing package. In both cases, it may be necessary to +preserve the old endpoint in minimal maintenance mode, to ensure that +old wheels can still be installed. The old endpoint can trigger +deprecation warnings in the ``get_all_configs()`` hook that is used when +building packages. + +An alternative approach is to use semantic versioning to cut off +breaking changes. However, this relies on package authors reliably using +caps on dependencies, as otherwise old wheels will start using +incompatible plugin versions. This is already a problem with Python +build backends used today. + +When vendoring or reimplementing plugins, installers need to follow +their current behavior. In particular, they should recognize the relevant provider +versions numbers, and possibly fall back to installing the external +plugin when the package in question is incompatible with the installer’s +implementation. + + +Example use cases +----------------- + +PyTorch CPU/GPU variants +'''''''''''''''''''''''' + +As of October 2025, +`PyTorch `__ publishes builds +a total of seven variants for every release: a CPU-only variant, three +CUDA variants with different minimal CUDA runtime versions and supported +GPUs, two ROCm variants and a Linux XPU variant. + +This setup could be improved using GPU/XPU plugins that query the +installed runtime version and installed GPUs/XPUs to filter out the wheels +for which the runtime is unavailable, it is too old or the user’s GPU is +not supported, and order the remaining variants by the runtime version. +The CPU-only version is published as a null variant that is always +supported. + +If a GPU runtime is available and supported, the installer automatically +chooses the wheel for the newest runtime supported. Otherwise, it falls +back to the CPU-only variant. In the corner case when multiple +accelerators are available and supported, PyTorch package maintainers +indicate which one takes preference by default. + + +Optimized CPU variants +'''''''''''''''''''''' + +Wheel variants can be used to provide variants requiring specific CPU +extensions, beyond what platform tags currently provide. They can be +particularly helpful when runtime dispatching is impractical, when the +package relies on prebuilt components that use instructions above the +baseline, when availability of instruction sets implies library ABI +changes, or simply to benefit from compiler optimizations such as +auto-vectorization applied across the code base. + +For example, an x86-64 CPU plugin can detect the capabilities for the +installed CPU, mapping them onto the appropriate x86-64 architecture +level and a set of extended instruction sets. Variant wheels indicate +which level and/or instruction sets are required. The installer filters out variants +that do not meet the requirements and select the best optimized variant. +A non-variant wheel can be used to represent the architecture baseline, +if supported. + +Implementation using wheel variants makes it possible to provide +fine-grained indication of instruction sets required, with plugins that +can be updated as frequently as necessary. In particular, it is neither +necessary to cover all available instruction sets from the start, nor to +update the installers whenever the instruction set coverage needs to be +improved. + + +BLAS / LAPACK variants +'''''''''''''''''''''' + +Packages such as NumPy_ and SciPy_ can be built using different BLAS / +LAPACK libraries. Users may wish to choose a specific library for +improved performance on a particular hardware, or based on license +considerations. Furthermore, different libraries may use different +OpenMP implementations, whereas using a consistent implementation across +the stack can avoid degrading performance through spawning too many +threads. + +BLAS / LAPACK variants do not require a plugin at install time, since +all variants built for a particular platform are compatible with it. +Therefore, an ahead-of-time provider (with ``install-time = false``) +that provides a predefined set of BLAS / LAPACK library names can be +used. When the package is installed, normally the default variant is +used, but the user can explicitly select another one. + + +Debug package variants +'''''''''''''''''''''' + +A package may wish to provide a special debug-enabled builds for +debugging or CI purposes, in addition to the regular release build. For +this purpose, an optional ahead-of-time provider can be used +(``install-time = false`` with ``optional = true``), defining a custom +property for the debug builds. Since the provider is disabled by +default, users normally install the non-variant wheel providing the +release build. However, they can easily obtain the debug build by +enabling the optional provider or selecting the variant explicitly. + + +Package ABI matching +'''''''''''''''''''' + +Packages such as `vLLM `_ +need to be pinned to the PyTorch version they were built against to +preserve Application Binary Interface (ABI) compatibility. This often +results in unnecessarily strict pins in package versions, making it +impossible to find a satisfactory resolution for an environment +involving multiple packages requiring different versions of PyTorch, or +resorting to source builds. Variant wheels can be used to publish +variants of vLLM built against different PyTorch versions, therefore +enabling upstream to easily provide support for multiple versions +simultaneously. + +The optional ``abi_dependency`` extension can be used to build multiple +``vllm`` variants that are pinned to different PyTorch versions, e.g.: + +- ``vllm-0.11.0-...-torch29.wheel`` with + ``abi_dependency :: torch :: 2.9`` +- ``vllm-0.11.0-...-torch28.wheel`` with + ``abi_dependency :: torch :: 2.8`` +- ``vllm-0.11.0-...-torch27.wheel`` with + ``abi_dependency :: torch :: 2.7`` + + +Security implications +===================== + +The proposal introduces a plugin system for querying the system +capabilities in order to determine variant wheel capability. The system +permits specifying additional Python packages providing the plugins +in the package index metadata. Installers and other tools that need +to determine whether a particular wheel is installable, or select +the most preferred variant among multiple variant wheels, may need +to install these packages and execute the code within them while +resolving dependencies or processing wheels. + +This elevates the supply-chain attack potential by introducing two new +points for malicious actors to inject arbitrary code payload: + +1. Publishing a version of a variant provider plugin or one of its + dependencies with malicious code. + +2. Introducing a malicious variant provider plugin in an existing + package metadata. + +While such attacks are already possible at the package dependency level, +it needs to be emphasized that in some scenarios the affected tools are +executed with elevated privileges, e.g. when installing packages for +multi-user systems, while the installed packages are only used with +regular user privileges afterwards. Therefore, variant provider plugins +could introduce a Remote Code Execution vulnerability with elevated +privileges. + +A similar issue already exists in the packaging ecosystem when packages +are installed from source distributions, whereas build backends +and other build dependencies are installed and executed. However, +various tools operating purely on wheels, as well as users using +tool-specific options to disable use of source distributions, +have been relying on the assumption that no code external to the system +will be executed while resolving dependencies, installing a wheel or +otherwise processing it. To uphold this assumption, the proposal +explicitly requires that provider plugin packages are never installed +without explicit user consent. + +The `Overview`_ section of the specification provides further +suggestions that aim to improve both security and the user experience. +It is expected that a limited subset of popular provider plugins will +be either vendored by the installer, eliminating the use of packages +external to the tool altogether, or pinned to specific versions, +providing the same level of code auditing as the tools themselves. +This will lead to the majority of packages focusing on these specific +plugins. External plugins requiring explicit opt-in should be rare, +minimizing the workflow disruption and reducing the risk that users +blanket-allow all plugins. + +Furthermore, the specification permits using static configuration as +input to skip running plugins altogether. + + +Specification +============= + +This PEP proposes a set of extensions to the +:ref:`packaging:binary-distribution-format` specification that enable +building additional variants of wheels that can be installed by +variant-aware tools while being ignored by programs that do not +implement this specification. + + +Wheel variant glossary +---------------------- + +This section focuses specifically on the vocabulary used by the proposed +“Wheel Variant” standard: + +Variant Wheels + Wheels that share the same distribution name, version, build number, + and platform compatibility tags, but are distinctly identified by an + arbitrary set of variant properties. + +Variant Namespace + An identifier used to group related features provided by a single + provider (e.g., ``nvidia``, ``x86_64``, ``arm``, etc.). + +Variant Feature + A specific characteristic (key) within a namespace (e.g., + ``version``, ``avx512_bf16``, etc.) that can have one or more + values. + +Variant Property + A 3-tuple (``namespace :: feature-name :: feature-value``) + describing a single specific feature and its value. If a feature has + multiple values, each is represented by a separate property. + +Variant Label + A string (up to 16 characters) added to the wheel filename to uniquely identify variants. + +Null Variant + A special variant with zero variant properties and the reserved + label ``null``. Always considered supported but has the lowest + priority among wheel variants, while being preferably chosen over + non-variant wheels. + +Variant Provider + A provider of supported and valid variant properties for a specific + namespace, usually in the form of a Python package that implements + system detection. + +Install-time Provider + A provider implemented as a plugin that can be queried during wheel + installation. + +Ahead-of-Time Provider + A provider that features a static list of supported properties which + is then embedded in the wheel metadata. Such a list can either be + embedded in ``pyproject.toml`` or provided by a plugin queried at + build time. + + +Overview +-------- + +Wheel variants introduce a more fine-grained specification of built +wheel characteristics beyond what existing wheel tags provide. Individual wheels +are characterized by sets of `variant properties`_ that are organized into +a hierarchical structure of namespaces, features and feature values. +When evaluating wheels to install, the installer must determine whether +variant properties are compatible with the system, and perform `variant +ordering`_ based on the priority of the compatible variant properties. This is done in +addition to determining the compatibility and ordering through tags. The +ordering by variant properties takes precedence over ordering by tags. +Null variants (variants with no variant properties) are preferred over +non-variant wheels with the same tags. + +Every variant namespace is governed by a variant provider. There are two +kinds of variant providers: install-time providers and ahead-of-time +(AoT) providers. Install-time providers require plugins that are queried +while installing wheels to determine the set of supported properties and +their preference order. For AoT providers, this data is static and +embedded in the wheel; it can be either provided directly by the +wheel maintainer or queried at wheel build time from an AoT plugin. Both +kinds of plugins are usually implemented as Python packages which +implement the `provider plugin API`_. + +Installers, as well as other tools that need to verify variant wheel +compatibility and are run in security-sensitive context, must not +install or run code from any untrusted package for variant resolution +without explicit user opt-in. Provider packages should take measures to +guard against supply chain attacks, for example by vendoring all +dependencies. Pinning dependencies is discouraged to comply with +:pep:`517` +build process, as provider plugins may be needed at build time and +dependencies pinned to different versions could prevent different +plugins from being installed simultaneously in the same environment. + +It is recommended that these tools vendor, reimplement or lock the most +commonly used plugins to specific specific wheels after verifying +their trustworthiness, to enable the ability to securely install variant +wheels out-of-the-box. To reduce the maintenance costs, repositories of +such vetted plugins could be maintained collaboratively and shared +between different tools. + +For plugins and their dependencies not in such a pre-approved list, +a trust-on-first-use mechanism for every version is recommended. In +interactive sessions, the tool can explicitly ask the user for approval. +In non-interactive sessions, the approval can be given using +command-line interface options. It is important that the user is +informed of the risk before giving such an approval. + +For a consistent experience between tools, variant wheels should be +supported by default. Tools may provide an option to only use +non-variant wheels. For scenarios requiring more control, providers can +be marked as optional and then must be explicitly enabled by the user +for the wheels to become available for installation. + + +Summary of changes +------------------- + +The Wheel Variant PEP introduces four key components: + +1. `Extended wheel filename`_: Variant wheels include a variant label + in their filename to ensure: + + a. that every distinct variant has a unique filename + b. that variant wheels are not accidentally installed by + non-variant-aware tools. + +2. `Variant metadata`_ format: Standardized metadata describing variant + properties and provider requirements. + + a. Metadata specification at “project level” inside + ``pyproject.toml`` + b. Metadata specification of “built packages” inside two JSON files: + + i. ``*.dist-info/variant.json``: Individual wheel variant + metadata. + ii. ``*-variants.json``: Variant metadata file aggregated on the + package index. + +3. `Provider plugin API`_: Plugin interface to allow detection of + system capabilities and validate variant compatibility. + +4. `Variant environment markers`_: New environment markers to declare + dependencies that are applicable to a subset of variants only. + + +Extended wheel filename +----------------------- + +This PEP changes the wheel filename template originally defined by +:pep:`427` to: + +.. code:: text + + {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}(-{variant label})?.whl + +Wheels using extensions introduced by this PEP must feature the variant +label component. The label must adhere to the following rules: + +- Lower case only (to prevent issues with case-sensitive + vs. case-insensitive filesystems) +- Between 1-16 characters +- Using only ``0-9``, ``a-z``, ``.`` or ``_`` ASCII characters + +This is equivalent to the following regular expression: +``^[0-9a-z._]{1,16}$``. + +Every label must uniquely correspond to a specific set of variant +properties, same for all wheels using the same label within a single +package version. Variant labels should be specified at wheel build time, +as human-readable strings. The label ``null`` is reserved for the null +variant and must use an empty set of variant properties. + +Installers that do not implement this specification must ignore wheels +with variant label when installing from an index, and fall back to a +wheel without such label if it is available. If no such wheel is +available, the installer should output an appropriate diagnostic, +in particular warning if it results in selecting an earlier package +version or a clear error if no package version can be installed. + +Examples: + +- Non-variant wheel: + ``numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl`` +- Wheel with variant label: + ``numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl`` + + +Variant properties +------------------ + +Every variant wheel must be described by zero or more variant +properties. A variant wheel with exactly zero properties represents the +null variant. The properties are specified when the variant wheel is +being built, using a mechanism defined by the project’s build backend. + +Each variant property is described by a 3-tuple that is serialized into +the following format: + +:: + + {namespace} :: {feature_name} :: {feature_value} + +The namespace must consist only of ``0-9``, ``a-z`` and ``_`` ASCII +characters (``^[a-z0-9_]+$``). It must correspond to a single variant +provider. + +The feature name must consist only of ``0-9``, ``a-z`` and ``_`` ASCII +characters (``^[a-z0-9_]+$``). It must correspond to a valid feature +name defined by the respective variant provider in the namespace. + +The feature value must consist only of ``0-9``, ``a-z``, ``_`` and ``.`` +ASCII Characters (``^[a-z0-9_.]+$``). It must correspond to a valid +value defined by the respective variant provider for the feature. + +If a feature is marked as “multi-value” by the provider plugin, a single +variant wheel may define multiple properties sharing the same namespace +and feature name. Otherwise, only a single value can correspond to a +single pair of namespace and feature name within a variant wheel. + +For a variant wheel to be considered compatible with the system, all of +the features defined within it must be determined to be compatible. For +a feature to be compatible, at least a single value corresponding to it +must be compatible. + +Examples: + +.. code:: text + + # all of the following must be supported + x86_64 :: level :: v3 + x86_64 :: avx512_bf16 :: on + nvidia :: cuda_version_lower_bound :: 12.8 + # additionally, at least one of the following must be supported + nvidia :: sm_arch :: 120_real + nvidia :: sm_arch :: 110_real + + +Variant metadata +---------------- + +This section describes the metadata format for the providers, variants +and properties of a package and its wheels. The format is used in three +locations, with slight variations: + +1. in the source tree, inside the ``pyproject.toml`` file +2. in the built wheel, as a ``*.dist-info/variant.json`` file +3. on the package index, as a ``{name}-{version}-variants.json`` file. + +All three variants metadata files share a common JSON-compatible +structure: + +.. code:: text + + (root) + | + +- providers + | +- {namespace} + | +- enable-if : str | None = None + | +- install-time : bool = True + | +- optional : bool = False + | +- plugin-api : str | None = None + | +- requires : list[str] = [] + | + +- default-priorities + | +- namespace : list[str] + | +- feature + | +- {namespace} : list[str] = [] + | +- property + | +- {namespace} + | +- {feature} : list[str] = [] + | + +- static-properties + | +- {namespace} + | +- {feature} : list[str] = [] + | + +- variants + +- {variant_label} + +- {namespace} + +- {feature} : list[str] = [] + +The top-level object is a dictionary rooted at a specific point in the +containing file. Its individual keys are sub-dictionaries that are +described in the subsequent sections, along with the requirements for +their presence. The tools must ignore unknown keys in the dictionaries +to permit future backwards compatible updates to the PEP. However, users +should not introduce custom keys to avoid potential future conflicts. + +A `JSON schema `_ is included in the Appendix +of this PEP, to ease comprehension and validation of the metadata +format. This schema will be updated with each revision to the variant +metadata specification. The schema is available in +:ref:`0817-variant-json-schema`. + +Ultimately, the variant metadata JSON schema should be served by +`packaging.python.org `__. + +Provider information +'''''''''''''''''''' + +``providers`` is a dictionary, the keys are namespaces, the values are +dictionaries with provider information. It specifies how to install and +use variant providers. A provider information dictionary must be +declared in ``pyproject.toml`` for every variant namespace supported by +the package. It must be copied to ``variant.json`` as-is, including +the data for providers that are not used in the particular wheel. + +A provider information dictionary may contain the following keys: + +- ``enable-if: str``: An :ref:`environment marker + ` defining when the plugin + should be used. If the environment marker does not match the running + environment, the provider will be disabled and the variants using its + properties will be deemed incompatible. If not provided, the plugin + will be used in all environments. + +- ``install-time: bool``: Whether this is an install-time provider. + Defaults to ``true``. ``false`` means that it is an AoT provider + instead. + +- ``optional: bool``: Whether the provider is optional. Defaults + to ``false``. If it is ``true``, the provider is + considered optional and should not be used unless the user opts in to + it, effectively rendering the variants using its properties + incompatible. If it is ``false``, the provider is considered + obligatory. + +- ``plugin-api: str``: The API endpoint for the plugin. If it is + specified, it must be an object reference as explained in the `API + endpoint`_ section. If it is missing, the package name from the first + dependency specifier in ``requires`` is used, after replacing all + ``-`` characters with ``_`` in the normalized package name. + +- ``requires: list[str]``: A list of zero or more package :ref:`dependency + specifiers `, that are used to install the + provider plugin. If the + dependency specifiers include environment markers, these are evaluated + against the environment where the plugin is being installed and the + requirements for which the markers evaluate to false are filtered out. + In that case, at least one dependency must remain present in every + possible environment. Additionally, if ``plugin-api`` is not + specified, the first dependency present after filtering must always + evaluate to the same API endpoint. + +All the fields are optional, with the following exceptions: + +1. If ``install-time`` is true, the dictionary describes an install-time + provider and the ``requires`` key must be present and specify at + least one dependency. + +2. If ``install-time`` is false, it describes an AoT provider and the + ``requires`` key is optional. In that case: + + a. If ``requires`` is provided and non-empty, the provider dictionary + must reference an AoT provider plugin that will be queried at + build time to fill ``static-properties``. + + b. Otherwise, ``static-properties`` must be specified in + ``pyproject.toml``. + + +Default priorities +'''''''''''''''''' + +The ``default-priorities`` dictionary controls the ordering of variants. +The exact algorithm is described in the `Variant ordering`_ section. + +It has a single required key: + +- ``namespace: list[str]``: All namespaces used by the wheel variants, + ordered in decreasing priority. This list must have the same members + as the keys of the ``providers`` dictionary. + +It may have the following optional keys: + +- ``feature: dict[str, list[str]]``: A dictionary with namespaces as + keys, and ordered list of corresponding feature names as values. The + values in each list override the default ordering from the provider + output. They are listed from the highest priority to the lowest + priority. Features not present on the list are considered of lower + priority than those present, and their relative priority is defined by + the plugin. + +- ``property: dict[str, dict[str, list[str]]]``: A nested dictionary + with namespaces as first-level keys, feature names as second-level + keys and ordered lists of corresponding property values as + second-level values. The values present in the list override the + default ordering from the provider output. They are listed from the + the highest priority to the lowest priority. Properties not present on + the list are considered of lower priority than these present, and + their relative priority is defined by the plugin output. + + +Static properties +''''''''''''''''' + +The ``static-properties`` dictionary specifies the supported properties +for AoT providers. It is a nested dictionary with namespaces as first +level keys, feature name as second level keys and ordered lists of +feature values as second level values. + +In ``pyproject.toml`` file, the namespaces present in this dictionary +must correspond to all AoT providers without a +plugin (i.e. with ``install-time`` of ``false`` and no or empty +``requires``). When building a wheel, the build backend must query the +AoT provider plugins (i.e. these with ``install-time`` being ``false`` and +non-empty ``requires``) to obtain supported properties and embed them +into the dictionary. Therefore, the dictionary in ``variant.json`` and +``*-variants.json`` must contain namespaces for all AoT providers +(i.e. all providers with ``install-time`` being ``false``). + +Since TOML and JSON dictionaries are unsorted, so are the features in +the ``static-properties`` dictionary. If more than one feature is +specified for a namespace, then the order for all features must be +specified in ``default-priorities.feature.{namespace}``. If an AoT +plugin is used to fill ``static-properties``, then the features not +already in the list in ``pyproject.toml`` must be appended to it. + +The list of values is ordered from the most preferred to the least +preferred, same as the lists returned by ``get_supported_configs()`` +plugin API call (as defined in `plugin interface`_). The +``default-priorities.property`` dict can be used to override the +property ordering. + + +Variants +'''''''' + +The ``variants`` dictionary is used in ``variant.json`` to indicate the +variant that the wheel was built for, and in ``*-variants.json`` to +indicate all the wheel variants available. It’s a 3-level dictionary +listing all properties per variant label: The first level keys are +variant labels, the second level keys are namespaces, the third level +are feature names, and the third level values are lists of feature +values. + + +``pyproject.toml``: variant project-level data table +'''''''''''''''''''''''''''''''''''''''''''''''''''' + +The ``pyproject.toml`` file is the standard project configuration file +as defined in :doc:`packaging:specifications/pyproject-toml`. The +variant metadata must be rooted at a top-level table named ``variant``. +It must not specify the ``variants`` dictionary. It is used by build +backends to build variant wheels. + +Example Structure: + +.. code:: toml + + [variant.default-priorities] + # prefer CPU features over BLAS/LAPACK variants + namespace = ["x86_64", "aarch64", "blas_lapack"] + + # prefer aarch64 version and x86_64 level features over other features + # (specific CPU extensions like "sse4.1") + feature.aarch64 = ["version"] + feature.x86_64 = ["level"] + + # prefer x86-64-v3 and then older (even if CPU is newer) + property.x86_64.level = ["v3", "v2", "v1"] + + [variant.providers.aarch64] + # example using different package based on Python version + requires = [ + "provider-variant-aarch64 >=0.0.1; python_version >= '3.12'", + "legacy-provider-variant-aarch64 >=0.0.1; python_version < '3.12'", + ] + # use only on aarch64/arm machines + enable-if = "platform_machine == 'aarch64' or 'arm' in platform_machine" + plugin-api = "provider_variant_aarch64.plugin:AArch64Plugin" + + [variant.providers.x86_64] + requires = ["provider-variant-x86-64 >=0.0.1"] + # use only on x86_64 machines + enable-if = "platform_machine == 'x86_64' or platform_machine == 'AMD64'" + plugin-api = "provider_variant_x86_64.plugin:X8664Plugin" + + [variant.providers.blas_lapack] + # plugin-api inferred from requires + requires = ["blas-lapack-variant-provider"] + # plugin used only when building package, properties will be inlined + # into variant.json + install-time = false + + +``*.dist-info/variant.json``: the packaged variant metadata file +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +The ``variant.json`` file must be present in the ``*.dist-info/`` +directory of a built variant wheel. It is serialized into JSON, with the +variant metadata dictionary being the top object. It must include all +the variant metadata present in ``pyproject.toml``, copied as indicated +in the individual key sections. In addition to that, it must contain: + +- a ``$schema`` key whose value is the URL corresponding to the schema + file supplied in the appendix of this PEP. The URL contains the + version of the format, and a new version must be added to the appendix + whenever the format changes in the future, + +- a ``variants`` object listing exactly one variant - the variant + provided by the wheel. + +The variant.json file corresponding to the wheel built from the example +pyproject.toml file for x86-64-v3 would look like: + +.. code:: json5 + + { + // The schema URL will be replaced with the final URL on packaging.python.org + "$schema": "https://variants-schema.wheelnext.dev/v0.0.3.json", + "default-priorities": { + "feature": { + "aarch64": ["version"], + "x86_64": ["level"] + }, + "namespace": ["x86_64", "aarch64", "blas_lapack"], + "property": { + "x86_64": { + "level": ["v3", "v2", "v1"] + } + } + }, + "providers": { + "aarch64": { + "enable-if": "platform_machine == 'aarch64' or 'arm' in platform_machine", + "plugin-api": "provider_variant_aarch64.plugin:AArch64Plugin", + "requires": [ + "provider-variant-aarch64 >=0.0.1; python_version >= '3.12'", + "legacy-provider-variant-aarch64 >=0.0.1; python_version < '3.12'" + ] + }, + "blas_lapack": { + "install-time": false, + "requires": ["blas-lapack-variant-provider"] + }, + "x86_64": { + "enable-if": "platform_machine == 'x86_64' or platform_machine == 'AMD64'", + "plugin-api": "provider_variant_x86_64.plugin:X8664Plugin", + "requires": ["provider-variant-x86-64 >=0.0.1"] + } + }, + "static-properties": { + "blas_lapack": { + "provider": ["accelerate", "openblas", "mkl"] + }, + }, + "variants": { + // always a single entry, expressing the variant properties of the wheel + "x8664v3_openblas": { + "blas_lapack": { + "provider": ["openblas"] + }, + "x86_64": { + "level": ["v3"] + } + } + } + } + + +``{name}-{version}-variants.json``: the index level variant metadata file +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +For every package version that includes at least one variant wheel, +there must exist a corresponding ``{name}-{version}-variants.json`` +file, hosted and served by the package index. The ``{name}`` and +``{version}`` placeholders correspond to the package name and version, +normalized according to the same rules as wheel files, as found in the +:ref:`packaging:wheel-file-name-spec` of the Binary Distribution Format +specification. The link to this file must be present on all index pages +where the variant wheels are linked. It is presented in the same simple +repository format as source distribution and wheel links in the index, +including an (optional) hash. + +This file uses the same structure as ``variant.json`` described above, +except that the variants object must list all variants available on the +package index for the package version in question. It is recommended +that tools enforce the same contents of the ``default-priorities``, +``providers`` and ``static-properties`` sections for all variants listed +in the file, though careful merging is possible, as long as no +conflicting information is introduced, and the resolution results within +a subset of variants do not change. + +The ``foo-1.2.3-variants.json`` corresponding to the package with two +wheel variants, one of them listed in the previous example, would look +like: + +.. code:: json5 + + { + // The schema URL will be replaced with the final URL on packaging.python.org + "$schema": "https://variants-schema.wheelnext.dev/v0.0.3.json", + "default-priorities": { + // identical to above + }, + "providers": { + // identical to above + }, + "static-properties": { + // identical to above + }, + "variants": { + // all available wheel variants + "x8664v3_openblas": { + "blas_lapack": { + "provider": ["openblas"] + }, + "x86_64": { + "level": ["v3"] + } + }, + "x8664v4_mkl": { + "blas_lapack": { + "provider": ["mkl"] + }, + "x86_64": { + "level": ["v4"] + } + } + } + } + + +Variant ordering +---------------- + +To determine which variant wheel to install when multiple wheels are +compatible, variant wheels must be ordered by their variant properties. + +For the purpose of ordering, variant properties are grouped into +features, and features into namespaces. The ordering must be equivalent +to the following algorithm: + +1. Construct the ordered list of namespaces by copying the value of the + ``default-priorities.namespace`` key. + +2. For every namespace: + + i. Construct the initial ordered list of feature names by copying the + value of the respective ``default-priorities.feature.{namespace}`` + key. + + ii. Obtain the supported feature names from the provider, in order. + For every feature name that is not present in the constructed + list, append it to the end. + + After this step, a list of ordered feature names is available for + every namespace. + +3. For every feature: + + i. Construct the initial ordered list of values by copying the value + of the respective + ``default-priorities.property.{namespace}.{feature_name}`` key. + + ii. Obtain the supported values from the provider, in order. For every + value that is not present in the constructed list, append it to + the end. + + After this step, a list of ordered property values is available for + every feature. + +4. For every variant property present in at least one of the compatible + variant wheels, construct a sort key that is a 3-tuple consisting of + its namespace, feature name and feature value indices in the + respective ordered lists. + +5. For every compatible variant wheel, order its properties by their + sort keys, in ascending order. + +6. To order variant wheels, compare their sorted properties. If the + properties at the first position are different, the variant with the + lower 3-tuple of the respective property is sorted earlier. If they + are the same, compare the properties at the second position, and so + on, until either a tie-breaker is found or the list of properties of + one wheel is exhausted. In the latter case, the variant with more + properties is sorted earlier. + +After this process, the variant wheels are sorted from the most +preferred to the least preferred. The null variant naturally sorts after +all the other variants, and the non-variant wheel must be sorted after +the null variant. Multiple wheels with the same variant set (and +multiple non-variant wheels) must then be ordered according to their +platform compatibility tags. + +Alternatively, the sort algorithm for variant wheels could be described +using the following pseudocode. For simplicity, this code does not +account for non-variant wheels or tags. + +.. code:: python + + from typing import Self + + + def get_supported_feature_names(namespace: str) -> list[str]: + """Get feature names from plugin's get_supported_configs()""" + ... + + + def get_supported_feature_values(namespace: str, feature_name: str) -> list[str]: + """Get feature values from plugin's get_supported_configs()""" + ... + + + # default-priorities dict from variant metadata + default_priorities = { + "namespace": [...], # : list[str] + "feature": {...}, # : dict[str, list[str]] + "property": {...}, # : dict[str, dict[str, list[str]]] + } + + + # 1. Construct the ordered list of namespaces. + namespace_order = default_priorities["namespace"] + feature_order = {} + value_order = {} + + for namespace in namespace_order: + # 2. Construct the ordered lists of feature names. + feature_order[namespace] = default_priorities["feature"].get(namespace, []) + for feature_name in get_supported_feature_names(namespace): + if feature_name not in feature_order[namespace]: + feature_order[namespace].append(feature_name) + + value_order[namespace] = {} + for feature_name in feature_order[namespace]: + # 3. Construct the ordered lists of feature values. + value_order[namespace][feature_name] = default_priorities["property"].get(namespace, {}).get(feature_name, []) + for feature_value in get_supported_feature_values(namespace, feature_name): + if feature_value not in value_order[namespace][feature_name]: + value_order[namespace][feature_name].append(feature_value) + + + def property_key(prop: tuple[str, str, str]) -> tuple[int, int, int]: + """Construct a sort key for variant property (akin to step 4.)""" + namespace, feature_name, feature_value = prop + return ( + namespace_order.index(namespace), + feature_order[namespace].index(feature_name), + value_order[namespace][feature_name].index(feature_value), + ) + + + class VariantWheel: + """Example class exposing properties of a variant wheel""" + properties: list[tuple[str, str, str]] + + def __lt__(self: Self, other: Self) -> bool: + """Variant comparison function for sorting (akin to step 6.)""" + for self_prop, other_prop in zip(self.properties, other.properties): + if self_prop != other_prop: + return property_key(self_prop) < property_key(other_prop) + return len(self.properties) > len(other.properties) + + + # A list of variant wheels to sort. + wheels: list[VariantWheel] = [...] + + + for wheel in wheels: + # 5. Order variant wheel properties by their sort keys. + wheel.properties.sort(key=property_key) + # 6. Order variant wheels by comparing their sorted properties + # (see VariantWheel.__lt__()) + wheels.sort() + + +Integration with ``pylock.toml`` +-------------------------------- + +The following section is added to the +:doc:`packaging:specifications/pylock-toml`: + +.. code:: rst + + .. _pylock-packages-variants-json: + + ``[packages.variants-json]`` + ---------------------------- + + - **Type**: table + - **Required?**: no; requires that :ref:`pylock-packages-wheels` is used, + mutually-exclusive with :ref:`pylock-packages-vcs`, + :ref:`pylock-packages-directory`, and :ref:`pylock-packages-archive`. + - **Inspiration**: uv_ + - The URL or path to the ``variants.json`` file. + - Only used if the project uses :ref:`wheel variants `. + + .. _pylock-packages-variants-json-url: + + ``packages.variants-json.url`` + '''''''''''''''''''''''''''''' + + See :ref:`pylock-packages-archive-url`. + + .. _pylock-packages-variants-json-path: + + ``packages.variants-json.path`` + ''''''''''''''''''''''''''''''' + + See :ref:`pylock-packages-archive-path`. + + .. _pylock-packages-variants-json-hashes: + + ``packages.variants-json.hashes`` + ''''''''''''''''''''''''''''''''' + + See :ref:`pylock-packages-archive-hashes`. + +If there is a ``[packages.variants-json]`` section, the installer should +resolve variants to select the best wheel file. + + +Provider plugin API +------------------- + +High level design +''''''''''''''''' + +Every provider plugin must operate within a single namespace. This +namespace is used as a unique key for all plugin-related operations. All +the properties defined by the plugin are bound within the plugin’s +namespace, and the plugin defines all the valid feature names and values +within that namespace. When building wheels, the tools should query the +respective provider plugins to verify that the properties specified by +the user are valid. + +Provider plugin authors should choose namespaces that can be clearly +associated with the project they represent, and avoid namespaces that +refer to other projects or generic terms that could lead to naming +conflicts in the future. + +All variants published on a single index for a specific package version +must use the same provider for a given namespace. Attempting to load +more than one plugin for the same namespace in the same release version +must result in a fatal error. While multiple plugins for the same +namespace may exist across different packages or release versions (such +as when a plugin is forked due to being unmaintained), they are mutually +exclusive within any single release version. + +To make it easier to discover and install plugins, they should be +published in the same indexes that the packages using them. In +particular, packages published to PyPI must not rely on plugins that +need to be installed from other indexes. + +Plugins are implemented as Python packages. They need to expose two +kinds of Python objects at a specified API endpoint: + +a. attributes that return a specific value after being accessed via: + + .. code:: text + + {API endpoint}.{attribute name} + +b. callables that are called via: + + .. code:: text + + {API endpoint}.{callable name}({arguments}...) + +These can be +implemented either as modules, or classes with class methods or static +methods. The specifics are provided in the subsequent sections. + + +API endpoint +'''''''''''' + +The location of the plugin code is called an “API endpoint”, and it is +expressed using the object reference notation following the +:doc:`packaging:specifications/entry-points`: + +.. code:: text + + {import_path}(:{object_path})? + +An API endpoint specification is equivalent to the following Python +pseudocode: + +.. code:: python + + import {import_path} + + if "{object_path}": + plugin = {import_path}.{object_path} + else: + plugin = {import_path} + +API endpoints are used in two contexts: + +a. in the ``plugin-api`` key of variant metadata, either explicitly or + inferred from the package name in the ``requires`` key. This is the + primary method of using the plugin when building and installing + wheels. + +b. as the value of an installed entry point in the ``variant_plugins`` + group. The name of said entry point is insignificant. This is + optional but recommended, as it permits variant-related utilities to + discover variant plugins installed to the user’s environment. + + +Variant feature config class +'''''''''''''''''''''''''''' + +The variant feature config class is used as a return value in plugin API +functions. It defines a single variant feature, along with a list of +possible values. Depending on the context, the order of values may be +significant. It is defined using the following protocol: + +.. code:: python + + from abc import abstractmethod + from typing import Protocol + + + class VariantFeatureConfigType(Protocol): + @property + @abstractmethod + def name(self) -> str: + """Feature name""" + raise NotImplementedError + + @property + @abstractmethod + def multi_value(self) -> bool: + """Does this property allow multiple values per variant?""" + raise NotImplementedError + + @property + @abstractmethod + def values(self) -> list[str]: + """List of values, possibly ordered from most preferred to least""" + raise NotImplementedError + +A “variant feature config” must provide the following properties or attributes: + +- ``name: str`` specifying the feature name. + +- ``multi_value: bool`` specifying whether the feature is allowed to have + multiple corresponding values within a single variant wheel. If it is + ``False``, then it is an error to specify multiple values for the + feature. + +- ``values: list[str]`` specifying feature values. In + contexts where the order is significant, the values must be ordered + from the most preferred to the least preferred. + +All features are interpreted as being within the plugin’s namespace. + + +Plugin interface +'''''''''''''''' + +The plugin interface must follow the following protocol: + +.. code:: python + + from abc import abstractmethod + from typing import Protocol + + + class PluginType(Protocol): + # Note: properties are used here for docstring purposes, these must + # be actually implemented as attributes. + + @property + @abstractmethod + def namespace(self) -> str: + """The provider namespace""" + raise NotImplementedError + + @property + def is_aot_plugin(self) -> bool: + """Is this plugin valid for `install-time = false`?""" + return False + + @classmethod + @abstractmethod + def get_all_configs(cls) -> list[VariantFeatureConfigType]: + """Get all valid configs for the plugin""" + raise NotImplementedError + + @classmethod + @abstractmethod + def get_supported_configs(cls) -> list[VariantFeatureConfigType]: + """Get supported configs for the current system""" + raise NotImplementedError + +The plugin interface must define the following attributes: + +- ``namespace: str`` specifying the plugin’s namespace. + +- ``is_aot_plugin: bool`` indicating whether the plugin is a valid AoT + plugin. If that is the case, ``get_supported_configs()`` must always + return the same value as ``get_all_configs()`` (modulo ordering), + which must be a fixed list independent of the platform on which the + plugin is running. Defaults to ``False`` if unspecified. + +The plugin interface must provide the following functions: + +- ``get_all_config() -> list[VariantFeatureConfigType]`` that returns a + list of “variant feature configs” describing all valid variant + features within the plugin’s namespace, along with all their permitted + values. The ordering of the lists is insignificant here. A particular + plugin version must always return the same value (modulo ordering), + irrespective of any runtime conditions. + +- ``get_supported_configs() -> list[VariantFeatureConfigType]`` that + returns a list of “variant feature configs” describing the variant + features within the plugin’s namespace that are compatible with this + particular system, along with their values that are supported. The + variant feature and value lists must be ordered from the most + preferred to the least preferred, as they affect `variant + ordering`_. + +The value returned by ``get_supported_configs()`` must be a subset of +the feature names and values returned by ``get_all_configs()`` (modulo +ordering). + + +Example implementation +'''''''''''''''''''''' + +.. code:: python + + from dataclasses import dataclass + + + @dataclass + class VariantFeatureConfig: + name: str + values: list[str] + multi_value: bool + + + # internal -- provided for illustrative purpose + _MAX_VERSION = 4 + _ALL_GPUS = ["narf", "poit", "zort"] + + + def _get_current_version() -> int: + """Returns currently installed runtime version""" + ... # implementation not provided + + + def _is_gpu_available(codename: str) -> bool: + """Is specified GPU installed?""" + ... # implementation not provided + + + class MyPlugin: + namespace = "example" + + # optional, defaults to False + is_aot_plugin = False + + # all valid properties + @staticmethod + def get_all_configs() -> list[VariantFeatureConfig]: + return [ + VariantFeatureConfig( + # example :: gpu -- multi-valued, since the package can target multiple GPUs + name="gpu", + # [narf, poit, zort] + values=_ALL_GPUS, + multi_value=True, + ), + VariantFeatureConfig( + # example :: min_version -- single-valued, since there is always one minimum + name="min_version", + # [1, 2, 3, 4] (order doesn't matter) + values=[str(x) for x in range(1, _MAX_VERSION + 1)], + multi_value=False, + ), + ] + + # properties compatible with the system + @staticmethod + def get_supported_configs() -> list[VariantFeatureConfig]: + current_version = _get_current_version() + if current_version is None: + # no runtime found, system not supported at all + return [] + + return [ + VariantFeatureConfig( + name="min_version", + # [current, current - 1, ..., 1] + values=[str(x) for x in range(current_version, 0, -1)], + multi_value=False, + ), + VariantFeatureConfig( + name="gpu", + # this may be empty if no GPUs are supported -- 'example :: gpu feature' is not supported then + # but wheels with no GPU-specific code and only 'example :: min_version' could still be installed + values=[x for x in _ALL_GPUS if _is_gpu_available(x)], + multi_value=True, + ), + ] + + +Future extensions +''''''''''''''''' + +The future versions of this specification, as well as third-party +extensions may introduce additional properties and methods on the plugin +instances. The implementations should ignore additional attributes. + +For best compatibility, all private attributes should be prefixed with +an underscore (``_``) character to avoid incidental conflicts with +future extensions. + + +Build backends +-------------- + +As a build backend can’t determine whether the frontend supports variant +wheels or not, :pep:`517` and :pep:`660` hooks must build non-variant wheels +by default. Build backends may provide ways to request variant builds. +This specification does not define any specific configuration. + + +Variant environment markers +--------------------------- + +Four new :ref:`environment markers +` are introduced in +dependency specifications: + +1. ``variant_namespaces`` corresponding to the set of namespaces of all + the variant properties that the wheel variant was built for. +2. ``variant_features`` corresponding to the set of + ``namespace :: feature`` pairs of all the variant properties that the + wheel variant was built for. +3. ``variant_properties`` corresponding to the set of + ``namespace :: feature :: value`` tuples of all the variant + properties that the wheel variant was built for. +4. ``variant_label`` corresponding to the exact variant label that the + wheel was built with. For the non-variant wheel, it is an empty + string. + +The markers evaluating to sets of strings must be matched via the ``in`` +or ``not in`` operator, e.g.: + +.. code:: + + # satisfied by any "foo :: * :: *" property + dep1; "foo" in variant_namespaces + # satisfied by any "foo :: bar :: *" property + dep2; "foo :: bar" in variant_features + # satisfied only by "foo :: bar :: baz" property + dep3; "foo :: bar :: baz" in variant_properties + +The ``variant_label`` marker is a plain string: + +.. code:: + + # satisfied by the variant "foobar" + dep4; variant_label == "foobar" + # satisfied by any wheel other other than the null variant + # (including the non-variant wheel) + dep5; variant_label != "null" + # satisfied by the non-variant wheel + dep6; variant_label == "" + +Implementations must ignore differences in whitespace while matching the +features and properties. + +Variant marker expressions must be evaluated against the variant +properties stored in the wheel being installed, not against the current +output of the provider plugins. If a non-variant wheel was selected or +built, all variant markers evaluate to ``False``. + + +ABI Dependency Variant Namespace (Optional) +------------------------------------------- + +This section describes an **optional** extension to the wheel variant +specification. Tools that choose to implement this feature must follow +this specification. Tools that do not implement this feature must treat +the variants using it as incompatible, and should inform users when such +wheels are skipped. + +The variant namespace ``abi_dependency`` is reserved for expressing that +different builds of the same version of a package are compatible with +different versions or version ranges of a dependency. This namespace +must not be used by any variant provider plugin, it must not be listed +in ``providers`` metadata, and can only appear in a built wheel variant +property. + +Within this namespace, zero or more properties can be used to express +compatible dependency versions. For each property, the feature name must +be the :ref:`normalized name ` of the +dependency, whereas the value must be a valid release segment of +a public version identifier, as defined by the +:doc:`packaging:specifications/version-specifiers` specification. +It must contain up to three version components, that are matched against +the installed version same as the ``=={value}.*`` specifier. Notably, +trailing zeroes match versions with fewer components (e.g. ``2.0`` +matches release ``2`` but not ``2.1``). This also implies that the +property values have different semantics than PEP 440 versions, in +particular ``2``, ``2.0`` and ``2.0.0`` represent different ranges. + +Versions with nonzero epoch are not supported. + +==================================== ================== +Variant Property Matching Rule +==================================== ================== +``abi_dependency :: torch :: 2`` ``torch==2.*`` +``abi_dependency :: torch :: 2.9`` ``torch==2.9.*`` +``abi_dependency :: torch :: 2.8.0`` ``torch==2.8.0.*`` +==================================== ================== + +Multiple variant properties with the same feature name can be used to +indicate wheels compatible with multiple providing package versions, +e.g.: + +.. code:: text + + abi_dependency :: torch :: 2.8.0 + abi_dependency :: torch :: 2.9.0 + +This means the wheel is compatible with both PyTorch 2.8.0 and 2.9.0. + + +How to teach this +================= + +Python package users +-------------------- + +The primary source of information for Python package users should be +installer documentation, supplemented by helpful informational messages +from command-line interface, and tutorials. Users without special needs +should not require any special variant awareness. Advanced users would +specifically need documentation on (provided the installer in question +implements these features): + +- enabling untrusted provider plugins and the security implications of + that + +- controlling provider usage, in particular enabling optional providers, + disabling undesirable plugins or disabling variant usage in general + +- explicitly selecting variants, as well as controlling variant + selection process + +- configuring variant selection for remote deployment targets, for + example using a static file generated on the target + +The installer documentation may also be supplemented by documentation +specific to Python projects, in particular their installation +instructions. + +For the transition period, during which some package managers do and +some do not support variant wheels, users need to be aware that certain +features may only be available with certain tools. + + +Python package maintainers +-------------------------- + +The primary source of information for maintainers of Python packages +should be build backend documentation, supplemented by tutorials. The +documentation needs to indicate: + +- how to declare variant support in ``pyproject.toml`` + +- how to use variant environment markers to specify dependencies + +- how to build variant wheels + +- how to publish them and generate the ``*-variants.json`` file on local + indexes + +The maintainers will also need to peruse provider plugin documentation. +They should also be aware which provider plugins are considered trusted +by commonly used installers, and know the implications of using untrusted +plugins. These materials may also be supplemented by generic documents +explaining publishing variant wheels, along with specific example use +cases. + +For the transition period, package maintainers need to be aware that +they should still publish non-variant wheels for backwards +compatibility. + + +Backwards compatibility +======================= + +Existing installers must not accidentally install variant wheels, as +they require additional logic to determine whether a wheel is compatible +with the user’s system. This is achieved by `extending wheel +filename <#extended-wheel-filename>`__ through adding a +``-{variant label}`` component to the end of the filename, effectively +causing variant wheels to be rejected by common installer +implementations. For backwards compatibility, a regular wheel can be +published in addition to the variant wheels. It will be the only wheel +supported by incompatible installers, and the least preferred wheel for +variant-compatible installers. + +Aside from this explicit incompatibility, the specification makes +minimal and non-intrusive changes to the binary package format. The +`variant metadata`_ is placed in a separate file in the ``.dist-info`` +directory, which should be preserved by tools that are not concerned +with variants, limiting the necessary changes to updating the filename +validation algorithm (if there is one). + +If the new `variant environment markers`_ are used in wheel dependencies, these +wheels will be incompatible with existing tools. This is a general +problem with the design of environment markers, and not specific to +wheel variants. It is possible to work around this problem by partially +evaluating environment markers at build time, and removing the markers +or dependencies specific to variant wheels from the regular wheel. + +`Build backends`_ produce non-variant wheels to preserve backwards +compatibility with existing frontends. Variant wheels can only be output +on explicit user request. + +By using a separate ``*-variants.json`` `file for shared metadata +<#name-version-variants-json-the-index-level-variant-metadata-file>`__, +it is +possible to use variant wheels on an index that does not specifically +support variant metadata. However, the index must permit distributing +wheels that use the extended filename syntax and the JSON file. + + +Reference implementation +======================== + +The `variantlib `__ project +contains a reference implementation of all the protocols and algorithms +introduced in this PEP, as well as a command-line tool to convert +wheels, generate the ``*-variants.json`` index and query plugins. + +A client for installing variant wheels is implemented in a +`uv branch `__. + +The `Wheel Variants +monorepo `__ +includes example implementations of provider plugins, as well as +modified versions of build backends featuring variant wheel building +support and modified versions of some Python packages demonstrating +variant wheel uses. + + +Rejected ideas +============== + +An approach without provider plugins +------------------------------------ + +The support for additional variant properties could technically be +implemented without introducing provider plugins, but rather defining +the available properties and their discovery methods as part of the +specification, much like how wheel tags are implemented currently. +However, the existing wheel tag logic already imposes a significant +complexity on packaging tools that need to maintain the logic for +generating supported tags, partially amortized by the data provided by +the Python interpreter itself. + +Every new axis would be imposing even more effort on package manager +maintainers, who would have to maintain an algorithm to determine the +property compatibility. This algorithm could become quite complex, possibly +needing to account for different platforms, hardware versions and +requiring more frequent updates than the one for platform tags. This would also +significantly increase the barrier towards adding new axes and therefore +the risk of lack of feature parity between different installers, as every new +axis will be imposing additional maintenance cost. + +For comparison, the plugin design essentially democratizes the variant +properties. Provider plugins can be maintained independently by people +having the necessary knowledge and hardware. They can be updated as +frequently as necessary, independently of package managers. The decision +to use a particular provider falls entirely on the maintainer of package +needing it, though they need to take into consideration that using +plugins that are not vetted by the common installers will inconvenience +their users. + + +Resolving variants to separate packages +--------------------------------------- + +An alternative proposal was to publish the variants of the package as +separate projects on the index, along with the main package serving as a +“resolver” directing to other variants via its metadata. For example, a +``torch`` package could indicate the conditions for using ``torch-cpu``, +``torch-cu129``, etc. subpackages. + +Such an approach could possibly feature better backwards compatibility +with existing tools. The changes would be limited to installers, and +even with pre-variant installers the users could explicitly request +installing a specific variant. However, it poses problems at multiple +levels. + +The necessity of creating a new project for every variant will lead to +the proliferation of old projects, such as ``torch-cu123``. While the +use of resolver package will ensure that only the modern variants are +used, users manually installing packages and cross-package dependencies +may accidentally be pinning to old variant projects, or even fall victim +to name squatting. For comparison, the variant wheel proposal scopes +variants to each project version, and ensures that only the project +maintainers can upload them. + +Furthermore, it requires significant changes to the dependency resolver +and package metadata formats. In particular, the dependency resolver +would need to query all “resolver” packages before performing +resolution. It is unclear how to account for such variants while +performing universal resolution. The one-to-one mapping between +dependencies and installed packages would be lost, as a ``torch`` +dependency could effectively be satisfied by ``torch-cu129``. + + +Appendices +========== + +- :ref:`0817-variant-json-schema` + + +Acknowledgements +================ + +This work would not have been possible without the contributions and +feedback of many people in the Python packaging community. In +particular, we would like to credit the following individuals for their +help in shaping this PEP (in alphabetical order): + +Alban Desmaison, Bradley Dice, Chris Gottbrath, +Dmitry Rogozhkin, Emma Smith, Geoffrey Thomas, Henry Schreiner, +Jeff Daily, Jeremy Tanner, Jithun Nair, Keith Kraus, Leo Fang, +Mike McCarty, Nikita Shulga, Paul Ganssle, Philip Hyunsu Cho, +Robert Maynard, Vyas Ramasubramani, and Zanie Blue. diff --git a/peps/pep-0817/appendix-variant-metadata-json-schema.rst b/peps/pep-0817/appendix-variant-metadata-json-schema.rst new file mode 100644 index 00000000000..b7603358f20 --- /dev/null +++ b/peps/pep-0817/appendix-variant-metadata-json-schema.rst @@ -0,0 +1,11 @@ +:orphan: + +.. _0817-variant-json-schema: + +Appendix: JSON Schema for Variant Metadata +========================================== + +.. literalinclude:: variant_schema.json + :language: json + :linenos: + :name: variant-json-schema diff --git a/peps/pep-0817/avx512_gromacs_benchmark.png b/peps/pep-0817/avx512_gromacs_benchmark.png new file mode 100644 index 0000000000000000000000000000000000000000..dd75e49e1e8f30df8189fee10325cd3f7bae31ca GIT binary patch literal 50433 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 C9UhGU literal 0 HcmV?d00001 diff --git a/peps/pep-0817/pytorch_variant_selector.png b/peps/pep-0817/pytorch_variant_selector.png new file mode 100644 index 0000000000000000000000000000000000000000..d3e8034e8ff053959c7e6c38c371660dc67f0b91 GIT binary patch literal 30716 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 ) z4GdZ!Anq}2HPs&Bg?p&uC~Ur@GSKm-P9&Tdo?yy!bseDLX%2v;+w&7Y1L?IQfKu~q zjNH1@t&C6)Vm)MCj43Q|{C;)3Rx4OH_$9>Nz6pp$^iyzrZ+AC=N)%VA0tk?>u(mEB zUvcWK&?D7(P?G+xf=z39SN%9&`H^qn%tBNt7-X1-;RV z@beeib_6_|Ve&S~pyX0!qSyQNPyovkJ+^pfS<0zrs$rdGdZi)e-0a5SK|~wTv#*D&U8+ zy|^c@Y5im^F65thP0bAn$wnMcXUv)}(@$5NE?7aWo#N`^g6!?IRYV}zk8OQdJ)U@b z;-6RHYvQ-;8G6(x+Du~)QtO&Z?7T6^0;a2^4Qv5T3%^S87*UNU-P}==j{j*Hfd)PA zZNNkKsQE1(@ILw?UX^)Ry-5yZ`!rx$00J#g=@CqQ&`}m$xOkr_U6(1~wYJ7=$tK#I zVp}RyE|&@98{Qd!c0|up86KpDeqYLxj1m5G=gB^`yQW%Wd@8FlL9`>D^9sYH<8& z`MVlA>gtcD^W9^C0)85AA?c=HP}y?yT7u1^GJS?@J<-cKmn}Kp9eq@TPm$S>$^>0X&cOSwZ5RP0K6zi>jMLmbMnjR=FWv;;p|~bC~i-2P!J$Q!Y5( zl<&?{n5j%=CQtIIU`v!#d^DcOGRed3^)W+h@$Xnor(L*9G#)SGXD3_clrX3|9`3)n zGS%x^iQ%@*u2{=XAaxJD^&8wWZPHDrBDf9#lpvf9>H>2Ro4((^zDeUPT+RtZ-Gv1}L0^g&WT z>Mac}9FS^t>K-@T*u>SZ4$0MOI6d-mIy_OY&Z5<}$&ig5bKvG~lU;AtMzeuxKIMLe zXGD@@J)uU8|W~t*9#VJ?}4VD%=4ztV}9orusKep z!p2p*NeAzpPDHQoojR!Pg0HD2p2AmrGJdP_OX@6!EskHP+lP4zSYn1xT}?Rbzmi7e z%Rb@O+jJwf799h4Wh%*;K`PP^uin4oOl|JS34XjKZTiDBVzc|?Q&|ys|JUG7i?KvI zQ$h8(T%#Z!l5b$%Ktbbhk}sN6(d|CI;>U@)z)L6|aH|qG-FiFhYg_Yk5jX1o{>qW& zdc%v;0%X?Z^Si1BHAQ&7GQr(QriBY)YP7$bazg$luejyHU9~3ObJUw%r*rLHjKWBh zP#6>%jVr)|dLqptnv-4G{krMZ12CdtEkkC;@B5}y*>%ZFv<+7JuWFphbG74Mpa=BY z&PzBfuK^g0dmJ~Be3x>qATnJr@oWsZ*7OeOEpY3~_DZ;)+CQAtqoxjAd0V|PvPebx zrCTfr@H47hOpKr(>y2-)c5+j0K!HO}oj4~fp_0ltQgGd4apjc%>io-brGaPZ6>emO zL(F)@yMoq&oA&flt2N-mci+vb_Po2a8%~kcvpkqyd4h{sB&<#Sp5!Af+Q5&GIlxOD zb_&O~$!HZ7C)nuw>al+|TN;U=KgxlNsEL`&zPJc7@8%9&o%6r4*ng6}SdD@8Y|%&jay{9oAfXiA;d6c;@b z{{nUGsRSWUY{y>=xyo9+4c%(LIL8>rBVuv;?i8S!8kd>nMUDnmp-iAEp3D$fai^?%zJ*Yle=CFLXw{Rii0yOR>D|ZcUI=Xou-IT4;WsClHx|RW(_WWau28j)=oLGa(S`Yaw zaw9WAA48p-nM9oo7g23@v|GzqU_a9O1{~Gz74kbQ5e$xL_^M6d_e-HBCn*ZsF z2LkT_|Nrt6{`Vxx1axx#B?=$x-=o4+{!{T^^R@-)D8DOBozbYyb^IrzJq9gH)&3_3 w*MS$6-|6K1tH=ZSchTnG3jZzV#rcE=Fk;ov_WsfT4{1Y5PF=QE+C1$40B8ZFrvLx| literal 0 HcmV?d00001 diff --git a/peps/pep-0817/variant_schema.json b/peps/pep-0817/variant_schema.json new file mode 100644 index 00000000000..bf4757f013a --- /dev/null +++ b/peps/pep-0817/variant_schema.json @@ -0,0 +1,163 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://wheelnext.dev/variants.json", + "title": "{name}-{version}-variants.json", + "description": "Combined index metadata for wheel variants", + "type": "object", + "properties": { + "default-priorities": { + "description": "Default provider priorities", + "type": "object", + "properties": { + "namespace": { + "description": "Default namespace priorities", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 1, + "uniqueItems": true + }, + "feature": { + "description": "Default feature priorities (by namespace)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Preferred features", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 0, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true + }, + "property": { + "description": "Default property priorities (by namespace)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Default property priorities (by feature) name", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 0, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true, + "required": [ + "namespace" + ] + }, + "providers": { + "description": "Mapping of namespaces to provider information", + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_]+$": { + "type": "object", + "description": "Provider information", + "properties": { + "plugin-api": { + "description": "Object reference to plugin class", + "type": "string", + "pattern": "^([a-zA-Z0-9._]+ *: *[a-zA-Z0-9._]+)|([a-zA-Z0-9._]+)$" + }, + "enable-if": { + "description": "Environment marker specifying when to enable the plugin", + "type": "string", + "minLength": 1 + }, + "optional": { + "description": "Whether the provider is optional", + "type": "boolean" + }, + "plugin-use": { + "description": "Whether a plugin is used: not at all, at build time or both at build and install time", + "type": "string", + "enum": [ + "none", + "build", + "all" + ] + }, + "requires": { + "description": "Dependency specifiers for how to install the plugin", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 0, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true, + "required": [ + "requires" + ] + } + }, + "additionalProperties": false, + "uniqueItems": true + }, + "variants": { + "description": "Mapping of variant labels to properties", + "type": "object", + "patternProperties": { + "^[a-z0-9_.]{1,16}$": { + "type": "object", + "description": "Mapping of namespaces in a variant", + "patternProperties": { + "^[a-z0-9_.]+$": { + "patternProperties": { + "^[a-z0-9_.]+$": { + "description": "list of possible values for this variant feature.", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "uniqueItems": true, + "additionalProperties": false + } + }, + "uniqueItems": true, + "additionalProperties": false + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "required": [ + "default-priorities", + "providers", + "variants" + ], + "uniqueItems": true +} From 5c3b8670197b702d40367c3d4d6e8b9a1f8e46f0 Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Wed, 10 Dec 2025 18:28:35 -0500 Subject: [PATCH 02/13] Code-Owners editing --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 41561368204..b10de5315e1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -691,8 +691,8 @@ peps/pep-0811.rst @sethmlarson @gpshead peps/pep-0814.rst @vstinner @corona10 peps/pep-0815.rst @emmatyping peps/pep-0816.rst @brettcannon -peps/pep-0817.rst @DEKHTIARJonathan @mgorny @konstin @rgommers @atalman @charliermarsh @msarahan @seemethere @warsaw @dstufft @aterrel -peps/pep-0817/ @DEKHTIARJonathan @mgorny @konstin @rgommers @atalman @charliermarsh @msarahan @seemethere @warsaw @dstufft @aterrel +peps/pep-0817.rst @warsaw @dstufft +peps/pep-0817/ @warsaw @dstufft # ... peps/pep-2026.rst @hugovk # ... From 8e3d5ea7baab6b7147b37f7dd5822bca6db910d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 11 Dec 2025 14:42:17 +0100 Subject: [PATCH 03/13] Apply suggestions from code review Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- peps/pep-0817.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index a93be6f023f..26def9a9db9 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -31,7 +31,7 @@ against different dependency ABIs. This is particularly challenging for the scientific computing, artificial intelligence (AI), machine learning (ML), and high-performance computing (HPC) communities. -This PEP proposes “Wheel Variants,” an extension to the +This PEP proposes "Wheel Variants", an extension to the :doc:`packaging:specifications/binary-distribution-format`. This extension introduces a mechanism for package maintainers to declare multiple build variants for the same package version, while allowing @@ -52,8 +52,8 @@ the best user experience. Definitions =========== -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, -“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in :rfc:`2119`. @@ -82,8 +82,8 @@ wheel of considerable size, or using separate package names (``mypackage-gpu``, ``mypackage-cpu``, etc.). Each of these approaches has significant drawbacks. -According to the `2024 Python Developers -Survey `__, +According to the `2024 Python Developers Survey +`__, a significant portion of respondents over the last years have been successively using Python for scientific computing purposes, covering such areas as Data analysis (steadily over 40% respondents), Machine @@ -224,7 +224,7 @@ Tools need to implement special handling for the way PyTorch uses local version segments. These requirements break the pattern that packages are usually installed with. Problems with installing PyTorch are a very common point of user confusion. To quantify this, on -2025-12-05, 552 out of 8136, or 6.8%, of issues on the `uv's issue +2025-12-05, 552 out of 8136, or 6.8%, of issues on `uv's issue tracker `__ contained the term "torch". From f0d43f653e933d1c6a45c441ce2ca292a676a62e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 11 Dec 2025 14:50:27 +0100 Subject: [PATCH 04/13] Switch to plain quotation marks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0817.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index 26def9a9db9..8bc35c12375 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -117,11 +117,11 @@ significant performance improvement. CPUs. Vertical axis shows performance expressed in ns/day, a GROMACS-specific measure of simulation speed (higher is better). - “Compiling `GROMACS `_ for architectures + Compiling `GROMACS `_ for architectures that can exploit the AVX-512 instructions supported by the Intel Cascade Lake microarchitecture gives an additional 18% performance improvement relative to using AVX2 instructions, with a speedup of - about 70% compared to a generic GROMACS installation with only SSE2.” + about 70% compared to a generic GROMACS installation with only SSE2. — `archspec: A library for detecting, labeling, and reasoning about microarchitectures `__ @@ -194,7 +194,7 @@ Separate package indexes as variants Projects such as `PyTorch `__ and `RAPIDS `__ -currently distribute packages that approximate “variants” through +currently distribute packages that approximate "variants" through separate package indexes with custom URLs. We will use the example of PyTorch, while the problem, the workarounds and the impact for users also apply to other packages. @@ -317,7 +317,7 @@ JAX includes 12 extra selectors to cover all use cases - many of which overlap and could be misleading to users if they don’t read the documentation in detail. -It should be noted that most of these “extras” are technically mutually +It should be noted that most of these "extras" are technically mutually exclusive, though it is currently impossible to correctly express this within the package metadata. @@ -432,7 +432,7 @@ computing clusters with different architectures (CPU, Hardware accelerators, ASICS, etc.). The current system requires environment-specific installation procedures, making reproducible deployment difficult. This situation also contributes to making -“scientific papers” difficult to reproduce. Application authors focused +"scientific papers" difficult to reproduce. Application authors focused on improving that are hindered by the packaging hurdles too: We've been developing a package manager for Spyder, a Python IDE for @@ -603,7 +603,7 @@ resolve dependencies like: conda install pytorch mkl **Mutex Metapackages**: Python metadata and conda metadata do not have -good ways to express ideas like “this package conflicts with that one.” +good ways to express ideas like "this package conflicts with that one." The main mechanism for enforcement is sharing a common package name - only one package with a given name can exist at one time. Mutex metapackages are sets of packages with the same name, but different @@ -1071,7 +1071,7 @@ Wheel variant glossary ---------------------- This section focuses specifically on the vocabulary used by the proposed -“Wheel Variant” standard: +"Wheel Variant" standard: Variant Wheels Wheels that share the same distribution name, version, build number, @@ -1189,9 +1189,9 @@ The Wheel Variant PEP introduces four key components: 2. `Variant metadata`_ format: Standardized metadata describing variant properties and provider requirements. - a. Metadata specification at “project level” inside + a. Metadata specification at "project level" inside ``pyproject.toml`` - b. Metadata specification of “built packages” inside two JSON files: + b. Metadata specification of "built packages" inside two JSON files: i. ``*.dist-info/variant.json``: Individual wheel variant metadata. @@ -1274,7 +1274,7 @@ The feature value must consist only of ``0-9``, ``a-z``, ``_`` and ``.`` ASCII Characters (``^[a-z0-9_.]+$``). It must correspond to a valid value defined by the respective variant provider for the feature. -If a feature is marked as “multi-value” by the provider plugin, a single +If a feature is marked as "multi-value" by the provider plugin, a single variant wheel may define multiple properties sharing the same namespace and feature name. Otherwise, only a single value can correspond to a single pair of namespace and feature name within a variant wheel. @@ -1928,7 +1928,7 @@ methods. The specifics are provided in the subsequent sections. API endpoint '''''''''''' -The location of the plugin code is called an “API endpoint”, and it is +The location of the plugin code is called an "API endpoint", and it is expressed using the object reference notation following the :doc:`packaging:specifications/entry-points`: @@ -1994,7 +1994,7 @@ significant. It is defined using the following protocol: """List of values, possibly ordered from most preferred to least""" raise NotImplementedError -A “variant feature config” must provide the following properties or attributes: +A "variant feature config" must provide the following properties or attributes: - ``name: str`` specifying the feature name. @@ -2061,14 +2061,14 @@ The plugin interface must define the following attributes: The plugin interface must provide the following functions: - ``get_all_config() -> list[VariantFeatureConfigType]`` that returns a - list of “variant feature configs” describing all valid variant + list of "variant feature configs" describing all valid variant features within the plugin’s namespace, along with all their permitted values. The ordering of the lists is insignificant here. A particular plugin version must always return the same value (modulo ordering), irrespective of any runtime conditions. - ``get_supported_configs() -> list[VariantFeatureConfigType]`` that - returns a list of “variant feature configs” describing the variant + returns a list of "variant feature configs" describing the variant features within the plugin’s namespace that are compatible with this particular system, along with their values that are supported. The variant feature and value lists must be ordered from the most @@ -2445,7 +2445,7 @@ Resolving variants to separate packages An alternative proposal was to publish the variants of the package as separate projects on the index, along with the main package serving as a -“resolver” directing to other variants via its metadata. For example, a +"resolver" directing to other variants via its metadata. For example, a ``torch`` package could indicate the conditions for using ``torch-cpu``, ``torch-cu129``, etc. subpackages. @@ -2466,7 +2466,7 @@ maintainers can upload them. Furthermore, it requires significant changes to the dependency resolver and package metadata formats. In particular, the dependency resolver -would need to query all “resolver” packages before performing +would need to query all "resolver" packages before performing resolution. It is unclear how to account for such variants while performing universal resolution. The one-to-one mapping between dependencies and installed packages would be lost, as a ``torch`` From e16bf03f7e929db1839afca000ce63e58360e51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 11 Dec 2025 14:53:25 +0100 Subject: [PATCH 05/13] Move "definitions" to "specification" section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0817.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index 8bc35c12375..e2232b758c9 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -49,14 +49,6 @@ The goal is for a familiar ``[uv] pip install `` to provide the best user experience. -Definitions -=========== - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -document are to be interpreted as described in :rfc:`2119`. - - Motivation ========== @@ -1067,6 +1059,14 @@ variant-aware tools while being ignored by programs that do not implement this specification. +Definitions +----------- + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this +document are to be interpreted as described in :rfc:`2119`. + + Wheel variant glossary ---------------------- From fede5ffc49216f9d9370932826e080b1777a1d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 11 Dec 2025 14:56:41 +0100 Subject: [PATCH 06/13] Reflow the cupy package list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0817.rst | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index e2232b758c9..b2c4a34b6cc 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -286,12 +286,18 @@ maintainers. .. code:: text - cupy-cuda100 cupy-cuda101 cupy-cuda102 cupy-cuda110 cupy-cuda111 cupy-cuda112 cupy-cuda113 cupy-cuda114 cupy-cuda115 - cupy-cuda116 cupy-cuda117 cupy-cuda118 cupy-cuda119 cupy-cuda11x cupy-cuda120 cupy-cuda121 cupy-cuda122 cupy-cuda123 - cupy-cuda124 cupy-cuda125 cupy-cuda126 cupy-cuda127 cupy-cuda128 cupy-cuda129 cupy-cuda12x cupy-cuda13x cupy-cuda70 - cupy-cuda75 cupy-cuda80 cupy-cuda90 cupy-cuda91 cupy-cuda92 cupy-rocm-4-0 cupy-rocm-4-1 cupy-rocm-4-2 cupy-rocm-4-3 - cupy-rocm-4-4 cupy-rocm-4-5 cupy-rocm-5-0 cupy-rocm-5-1 cupy-rocm-5-2 cupy-rocm-5-3 cupy-rocm-5-4 cupy-rocm-5-5 - cupy-rocm-5-6 cupy-rocm-5-7 cupy-rocm-5-8 cupy-rocm-5-9 cupy-rocm-6-0 cupy-rocm-6-1 cupy-rocm-6-2 cupy-rocm-6-3 + cupy-cuda70 cupy-cuda75 cupy-cuda80 cupy-cuda90 cupy-cuda91 + cupy-cuda92 cupy-cuda100 cupy-cuda101 cupy-cuda102 cupy-cuda110 + cupy-cuda111 cupy-cuda112 cupy-cuda113 cupy-cuda114 cupy-cuda115 + cupy-cuda116 cupy-cuda117 cupy-cuda118 cupy-cuda119 cupy-cuda11x + cupy-cuda120 cupy-cuda121 cupy-cuda122 cupy-cuda123 cupy-cuda124 + cupy-cuda125 cupy-cuda126 cupy-cuda127 cupy-cuda128 cupy-cuda129 + cupy-cuda12x cupy-cuda13x + cupy-rocm-4-0 cupy-rocm-4-1 cupy-rocm-4-2 cupy-rocm-4-3 + cupy-rocm-4-4 cupy-rocm-4-5 cupy-rocm-5-0 cupy-rocm-5-1 + cupy-rocm-5-2 cupy-rocm-5-3 cupy-rocm-5-4 cupy-rocm-5-5 + cupy-rocm-5-6 cupy-rocm-5-7 cupy-rocm-5-8 cupy-rocm-5-9 + cupy-rocm-6-0 cupy-rocm-6-1 cupy-rocm-6-2 cupy-rocm-6-3 Extra-Dependency as variants From c68242d106f0fa5c1b064a4e4d22b41f342905b6 Mon Sep 17 00:00:00 2001 From: Jonathan DEKHTIAR Date: Thu, 11 Dec 2025 22:05:33 -0500 Subject: [PATCH 07/13] Update peps/pep-0817/appendix-variant-metadata-json-schema.rst Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- peps/pep-0817/appendix-variant-metadata-json-schema.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peps/pep-0817/appendix-variant-metadata-json-schema.rst b/peps/pep-0817/appendix-variant-metadata-json-schema.rst index b7603358f20..27f3871b867 100644 --- a/peps/pep-0817/appendix-variant-metadata-json-schema.rst +++ b/peps/pep-0817/appendix-variant-metadata-json-schema.rst @@ -6,6 +6,6 @@ Appendix: JSON Schema for Variant Metadata ========================================== .. literalinclude:: variant_schema.json - :language: json - :linenos: - :name: variant-json-schema + :language: json + :linenos: + :name: variant-json-schema From 64f32ed03b60dcc18cc77f4dbb4e43108af81669 Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Thu, 11 Dec 2025 22:10:17 -0500 Subject: [PATCH 08/13] [PEP Review] JSON Schema Indent switched to 2 --- peps/pep-0817/variant_schema.json | 310 +++++++++++++++--------------- 1 file changed, 155 insertions(+), 155 deletions(-) diff --git a/peps/pep-0817/variant_schema.json b/peps/pep-0817/variant_schema.json index bf4757f013a..010fabaeb96 100644 --- a/peps/pep-0817/variant_schema.json +++ b/peps/pep-0817/variant_schema.json @@ -1,163 +1,163 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://wheelnext.dev/variants.json", - "title": "{name}-{version}-variants.json", - "description": "Combined index metadata for wheel variants", - "type": "object", - "properties": { - "default-priorities": { - "description": "Default provider priorities", - "type": "object", - "properties": { - "namespace": { - "description": "Default namespace priorities", - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-z0-9_]+$" - }, - "minItems": 1, - "uniqueItems": true - }, - "feature": { - "description": "Default feature priorities (by namespace)", - "type": "object", - "patternProperties": { - "^[a-z0-9_]+$": { - "description": "Preferred features", - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-z0-9_]+$" - }, - "minItems": 0, - "uniqueItems": true - } - }, - "additionalProperties": false, - "uniqueItems": true - }, - "property": { - "description": "Default property priorities (by namespace)", - "type": "object", - "patternProperties": { - "^[a-z0-9_]+$": { - "description": "Default property priorities (by feature) name", - "type": "object", - "patternProperties": { - "^[a-z0-9_]+$": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-z0-9_.]+$" - }, - "minItems": 0, - "uniqueItems": true - } - }, - "additionalProperties": false, - "uniqueItems": true - } - }, - "additionalProperties": false, - "uniqueItems": true - } - }, - "additionalProperties": false, - "uniqueItems": true, - "required": [ - "namespace" - ] + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://wheelnext.dev/variants.json", + "title": "{name}-{version}-variants.json", + "description": "Combined index metadata for wheel variants", + "type": "object", + "properties": { + "default-priorities": { + "description": "Default provider priorities", + "type": "object", + "properties": { + "namespace": { + "description": "Default namespace priorities", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 1, + "uniqueItems": true }, - "providers": { - "description": "Mapping of namespaces to provider information", - "type": "object", - "patternProperties": { - "^[A-Za-z0-9_]+$": { - "type": "object", - "description": "Provider information", - "properties": { - "plugin-api": { - "description": "Object reference to plugin class", - "type": "string", - "pattern": "^([a-zA-Z0-9._]+ *: *[a-zA-Z0-9._]+)|([a-zA-Z0-9._]+)$" - }, - "enable-if": { - "description": "Environment marker specifying when to enable the plugin", - "type": "string", - "minLength": 1 - }, - "optional": { - "description": "Whether the provider is optional", - "type": "boolean" - }, - "plugin-use": { - "description": "Whether a plugin is used: not at all, at build time or both at build and install time", - "type": "string", - "enum": [ - "none", - "build", - "all" - ] - }, - "requires": { - "description": "Dependency specifiers for how to install the plugin", - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "minItems": 0, - "uniqueItems": true - } - }, - "additionalProperties": false, - "uniqueItems": true, - "required": [ - "requires" - ] - } - }, - "additionalProperties": false, - "uniqueItems": true + "feature": { + "description": "Default feature priorities (by namespace)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Preferred features", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 0, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true }, - "variants": { - "description": "Mapping of variant labels to properties", - "type": "object", - "patternProperties": { - "^[a-z0-9_.]{1,16}$": { - "type": "object", - "description": "Mapping of namespaces in a variant", - "patternProperties": { - "^[a-z0-9_.]+$": { - "patternProperties": { - "^[a-z0-9_.]+$": { - "description": "list of possible values for this variant feature.", - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-z0-9_.]+$" - }, - "minItems": 1, - "uniqueItems": true - } - }, - "uniqueItems": true, - "additionalProperties": false - } - }, - "uniqueItems": true, - "additionalProperties": false + "property": { + "description": "Default property priorities (by namespace)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Default property priorities (by feature) name", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 0, + "uniqueItems": true } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true, + "required": [ + "namespace" + ] + }, + "providers": { + "description": "Mapping of namespaces to provider information", + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_]+$": { + "type": "object", + "description": "Provider information", + "properties": { + "plugin-api": { + "description": "Object reference to plugin class", + "type": "string", + "pattern": "^([a-zA-Z0-9._]+ *: *[a-zA-Z0-9._]+)|([a-zA-Z0-9._]+)$" }, - "additionalProperties": false, - "uniqueItems": true + "enable-if": { + "description": "Environment marker specifying when to enable the plugin", + "type": "string", + "minLength": 1 + }, + "optional": { + "description": "Whether the provider is optional", + "type": "boolean" + }, + "plugin-use": { + "description": "Whether a plugin is used: not at all, at build time or both at build and install time", + "type": "string", + "enum": [ + "none", + "build", + "all" + ] + }, + "requires": { + "description": "Dependency specifiers for how to install the plugin", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 0, + "uniqueItems": true + } + }, + "additionalProperties": false, + "uniqueItems": true, + "required": [ + "requires" + ] } + }, + "additionalProperties": false, + "uniqueItems": true }, - "required": [ - "default-priorities", - "providers", - "variants" - ], - "uniqueItems": true + "variants": { + "description": "Mapping of variant labels to properties", + "type": "object", + "patternProperties": { + "^[a-z0-9_.]{1,16}$": { + "type": "object", + "description": "Mapping of namespaces in a variant", + "patternProperties": { + "^[a-z0-9_.]+$": { + "patternProperties": { + "^[a-z0-9_.]+$": { + "description": "list of possible values for this variant feature.", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "uniqueItems": true, + "additionalProperties": false + } + }, + "uniqueItems": true, + "additionalProperties": false + } + }, + "additionalProperties": false, + "uniqueItems": true + } + }, + "required": [ + "default-priorities", + "providers", + "variants" + ], + "uniqueItems": true } From 78ff40b4b60c6f2ad36b3886aebab10ac40077b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 12 Dec 2025 15:20:41 +0100 Subject: [PATCH 09/13] Replace the benchmark chart with SVG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0817.rst | 2 +- peps/pep-0817/avx512_gromacs_benchmark.png | Bin 50433 -> 0 bytes peps/pep-0817/avx512_gromacs_benchmark.svg | 953 +++++++++++++++++++++ 3 files changed, 954 insertions(+), 1 deletion(-) delete mode 100644 peps/pep-0817/avx512_gromacs_benchmark.png create mode 100644 peps/pep-0817/avx512_gromacs_benchmark.svg diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index b2c4a34b6cc..691ec42d4a7 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -95,7 +95,7 @@ As illustrated by `archspec `_, the ability to optimize a package for a specific architecture can lead to significant performance improvement. - .. figure:: pep-0817/avx512_gromacs_benchmark.png + .. figure:: pep-0817/avx512_gromacs_benchmark.svg :alt: A bar graph comparing GROMACS performance (in ns/day) with various targets. The first two bars are labeled "yum (2018.8)" and "generic (SSE2)", reach about 1.0 ns/day and are both diff --git a/peps/pep-0817/avx512_gromacs_benchmark.png b/peps/pep-0817/avx512_gromacs_benchmark.png deleted file mode 100644 index dd75e49e1e8f30df8189fee10325cd3f7bae31ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50433 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 C9UhGU diff --git a/peps/pep-0817/avx512_gromacs_benchmark.svg b/peps/pep-0817/avx512_gromacs_benchmark.svg new file mode 100644 index 00000000000..ce2023f5939 --- /dev/null +++ b/peps/pep-0817/avx512_gromacs_benchmark.svg @@ -0,0 +1,953 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0 + + + 0.5 + + + 1.0 + + + 1.5 + + + 2.0 + + + yum (2018.8) + + + generic (SSE2) + + + ivybridge + + + haswell + + + broadwell + + + skylake_avx512 + + + cascadelake + + + performance (ns/day) + + + + + + + + + + + + + + + SSE2 + + + AVX + + + AVX2 + + + AVX512 + + + + From a31b68bf27e54736b621cf1e7c6af6db49fd8ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 12 Dec 2025 15:33:56 +0100 Subject: [PATCH 10/13] Compact the SVG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0817/avx512_gromacs_benchmark.svg | 955 +-------------------- 1 file changed, 2 insertions(+), 953 deletions(-) diff --git a/peps/pep-0817/avx512_gromacs_benchmark.svg b/peps/pep-0817/avx512_gromacs_benchmark.svg index ce2023f5939..d6691364839 100644 --- a/peps/pep-0817/avx512_gromacs_benchmark.svg +++ b/peps/pep-0817/avx512_gromacs_benchmark.svg @@ -1,953 +1,2 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0.0 - - - 0.5 - - - 1.0 - - - 1.5 - - - 2.0 - - - yum (2018.8) - - - generic (SSE2) - - - ivybridge - - - haswell - - - broadwell - - - skylake_avx512 - - - cascadelake - - - performance (ns/day) - - - - - - - - - - - - - - - SSE2 - - - AVX - - - AVX2 - - - AVX512 - - - - + +0.00.51.01.52.0yum (2018.8)generic (SSE2)ivybridgehaswellbroadwellskylake_avx512cascadelakeperformance (ns/day)SSE2AVXAVX2AVX512 From 73753b9a20f40c6a4dd46e337f4f3b6e1b8f2c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 15 Dec 2025 13:41:07 +0100 Subject: [PATCH 11/13] Reflow text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reflow the text to restore correct text width after all the inline changes and applied suggestions. Signed-off-by: Michał Górny --- peps/pep-0817.rst | 437 ++++++++++++++++++++++++---------------------- 1 file changed, 233 insertions(+), 204 deletions(-) diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index 691ec42d4a7..a456ceeb985 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -42,8 +42,9 @@ proposes: - An evolution of the wheel format called **Wheel Variant** that allows wheels to be distinguished by hardware or software attributes. -- A **variant provider plugin** interface that allows installers to dynamically detect - platform attributes and select the most suitable wheel. +- A **variant provider plugin** interface that allows installers to + dynamically detect platform attributes and select the most suitable + wheel. The goal is for a familiar ``[uv] pip install `` to provide the best user experience. @@ -85,25 +86,26 @@ packaging. This issue is often crossing the boundaries of scientific computing. Wheels currently cannot express more specific CPU requirements, forcing -the maintainers to build and ship only for the oldest CPU baseline they wish to -support. Newer CPU features are sometimes utilized through runtime -dispatching, but for example this does not let software benefit from the -performance improvements related to auto-vectorization for x86-64-v4 -CPUs. +the maintainers to build and ship only for the oldest CPU baseline they +wish to support. Newer CPU features are sometimes utilized through +runtime dispatching, but for example this does not let software benefit +from the performance improvements related to auto-vectorization for +x86-64-v4 CPUs. As illustrated by `archspec `_, -the ability to optimize a package for a specific architecture can lead to -significant performance improvement. +the ability to optimize a package for a specific architecture can lead +to significant performance improvement. .. figure:: pep-0817/avx512_gromacs_benchmark.svg :alt: A bar graph comparing GROMACS performance (in ns/day) with - various targets. The first two bars are labeled "yum (2018.8)" - and "generic (SSE2)", reach about 1.0 ns/day and are both - marked as "SSE2". The next bar is labeled "ivybridge" ("AVX") - and reaches almost 1.5 ns/day. Two following bars are labeled - "haswell" and "broadwell" (both "AVX2") and exceed 1.5 ns/day - slightly. The last two bars are labeled "skylake_avx512" and - "cascadelake" (both "AVX512") and reach almost 2.0 ns/day. + various targets. The first two bars are labeled "yum + (2018.8)" and "generic (SSE2)", reach about 1.0 ns/day and + are both marked as "SSE2". The next bar is labeled + "ivybridge" ("AVX") and reaches almost 1.5 ns/day. Two + following bars are labeled "haswell" and "broadwell" (both + "AVX2") and exceed 1.5 ns/day slightly. The last two bars + are labeled "skylake_avx512" and "cascadelake" (both + "AVX512") and reach almost 2.0 ns/day. Performance of GROMACS 2020.1 built for different generations of CPUs. Vertical axis shows performance expressed in ns/day, a @@ -116,7 +118,8 @@ significant performance improvement. about 70% compared to a generic GROMACS installation with only SSE2. — `archspec: A library for detecting, labeling, and reasoning about - microarchitectures `__ + microarchitectures + `__ The limitations of platform compatibility tags @@ -163,9 +166,9 @@ functionality and performance. The current wheel format is not able to encode that dependency. This lack of flexibility has led many projects to find sub-optimal - yet -necessary - workarounds, such as the manual installation command selector -provided by the PyTorch team. This complexity represents a fundamental -scalability issue with the current tag system. +necessary - workarounds, such as the manual installation command +selector provided by the PyTorch team. This complexity represents a +fundamental scalability issue with the current tag system. This problem is not unique to PyTorch. Projects like `JAX `_, `NumPy `_, @@ -188,8 +191,8 @@ Projects such as `PyTorch `__ and `RAPIDS `__ currently distribute packages that approximate "variants" through separate package indexes with custom URLs. We will use the -example of PyTorch, while the problem, the workarounds and the impact for -users also apply to other packages. +example of PyTorch, while the problem, the workarounds and the impact +for users also apply to other packages. .. figure:: pep-0817/pytorch_variant_selector.png :alt: A grid-based selector for PyTorch versions. Individual rows @@ -200,13 +203,13 @@ users also apply to other packages. Below these rows, the pip install command for the selected variant is provided, utilizing the --index-url parameter. - The PyTorch install selector (https://pytorch.org/get-started/locally/, - captured 22-Aug-2025) + The PyTorch install selector + (https://pytorch.org/get-started/locally/, captured 22-Aug-2025) PyTorch uses a combination of index URLs per accelerator type and local version segments as accelerator tag (such as ``+cu130``, ``+rocm6.4`` or -``+cpu``) . Users need to first determine the correct index URL for their -system, and add an index specifically for PyTorch. +``+cpu``) . Users need to first determine the correct index URL for +their system, and add an index specifically for PyTorch. .. code:: bash @@ -220,22 +223,24 @@ are a very common point of user confusion. To quantify this, on tracker `__ contained the term "torch". -Wheel variants remove this special casing and make GPU and TPU packages work -just as well as regular packages with native code. They also reduce the burden -on the maintainers to run separate infrastructure and to find and use -non-standard features such as local version segments present on an index. +Wheel variants remove this special casing and make GPU and TPU packages +work just as well as regular packages with native code. They also reduce +the burden on the maintainers to run separate infrastructure and to find +and use non-standard features such as local version segments present on +an index. **Induced Security Risk:** This approach has unfortunately led to supply chain attacks - more details on the `PyTorch Blog `__. It’s -a non-trivial problem to address which has forced the PyTorch team to create -a complete mirror of all their dependencies, and is one of the core -motivations behind :pep:`766`. +a non-trivial problem to address which has forced the PyTorch team to +create a complete mirror of all their dependencies, and is one of the +core motivations behind :pep:`766`. The complexity of configuration often leads to projects providing ad-hoc installation instructions rather than covering permanent settings. This can lead to users being unable to cleanly upgrade the packages, or the -upgraded packages being reverted to the default variant on subsequent upgrades. +upgraded packages being reverted to the default variant on subsequent +upgrades. Package names as variants @@ -264,10 +269,11 @@ installed first. This leads to runtime errors, and the possibility of incidentally switching between variants depending on the way package upgrades are ordered. -An additional limitation of this approach is that publishing a new release -synchronously across multiple package names is not currently possible. -:pep:`694` proposes adding such a mechanism for multiple wheels within a -single package, but extending it to multiple packages is not a goal. +An additional limitation of this approach is that publishing a new +release synchronously across multiple package names is not currently +possible. :pep:`694` proposes adding such a mechanism for multiple +wheels within a single package, but extending it to multiple packages is +not a goal. **Induced Security Risk:** proliferation of suffixed variant packages leads users to expect these suffixes in other packages, making name @@ -339,10 +345,10 @@ Bundled universal packages - monolithic builds '''''''''''''''''''''''''''''''''''''''''''''' Including all possible variants in a single wheel is another option, but -this leads to excessively large artifacts, wasting bandwidth and leading to slower -installation times for users who only need one specific variant. In some -cases, such artifacts cannot be hosted on PyPI because they exceed its -size limits. +this leads to excessively large artifacts, wasting bandwidth and leading +to slower installation times for users who only need one specific +variant. In some cases, such artifacts cannot be hosted on PyPI because +they exceed its size limits. Wheel variant selection via source distribution @@ -395,11 +401,12 @@ AI/ML applications where performance optimization is critical: suboptimal experience for hardware-dependent packages. While plugins help with smaller and well scoped packages, users must currently manually identify the correct variant (e.g., ``jax[cuda13]``) to - avoid generic defaults or incompatible combinations. We need a system - where ``pip install jax`` automatically selects packages matching the - user’s hardware, unless explicitly overridden. + avoid generic defaults or incompatible combinations. We need a + system where ``pip install jax`` automatically selects packages + matching the user’s hardware, unless explicitly overridden. - Wheel variants are a clear step in the right direction in this regard. + Wheel variants are a clear step in the right direction in this + regard. — Michael Hudgins, JAX_ Developer Infrastructure Lead @@ -475,8 +482,9 @@ package maintainers). — The PyTorch_ Core Maintainers -The lead maintainer of `XGBoost `_ enumerates a number of problems XGBoost -has that he expects will be addressed by wheel variants: +The lead maintainer of `XGBoost `_ enumerates a +number of problems XGBoost has that he expects will be addressed by +wheel variants: * Large download size, due to the use of "fat binaries" for multiple SMs [GPU targets]. Currently, XGBoost builds for 11 different SMs. @@ -533,12 +541,13 @@ increase maintenance burden, and fragment the ecosystem. **Wheel Variants provide a standardized solution that:** - Enables automatic hardware-appropriate package selection. -- Maintains full backward compatibility with existing tools (i.e., non-variant aware installers, tools, and - indexes remain unaffected). +- Maintains full backward compatibility with existing tools (i.e., + non-variant aware installers, tools, and indexes remain unaffected). - Simplifies package maintenance by offering a unified and flexible solution to the challenge of managing multiple platform-specific package builds and distributions. -- Provides a seamless and predictable experience for users, with little-to-no user input required. +- Provides a seamless and predictable experience for users, with + little-to-no user input required. - Supports the full spectrum of modern computing hardware. - Provides a future-proof and flexible system that can evolve with the ecosystem and future use cases. @@ -550,9 +559,10 @@ Out-of-scope features This PEP tries to present the minimal scope required and leaves aspects to tools to evolve. A non-exhaustive list: -- The format of a static file to select variants deterministically or include - variants in a ``pylock.toml`` file, -- The list of variant providers that are vendored or re-implemented by installers, +- The format of a static file to select variants deterministically or + include variants in a ``pylock.toml`` file, +- The list of variant providers that are vendored or re-implemented by + installers, - The specific opt-in mechanisms and UX for allowing an installer to run non-vendored variant providers, - How to instruct build backends to emit variants through the :pep:`517` @@ -580,8 +590,8 @@ resolution relies on `repodata indexes per platform containing full metadata, making filenames purely identifiers with no parsing requirements. -**Variant System**: In -`2016-2017 `__, +**Variant System**: In `2016-2017 +`__, conda-build introduced variants to differentiate packages with identical name/version but different dependencies. @@ -614,10 +624,12 @@ packages using different dependency libraries, such as NumPy_ using **Example software variants**: `BLAS `__, -`MPI `__, -`OpenMP `__, -`noarch vs -native `__ +`MPI +`__, +`OpenMP +`__, +`noarch vs native +`__ **Virtual Packages**: `Introduced in 2019 `__, virtual packages @@ -626,8 +638,10 @@ constraints. Built packages express dependencies like ``__cuda >=12.8``, and the installer verifies compatibility at install time. Current virtual packages include ``archspec`` (CPU capabilities), OS/system libraries, and CUDA driver version. Detection logic is tool-specific -(`rattler `__, -`mamba `__). +(`rattler +`__, +`mamba +`__). Spack / Archspec @@ -639,8 +653,8 @@ developed for the `Spack `_ package manager. **Variant Model:** CPU Microarchitectures (e.g., ``haswell``, ``skylake``, ``zen2``, ``armv8.1a``) form a `Directed Acyclic Graph -(DAG) encoding binary -compatibility `__, +(DAG) encoding binary compatibility +`__, which helps at resolve to express that ``packageB`` depends on ``packageA``. The ordering is partial because (1) separate ISA families are incomparable, and (2) contemporary designs may have incompatible @@ -658,8 +672,8 @@ operators. as package provenance (``spack install fftw target=broadwell``), automatically selects compiler flags, and enables microarchitecture-aware binary caching. The `European Environment for -Scientific Software Installations -(EESSI) `__ +Scientific Software Installations (EESSI) +`__ distributes optimized builds in separate subdirectories per microarchitecture (e.g., ``x86_64``, ``armv8.1a``, ``haswell``); runtime initialization uses ``archspec`` to select best compatible build @@ -671,29 +685,29 @@ Gentoo Linux `Gentoo Linux `_ is a source-first distribution with support for extensive package customization. This is primarily -achieved via `USE -flags `__: +achieved via `USE flags +`__: boolean flags exposed by individual packages and permitting fine-tuning the enabled features, optional dependencies and some build parameters (e.g. ``jpegxl`` for JPEG XL image format support, -``cpu_flags_x86_avx2`` for AVX2 instruction set use). Flags can be toggled -individually, and separate binary packages can be built for different -sets of flags. The package manager can either pick a binary package with -matching configuration or build from source. +``cpu_flags_x86_avx2`` for AVX2 instruction set use). Flags can be +toggled individually, and separate binary packages can be built for +different sets of flags. The package manager can either pick a binary +package with matching configuration or build from source. -API and ABI matching is primarily done through use of -`slotting `__. +API and ABI matching is primarily done through use of `slotting +`__. Slots are generally used to provide multiple versions or variants of given package that can be installed alongside (e.g. different major GTK+ or LLVM versions, or GTK+3 and GTK4 builds of WebKitGTK), whereas -subslots are used to group versions within a slot, usually -corresponding to the library ABI version. Packages can then declare -dependencies bound to the slot and subslot used at build time. Again, -separate binary packages can be built against different dependency -slots. When installing a dependency version falling into a different -slot or subslot, the package manager may either replace the package -needing that dependency with a binary packages built against the new -slot, or rebuild it from source. +subslots are used to group versions within a slot, usually corresponding +to the library ABI version. Packages can then declare dependencies bound +to the slot and subslot used at build time. Again, separate binary +packages can be built against different dependency slots. When +installing a dependency version falling into a different slot or +subslot, the package manager may either replace the package needing that +dependency with a binary packages built against the new slot, or rebuild +it from source. Normally, the use of slots assumes that upgrading to the newest version possible is desirable. When more fine-grained control is desired, slots @@ -737,26 +751,31 @@ it: ``numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64-x86_64_v3.whl`` This behavior was confirmed for a number of existing tools: -`auditwheel `__, -`packaging `__, -`pdm `__, -`pip `__, -`poetry `__, -`uv `__. +`auditwheel +`__, +`packaging +`__, +`pdm +`__, +`pip +`__, +`poetry +`__, +`uv +`__. Variant properties system ------------------------- Variant properties serve the purpose of expressing the characteristics -of the variant. Unlike platform compatibility tags, they are stored -in the variant metadata and therefore do not affect the wheel filename +of the variant. Unlike platform compatibility tags, they are stored in +the variant metadata and therefore do not affect the wheel filename length. They follow a hierarchical key-value design, with the key -further broken into a namespace and a feature name. -Namespaces are used to group features -defined by a single provider, and to avoid conflicts should multiple -providers define a feature with the same name. This permits independent -governance and evolution of every namespace. +further broken into a namespace and a feature name. Namespaces are used +to group features defined by a single provider, and to avoid conflicts +should multiple providers define a feature with the same name. This +permits independent governance and evolution of every namespace. The keys are restricted to lowercase letters, digits and underscores. Uppercase characters are disallowed to avoid different spellings of the @@ -764,13 +783,13 @@ same name. The character set for values is more relaxed, to permit values resembling versions. Variant features can be declared as allowing multiple values to be -present within a single variant wheel. If that is -the case, these values are matched as a logical disjunction, i.e. only a -single value needs to be compatible with the system for the wheel to be -considered supported. On the other hand, features are treated conjunctively, -i.e. all of them need to be compatible. This provides some flexibility in -designating variant compatibility while avoiding having to implement a -complete boolean logic. +present within a single variant wheel. If that is the case, these values +are matched as a logical disjunction, i.e. only a single value needs to +be compatible with the system for the wheel to be considered supported. +On the other hand, features are treated conjunctively, i.e. all of them +need to be compatible. This provides some flexibility in designating +variant compatibility while avoiding having to implement a complete +boolean logic. Variant properties are serialized into a structured 3-tuple format inspired by Trove Classifiers in :pep:`301`: @@ -799,11 +818,10 @@ could publish the following wheels during the transition period: - A GPU+CPU regular wheel, that will be installed on systems without an installer supporting variants. -Notably, this makes it possible to publish a smaller null variant -for systems that do not feature suitable hardware, with a fallback -regular wheel with support for CPU and all GPUs for systems where -variants are not supported and therefore GPU support cannot be -determined. +Notably, this makes it possible to publish a smaller null variant for +systems that do not feature suitable hardware, with a fallback regular +wheel with support for CPU and all GPUs for systems where variants are +not supported and therefore GPU support cannot be determined. Publishing a null variant is optional. If one is published, a wheel variant-enabled installer will prefer it over the non-variant wheel. If @@ -825,16 +843,17 @@ The specification covers two related use cases for package variants: GPUs or requiring CPU instruction sets beyond what platform tags provide. -2. Variants that can always be installed, and therefore only provide - a choice between different installation options. For example, software built - against different BLAS / LAPACK providers or debug builds of packages. +2. Variants that can always be installed, and therefore only provide a + choice between different installation options. For example, software + built against different BLAS / LAPACK providers or debug builds of + packages. The first class of variants requires querying provider plugins to determine package compatibility, and therefore involves the potential risks highlighted in the `security implications`_ section. The proposed mitigations incur a cost on installer implementations, making it -desirable to avoid plugin use at install time for the second class of variants, -where it is not strictly necessary. +desirable to avoid plugin use at install time for the second class of +variants, where it is not strictly necessary. To account for this, the specification proposes two classes of providers: @@ -884,10 +903,10 @@ incompatible plugin versions. This is already a problem with Python build backends used today. When vendoring or reimplementing plugins, installers need to follow -their current behavior. In particular, they should recognize the relevant provider -versions numbers, and possibly fall back to installing the external -plugin when the package in question is incompatible with the installer’s -implementation. +their current behavior. In particular, they should recognize the +relevant provider versions numbers, and possibly fall back to installing +the external plugin when the package in question is incompatible with +the installer’s implementation. Example use cases @@ -896,18 +915,18 @@ Example use cases PyTorch CPU/GPU variants '''''''''''''''''''''''' -As of October 2025, -`PyTorch `__ publishes builds -a total of seven variants for every release: a CPU-only variant, three -CUDA variants with different minimal CUDA runtime versions and supported +As of October 2025, `PyTorch +`__ publishes builds a total +of seven variants for every release: a CPU-only variant, three CUDA +variants with different minimal CUDA runtime versions and supported GPUs, two ROCm variants and a Linux XPU variant. This setup could be improved using GPU/XPU plugins that query the -installed runtime version and installed GPUs/XPUs to filter out the wheels -for which the runtime is unavailable, it is too old or the user’s GPU is -not supported, and order the remaining variants by the runtime version. -The CPU-only version is published as a null variant that is always -supported. +installed runtime version and installed GPUs/XPUs to filter out the +wheels for which the runtime is unavailable, it is too old or the user’s +GPU is not supported, and order the remaining variants by the runtime +version. The CPU-only version is published as a null variant that is +always supported. If a GPU runtime is available and supported, the installer automatically chooses the wheel for the newest runtime supported. Otherwise, it falls @@ -930,10 +949,10 @@ auto-vectorization applied across the code base. For example, an x86-64 CPU plugin can detect the capabilities for the installed CPU, mapping them onto the appropriate x86-64 architecture level and a set of extended instruction sets. Variant wheels indicate -which level and/or instruction sets are required. The installer filters out variants -that do not meet the requirements and select the best optimized variant. -A non-variant wheel can be used to represent the architecture baseline, -if supported. +which level and/or instruction sets are required. The installer filters +out variants that do not meet the requirements and select the best +optimized variant. A non-variant wheel can be used to represent the +architecture baseline, if supported. Implementation using wheel variants makes it possible to provide fine-grained indication of instruction sets required, with plugins that @@ -1099,7 +1118,8 @@ Variant Property multiple values, each is represented by a separate property. Variant Label - A string (up to 16 characters) added to the wheel filename to uniquely identify variants. + A string (up to 16 characters) added to the wheel filename to + uniquely identify variants. Null Variant A special variant with zero variant properties and the reserved @@ -1127,16 +1147,17 @@ Overview -------- Wheel variants introduce a more fine-grained specification of built -wheel characteristics beyond what existing wheel tags provide. Individual wheels -are characterized by sets of `variant properties`_ that are organized into -a hierarchical structure of namespaces, features and feature values. -When evaluating wheels to install, the installer must determine whether -variant properties are compatible with the system, and perform `variant -ordering`_ based on the priority of the compatible variant properties. This is done in -addition to determining the compatibility and ordering through tags. The -ordering by variant properties takes precedence over ordering by tags. -Null variants (variants with no variant properties) are preferred over -non-variant wheels with the same tags. +wheel characteristics beyond what existing wheel tags provide. +Individual wheels are characterized by sets of `variant properties`_ +that are organized into a hierarchical structure of namespaces, features +and feature values. When evaluating wheels to install, the installer +must determine whether variant properties are compatible with the +system, and perform `variant ordering`_ based on the priority of the +compatible variant properties. This is done in addition to determining +the compatibility and ordering through tags. The ordering by variant +properties takes precedence over ordering by tags. Null variants +(variants with no variant properties) are preferred over non-variant +wheels with the same tags. Every variant namespace is governed by a variant provider. There are two kinds of variant providers: install-time providers and ahead-of-time @@ -1398,16 +1419,15 @@ A provider information dictionary may contain the following keys: dependency specifier in ``requires`` is used, after replacing all ``-`` characters with ``_`` in the normalized package name. -- ``requires: list[str]``: A list of zero or more package :ref:`dependency - specifiers `, that are used to install the - provider plugin. If the - dependency specifiers include environment markers, these are evaluated - against the environment where the plugin is being installed and the - requirements for which the markers evaluate to false are filtered out. - In that case, at least one dependency must remain present in every - possible environment. Additionally, if ``plugin-api`` is not - specified, the first dependency present after filtering must always - evaluate to the same API endpoint. +- ``requires: list[str]``: A list of zero or more package + :ref:`dependency specifiers `, that are used to + install the provider plugin. If the dependency specifiers include + environment markers, these are evaluated against the environment where + the plugin is being installed and the requirements for which the + markers evaluate to false are filtered out. In that case, at least + one dependency must remain present in every possible environment. + Additionally, if ``plugin-api`` is not specified, the first dependency + present after filtering must always evaluate to the same API endpoint. All the fields are optional, with the following exceptions: @@ -1470,10 +1490,10 @@ In ``pyproject.toml`` file, the namespaces present in this dictionary must correspond to all AoT providers without a plugin (i.e. with ``install-time`` of ``false`` and no or empty ``requires``). When building a wheel, the build backend must query the -AoT provider plugins (i.e. these with ``install-time`` being ``false`` and -non-empty ``requires``) to obtain supported properties and embed them -into the dictionary. Therefore, the dictionary in ``variant.json`` and -``*-variants.json`` must contain namespaces for all AoT providers +AoT provider plugins (i.e. these with ``install-time`` being ``false`` +and non-empty ``requires``) to obtain supported properties and embed +them into the dictionary. Therefore, the dictionary in ``variant.json`` +and ``*-variants.json`` must contain namespaces for all AoT providers (i.e. all providers with ``install-time`` being ``false``). Since TOML and JSON dictionaries are unsorted, so are the features in @@ -1721,9 +1741,9 @@ to the following algorithm: of the respective ``default-priorities.property.{namespace}.{feature_name}`` key. - ii. Obtain the supported values from the provider, in order. For every - value that is not present in the constructed list, append it to - the end. + ii. Obtain the supported values from the provider, in order. For + every value that is not present in the constructed list, append + it to the end. After this step, a list of ordered property values is available for every feature. @@ -1793,7 +1813,9 @@ account for non-variant wheels or tags. value_order[namespace] = {} for feature_name in feature_order[namespace]: # 3. Construct the ordered lists of feature values. - value_order[namespace][feature_name] = default_priorities["property"].get(namespace, {}).get(feature_name, []) + value_order[namespace][feature_name] = ( + default_priorities["property"].get(namespace, {}).get(feature_name, []) + ) for feature_value in get_supported_feature_values(namespace, feature_name): if feature_value not in value_order[namespace][feature_name]: value_order[namespace][feature_name].append(feature_value) @@ -2000,18 +2022,19 @@ significant. It is defined using the following protocol: """List of values, possibly ordered from most preferred to least""" raise NotImplementedError -A "variant feature config" must provide the following properties or attributes: +A "variant feature config" must provide the following properties or +attributes: - ``name: str`` specifying the feature name. -- ``multi_value: bool`` specifying whether the feature is allowed to have - multiple corresponding values within a single variant wheel. If it is - ``False``, then it is an error to specify multiple values for the - feature. +- ``multi_value: bool`` specifying whether the feature is allowed to + have multiple corresponding values within a single variant wheel. If + it is ``False``, then it is an error to specify multiple values for + the feature. -- ``values: list[str]`` specifying feature values. In - contexts where the order is significant, the values must be ordered - from the most preferred to the least preferred. +- ``values: list[str]`` specifying feature values. In contexts where the + order is significant, the values must be ordered from the most + preferred to the least preferred. All features are interpreted as being within the plugin’s namespace. @@ -2028,8 +2051,8 @@ The plugin interface must follow the following protocol: class PluginType(Protocol): - # Note: properties are used here for docstring purposes, these must - # be actually implemented as attributes. + # Note: properties are used here for docstring purposes, these + # must be actually implemented as attributes. @property @abstractmethod @@ -2127,14 +2150,16 @@ Example implementation def get_all_configs() -> list[VariantFeatureConfig]: return [ VariantFeatureConfig( - # example :: gpu -- multi-valued, since the package can target multiple GPUs + # example :: gpu -- multi-valued, since the package + # can target multiple GPUs name="gpu", # [narf, poit, zort] values=_ALL_GPUS, multi_value=True, ), VariantFeatureConfig( - # example :: min_version -- single-valued, since there is always one minimum + # example :: min_version -- single-valued, since + # there is always one minimum name="min_version", # [1, 2, 3, 4] (order doesn't matter) values=[str(x) for x in range(1, _MAX_VERSION + 1)], @@ -2159,8 +2184,10 @@ Example implementation ), VariantFeatureConfig( name="gpu", - # this may be empty if no GPUs are supported -- 'example :: gpu feature' is not supported then - # but wheels with no GPU-specific code and only 'example :: min_version' could still be installed + # this may be empty if no GPUs are supported -- + # 'example :: gpu feature' is not supported then; + # but wheels with no GPU-specific code and only + # 'example :: min_version' could still be installed values=[x for x in _ALL_GPUS if _is_gpu_available(x)], multi_value=True, ), @@ -2183,9 +2210,9 @@ Build backends -------------- As a build backend can’t determine whether the frontend supports variant -wheels or not, :pep:`517` and :pep:`660` hooks must build non-variant wheels -by default. Build backends may provide ways to request variant builds. -This specification does not define any specific configuration. +wheels or not, :pep:`517` and :pep:`660` hooks must build non-variant +wheels by default. Build backends may provide ways to request variant +builds. This specification does not define any specific configuration. Variant environment markers @@ -2343,10 +2370,10 @@ documentation needs to indicate: The maintainers will also need to peruse provider plugin documentation. They should also be aware which provider plugins are considered trusted -by commonly used installers, and know the implications of using untrusted -plugins. These materials may also be supplemented by generic documents -explaining publishing variant wheels, along with specific example use -cases. +by commonly used installers, and know the implications of using +untrusted plugins. These materials may also be supplemented by generic +documents explaining publishing variant wheels, along with specific +example use cases. For the transition period, package maintainers need to be aware that they should still publish non-variant wheels for backwards @@ -2358,8 +2385,8 @@ Backwards compatibility Existing installers must not accidentally install variant wheels, as they require additional logic to determine whether a wheel is compatible -with the user’s system. This is achieved by `extending wheel -filename <#extended-wheel-filename>`__ through adding a +with the user’s system. This is achieved by `extending wheel filename +<#extended-wheel-filename>`__ through adding a ``-{variant label}`` component to the end of the filename, effectively causing variant wheels to be rejected by common installer implementations. For backwards compatibility, a regular wheel can be @@ -2374,12 +2401,13 @@ directory, which should be preserved by tools that are not concerned with variants, limiting the necessary changes to updating the filename validation algorithm (if there is one). -If the new `variant environment markers`_ are used in wheel dependencies, these -wheels will be incompatible with existing tools. This is a general -problem with the design of environment markers, and not specific to -wheel variants. It is possible to work around this problem by partially -evaluating environment markers at build time, and removing the markers -or dependencies specific to variant wheels from the regular wheel. +If the new `variant environment markers`_ are used in wheel +dependencies, these wheels will be incompatible with existing tools. +This is a general problem with the design of environment markers, and +not specific to wheel variants. It is possible to work around this +problem by partially evaluating environment markers at build time, and +removing the markers or dependencies specific to variant wheels from the +regular wheel. `Build backends`_ produce non-variant wheels to preserve backwards compatibility with existing frontends. Variant wheels can only be output @@ -2404,12 +2432,12 @@ wheels, generate the ``*-variants.json`` index and query plugins. A client for installing variant wheels is implemented in a `uv branch `__. -The `Wheel Variants -monorepo `__ -includes example implementations of provider plugins, as well as -modified versions of build backends featuring variant wheel building -support and modified versions of some Python packages demonstrating -variant wheel uses. +The `Wheel Variants monorepo +`__ includes +example implementations of provider plugins, as well as modified +versions of build backends featuring variant wheel building support and +modified versions of some Python packages demonstrating variant wheel +uses. Rejected ideas @@ -2429,12 +2457,13 @@ the Python interpreter itself. Every new axis would be imposing even more effort on package manager maintainers, who would have to maintain an algorithm to determine the -property compatibility. This algorithm could become quite complex, possibly -needing to account for different platforms, hardware versions and -requiring more frequent updates than the one for platform tags. This would also -significantly increase the barrier towards adding new axes and therefore -the risk of lack of feature parity between different installers, as every new -axis will be imposing additional maintenance cost. +property compatibility. This algorithm could become quite complex, +possibly needing to account for different platforms, hardware versions +and requiring more frequent updates than the one for platform tags. This +would also significantly increase the barrier towards adding new axes +and therefore the risk of lack of feature parity between different +installers, as every new axis will be imposing additional maintenance +cost. For comparison, the plugin design essentially democratizes the variant properties. Provider plugins can be maintained independently by people @@ -2493,8 +2522,8 @@ feedback of many people in the Python packaging community. In particular, we would like to credit the following individuals for their help in shaping this PEP (in alphabetical order): -Alban Desmaison, Bradley Dice, Chris Gottbrath, -Dmitry Rogozhkin, Emma Smith, Geoffrey Thomas, Henry Schreiner, -Jeff Daily, Jeremy Tanner, Jithun Nair, Keith Kraus, Leo Fang, -Mike McCarty, Nikita Shulga, Paul Ganssle, Philip Hyunsu Cho, -Robert Maynard, Vyas Ramasubramani, and Zanie Blue. +Alban Desmaison, Bradley Dice, Chris Gottbrath, Dmitry Rogozhkin, +Emma Smith, Geoffrey Thomas, Henry Schreiner, Jeff Daily, Jeremy Tanner, +Jithun Nair, Keith Kraus, Leo Fang, Mike McCarty, Nikita Shulga, +Paul Ganssle, Philip Hyunsu Cho, Robert Maynard, Vyas Ramasubramani, +and Zanie Blue. From 162ec7b4a9bd051abcf9d6cf35ca48d8fd7ec9c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 15 Dec 2025 13:44:53 +0100 Subject: [PATCH 12/13] Replace typographic apostrophes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-0817.rst | 69 +++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index a456ceeb985..ae3842d2ac9 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -24,7 +24,7 @@ Abstract The Python wheel packaging format uses :doc:`packaging:specifications/platform-compatibility-tags` to specify -a given wheel’s supported environments. These tags are unable to express +a given wheel's supported environments. These tags are unable to express features of modern hardware configurations, such as the availability of GPU acceleration, or to provide custom package variants, such as builds against different dependency ABIs. This is particularly challenging for @@ -219,9 +219,8 @@ Tools need to implement special handling for the way PyTorch uses local version segments. These requirements break the pattern that packages are usually installed with. Problems with installing PyTorch are a very common point of user confusion. To quantify this, on -2025-12-05, 552 out of 8136, or 6.8%, of issues on `uv's issue -tracker `__ contained the term -"torch". +2025-12-05, 552 out of 8136, or 6.8%, of issues on `uv's issue tracker +`__ contained the term "torch". Wheel variants remove this special casing and make GPU and TPU packages work just as well as regular packages with native code. They also reduce @@ -230,9 +229,9 @@ and use non-standard features such as local version segments present on an index. **Induced Security Risk:** This approach has unfortunately led to supply -chain attacks - more details on the `PyTorch -Blog `__. It’s -a non-trivial problem to address which has forced the PyTorch team to +chain attacks - more details on the `PyTorch Blog +`__. It's a +non-trivial problem to address which has forced the PyTorch team to create a complete mirror of all their dependencies, and is one of the core motivations behind :pep:`766`. @@ -278,7 +277,7 @@ not a goal. **Induced Security Risk:** proliferation of suffixed variant packages leads users to expect these suffixes in other packages, making name squatting much easier. For example, one could create a malicious -``numpy-cuda`` package that users will be lead to believe it’s a CUDA +``numpy-cuda`` package that users will be lead to believe it's a CUDA variant of NumPy. `CuPy `_ had to build @@ -318,7 +317,7 @@ installation, and consequently dependency chains, a fundamental expected behavior in the Python ecosystem, are dysfunctional. JAX includes 12 extra selectors to cover all use cases - many of which -overlap and could be misleading to users if they don’t read the +overlap and could be misleading to users if they don't read the documentation in detail. It should be noted that most of these "extras" are technically mutually @@ -397,13 +396,13 @@ Impact on scientific computing and AI/ML workflows The packaging limitations particularly affect scientific computing and AI/ML applications where performance optimization is critical: - The current wheel format’s lack of hardware awareness creates a + The current wheel format's lack of hardware awareness creates a suboptimal experience for hardware-dependent packages. While plugins help with smaller and well scoped packages, users must currently manually identify the correct variant (e.g., ``jax[cuda13]``) to avoid generic defaults or incompatible combinations. We need a system where ``pip install jax`` automatically selects packages - matching the user’s hardware, unless explicitly overridden. + matching the user's hardware, unless explicitly overridden. Wheel variants are a clear step in the right direction in this regard. @@ -467,7 +466,7 @@ and adds a significant burden on open source developers of the entire tool stack (from build backends to installers, not forgetting the package maintainers). - PyTorch’s extensive wheel support was always state of the art and + PyTorch's extensive wheel support was always state of the art and provided hardware accelerator support from day zero via our `package selector `__. We believe this was always a superpower of PyTorch to get things working out of @@ -584,7 +583,7 @@ Conda - conda-forge `Conda `__ is a binary-only package ecosystem that uses aggregated metadata indexes for resolution rather than filename parsing. Unlike the -:doc:`packaging:specifications/simple-repository-api`, conda’s +:doc:`packaging:specifications/simple-repository-api`, conda's resolution relies on `repodata indexes per platform `__ containing full metadata, making filenames purely identifiers with no @@ -631,9 +630,9 @@ packages using different dependency libraries, such as NumPy_ using `noarch vs native `__ -**Virtual Packages**: `Introduced in -2019 `__, virtual packages -inject system detection (CUDA version, glibc, CPU features) as solver +**Virtual Packages**: `Introduced in 2019 +`__, virtual packages inject +system detection (CUDA version, glibc, CPU features) as solver constraints. Built packages express dependencies like ``__cuda >=12.8``, and the installer verifies compatibility at install time. Current virtual packages include ``archspec`` (CPU capabilities), OS/system @@ -906,7 +905,7 @@ When vendoring or reimplementing plugins, installers need to follow their current behavior. In particular, they should recognize the relevant provider versions numbers, and possibly fall back to installing the external plugin when the package in question is incompatible with -the installer’s implementation. +the installer's implementation. Example use cases @@ -923,7 +922,7 @@ GPUs, two ROCm variants and a Linux XPU variant. This setup could be improved using GPU/XPU plugins that query the installed runtime version and installed GPUs/XPUs to filter out the -wheels for which the runtime is unavailable, it is too old or the user’s +wheels for which the runtime is unavailable, it is too old or the user's GPU is not supported, and order the remaining variants by the runtime version. The CPU-only version is published as a null variant that is always supported. @@ -1280,7 +1279,7 @@ Variant properties Every variant wheel must be described by zero or more variant properties. A variant wheel with exactly zero properties represents the null variant. The properties are specified when the variant wheel is -being built, using a mechanism defined by the project’s build backend. +being built, using a mechanism defined by the project's build backend. Each variant property is described by a 3-tuple that is serialized into the following format: @@ -1515,7 +1514,7 @@ Variants The ``variants`` dictionary is used in ``variant.json`` to indicate the variant that the wheel was built for, and in ``*-variants.json`` to -indicate all the wheel variants available. It’s a 3-level dictionary +indicate all the wheel variants available. It's a 3-level dictionary listing all properties per variant label: The first level keys are variant labels, the second level keys are namespaces, the third level are feature names, and the third level values are lists of feature @@ -1909,7 +1908,7 @@ High level design Every provider plugin must operate within a single namespace. This namespace is used as a unique key for all plugin-related operations. All -the properties defined by the plugin are bound within the plugin’s +the properties defined by the plugin are bound within the plugin's namespace, and the plugin defines all the valid feature names and values within that namespace. When building wheels, the tools should query the respective provider plugins to verify that the properties specified by @@ -1986,7 +1985,7 @@ a. in the ``plugin-api`` key of variant metadata, either explicitly or b. as the value of an installed entry point in the ``variant_plugins`` group. The name of said entry point is insignificant. This is optional but recommended, as it permits variant-related utilities to - discover variant plugins installed to the user’s environment. + discover variant plugins installed to the user's environment. Variant feature config class @@ -2036,7 +2035,7 @@ attributes: order is significant, the values must be ordered from the most preferred to the least preferred. -All features are interpreted as being within the plugin’s namespace. +All features are interpreted as being within the plugin's namespace. Plugin interface @@ -2079,7 +2078,7 @@ The plugin interface must follow the following protocol: The plugin interface must define the following attributes: -- ``namespace: str`` specifying the plugin’s namespace. +- ``namespace: str`` specifying the plugin's namespace. - ``is_aot_plugin: bool`` indicating whether the plugin is a valid AoT plugin. If that is the case, ``get_supported_configs()`` must always @@ -2091,14 +2090,14 @@ The plugin interface must provide the following functions: - ``get_all_config() -> list[VariantFeatureConfigType]`` that returns a list of "variant feature configs" describing all valid variant - features within the plugin’s namespace, along with all their permitted + features within the plugin's namespace, along with all their permitted values. The ordering of the lists is insignificant here. A particular plugin version must always return the same value (modulo ordering), irrespective of any runtime conditions. - ``get_supported_configs() -> list[VariantFeatureConfigType]`` that returns a list of "variant feature configs" describing the variant - features within the plugin’s namespace that are compatible with this + features within the plugin's namespace that are compatible with this particular system, along with their values that are supported. The variant feature and value lists must be ordered from the most preferred to the least preferred, as they affect `variant @@ -2209,7 +2208,7 @@ future extensions. Build backends -------------- -As a build backend can’t determine whether the frontend supports variant +As a build backend can't determine whether the frontend supports variant wheels or not, :pep:`517` and :pep:`660` hooks must build non-variant wheels by default. Build backends may provide ways to request variant builds. This specification does not define any specific configuration. @@ -2385,14 +2384,14 @@ Backwards compatibility Existing installers must not accidentally install variant wheels, as they require additional logic to determine whether a wheel is compatible -with the user’s system. This is achieved by `extending wheel filename -<#extended-wheel-filename>`__ through adding a -``-{variant label}`` component to the end of the filename, effectively -causing variant wheels to be rejected by common installer -implementations. For backwards compatibility, a regular wheel can be -published in addition to the variant wheels. It will be the only wheel -supported by incompatible installers, and the least preferred wheel for -variant-compatible installers. +with the user's system. This is achieved by `extending wheel filename +<#extended-wheel-filename>`__ through adding a ``-{variant label}`` +component to the end of the filename, effectively causing variant wheels +to be rejected by common installer implementations. For backwards +compatibility, a regular wheel can be published in addition to the +variant wheels. It will be the only wheel supported by incompatible +installers, and the least preferred wheel for variant-compatible +installers. Aside from this explicit incompatibility, the specification makes minimal and non-intrusive changes to the binary package format. The From 0064073175538c46643c9dd330a4dcd1b34bb318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 15 Dec 2025 14:17:12 +0100 Subject: [PATCH 13/13] Remove double spaces introduced while reflowing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove accidental double spaces that vim's `gq` introduced while I was reflowing the text. Thanks to @konstin for noticing. Signed-off-by: Michał Górny --- peps/pep-0817.rst | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/peps/pep-0817.rst b/peps/pep-0817.rst index ae3842d2ac9..795fb2eea4f 100644 --- a/peps/pep-0817.rst +++ b/peps/pep-0817.rst @@ -32,7 +32,7 @@ the scientific computing, artificial intelligence (AI), machine learning (ML), and high-performance computing (HPC) communities. This PEP proposes "Wheel Variants", an extension to the -:doc:`packaging:specifications/binary-distribution-format`. This +:doc:`packaging:specifications/binary-distribution-format`. This extension introduces a mechanism for package maintainers to declare multiple build variants for the same package version, while allowing installers to automatically select the most appropriate variant based on @@ -101,14 +101,14 @@ to significant performance improvement. various targets. The first two bars are labeled "yum (2018.8)" and "generic (SSE2)", reach about 1.0 ns/day and are both marked as "SSE2". The next bar is labeled - "ivybridge" ("AVX") and reaches almost 1.5 ns/day. Two + "ivybridge" ("AVX") and reaches almost 1.5 ns/day. Two following bars are labeled "haswell" and "broadwell" (both - "AVX2") and exceed 1.5 ns/day slightly. The last two bars + "AVX2") and exceed 1.5 ns/day slightly. The last two bars are labeled "skylake_avx512" and "cascadelake" (both "AVX512") and reach almost 2.0 ns/day. Performance of GROMACS 2020.1 built for different generations of - CPUs. Vertical axis shows performance expressed in ns/day, a + CPUs. Vertical axis shows performance expressed in ns/day, a GROMACS-specific measure of simulation speed (higher is better). Compiling `GROMACS `_ for architectures @@ -270,7 +270,7 @@ the way package upgrades are ordered. An additional limitation of this approach is that publishing a new release synchronously across multiple package names is not currently -possible. :pep:`694` proposes adding such a mechanism for multiple +possible. :pep:`694` proposes adding such a mechanism for multiple wheels within a single package, but extending it to multiple packages is not a goal. @@ -771,7 +771,7 @@ Variant properties serve the purpose of expressing the characteristics of the variant. Unlike platform compatibility tags, they are stored in the variant metadata and therefore do not affect the wheel filename length. They follow a hierarchical key-value design, with the key -further broken into a namespace and a feature name. Namespaces are used +further broken into a namespace and a feature name. Namespaces are used to group features defined by a single provider, and to avoid conflicts should multiple providers define a feature with the same name. This permits independent governance and evolution of every namespace. @@ -924,7 +924,7 @@ This setup could be improved using GPU/XPU plugins that query the installed runtime version and installed GPUs/XPUs to filter out the wheels for which the runtime is unavailable, it is too old or the user's GPU is not supported, and order the remaining variants by the runtime -version. The CPU-only version is published as a null variant that is +version. The CPU-only version is published as a null variant that is always supported. If a GPU runtime is available and supported, the installer automatically @@ -950,7 +950,7 @@ installed CPU, mapping them onto the appropriate x86-64 architecture level and a set of extended instruction sets. Variant wheels indicate which level and/or instruction sets are required. The installer filters out variants that do not meet the requirements and select the best -optimized variant. A non-variant wheel can be used to represent the +optimized variant. A non-variant wheel can be used to represent the architecture baseline, if supported. Implementation using wheel variants makes it possible to provide @@ -967,7 +967,7 @@ BLAS / LAPACK variants Packages such as NumPy_ and SciPy_ can be built using different BLAS / LAPACK libraries. Users may wish to choose a specific library for improved performance on a particular hardware, or based on license -considerations. Furthermore, different libraries may use different +considerations. Furthermore, different libraries may use different OpenMP implementations, whereas using a consistent implementation across the stack can avoid degrading performance through spawning too many threads. @@ -1137,7 +1137,7 @@ Install-time Provider Ahead-of-Time Provider A provider that features a static list of supported properties which - is then embedded in the wheel metadata. Such a list can either be + is then embedded in the wheel metadata. Such a list can either be embedded in ``pyproject.toml`` or provided by a plugin queried at build time. @@ -1149,12 +1149,12 @@ Wheel variants introduce a more fine-grained specification of built wheel characteristics beyond what existing wheel tags provide. Individual wheels are characterized by sets of `variant properties`_ that are organized into a hierarchical structure of namespaces, features -and feature values. When evaluating wheels to install, the installer +and feature values. When evaluating wheels to install, the installer must determine whether variant properties are compatible with the system, and perform `variant ordering`_ based on the priority of the compatible variant properties. This is done in addition to determining the compatibility and ordering through tags. The ordering by variant -properties takes precedence over ordering by tags. Null variants +properties takes precedence over ordering by tags. Null variants (variants with no variant properties) are preferred over non-variant wheels with the same tags. @@ -1423,7 +1423,7 @@ A provider information dictionary may contain the following keys: install the provider plugin. If the dependency specifiers include environment markers, these are evaluated against the environment where the plugin is being installed and the requirements for which the - markers evaluate to false are filtered out. In that case, at least + markers evaluate to false are filtered out. In that case, at least one dependency must remain present in every possible environment. Additionally, if ``plugin-api`` is not specified, the first dependency present after filtering must always evaluate to the same API endpoint. @@ -2211,7 +2211,7 @@ Build backends As a build backend can't determine whether the frontend supports variant wheels or not, :pep:`517` and :pep:`660` hooks must build non-variant wheels by default. Build backends may provide ways to request variant -builds. This specification does not define any specific configuration. +builds. This specification does not define any specific configuration. Variant environment markers