Skip to content

Commit 628343c

Browse files
mgornykonstinrgommers
committed
PEP 825: provide a high-level ordering overview and clarify
Provide a high-level description of how ordering works and what it is meant to achieve, in addition to the precise algorithm provided right now. Add clarifications to the algorithm, to make it clear in what order the lists are actually arranged. Signed-off-by: Michał Górny <mgorny@quansight.com> Co-authored-by: konstin <konstin@mailbox.org> Co-authored-by: rgommers <ralf.gommers@gmail.com>
1 parent 910d8ce commit 628343c

1 file changed

Lines changed: 135 additions & 36 deletions

File tree

‎peps/pep-0825.rst‎

Lines changed: 135 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ Default priorities
256256

257257
The ``default-priorities`` dictionary defines the ordering of
258258
namespaces which is used in variant ordering. The exact algorithm is
259-
described in the `Variant ordering`_ section.
259+
described in the `Variant ordering and selection`_ section.
260260

261261
The following key is REQUIRED:
262262

@@ -462,11 +462,97 @@ consistency`_ and set out in full in
462462
:ref:`pep825-metadata-consistency`.
463463

464464

465-
Variant ordering
466-
----------------
465+
Variant ordering and selection
466+
------------------------------
467+
468+
High-level overview
469+
'''''''''''''''''''
470+
471+
This specification defines an ordering of wheels based on their variant
472+
metadata, from the most preferable to the least preferable.
473+
474+
The ordering of wheels by platform compatibility tags is not currently
475+
defined by the specification, beyond a guideline that more specific
476+
wheels should be preferred. This has not been a big problem, as usually
477+
there is only one wheel that is compatible with the system, and where
478+
there are more, the ordering is either clear or insignificant.
479+
480+
With wheel variants, there can be several compatible wheels per project
481+
version. We define a total ordering to give package authors and users
482+
control over wheel preference and to ensure that all tools select the
483+
same variant without ambiguity. Tools may allow users to override this
484+
ordering.
485+
486+
Every variant property is a ``namespace :: feature :: value`` triple
487+
whose components are ranked in that order: by namespace first, then by
488+
feature within the namespace, then by value within the feature. Only
489+
properties compatible with the target system take part, and only the highest
490+
ranking compatible value for each feature. The ranking for namespaces comes
491+
from the package's variant metadata. The ranking for their features and the
492+
ranking of values within features will be defined in a subsequent PEP.
493+
494+
Variant wheels sort as Python sorts lists of tuples, each wheel being
495+
the list of its property triples ordered best first. Where one wheel's
496+
triples run out, the longer list therefore wins. The intuition is that
497+
this selects the wheel that makes the best use of the target hardware.
498+
499+
Spelled out: compare two wheels by their best-ranked property triple; if
500+
those are equal, use the next lower-ranked property as a tiebreaker,
501+
repeatedly until an ordering is established. Comparing all compatible
502+
wheels this way yields the most preferred wheel.
503+
504+
As an example, take four variant wheels of a package that ranks the
505+
``nvidia`` namespace above ``x86_64``:
506+
507+
.. code:: python
467508
468-
This specification defines an ordering between different wheels based on
469-
the presence of variant metadata.
509+
# Rankings, most preferred first. The namespace ranking comes from
510+
# the package's variant metadata; the feature and value rankings
511+
# will be defined in a subsequent PEP.
512+
namespaces = ["nvidia", "x86_64"]
513+
features = {"nvidia": ["cuda_version_lower_bound"], "x86_64": ["level"]}
514+
values = {
515+
"nvidia": {"cuda_version_lower_bound": ["13.0", "12.0"]},
516+
"x86_64": {"level": ["v4", "v3", "v2"]},
517+
}
518+
519+
# Each variant label maps to its properties, with only the best
520+
# compatible value kept for every feature.
521+
variant_wheels = {
522+
"gpu": [("nvidia", "cuda_version_lower_bound", "13.0")],
523+
"gpu_cpuv2": [("nvidia", "cuda_version_lower_bound", "13.0"),
524+
("x86_64", "level", "v2")],
525+
"gpu_cpuv4": [("nvidia", "cuda_version_lower_bound", "13.0"),
526+
("x86_64", "level", "v4")],
527+
"cpuv4": [("x86_64", "level", "v4")],
528+
}
529+
530+
def rank(prop):
531+
"""Rank a triple. The ranking lists put the best first, so negate
532+
the positions to make a bigger rank mean a better property."""
533+
namespace, feature, value = prop
534+
return (-namespaces.index(namespace),
535+
-features[namespace].index(feature),
536+
-values[namespace][feature].index(value))
537+
538+
def sort_key(label):
539+
"""Rank a wheel: its property ranks, best first."""
540+
return sorted(map(rank, variant_wheels[label]), reverse=True)
541+
542+
sorted(variant_wheels, key=sort_key, reverse=True)
543+
# ['gpu_cpuv4', 'gpu_cpuv2', 'gpu', 'cpuv4']
544+
545+
The ``gpu*`` wheels sort ahead of ``cpuv4`` because their best triple
546+
is in the higher-ranked ``nvidia`` namespace. ``gpu_cpuv4`` beats
547+
``gpu_cpuv2`` on their second triple, since ``v4`` outranks ``v2``. Both
548+
beat ``gpu``, whose triples run out first, as the wheel with more
549+
properties wins.
550+
551+
The ordering for non-variant wheels remains unchanged.
552+
553+
554+
Ordering algorithm
555+
''''''''''''''''''
470556

471557
For the purpose of ordering, the combined variant metadata for all
472558
candidate variant wheels MUST be obtained. It can be sourced either from
@@ -481,12 +567,12 @@ features, and features into namespaces. For every namespace, the tool
481567
MUST obtain a list of compatible features, and for every feature, a list
482568
of compatible values. The method of obtaining these lists will be
483569
defined in a subsequent PEP. The items in these lists will be provided
484-
in specific order that will impact variant wheel ordering.
570+
ordered from the most preferable to the least preferable.
485571

486572
The compatible wheels corresponding to a particular combination of
487-
package name, version and build number MUST be grouped by their variant
488-
label, and a separate group of non-variant wheels MUST be formed. The
489-
groups of variant wheels MUST then be ordered according to the following
573+
package name and version MUST be grouped by their variant label, and a
574+
separate group of non-variant wheels MUST be formed. The groups of
575+
variant wheels MUST then be ordered according to the following
490576
algorithm:
491577

492578
1. Construct the ordered list of namespaces by copying the value of the
@@ -502,7 +588,7 @@ algorithm:
502588
4. For every group, determine the most preferred value corresponding to
503589
every variant feature present in the variant properties corresponding
504590
to the group. This is done by finding among the values the one that
505-
has the lowest position in the ordered property value list. After
591+
has the lowest index in the ordered property value list. After
506592
this step, a list of features along with their best values is
507593
available for every variant. This is done in the
508594
``VariantWheel.best_value_properties()`` method in the example.
@@ -514,7 +600,9 @@ algorithm:
514600
function in the example.
515601

516602
6. For every group, sort the list constructed in step 4 using the sort
517-
keys constructed in step 5, in ascending order. This is done by the
603+
keys constructed in step 5, in ascending order. The resulting list
604+
will be ordered from the most preferable feature to the least
605+
preferable feature. This is done by the
518606
``VariantWheel.sorted_properties()`` method in the example.
519607

520608
7. To order groups, compare their sorted lists from step 6. If the
@@ -524,17 +612,21 @@ algorithm:
524612
found or the list in one of the groups is exhausted. In the latter
525613
case, the group with more keys is sorted earlier. As a fallback,
526614
if both groups have the same number of keys, they are ordered
527-
lexically by the variant label, ascending. This is done by the
528-
ultimate step of the example algorithm, with the comparison function
529-
being implemented as ``VariantWheel.__lt__()``.
615+
lexically by the variant label, ascending. The resulting list of
616+
groups will be sorted from the most preferable to the least
617+
preferable. This is done by the ultimate step of the example
618+
algorithm, with the comparison function being implemented as
619+
``VariantWheel.__lt__()``.
530620

531621
The algorithm sorts the group of null variant wheels last, as they
532622
feature no variant properties. The group of non-variant wheels MUST be
533623
placed after all the other groups.
534624

535625
Within every group, the wheels MUST then be ordered according to their
536-
platform compatibility tags. After this process, the variant wheels are
537-
sorted from the most preferred to the least preferred.
626+
remaining properties, such as platform compatibility tags and build
627+
numbers. This specification does not alter this ordering; it remains the
628+
same as before. After completing this process, the wheels are sorted
629+
from the most preferred to the least preferred.
538630

539631
The tools MAY provide options to override the default ordering, for
540632
example by specifying a preference for specific namespaces, features
@@ -696,7 +788,7 @@ The variant markers MUST only be used in dependency specifiers and MUST
696788
NOT take part in selecting a wheel. These markers gate the individual
697789
dependency specifiers of a wheel that has already been selected. They
698790
MUST be evaluated only once variant wheel selection, as described in
699-
`variant ordering`_, has taken place.
791+
`variant ordering and selection`_, has taken place.
700792

701793
Their values MUST be determined as follows:
702794

@@ -883,9 +975,9 @@ behavior would be to:
883975
7. Obtain the ordered lists of compatible variant properties. The
884976
mechanism for this will be specified in a subsequent PEP.
885977
8. Filter and order variants based on the lists of compatible
886-
properties, per `variant ordering`_, and select the most preferred
887-
variant. If no variant wheel matched, use the non-variant wheels by
888-
their rules.
978+
properties and select the most preferred variant, per `variant
979+
ordering and selection`_. If no variant wheel matched, use the
980+
non-variant wheels by their rules.
889981
9. If multiple wheels for a given version share the same variant label,
890982
order them by Platform compatibility tags and build number, and
891983
select the best wheel.
@@ -1034,8 +1126,8 @@ When a package manager is requested to install ``torch``, in order:
10341126
the list.
10351127

10361128
5. If variant wheels with multiple different labels remain on the list,
1037-
the results are sorted per the algorithm in `variant ordering`_.
1038-
The most preferred label is selected.
1129+
the results are sorted per the algorithm in `variant ordering and
1130+
selection`_. The most preferred label is selected.
10391131

10401132
For example, in this case the ``cuda*`` wheels are ordered by their
10411133
properties, using the lists obtained in step 4. The wheels for CUDA
@@ -1103,9 +1195,9 @@ sources.
11031195

11041196
A tool that searches sources in priority order (for example, the "index
11051197
priority" in :pep:`766`) can order variants from one source at a time
1106-
using the `variant ordering`_ algorithm, and proceed to the next source
1107-
only if the current one has no viable candidates. No cross-source
1108-
metadata merge is necessary.
1198+
using the `variant ordering and selection`_ algorithm, and proceed to
1199+
the next source only if the current one has no viable candidates. No
1200+
cross-source metadata merge is necessary.
11091201

11101202
A tool that collates candidates from all sources before selecting among
11111203
them (for example, "version priority" in :pep:`766`) must compare wheels
@@ -1264,12 +1356,12 @@ and a publisher of variant wheels may still take that route.
12641356
The requirement is what makes several of the mechanisms defined here
12651357
work at all. If the wheels of one release disagreed about namespace
12661358
order, there would be no total order over the variants, so `variant
1267-
ordering`_ would have no defined output and two conforming installers
1268-
could select different wheels from the same inputs. ``pylock.toml``
1269-
inlines combined variant metadata, so non-deterministic combination
1270-
would mean that two lock runs over one release can produce different
1271-
lock files. And the `index-level metadata`_ file could neither be
1272-
generated from the uploaded wheels alone, nor be relied upon once
1359+
ordering and selection`_ would have no defined output and two conforming
1360+
installers could select different wheels from the same inputs.
1361+
``pylock.toml`` inlines combined variant metadata, so non-deterministic
1362+
combination would mean that two lock runs over one release can produce
1363+
different lock files. And the `index-level metadata`_ file could neither
1364+
be generated from the uploaded wheels alone, nor be relied upon once
12731365
generated, since a resolver would then have to fetch every candidate
12741366
wheel to discover the true combined picture.
12751367

@@ -1319,7 +1411,7 @@ only ``120_real``, much as a dependency that supports a single CPU
13191411
architecture is gated on ``platform_machine``. Where a dependency does
13201412
exist for every value of a feature, it can instead be published as a
13211413
variant package and depended upon unconditionally, leaving the choice to
1322-
`variant ordering`_.
1414+
`variant ordering and selection`_.
13231415

13241416
Because of this filtering, the variant markers do not depart from the
13251417
established meaning of an environment marker. Every property in
@@ -1342,10 +1434,10 @@ Three consequences of this design are worth noting:
13421434
for every feature it declares (see `variant properties`_).
13431435
``variant_features`` and ``variant_namespaces`` therefore always list
13441436
every feature and namespace the wheel was built for. This is why the
1345-
overrides permitted in `variant ordering`_ may not reach past the
1346-
compatibility filter: a wheel selected in spite of being unsupported
1347-
could have its properties filtered away, and the dependencies gated on
1348-
them would silently disappear.
1437+
overrides permitted in `variant ordering and selection`_ may not reach
1438+
past the compatibility filter: a wheel selected in spite of being
1439+
unsupported could have its properties filtered away, and the
1440+
dependencies gated on them would silently disappear.
13491441

13501442
- For the null variant, ``variant_label`` is ``"null"`` and the three
13511443
set-valued markers are empty sets, as the null variant has zero
@@ -1610,6 +1702,13 @@ Change History
16101702
The investigation in the appendix settles the question in favour of
16111703
markers, so the Open Issues section has been removed and the
16121704
reasoning recorded in the Rationale instead.
1705+
- Removed grouping by build numbers, instead relegating them to
1706+
tie-breaking along with other wheel properties. This reverts a
1707+
potential change in behavior for non-variant wheels.
1708+
- Added a high-level overview to `variant ordering and selection`_,
1709+
clarified that the ordering of the lists used in the algorithm is
1710+
from the most preferable to the least preferable and that the
1711+
ordering by platform compatibility tags is not changed.
16131712

16141713
- 19-Aug-2026
16151714

0 commit comments

Comments
 (0)