diff --git a/bin/update_docker.py b/bin/update_docker.py index 8eccde3af..63b35fcd3 100755 --- a/bin/update_docker.py +++ b/bin/update_docker.py @@ -33,6 +33,23 @@ def __init__(self, manylinux_version: str, platforms: list[str], tag: str | None super().__init__(manylinux_version, platforms, image_name, tag, True) +class CudaImage(Image): + """ + A CUDA manylinux image from the manylinux_cuda project, built by + https://github.com/gpu-ci-demo/manylinux-cuda-container. + + Each architecture has its own repository (``{arch}`` in the image name is + substituted per platform), so (unlike the PyPA images) these are resolved + one repository at a time, but they carry the same dated tags and are pinned + to those just like the PyPA images. + """ + + def __init__(self, manylinux_version: str, cuda_version: str, platforms: list[str]): + alias = f"{manylinux_version}_cuda{cuda_version}" + image_name = f"quay.io/manylinux_cuda/{manylinux_version}_{{arch}}_cuda{cuda_version}" + super().__init__(alias, platforms, image_name) + + images = [ # manylinux2014 images PyPAImage( @@ -86,11 +103,41 @@ def __init__(self, manylinux_version: str, platforms: list[str], tag: str | None PyPAImage( "musllinux_1_2", ["x86_64", "i686", "aarch64", "ppc64le", "s390x", "armv7l", "riscv64"] ), + # CUDA manylinux images (x86_64 and aarch64 only, one repository per arch) + *( + CudaImage(manylinux_version, cuda_version, ["x86_64", "aarch64"]) + for manylinux_version in ("manylinux_2_28", "manylinux_2_34") + for cuda_version in ("12_9", "13_1") + ), ] config = configparser.ConfigParser() for image in images: + if "{arch}" in image.image_name: + # Per-architecture repositories: each arch has its own repo, so resolve + # the dated tag matching 'latest' for each one individually. + for platform in image.platforms: + arch = platform.removeprefix("pypy_") + image_name = image.image_name.format(arch=arch) + _, _, repository_name = image_name.partition("/") + response = requests.get( + f"https://quay.io/api/v1/repository/{repository_name}?includeTags=true" + ) + response.raise_for_status() + tags_dict = response.json()["tags"] + latest_tag = tags_dict.pop("latest") + # find the tag whose manifest matches 'latest' + tag_name = next( + name + for (name, info) in tags_dict.items() + if info["manifest_digest"] == latest_tag["manifest_digest"] + ) + if not config.has_section(platform): + config[platform] = {} + config[platform][image.manylinux_version] = f"{image_name}:{tag_name}" + continue + # get the tag name whose digest matches 'latest' if image.tag is not None: # image has been pinned, do not update diff --git a/cibuildwheel/resources/pinned_docker_images.cfg b/cibuildwheel/resources/pinned_docker_images.cfg index 461e6bf30..b3dc3f947 100644 --- a/cibuildwheel/resources/pinned_docker_images.cfg +++ b/cibuildwheel/resources/pinned_docker_images.cfg @@ -3,6 +3,10 @@ manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2026.06.04-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2026.06.04-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_x86_64:2026.06.04-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2026.06.04-1 +manylinux_2_28_cuda12_9 = quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda12_9:2026.06.08-1 +manylinux_2_28_cuda13_1 = quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda13_1:2026.06.08-1 +manylinux_2_34_cuda12_9 = quay.io/manylinux_cuda/manylinux_2_34_x86_64_cuda12_9:2026.06.08-1 +manylinux_2_34_cuda13_1 = quay.io/manylinux_cuda/manylinux_2_34_x86_64_cuda13_1:2026.06.08-1 [i686] manylinux2014 = quay.io/pypa/manylinux2014_i686:2026.06.04-1 @@ -15,6 +19,10 @@ manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2026.06.04-1 manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2026.06.04-1 manylinux_2_34 = quay.io/pypa/manylinux_2_34_aarch64:2026.06.04-1 musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2026.06.04-1 +manylinux_2_28_cuda12_9 = quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda12_9:2026.06.08-1 +manylinux_2_28_cuda13_1 = quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda13_1:2026.06.08-1 +manylinux_2_34_cuda12_9 = quay.io/manylinux_cuda/manylinux_2_34_aarch64_cuda12_9:2026.06.08-1 +manylinux_2_34_cuda13_1 = quay.io/manylinux_cuda/manylinux_2_34_aarch64_cuda13_1:2026.06.08-1 [ppc64le] manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2026.06.04-1 diff --git a/docs/faq.md b/docs/faq.md index 456022a40..d9a9ec525 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -224,24 +224,26 @@ Consider incorporating these into your package, for example, in `setup.py` using ### Building wheels with CUDA on Linux On Linux, you can build binary wheels with CUDA to take advantage of NVIDIA GPUs for hardware acceleration. -Specify the custom Docker containers with CUDA Toolkit as follows: +The [manylinux_cuda](https://quay.io/organization/manylinux_cuda) project ([source](https://github.com/gpu-ci-demo/manylinux-cuda-container)) publishes manylinux containers that bundle the CUDA Toolkit, and cibuildwheel ships pinned aliases for them. Just like `manylinux_2_28`, you can pass an alias to the `manylinux-*-image` options and cibuildwheel will expand it to a specific, pinned image: ```yaml -CIBW_MANYLINUX_X86_64_IMAGE: >- - quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda13_1:latest -CIBW_MANYLINUX_AARCH64_IMAGE: >- - quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda13_1:latest +CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28_cuda13_1 +CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28_cuda13_1 ``` -Currently, we support the following CUDA manylinux containers: -* `quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda12_9:latest` -* `quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda12_9:latest` -* `quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda13_1:latest` -* `quay.io/manylinux_cuda/manylinux_2_28_aarch64_cuda13_1:latest` -* `quay.io/manylinux_cuda/manylinux_2_34_x86_64_cuda12_9:latest` -* `quay.io/manylinux_cuda/manylinux_2_34_aarch64_cuda12_9:latest` -* `quay.io/manylinux_cuda/manylinux_2_34_x86_64_cuda13_1:latest` -* `quay.io/manylinux_cuda/manylinux_2_34_aarch64_cuda13_1:latest` +The following CUDA aliases are available (for `x86_64` and `aarch64` only): + +* `manylinux_2_28_cuda12_9` +* `manylinux_2_28_cuda13_1` +* `manylinux_2_34_cuda12_9` +* `manylinux_2_34_cuda13_1` + +These aliases resolve to images under `quay.io/manylinux_cuda/`, named +`manylinux___cuda` (e.g. +`quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda13_1`). If you want a CUDA/glibc/arch +combination that isn't aliased above, or you'd rather track the latest build yourself, you +can point at the repository directly with an explicit tag or digest, e.g. +`quay.io/manylinux_cuda/manylinux_2_28_x86_64_cuda13_1:latest`. A typical GitHub Actions workflow will look like this: @@ -267,10 +269,8 @@ jobs: - name: Build wheels uses: pypa/cibuildwheel@v3 env: - CIBW_MANYLINUX_X86_64_IMAGE: >- - quay.io/manylinux_cuda/${{ matrix.manylinux-base }}_x86_64_cuda${{ matrix.cuda-version }}:latest - CIBW_MANYLINUX_AARCH64_IMAGE: >- - quay.io/manylinux_cuda/${{ matrix.manylinux-base }}_aarch64_cuda${{ matrix.cuda-version }}:latest + CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux-base }}_cuda${{ matrix.cuda-version }} + CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.manylinux-base }}_cuda${{ matrix.cuda-version }} CIBW_BUILD: cp312-manylinux_${{ matrix.target.arch }} ``` diff --git a/unit_test/options_test.py b/unit_test/options_test.py index 6b586038f..a2c957fb4 100644 --- a/unit_test/options_test.py +++ b/unit_test/options_test.py @@ -125,6 +125,29 @@ def test_passthrough(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: } +@pytest.mark.parametrize("arch", ["x86_64", "aarch64"]) +@pytest.mark.parametrize( + "alias", + [ + "manylinux_2_28_cuda12_9", + "manylinux_2_28_cuda13_1", + "manylinux_2_34_cuda12_9", + "manylinux_2_34_cuda13_1", + ], +) +def test_cuda_pinned_images(arch: str, alias: str) -> None: + pinned_images = _get_pinned_container_images() + + # CUDA aliases exist for x86_64 and aarch64, each in their own repository + image = pinned_images[arch][alias] + repository, _, tag = image.partition(":") + assert repository == f"quay.io/manylinux_cuda/{alias.replace('cuda', arch + '_cuda', 1)}" + assert tag + + # ... but not for architectures without CUDA images + assert alias not in pinned_images["i686"] + + @pytest.mark.parametrize( "env_var_value", [