Skip to content

cuda.bindings: support multiple CTK release lines on main - #2737

Draft
rwgk wants to merge 64 commits into
NVIDIA:mainfrom
rwgk:agent/cuda-bindings-12-on-main
Draft

cuda.bindings: support multiple CTK release lines on main#2737
rwgk wants to merge 64 commits into
NVIDIA:mainfrom
rwgk:agent/cuda-bindings-12-on-main

Conversation

@rwgk

@rwgk rwgk commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

REMINDER

After approval and before triggering the final merge CI, remove the temporary .lycheeignore. It excludes only three canonical main/cuda_bindings_12 URLs that cannot resolve until this PR is merged. The authored-source lychee pre-commit hook is explicitly skipped by the CI pre-commit jobs; CI separately checks links in rendered documentation. Removing the file therefore will not introduce a required-check failure before merge.

After merging, run pre-commit run lychee --all-files on fresh main to validate those links.

Summary

Closes #1199.

This is the writable continuation of Keith Kraus's original PR #2675, "cuda.bindings: build 12.9 and 13.x selectively from main". Keith authored most of the CUDA 12 source import and the initial build, test, and release integration. GitHub closed #2675 automatically when its temporary base branch was deleted after #2467 merged; this replacement preserves that work and incorporates the subsequent review-driven redesign and hardening.

This PR moves active development of both released CUDA bindings lines onto main:

Source root Release status Toolkit pin
cuda_bindings_12/ maintenance 12.9.1
cuda_bindings/ current 13.4.1

The two roots are alternative source trees for the same cuda-bindings distribution and cuda.bindings import namespace. Either can be built and installed independently, but they are not separately named and are not intended to coexist in one environment. CI, documentation, and release workflows select the appropriate source root and toolkit pin.

After merge, main becomes the active source of truth for both lines. The historical 12.9.x branch remains only as a read-only release record rather than an active backport or artifact-source branch.

What Changes

  • Imports the maintained CUDA 12.9 bindings tree under cuda_bindings_12/, including packaging, tests, examples, documentation, and its lock file.
  • Replaces the former cuda.build/cuda.prev_build fields and backport_branch setting with a package-root registry in ci/versions.yml. Each unique root declares its exact toolkit pin and scalar current or maintenance release status.
  • Extends dependency-aware CI planning from ci: activate dependency-aware package builds and tests #2467 so builds and tests are selected by bindings package root while CUDA Core work remains selected by ABI variant.
  • Routes wheel, sdist, source-smoke, coverage, nightly, documentation, and release workflows through normalized package-root metadata.
  • Retires the 12.9.x backport workflow and branch-sourced artifact flow.
  • Adds an explicit maintenance policy for generated, cybind-owned, and handwritten files in cuda_bindings_12/MAINTENANCE.md.

Review Guide

Because the raw PR is dominated by the CUDA 12 tree (approximately 98% of the additions), the following six-commit branch decomposes the architecture at review snapshot 7cb81e1:

review/multi-ctk-bindings-on-main-20260903-1844-7cb81e1

Read it oldest to newest. Each commit message explains its boundary, suggested review focus, and final-tree validation:

  1. Import the maintained CUDA 12.9 source root
  2. Define bindings package roots and release statuses
  3. Plan selective work by bindings package root
  4. Route builds and tests by bindings package root
  5. Select releases from the tagged package root
  6. Retire the CUDA 12.9 backport workflow

The highest-value review path is layers 2-5. Layer 1 is predominantly Keith's imported CUDA 12.9 tree; layer 6 isolates the branch-ownership policy. These are narrative review layers, not independently deployable states, and the PR branch remains authoritative.

Post-snapshot changes are best reviewed in these groups:

Design

  • The source root identifies the release line. ci/versions.yml maps each unique root directly to its toolkit pin and release status. There are no synthetic line IDs or separate role-to-line mappings.
  • Package metadata owns tag syntax. Each root's [tool.setuptools_scm].tag_regex defines its accepted release family. Registry validation checks that this metadata agrees with the configured toolkit pin and derives the CTK target and CUDA ABI fields.
  • The supported registry shape is explicit. Validation currently requires exactly one current root and one maintenance root with different CUDA ABI majors. Other shapes fail rather than implying workflow support that does not exist.
  • Selective CI remains dependency-aware. Package-local changes select the affected work and reuse exact-merge-base artifacts where safe; shared or uncertain changes expand conservatively. Bindings and metapackage tests are gated by source root. A one-root bindings source change still rebuilds both CUDA Core ABI variants because they are merged into one Core wheel.
  • Workflows consume normalized records. The repository-local, importable ci.tools package centralizes registry, version, planner, and release logic. Workflows consume explicit normalized inputs rather than reconstructing bindings identity from directory names or hard-coded CUDA majors.
  • Release selection follows the triggering tag and tagged source tree. A v12 and v13 tag may point to the same commit, but they produce distinct tag-push CI runs. Planning selects the package root matching the triggering tag, and release lookup requires the requested tag ref and SHA, preventing selection of a sibling tag's artifacts.
  • Release validation is fail-closed. The release path derives the complete Python/platform/ABI wheel matrix from the tagged workflows and rejects missing or duplicate targets, unexpected platform/ABI targets or distributions, wheel build tags, development/local versions, and wrongly versioned wheels. Component-prefixed releases also resolve their bindings dependency from the tagged tree.
  • Historical releases retain a contained compatibility path. The resolver reads either ci/versions.yml or legacy ci/versions.json from the tag tree and handles older tags that predate setuptools_scm. Malformed modern registries and unsupported tag forms fail closed.
  • Cross-root maintenance is a review responsibility. Generated and target-specific differences may be legitimate. Handwritten changes require an explicit applicability review across both roots rather than a byte-identity requirement.

Reviewer Decisions

Please explicitly accept or reject these policies:

  1. main is the sole active source of truth. The historical 12.9.x branch receives no further routine or emergency backports. CUDA 12 fixes are made in cuda_bindings_12/ on main, alongside a corresponding current-root change when applicable.
  2. Released bindings lines use explicit source roots. The directory identifies the CI/build/release source line; release_status records its place in the release lifecycle without introducing a second line identity.
  3. Full-root duplication is reviewed semantically. Contributors assess handwritten changes for both roots and explain intentional one-root-only changes; the repository does not require byte identity across roots that legitimately differ.

Validation

Validation completed successfully for final head cb95f0d73e1:

  • pre-commit run --all-files passed, including Ruff, actionlint, mypy, YAML/TOML/RST checks, generated-file seals, SCM/registry checks, and authored-source lychee with the three temporary exact exclusions described in the REMINDER.
  • The CI-tool unit suite passed: 154 tests and 55 subtests.
  • The CUDA Core benchmark lock passed the workflow-pinned Pixi 0.73.0 freshness check.
  • Full GitHub validation completed with 124 passed checks, 3 intentional skips, and 0 pending, failed, or cancelled checks.
  • Bindings wheels for both roots were built and tested on Linux x86_64, Linux AArch64, and Windows AMD64; the current CUDA 13.4.1 root was additionally built on Windows ARM64. Both bindings sdists, CUDA Core ABI testing, documentation, source-build smoke, static analysis, and security workflows passed.
  • Targeted release tests cover mixed selective changes, same-commit v12/v13 tags, historical JSON metadata, pre-setuptools_scm tags, component-prefixed releases, complete binary wheel matrices, and partial-matrix rejection. Local validation against the actual v12.9.7 and v13.4.1 tag trees derived 21 and 23 required wheel targets per binary distribution respectively.

View all PR checks, the completed full CI run, the selected-source smoke run, and the Pixi lockfile freshness run.

Remaining Before Merge

  • Obtain reviewer approval for the source-root, branch-ownership, and semantic-duplication policies above.
  • Complete a hosted CUDA 12.9 CI: Release dry run for this head. The historical v12.9.7 run succeeded, but its artifacts have expired; normal PR artifacts cannot substitute for a fresh tag-shaped run because release lookup deliberately validates the tag event, ref, SHA, and release-version wheels.
  • After approval, remove .lycheeignore and run the final merge CI as described in the REMINDER.

Out of Scope

  • Restructuring or deduplicating the contents of the two source roots. A future shared-source or overlay design can be evaluated separately.
  • Supporting registry shapes beyond the one-current/one-maintenance public release model implemented here.
  • Regenerating the CI pipeline diagram, tracked separately in Rewrite ci/ci-pipeline.svg in something text-based #2740.

@rwgk rwgk added this to the cuda.bindings 13.5.0 & 12.9.10 milestone Aug 31, 2026
@rwgk rwgk added enhancement Any code-related improvements CI/CD CI/CD infrastructure cuda.bindings Everything related to the cuda.bindings module labels Aug 31, 2026
@rwgk rwgk self-assigned this Aug 31, 2026
@rwgk

rwgk commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 09f27fb

@rwgk

rwgk commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 7cb81e1

@rwgk
rwgk marked this pull request as ready for review September 4, 2026 15:20

@rwgk rwgk left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mdboom, I rebuilt the six-layer narrated review branch a third time from the current PR head (7cb81e1):

https://github.com/rwgk/cuda-python/commits/review/multi-ctk-bindings-on-main-20260903-1844-7cb81e1

That branch is only a review aid; this PR remains authoritative. The six commits are meant to be read oldest to newest. Each commit message explains its boundary, suggested review focus, and validation:

  1. 6abe8bb — import the maintained CUDA 12.9 source root
  2. dbbdf68 — define the package-root/release-status registry
  3. 8be614a — plan selective work by package root
  4. 61e67bb — route builds and tests
  5. c1bad81 — resolve and validate releases
  6. 04bfed6 — retire the 12.9 backport workflow

For the shortest useful path, I'd start with layer 2 and then review layers 3-5. Layer 4 is necessarily the broad workflow-integration layer.

I incorporated your latest feedback: ci.tools is now a small package under ci/pyproject.toml; downstream consumers trust normalized package records; pre-commit checks the registry against both bindings packages' metadata; and the generic --package-json behavior was replaced by the purpose-specific write-github-env command.

I then made another simplification pass, removing unsupported planner shapes, unused workflow fallbacks, repeated registry reads, and redundant release metadata/checks while preserving historical-tag compatibility. That pass changed 280 lines in and 412 lines out - a net reduction of 132 lines, including the added focused tests.

The current PR head is fully green. When you have a chance, could you please review this fresh layered view and let me know whether you see anything else blocking this PR? The main decisions I hope to settle are the package-root/release-status model, release routing, and moving CUDA 12.9 maintenance onto main.

Comment thread ci/README.md
moves to a new toolkit minor; registry validation rejects a configuration
where the two disagree.

The Python helpers share registry parsing and validation, so they are modules

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that (a) you are running from the root of the repo, and (b) you already did a pip install -r ci/tools/requirements.txt.

requirements.txt has been deprecated for a long time. We should either: add a pyproject.toml for ci/tools and require that the package is installed into a venv (which removes the $PWD problem), or use PEP 723 comments in each script and not have a proper package altogether.

Personally, I think PEP 723 is the simpler solution (it doesn't require juggling virtual environments), but it does require having uv or pixi around to run the script.

The argument in favor of a package would be if these scripts start to share a lot of logic and helpers between them such that things in ci/tools need to import other things in ci/tools. It doesn't look like they do right now, but in the original mega-PR they started to do that a bit.

Agreed. I chose the package option after the later refactor made the helpers genuinely share logic: bindings_scm_version, check_pixi_cuda_version, check_release_notes, and compute_ci_plan now import the registry/version machinery rather than duplicating it. The PR now adds ci/pyproject.toml, removes both requirements files, and installs ./ci in editable mode at workflow call sites. The documented module invocation therefore works through an installed package and no longer relies on $PWD putting the repository on Python's import path; PEP 723 would repeat the same dependency metadata across several scripts.

parser.add_argument("--control-config", type=Path)
commands = parser.add_subparsers(dest="command")
write_env = commands.add_parser(
"write-github-env",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this flag? It looks like it just spits the JSON provided right back out, doing nothing to it?

Agreed--it had no useful standalone meaning. --package-json and --github-env are gone. The only JSON-consuming operation is now the purpose-specific write-github-env GITHUB_ENV subcommand, which reads a selected package record from stdin, validates it, and appends only the supported environment variables. This removes the round-trip behavior and prevents that operation from being accidentally combined with registry-selection modes.

Comment thread .pre-commit-config.yaml
types: [text]

- id: check-bindings-config
name: Check bindings registry tracks package metadata

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplication from versions.yml is worrisome, but I don't see any way around it. I think a precommit hook to make sure it's in sync with versions.yml would be a good idea.

Agreed. I added this check-bindings-config hook, triggered by ci/versions.yml or any bindings pyproject.toml. Registry validation reads each package root's tag_regex, constructs v<toolkit_version> from the registry, and requires the SCM regex to accept that exact version. Focused SCM tests additionally verify that the current 13.3 pattern rejects 13.4 and that metapackage SCM metadata matches both bindings roots. A normal release-family update on either side now fails locally and in CI if the duplicated metadata drifts.

@kkraus14 kkraus14 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review at 7cb81e114f8f4fabe3e4fa35a9f2bb5c11b5a45a. I left inline comments for the code findings and temporary-ignore cleanup. Two branch-wide blockers do not have a meaningful single-line anchor: this head currently conflicts with main in the backport/workflow files, so it needs to be updated and CI rerun; and I could not find a CI: Release 12.9 dry run for this head, so the maintenance release path is not yet validated end to end. The successful normal CI run rebuilt both bindings lines because CI files changed, so it does not cover the mixed selective-build case called out inline. The pipeline-diagram follow-up is already covered by the existing review thread and #2740.

- name: Download cuda.bindings build artifacts
if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') &&
env.BINDINGS_SOURCE == 'main' }}
env.BINDINGS_SOURCE == 'local' }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — High: this gate consumes the aggregate TEST_BINDINGS / TEST_CORE / TEST_PYTHON flags for every CUDA-major matrix row. For a mixed change such as a v12 bindings file plus a cuda_core test, the plan builds only the v12 bindings test artifact but schedules core tests for both v12 and v13; the v13 row then reaches this step and requests a v13 artifact that was never built. The Windows workflow has the same shape. Please derive these gates per selected bindings line (or otherwise ensure each local row only downloads an artifact its root built) and add a mixed-change regression test.

Comment thread ci/tools/validate_release_wheels.py Outdated
)

missing_distributions = sorted(expected_distributions - set(seen_versions))
missing_distributions = sorted(expected_distributions - seen_distributions)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — High: this verifies only that each distribution name appeared. I reproduced a successful v12 validation with a directory containing just one correctly named wheel, even though a release should contain every supported Python/platform/ABI variant. That can allow a partial TestPyPI/PyPI release after an artifact is missing, deleted, or expired. Please validate the complete expected wheel matrix (or exact expected artifact set from the build plan) before upload and add a partial-matrix rejection test.

else
echo "error: cannot find ci/versions.yml or ci/versions.json" >&2
exit 1
resolved_bindings_package=$(cd .ci-control && python3 -m ci.tools.bindings_config \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — Medium: cuda-core-v* releases reach this fallback because release.yml does not pass bindings-package for cuda-core and the branch above recognizes only bare v* tags. This selects current control-plane bindings metadata instead of the metadata in the release tag. A delayed or historical core release can therefore request a newer bindings artifact that its tagged CI run never built (for example, cuda-core v1.0.0 used CUDA 13.2.1 while current control uses 13.3.0). Please resolve the bindings dependency from the checked-out release tag registry for component-prefixed releases too.

Comment thread ci/tools/bindings_config.py Outdated
if not release_source_root.is_dir():
raise BindingsConfigError(f"release source root is not a directory: {release_source_root}")

tagged_config_path = release_source_root / "ci" / "versions.yml"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — Medium: real v12.9.1–v12.9.7 and v13.0.x tags store this metadata in ci/versions.json, not ci/versions.yml. Treating the YAML as absent sends those tags to the legacy fallback, which derives the line from today's control config; that only works while today's pins happen to match. Please probe and parse both legacy filenames from the tag tree before using control metadata, with a fixture shaped like an actual old tag.

Comment thread cuda_bindings/pyproject.toml Outdated
tag_regex = "^(?P<version>v\\d+\\.\\d+\\.\\d+(?:[ab]\\d+)?)"
git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v*[0-9]*"]
# Accept the project's prerelease, post-release, and development suffixes.
tag_regex = "^(?P<version>v13\\.3\\.\\d+(?:(?:a|b|rc)\\d+)?(?:\\.post\\d+)?(?:\\.dev\\d+)?)$"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — Medium: accepting .devN here makes a development tag valid for tag CI, but cuda_bindings/docs/build_docs.sh truncates the version to the first three dot components. As a result, v13.3.0.dev1 deploys production docs under 13.3.0, potentially occupying or overwriting the stable path even though release-wheel validation rejects development releases. Please retain the dev suffix in the docs path or prevent production docs deployment for dev tags.

Comment thread cuda_bindings_12/docs/source/install.md Outdated
## Installing from PyPI

```console
$ pip install -U cuda-python

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — Medium: this is the CUDA 12 maintenance documentation, but the unpinned metapackage command now resolves the current v13 line. The [all] and Conda examples have the same issue, and the copied README/DESCRIPTION links target /cuda-bindings/latest/ (v13). Please pin the maintenance line (for example, cuda-python~=12.9.0 or cuda-bindings~=12.9.0) and direct readers to the versioned 12.9 docs or version selector.

cuda_devices = get_cuda_device_names()
nvml_devices = get_nvml_device_names()

assert cuda_devices == nvml_devices, "CUDA and NVML device lists do not match"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — Medium: this copied v12 test omits the fixes already present in the current-root test for Orin/Thor and CUDA_VISIBLE_DEVICES. On a multi-GPU host with CUDA_VISIBLE_DEVICES=0, CUDA reports one visible GPU while NVML reports all GPUs, so this exact equality fails. Please port the current-root handling to the v12 copy, consistent with the cross-root handwritten-fix policy in cuda_bindings_12/MAINTENANCE.md.

git(tmp_path, "init")
git(tmp_path, "config", "user.name", "CUDA Python CI")
git(tmp_path, "config", "user.email", "cuda-python@nvidia.com")
git(tmp_path, "config", "commit.gpgsign", "false")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — Low: this temporary repository disables commit signing but still inherits global tag signing. With tag.gpgSign=true, the plain git tag calls fail or prompt (I reproduced 14 failures); with tag signing disabled, the same suite passes. Please set tag.gpgSign=false here and in the analogous temp-repo helper in test_lookup_run_id.py, or create tags with --no-sign, so the tests are hermetic.

Comment thread .lycheeignore
@@ -0,0 +1,7 @@
# REMOVE THIS FILE BEFORE MERGING THIS PR

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review — Pre-merge cleanup: this temporary file is still tracked and suppresses link checks for paths that do not yet exist on main. Please remove it before the final CI run and merge, as its header and the PR checklist require.

# run-id blank so automatic tag-run discovery, artifact filtering, and version
# validation are exercised.

on:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it hard to answer this from reading alone:

In this new model, 13.x and 12.x will be released from the same commit, right? And the same commit would be tagged with both v13.4.2 and v12.9.9. That's all supported by git, but how does this infrastructure handle that correctly, since it would have to dispatch based on the commit.

@rwgk
rwgk marked this pull request as draft September 11, 2026 18:31
@rwgk

rwgk commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

I converted to Draft to have direct control over triggering the CI.

# Conflicts:
#	.github/workflows/backport.yml
#	.github/workflows/build-wheel.yml
#	.github/workflows/ci-pixi-source-test.yml
#	.github/workflows/ci.yml
#	.github/workflows/coverage.yml
#	.github/workflows/release-upload.yml
#	.github/workflows/release.yml
#	.github/workflows/test-wheel-linux.yml
#	.github/workflows/test-wheel-windows.yml
#	ci/tools/env-vars
#	ci/tools/run-tests
#	ci/versions.yml
#	cuda_python/setup.py
@rwgk

rwgk commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 98c987a

@rwgk

rwgk commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 8e1822c

@rwgk

rwgk commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test cb95f0d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD CI/CD infrastructure cuda.bindings Everything related to the cuda.bindings module cuda.core Everything related to the cuda.core module cuda.pathfinder Everything related to the cuda.pathfinder module enhancement Any code-related improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Revisit cuda-bindings branching strategy

3 participants