From d2c2750975473a5842376aa944182b66d74d9c45 Mon Sep 17 00:00:00 2001 From: Andrey Talman Date: Wed, 30 Sep 2026 12:54:49 -0700 Subject: [PATCH 1/3] Drop unused CUDA entries from preview in published_versions.json After #2139 the page hides CUDA boxes the channel does not ship, but `update_versions` only skipped those labels, so `preview` in published_versions.json still carried `cuda.z` with a stale command on every OS and package. Add `sync_cuda_entries`, run before each channel's update: - nightly (preview): remove `cuda.x/y/z` entries the channel has no CUDA version for. - A new stable is created as a copy of preview, so re-create any label the release channel needs from a sibling CUDA entry, in cuda.x/y/z order. The existing update then fills in its command. Release entries are never dropped. Test plan: simulated the update-quick-start-module run (getting-started matrices from pytorch/test-infra main, published_versions.json from `site`): - preview: `cuda.z` removed for linux/windows/macos, pip and libtorch. - 2.14.1 (created from preview in this run): has cuda.x/y/z in order, and the whole file is identical to the current generator's output apart from the removed preview `cuda.z` entries. - Page logic (Node harness): nightly two boxes, stable three, selection moves off the hidden box, as before. - `sync_cuda_entries` edge cases: nightly back to three CUDA versions re-adds `cuda.z`; nightly with one drops `cuda.y`/`cuda.z`; release never drops entries. --- scripts/gen_quick_start_module.py | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/scripts/gen_quick_start_module.py b/scripts/gen_quick_start_module.py index 1f074da6031c..e9fbf8e74ed7 100755 --- a/scripts/gen_quick_start_module.py +++ b/scripts/gen_quick_start_module.py @@ -119,6 +119,38 @@ def get_gpu_info(acc_key, instr, acc_arch_map): # It will modify versions json object with installation instructions # Provided by generate install matrix Github Workflow, stored in release_matrix # json object. +CUDA_LABELS = ["cuda.x", "cuda.y", "cuda.z"] + + +def sync_cuda_entries(version_entry, acc_arch_map, release_version): + """Match the CUDA entries of a version to the channel's CUDA labels. + + Nightly drops labels it has no CUDA version for, so preview carries no + stale commands for hidden boxes. A new stable is copied from preview, so + any label the release channel needs is re-created from a sibling entry. + """ + for os_vers in version_entry.values(): + for pkg_key, pkg_vers in os_vers.items(): + siblings = [pkg_vers[k] for k in CUDA_LABELS if k in pkg_vers] + if not siblings: + continue + rebuilt = {} + for acc_key, instr in pkg_vers.items(): + unused = acc_key in CUDA_LABELS and acc_key not in acc_arch_map + if unused and release_version == "nightly": + continue + rebuilt[acc_key] = instr + if acc_key in CUDA_LABELS: + # Re-add missing labels right after their predecessor so + # cuda.x/y/z stay in order. + for label in CUDA_LABELS[CUDA_LABELS.index(acc_key) + 1 :]: + if label in pkg_vers: + break + if label in acc_arch_map: + rebuilt[label] = copy.deepcopy(siblings[-1]) + os_vers[pkg_key] = rebuilt + + def update_versions(versions, release_matrix, release_version): version = "preview" template = "preview" @@ -132,6 +164,8 @@ def update_versions(versions, release_matrix, release_version): ) versions["latest_stable"] = version + sync_cuda_entries(versions["versions"][version], acc_arch_map, release_version) + # Perform update of the json file from release matrix for os_key, os_vers in versions["versions"][version].items(): for pkg_key, pkg_vers in os_vers.items(): From 96800b0950aeefbd3cc080ece16471afecbecea2 Mon Sep 17 00:00:00 2001 From: Andrey Talman Date: Wed, 30 Sep 2026 13:03:17 -0700 Subject: [PATCH 2/3] Support a release channel with two CUDA versions `sync_cuda_entries` only dropped unused CUDA labels for nightly, so a future release with two CUDA versions would still have kept a `cuda.z` entry in its stable version. Drop labels the channel has no CUDA version for on both channels. The page already hides those boxes for either channel, since `extract_arch_ver_map` builds `version_map` the same way for nightly and release. The number of labels now follows each channel's Linux matrix: three CUDA versions give cuda.x/y/z, two give cuda.x/y. Only the entry being updated (preview or the latest stable) changes; older versions keep their labels. Test plan: simulated the update-quick-start-module run with scratch copies of pytorch/test-infra: - Today (nightly 13.2/13.4, release 12.6/13.0/13.2): output identical to the previous commit; preview cuda.x/y, 2.14.1 cuda.x/y/z. - Nightly 2, release 2, new stable 2.15.0: preview and 2.15.0 both cuda.x/y; 2.14.0 unchanged (cuda.x/y/z). - Nightly 3, release 2, new stable 2.15.0: preview cuda.x/y/z, 2.15.0 cuda.x/y. - Release going from 3 to 2 CUDA versions on an existing stable: its cuda.z is dropped. - Page logic (Node harness): with a two-version release, Stable shows two boxes at `col-md-3`. --- scripts/gen_quick_start_module.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/gen_quick_start_module.py b/scripts/gen_quick_start_module.py index e9fbf8e74ed7..c35abaa7dd09 100755 --- a/scripts/gen_quick_start_module.py +++ b/scripts/gen_quick_start_module.py @@ -122,12 +122,14 @@ def get_gpu_info(acc_key, instr, acc_arch_map): CUDA_LABELS = ["cuda.x", "cuda.y", "cuda.z"] -def sync_cuda_entries(version_entry, acc_arch_map, release_version): +def sync_cuda_entries(version_entry, acc_arch_map): """Match the CUDA entries of a version to the channel's CUDA labels. - Nightly drops labels it has no CUDA version for, so preview carries no - stale commands for hidden boxes. A new stable is copied from preview, so - any label the release channel needs is re-created from a sibling entry. + A channel with fewer CUDA versions (e.g. two) drops the unused labels, so + the entry carries no stale commands for boxes the page hides. A new stable + is copied from preview, so any label the channel needs but the entry lacks + is re-created from a sibling entry. Only the entry being updated (preview + or the latest stable) is touched; older versions keep their labels. """ for os_vers in version_entry.values(): for pkg_key, pkg_vers in os_vers.items(): @@ -136,8 +138,7 @@ def sync_cuda_entries(version_entry, acc_arch_map, release_version): continue rebuilt = {} for acc_key, instr in pkg_vers.items(): - unused = acc_key in CUDA_LABELS and acc_key not in acc_arch_map - if unused and release_version == "nightly": + if acc_key in CUDA_LABELS and acc_key not in acc_arch_map: continue rebuilt[acc_key] = instr if acc_key in CUDA_LABELS: @@ -164,7 +165,7 @@ def update_versions(versions, release_matrix, release_version): ) versions["latest_stable"] = version - sync_cuda_entries(versions["versions"][version], acc_arch_map, release_version) + sync_cuda_entries(versions["versions"][version], acc_arch_map) # Perform update of the json file from release matrix for os_key, os_vers in versions["versions"][version].items(): From 38d954466cd33c684e6289fb57840f1de95f4dd9 Mon Sep 17 00:00:00 2001 From: Andrey Talman Date: Wed, 30 Sep 2026 13:06:48 -0700 Subject: [PATCH 3/3] Derive the accelerator map only from the test-infra matrices Make `extract_arch_ver_map` follow whatever the getting-started matrices contain, without silent assumptions: - Compare versions numerically. As strings, `max("7.2", "7.14")` is "7.2"; it only worked because test-infra sends one ROCm version per channel. - If a channel has no ROCm version, drop `rocm5.x` from its map instead of crashing on `max()` of an empty list, and publish "Not available for this build" rather than a stale command. Existing notes (e.g. "ROCm is not available on Windows") are kept, and a stable copied from preview drops the generic note once its channel matches a build again. - The page has three CUDA boxes and test-infra ships at most three CUDA versions per channel. Fail with a clear error if that ever changes, instead of `zip` silently dropping the newest version. - Resolve `default` entries (macOS) to CPU before looking up the accelerator, so they never depend on which labels a channel has. Test plan: simulated the update-quick-start-module run. - Today (nightly 13.2/13.4, release 12.6/13.0/13.2) and the two-version release scenarios from the previous commit: output byte-for-byte identical. - Nightly matrix with the ROCm rows removed: preview linux rocm5.x shows the note, Windows/macOS notes unchanged, 2.14.1 identical to today's run. - Four nightly CUDA versions: fails with "nightly getting-started matrix has 4 CUDA versions (12.6, 13.0, 13.2, 13.4), but the page has only 3 CUDA boxes". - `extract_arch_ver_map` with CUDA 13.2/13.9/13.10 and ROCm 7.2/7.14: labels 13.2, 13.9, 13.10 and ROCm 7.14. --- scripts/gen_quick_start_module.py | 52 +++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/scripts/gen_quick_start_module.py b/scripts/gen_quick_start_module.py index c35abaa7dd09..d3b4a3c42e32 100755 --- a/scripts/gen_quick_start_module.py +++ b/scripts/gen_quick_start_module.py @@ -107,12 +107,11 @@ def get_package_type(pkg_key: str, os_key: OperatingSystem) -> str: def get_gpu_info(acc_key, instr, acc_arch_map): + if DEFAULT in instr: + return acc_arch_map["accnone"] if acc_key not in acc_arch_map: return (None, None) - gpu_arch_type, gpu_arch_version = acc_arch_map[acc_key] - if DEFAULT in instr: - gpu_arch_type, gpu_arch_version = acc_arch_map["accnone"] - return (gpu_arch_type, gpu_arch_version) + return acc_arch_map[acc_key] # This method is used for generating new published_versions.json file @@ -120,6 +119,7 @@ def get_gpu_info(acc_key, instr, acc_arch_map): # Provided by generate install matrix Github Workflow, stored in release_matrix # json object. CUDA_LABELS = ["cuda.x", "cuda.y", "cuda.z"] +UNAVAILABLE_NOTE = "NOTE: Not available for this build" def sync_cuda_entries(version_entry, acc_arch_map): @@ -184,15 +184,26 @@ def update_versions(versions, release_matrix, release_version): ] if gpu_arch_type is None: + # The channel ships no build for this accelerator (e.g. no + # ROCm version in the matrix): don't keep a stale command. + if not instr.get("note"): + instr["note"] = UNAVAILABLE_NOTE + instr["command" if package_type != "libtorch" else "versions"] = None continue + if pkg_arch_matrix: + # A new stable is copied from preview; drop the note an + # accelerator got there while the channel lacked it. + if instr.get("note") == UNAVAILABLE_NOTE: + instr["note"] = None + if package_type == "libtorch" and instr.get("versions") is None: + instr["versions"] = {} + # A CUDA version the channel ships but this OS does not (e.g. no # Windows build): say so instead of keeping a stale command. if gpu_arch_type == "cuda": if pkg_arch_matrix: instr["note"] = None - if package_type == "libtorch" and instr.get("versions") is None: - instr["versions"] = {} else: instr["note"] = ( f"NOTE: CUDA {gpu_arch_version} is not available " @@ -285,17 +296,32 @@ def gen_ver_list(chan, gpu_arch_type): if x["gpu_arch_type"] == gpu_arch_type } + # Compare numerically: as strings, "7.14" sorts before "7.2". + def version_key(ver): + return tuple(int(part) for part in ver.split(".")) + for chan in ("nightly", "release"): cuda_ver_list = gen_ver_list(chan, "cuda") rocm_ver_list = gen_ver_list(chan, "rocm") - cuda_list = sorted(cuda_ver_list.values()) - acc_arch_ver_map[chan]["rocm5.x"] = ("rocm", max(rocm_ver_list.values())) - cuda_labels = ["cuda.x", "cuda.y", "cuda.z"] - for cuda_ver, label in zip(cuda_list, cuda_labels): + cuda_list = sorted(set(cuda_ver_list.values()), key=version_key) + if len(cuda_list) > len(CUDA_LABELS): + raise ValueError( + f"{chan} getting-started matrix has {len(cuda_list)} CUDA versions " + f"({', '.join(cuda_list)}), but the page has only " + f"{len(CUDA_LABELS)} CUDA boxes" + ) + if rocm_ver_list: + acc_arch_ver_map[chan]["rocm5.x"] = ( + "rocm", + max(rocm_ver_list.values(), key=version_key), + ) + else: + acc_arch_ver_map[chan].pop("rocm5.x", None) + for cuda_ver, label in zip(cuda_list, CUDA_LABELS): acc_arch_ver_map[chan][label] = ("cuda", cuda_ver) - # Drop labels the channel has no CUDA version for, so the page strikes - # them out instead of showing the hard-coded defaults. - for label in cuda_labels[len(cuda_list):]: + # Drop labels the channel has no CUDA version for, so the page hides + # them instead of showing the hard-coded defaults. + for label in CUDA_LABELS[len(cuda_list):]: acc_arch_ver_map[chan].pop(label, None)