|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | +# All rights reserved. |
| 4 | +# |
| 5 | +# This source code is licensed under the BSD-style license found in the |
| 6 | +# LICENSE file in the root directory of this source tree. |
| 7 | + |
| 8 | +"""Narrow the generated build matrix to the rows a GPU wheel can honestly support. |
| 9 | +
|
| 10 | +The shared matrix generator emits every CUDA version and Python version it knows about. |
| 11 | +Building all of them would publish wheels for combinations nothing can verify, and a GPU |
| 12 | +wheel that installs and then cannot run is worse than one that does not exist: the failure |
| 13 | +appears when a model runs, and it looks like a model problem rather than a packaging one. |
| 14 | +
|
| 15 | +A row is kept only when all three of these hold: |
| 16 | +
|
| 17 | + a GPU exists that the row's device code covers |
| 18 | + a PyTorch build is published for that CUDA version and architecture |
| 19 | + a machine is available to run a real model before release |
| 20 | +
|
| 21 | +The values below are the current answers to those questions. They are written out rather |
| 22 | +than derived because each one is an external fact that can change independently. |
| 23 | +""" |
| 24 | + |
| 25 | +import argparse |
| 26 | +import json |
| 27 | +import sys |
| 28 | +from typing import Any, Dict, List |
| 29 | + |
| 30 | +# Python versions to skip. 3.14 is excluded because the current CPU wheel rows already fail |
| 31 | +# on it for an unrelated reason in the example requirements, so a GPU row would inherit a |
| 32 | +# known-broken build. The free-threaded builds are excluded because the CUDA dependencies |
| 33 | +# are not published for them. |
| 34 | +DISABLED_PYTHON_VERSIONS: List[str] = ["3.13t", "3.14", "3.14t", "3.15", "3.15t"] |
| 35 | + |
| 36 | +# CUDA versions to publish. cu130 first because it is the generator's stable choice, and |
| 37 | +# cu126 because it is the floor a consumer pairing with an older PyTorch needs. |
| 38 | +SUPPORTED_CUDA_VERSIONS: List[str] = ["cu126", "cu130"] |
| 39 | + |
| 40 | +# The single row built for a pull request. A full matrix on every push would cost hours for |
| 41 | +# little signal, and this pair is the one with a machine that can run a model on it. |
| 42 | +PR_PYTHON_VERSION: str = "3.12" |
| 43 | +PR_CUDA_VERSION: str = "cu130" |
| 44 | + |
| 45 | +# Jetson devices are their own row: a JetPack image, one Python version, and one CUDA |
| 46 | +# version. They cannot take a generic aarch64 wheel, because the generic builds carry no |
| 47 | +# device code for their GPU architecture and no portable fallback either. |
| 48 | +# |
| 49 | +# Kept empty on purpose. Published PyTorch stopped shipping sm_87 device code after 2.8.0, |
| 50 | +# so a Jetson row today would produce a wheel whose PyTorch dependency cannot execute on the |
| 51 | +# device. Populate this when that changes. |
| 52 | +JETPACK_PYTHON_VERSIONS: List[str] = [] |
| 53 | +JETPACK_CUDA_VERSIONS: List[str] = [] |
| 54 | +JETPACK_CONTAINER_IMAGE: str = "nvcr.io/nvidia/l4t-jetpack:r36.4.0" |
| 55 | + |
| 56 | +# The generic aarch64 builder image predates the glibc that the CUDA wheels need, so the |
| 57 | +# aarch64 rows build in a newer manylinux image instead. |
| 58 | +SBSA_CONTAINER_IMAGE: str = "quay.io/pypa/manylinux_2_39_aarch64" |
| 59 | + |
| 60 | + |
| 61 | +def keep(item: Dict[str, Any], is_jetpack: bool) -> bool: |
| 62 | + """Whether this row should be built, adjusting its container image where needed.""" |
| 63 | + if item["python_version"] in DISABLED_PYTHON_VERSIONS: |
| 64 | + return False |
| 65 | + |
| 66 | + if is_jetpack: |
| 67 | + if ( |
| 68 | + item["python_version"] in JETPACK_PYTHON_VERSIONS |
| 69 | + and item["desired_cuda"] in JETPACK_CUDA_VERSIONS |
| 70 | + ): |
| 71 | + item["container_image"] = JETPACK_CONTAINER_IMAGE |
| 72 | + return True |
| 73 | + return False |
| 74 | + |
| 75 | + if item["desired_cuda"] not in SUPPORTED_CUDA_VERSIONS: |
| 76 | + return False |
| 77 | + |
| 78 | + if item.get("gpu_arch_type") == "cuda-aarch64": |
| 79 | + item["container_image"] = SBSA_CONTAINER_IMAGE |
| 80 | + |
| 81 | + return True |
| 82 | + |
| 83 | + |
| 84 | +def only_pull_request_row(items: List[Dict[str, Any]]) -> List[Dict[str, Any]]: |
| 85 | + """One representative row, so a pull request does not build the whole matrix.""" |
| 86 | + for item in items: |
| 87 | + if ( |
| 88 | + item["python_version"] == PR_PYTHON_VERSION |
| 89 | + and item["desired_cuda"] == PR_CUDA_VERSION |
| 90 | + ): |
| 91 | + return [item] |
| 92 | + # Falling back to the first row rather than to nothing: an empty matrix would make the |
| 93 | + # build job vanish, which reads as a pass. |
| 94 | + return items[:1] |
| 95 | + |
| 96 | + |
| 97 | +def main(argv: List[str]) -> None: |
| 98 | + parser = argparse.ArgumentParser() |
| 99 | + parser.add_argument("--matrix", required=True, help="the generated matrix, as JSON") |
| 100 | + parser.add_argument( |
| 101 | + "--jetpack", default="false", help="build the Jetson row instead" |
| 102 | + ) |
| 103 | + parser.add_argument("--limit-pr-builds", default="false", help="build one row only") |
| 104 | + args = parser.parse_args(argv) |
| 105 | + |
| 106 | + try: |
| 107 | + matrix = json.loads(args.matrix) |
| 108 | + except json.JSONDecodeError as error: |
| 109 | + print(f"could not parse the matrix: {error}", file=sys.stderr) |
| 110 | + sys.exit(1) |
| 111 | + |
| 112 | + is_jetpack = args.jetpack.lower() == "true" |
| 113 | + items = [item for item in matrix.get("include", []) if keep(item, is_jetpack)] |
| 114 | + |
| 115 | + if args.limit_pr_builds.lower() == "true" and items: |
| 116 | + items = only_pull_request_row(items) |
| 117 | + |
| 118 | + # Fail loudly on an empty result. A silently empty matrix produces a workflow with no |
| 119 | + # build job, which shows up as a green check for a build that never happened. |
| 120 | + if not items: |
| 121 | + print( |
| 122 | + "the filter produced no rows to build, so nothing would be verified. " |
| 123 | + f"jetpack={is_jetpack}, supported CUDA={SUPPORTED_CUDA_VERSIONS}", |
| 124 | + file=sys.stderr, |
| 125 | + ) |
| 126 | + sys.exit(1) |
| 127 | + |
| 128 | + print(json.dumps({"include": items})) |
| 129 | + |
| 130 | + |
| 131 | +if __name__ == "__main__": |
| 132 | + main(sys.argv[1:]) |
0 commit comments