Problem
Fromager-built wheels are platform-specific and may depend on:
- OS / distribution version, e.g. Fedora 43, RHEL 9.6, RHEL 10.1
- AI accelerator stack, e.g. CUDA 13.1 vs CUDA 12.9, ROCm 7.1
- Torch ABI, which is unstable across versions; a wheel compiled for Torch 2.10.0 may have a different ABI than one compiled for Torch 2.11.0
Currently, wheel filenames carry none of this information. A rebuild for a new Torch version does not produce a new filename, making it difficult to:
- Invalidate caches when the underlying platform or dependency stack changes.
- Distinguish wheels built for different accelerator stacks or OS versions.
- Replace outdated wheels with correctly-targeted rebuilds.
- Share wheels between indexes. Downstream maintains a separate index for each accelerator version, but only a couple of dozen packages out of over 1,200 are CUDA/ROCm/Torch-specific. Wheels like
pillow or fromager are identical across accelerator indexes and could be shared if their filenames clearly indicate they have no accelerator dependency. Sharing is out of scope for this proposal but is a possibility for future improvements in downstream.
Proposal
Add a new stevedore hook point, wheel_build_tag, to the existing fromager.hooks namespace. This hook lets downstream plugin packages inject custom suffixes into the wheel build tag.
Wheel spec background
The wheel filename format is:
{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
The build tag is optional, must start with a digit, and must not contain -. It is parsed as tuple[int, str]. Fromager already fills the int part from variant + package changelog and sets the str suffix to "". The build tag is also stored in the {name}-{version}.dist-info/WHEEL metadata file and shown in pip list output.
This proposal extends that suffix with hook-provided segments (e.g. _el9.6_rocm7.1_torch2.10.0).
Hook signature
def run_wheel_build_tag_hooks(
*,
ctx: context.WorkContext,
req: Requirement,
version: Version,
wheel_tags: frozenset[Tag],
) -> typing.Sequence[str]:
...
Each registered hook returns typing.Sequence[tuple[int, str]], a sequence of (sort-order, suffix) pairs. This allows a single hook to contribute multiple suffix segments (e.g. both an OS tag and an accelerator tag). The runner collects all pairs from all hooks, sorts them by the integer key, and returns the suffix parts as a sequence. These are joined and appended to the existing build tag.
Example hook
def example_hook(
*,
ctx: context.WorkContext,
req: Requirement,
version: Version,
wheel_tags: frozenset[Tag],
) -> typing.Sequence[tuple[int, str]]:
result: list[tuple[int, str]] = []
platlib = any(tag.platform != "any" for tag in wheel_tags)
if platlib:
# fc43, el9.6, ...
result.append((1, get_distro_tag()))
return result
What hooks can access
ctx + req: package configuration, annotations from pbi, variant settings.
wheel_tags: detect whether a wheel is purelib or platlib (platform/arch-specific).
platform.freedesktop_os_release(): read distribution name and version from /etc/os-release.
- Annotations: downstream-specific metadata such as "depends on CUDA" or "depends on Torch" read from context.
What hooks cannot access
The hook does not have access to wheel content, the build environment, or ELF dependency info. While this information exists during the build, it is not available when wheels are retrieved from cache servers or local cache. The hook must work identically in both paths.
Integration with existing override system
In addition to global hooks via stevedore, per-package overrides (overrides.find_and_invoke() with method wheel_build_tag) can further customize the tag for individual packages.
Examples
RHEL 9.6, ROCm 7.1, Torch 2.10.0
| Wheel |
Build tag |
flash_attn-2.8.3-8_el9.6_rocm7.1_torch2.10.0-cp312-cp312-linux_x86_64.whl |
8_el9.6_rocm7.1_torch2.10.0 |
torch-2.10.0-7_el9.6_rocm7.1-cp312-cp312-linux_x86_64.whl |
7_el9.6_rocm7.1 |
pillow-12.2.0-2_el9.6-cp312-cp312-linux_x86_64.whl |
2_el9.6 |
fromager-0.79.0-2-py3-none-any.whl |
2 (pure-python, no suffix) |
Fedora 43, CUDA 13.0, Torch 2.9.1
| Wheel |
Build tag |
flash_attn-2.8.3-8_fc43_cuda13.0_torch2.9.1-cp312-cp312-linux_x86_64.whl |
8_fc43_cuda13.0_torch2.9.1 |
torch-2.9.1-8_fc43_cuda13.0-cp312-cp312-linux_x86_64.whl |
8_fc43_cuda13.0 |
pillow-12.2.0-2_fc43-cp312-cp312-linux_x86_64.whl |
2_fc43 |
fromager-0.79.0-2-py3-none-any.whl |
2 (pure-python, no suffix) |
Note how pure-python wheels (py3-none-any) receive no suffix, while platlib wheels get progressively more specific tags based on their actual dependencies.
Limitations
A single package index cannot contain wheels for multiple AI accelerators (e.g. both CUDA and ROCm builds of the same package). pip install and uv pip install only use the build tag for sorting, not for filtering. An index with both CUDA and ROCm wheels would result in the installer picking whichever has the highest build tag, not the correct accelerator.
Logic for Torch and CUDA/ROCm dependency selection may be reused by wheel variants when the new standard becomes available. See PEP 817, PEP 825, and https://wheelnext.dev/ .
Problem
Fromager-built wheels are platform-specific and may depend on:
Currently, wheel filenames carry none of this information. A rebuild for a new Torch version does not produce a new filename, making it difficult to:
pilloworfromagerare identical across accelerator indexes and could be shared if their filenames clearly indicate they have no accelerator dependency. Sharing is out of scope for this proposal but is a possibility for future improvements in downstream.Proposal
Add a new stevedore hook point,
wheel_build_tag, to the existingfromager.hooksnamespace. This hook lets downstream plugin packages inject custom suffixes into the wheel build tag.Wheel spec background
The wheel filename format is:
The build tag is optional, must start with a digit, and must not contain
-. It is parsed astuple[int, str]. Fromager already fills theintpart from variant + package changelog and sets thestrsuffix to"". The build tag is also stored in the{name}-{version}.dist-info/WHEELmetadata file and shown inpip listoutput.This proposal extends that suffix with hook-provided segments (e.g.
_el9.6_rocm7.1_torch2.10.0).Hook signature
Each registered hook returns
typing.Sequence[tuple[int, str]], a sequence of (sort-order, suffix) pairs. This allows a single hook to contribute multiple suffix segments (e.g. both an OS tag and an accelerator tag). The runner collects all pairs from all hooks, sorts them by the integer key, and returns the suffix parts as a sequence. These are joined and appended to the existing build tag.Example hook
What hooks can access
ctx+req: package configuration, annotations frompbi, variant settings.wheel_tags: detect whether a wheel is purelib or platlib (platform/arch-specific).platform.freedesktop_os_release(): read distribution name and version from/etc/os-release.What hooks cannot access
The hook does not have access to wheel content, the build environment, or ELF dependency info. While this information exists during the build, it is not available when wheels are retrieved from cache servers or local cache. The hook must work identically in both paths.
Integration with existing override system
In addition to global hooks via stevedore, per-package overrides (
overrides.find_and_invoke()with methodwheel_build_tag) can further customize the tag for individual packages.Examples
RHEL 9.6, ROCm 7.1, Torch 2.10.0
flash_attn-2.8.3-8_el9.6_rocm7.1_torch2.10.0-cp312-cp312-linux_x86_64.whl8_el9.6_rocm7.1_torch2.10.0torch-2.10.0-7_el9.6_rocm7.1-cp312-cp312-linux_x86_64.whl7_el9.6_rocm7.1pillow-12.2.0-2_el9.6-cp312-cp312-linux_x86_64.whl2_el9.6fromager-0.79.0-2-py3-none-any.whl2(pure-python, no suffix)Fedora 43, CUDA 13.0, Torch 2.9.1
flash_attn-2.8.3-8_fc43_cuda13.0_torch2.9.1-cp312-cp312-linux_x86_64.whl8_fc43_cuda13.0_torch2.9.1torch-2.9.1-8_fc43_cuda13.0-cp312-cp312-linux_x86_64.whl8_fc43_cuda13.0pillow-12.2.0-2_fc43-cp312-cp312-linux_x86_64.whl2_fc43fromager-0.79.0-2-py3-none-any.whl2(pure-python, no suffix)Note how pure-python wheels (
py3-none-any) receive no suffix, while platlib wheels get progressively more specific tags based on their actual dependencies.Limitations
A single package index cannot contain wheels for multiple AI accelerators (e.g. both CUDA and ROCm builds of the same package).
pip installanduv pip installonly use the build tag for sorting, not for filtering. An index with both CUDA and ROCm wheels would result in the installer picking whichever has the highest build tag, not the correct accelerator.Logic for Torch and CUDA/ROCm dependency selection may be reused by wheel variants when the new standard becomes available. See PEP 817, PEP 825, and https://wheelnext.dev/ .