Skip to content

feat: Unique wheel file names with a new wheel_build_tag hook #1059

@tiran

Description

@tiran

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:

  1. Invalidate caches when the underlying platform or dependency stack changes.
  2. Distinguish wheels built for different accelerator stacks or OS versions.
  3. Replace outdated wheels with correctly-targeted rebuilds.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions