Using Environment Constraints in `pyproject.toml` for Cross-Platform PyTorch Builds

Use UV's optional dependencies, conflict rules, and source selectors in pyproject.toml to declare mutually exclusive CPU and CUDA PyTorch extras that resolve to platform-specific wheels without editing lock files.

The baonguyen6742/uv-install-torch repository demonstrates a production-ready pattern for managing PyTorch's CPU-only and CUDA-enabled variants within a single Python project. By leveraging environment constraints and explicit package indexes in pyproject.toml, you can ensure that uv sync --extra cpu installs portable wheels on any operating system, while uv sync --extra cu124 resolves to CUDA 12.4 builds only on compatible Linux systems.

How UV Manages Cross-Platform PyTorch Dependencies

PyTorch distributes separate wheels for CPU and CUDA variants through different repository indexes. UV (the Astral package manager) handles this complexity through three coordinated mechanisms defined in pyproject.toml:

1. Optional Dependencies (Extras)

Define mutually exclusive extras under [project.optional-dependencies] at lines 24-28. Each extra lists identical package versions but resolves to different wheels based on source configuration:

[project.optional-dependencies]
cpu = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]
cu124 = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]

2. Conflict Rules

Prevent simultaneous activation of incompatible extras using the conflicts matrix in [tool.uv] at lines 30-33:

[tool.uv]
conflicts = [[{ extra = "cpu" }, { extra = "cu124" }]]

This declaration ensures UV rejects uv sync --extra cpu --extra cu124 with a clear resolution error, protecting against undefined runtime behavior.

3. Source Selectors and Explicit Indexes

Map each extra to its corresponding PyTorch index in [tool.uv.sources] and [[tool.uv.index]] at lines 37-62:

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

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

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]

The explicit = true flag ensures these indexes are only used for packages that explicitly reference them, preventing pollution of your dependency resolution with unrelated CUDA packages.

Implementing Platform-Specific Environment Constraints

While the extras system handles wheel selection, you can add optional platform guards to restrict entire installation paths to specific operating systems. In pyproject.toml at line 35, the commented environments entry demonstrates this:

[tool.uv]

# Uncomment to restrict CUDA builds to Linux only

# environments = ["sys_platform == 'linux' and os_name == 'posix'"]

When uncommented, this constraint prevents the cu124 extra from resolving on Windows or macOS, enforcing the requirement that CUDA builds only install on Linux systems where NVIDIA drivers are typically available.

Complete Configuration Example

The following pyproject.toml from baonguyen6742/uv-install-torch implements the full cross-platform build system:

[project]
name = "uv-install-torch"
requires-python = ">=3.10"

[project.optional-dependencies]
cpu = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]
cu124 = ["torch==2.4.1", "torchvision==0.19.1", "torchaudio==2.4.1"]

[tool.uv]
conflicts = [[{ extra = "cpu" }, { extra = "cu124" }]]

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]
torchvision = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]
torchaudio = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
]

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

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

Installing and Verifying Your Build

Select your target platform during installation:


# CPU-only: works on Linux, macOS, Windows

uv sync --extra cpu

# CUDA 12.4: requires Linux and compatible NVIDIA driver

uv sync --extra cu124

Verify the installation using the main.py script in the repository:

import torch

print("torch version:", torch.__version__)
print("CUDA available:", torch.cuda.is_available())
if torch.cuda.is_available():
    print("GPU name:", torch.cuda.get_device_name(0))
    print("CUDA version:", torch.version.cuda)

Running uv run main.py after installing the cu124 extra outputs:


torch version: 2.4.1+cu124
CUDA available: True
GPU name: NVIDIA GeForce RTX 3060

Extending to New CUDA Versions

To add support for CUDA 12.5, extend the configuration with new indexes and sources without removing existing ones:

[project.optional-dependencies]
cu125 = ["torch==2.5.0", "torchvision==0.20.0", "torchaudio==2.5.0"]

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

[tool.uv.sources]
torch = [
  { index = "pytorch-cpu", extra = "cpu" },
  { index = "pytorch-cu124", extra = "cu124" },
  { index = "pytorch-cu125", extra = "cu125" },
]

# Repeat for torchvision and torchaudio arrays

[tool.uv]
conflicts = [[{ extra = "cpu" }, { extra = "cu124" }, { extra = "cu125" }]]

Update the conflict matrix to include all mutually exclusive variants, ensuring only one PyTorch backend installs at a time.

Summary

  • Define extras in [project.optional-dependencies] to declare CPU and CUDA package sets without duplicating version constraints.
  • Use conflict rules in [tool.uv] to prevent installing CPU and CUDA wheels simultaneously, avoiding runtime conflicts.
  • Configure explicit indexes in [[tool.uv.index]] and map extras to them via [tool.uv.sources] to ensure UV pulls wheels from the correct PyTorch repository.
  • Add environment constraints via [tool.uv].environments to restrict CUDA extras to Linux systems where drivers are available.
  • Reference the implementation in baonguyen6742/uv-install-torch for the complete pyproject.toml structure and the main.py validation script.

Frequently Asked Questions

How do I prevent UV from installing CUDA wheels on macOS?

Add an environment constraint to the [tool.uv] section: environments = ["sys_platform == 'linux' and os_name == 'posix'"]. This restricts dependency resolution to Linux systems, preventing UV from attempting to install Linux-only CUDA wheels on macOS or Windows.

Can I install multiple CUDA versions simultaneously?

No. The conflicts matrix explicitly prevents activating multiple extras at once. PyTorch's CUDA and CPU wheels contain overlapping file paths that would cause installation conflicts. You must choose one backend per virtual environment, though you can maintain separate .venv directories for different targets.

What happens if I forget to specify --extra during uv sync?

UV installs only the core dependencies defined in [project.dependencies], omitting PyTorch entirely. You must explicitly pass --extra cpu or --extra cu124 to include the optional PyTorch wheels, ensuring intentional selection of the appropriate backend for your platform.

Where does UV store the index URLs for PyTorch wheels?

Index definitions reside in [[tool.uv.index]] table entries within pyproject.toml. The baonguyen6742/uv-install-torch repository defines these at lines 55-62, setting explicit = true to prevent UV from searching these indexes for unrelated packages, which speeds up resolution and prevents version conflicts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →