You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The pain comes from having multiple long-lived branches, not from how the source is laid out. Move to a single branch with per-major directories and most of it goes away. Build-time macros (proposal #2) is a nicer end-state but the wrong first step.
What I found
The .pyx.in/.pxd.in files are not hand-written templates. They're generated, with a banner: "automatically generated… Do not modify it directly."
What differs between main (13.x) and 12.9.x is real CUDA 12-vs-13 API surface (new enums, structs, renumbered values) — emitted by the generator. ~600 lines in cydriver.pxd.in alone.
All generated output (56 .pyx/.pxd/.cpp + 18 .in) is git-tracked. The build already regenerates from the installed toolkit's headers, so the committed copies are effectively cached.
So: every pain you listed (#965, #1058, #809, #1120) exists because there are multiple branches. Collapse to one branch and all four go away — independent of dirs-vs-macros.
The two proposals
#1 — per-major directories on main (recommended first step)
One branch. Each CUDA major is a generated directory: gen/v12/, gen/v13/. New major = copy + regenerate. EOL = delete the directory. No branch is ever cut or retired.
Hand-written code (__init__.py, _lib/, build_hooks.py, ci/, .github/, docs) lives once. A fix is one PR — no backport.
Build/release picks the major from the toolkit headers (already keys off CUDA_PATH) and maps gen/vNN/ onto the cuda.bindings package. Same package name, major-distinct versions, one installed at a time — no wheel collisions.
#2 — single source + build-time macros (better end-state, later)
The deltas are real API differences the generator owns. A single tree with {{if cuda_major==13}} means either hand-editing thousands of generated symbols, or teaching the upstream generator to emit version-gated code. That's a generator project, not a repo change. (CuPy does this — worth a look once/if the generator supports it.)
Migration path
Make the build's major-selection explicit so a gen/vNN/ dir can be picked deterministically.
Move 13.x into gen/v13/; verify wheels are byte-equivalent to today.
TL;DR: CUDA 12 and CUDA 13 now share CI/release infrastructure on main, replacing two separately maintained branch implementations. Counting .github/ and ci/ across both old branches, the combined maintenance surface decreased:
Measure
Before: main + 12.9.x
After: shared main
Reduction
All tracked text lines
22,737
20,819
1,918 (8.4%)
Code/configuration lines, including tests
15,488
14,211
1,277 (8.2%)
Production/configuration code decreased 14.9% (13,196 → 11,229); CI-tool test code grew by 690 lines (2,292 → 2,982). These counts describe maintenance surface. The architectural benefit is removing routine cross-branch CI/release synchronization and its opportunities for divergence.
Overview. The migration stack and cleanup in #3434 and #3456 put the CUDA-major choice into a shared registry and shared workflows. Builds, tests, releases and documentation can now be maintained together. Both bindings majors remain available; retiring routine 12.9.x maintenance preserves the branch's Git history.
The tradeoff is explicit: the shared implementation is larger than old main alone—18,364 → 20,819 total text lines—while the combined two-branch footprint is smaller. The comparison also includes concurrent infrastructure changes; it does not isolate the migration's causal effect on every line.
What changed, what remains, and the detailed breakdown
Maintenance responsibilities reduced
One CI/release implementation handles both bindings majors. Core and metapackage testing can use the same run's CUDA 12 builds, replacing cross-branch artifact coordination. Shared behavior.
Future infrastructure changes can be reviewed and validated in one maintained branch, instead of reconciling two independently evolving .github/ and ci/ trees.
Remaining obligations
cuda_bindings_12/ and cuda_bindings_13/ remain separate package roots. Applicable handwritten fixes still require checking both; generated bindings still require each major's own toolkit inputs. These package sources are outside this LOC comparison. Maintenance guidance.
Current release workflows expect the registry layout. Releases of older, pre-registry layouts use compatible historical workflow refs, retaining access to old behavior without carrying its parser in the current workflows. Release policy.
Full CI, nightly, coverage and eight release rehearsals were completed for the cleanup. The validation summary records the scope and limits, including the exclusion of live release publication.
Production, tests, documentation and assets
Category
All text lines before
After
Code/config lines before
After
Production/configuration
17,265
14,602
13,196
11,229
CI-tool tests
3,071
3,939
2,292
2,982
Documentation/diagrams
789
1,132
Excluded
Excluded
Certificate/data assets
1,612
1,146
Excluded
Excluded
Total
22,737
20,819
15,488
14,211
The production/configuration reduction of 1,967 code lines and test increase of 690 yield the net reduction of 1,277. The physical-line total also includes the removal of one 466-line copy of certificate data; that asset contributes nothing to the code/configuration measure.
By directory
Directory
All text lines before → after
Code/config lines before → after
.github/
12,811 → 9,854
9,155 → 7,113
ci/
9,926 → 10,965
6,333 → 7,098
Shared Python helpers and their tests grew as workflow handling was consolidated. Directory subtotals show this redistribution; the headline comparison includes both directories and both old branches.
How different were the old trees?
Matching files strictly by relative path:
Relationship between old main and 12.9.x
Paths
Present in both, byte-identical
3
Present in both, different contents
31
Present only in main
92
Present only in 12.9.x
7
The two old trees contained 126 + 41 = 167 file copies; the shared tree contains 144. Different contents include intentional CUDA-major differences, and path-only comparisons do not identify renames or quantify harmful drift. These figures show the comparison/synchronization surface that routine branch maintenance previously involved.
Full details: fixed snapshots, counting rules and reproducibility
Snapshots
The before boundary is immediately before #3314, the first PR of the implemented migration, merged on 2026-11-06 at 16:37:20 UTC. This includes its preparatory work in the measured change. It is not the earlier historical creation of the cuda_bindings_12/ directory.
The 12.9.x snapshot is the result of #3306; its next update (#3429) occurred November 10 and did not change either counted directory. The after snapshot is fixed to the reviewed #3456 revision, rather than a moving main reference.
Counting rules
Scope is every tracked regular text file below .github/ and ci/. Every file copy in each old branch counts, even if byte-identical. All three snapshots contain only regular UTF-8 text files within this scope; there are no binary files, symlinks or submodules to resolve.
All text lines means LF-delimited physical lines, including blank lines, comments, documentation and text assets; an unterminated final line counts once.
Code/configuration means cloc1.98's code field: nonblank lines after its language-specific comment filtering, including tests. Exclude *.md, *.txt, *.mmd, *.pem, and the vendored certificate disclosure .github/actions/upgrade-ca-certificates/impacted-intermediates.json. Configuration YAML/JSON remains included, including issue forms and problem matchers.
CI-tool tests are code/configuration files under ci/tools/tests/, plus ci/tools/smoke_test_lychee_precommit.py. Operational test runners, matrix selectors and test workflows remain production/configuration infrastructure.
Use --skip-uniqueness so cloc counts duplicate copies; --config /dev/null avoids user-specific options. Map .env to shell syntax. Count the old one-line .github/BACKPORT_BRANCH setting explicitly with shell filters. Every eligible file was accounted for; no unknown file types were silently omitted.
cloc provides a repeatable syntax-based approximation. Embedded scripts and prose-valued YAML prevent an exact semantic “functional LOC” interpretation. LOC also does not measure review difficulty, runtime, job count or developer hours.
The broad scope includes copies of default-branch-scoped GitHub metadata. A copied file on 12.9.x does not establish that a second instance executed. Retiring that branch removes its routine maintenance surface; historical copies remain in Git.
Two independently implemented counts agreed on the headline numbers and directory/test subtotals. The standalone reproduction below was also run successfully against the fixed objects.
Reproduce the counts from Git objects
Run this Python script from a clone of NVIDIA/cuda-python containing the three commits, with Python 3 and cloc 1.98 installed. It reads fixed Git objects and extracts temporary data; it does not inspect or change the checkout, branches, or tags.
The two before results are added, and the reduction percentage is (before_total - after) / before_total * 100.
Per-file counts (155 paths across the three snapshots)
Each cell is physical text lines / code-config lines. A single - means the path is absent; P/- means the file contributes P physical lines and is excluded from code/configuration. Zero code lines remain 0.
Activity
rparolin commentedon Jun 9, 2026
TL;DR
The pain comes from having multiple long-lived branches, not from how the source is laid out. Move to a single branch with per-major directories and most of it goes away. Build-time macros (proposal #2) is a nicer end-state but the wrong first step.
What I found
.pyx.in/.pxd.infiles are not hand-written templates. They're generated, with a banner: "automatically generated… Do not modify it directly."main(13.x) and12.9.xis real CUDA 12-vs-13 API surface (new enums, structs, renumbered values) — emitted by the generator. ~600 lines incydriver.pxd.inalone..pyx/.pxd/.cpp+ 18.in) is git-tracked. The build already regenerates from the installed toolkit's headers, so the committed copies are effectively cached.So: every pain you listed (#965, #1058, #809, #1120) exists because there are multiple branches. Collapse to one branch and all four go away — independent of dirs-vs-macros.
The two proposals
#1 — per-major directories on
main(recommended first step)gen/v12/,gen/v13/. New major = copy + regenerate. EOL = delete the directory. No branch is ever cut or retired.__init__.py,_lib/,build_hooks.py,ci/,.github/, docs) lives once. A fix is one PR — no backport.CUDA_PATH) and mapsgen/vNN/onto thecuda.bindingspackage. Same package name, major-distinct versions, one installed at a time — no wheel collisions.#2 — single source + build-time macros (better end-state, later)
The deltas are real API differences the generator owns. A single tree with
{{if cuda_major==13}}means either hand-editing thousands of generated symbols, or teaching the upstream generator to emit version-gated code. That's a generator project, not a repo change. (CuPy does this — worth a look once/if the generator supports it.)Migration path
gen/vNN/dir can be picked deterministically.13.xintogen/v13/; verify wheels are byte-equivalent to today.12.9.xintogen/v12/and cut a patch release frommain— proves release works without a branch (closes the BUG:./ci/tools/download-wheelsdoes not exist in the backport branch #1120 class).gen/vs. regenerate in CI. If regenerating, validate generator availability in CI.11.8.x/12.2.x/12.9.xas their lines move to directories or hit EOL.Bottom line: single-branch is the decision that matters. Start with per-major directories; treat build-time macros as a later optimization.
rwgk commentedon Nov 13, 2026
— codex GPT-6 on behalf of Ralf —
CI/release consolidation: before and after
TL;DR: CUDA 12 and CUDA 13 now share CI/release infrastructure on
main, replacing two separately maintained branch implementations. Counting.github/andci/across both old branches, the combined maintenance surface decreased:main+12.9.xmainProduction/configuration code decreased 14.9% (13,196 → 11,229); CI-tool test code grew by 690 lines (2,292 → 2,982). These counts describe maintenance surface. The architectural benefit is removing routine cross-branch CI/release synchronization and its opportunities for divergence.
Overview. The migration stack and cleanup in #3434 and #3456 put the CUDA-major choice into a shared registry and shared workflows. Builds, tests, releases and documentation can now be maintained together. Both bindings majors remain available; retiring routine
12.9.xmaintenance preserves the branch's Git history.The tradeoff is explicit: the shared implementation is larger than old
mainalone—18,364 → 20,819 total text lines—while the combined two-branch footprint is smaller. The comparison also includes concurrent infrastructure changes; it does not isolate the migration's causal effect on every line.What changed, what remains, and the detailed breakdown
Maintenance responsibilities reduced
.github/andci/trees.Remaining obligations
cuda_bindings_12/andcuda_bindings_13/remain separate package roots. Applicable handwritten fixes still require checking both; generated bindings still require each major's own toolkit inputs. These package sources are outside this LOC comparison. Maintenance guidance.Production, tests, documentation and assets
The production/configuration reduction of 1,967 code lines and test increase of 690 yield the net reduction of 1,277. The physical-line total also includes the removal of one 466-line copy of certificate data; that asset contributes nothing to the code/configuration measure.
By directory
.github/ci/Shared Python helpers and their tests grew as workflow handling was consolidated. Directory subtotals show this redistribution; the headline comparison includes both directories and both old branches.
How different were the old trees?
Matching files strictly by relative path:
mainand12.9.xmain12.9.xThe two old trees contained 126 + 41 = 167 file copies; the shared tree contains 144. Different contents include intentional CUDA-major differences, and path-only comparisons do not identify renames or quantify harmful drift. These figures show the comparison/synchronization surface that routine branch maintenance previously involved.
Full details: fixed snapshots, counting rules and reproducibility
Snapshots
The before boundary is immediately before #3314, the first PR of the implemented migration, merged on 2026-11-06 at 16:37:20 UTC. This includes its preparatory work in the measured change. It is not the earlier historical creation of the
cuda_bindings_12/directory.mainceb91b50b91293231c13494ea414caa8d115a75912.9.x, at the same cutoff06e182cee03624c6ebdf3b865eaaefa2011976618f01de551993ab0d9102002d97a9c113776bdd39The
12.9.xsnapshot is the result of #3306; its next update (#3429) occurred November 10 and did not change either counted directory. The after snapshot is fixed to the reviewed #3456 revision, rather than a movingmainreference.Counting rules
.github/andci/. Every file copy in each old branch counts, even if byte-identical. All three snapshots contain only regular UTF-8 text files within this scope; there are no binary files, symlinks or submodules to resolve.cloc1.98'scodefield: nonblank lines after its language-specific comment filtering, including tests. Exclude*.md,*.txt,*.mmd,*.pem, and the vendored certificate disclosure.github/actions/upgrade-ca-certificates/impacted-intermediates.json. Configuration YAML/JSON remains included, including issue forms and problem matchers.ci/tools/tests/, plusci/tools/smoke_test_lychee_precommit.py. Operational test runners, matrix selectors and test workflows remain production/configuration infrastructure.--skip-uniquenesssocloccounts duplicate copies;--config /dev/nullavoids user-specific options. Map.envto shell syntax. Count the old one-line.github/BACKPORT_BRANCHsetting explicitly with shell filters. Every eligible file was accounted for; no unknown file types were silently omitted.clocprovides a repeatable syntax-based approximation. Embedded scripts and prose-valued YAML prevent an exact semantic “functional LOC” interpretation. LOC also does not measure review difficulty, runtime, job count or developer hours.12.9.xdoes not establish that a second instance executed. Retiring that branch removes its routine maintenance surface; historical copies remain in Git.Two independently implemented counts agreed on the headline numbers and directory/test subtotals. The standalone reproduction below was also run successfully against the fixed objects.
Reproduce the counts from Git objects
Run this Python script from a clone of
NVIDIA/cuda-pythoncontaining the three commits, with Python 3 andcloc1.98 installed. It reads fixed Git objects and extracts temporary data; it does not inspect or change the checkout, branches, or tags.The two before results are added, and the reduction percentage is
(before_total - after) / before_total * 100.Per-file counts (155 paths across the three snapshots)
Each cell is physical text lines / code-config lines. A single
-means the path is absent;P/-means the file contributes P physical lines and is excluded from code/configuration. Zero code lines remain0.mainP/C12.9.xP/C.github/BACKPORT_BRANCH.github/CODERABBIT.md.github/ISSUE_TEMPLATE/bug_report.yml.github/ISSUE_TEMPLATE/config.yml.github/ISSUE_TEMPLATE/doc_request.yml.github/ISSUE_TEMPLATE/feature_request.yml.github/ISSUE_TEMPLATE/release_checklist.yml.github/PULL_REQUEST_TEMPLATE.md.github/RELEASE-bindings.md.github/RELEASE-core.md.github/actionlint.yaml.github/actions/doc_preview/action.yml.github/actions/download-artifact-retry/action.yml.github/actions/fetch_ctk/action.yml.github/actions/get_pr_number/action.yml.github/actions/griffe-api-check/action.yml.github/actions/install-sccache/action.yml.github/actions/install_unix_deps/action.yml.github/actions/install_windows_deps/action.yml.github/actions/lychee-links/action.yml.github/actions/sccache-summary/action.yml.github/actions/setup-hardened-python/action.yml.github/actions/setup-pixi/action.yml.github/actions/upgrade-ca-certificates/action.yml.github/actions/upgrade-ca-certificates/emudhra_intermediates.ps1.github/actions/upgrade-ca-certificates/impacted-intermediates.json.github/copy-pr-bot.yaml.github/dependabot.yml.github/labeler.yml.github/matchers/compiler.json.github/matchers/compute-sanitizer.json.github/workflows/backport.yml.github/workflows/bandit.yml.github/workflows/build-docs.yml.github/workflows/build-wheel.yml.github/workflows/ci-nightly.yml.github/workflows/ci-pixi-lockfile-freshness-check.yml.github/workflows/ci-pixi-lockfile-refresh.yml.github/workflows/ci-pixi-source-test.yml.github/workflows/ci-workflow-health.yml.github/workflows/ci.yml.github/workflows/cleanup-pr-previews.yml.github/workflows/codeql.yml.github/workflows/coverage.yml.github/workflows/guess_latest.sh.github/workflows/install_gpu_driver.ps1.github/workflows/lychee.yml.github/workflows/pr-auto-label.yml.github/workflows/pr-metadata-check.yml.github/workflows/pre-commit.yml.github/workflows/release-upload.yml.github/workflows/release.yml.github/workflows/runner-trust-probe.yml.github/workflows/security-suite.yml.github/workflows/status-check.yml.github/workflows/test-sdist-linux.yml.github/workflows/test-sdist-windows.yml.github/workflows/test-wheel-linux.yml.github/workflows/test-wheel-windows.yml.github/workflows/triage-author-reply.yml.github/workflows/triage-close-awaiting-response.yml.github/workflows/triagelabel.yml.github/workflows/woa-main-ci.ymlci/README.mdci/__init__.pyci/ci-pipeline.mmdci/cleanup-pr-previewsci/pixi-version.envci/pyproject.tomlci/test-matrix.jsonci/test-matrix.ymlci/tools/__init__.pyci/tools/bindings_config.pyci/tools/build_docs_for_link_check.shci/tools/check_mempool_hygiene.pyci/tools/check_pathfinder_ctk_libs.pyci/tools/check_pixi_cuda_version.pyci/tools/check_release_notes.pyci/tools/checkout_source_links.pyci/tools/ci_artifacts.pyci/tools/ci_env.pyci/tools/classify_pixi_lockfile_freshness.pyci/tools/compute_ci_plan.pyci/tools/configure_driver_mode.ps1ci/tools/cuda_core_bindings_floor.pyci/tools/download-wheelsci/tools/env-varsci/tools/fetch_ctk_redistrib.pyci/tools/fold_pytest_sections.pyci/tools/guess_latest.shci/tools/install_gpu_driver.ps1ci/tools/install_gpu_driver.shci/tools/list_pixi_workspaces.pyci/tools/lookup-run-idci/tools/merge_cuda_core_wheels.pyci/tools/retry_lychee.pyci/tools/run-testsci/tools/run_lychee.pyci/tools/run_lychee_precommit.pyci/tools/runner_trust_probe.ps1ci/tools/runner_trust_probe.shci/tools/runner_trust_probe_chains/README.txtci/tools/runner_trust_probe_chains/dv-g3b-aaa-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/dv-g3b-aaa-noct-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/dv-g3b-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/dv-g3b-noct-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/dv-g3c-aaa-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/dv-g3c-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ev-g3b-aaa-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ev-g3b-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ev-g3c-aaa-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ev-g3c-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ov-g3b-aaa-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ov-g3b-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ov-g3c-aaa-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_chains/ov-g3c-emudhra.we-have-a-problem.com.pemci/tools/runner_trust_probe_issuers/README.txtci/tools/runner_trust_probe_issuers/emsign-root-cs-ca-g3-cross.pemci/tools/runner_trust_probe_node.jsci/tools/select-woa-batchci/tools/select_pixi_packages.pyci/tools/select_test_matrix.pyci/tools/setup-sanitizerci/tools/smoke_test_lychee_precommit.pyci/tools/tests/AGENTS.mdci/tools/tests/test_bindings_config.pyci/tools/tests/test_bindings_metadata.pyci/tools/tests/test_bindings_release_compat.pyci/tools/tests/test_build_docs_for_link_check.pyci/tools/tests/test_check_mempool_hygiene.pyci/tools/tests/test_check_release_notes.pyci/tools/tests/test_check_spdx.pyci/tools/tests/test_checkout_source_links.pyci/tools/tests/test_classify_pixi_lockfile_freshness.pyci/tools/tests/test_compute_ci_plan.pyci/tools/tests/test_cuda_core_bindings_floor.pyci/tools/tests/test_cuda_python_setup.pyci/tools/tests/test_download_wheels.pyci/tools/tests/test_fetch_ctk_redistrib.pyci/tools/tests/test_fold_pytest_sections.pyci/tools/tests/test_list_pixi_workspaces.pyci/tools/tests/test_merge_cuda_core_wheels.pyci/tools/tests/test_pixi_lockfile_freshness_workflow.pyci/tools/tests/test_release_publish_security.pyci/tools/tests/test_retry_lychee.pyci/tools/tests/test_run_lychee.pyci/tools/tests/test_run_lychee_precommit.pyci/tools/tests/test_select_pixi_packages.pyci/tools/tests/test_select_test_matrix.pyci/tools/tests/test_test_matrix_driver_hashes.pyci/tools/tests/test_validate_release_wheels.pyci/tools/validate-release-wheelsci/tools/validate_release_wheels.pyci/versions.jsonci/versions.yml