# Why Installing GPU Python Packages Is So Complicated


Python [wheels](https://pydevtools.com/handbook/reference/wheel.md) have no way to express GPU hardware requirements. A wheel's three metadata tags (Python version, ABI, and platform) tell an installer everything about the CPU environment, but nothing about the GPU or which CUDA version is available.

PyTorch ships different binaries for CUDA 12.6, 12.8, 13.0, and CPU-only builds. Without GPU metadata in wheels, it has no standard way to publish all four variants to [PyPI](https://pydevtools.com/handbook/explanation/what-is-pypi.md) and let installers pick the right one. Every CUDA-dependent project has had to invent its own distribution workaround.

## How packages work around this today

Without standard metadata for GPU support, every CUDA-dependent project has invented its own distribution scheme.

### Separate index URLs (the PyTorch approach)

PyTorch hosts its own package index at `download.pytorch.org/whl/`. Users select their CUDA version by pointing [pip](https://pydevtools.com/handbook/reference/pip.md) or [uv](https://pydevtools.com/handbook/reference/uv.md) at the matching URL:

```shell
# CUDA 12.8
pip install torch --index-url https://download.pytorch.org/whl/cu128

# CPU only
pip install torch --index-url https://download.pytorch.org/whl/cpu
```

The package name stays `torch` everywhere. The index determines which binary gets installed. PyTorch 2.11.0 ships variants for CUDA 12.6, 12.8, and 13.0, plus ROCm 7.2 and CPU-only builds.

This approach keeps the package name clean but pushes complexity into project configuration. Every developer on a team needs to use the same index URL, and CI pipelines need separate configurations for GPU and CPU runners.

### Package name suffixes (the RAPIDS approach)

NVIDIA's RAPIDS libraries encode the CUDA version in the package name itself: `cudf-cu12`, `cuml-cu12`, `cudf-cu13`. These are hosted on NVIDIA's own index at `pypi.nvidia.com`. NVIDIA's lower-level runtime libraries use the same convention on PyPI: `nvidia-cuda-runtime-cu12`.

```shell
pip install cudf-cu12 --extra-index-url=https://pypi.nvidia.com
```

Users must know their CUDA major version and select the matching package. Dependency resolution becomes awkward because `cudf-cu12` and `cudf-cu13` look like unrelated packages to the installer, even though they provide the same Python API.

### Extras syntax (the JAX approach)

JAX uses [pip extras](https://pydevtools.com/handbook/explanation/what-are-optional-dependencies-and-dependency-groups.md) to pull in the right backend:

```shell
pip install "jax[cuda13]"
```

The extras trigger installation of CUDA-specific dependencies. This keeps the base package name intact and uses a standard pip mechanism, though it still requires the user to specify the CUDA version manually.

## How uv handles this today

[uv](https://pydevtools.com/handbook/reference/uv.md) has built-in support for routing packages to different indexes based on platform markers. A `pyproject.toml` can encode the PyTorch index dance declaratively:

```toml
[[tool.uv.index]]
name = "pytorch-cu128"
url = "https://download.pytorch.org/whl/cu128"
explicit = true

[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", marker = "sys_platform != 'linux'" },
  { index = "pytorch-cu128", marker = "sys_platform == 'linux'" },
]
```

This gives teams a single lockable configuration that routes GPU builds to Linux and CPU builds to macOS/Windows. uv also provides a `--torch-backend=auto` flag (and `UV_TORCH_BACKEND` environment variable) in its `uv pip` interface that auto-detects GPU hardware, though this feature is not yet available in the project-level workflow.

## What conda and pixi do differently

[Conda](https://pydevtools.com/handbook/reference/conda.md) sidesteps this problem by managing CUDA as a non-Python dependency. A conda environment file can pin a specific CUDA toolkit version alongside `pytorch` and `numpy`, and conda's solver resolves all of them together. The CUDA toolkit is just another package in the dependency graph.

[Pixi](https://pydevtools.com/handbook/reference/pixi.md) inherits this capability through [conda-forge](https://pydevtools.com/handbook/reference/conda-forge.md). For teams already using conda-based tooling, CUDA versioning is a solved problem. The tradeoff is that you must commit to the conda ecosystem for your entire dependency stack, or carefully manage [the boundary between conda and pip packages](https://pydevtools.com/handbook/explanation/why-should-i-choose-conda.md).

## What wheel variants will change

Two draft PEPs aim to fix this at the packaging standard level. [PEP 817](https://peps.python.org/pep-0817/), created December 10, 2025, describes the full variant model, including the provider plugin API and the variant ordering algorithm. [PEP 825](https://peps.python.org/pep-0825/), created February 17, 2026, specifies the wheel format extensions: where variant metadata lives in the wheel, and the `namespace :: feature :: value` property structure. The same 11 authors sign both PEPs, with contributors associated with NVIDIA, Quansight, Astral, Meta, and PyPA projects.

The key idea: wheels gain structured metadata like `nvidia :: cuda_version_lower_bound :: 12.8`. At install time, a provider plugin queries the local hardware (GPU model, driver version) and reports what variants the machine supports. The installer then picks the best match from available wheels.

Variants are not a GPU-only mechanism. The same property system describes CPU [microarchitecture levels](https://gitlab.com/x86-psABIs/x86-64-ABI) (`x86_64 :: level :: v3` covers the AVX2-and-FMA baseline) and other compatibility dimensions the current platform tags cannot express. For the full explanation of how the format and plugin model work, see [What are wheel variants?](https://pydevtools.com/handbook/explanation/what-are-wheel-variants.md).

If adopted, `pip install torch` or `uv add torch` could select the correct CUDA build automatically, with no index URL configuration and no package name suffixes. Astral [published a variant-enabled build of uv](https://astral.sh/blog/wheel-variants) on August 13, 2025, though this remains separate from mainline uv.

## What to do now

For PyTorch-centric projects, configure index URLs in `pyproject.toml` using uv's source routing. This gives reproducible builds without manual index URL flags. For mixed GPU workloads spanning PyTorch and RAPIDS, consider whether [conda](https://pydevtools.com/handbook/reference/conda.md) or [pixi](https://pydevtools.com/handbook/reference/pixi.md) would reduce the configuration burden, since both manage CUDA versions as first-class dependencies.

Watch PEP 817 and PEP 825. If wheel variants reach acceptance, the index URL workarounds become unnecessary, and CUDA packages can distribute through PyPI like any other wheel.

> [!NOTE]
> PEP 817 and PEP 825 are in Draft status as of early 2026. Draft PEPs can change or be withdrawn. Check the [PEP index](https://peps.python.org/) for current status.
