# How to Install CUDA PyTorch Wheels Without a GPU


PyTorch publishes CPU, CUDA, and ROCm [wheels](https://pydevtools.com/handbook/reference/wheel.md) on separate indexes, and [uv](https://pydevtools.com/handbook/reference/uv.md)'s `--torch-backend=auto` picks between them by inspecting the local machine for a GPU. On a CI runner or Docker build stage there is no GPU to find, so `auto` quietly selects CPU wheels and the build succeeds with the wrong artifact.

Two environment variables let you state what the *target* machine has instead of what the build machine has.

## Prerequisites

- uv installed, with `--torch-backend` available on the `uv pip` interface
- The CUDA driver version of the machine that will run the code

## Confirm the build machine fell back to CPU

Resolve `torch` on the build machine and read the local version suffix:

```console
$ echo "torch" > requirements.in
$ uv pip compile requirements.in --torch-backend=auto
torch==2.13.0+cpu
```

The `+cpu` suffix is the symptom. uv found no driver and fell through to the CPU index.

## Declare the driver the target machine runs

Set `UV_CUDA_DRIVER_VERSION` to the driver version reported by `nvidia-smi --query-gpu=driver_version --format=csv,noheader` on the target host:

```console
$ UV_CUDA_DRIVER_VERSION=550.144.03 uv pip compile requirements.in --torch-backend=auto
torch==2.13.0+cu129
```

uv now behaves as though it had detected that driver locally. The same variable works for an install:

```console
$ UV_CUDA_DRIVER_VERSION=550.144.03 uv pip install torch --torch-backend=auto
```

In a Dockerfile, set it for the build stage that installs dependencies:

```dockerfile
ENV UV_CUDA_DRIVER_VERSION=550.144.03
RUN uv pip install --system torch --torch-backend=auto
```

Both overrides are read before uv probes the hardware, so they win even on a machine that has a GPU. A stale value will quietly override a correct autodetection.

> [!IMPORTANT]
> `--torch-backend` is respected by `uv pip install`, `uv pip compile`, `uv pip sync`, `uv tool run`, and `uv tool install`. Project commands ignore it, so `uv add`, `uv sync`, and `uv lock` see no effect from either variable. To pin a backend in a project, route torch through an explicit index as described in [How to Install PyTorch with uv](https://pydevtools.com/handbook/how-to/how-to-install-pytorch-with-uv.md).

## Name the target platform when resolving from macOS

uv maps drivers to CUDA indexes only for Linux and Windows targets. Resolving from macOS without naming a target platform drops the override and returns a plain PyPI build:

```console
$ UV_CUDA_DRIVER_VERSION=550.144.03 uv pip compile requirements.in --torch-backend=auto
torch==2.13.0
```

Add `--python-platform` so the resolution targets the machine that will run the wheels:

```console
$ UV_CUDA_DRIVER_VERSION=550.144.03 uv pip compile requirements.in \
    --torch-backend=auto --python-platform linux
torch==2.13.0+cu129
```

Linux build agents already resolve for Linux, so this step only applies when the resolve runs on macOS.

## Pick a driver value

uv compares the driver against a floor for each CUDA version, then takes the highest backend the driver satisfies. Exact values do not matter, only which floor they clear:

| Driver version | Highest CUDA backend selected |
| --- | --- |
| 580 and above | `cu132` |
| 525.60.13 up to 580 | `cu129` |
| 450.80.2 up to 525.60.13 | `cu118` |

An older driver constrains the torch version too, because recent torch releases stopped publishing CUDA 11.8 wheels:

```console
$ UV_CUDA_DRIVER_VERSION=470.199.02 uv pip compile requirements.in \
    --torch-backend=auto --python-platform linux --python-version 3.12
torch==2.7.1+cu118
```

Name the target Python version alongside the driver. The CUDA 11.8 index carries no build for Python 3.14, so asking for both drops the resolution back to CPU wheels without reporting a conflict:

```console
$ UV_CUDA_DRIVER_VERSION=470.199.02 uv pip compile requirements.in \
    --torch-backend=auto --python-platform linux --python-version 3.14
torch==2.13.0+cpu
```

A `+cpu` suffix after setting the override means the requested combination of driver, platform, and Python version has no CUDA build, not that the override was ignored.

An unparseable value fails the command rather than falling back to autodetection, so a typo surfaces immediately:

```console
$ UV_CUDA_DRIVER_VERSION=not-a-version uv pip compile requirements.in --torch-backend=auto
error: Failed to parse environment variable `UV_CUDA_DRIVER_VERSION` with invalid value `not-a-version`: expected version to start with a number, but no leading ASCII digits were found
```

## Target an AMD GPU instead

`UV_AMD_GPU_ARCHITECTURE` does the same job for ROCm. Set it to the architecture of the target card, such as `gfx1100` for RDNA 3:

```console
$ UV_AMD_GPU_ARCHITECTURE=gfx1100 uv pip compile requirements.in \
    --torch-backend=auto --python-platform linux
torch==2.13.0+rocm7.2
```

uv checks the CUDA override first, so set only one of the two.

## Learn More

- [Using uv with PyTorch](https://docs.astral.sh/uv/guides/integration/pytorch/) documents `--torch-backend` and the index layout it selects from
- [Why Installing GPU Python Packages Is So Complicated](https://pydevtools.com/handbook/explanation/installing-cuda-python-packages.md) explains why the wheels are split across indexes at all
- [How to Use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md) covers the surrounding build-stage and caching setup
- [What Are Wheel Variants?](https://pydevtools.com/handbook/explanation/what-are-wheel-variants.md) describes the proposed standard that would make hardware detection part of package metadata
- [light-the-torch](https://github.com/pmeier/light-the-torch) is the pip wrapper whose driver-to-CUDA mapping uv's implementation is based on
