diff --git a/.github/RELEASE-bindings.md b/.github/RELEASE-bindings.md index 5e3373effb4..5f84d8f76d1 100644 --- a/.github/RELEASE-bindings.md +++ b/.github/RELEASE-bindings.md @@ -36,15 +36,20 @@ boundary, not a separate long-term home for shared infrastructure. ## Move fixes between integration and release branches Prefer a reviewed fix on `main`, then backport it to the selected release -branch in a separate PR. Inspect the diff, request CI using the normal PR -approval procedure, and review it before merging. Conflicts and toolkit-specific -generated changes require an explicitly reviewed adaptation. +branch. Run **CI: Backport a merged PR to a release branch** from the Actions +tab on `main`, supplying the merged PR number and one existing target branch. +The workflow opens a PR; it does not merge or release the result. Inspect the +diff, request CI using the normal PR approval procedure, and review it before +merging. Workflow-file changes require a manual backport with appropriate +credentials. Conflicts and toolkit-specific generated changes require an +explicitly reviewed adaptation. If urgency requires a fix on the release branch first, link a follow-up PR reconciling the fix back into `main` before closing the release checklist. In either direction, assess both bindings roots and record why a change applies to one or both. Regenerate affected generated files from the intended toolkit and cybind inputs instead of blindly copying generated output between majors. +See the [shared maintenance policy](../ci/README.md#maintaining-both-bindings-roots). ## Validate and publish the selected line @@ -55,8 +60,8 @@ cybind inputs instead of blindly copying generated output between majors. branch. Normal development CI includes dependent packages and may cover both majors. Resolve failures in affected code before selecting the tag. 3. Create the approved immutable `vX.Y.Z` tag at the selected source commit. - Tag-triggered CI builds both bindings lines and their consumers. The release - workflow selects only the requested line's bindings or metapackage artifacts. + Tag-triggered CI selects that line's bindings and metapackage, validates them + with the published Pathfinder dependency, and builds release artifacts. The tag does not have to be on `main`. Wait for this exact tag's CI to pass. 4. Run **CI: Release** with `release-action=dry-run`, the tag, and `component=cuda-bindings`; repeat for `cuda-python`. Leave `run-id` blank diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7a87fb3ac04..43c274686bd 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -55,25 +55,3 @@ updates: time: "09:00" timezone: "America/Los_Angeles" open-pull-requests-limit: 1 - - # GitHub Actions updates targeting the 12.9.x branch - - package-ecosystem: github-actions - directories: - - / - - /.github/actions/* - target-branch: "12.9.x" # keep in sync with backport_branch in ci/versions.yml - schedule: - interval: "monthly" - time: "09:00" - timezone: "America/Los_Angeles" - - # Keep churn down: only one open PR from this ecosystem at a time - open-pull-requests-limit: 1 - - groups: - actions-monthly: - applies-to: version-updates - patterns: ["*"] - update-types: ["major", "minor", "patch"] - - labels: [CI/CD, dependencies] diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index cb64af94faf..304d368b971 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -2,75 +2,84 @@ # # SPDX-License-Identifier: Apache-2.0 -name: "CI: Backport the merged PR" +name: "CI: Backport a merged PR to a release branch" on: - pull_request_target: - types: [closed, labeled] - branches: - - main workflow_dispatch: inputs: backport-branch: - description: "Branch to backport commits onto" - required: false + description: "Existing release branch to receive the fix" + required: true type: string pull-request: - description: "PR to backport" + description: "Merged PR to backport" required: true type: number - permissions: - contents: write # so it can comment - pull-requests: write # so it can create pull requests + contents: write + pull-requests: write + +concurrency: + group: backport-${{ inputs.pull-request }}-${{ inputs.backport-branch }} + cancel-in-progress: false jobs: - backport-from-pr: - name: Backport directly from a pull request - if: ${{ github.repository_owner == 'nvidia' && - github.event.pull_request.merged == true && - contains( github.event.pull_request.labels.*.name, 'to-be-backported') - }} + backport: + if: github.repository == 'NVIDIA/cuda-python' runs-on: ubuntu-latest steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Checkout workflow source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} - - name: Load branch name - id: get-branch + - name: Validate source PR and target branch + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + BACKPORT_BRANCH: ${{ inputs.backport-branch }} + PR_NUMBER: ${{ inputs.pull-request }} run: | - OLD_BRANCH=$(yq '.backport_branch' ci/versions.yml) - echo "OLD_BRANCH=${OLD_BRANCH}" >> $GITHUB_ENV + set -euo pipefail + if [[ ! "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then + echo "::error::The source PR must be a positive integer." + exit 1 + fi + if ! git check-ref-format "refs/heads/$BACKPORT_BRANCH"; then + echo "::error::Provide one exact target branch name." + exit 1 + fi + if [[ "$BACKPORT_BRANCH" == "$DEFAULT_BRANCH" ]]; then + echo "::error::Choose a release branch, not the default branch." + exit 1 + fi + if ! git ls-remote --exit-code --heads origin "refs/heads/$BACKPORT_BRANCH"; then + echo "::error::The target branch must already exist in this repository." + exit 1 + fi + if [[ "$(gh pr view "$PR_NUMBER" --repo "$GH_REPO" --json state --jq .state)" != MERGED ]]; then + echo "::error::The source PR must already be merged in this repository." + exit 1 + fi - - name: Create backport pull requests + # Workflow-file changes need a manual backport because GITHUB_TOKEN + # cannot push them. Generated changes need target-specific regeneration. + - name: Create backport pull request + id: backport uses: korthout/backport-action@8560fb503c275d433c56f05a2f64850093baa9f7 # v4.7.0 with: + source_pr_number: ${{ inputs.pull-request }} + target_branches: ${{ inputs.backport-branch }} + # The explicit target must not be combined with source PR labels. + label_pattern: '' copy_assignees: true - copy_labels_pattern: true + copy_labels_pattern: '^(?!to-be-backported$|backport ).*' copy_requested_reviewers: true - target_branches: ${{ env.OLD_BRANCH }} - backport-to-branch: - name: Backport a specific PR against a specific branch - if: github.repository_owner == 'nvidia' && github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Load branch from environment name - if: inputs.backport-branch == null + - name: Require successful backport + if: steps.backport.outputs.was_successful != 'true' run: | - BACKPORT_BRANCH=$(yq '.backport_branch' ci/versions.yml) - echo "BACKPORT_BRANCH=${BRANCH}" >> $GITHUB_ENV - - - name: Load branch name from input - if: inputs.backport-branch != null - run: echo "BACKPORT_BRANCH=${{ inputs.backport-branch }}" >> $GITHUB_ENV - - - name: Create backport pull requests - uses: korthout/backport-action@8560fb503c275d433c56f05a2f64850093baa9f7 # v4.7.0 - with: - copy_assignees: true - copy_labels_pattern: true - copy_requested_reviewers: true - target_branches: ${{ env.BACKPORT_BRANCH }} - source_pr_number: ${{ inputs.pull-request }} + echo "::error::Backport failed; resolve the conflict or prepare the target-specific fix manually." + exit 1 diff --git a/.github/workflows/build-wheel.yml b/.github/workflows/build-wheel.yml index f5e32003c14..c35f3fee32d 100644 --- a/.github/workflows/build-wheel.yml +++ b/.github/workflows/build-wheel.yml @@ -510,14 +510,6 @@ jobs: path: cuda_python/dist/${{ env.CURRENT_CUDA_VARIANT }}/*.whl if-no-files-found: error - - name: Upload legacy CUDA13 metapackage alias - if: ${{ (env.BUILD_CUDA_PYTHON_CURRENT == 'true' || env.BASELINE_RUN_ID != '') && strategy.job-index == 0 && inputs.host-platform == 'linux-64' }} - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: cuda-python-wheel - path: cuda_python/dist/${{ env.CURRENT_CUDA_VARIANT }}/*.whl - if-no-files-found: error - - name: List the cuda-python artifacts directories if: ${{ (env.BUILD_CUDA_PYTHON == 'true' || env.BASELINE_RUN_ID != '') && strategy.job-index == 0 && inputs.host-platform == 'linux-64' }} run: | @@ -625,14 +617,6 @@ jobs: path: ${{ env.CUDA_CORE_CYTHON_TESTS_DIR }}/test_*${{ env.PY_EXT_SUFFIX }} if-no-files-found: error - - name: Upload legacy CUDA13 Core Cython alias - if: ${{ env.TEST_CORE_CURRENT == 'true' }} - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: ${{ env.CUDA_CORE_ARTIFACT_NAME }}-tests - path: ${{ env.CUDA_CORE_CYTHON_TESTS_DIR }}/test_*${{ env.PY_EXT_SUFFIX }} - if-no-files-found: error - # Note: This overwrites CUDA_PATH etc. - name: Set up maintenance mini CTK if: ${{ !inputs.single-cuda-major && (env.BUILD_BINDINGS_MAINTENANCE == 'true' || env.BUILD_CORE == 'true' || env.TEST_BINDINGS_MAINTENANCE == 'true' || env.TEST_CORE_MAINTENANCE == 'true') }} diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index f2026a379e3..1c6dfe7f5b9 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -74,26 +74,43 @@ jobs: outputs: RUN_ID: ${{ steps.find.outputs.run_id }} HEAD_SHA: ${{ steps.find.outputs.head_sha }} + BINDINGS_CONFIG: ${{ steps.find.outputs.bindings_config }} CUDA_BUILD_VER: ${{ steps.find.outputs.cuda_build_ver }} + LEGACY_BACKPORT_BRANCH: ${{ steps.find.outputs.legacy_backport_branch }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 + - name: Set up Python + uses: ./.github/actions/setup-hardened-python + with: + python-version: "3.11" + + - name: Install CI tool dependencies + run: python -m pip install -e ./ci + - name: Find latest successful CI run on main id: find env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_REPOSITORY: ${{ github.repository }} + REQUESTED_RUN_ID: ${{ inputs.run-id }} run: | - if [[ -n "${{ inputs.run-id }}" ]]; then - RUN_ID="${{ inputs.run-id }}" + set -euo pipefail + if [[ -n "$REQUESTED_RUN_ID" ]]; then + if [[ ! "$REQUESTED_RUN_ID" =~ ^[1-9][0-9]*$ ]]; then + echo "::error::run-id must be a positive integer" + exit 1 + fi + RUN_ID="$REQUESTED_RUN_ID" HEAD_SHA=$(gh run view "$RUN_ID" \ - -R "${{ github.repository }}" \ + -R "$RELEASE_REPOSITORY" \ --json headSha | jq -r '.headSha') else # lookup-run-id --branch --head-sha prints two lines: run_id then head_sha - OUTPUT=$(./ci/tools/lookup-run-id --branch main --head-sha "${{ github.repository }}" "CI") + OUTPUT=$(./ci/tools/lookup-run-id --branch main --head-sha "$RELEASE_REPOSITORY" "CI") RUN_ID=$(echo "$OUTPUT" | sed -n '1p') HEAD_SHA=$(echo "$OUTPUT" | sed -n '2p') fi @@ -103,20 +120,46 @@ jobs: exit 1 fi - CUDA_BUILD_VER=$(gh api \ - "repos/${{ github.repository }}/contents/ci/versions.yml?ref=$HEAD_SHA" \ - --jq '.content' \ - | base64 -d \ - | yq '.cuda.build.version') - - if [[ -z "$CUDA_BUILD_VER" || "$CUDA_BUILD_VER" == "null" ]]; then - echo "::error::Could not resolve CUDA build version from $HEAD_SHA" - exit 1 - fi + git fetch --depth=1 origin "$HEAD_SHA" + snapshot=$(mktemp -d) + trap 'rm -rf -- "$snapshot"' EXIT + git archive "$HEAD_SHA" ci/versions.yml | tar -x -C "$snapshot" + python - "$snapshot/ci/versions.yml" <<'PYTHON' + import os + import re + import subprocess + import sys + from pathlib import Path + + import yaml + + from ci.tools.bindings_config import load_config + + path = Path(sys.argv[1]) + raw = yaml.safe_load(path.read_text(encoding="utf-8")) + if "schema_version" not in raw: + # Preserve the artifact source's original test and artifact contract. + toolkit = raw["cuda"]["build"]["version"] + branch = raw["backport_branch"] + subprocess.run(["git", "check-ref-format", f"refs/heads/{branch}"], check=True) + config_json = "" + else: + if raw["schema_version"] != 2: + raise ValueError(f"unsupported nightly source schema: {raw['schema_version']!r}") + config = load_config(path) + toolkit = config.package_for_release_status("current").toolkit_version + branch = "" + config_json = config.to_json() + if re.fullmatch(r"[1-9][0-9]*\.[0-9]+\.[0-9]+", toolkit) is None: + raise ValueError(f"invalid nightly source build toolkit: {toolkit!r}") + with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output: + output.write(f"bindings_config={config_json}\n") + output.write(f"cuda_build_ver={toolkit}\n") + output.write(f"legacy_backport_branch={branch}\n") + PYTHON echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT - echo "cuda_build_ver=$CUDA_BUILD_VER" >> $GITHUB_OUTPUT # ── PyTorch interop tests ── @@ -132,7 +175,9 @@ jobs: with: build-type: nightly host-platform: linux-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-pytorch @@ -150,7 +195,9 @@ jobs: with: build-type: nightly host-platform: linux-aarch64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-pytorch @@ -168,7 +215,9 @@ jobs: with: build-type: nightly host-platform: win-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-pytorch @@ -188,7 +237,9 @@ jobs: with: build-type: nightly host-platform: linux-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda @@ -206,7 +257,9 @@ jobs: with: build-type: nightly host-platform: linux-aarch64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda @@ -224,7 +277,9 @@ jobs: with: build-type: nightly host-platform: win-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda @@ -244,7 +299,9 @@ jobs: with: build-type: nightly host-platform: linux-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda-mlir @@ -262,7 +319,9 @@ jobs: with: build-type: nightly host-platform: win-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda-mlir @@ -282,7 +341,9 @@ jobs: with: build-type: nightly host-platform: linux-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-cuda-core @@ -300,7 +361,9 @@ jobs: with: build-type: nightly host-platform: win-64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-cuda-core @@ -320,7 +383,9 @@ jobs: with: build-type: nightly host-platform: linux-aarch64 + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + legacy-backport-branch: ${{ needs.find-wheels.outputs.LEGACY_BACKPORT_BRANCH }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: standard diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f75ac200b06..1ec7711ec5a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -20,15 +20,29 @@ jobs: if: ${{ github.repository_owner == 'nvidia' }} runs-on: ubuntu-latest outputs: + BINDINGS_CONFIG: ${{ steps.get-vars.outputs.bindings_config }} + BINDINGS_ROOT: ${{ steps.get-vars.outputs.bindings_root }} CUDA_VER: ${{ steps.get-vars.outputs.cuda_ver }} steps: - name: Checkout ${{ github.event.repository.name }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Get CUDA version + - name: Set up Python + uses: ./.github/actions/setup-hardened-python + with: + python-version: "3.12" + + - name: Install CI tool dependencies + run: python3 -m pip install -e ./ci + + - name: Resolve current CUDA bindings package id: get-vars run: | - cuda_ver=$(yq '.cuda.build.version' ci/versions.yml) - echo "cuda_ver=$cuda_ver" >> $GITHUB_OUTPUT + current=$(python3 -m ci.tools.bindings_config --release-status current) + { + echo "bindings_config=$(python3 -m ci.tools.bindings_config)" + echo "bindings_root=$(jq -r '.package_root' <<< "$current")" + echo "cuda_ver=$(jq -r '.toolkit_version' <<< "$current")" + } >> "$GITHUB_OUTPUT" coverage-linux: name: Coverage (Linux) @@ -43,6 +57,7 @@ jobs: env: HOST_PLATFORM: "linux-64" ARCH: "x86_64" + BINDINGS_ROOT: ${{ needs.coverage-vars.outputs.BINDINGS_ROOT }} CUDA_VER: ${{ needs.coverage-vars.outputs.CUDA_VER }} # Our self-hosted runners require a container # TODO: use a different (nvidia?) container @@ -91,7 +106,12 @@ jobs: - name: Set environment variables env: + BINDINGS_CONFIG: ${{ needs.coverage-vars.outputs.BINDINGS_CONFIG }} BUILD_CUDA_VER: ${{ env.CUDA_VER }} + BINDINGS_SOURCE: local + BINDINGS_SOURCE_DIR: ${{ env.BINDINGS_ROOT }} + DEFAULT_BINDINGS_SOURCE_DIR: ${{ env.BINDINGS_ROOT }} + PATHFINDER_SOURCE: artifact CUDA_VER: ${{ env.CUDA_VER }} HOST_PLATFORM: ${{ env.HOST_PLATFORM }} LOCAL_CTK: ${{ env.LOCAL_CTK }} @@ -133,8 +153,8 @@ jobs: run: | export PIP_BUILD_CONSTRAINT="$(pwd)/wheel-constraints/cuda-bindings.txt" export PIP_CONSTRAINT="${PIP_BUILD_CONSTRAINT}" - .venv/bin/pip wheel -v --no-deps ./cuda_bindings_13 -w ./wheels/ - .venv/bin/pip install -v ./wheels/cuda_bindings*.whl --group ./cuda_bindings_13/pyproject.toml:test + .venv/bin/pip wheel -v --no-deps ./${BINDINGS_ROOT} -w ./wheels/ + .venv/bin/pip install -v ./wheels/cuda_bindings*.whl --group ./${BINDINGS_ROOT}/pyproject.toml:test - name: Constrain cuda-core to the local cuda-bindings wheel run: | @@ -179,7 +199,7 @@ jobs: continue-on-error: true run: | cd $INSTALL_ROOT - $REPO_ROOT/.venv/bin/pytest -v --cov=./cuda --cov-append --cov-context=test --cov-config=$REPO_ROOT/.coveragerc $REPO_ROOT/cuda_bindings_13/tests + $REPO_ROOT/.venv/bin/pytest -v --cov=./cuda --cov-append --cov-context=test --cov-config=$REPO_ROOT/.coveragerc $REPO_ROOT/${BINDINGS_ROOT}/tests - name: Run cuda.core tests continue-on-error: true @@ -223,6 +243,7 @@ jobs: env: HOST_PLATFORM: "win-64" CUDA_PYTHON_COVERAGE: "1" + BINDINGS_ROOT: ${{ needs.coverage-vars.outputs.BINDINGS_ROOT }} CUDA_VER: ${{ needs.coverage-vars.outputs.CUDA_VER }} steps: - name: Harden OS certificate trust (eMudhra/emSign CA stopgap) @@ -276,8 +297,7 @@ jobs: run: | export PIP_BUILD_CONSTRAINT="$(cygpath -w "$(pwd)/wheel-constraints/cuda-bindings.txt")" export PIP_CONSTRAINT="${PIP_BUILD_CONSTRAINT}" - cd cuda_bindings_13 - ../.venv/Scripts/pip wheel -v --no-deps . -w ../wheels/ + .venv/Scripts/pip wheel -v --no-deps "./${BINDINGS_ROOT}" -w ./wheels/ - name: Constrain cuda.core to the local cuda.bindings wheel run: | @@ -342,6 +362,7 @@ jobs: HOST_PLATFORM: "win-64" ARCH: "amd64" CUDA_PYTHON_COVERAGE: "1" + BINDINGS_ROOT: ${{ needs.coverage-vars.outputs.BINDINGS_ROOT }} CUDA_VER: ${{ needs.coverage-vars.outputs.CUDA_VER }} defaults: run: @@ -413,7 +434,7 @@ jobs: run: | .venv/Scripts/pip install coverage pytest-cov Cython .venv/Scripts/pip install --group ./cuda_pathfinder/pyproject.toml:test - .venv/Scripts/pip install --group ./cuda_bindings_13/pyproject.toml:test + .venv/Scripts/pip install --group ./${BINDINGS_ROOT}/pyproject.toml:test .venv/Scripts/pip install --group ./cuda_core/pyproject.toml:test - name: Get install root @@ -438,7 +459,7 @@ jobs: "$GITHUB_WORKSPACE/.venv/Scripts/python" -m pytest \ -v --cov=./cuda --cov-append --cov-context=test \ --cov-config="$GITHUB_WORKSPACE/.coveragerc" \ - "$GITHUB_WORKSPACE/cuda_bindings_13/tests" + "$GITHUB_WORKSPACE/${BINDINGS_ROOT}/tests" - name: Run cuda.core tests continue-on-error: true diff --git a/.github/workflows/test-wheel-linux.yml b/.github/workflows/test-wheel-linux.yml index fc191a0dff6..0697f14ddd1 100644 --- a/.github/workflows/test-wheel-linux.yml +++ b/.github/workflows/test-wheel-linux.yml @@ -21,6 +21,10 @@ on: description: Normalized registry; empty retains legacy snapshot routing. type: string default: '' + legacy-backport-branch: + description: Backport branch from the legacy artifact source; unused with bindings-config. + type: string + default: '' matrix_filter: type: string default: "." @@ -63,6 +67,7 @@ jobs: runs-on: ubuntu-latest env: BINDINGS_CONFIG: ${{ inputs.bindings-config }} + LEGACY_BACKPORT_BRANCH: ${{ inputs.legacy-backport-branch }} BUILD_TYPE: ${{ inputs.build-type }} WORKPLAN: ${{ inputs.workplan }} TEST_MODE: ${{ inputs.test-mode }} @@ -106,8 +111,7 @@ jobs: if [[ -z "$BINDINGS_CONFIG" ]]; then MATRIX=$(echo "$TEST_MATRIX" | jq -c '${{ inputs.matrix_filter }} | if any(.[]; .DRIVER != "latest" and .DRIVER != "earliest" and .FLAVOR == "wsl") then "Error: custom DRIVER is not supported with FLAVOR=wsl\n" | halt_error(1) else . end | map(. + {RUNNER_DRIVER: (if .DRIVER == "latest" or .DRIVER == "earliest" then .DRIVER else "latest" end)}) | if (. | length) > 0 then {include: .} else "Error: Empty matrix\n" | halt_error(1) end') - OLD_BRANCH=$(yq -r '.backport_branch' ci/versions.yml) - echo "OLD_BRANCH=${OLD_BRANCH}" >> "$GITHUB_OUTPUT" + echo "OLD_BRANCH=${LEGACY_BACKPORT_BRANCH}" >> "$GITHUB_OUTPUT" else # Keep the caller's free-form jq filter; shared Python code handles # source selection, test suites, runner routing, and coverage checks. @@ -261,6 +265,7 @@ jobs: && apt update \ && apt install gh -y + : "${OLD_BRANCH:?legacy-backport-branch is required for legacy prior-branch artifacts}" OLD_ARTIFACT_PATTERN="cuda-bindings-python${PYTHON_VERSION_FORMATTED}-cuda*-${{ inputs.host-platform }}-*[0-9a-f]" LOOKUP_ARGS=( --branch "${OLD_BRANCH}" diff --git a/.github/workflows/test-wheel-windows.yml b/.github/workflows/test-wheel-windows.yml index 490a76df97b..4e2ca98851d 100644 --- a/.github/workflows/test-wheel-windows.yml +++ b/.github/workflows/test-wheel-windows.yml @@ -21,6 +21,10 @@ on: description: Normalized registry; empty retains legacy snapshot routing. type: string default: '' + legacy-backport-branch: + description: Backport branch from the legacy artifact source; unused with bindings-config. + type: string + default: '' matrix_filter: type: string default: "." @@ -62,6 +66,7 @@ jobs: shell: bash --noprofile --norc -xeuo pipefail {0} env: BINDINGS_CONFIG: ${{ inputs.bindings-config }} + LEGACY_BACKPORT_BRANCH: ${{ inputs.legacy-backport-branch }} BUILD_TYPE: ${{ inputs.build-type }} WORKPLAN: ${{ inputs.workplan }} TEST_MODE: ${{ inputs.test-mode }} @@ -103,8 +108,7 @@ jobs: if [[ -z "$BINDINGS_CONFIG" ]]; then MATRIX=$(echo "$TEST_MATRIX" | jq -c '${{ inputs.matrix_filter }} | map(. + {RUNNER_DRIVER: (if .DRIVER == "latest" or .DRIVER == "earliest" then .DRIVER else "latest" end)}) | if (. | length) > 0 then {include: .} else "Error: Empty matrix\n" | halt_error(1) end') - OLD_BRANCH=$(yq -r '.backport_branch' ci/versions.yml) - echo "OLD_BRANCH=${OLD_BRANCH}" >> "$GITHUB_OUTPUT" + echo "OLD_BRANCH=${LEGACY_BACKPORT_BRANCH}" >> "$GITHUB_OUTPUT" else # Keep the caller's free-form jq filter; shared Python code handles # source selection, test suites, runner routing, and coverage checks. @@ -251,6 +255,7 @@ jobs: OLD_BRANCH: ${{ needs.compute-matrix.outputs.OLD_BRANCH }} shell: bash --noprofile --norc -xeuo pipefail {0} run: | + : "${OLD_BRANCH:?legacy-backport-branch is required for legacy prior-branch artifacts}" OLD_ARTIFACT_PATTERN="cuda-bindings-python${PYTHON_VERSION_FORMATTED}-cuda*-${{ inputs.host-platform }}-*[0-9a-f]" LOOKUP_ARGS=( --branch "${OLD_BRANCH}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 199987f145f..a95ebd2add3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,10 +50,10 @@ repos: verbose: true - id: check-cuda-core-bindings-floor - name: Check the cuda-bindings floor against ci/versions.yml and the docs + name: Check cuda-core bindings floor declarations and documentation entry: python ./toolshed/check_cuda_core_bindings_floor.py language: python - files: ^(cuda_core/pyproject\.toml|ci/versions\.yml|cuda_core/cuda/core/_bindings_floor\.py|cuda_core/docs/source/.*\.rst|toolshed/check_cuda_core_bindings_floor\.py)$ + files: ^(cuda_core/pyproject\.toml|cuda_core/cuda/core/_bindings_floor\.py|cuda_core/docs/source/.*\.rst|toolshed/check_cuda_core_bindings_floor\.py)$ pass_filenames: false - id: check-spdx @@ -78,6 +78,14 @@ repos: files: ^cuda_bindings_(?:12|13)/ types: [text] + - id: check-bindings-config + name: Validate bindings registry and package metadata + entry: python -m ci.tools.bindings_config --check-package-metadata + language: python + additional_dependencies: [packaging==26.0, pyyaml==6.0.3] + files: '^(ci/versions\.yml|ci/tools/bindings_config\.py|cuda_bindings_(?:12|13)/(pyproject|pixi)\.toml|cuda_python/(?:pyproject\.toml|setup\.py)|cuda_core/pyproject\.toml|cuda_core/cuda/core/_bindings_floor\.py)$' + pass_filenames: false + - id: check-pixi-cuda-version name: Check pixi cuda-version pins track ci/versions.yml entry: python -m ci.tools.check_pixi_cuda_version diff --git a/AGENTS.md b/AGENTS.md index 9a558f9dad9..b80f69f6be6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,35 @@ inputs, CI selection, or release routing. and identify the matching changes or explain concretely why none are needed. Reviewers must verify that assessment and flag omitted applicable changes; absence from the diff is not evidence that the other root is unaffected. -- Preserve byte-for-byte invariants for package-local copies of shared helpers. +- For transferring handwritten fixes between roots, use the path-relative + patch procedure in [CONTRIBUTING.md](CONTRIBUTING.md#transferring-fixes-between-bindings-roots), + then inspect and test both results. Regenerate generated bindings instead. +- For marked SYNCED blocks and package-local copies of shared helpers, also + enforce their byte-for-byte invariants. Semantic differences elsewhere do + not relax those requirements. + +## Synchronized code blocks + +Use matching `BEGIN SYNCED ` and `END SYNCED ` comments for +deliberately duplicated snippets whose contents must remain byte-for-byte +identical. The group name identifies all copies; the boundaries define the +shared snippet, not the surrounding file. + +- Before editing a marked block, search from the repository root for its exact + `BEGIN SYNCED ` marker, for example with `rg -n -F`. Read every copy + and update them together, including copies outside the current package. +- Compare the complete marked blocks after editing. Also check required + imports and surrounding assumptions, such as relative directory depth, + which may live outside the markers. +- During review, identify groups touched by the diff and compare every copy, + including files absent from the diff. Use the base revision too when markers + were removed or renamed. Report drift, missing boundaries, and newly copied + snippets that omit the markers as review findings. +- Preserve both markers when copying a snippet. If a copy must diverge, + explain why and explicitly revise the group membership; do not remove or + rename a marker merely to conceal drift. +- In the work or review summary, name the groups and files compared, or state + why verification could not be completed. # Git-derived version lifecycle diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df487ccfdc1..2f81ba1f261 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,11 +27,8 @@ reproducers, documentation improvements, and code changes are welcome. - [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco) - [CI infrastructure overview](#ci-infrastructure-overview) - [CI Pipeline Flow](#ci-pipeline-flow) - - [Pipeline Execution Details](#pipeline-execution-details) - - [Branch-specific Artifact Flow](#branch-specific-artifact-flow) - - [Main Branch](#main-branch) - - [Backport Branches](#backport-branches) - - [Key Infrastructure Details](#key-infrastructure-details) + - [CUDA-major Artifact Flow](#cuda-major-artifact-flow) + - [Release Branches](#release-branches) - [Code coverage](#code-coverage) @@ -91,6 +88,9 @@ build and test instructions for [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/install.html), [`cuda.bindings`](https://nvidia.github.io/cuda-python/cuda-bindings/latest/install.html), or [`cuda.pathfinder`](https://nvidia.github.io/cuda-python/cuda-pathfinder/latest/install.html). +The bindings `latest` guide covers CUDA 13. For CUDA 12 development, start +with [`cuda_bindings_12/`](cuda_bindings_12/). Both bindings roots require +Python 3.11 or newer; use a separate environment for each CUDA major. ### Updating your clone @@ -117,17 +117,29 @@ $ git fetch --tags https://github.com/NVIDIA/cuda-python.git Every package in this repository derives its version from git tags using [`setuptools-scm`](https://setuptools-scm.readthedocs.io/), so **how you clone determines whether you can build at all, and whether the version you build is -correct.** Each package matches its own tag prefix: +correct.** Packages select tags for their own release line: | Package | Tag pattern | | --- | --- | -| `cuda-bindings`, `cuda-python` | `v*` (e.g. `v13.4.2`) | +| `cuda-bindings` from `cuda_bindings_12/` | `v12.*` (e.g. `v12.9.10`) | +| `cuda-bindings` from `cuda_bindings_13/` | `v13.*` (e.g. `v13.4.2`) | +| `cuda-python` | The matching bindings selector, chosen explicitly with `CUDA_PYTHON_BUILD_MAJOR=12` or `13`, or inferred as described below | | `cuda-core` | `cuda-core-v*` (e.g. `cuda-core-v1.1.0`) | | `cuda-pathfinder` | `cuda-pathfinder-v*` (e.g. `cuda-pathfinder-v1.6.0`) | -Each package sets `root = ".."` in its `[tool.setuptools_scm]` table, meaning the -version is read from the *repository root* rather than the package directory. A -working build therefore needs all of the following: +Git source builds of `cuda-python` honor `CUDA_PYTHON_BUILD_MAJOR=12` or `13` +when set. Without it, an exact `v12.*` tag at `HEAD` selects CUDA 12 when no +`v13.*` tag points to that commit. Untagged commits and commits tagged for +both majors default to CUDA 13. +The resolved version determines dependencies: CUDA 12 depends on matching +bindings, while CUDA 13 also depends on Core and Pathfinder. A standalone +sdist retains its version and dependency major without requiring this variable. + +The selectors are recorded in the bindings roots' `pyproject.toml` files and +the metapackage's `pyproject.toml`; the metadata check keeps them aligned with +`ci/versions.yml`. All packages read versions from the *repository root*, +rather than their package directory. Builds from a Git checkout therefore +need all of the following: 1. **A real git clone.** Source zips and GitHub "Download ZIP" archives have no git metadata and the build fails outright. Tarballs produced by @@ -142,19 +154,22 @@ working build therefore needs all of the following: 4. **The CCCL submodule** when building `cuda.core`. That package compiles against the headers pinned in `cuda_core/third_party/cccl`. +Copying CUDA 12 sources into `main` does not make their historical branch tags +reachable. For maintenance preview builds without an appropriate reachable +tag, follow the [SCM bootstrap guidance](ci/README.md#cuda-bindings-package-root-registry) +in a disposable clone; do not create preview tags in a shared worktree repository. + ### Symptoms of a bad clone -Only case 3 below reports an error. The first two fail *silently*, producing a -wrong version that surfaces much later as a confusing dependency-resolution or -version-check failure: +Incomplete history can fail version detection or produce a plausible but +incorrect version that surfaces later as a dependency-resolution failure: -1. **No tags reachable.** The build succeeds and produces a version starting at - `0.1.dev`: a `--depth 1` clone yields `0.1.dev1+g0d22cb444`, a full clone made - with `--no-tags` yields `0.1.dev2114+g0d22cb444`. Installing `cuda-python` - built this way then fails, because its `install_requires` pins - `cuda-bindings` to that same bogus version. +1. **No matching tags reachable.** Depending on the package's SCM configuration, + the build can fail or produce a placeholder such as `0.1.dev...`. Fetching + tags is insufficient if the checkout lacks the ancestry connecting them to + the build. Check the selected release line, not just whether any tag exists. 2. **Stale tags** (a fork that has not fetched upstream in a while): you get a - plausible-looking but wrong version, e.g. `13.0.4.dev650+g0d22cb44` when the + plausible-looking but wrong version, e.g. `13.4.1.dev7+g0d22cb44` when the real latest tag is `v13.4.2`. Nothing warns you. Note there is no leading `v` — `setuptools-scm` strips the tag prefix. 3. **No git metadata** (source zip): the build fails with @@ -173,6 +188,25 @@ hyphens replaced by underscores: `..._FOR_CUDA_BINDINGS`, `..._FOR_CUDA_CORE`, genuinely cannot provide tags; it is not a substitute for a correct clone. +## Transferring fixes between bindings roots + +For a handwritten fix that applies to both majors, a path-relative patch can +transfer the source-root portion of a commit. From the repository root, with a +clean worktree, replace `COMMIT` with the reviewed source commit: + +```console +$ git format-patch -1 --stdout --relative=cuda_bindings_13 COMMIT -- cuda_bindings_13 > bindings-fix.patch +$ git am --directory=cuda_bindings_12 bindings-fix.patch +``` + +Inspect the patch before applying it. The path restriction excludes changes +outside the selected root; assess shared files separately. Resolve conflicts +and review the result against the destination API and toolkit, then run the +relevant tests. Swap the two roots for the reverse direction, and remove the +temporary patch when finished. These commands copy handwritten changes; +generated bindings must be regenerated from their generation source for each +toolkit. Record matching changes or a concrete reason they do not apply. + ## Development on Windows This section collects Windows-specific guidance for contributors working @@ -311,20 +345,10 @@ must stay in sync with their manifests and easy to review. Contributor expectations: -- If a PR changes a `pixi.toml`, update the corresponding `pixi.lock` in the - same PR. Regenerating one lockfile: - - ```console - $ pixi lock --manifest-path cuda_core - ``` - - Use `--manifest-path .` for the repository-root environment. Regenerate with - the pixi version pinned in `ci/pixi-version.env`: different pixi versions - write different canonical forms, such as the `pixi.lock` format version or - the generated platform alias names, and CI requires the committed bytes to - match what the pinned version produces. The pin must remain at least 0.71.0, - which supplies the content-addressed source-build cache used by CI and writes - the repository's version 7 lockfiles. +- If a PR changes dependencies in a `pixi.toml`, update and validate the + corresponding `pixi.lock` in the same PR. Use the Pixi version pinned in + `ci/pixi-version.env`; review the affected solution rather than performing + an unrelated dependency refresh. - If a PR does not intentionally change pixi dependencies or metadata, do not include unrelated lockfile churn. `pixi run` can refresh a stale lockfile implicitly; revert that noise unless the refresh is the point of the change. @@ -332,37 +356,14 @@ Contributor expectations: - Isolate large dependency refreshes from feature work when possible. Prefer a dedicated lockfile-only PR over mixing solver churn into an unrelated change. -CI enforces the contract: `pixi lock --check` fails when a committed lockfile is -stale, and pixi source-build jobs run with `PIXI_LOCKED=true` so they install from -the committed lock rather than updating it during the job. If either check -fails, regenerate and commit the affected lockfile. - -The freshness check additionally fails when the check itself rewrote a lockfile. -`pixi lock --check` accepts a lock whose solution is current but whose bytes are -not canonical for the pinned pixi version, quietly normalizing the file instead, -which leaves every later pixi run rewriting the committed lockfile. - -A scheduled workflow (`CI: pixi lockfile refresh`) runs -`pixi update --no-install` for every workspace in one job and opens one PR with -all changed lockfiles, so broad dependency churn is reviewed as maintenance -rather than landing inside unrelated feature work. The workflow can also be -dispatched manually. Both lockfile workflows resolve their workspace lists -through `ci/tools/list_pixi_workspaces.py`, which derives the inventory from the -committed manifests, so a newly added workspace is picked up without editing a -workflow. - -Refresh PRs use `GITHUB_TOKEN`. After one opens, a maintainer with write access -must first select **Approve workflows to run** in the merge box, then assign -themselves to the PR. Approval starts the queued `pull_request` runs; the -human-generated `assigned` event creates the required -**PR has assignee, labels, and milestone** `pull_request_target` check and gives -the PR a clear owner. - -A future GitHub App integration could trigger both `pull_request` and -`pull_request_target` workflows automatically. -See GitHub's -[token event documentation](https://docs.github.com/en/actions/concepts/security/github_token#when-github_token-triggers-workflow-runs) -for the current behavior. +Freshness enforcement and automated refreshes are temporarily suspended. +The freshness workflow succeeds with a warning; it does not verify +manifest/lock consistency. The refresh workflow has no schedule, and manual +dispatch reports the suspension without changing dependencies or opening a PR. +Source-build CI uses `PIXI_FROZEN=true` to consume committed lockfiles without +solving them. Necessary dependency changes require manual review and validation. +See the [maintenance pause and restoration criteria](ci/README.md#temporary-pixi-lockfile-maintenance-pause) +before attempting a lockfile repair or refresh. ## Secret Scanning @@ -442,36 +443,37 @@ The CUDA Python project uses a comprehensive CI pipeline that builds, tests, and The CI pipeline diagram is maintained as Mermaid source in [`ci/ci-pipeline.mmd`](ci/ci-pipeline.mmd). -### Pipeline Execution Details - -**Parallel Execution**: The CI pipeline leverages parallel execution to optimize build and test times: -- **Build Stage**: Different architectures/operating systems (linux-64, linux-aarch64, win-64) are built in parallel across their respective runners -- **Test Stage**: Different architectures/operating systems/CUDA versions are tested in parallel; documentation preview is also built in parallel with testing - -### Branch-specific Artifact Flow - -#### Main Branch -- **Build** → **Test** → **Documentation** → **Potential Release** -- Artifacts stored as `{component}-python{version}-{platform}-{sha}` -- Full test coverage across all platforms and CUDA versions -- **Artifact flow out**: `cuda-pathfinder` artifacts → backport branches - -#### Backport Branches -- **Build** → **Test** → **Backport PR Creation** -- Artifacts used for validation before creating backport pull requests -- Maintains compatibility with older CUDA versions -- **Artifact flow in**: `cuda-pathfinder` artifacts ← main branch -- **Artifact flow out**: older `cuda-bindings` artifacts → main branch - -### Key Infrastructure Details - -- **Self-hosted runners**: Used for Linux builds and GPU testing (more resources, faster builds) -- **GitHub-hosted runners**: Used for Windows builds and general tasks -- **Artifact retention**: 30 days for GitHub Artifacts (wheels, docs, tests) -- **Cache retention**: GitHub Cache for build dependencies and environments -- **Security**: All commits must be signed, untrusted code blocked -- **Parallel execution**: Matrix builds across Python versions and platforms -- **Component isolation**: Each component (core, bindings, pathfinder, python) can be built/released independently +The [CI workflow](.github/workflows/ci.yml) defines the supported platform, +Python, and CUDA matrices. Build and test jobs run in parallel across their +configured runners. Package selection and artifact validation are shared +through the [CI tools](ci/README.md). + +### CUDA-major Artifact Flow + +- CUDA 12.9 bindings live in `cuda_bindings_12/`; CUDA 13 bindings live in + `cuda_bindings_13/`. The registry assigns their maintenance and current roles. +- Development CI builds changed roots and affected dependents, reusing a + validated baseline for unaffected roots. Shared changes and scheduled runs + cover both CUDA majors. +- A bindings tag selects one line's bindings and metapackage. Its release + validation uses published Pathfinder and does not require a CUDA Core or + other-major release. +- Artifacts identify their Python version, toolkit, platform, and source SHA + where applicable. Release workflows require the exact tag's successful CI. + +### Release Branches + +`main` integrates changes for both supported majors. Short release branches +can stabilize a selected release or carry urgent fixes while development +continues. Return applicable fixes to `main` and assess both source roots. +The historical `12.9.x` branch stays frozen except for emergency fixes, +which must be reconciled into `main`. New main CI and releases do not fetch +package artifacts from that branch. + +The [bindings release guide](.github/RELEASE-bindings.md) covers release scope, +manual backport automation, source and control revisions, and validation. +The [CUDA 12 maintenance guide](cuda_bindings_12/MAINTENANCE.md) links the +shared regeneration and cross-root review policy. ## Code coverage diff --git a/README.md b/README.md index 016cb8a28ec..889c2fb31cf 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,12 @@ The list of available interfaces is: ## Bindings source roots -Current CUDA 13 bindings live in [`cuda_bindings_13/`](cuda_bindings_13/), and -CUDA 12 bindings sources have a separate -[developer environment](cuda_bindings_12/README.md). Both roots build the same +Both bindings release lines are maintained on `main`: current CUDA 13 sources +live in [`cuda_bindings_13/`](cuda_bindings_13/), and CUDA 12.9 maintenance +sources live in [`cuda_bindings_12/`](cuda_bindings_12/). Both build the same `cuda-bindings` distribution and `cuda.bindings` namespace; develop and test -them in separate environments. +them in separate Python environments. See the [contributor guide](CONTRIBUTING.md) +for source selection and changes across majors. ## License diff --git a/ci/README.md b/ci/README.md index 2fcd5b4ab1d..f38c83ba001 100644 --- a/ci/README.md +++ b/ci/README.md @@ -203,12 +203,12 @@ committed lockfiles to address the eMudhra/emSign CA incident; no general lockfile refresh was performed. Re-enable maintenance only after a released Pixi passes both upstream issue -reproducers and repeated byte-stable update/lock/check cycles across all six -workspaces: the repository root, `cuda_pathfinder`, `cuda_bindings_13`, `cuda_core`, -`benchmarks/cuda_bindings`, and `benchmarks/cuda_core`. Verify both `pixi update ---no-install` and `pixi lock`, followed by repeated `pixi lock --check` commands, -converge to unchanged lockfile bytes. Then restore strict freshness checks, -scheduled refreshes, and `PIXI_LOCKED=true` in source-build CI. +reproducers and repeated byte-stable update/lock/check cycles across every +workspace reported by `python ci/tools/list_pixi_workspaces.py`, including both +bindings roots. Verify `pixi update --no-install` and `pixi lock`, followed by +repeated `pixi lock --check` commands, converge to unchanged lockfile bytes. +Then restore strict freshness checks, scheduled refreshes, and `PIXI_LOCKED=true` +in source-build CI. ## Repository Customizations @@ -232,3 +232,275 @@ The canonical `NVIDIA/cuda-python` repository does not need this variable because its standard workflow behavior is enabled directly. Before enabling a customization elsewhere, document the repository-specific prerequisites and verification procedure in that repository's own documentation. + +## CUDA Bindings Package-Root Registry + +`ci/versions.yml` is the authoritative public registry for CUDA bindings +package roots. Each mapping key is a repository-relative package root; its +record declares an exact toolkit build/test pin and a `release_status` of +`current` or `maintenance`. Release routing uses the tag's CUDA major/minor +and the toolkit pin to select a root. Bindings and metapackage builds use +setuptools-scm's default tag parser. Each root's `git_describe_command` +selects its CUDA ABI major for source builds (`v12.*` or `v13.*`), +independently of the toolkit minor used to build it. + +The Python helpers share registry parsing and package consistency checks. The small, +repository-private `cuda-python-ci-tools` package installs their common +dependencies and makes those helpers importable from each build environment; +it is not part of a CUDA Python wheel. Workflows keep their control tooling separate when inspecting another +source revision. Install it locally from the repository root: + +```console +python -m pip install -e ./ci +``` + +Use `ci.tools.bindings_config` instead of reading the YAML directly. It +reads the registry and emits normalized JSON with the configured values +plus the derived CTK target and CUDA ABI major/variant: + +```console +python -m ci.tools.bindings_config +python -m ci.tools.bindings_config --package-roots +python -m ci.tools.bindings_config --release-status current +``` + +Install the test extra to run the CI-tool tests: + +```console +python -m pip install -e './ci[test]' +python -m pytest ci/tools/tests +``` + +The public wheel builder requires one `current` package root and one +`maintenance` package root with different CUDA ABI majors. +This is deliberately a two-line policy. The mapping keeps package paths out of +workflow conditionals; it does not promise arbitrary numbers of supported +lines. `cuda_bindings_12` names a stable ABI-major source tree, while `current` +and `maintenance` describe release roles that can change at the next major. + +The standalone `cuda-python` source distribution carries its selectors in +`cuda_python/pyproject.toml`, without depending on repository CI tooling. The +pre-commit metadata check keeps those selectors, bindings packaging, and Core +toolkit floors aligned with the registry: + +```console +python -m ci.tools.bindings_config --check-package-metadata +``` + +Copying maintenance sources does not make their later branch tags ancestors of +`main`. Preview builds need an appropriate reachable milestone for their target +release. Follow the [Git-derived version lifecycle](../AGENTS.md#git-derived-version-lifecycle) +and keep temporary preview tags local to an independent disposable clone; +worktrees share tags with their repository. +CI uses `ci.tools.bindings_scm_version` in disposable build checkouts until an +appropriate maintenance milestone is reachable. It creates a local preview tag +at a validated ancestor only when required; existing suitable tags govern the +version through standard setuptools-scm. It never pushes or moves tags. +For a local maintenance preview, use an independent disposable clone (not a +linked worktree), install the CI tools, and run from its root: + +```console +python -m ci.tools.bindings_scm_version --package-root cuda_bindings_12 --sha "$(git rev-parse HEAD)" +``` + +### SCM Milestones and Bootstrap Targets + +The maintenance root's `[tool.cuda-python].scm-bootstrap-release` records the +intended preview release, for example `12.9.11`. It is not a version override. +Suitable reachable release or milestone tags take precedence through standard +`setuptools-scm` progression; do not add a synthetic tag when that history +already supplies the version. + +When choosing a new target, fetch the canonical tags and coordinate with +maintainers of all release branches. Reserve the next globally unused release +number for the intended line. If an urgent release from another branch consumes +`12.9.11`, update main's preview target to the next unused release before another +preview build. Unreachable tags still reserve their release numbers. Never move +or replace such a tag to reuse its name on main. Keep the same target across +PRs and builds until release coordination calls for a change. + +Temporary bootstrap tags must stay local to an independent disposable clone; +worktrees share tags. After CUDA 13 tag isolation lands on `main`, maintainers +can establish the retained CUDA 12 milestone on that validated state. Rebase +remaining migration work onto the tagged commit before removing the temporary +bootstrap machinery and its workflow calls. Ordinary SCM history then governs +subsequent builds. Creating or publishing a milestone requires an explicit +maintainer decision. + +A toolkit minor rollover does not change the major-specific SCM selectors. +Let reachable tags govern preview versions; release tags establish the +intended release number. + +## Nightly Artifact Sources And Rollout + +`ci-nightly.yml` downloads wheels from the latest successful main CI run and +reads configuration from that run's source commit. Schema-2 sources use the +registry and toolkit-qualified artifacts for both local bindings roots. Older +sources retain the legacy route: their build-toolkit pin, backport branch, +artifact names, and test helpers come from that source revision. The workflow +does not invent a local CUDA 12 artifact for an older run that never built it. +That legacy route can still read existing artifacts from the frozen `12.9.x` +branch; it does not update the branch. +Selected rows that install a custom driver must also include its +`DRIVER_SHA256` in the source matrix. Registry compatibility alone does not +make a pre-hardening source compatible: choose a newer successful run rather +than disabling installer verification. Rows using preinstalled drivers do not +need an installer checksum. + +After multi-root CI lands on `main`, the first successful, complete main CI +run containing the new registry and canonical artifacts enables the new +nightly route. Until then, auto-detection can use an older successful run +through the legacy route. Keep that consumer compatibility while supported +older snapshots remain in use; their original producers retain their artifact +aliases. New schema-2 producers publish only the canonical artifacts. PR CI builds and tests everything +when it has no complete, compatible artifact baseline. + +To test the nightly workflow before that main run is available, supply the ID +of a successful CI run built with the new registry, required canonical wheel +artifacts, and driver-checksum metadata, such as a validated PR run: + +```console +gh workflow run ci-nightly.yml --repo NVIDIA/cuda-python --ref main -f 'run-id=' +``` + +The override selects both the wheel artifacts and their source revision. It +does not convert old artifacts or change the default selection for later +nightly runs. Raw-config runs made during the migration still use the legacy +route and its retained CUDA 13 aliases; they do not exercise the new local +CUDA 12 nightly route. Once a compatible main run succeeds, ordinary nightly +dispatches can leave `run-id` empty. + +## Reading CI Jobs and Summaries + +A name such as `pathfinder+bindings12+core12+meta12 / py3.11, CTK12.9.1 +(wheels; bindings PR)` identifies the enabled test suites, Python version, +and toolkit. `meta12` means the CUDA 12 `cuda-python` metapackage; `core12` +means Core tested with CUDA 12. The corresponding CUDA 13 suites use `13`. +`pathfinder` is shared across majors. + +The two source descriptions answer different questions: + +- `wheels` or `local CTK` describes how the toolkit libraries are supplied. +- `bindings PR` uses the selected root's artifact from this CI run. Unaffected + packages can reuse exact-baseline wheels, so this label alone does not mean + the bindings were rebuilt for the PR. +- `bindings floor` uses the oldest published bindings supported by the Core + wheel for that CUDA major, as declared in its metadata. These rows test Core + or the selected nightly integration against published bindings, without + running the bindings or metapackage suites. + +Open the change-detection job's **CI workplan** summary and expand its JSON. +The component build/test flags identify selected work; the baseline records +the original run and exact source SHA for reusable artifacts. Reuse requires a +successful push CI run for the exact merge-base commit on the PR's base branch, +with the complete required artifact set. If that baseline is unavailable, CI +builds and tests everything. A bindings-only change reuses the unchanged +merged Core wheel while testing Core against the affected bindings major. + +The platform matrix-setup jobs' **Selected wheel test coverage** summaries +show the final suites, toolkit source, bindings root or published floor, and +GPU. Shared infrastructure changes, regular branch runs, and scheduled CI +cover both majors; a bindings tag deliberately selects one bindings/metapackage +line and published Pathfinder. Windows ARM64 omits CUDA 12 because that toolkit +has no Windows ARM64 build. + +For registry-backed runs, `ci/tools/select_test_matrix.py` applies the shared selection and local-wheel +coverage rules after each platform reads its source matrix and applies an +optional jq filter. Matrix data comes from the artifact source revision; the +helper comes from the workflow control revision. Legacy snapshots retain their +original source-selection mode and do not need newly introduced CI tooling. + +## Historical Release And Documentation Reruns + +Current release and documentation workflows read package paths and toolkit +settings from the release tag. Retained CUDA 13 sources also support the +older `cuda.build.version` configuration and `cuda_bindings` directory. Their +original Python build matrix and generic metapackage artifact remain usable; +an available toolkit-qualified artifact takes precedence. The control revision +does not substitute its own source layout, toolkit pin, or release notes. + +Validate the existing `v13.4.4` release and rebuild its docs from the current +control revision with: + +```console +gh workflow run release.yml --repo NVIDIA/cuda-python --ref main \ + -f component=cuda-bindings -f release-action=dry-run \ + -f git-tag=v13.4.4 +``` + +Here `--ref main` selects the workflow and its reusable documentation +workflow; `git-tag` selects the immutable release source. Leave `run-id` empty +to find that tag's successful CI run, or supply its validated artifact run ID +if needed. The artifacts must still exist. A dry run validates artifacts and +builds docs without publishing a release; its documentation is retained as a +workflow artifact. Optionally add `-f dry-run-docs-branch=gh-pages-dry-run` to +deploy the documentation to that preview branch. Run once per component when +validating more than one package; use `component=cuda-python` for the matching +metapackage documentation. Older CUDA 12 branch releases still need compatible +historical tooling. Before dispatching that tooling, update its control branch +with applicable CI security fixes, including a verified patched Python and +current CA/download protections. This does not change the immutable release +source and is not required for the current workflow's retained CUDA 13 path. + +## Maintaining Both Bindings Roots + +`main` is the integration branch for both bindings release lines. The old +`12.9.x` branch stays frozen except for emergency fixes using compatible +historical tooling; reconcile any such fix into `cuda_bindings_12/` on `main`. +Routine CUDA 12 development belongs in that main-tree root. Short release +branches may stabilize a selected version or carry urgent fixes while `main` advances. +The [bindings release guide](../.github/RELEASE-bindings.md) describes branch +creation, backport automation, independent release validation, and returning +fixes to `main`. The +[CUDA 12 maintenance guide](../cuda_bindings_12/MAINTENANCE.md) identifies the +maintenance root. + +For generated bindings, change cybind first and regenerate each affected +package root against its intended toolkit inputs. Review the generated diff, +record the cybind revision, toolkit inputs, and command in the pull request, +and check modified files with +`python toolshed/check_generated_file_seals.py `. The seal verifies +the content of an individual generated file; it does not identify the cybind +revision or prove that the two roots should have identical output. Generated +output may legitimately differ across toolkit targets. + +For a handwritten bindings fix, inspect the corresponding code in the other +root. In the pull request, identify the matching change or explain concretely +why the fix does not apply there. A reviewer must verify that judgment: there +is no general deterministic check for semantic equivalence of handwritten +code across the two roots. Where files are intentionally identical, a narrowly +scoped byte-for-byte check can be added after establishing that invariant. + +Major-line maintenance can include deliberately selected compatibility work +such as a new Python version or a new API supported by that toolkit. A frozen +patch release has a narrower scope: fixes and release prerequisites approved +for that release. State which policy applies in the release checklist and +notes; a maintenance label alone does not promise a fixes-only release. + +Keep generator development and continuous toolkit qualification independent +of this source layout. A cybind change should be exercised against both +supported consumers before its generated output is imported. Record the +generator revision and toolkit inputs for each import, including any manual +post-generation adjustment. File seals establish content integrity, not +generation provenance. Moving QA directories or redesigning cybind asset +storage is not required to maintain both roots here. + +## Changing Supported Release Lines + +When support moves from CUDA 12/13 to CUDA 13/14, retain `cuda_bindings_13/` +as the maintenance source and introduce a distinct CUDA 14 root. Then: + +1. Update `ci/versions.yml`: remove the retired CUDA 12 root, mark the CUDA + 13 root `maintenance`, mark the CUDA 14 root `current`, and set each exact + toolkit build/test version. +2. Align each root's `pyproject.toml` `git_describe_command --match` selector + with its CUDA ABI major. Update per-root packaging, + pixi environments, documentation, and dependent package constraints for the + supported lines. +3. Build and test both roots and their dependent packages on the supported + platforms. Validate release selection and run publication-incapable dry + runs for tags from each line before releasing from the new layout. + +The same branch policy applies after the rollover: `main` integrates the +current and maintenance roots, short release branches stabilize releases, +and retired release branches are retained for history. diff --git a/ci/ci-pipeline.mmd b/ci/ci-pipeline.mmd index b085fa869bc..c5feebaf306 100644 --- a/ci/ci-pipeline.mmd +++ b/ci/ci-pipeline.mmd @@ -25,22 +25,24 @@ config: flowchart TB subgraph TRIGGER["TRIGGER EVENTS"] direction LR - T1["Push to main branch"] ~~~ T2["Pull request"] ~~~ T3["Manual workflow dispatch"] + T1["Main or approved pull request"] ~~~ T2["Immutable package tag"] ~~~ T3["Manual workflow dispatch"] end + PLAN["Select affected packages and dependencies
A package tag selects its release line
Registry: ci/versions.yml"] + subgraph BUILD["BUILD STAGE"] direction TB subgraph BUILD_PLATFORMS["Parallel Platform Builds"] direction LR - B1["linux-64
(Self-hosted)"] ~~~ B2["linux-aarch64
(Self-hosted)"] ~~~ B3["win-64
(GitHub-hosted)"] + B1["Linux x86_64
(Self-hosted)"] ~~~ B2["Linux ARM64
(Self-hosted)"] ~~~ B3["Windows x86_64 / ARM64
(GitHub-hosted)"] end - BUILD_DETAILS["Python versions: 3.11, 3.12, 3.13, 3.14, 3.15
CUDA version: 13.0.0 (build-time)
Components: cuda-core, cuda-bindings,
cuda-pathfinder, cuda-python"] + BUILD_DETAILS["Python matrix: ci/versions.yml
Platforms: ci.yml
Bindings toolkits and roots: registry
Build selected package artifacts"] end subgraph ARTIFACTS["ARTIFACT STORAGE"] direction TB subgraph GITHUB_ARTIFACTS["GitHub Artifacts"] - GA1["Wheel files (.whl)
Test artifacts
Documentation
(30-day retention)"] + GA1["Wheel files (.whl)
Test artifacts
Documentation
(Workflow-specific retention)"] end subgraph GITHUB_CACHE["GitHub Cache"] GC1["Mini CTK cache"] @@ -51,22 +53,22 @@ flowchart TB direction TB subgraph TEST_PLATFORMS["Parallel Platform Tests"] direction LR - TS1["linux-64
(Self-hosted)"] ~~~ TS2["linux-aarch64
(Self-hosted)"] ~~~ TS3["win-64
(GitHub-hosted)"] + TS1["linux-64
(Self-hosted)"] ~~~ TS2["linux-aarch64
(Self-hosted)"] ~~~ TS3["win-64
(Self-hosted GPU)"] end - TEST_DETAILS["Download wheels from artifacts
Test against multiple CUDA runtime versions
Run Python unit tests, Cython tests, and examples"] - ARTIFACT_FLOWS["Artifact Flows:
cuda-pathfinder: main to backport
cuda-bindings: backport to main"] + TEST_DETAILS["Test sources match wheel build revision
Test against multiple CUDA runtime versions
Run Python unit tests, Cython tests, and examples"] + ARTIFACT_FLOWS["Both bindings roots build from this source tree
Reuse validated baseline for unaffected packages
Bindings tag: selected line and published Pathfinder
Core older-toolkit tests: published bindings floor"] end subgraph RELEASE["RELEASE PIPELINE"] direction TB subgraph RELEASE_STAGES["Sequential Release Steps"] direction LR - R1["Validation
Artifact integrity
Git tag verification"] --> R2["Publishing
PyPI/TestPyPI
Component or all releases"] --> R3["Documentation
GitHub Pages
Release notes"] + R1["Validation
Git tag, wheels
and release notes"] --> R2["Documentation
Build selected component
before index publication"] --> R3["Publishing
TestPyPI/PyPI
Selected component"] end - RELEASE_DETAILS["Manual workflow dispatch with run ID
Supports individual component or full releases"] + RELEASE_DETAILS["Manual dry run for exact tag and successful CI run
Explicit publication for selected component
Bindings release does not require a Core release"] end - TRIGGER --> BUILD + TRIGGER --> PLAN --> BUILD BUILD -. "wheel upload" .-> ARTIFACTS ARTIFACTS --> TEST TEST --> RELEASE @@ -90,7 +92,7 @@ flowchart TB class TRIGGER triggerStyle class T1,T2,T3 triggerNodeStyle class BUILD,BUILD_PLATFORMS buildStyle - class B1,B2,B3,BUILD_DETAILS buildNodeStyle + class PLAN,B1,B2,B3,BUILD_DETAILS buildNodeStyle class ARTIFACTS,GITHUB_ARTIFACTS,GITHUB_CACHE artifactStyle class GA1,GC1 artifactNodeStyle class TEST,TEST_PLATFORMS testStyle diff --git a/ci/tools/bindings_config.py b/ci/tools/bindings_config.py index ecf08a2b995..b3e78a8012d 100644 --- a/ci/tools/bindings_config.py +++ b/ci/tools/bindings_config.py @@ -6,9 +6,11 @@ from __future__ import annotations import argparse +import importlib.util import json import re import sys +import tomllib from dataclasses import dataclass from pathlib import Path from typing import Any, Mapping @@ -119,6 +121,59 @@ def load_config(path: Path = DEFAULT_CONFIG) -> BindingsConfig: return parse_config(yaml.safe_load(path.read_text(encoding="utf-8"))) +def check_core_toolkit_floors(config: BindingsConfig, repo_root: Path) -> None: + """Check toolkit pins against core's existing, standard-library floor reader.""" + core = repo_root / "cuda_core" + floor_path = core / "cuda" / "core" / "_bindings_floor.py" + spec = importlib.util.spec_from_file_location("_cuda_core_bindings_floor", floor_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + with (core / "pyproject.toml").open("rb") as stream: + extras = tomllib.load(stream)["project"]["optional-dependencies"] + floors = module.floors_from_extras(extras) + pins = {int(package.cuda_major): package for package in config.package_roots} + if pins.keys() != floors.keys(): + raise ValueError("registered CUDA majors must match cuda_core's cu extras") + for major, floor in floors.items(): + package = pins[major] + if package.toolkit[:2] < floor[:2]: + raise ValueError( + f"{package.package_root} toolkit {package.toolkit_version} is below " + f"the CUDA {major} bindings floor {'.'.join(map(str, floor))}" + ) + + +def check_package_metadata(config: BindingsConfig, repo_root: Path) -> None: + """Check consistency of package metadata in the selected source tree.""" + check_core_toolkit_floors(config, repo_root) + selectors: dict[str, str] = {} + for package in config.package_roots: + path = repo_root / package.package_root / "pyproject.toml" + with path.open("rb") as stream: + scm = tomllib.load(stream)["tool"]["setuptools_scm"] + if "tag_regex" in scm: + raise ValueError(f"{path} must use setuptools-scm's default tag parser") + command = scm["git_describe_command"] + if command.count("--match") != 1: + raise ValueError(f"{path} must specify one git_describe_command --match selector") + selector = command[command.index("--match") + 1] + expected_selector = f"v{package.cuda_major}.*" + if selector != expected_selector: + raise ValueError( + f"{path} --match must select registered CUDA major {package.cuda_major} " + f"using {expected_selector!r} for the {package.release_status} root, got {selector!r}" + ) + selectors[package.cuda_major] = selector + if "fallback_version" in scm: + raise ValueError(f"{path} bindings root must not declare fallback_version") + + path = repo_root / "cuda_python" / "pyproject.toml" + with path.open("rb") as stream: + meta_selectors = tomllib.load(stream)["tool"]["cuda-python"]["scm-describe-match-by-major"] + if meta_selectors != selectors: + raise ValueError(f"{path} scm-describe-match-by-major must match the bindings package selectors") + + def parse_prefixed_version(tag: str, prefix: str) -> Version | None: """Parse the PEP 440 version following an exact component tag prefix.""" if not tag.startswith(prefix): @@ -258,6 +313,7 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--config", type=Path, default=DEFAULT_CONFIG) output = parser.add_mutually_exclusive_group() output.add_argument("--package-roots", action="store_true") + output.add_argument("--check-package-metadata", action="store_true") output.add_argument("--release-status", choices=sorted(RELEASE_STATUSES)) output.add_argument("--release-tag", help="resolve a release tag against its source tree") parser.add_argument("--release-source-root", type=Path) @@ -284,7 +340,13 @@ def main(argv: list[str] | None = None) -> int: ) return 0 if args.command == "write-github-env": - if args.package_roots or args.release_status or args.release_tag or args.release_source_root is not None: + if ( + args.package_roots + or args.check_package_metadata + or args.release_status + or args.release_tag + or args.release_source_root is not None + ): parser.error("write-github-env does not accept registry selectors") value = json.load(sys.stdin) if not isinstance(value, dict): @@ -299,6 +361,9 @@ def main(argv: list[str] | None = None) -> int: if args.release_source_root is not None: parser.error("--release-source-root requires --release-tag") config = load_config(args.config) + if args.check_package_metadata: + check_package_metadata(config, REPO_ROOT) + return 0 if args.release_status: result = config.package_for_release_status(args.release_status).to_dict() else: diff --git a/ci/tools/check_pixi_cuda_version.py b/ci/tools/check_pixi_cuda_version.py index 14f4f8cbe9d..a0f70328094 100644 --- a/ci/tools/check_pixi_cuda_version.py +++ b/ci/tools/check_pixi_cuda_version.py @@ -22,9 +22,8 @@ def main() -> int: print(f"error: invalid CUDA toolkit pins in {bindings_config.DEFAULT_CONFIG}: {exc}", file=sys.stderr) return 2 - current = config.package_for_release_status("current") checks = [(package.package_root, package) for package in config.package_roots] - checks.append(("cuda_core", current)) + checks.extend(("cuda_core", package) for package in config.package_roots) errors: list[str] = [] checked: list[str] = [] for package_root, package in checks: diff --git a/ci/tools/tests/test_bindings_config.py b/ci/tools/tests/test_bindings_config.py index e8ff344953f..e539d17e93d 100644 --- a/ci/tools/tests/test_bindings_config.py +++ b/ci/tools/tests/test_bindings_config.py @@ -73,10 +73,14 @@ def pixi_checkout(tmp_path, monkeypatch, legacy_config): ): path = tmp_path / package_root / "pixi.toml" path.parent.mkdir() + variants = ["12.*", pin] if package_root == "cuda_core" else [pin] path.write_text( - f'[workspace.build-variants]\ncuda-version = ["{pin}"]\n' + f"[workspace.build-variants]\ncuda-version = {json.dumps(variants)}\n" f'[feature.{variant}.dependencies]\ncuda-version = "{pin}"\n' ) + if package_root == "cuda_core": + with path.open("a") as stream: + stream.write('[feature.cu12.dependencies]\ncuda-version = "12.*"\n') monkeypatch.setattr(check_pixi_cuda_version, "ROOT", tmp_path) monkeypatch.setattr(check_pixi_cuda_version.bindings_config, "load_config", lambda: load_config(legacy_config)) return tmp_path @@ -99,6 +103,14 @@ def test_pixi_check_reports_core_pin_drift(pixi_checkout, capsys): assert "cuda_core/pixi.toml: feature.cu13.dependencies.cuda-version='13.3.*'" in capsys.readouterr().err +@pytest.mark.agent_authored(model="gpt-6") +def test_pixi_check_reports_maintenance_core_pin_drift(pixi_checkout, capsys): + path = pixi_checkout / "cuda_core" / "pixi.toml" + path.write_text(path.read_text().replace('cuda-version = "12.*"', 'cuda-version = "11.*"')) + assert check_pixi_cuda_version.main() == 1 + assert "cuda_core/pixi.toml: feature.cu12.dependencies.cuda-version='11.*'" in capsys.readouterr().err + + @pytest.mark.parametrize( ("package_root", "old_pin", "new_pin"), [("cuda_bindings_12", "12.*", "11.*"), ("cuda_bindings_13", "13.4.*", "13.*")], @@ -130,15 +142,15 @@ def git(*args): [ # noqa: S607 "git", "-c", - "user.name=CI test", - "-c", - "user.email=ci-test@example.invalid", - "-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false", "-c", "core.hooksPath=/dev/null", + "-c", + "user.name=CI test", + "-c", + "user.email=ci-test@example.invalid", *args, ], cwd=tmp_path, diff --git a/ci/tools/tests/test_bindings_metadata.py b/ci/tools/tests/test_bindings_metadata.py new file mode 100644 index 00000000000..17d437284d5 --- /dev/null +++ b/ci/tools/tests/test_bindings_metadata.py @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import shutil +from pathlib import Path + +import pytest + +from ci.tools.bindings_config import check_package_metadata, parse_config + + +def _registry(maintenance_root="cuda_bindings_12", current_root="cuda_bindings_13"): + return { + "schema_version": 2, + "python": {"supported": ["3.11", "3.14t", "3.15"]}, + "cuda": { + "bindings": { + "package_roots": { + maintenance_root: {"toolkit_version": "12.9.1", "release_status": "maintenance"}, + current_root: {"toolkit_version": "13.4.1", "release_status": "current"}, + } + } + }, + } + + +def _write_package_metadata(root): + config = parse_config(_registry()) + for package in config.package_roots: + path = root / package.package_root / "pyproject.toml" + path.parent.mkdir(parents=True) + path.write_text( + '[tool.setuptools_scm]\ngit_describe_command = ["git", "describe", "--match", ' + f'"v{package.cuda_major}.*"]\n', + encoding="utf-8", + ) + meta = root / "cuda_python" / "pyproject.toml" + meta.parent.mkdir() + meta.write_text( + '[tool.cuda-python.scm-describe-match-by-major]\n"12" = "v12.*"\n"13" = "v13.*"\n', + encoding="utf-8", + ) + core = root / "cuda_core" + core.mkdir() + (core / "pyproject.toml").write_text( + '[project.optional-dependencies]\ncu12 = ["cuda-bindings>=12.9.0,<13"]\ncu13 = ["cuda-bindings>=13.4.0,<14"]\n', + encoding="utf-8", + ) + floor_reader = Path("cuda_core/cuda/core/_bindings_floor.py") + (root / floor_reader).parent.mkdir(parents=True) + shutil.copyfile(Path(__file__).resolve().parents[3] / floor_reader, root / floor_reader) + return config + + +@pytest.mark.parametrize( + "changed_paths", + ( + ("cuda_bindings_13/pyproject.toml",), + ("cuda_python/pyproject.toml",), + ("cuda_bindings_13/pyproject.toml", "cuda_python/pyproject.toml"), + ), +) +@pytest.mark.agent_authored(model="gpt-6.1-sol") +def test_metadata_check_catches_selectors_drifting_from_the_registered_major(tmp_path, changed_paths): + config = _write_package_metadata(tmp_path) + check_package_metadata(config, tmp_path) + for relative in changed_paths: + path = tmp_path / relative + path.write_text(path.read_text().replace("v13.*", "v14.*"), encoding="utf-8") + + with pytest.raises(ValueError): + check_package_metadata(config, tmp_path) + + +@pytest.mark.agent_authored(model="gpt-6") +def test_registered_toolkit_below_core_floor_is_rejected(tmp_path): + config = _write_package_metadata(tmp_path) + core_metadata = tmp_path / "cuda_core" / "pyproject.toml" + core_metadata.write_text(core_metadata.read_text().replace(">=12.9.0", ">=12.10.0"), encoding="utf-8") + with pytest.raises(ValueError, match="below.*floor"): + check_package_metadata(config, tmp_path) diff --git a/ci/versions.yml b/ci/versions.yml index 4891cc94644..babcd593fbb 100644 --- a/ci/versions.yml +++ b/ci/versions.yml @@ -1,20 +1,22 @@ # SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -backport_branch: "12.9.x" # keep in sync with target-branch in .github/dependabot.yml +schema_version: 2 # This controls the version of the upstream CTK to install to build the wheels. # It is not the version of cuda_bindings/cuda_python being released, which is # solely determined by git tags via setuptools_scm. cuda: - build: - version: "13.4.2" - prev_build: - version: "12.9.1" - + bindings: + package_roots: + cuda_bindings_12: + toolkit_version: "12.9.1" + release_status: maintenance + cuda_bindings_13: + toolkit_version: "13.4.2" + release_status: current python: - # Python versions the wheel matrix is built across. ci.yml reads this list - # and passes it to build-wheel.yml as the `python-versions` input. + # Shared wheel build matrix, also read from tagged sources during releases. supported: - "3.11" - "3.12" diff --git a/cuda_bindings_12/AGENTS.md b/cuda_bindings_12/AGENTS.md index c3a5af6e238..931d0ab4e45 100644 --- a/cuda_bindings_12/AGENTS.md +++ b/cuda_bindings_12/AGENTS.md @@ -3,29 +3,47 @@ This root maintains CUDA 12.9 bindings under the `cuda.bindings` namespace. Follow the repository-wide instructions, including [Changes across bindings majors](../AGENTS.md#changes-across-bindings-majors). -Inspect the CUDA 13 counterpart during implementation and review; preserve -intentional API, layout, and build differences. +Inspect the CUDA 13 counterpart during implementation and review, and record +matching changes or why they are not applicable. Preserve intentional API, +layout, and build differences rather than copying the CUDA 13 root wholesale. + +Follow the [maintenance guide](MAINTENANCE.md) for branch and release policy +and [Maintaining Both Bindings Roots](../ci/README.md#maintaining-both-bindings-roots) +for generation provenance and review expectations. ## Generated and handwritten sources -- Files carrying a `CYTHON-BINDINGS-GENERATED` seal are generated inputs. - Change their generation source and regenerate against CUDA 12.9 inputs. - Record generator revision, toolkit inputs, and affected files. Do not edit - sealed files or recalculate their seals to conceal manual changes. -- Handwritten utilities, build helpers, tests, and documentation can be edited - directly. Inspect headers and provenance; a filename extension alone does - not identify generated code. -- Source builds parse CUDA headers and expand `.in` templates before Cython. - Regenerate these outputs from their templates and headers. Preserve the - CUDA 12 parser, legacy APIs, and namespace redirector. +- Files carrying a `CYTHON-BINDINGS-GENERATED` seal are cybind output, + including some `.pyx`, `.pxd`, and `.in` files. Change their generation source + and regenerate against the intended CUDA 12.9 inputs. Record the generator + revision, toolkit inputs, and affected files in the review summary. Do not + edit sealed files or recalculate their seals to conceal manual changes. +- Handwritten files, such as `cuda/bindings/_internal/utils.pyx`, build helpers, + tests, and documentation, can be edited directly. Inspect the file's header + and provenance; a filename extension alone does not identify generated code. +- Source builds also parse CUDA headers with pyclibrary and expand `.in` + templates through `build_hooks.py` before Cythonization. Those generated + `.pyx`, `.pxd`, and `.pxi` outputs must be regenerated from their templates + and headers, rather than patched. Build-time expansion does not make a + sealed input template handwritten. +- Keep the CUDA 12 header parser, templates, legacy APIs, and namespace + redirector. The CUDA 13 extension list and generated layout are different. ## Build and test guidance -- Use CUDA 12.9 headers and a separate Python environment from CUDA 13. +- Source builds require CUDA 12.9 headers. `build_hooks.py` checks the first + `cuda.h` found through `CUDA_HOME` or `CUDA_PATH` before generating files. + Split installations can still supply the existing path list. - `_build_shared.py` links to the canonical helper in `cuda_bindings_13/`. - Preserve its symlink or byte-for-byte identity with the shared copies. -- Use the package's [developer commands](README.md). Build the Cython test - extensions before running `tests/cython/`. -- Legacy samples remain under `examples/`; CUDA 13 uses a different layout. -- Run the SCM preview bootstrap only in a disposable build checkout. Never - push its synthetic tags or run it in a shared development repository. + Preserve the symlink or a byte-for-byte identical copy, including matching + changes in the Core and Pathfinder helper copies. +- Use Python 3.11 or newer and a separate environment from CUDA 13 bindings. + From this directory, install with + `python -m pip install -v -e . --group test`, then run `python -m pytest tests/`. + Free-threaded development also needs the `test-ft` dependency group. +- Build Cython tests with `tests/cython/build_tests.sh` or `build_tests.bat` + before running `python -m pytest tests/cython/`. This requires the matching + CUDA headers and compiler toolchain. +- Legacy samples remain under `examples/`; do not assume CUDA 13's sample + layout applies to this root. Keep marked SYNCED test-bootstrap blocks equal + across both bindings roots and Core. diff --git a/cuda_bindings_12/MAINTENANCE.md b/cuda_bindings_12/MAINTENANCE.md new file mode 100644 index 00000000000..dc565132e8e --- /dev/null +++ b/cuda_bindings_12/MAINTENANCE.md @@ -0,0 +1,23 @@ + + +# Maintaining the CUDA 12 bindings line + +`cuda_bindings_12/` is the CUDA 12.9 maintenance package root. The current +CUDA 13 bindings live in `cuda_bindings_13/`. The package roots and their release +statuses are configured in [`ci/versions.yml`](../ci/versions.yml). + +Integrate both release lines on `main`. The former `12.9.x` branch stays +frozen except for emergency fixes using compatible historical tooling; +reconcile those fixes into `cuda_bindings_12/`. Routine CUDA 12 fixes, tests, +and documentation belong in this main-tree root. Short release branches may +stabilize a release or carry an urgent fix; reconcile such fixes into `main` +and assess the CUDA 13 root too. + +Follow the [bindings release guide](../.github/RELEASE-bindings.md) for branch +selection, patch-release scope, backports, and release validation. + +Follow the [shared regeneration and review policy](../ci/README.md#maintaining-both-bindings-roots) +when changing implementation in either package root. diff --git a/cuda_bindings_12/README.md b/cuda_bindings_12/README.md index 6cda8e854b7..d69a69bc045 100644 --- a/cuda_bindings_12/README.md +++ b/cuda_bindings_12/README.md @@ -11,7 +11,8 @@ Please refer to the [Installation page](https://nvidia.github.io/cuda-python/cud ## Developing -This subpackage adheres to the developing practices described in the parent metapackage [CONTRIBUTING.md](https://github.com/NVIDIA/cuda-python/blob/main/CONTRIBUTING.md). +Follow the repository [contribution guide](../CONTRIBUTING.md) and the +[maintenance guide](MAINTENANCE.md) for source builds and cross-major changes. CUDA 12.9 sources are developed here on `main`. This root and `cuda_bindings_13/` build the same distribution and Python namespace; use @@ -46,8 +47,9 @@ or push its synthetic tags. See the repository's ## Testing -Install the package and its `test` dependency group with -`python -m pip install -v -e . --group test` (pip 25.1 or newer). +Run the commands below from `cuda_bindings_12/`, in an environment containing +the CUDA 12 bindings. With pip 25.1 or newer, install the package and its +`test` dependency group with `python -m pip install -v -e . --group test`. Multiple testing options are available: @@ -69,7 +71,7 @@ To run these tests: Cython tests are located in `tests/cython` and need to be built. These builds have the same CUDA Toolkit header requirements as [Installing from Source](https://nvidia.github.io/cuda-python/cuda-bindings/12.9.7/install.html#requirements) where the major.minor version must match `cuda.bindings`. To build them: 1. Setup environment variable `CUDA_HOME` with the path to the CUDA Toolkit installation. -2. Run `build_tests` script located in `test/cython` appropriate to your platform. This will both cythonize the tests and build them. +2. Run the `build_tests` script located in `tests/cython` appropriate to your platform. This will both cythonize the tests and build them. To run these tests: * `python -m pytest tests/cython/` against editable installations @@ -90,10 +92,12 @@ In addition, extra examples are included: wrappers of the driver API. To run these samples: -* `python -m pytest tests/cython/` against editable installations -* `pytest tests/cython/` against installed packages +* `python -m pytest examples/` against editable installations +* `pytest examples/` against installed packages ### Benchmark See the shared [cuda.bindings benchmark harness](../benchmarks/cuda_bindings/README.md) -for performance benchmarks and usage instructions. +for performance benchmarks and usage instructions. Its configured environments +currently target CUDA 13; benchmarking CUDA 12 requires a compatible environment +configured separately. diff --git a/cuda_bindings_12/docs/README.md b/cuda_bindings_12/docs/README.md index 8b445f41e65..b533f56e349 100644 --- a/cuda_bindings_12/docs/README.md +++ b/cuda_bindings_12/docs/README.md @@ -1,10 +1,22 @@ # Build the documentation -1. Install the `cuda-bindings` package of the version that we need to document. +Use a separate environment from CUDA 13 documentation builds: both roots +install the same `cuda-bindings` distribution and `cuda.bindings` namespace. +The aggregate `cuda_python/docs/build_all_docs.sh` script builds the current +CUDA 13 line, so build this maintenance documentation separately. + +1. Install the CUDA 12 `cuda-bindings` build that you need to document. 2. Ensure the version is included in the canonical [`nv-versions.json`](../../cuda_bindings_13/docs/nv-versions.json). 3. Build the docs with `./build_docs.sh`. 4. The html artifacts should be available under both `./build/html/latest` and `./build/html/`. -To publish the docs with the built version, it is important to note that the html files of older versions -should be kept intact, in order for the version selection (through `nv-versions.json`) to work. +The Pixi `docs` environment currently installs a published `12.9.*` package. +To document APIs changed in the checkout, install the matching source build +in your documentation environment before running `./build_docs.sh`. + +`build/html/latest` is for local preview only. Follow the selected branch's +[bindings release guide](../../.github/RELEASE-bindings.md) for publication. Maintenance publication must exclude `latest` +and the shared version-selector files so it cannot overwrite the current-major +site. Keep older versioned pages intact; do not publish the entire local build +directory over the shared site. diff --git a/cuda_bindings_12/docs/source/contribute.md b/cuda_bindings_12/docs/source/contribute.md index e2f95fab6f2..00e4f7cdc52 100644 --- a/cuda_bindings_12/docs/source/contribute.md +++ b/cuda_bindings_12/docs/source/contribute.md @@ -1,17 +1,5 @@ # Contributing -Thank you for your interest in contributing to `cuda-bindings`! Based on the type of contribution, it will fall into two categories: - -1. You want to report a bug, feature request, or documentation issue - - File an [issue](https://github.com/NVIDIA/cuda-python/issues/new/choose) - describing what you encountered or what you want to see changed. - - The NVIDIA team will evaluate the issues and triage them, scheduling - them for a release. If you believe the issue needs priority attention - comment on the issue to notify the team. -2. You want to implement a feature, improvement, or bug fix: - - Before starting work on an existing issue, comment on the issue to - express your interest and wait to be assigned by a maintainer. This - helps avoid redundant effort. - - Follow the repository [contribution guide](https://github.com/NVIDIA/cuda-python/blob/main/CONTRIBUTING.md), - including signing off each commit under the Developer Certificate of - Origin (DCO) and cryptographically signing commits. +Thank you for your interest in contributing to `cuda-bindings`! +Please follow the repository [contribution guide](https://github.com/NVIDIA/cuda-python/blob/main/CONTRIBUTING.md) +for coordination, validation, and commit signing. diff --git a/cuda_bindings_12/docs/source/install.md b/cuda_bindings_12/docs/source/install.md index 728c06729a4..3696157ab8c 100644 --- a/cuda_bindings_12/docs/source/install.md +++ b/cuda_bindings_12/docs/source/install.md @@ -6,14 +6,24 @@ * Linux (x86-64, arm64) and Windows (x86-64) * Python 3.11 - 3.15 -* Driver: Linux (450.80.02 or later) Windows (456.38 or later) -* Optionally, NVRTC, nvJitLink, and NVVM from CUDA Toolkit 12.x +* Driver: Linux (525.60.13 or later), Windows (528.33 or later) +* Optionally, NVRTC, nvJitLink, NVVM, nvFatbin, and cuFile (Linux only) from CUDA Toolkit 12.x + +These driver versions are the CUDA 12 minor-version compatibility baseline in the +[CUDA 12.9 release notes](https://docs.nvidia.com/cuda/archive/12.9.1/cuda-toolkit-release-notes/index.html#cuda-driver). +Features introduced with newer drivers and PTX compilation can require a newer +driver; see the +[minor-version compatibility limitations](https://docs.nvidia.com/deploy/cuda-compatibility/minor-version-compatibility.html#application-considerations-for-minor-version-compatibility). ```{note} The optional CUDA Toolkit components can be installed via PyPI, Conda, OS-specific package managers, or local installers (as described in the CUDA Toolkit [Windows](https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html) and [Linux](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html) Installation Guides). ``` -Starting from v12.8.0, `cuda-python` becomes a meta package which currently depends only on `cuda-bindings`; in the future more sub-packages will be added to `cuda-python`. In the instructions below, we still use `cuda-python` as example to serve existing users, but everything is applicable to `cuda-bindings` as well. +The CUDA 12 releases of `cuda-python` are a metapackage that depends directly +on `cuda-bindings`. The examples below use `cuda-python` for existing users; +install `cuda-bindings` directly if you only need the low-level interfaces. + +Both main-tree CUDA 12 packages require Python 3.11 or newer. ## Installing from PyPI @@ -32,6 +42,8 @@ Where the optional dependencies are: * nvidia-cuda-nvrtc-cu12 (Provides NVRTC shared library) * nvidia-nvjitlink-cu12>=12.3 (Provides nvJitLink shared library) * nvidia-cuda-nvcc-cu12 (Provides NVVM shared library) +* nvidia-nvfatbin-cu12 (Provides nvFatbin shared library) +* nvidia-cufile-cu12 (Provides cuFile shared library, Linux only) ## Installing from Conda @@ -43,6 +55,19 @@ $ conda install -c conda-forge cuda-python=12.9 ## Installing from Source +Start with the repository +[recommended clone](https://github.com/NVIDIA/cuda-python/blob/main/CONTRIBUTING.md#recommended-clone). +On native Windows outside WSL, complete its symlink setup before cloning. +CUDA 12 sources live in `cuda_bindings_12/`; CUDA 13 sources have a separate root. +Both build the same `cuda-bindings` distribution, so use a separate Python +environment for each major. + +Versions are derived from reachable `v12.*` tags using `setuptools-scm`. +Historical tags from the old maintenance branch may not be ancestors of `main`. +Before a suitable tag is reachable, follow the +[maintenance preview-build guidance](https://github.com/NVIDIA/cuda-python/blob/main/ci/README.md#cuda-bindings-package-root-registry) +in an independent clone; worktrees share tags with the rest of their repository. + ### Requirements * CUDA Toolkit headers[^1] @@ -67,10 +92,12 @@ Only `cydriver`, `cyruntime` and `cynvrtc` are impacted by the header requiremen ### Editable Install -You can use +From the repository root, run: ```console -$ pip install -v -e . +$ cd cuda_bindings_12 +$ python -m pip install -v -e . ``` -to install the module as editable in your current Python environment (e.g. for testing of porting other libraries to use the binding). +This installs the CUDA 12 bindings as editable in your current Python environment +for development and testing. diff --git a/cuda_bindings_12/docs/source/release/12.X.Y-notes.rst b/cuda_bindings_12/docs/source/release/12.X.Y-notes.rst index 74d8eca4423..34dc8ed54cb 100644 --- a/cuda_bindings_12/docs/source/release/12.X.Y-notes.rst +++ b/cuda_bindings_12/docs/source/release/12.X.Y-notes.rst @@ -33,3 +33,11 @@ Packaging following the CUDA 13 packaging policy. Cython declarations and includes (``.pxd`` and ``.pxi``), and required headers, remain available for downstream Cython builds. Source distributions retain the inputs needed to build the package. + +Documentation +------------- + +* Corrected the CUDA 12 driver guidance to its existing minor-version + compatibility baseline: Linux 525.60.13 and Windows 528.33. This is a + documentation correction, not a new runtime version check. Newer features + and PTX compilation can still require a newer driver. diff --git a/cuda_bindings_13/AGENTS.md b/cuda_bindings_13/AGENTS.md index 6b3a3d442b7..a892a0799f3 100644 --- a/cuda_bindings_13/AGENTS.md +++ b/cuda_bindings_13/AGENTS.md @@ -9,6 +9,10 @@ subpackage in the `cuda-python` monorepo. wrappers. High-level ergonomics belong in `cuda_core`, not here. - **Cross-platform**: preserve Linux and Windows behavior unless a change is intentionally platform-specific. +- **Cross-major review**: follow + [Changes across bindings majors](../AGENTS.md#changes-across-bindings-majors) + during implementation and review. Inspect the CUDA 12 counterpart and record + matching changes or why they are not applicable. ## Package architecture @@ -23,8 +27,8 @@ subpackage in the `cuda-python` monorepo. (toolchain selection, the compiler flag set, the Cython cache helpers, the rebuild stamps and the release-tag pinning of README links) lives in `_build_shared.py`. `cuda_bindings_13/_build_shared.py` is canonical; - the `cuda_core` and `cuda_pathfinder` copies must each be either a symlink to - it or a byte-for-byte identical copy. + the `cuda_bindings_12`, `cuda_core`, and `cuda_pathfinder` copies must each + be either a symlink to it or a byte-for-byte identical copy. ## Generated-source workflow diff --git a/cuda_bindings_13/README.md b/cuda_bindings_13/README.md index 6495f0aa776..e603e713ec8 100644 --- a/cuda_bindings_13/README.md +++ b/cuda_bindings_13/README.md @@ -20,8 +20,9 @@ Debuggable builds are not supported on Windows. ## Testing -With pip 25.1 or newer, install testing dependencies using the `test` -dependency group: `python -m pip install -v -e . --group test`. +Run the commands below from `cuda_bindings_13/`, in an environment containing +the CUDA 13 bindings. With pip 25.1 or newer, install the package and its +`test` dependency group with `python -m pip install -v -e . --group test`. Multiple testing options are available: diff --git a/cuda_bindings_13/docs/README.md b/cuda_bindings_13/docs/README.md index 54d670d0917..c0311407c28 100644 --- a/cuda_bindings_13/docs/README.md +++ b/cuda_bindings_13/docs/README.md @@ -1,11 +1,15 @@ # Build the documentation -1. Install the `cuda-bindings` package of the version that we need to document. +1. Install the CUDA 13 `cuda-bindings` build that you need to document. 2. Ensure the version is included in the [`nv-versions.json`](./nv-versions.json). 3. Build the docs with `./build_docs.sh`. 4. The html artifacts should be available under both `./build/html/latest` and `./build/html/`. -Alternatively, we can build all the docs at once by running [`cuda_python/docs/build_all_docs.sh`](../../cuda_python/docs/build_all_docs.sh). +[`cuda_python/docs/build_all_docs.sh`](../../cuda_python/docs/build_all_docs.sh) +builds this current bindings line together with the metapackage, Core, and +Pathfinder documentation. Build [CUDA 12 maintenance documentation](../../cuda_bindings_12/docs/README.md) +separately in its own environment; both roots install the same `cuda-bindings` +distribution and `cuda.bindings` namespace. To publish the docs with the built version, it is important to note that the html files of older versions should be kept intact, in order for the version selection (through `nv-versions.json`) to work. diff --git a/cuda_bindings_13/docs/source/install.rst b/cuda_bindings_13/docs/source/install.rst index 2c8213816d7..ddfae3aaafb 100644 --- a/cuda_bindings_13/docs/source/install.rst +++ b/cuda_bindings_13/docs/source/install.rst @@ -18,7 +18,11 @@ Runtime Requirements The optional CUDA Toolkit components are now installed via the ``cuda-toolkit`` metapackage from PyPI for improved dependency resolution. Components can also be installed via Conda, OS-specific package managers, or local installers (as described in the CUDA Toolkit `Windows `_ and `Linux `_ Installation Guides). -Starting from v12.8.0, ``cuda-python`` becomes a meta package which currently depends only on ``cuda-bindings``; in the future more sub-packages will be added to ``cuda-python``. In the instructions below, we still use ``cuda-python`` as example to serve existing users, but everything is applicable to ``cuda-bindings`` as well. +The CUDA 13 releases of ``cuda-python`` are a metapackage that installs +``cuda-bindings``, ``cuda-core``, and ``cuda-pathfinder``. The examples below use +``cuda-python`` for existing users; install ``cuda-bindings`` directly if you +only need the low-level interfaces. Its ``all`` extra installs the optional +Toolkit components listed below. Free-threading Build Support @@ -108,9 +112,9 @@ obtain the most recent build, use the following commands: Replace ``python312`` with your Python version (e.g. ``python311``, ``python313``, ``python314``, ``python314t``, ``python315``, ``python315t``). For aarch64, replace ``linux-64`` -with ``linux-aarch64``; for Windows, use ``win-64``. Only the current CUDA -major version is built on ``main``; wheels for the prior CUDA major are -published from the corresponding backport branch. +with ``linux-aarch64``; for Windows, use ``win-64``. To use the CUDA 12 line, +replace ``cuda13`` with ``cuda12`` in both commands. Both supported CUDA major +lines are published by CI from ``main``. Installing from Source ~~~~~~~~~~~~~~~~~~~~~~ @@ -133,8 +137,10 @@ Requirements [^2]: The CUDA Runtime static library (``libcudart_static.a`` on Linux, ``cudart_static.lib`` on Windows) is part of the CUDA Toolkit. If using conda packages, it is contained in the ``cuda-cudart-static`` package. [^3]: The version is derived from git tags via ``setuptools-scm``, so the clone -must include tags reaching back to at least the latest ``v*`` tag. Do not use -``--depth`` or ``--no-tags``: a clone without reachable tags can build with a +must include a reachable tag matching the release line configured in this +package's ``pyproject.toml`` (currently ``v13.*``). A tag from the other CUDA +major does not determine this package's version. Do not use ``--depth`` or +``--no-tags``: a clone without matching reachable tags can build with a bogus version such as ``0.1.dev1+g0d22cb444``. See `Cloning the repository `_ for details. diff --git a/cuda_bindings_13/docs/source/support.rst b/cuda_bindings_13/docs/source/support.rst index 665c29c029f..b9617826d5c 100644 --- a/cuda_bindings_13/docs/source/support.rst +++ b/cuda_bindings_13/docs/source/support.rst @@ -12,8 +12,8 @@ The ``cuda.bindings`` module has the following support policy: third number in the version string), however, is reserved to reflect Python-only changes and is out of sync with the Toolkit patch version. 2. The module is actively maintained to support the latest CUDA major version and its prior major - version. For example, as of writing the bindings for CUDA 12 & 13 are maintained. Any fix in the - latest bindings would be backported to the prior major version. + version. Both active source trees live on the main branch so applicable fixes can update the + CUDA 12 and CUDA 13 packages in one change. 3. The module supports `CUDA minor version compatibility`_, meaning that ``cuda.bindings`` 12.x supports any Toolkit 12.y. (Whether or not a binding API would actually correctly function depends on the underlying driver and the Toolkit versions, as described in the compatibility diff --git a/cuda_core/AGENTS.md b/cuda_core/AGENTS.md index 2febc5374ed..b4b8f2b53af 100644 --- a/cuda_core/AGENTS.md +++ b/cuda_core/AGENTS.md @@ -26,8 +26,8 @@ This file describes `cuda_core`, the high-level Pythonic CUDA subpackage in the dependency wiring. Logic shared with `cuda-bindings` (toolchain selection, the compiler flag set, the Cython cache helpers and the rebuild stamps) lives in `_build_shared.py`. `cuda_bindings_13/_build_shared.py` is canonical; - `cuda_core/_build_shared.py` must be either a symlink to it or a byte-for-byte - identical copy. + each package-local `_build_shared.py` must be a symlink to it or a byte-for-byte + identical copy, including maintenance bindings roots. ## Build and version coupling @@ -59,12 +59,13 @@ a bump. To bump: 1. Edit the `cuda-bindings` pin in the `cu12` or `cu13` extra in `pyproject.toml`. That is the only version to type. -2. Check `ci/versions.yml`. The `build` pin is the current major and the - `prev_build` pin is the prior major. Neither toolkit pin may sit below its - floor's major.minor, or CI builds a configuration that the build rejects. A - toolkit ahead of the floor is the bump window described below. The - pre-commit hook `check-cuda-core-bindings-floor` - (`toolshed/check_cuda_core_bindings_floor.py`) checks the pins. It also +2. Check each `cuda.bindings.package_roots..toolkit_version` in + `ci/versions.yml`. Neither toolkit pin may sit below the corresponding + floor's major.minor, or CI builds a configuration that the build rejects. + A toolkit ahead of the floor is the bump window described below. The + pre-commit hook `check-bindings-config` checks these pins against the floors. + The separate `check-cuda-core-bindings-floor` hook + (`toolshed/check_cuda_core_bindings_floor.py`) validates the extras and checks that no documentation page spells a floor out by hand. 3. Add to the release notes a "Breaking Changes" entry that names the new floors. The support-policy table in `docs/source/support.rst` reads the diff --git a/cuda_core/cuda/core/_bindings_floor.py b/cuda_core/cuda/core/_bindings_floor.py index bc915916880..9251c4408d2 100644 --- a/cuda_core/cuda/core/_bindings_floor.py +++ b/cuda_core/cuda/core/_bindings_floor.py @@ -23,8 +23,10 @@ - ``cuda/core/__init__.py`` checks the installed cuda-bindings against that record with :func:`check_installed_bindings`. - The documentation reads the floors into substitutions in ``docs/source/conf.py``. -- ``toolshed/check_cuda_core_bindings_floor.py``, a pre-commit hook, checks - that the CI toolkit pins in ``ci/versions.yml`` sit in the minors of the floors. +- The ``check-bindings-config`` pre-commit hook checks that each registered + toolkit in ``ci/versions.yml`` is not below its corresponding floor + major.minor. The separate ``check-cuda-core-bindings-floor`` hook validates + the extras and prevents documentation from hardcoding the floors. The build and the tools above load this module by file path. It therefore uses the standard library only and must not import anything from ``cuda``. diff --git a/cuda_core/tests/test_bindings_floor.py b/cuda_core/tests/test_bindings_floor.py index 05b218722f3..d70ea8acfa9 100644 --- a/cuda_core/tests/test_bindings_floor.py +++ b/cuda_core/tests/test_bindings_floor.py @@ -5,11 +5,11 @@ """Tests for the cuda-bindings version floor in cuda/core/_bindings_floor.py. The tests cover the floor as read from the pyproject extras and the import-time check -built on it. They also cover the consistency hook that guards ci/versions.yml and the docs. +built on it. They also cover the consistency hook that guards the pyproject extras and the docs. The tests check source-tree properties and pure functions and need no GPU. The file also runs with --noconftest, which skips the CUDA setup in conftest.py. The consistency -tests read pyproject.toml and ci/versions.yml from the checkout. They need the source +tests read pyproject.toml and documentation from the checkout. They need the source tree next to the tests. Every CI job that runs tests/ has it. pytest tests/test_bindings_floor.py -v --noconftest @@ -349,7 +349,7 @@ def test_the_checkout_is_consistent(self, hook): @pytest.mark.agent_authored(model="claude-fable-5-1") def test_a_malformed_extra_fails_the_hook(self, hook, tmp_path, capsys): - # check() returns before it reads ci/versions.yml or the docs, so the tree needs only these two files. + # check() returns before it reads the docs, so the tree needs only these two files. core = tmp_path / "cuda_core" / "cuda" / "core" core.mkdir(parents=True) shutil.copy(CUDA_CORE / "cuda" / "core" / "_bindings_floor.py", core) @@ -359,20 +359,6 @@ def test_a_malformed_extra_fails_the_hook(self, hook, tmp_path, capsys): assert hook.main(["--repo-root", str(tmp_path)]) == 1 assert "pyproject.toml: the 'cu13' extra must pin cuda-bindings" in capsys.readouterr().err - @pytest.mark.agent_authored(model="claude-fable-5-1") - def test_ci_toolkit_pins_must_not_sit_below_the_floors_minor(self, hook): - floors = {12: (12, 9, 8), 13: (13, 4, 1)} - good = 'cuda:\n build:\n version: "13.4.2"\n prev_build:\n version: "12.9.1"\n' - assert hook.ci_pin_problems(floors, good) == [] - assert hook.ci_pin_problems(floors, good.replace("13.4.2", "13.5.0")) == [] # the toolkit-bump window - stale = good.replace("13.4.2", "13.3.0") - (problem,) = hook.ci_pin_problems(floors, stale) - assert "cuda.build.version is 13.3 but the CUDA 13 floor is cuda-bindings 13.4.1" in problem - (problem,) = hook.ci_pin_problems({13: (13, 4, 1)}, good) - assert "pins CUDA 12, which has no cu12 extra" in problem - (problem,) = hook.ci_pin_problems(floors, 'cuda:\n build:\n version: "13.4.2"\n') - assert "no build or prev_build toolkit pin for CUDA 12" in problem - @pytest.mark.agent_authored(model="claude-fable-5-1") def test_docs_must_use_the_substitutions(self, hook, tmp_path): docs = tmp_path / "cuda_core" / "docs" / "source" diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index a2afff99469..7d073bdca34 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -2946,6 +2946,7 @@ def test_mempool_watermark_reset(memory_resource_factory, kind): mr.close() +@pytest.mark.thread_unsafe(reason="CUDA driver synchronization crash with concurrent pool cleanup; see gh-3350") @pytest.mark.agent_authored(model="claude-opus-5-5") def test_mempool_trim(memory_resource_factory): device = Device() diff --git a/toolshed/check_cuda_core_bindings_floor.py b/toolshed/check_cuda_core_bindings_floor.py index c01a2d6fea1..bdf6f581bed 100644 --- a/toolshed/check_cuda_core_bindings_floor.py +++ b/toolshed/check_cuda_core_bindings_floor.py @@ -11,12 +11,7 @@ 1. The extras parse: each `cu` extra pins exactly `cuda-bindings[...]>=..,<`. -2. ci/versions.yml builds each major against a CUDA Toolkit of at least the - floor's major.minor. A toolkit below the floor's minor cannot build the - floor's cuda-bindings, because the build requires the header that - cuda-bindings was generated from. A toolkit ahead of the floor is the - toolkit-bump window described in cuda_core/AGENTS.md. -3. No documentation page spells a floor out by hand. docs/source/conf.py +2. No documentation page spells a floor out by hand. docs/source/conf.py provides |cuda-bindings-floor-cu12| and |cuda-bindings-floor-cu13|. Release notes are history and are exempt. @@ -35,10 +30,8 @@ REPO_ROOT = Path(__file__).resolve().parents[1] FLOOR_MODULE = Path("cuda_core", "cuda", "core", "_bindings_floor.py") PYPROJECT = Path("cuda_core", "pyproject.toml") -CI_VERSIONS = Path("ci", "versions.yml") DOCS_SOURCE = Path("cuda_core", "docs", "source") -_CI_PIN_RE = re.compile(r"^\s+(build|prev_build):\s*\n\s+version:\s*\"(\d+)\.(\d+)(?:\.\d+)?\"", re.M) # `cuda-bindings >= 13.4.1`, ``cuda-bindings`` 13.4.1 or later, ... (release notes are exempt). _HAND_WRITTEN_FLOOR_RE = re.compile(r"cuda-bindings[`'\" ]{0,4}(?:>=\s*)?\d+\.\d+\.\d+") @@ -58,27 +51,6 @@ def read_floors(repo_root: Path) -> dict[int, tuple[int, int, int]]: return load_floor_module(repo_root).floors_from_extras(extras) -def ci_pin_problems(floors: dict[int, tuple[int, int, int]], versions_yml: str) -> list[str]: - """Toolkit pins in ci/versions.yml that sit below the floor's major.minor.""" - pins = {int(major): (int(major), int(minor), key) for key, major, minor in _CI_PIN_RE.findall(versions_yml)} - problems = [] - for major, floor in floors.items(): - if major not in pins: - problems.append(f"{CI_VERSIONS}: no build or prev_build toolkit pin for CUDA {major} (floor {floor})") - continue - pinned_major, pinned_minor, key = pins[major] - if pinned_major != floor[0] or pinned_minor < floor[1]: - problems.append( - f"{CI_VERSIONS}: cuda.{key}.version is {pinned_major}.{pinned_minor} but the CUDA {major} floor " - f"is cuda-bindings {'.'.join(map(str, floor))}. The toolkit must not sit below the floor's " - "major.minor. A toolkit ahead of the floor is the toolkit-bump window. See cuda_core/AGENTS.md" - ) - for major in pins: - if major not in floors: - problems.append(f"{CI_VERSIONS}: pins CUDA {major}, which has no cu{major} extra in {PYPROJECT}") - return problems - - def docs_problems(repo_root: Path) -> list[str]: """Documentation pages that spell out a floor by hand. Release notes are exempt.""" problems = [] @@ -96,11 +68,10 @@ def docs_problems(repo_root: Path) -> list[str]: def check(repo_root: Path) -> list[str]: try: - floors = read_floors(repo_root) + read_floors(repo_root) except ValueError as exc: return [f"{PYPROJECT}: {exc}"] - problems = ci_pin_problems(floors, (repo_root / CI_VERSIONS).read_text(encoding="utf-8")) - problems += docs_problems(repo_root) + problems = docs_problems(repo_root) return problems