diff --git a/.github/actions/doc_preview/action.yml b/.github/actions/doc_preview/action.yml index a0c7f212297..3a4373b3a63 100644 --- a/.github/actions/doc_preview/action.yml +++ b/.github/actions/doc_preview/action.yml @@ -20,7 +20,7 @@ runs: steps: - name: Deploy doc preview if: ${{ startsWith(github.ref_name, 'pull-request/') }} - uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0 + uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0 with: git-config-name: cuda-python-bot git-config-email: cuda-python-bot@users.noreply.github.com diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index e28fbd5dd87..414b94242b6 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -43,6 +43,11 @@ on: required: false default: "gh-pages" type: string + bindings-package: + description: "Resolved CUDA bindings package JSON" + required: false + default: "" + type: string jobs: build: @@ -62,23 +67,39 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 - ref: ${{ inputs.git-tag }} + ref: ${{ inputs.is-release && format('refs/tags/{0}', inputs.git-tag) || inputs.git-tag }} + + # Release sources and workflow tools can come from different commits. + # Keep the shared CI helpers at this workflow revision while reading + # package paths and toolkit settings from the checked-out release tag. + - name: Checkout docs control plane + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + path: .ci-control - - name: Read build CTK version + - name: Install CI tool dependencies + run: python3 -m pip install -e ./.ci-control/ci + + - name: Resolve CUDA bindings package + env: + BINDINGS_PACKAGE: ${{ inputs.bindings-package }} + IS_RELEASE: ${{ inputs.is-release }} + RELEASE_TAG: ${{ inputs.git-tag }} run: | - if [[ -f ci/versions.yml ]]; then - BUILD_CTK_VER=$(yq '.cuda.build.version' ci/versions.yml) - elif [[ -f ci/versions.json ]]; then - BUILD_CTK_VER=$(jq -r '.cuda.build.version' ci/versions.json) + set -euo pipefail + if [[ -n "$BINDINGS_PACKAGE" ]]; then + resolved_bindings_package="$BINDINGS_PACKAGE" + elif [[ "${IS_RELEASE}" == "true" ]]; then + resolved_bindings_package=$(cd .ci-control && python3 -m ci.tools.bindings_config \ + --release-tag "$RELEASE_TAG" \ + --release-source-root ..) else - echo "error: cannot find ci/versions.yml or ci/versions.json" >&2 - exit 1 - fi - if [[ ! "${BUILD_CTK_VER}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "error: derived CTK build version ${BUILD_CTK_VER} does not match MAJOR.MINOR.MICRO" >&2 - exit 1 + resolved_bindings_package=$(cd .ci-control && python3 -m ci.tools.bindings_config \ + --release-status current) fi - echo "BUILD_CTK_VER=${BUILD_CTK_VER}" >> "$GITHUB_ENV" + (cd .ci-control && python3 -m ci.tools.bindings_config \ + write-github-env "$GITHUB_ENV" <<< "$resolved_bindings_package") # TODO: This workflow runs on GH-hosted runner and cannot use the proxy cache @@ -86,7 +107,7 @@ jobs: uses: conda-incubator/setup-miniconda@be893c923ea9cf1cf7cd510fbdde27c7e18cbdcb # v4.1.0 with: activate-environment: cuda-python-docs - environment-file: ./cuda_python/docs/environment-docs.yml + environment-file: ./.ci-control/cuda_python/docs/environment-docs.yml miniforge-version: latest conda-remove-defaults: "true" python-version: 3.12 @@ -106,24 +127,16 @@ jobs: cuda-version: ${{ env.BUILD_CTK_VER }} - name: Set environment variables + env: + IS_RELEASE: ${{ inputs.is-release }} + RELEASE_TAG: ${{ inputs.git-tag }} run: | PYTHON_VERSION_FORMATTED="312" # see above REPO_DIR=$(pwd) - # Release docs may check out tags from before the source-root rename. - if [[ -d cuda_bindings_13 ]]; then - CUDA_BINDINGS_SOURCE_DIR=cuda_bindings_13 - elif [[ -d cuda_bindings ]]; then - CUDA_BINDINGS_SOURCE_DIR=cuda_bindings - else - echo "error: cannot find the cuda.bindings source directory" >&2 - exit 1 - fi - echo "CUDA_BINDINGS_SOURCE_DIR=${CUDA_BINDINGS_SOURCE_DIR}" >> "$GITHUB_ENV" - - if [[ ${{ inputs.is-release }} == "true" ]]; then + if [[ "$IS_RELEASE" == "true" ]]; then FILE_HASH="*" - DOCS_GITHUB_REF="${{ inputs.git-tag }}" + DOCS_GITHUB_REF="$RELEASE_TAG" if [[ -z "${DOCS_GITHUB_REF}" ]]; then DOCS_GITHUB_REF="${GITHUB_REF_NAME}" fi @@ -132,6 +145,8 @@ jobs: DOCS_GITHUB_REF=$(python ci/tools/checkout_source_links.py --github-ref) fi + echo "CUDA_BINDINGS_SOURCE_DIR=${BINDINGS_PACKAGE_ROOT}" >> "$GITHUB_ENV" + # make outputs from the previous job as env vars CUDA_CORE_ARTIFACT_BASENAME="cuda-core-python${PYTHON_VERSION_FORMATTED}-linux-64" echo "CUDA_PYTHON_DOCS_GITHUB_REF=${DOCS_GITHUB_REF}" >> $GITHUB_ENV @@ -141,22 +156,27 @@ jobs: CUDA_BINDINGS_ARTIFACT_BASENAME="cuda-bindings-python${PYTHON_VERSION_FORMATTED}-cuda${BUILD_CTK_VER}-linux-64" echo "CUDA_BINDINGS_ARTIFACT_BASENAME=${CUDA_BINDINGS_ARTIFACT_BASENAME}" >> $GITHUB_ENV echo "CUDA_BINDINGS_ARTIFACT_NAME=${CUDA_BINDINGS_ARTIFACT_BASENAME}-${FILE_HASH}" >> $GITHUB_ENV - echo "CUDA_BINDINGS_ARTIFACTS_DIR=$(realpath "$REPO_DIR/$CUDA_BINDINGS_SOURCE_DIR/dist")" >> $GITHUB_ENV + echo "CUDA_BINDINGS_ARTIFACTS_DIR=$(realpath "$REPO_DIR/${BINDINGS_PACKAGE_ROOT}/dist")" >> $GITHUB_ENV + CUDA_PYTHON_ARTIFACT_NAME="cuda-python-wheel-cuda${BUILD_CTK_VER}" + echo "CUDA_PYTHON_ARTIFACT_NAME=${CUDA_PYTHON_ARTIFACT_NAME}" >> $GITHUB_ENV - name: Download cuda-python build artifacts + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-python' }} uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: - name: cuda-python-wheel + name: ${{ env.CUDA_PYTHON_ARTIFACT_NAME }} path: . run-id: ${{ inputs.run-id }} github-token: ${{ github.token }} - name: Display structure of downloaded cuda-python artifacts + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-python' }} run: | pwd ls -lahR . - name: Download cuda-pathfinder build artifacts + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-pathfinder' || inputs.component == 'cuda-core' }} uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: cuda-pathfinder-wheel @@ -165,6 +185,7 @@ jobs: github-token: ${{ github.token }} - name: Display structure of downloaded cuda-pathfinder artifacts + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-pathfinder' || inputs.component == 'cuda-core' }} run: | pwd ls -lahR cuda_pathfinder @@ -177,7 +198,7 @@ jobs: path: ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }} - name: Download cuda.bindings build artifacts - if: ${{ inputs.is-release }} + if: ${{ inputs.is-release && (inputs.component == 'all' || inputs.component == 'cuda-bindings' || inputs.component == 'cuda-core') }} uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: pattern: ${{ env.CUDA_BINDINGS_ARTIFACT_NAME }} @@ -187,6 +208,7 @@ jobs: github-token: ${{ github.token }} - name: Display structure of downloaded cuda.bindings artifacts + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-bindings' || inputs.component == 'cuda-core' }} run: | pwd ls -lahR $CUDA_BINDINGS_ARTIFACTS_DIR @@ -199,7 +221,7 @@ jobs: path: ${{ env.CUDA_CORE_ARTIFACTS_DIR }} - name: Download cuda.core build artifacts - if: ${{ inputs.is-release }} + if: ${{ inputs.is-release && (inputs.component == 'all' || inputs.component == 'cuda-core') }} uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: pattern: ${{ env.CUDA_CORE_ARTIFACT_NAME }} @@ -209,26 +231,37 @@ jobs: github-token: ${{ github.token }} - name: Display structure of downloaded cuda.core build artifacts + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-core' }} run: | pwd ls -lahR $CUDA_CORE_ARTIFACTS_DIR - - name: Install all packages + - name: Install cuda-pathfinder + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-pathfinder' || inputs.component == 'cuda-core' }} run: | pushd cuda_pathfinder pip install *.whl popd + - name: Install cuda.bindings + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-bindings' || inputs.component == 'cuda-core' }} + run: | pushd "${CUDA_BINDINGS_ARTIFACTS_DIR}" pip install *.whl popd + - name: Install cuda.core + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-core' }} + run: | pushd "${CUDA_CORE_ARTIFACTS_DIR}" pip install *.whl popd - # Subpackages are already installed from CI artifacts above. - # --no-deps avoids re-resolving cuda-core from PyPI during tag releases. + - name: Install cuda-python + if: ${{ !inputs.is-release || inputs.component == 'all' || inputs.component == 'cuda-python' }} + run: | + # Documentation builds do not exercise runtime dependencies. + # --no-deps also avoids resolving artifacts absent from focused tag runs. pip install --no-deps cuda_python*.whl # PR CI uses pull-request/ refs regardless of the target branch. @@ -252,9 +285,11 @@ jobs: - name: Build all docs if: ${{ inputs.component == 'all' }} + env: + IS_RELEASE: ${{ inputs.is-release }} run: | pushd cuda_python/docs/ - if [[ "${{ inputs.is-release }}" == "false" ]]; then + if [[ "$IS_RELEASE" == "false" ]]; then ./build_all_docs.sh latest-only else ./build_all_docs.sh @@ -271,13 +306,20 @@ jobs: - name: Build component docs if: ${{ inputs.component != 'all' }} + env: + DOCS_COMPONENT: ${{ inputs.component }} + IS_RELEASE: ${{ inputs.is-release }} run: | - COMPONENT=$(echo "${{ inputs.component }}" | tr '-' '_') - if [[ "${{ inputs.component }}" == "cuda-bindings" ]]; then - COMPONENT="${CUDA_BINDINGS_SOURCE_DIR}" + case "$DOCS_COMPONENT" in + cuda-bindings|cuda-core|cuda-pathfinder|cuda-python) ;; + *) echo "error: unsupported docs component: $DOCS_COMPONENT" >&2; exit 1 ;; + esac + COMPONENT="${DOCS_COMPONENT//-/_}" + if [[ "$DOCS_COMPONENT" == "cuda-bindings" ]]; then + COMPONENT="${BINDINGS_PACKAGE_ROOT}" fi - pushd ${COMPONENT}/docs/ - if [[ "${{ inputs.is-release }}" == "false" ]]; then + pushd "${COMPONENT}/docs/" + if [[ "$IS_RELEASE" == "false" ]]; then ./build_docs.sh latest-only else ./build_docs.sh @@ -290,13 +332,13 @@ jobs: fi ls -l build popd - if [[ "${{ inputs.component }}" != "cuda-python" ]]; then - TARGET="${{ inputs.component }}" - mkdir -p artifacts/docs/${TARGET} + if [[ "$DOCS_COMPONENT" != "cuda-python" ]]; then + TARGET="$DOCS_COMPONENT" + mkdir -p "artifacts/docs/${TARGET}" else TARGET="" fi - mv ${COMPONENT}/docs/build/html/* artifacts/docs/${TARGET} + mv "${COMPONENT}"/docs/build/html/* "artifacts/docs/${TARGET}" # Check links and anchors in the rendered HTML built above. # Links in authored Markdown and reST files are checked separately by the @@ -363,7 +405,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 - ref: ${{ inputs.git-tag }} + ref: ${{ inputs.is-release && format('refs/tags/{0}', inputs.git-tag) || inputs.git-tag }} - name: Download docs deployment payload uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index be457a60d24..060bab03795 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -60,7 +60,7 @@ jobs: - name: Run CI tools unit tests run: | - python -m pip install pytest pyyaml==6.0.3 + python -m pip install -e './ci[test]' # Standalone CI tool tests; skip repo-root conftest.py (imports cuda.pathfinder). python -m pytest -v --noconftest ci/tools/tests @@ -70,26 +70,36 @@ jobs: outputs: RUN_ID: ${{ steps.find.outputs.run_id }} HEAD_SHA: ${{ steps.find.outputs.head_sha }} - CUDA_BUILD_VER: ${{ steps.find.outputs.cuda_build_ver }} + BINDINGS_CONFIG: ${{ steps.find.outputs.bindings_config }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 + - 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 @@ -99,20 +109,16 @@ 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" | tar -x -C "$snapshot" + BINDINGS_CONFIG=$(python -m ci.tools.bindings_config \ + --config "$snapshot/ci/versions.yml") echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT - echo "cuda_build_ver=$CUDA_BUILD_VER" >> $GITHUB_OUTPUT + echo "bindings_config=$BINDINGS_CONFIG" >> $GITHUB_OUTPUT # ── PyTorch interop tests ── @@ -128,7 +134,7 @@ jobs: with: build-type: nightly host-platform: linux-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-pytorch @@ -146,7 +152,7 @@ jobs: with: build-type: nightly host-platform: linux-aarch64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-pytorch @@ -164,7 +170,7 @@ jobs: with: build-type: nightly host-platform: win-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-pytorch @@ -184,7 +190,7 @@ jobs: with: build-type: nightly host-platform: linux-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda @@ -202,7 +208,7 @@ jobs: with: build-type: nightly host-platform: linux-aarch64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda @@ -220,7 +226,7 @@ jobs: with: build-type: nightly host-platform: win-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda @@ -240,7 +246,7 @@ jobs: with: build-type: nightly host-platform: linux-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda-mlir @@ -258,7 +264,7 @@ jobs: with: build-type: nightly host-platform: win-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-numba-cuda-mlir @@ -278,7 +284,7 @@ jobs: with: build-type: nightly host-platform: linux-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-cuda-core @@ -296,7 +302,7 @@ jobs: with: build-type: nightly host-platform: win-64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: nightly-cuda-core @@ -316,7 +322,7 @@ jobs: with: build-type: nightly host-platform: linux-aarch64 - build-ctk-ver: ${{ needs.find-wheels.outputs.CUDA_BUILD_VER }} + bindings-config: ${{ needs.find-wheels.outputs.BINDINGS_CONFIG }} run-id: ${{ needs.find-wheels.outputs.RUN_ID }} sha: ${{ needs.find-wheels.outputs.HEAD_SHA }} test-mode: standard diff --git a/.github/workflows/ci-pixi-source-test.yml b/.github/workflows/ci-pixi-source-test.yml index a09f2e2756f..a6456f72d33 100644 --- a/.github/workflows/ci-pixi-source-test.yml +++ b/.github/workflows/ci-pixi-source-test.yml @@ -11,14 +11,14 @@ # cython-test build mechanics drift (see #2182, #2183). This workflow is the # only thing that runs the pixi source build end to end. # -# Two tiers, to spend GPU minutes deliberately (GPUs are scarce): +# Three tiers, to spend GPU minutes deliberately (GPUs are scarce): # - build-smoke (PRs): CPU-only. Source-builds bindings + core, imports them, # builds the cython test extensions and checks placement. Catches the # compile / ABI / .so-placement regressions WITHOUT a GPU. -# - build-identity-roundtrip (nightly + manual): CPU-only. cu13 -> cu12 -> -# cu13 in one checkout, so build artifacts cannot be reused across CUDA -# majors. Kept off PRs to avoid adding another job to the org's shared -# concurrent-job quota; the fast unit tests in +# - build-identity-roundtrip (nightly + manual): CPU-only. The current ABI -> +# another configured ABI -> current ABI in one checkout, so build artifacts +# cannot be reused across CUDA majors. Kept off PRs to avoid adding another +# job to the org's shared concurrent-job quota; the fast unit tests in # cuda_core/tests/test_build_hooks.py cover the same intent per-PR. # - full-test (nightly + manual): GPU runner, full `pixi run test`. @@ -41,11 +41,15 @@ on: # Path-dependency metadata (including cuda_python_test_helpers) feeds the # solve, so it can break the source build without a pixi.toml edit. - "**/pyproject.toml" - - "cuda_bindings_13/build_hooks.py" + - "cuda_bindings*/build_hooks.py" - "cuda_core/build_hooks.py" - - "cuda_bindings_13/cuda/bindings/**" # generated bindings sources - - "cuda_bindings_13/tests/cython/**" + - "cuda_bindings*/cuda/bindings/**" # generated bindings sources + - "cuda_bindings*/docs/**" + - "cuda_bindings*/tests/cython/**" - "cuda_core/tests/cython/**" + - "ci/tools/bindings_config.py" + - "ci/tools/compute_ci_plan.py" + - "ci/pyproject.toml" - "ci/versions.yml" - "ci/pixi-version.env" - ".github/actions/setup-pixi/action.yml" @@ -53,9 +57,9 @@ on: workflow_dispatch: inputs: cuda-env: - description: "pixi environment to test (cu13 or cu12)." + description: "Registered pixi CUDA ABI environment to test (empty selects current)." type: string - default: "cu13" + default: "" defaults: run: @@ -73,7 +77,7 @@ env: jobs: # ── PR guard: CPU-only build + import + placement smoke ── build-smoke: - name: "build smoke (cu13, linux-64, CPU)" + name: "build smoke (selected CUDA packages, linux-64, CPU)" if: >- github.repository_owner == 'nvidia' && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') @@ -83,58 +87,176 @@ jobs: - name: Checkout ${{ github.event.repository.name }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - # Full history + tags so setuptools-scm derives the real (13.x) + # Full history + tags so setuptools-scm derives the real # package version; a shallow checkout yields 0.1.dev1, which trips - # cuda.core's "cuda.bindings 12.x or 13.x must be installed" guard. + # cuda.core's supported cuda.bindings-version guard. fetch-depth: 0 filter: blob:none # third_party/cccl provides the CCCL headers cuda.core compiles against. submodules: true + - name: Install CI tool dependencies + run: python3 -m pip install -e ./ci + - name: Setup pixi uses: ./.github/actions/setup-pixi - - name: Source-build + import + cython-placement smoke + - name: Select CUDA environments env: - CUDA_ENV: ${{ inputs.cuda-env || 'cu13' }} + BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} + EVENT_NAME: ${{ github.event_name }} + REQUESTED_CUDA_ENV: ${{ inputs.cuda-env || '' }} run: | - # pathfinder: pure-Python, no GPU. - pixi run -e "${CUDA_ENV}" test-pathfinder - - # bindings + core: force the source build (catches nvrtc/driver - # compile errors like #2182) and import them (catches ABI mismatches). - pixi run --manifest-path cuda_bindings_13 -e "${CUDA_ENV}" \ - python -c "import cuda.bindings.driver, cuda.bindings.nvrtc, cuda.bindings.runtime; print('bindings import OK')" - pixi run --manifest-path cuda_core -e "${CUDA_ENV}" \ - python -c "import cuda.core; print('core import OK')" - - # cython test extensions: build them and confirm each .so landed next - # to its .pyx in tests/cython (catches the placement regression #2180). - pixi run --manifest-path cuda_bindings_13 -e "${CUDA_ENV}" build-cython-tests - pixi run --manifest-path cuda_core -e "${CUDA_ENV}" build-cython-tests - for d in cuda_bindings_13/tests/cython cuda_core/tests/cython; do - if ! compgen -G "${d}/*.cpython-*.so" > /dev/null; then - echo "::error::no compiled cython test .so in ${d} (placement regression)" - exit 1 + bindings_packages=$(python3 -m ci.tools.bindings_config --package-roots) + selected_packages='[]' + docs_packages='[]' + + merge_packages() { + jq -cn --argjson left "$1" --argjson right "$2" \ + '$left + $right | unique_by(.package_root)' + } + + if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then + if [[ -n "${REQUESTED_CUDA_ENV}" ]]; then + selected_packages=$(jq -c --arg cuda_env "${REQUESTED_CUDA_ENV}" \ + '[.[] | select(.cuda_variant == $cuda_env)]' <<< "${bindings_packages}") + if [[ "${selected_packages}" == '[]' ]]; then + registered_envs=$(jq -r '[.[].cuda_variant] | join(", ")' <<< "${bindings_packages}") + echo "::error::cuda-env must be a registered pixi environment: ${registered_envs}" + exit 1 + fi + else + selected_packages=$(jq -c '[.[] | select(.release_status == "current")]' \ + <<< "${bindings_packages}") fi - done + else + merge_base=$(git merge-base HEAD "${BASE_SHA}") + workplan=$(python3 -m ci.tools.compute_ci_plan \ + --merge-base "${merge_base}" \ + --baseline-run-id pixi-source) + + # Keep package-specific work package-specific, while selecting the + # registered package for any ABI-level work. + selected_packages=$(jq -cn \ + --argjson registry "${bindings_packages}" \ + --argjson plan "${workplan}" ' + $registry + | map(. as $package | select( + ($plan.modules.bindings.package_roots[$package.package_root].needs_build // false) + or ($plan.modules.bindings.package_roots[$package.package_root].needs_test // false) + or ($plan.modules.python.package_roots[$package.package_root].needs_build // false) + or ($plan.modules.python.package_roots[$package.package_root].needs_test // false) + or ($plan.jobs.test_cuda_majors[$package.cuda_variant] // false) + )) + ') + + # Pixi manifests are deliberately ignored by the wheel planner. + # Add their package-local impact to its source/test decisions. The + # wheel planner also deliberately ignores documentation changes. + while IFS= read -r -d '' path; do + manifest_packages=$(jq -c --arg path "${path}" ' + [.[] as $package + | select( + $path == ($package.package_root + "/pixi.toml") + or $path == ($package.package_root + "/pixi.lock") + ) + | $package] + ' <<< "${bindings_packages}") + if [[ "${manifest_packages}" != '[]' ]]; then + selected_packages=$(merge_packages "${selected_packages}" "${manifest_packages}") + else + case "${path}" in + */pixi.toml|*/pixi.lock|pixi.toml|pixi.lock) + selected_packages=$(merge_packages "${selected_packages}" "${bindings_packages}") + ;; + esac + fi + + changed_docs_packages=$(jq -c --arg path "${path}" ' + [.[] as $package + | select($path | startswith($package.package_root + "/docs/")) + | $package] + ' <<< "${bindings_packages}") + if [[ "${changed_docs_packages}" != '[]' ]]; then + selected_packages=$(merge_packages "${selected_packages}" "${changed_docs_packages}") + docs_packages=$(merge_packages "${docs_packages}" "${changed_docs_packages}") + fi + done < <(git diff --no-renames --name-only -z "${merge_base}" HEAD) + fi + + if [[ "${selected_packages}" == '[]' ]]; then + echo "::error::no bindings package selected for source-build smoke tests" + exit 1 + fi + echo "BINDINGS_PACKAGES=${selected_packages}" | tee -a "$GITHUB_ENV" + { + echo '### Selected source builds' + jq -r '.[] | "- \(.package_root): CUDA \(.cuda_major), pixi \(.cuda_variant)"' <<< "$selected_packages" + } >> "$GITHUB_STEP_SUMMARY" + echo "BINDINGS_DOC_PACKAGES=${docs_packages}" | tee -a "$GITHUB_ENV" + + - name: Source-build + import + cython-placement smoke + run: | + while IFS= read -r bindings_package; do + BINDINGS_ROOT=$(jq -r '.package_root' <<< "${bindings_package}") + CUDA_ENV=$(jq -r '.cuda_variant' <<< "${bindings_package}") + echo "::group::Source-build ${BINDINGS_ROOT} (${CUDA_ENV})" + if [[ $(jq -r '.release_status' <<< "${bindings_package}") == maintenance ]]; then + python3 -m ci.tools.bindings_scm_version --package-root "${BINDINGS_ROOT}" --sha "$GITHUB_SHA" + fi + # Avoid carrying Cython outputs between bindings packages or CUDA ABIs. + rm -rf cuda_core/build/cython cuda_core/cython_debug + find cuda_core/cuda/core -type f -name '*.so' -delete + find "${BINDINGS_ROOT}/tests/cython" cuda_core/tests/cython \ + -maxdepth 1 -type f \( -name 'test_*.cpp' -o -name 'test_*.so' \) -delete + + # pathfinder: pure-Python, no GPU. + pixi run -e "${CUDA_ENV}" test-pathfinder + + # Force both source builds and import them to catch compile/ABI + # regressions such as #2182. cuda-core environments may use + # published bindings by design; wheel CI covers local pairing. + pixi run --manifest-path "${BINDINGS_ROOT}" -e "${CUDA_ENV}" \ + python -c "import cuda.bindings.driver, cuda.bindings.nvrtc, cuda.bindings.runtime; print('bindings import OK')" + pixi run --manifest-path cuda_core -e "${CUDA_ENV}" \ + python -c "import cuda.core; print('core import OK')" + pixi run --manifest-path "${BINDINGS_ROOT}" -e "${CUDA_ENV}" build-cython-tests + pixi run --manifest-path cuda_core -e "${CUDA_ENV}" build-cython-tests + + # Confirm each extension landed next to its .pyx (catches #2180). + for d in "${BINDINGS_ROOT}/tests/cython" cuda_core/tests/cython; do + if ! compgen -G "${d}/*.cpython-*.so" > /dev/null; then + echo "::error::no compiled cython test .so in ${d} (placement regression)" + exit 1 + fi + done + echo "::endgroup::" + done < <(jq -c '.[]' <<< "${BINDINGS_PACKAGES}") echo "cython test extensions placed correctly" + - name: Build changed bindings documentation + if: ${{ env.BINDINGS_DOC_PACKAGES != '[]' }} + run: | + while IFS= read -r bindings_package; do + bindings_root=$(jq -r '.package_root' <<< "${bindings_package}") + pixi run --manifest-path "${bindings_root}" -e docs build-docs + done < <(jq -c '.[]' <<< "${BINDINGS_DOC_PACKAGES}") + # ── Nightly guard: build artifacts must be CUDA-major aware ── # # Neither Cython nor setuptools tracks the CUDA major in its own up-to-date # check: Cython does not hash `compile_time_env`, and an editable install's # .so is named by the Python ABI tag alone. Before build_hooks keyed them, - # a cu13 build followed by a cu12 build in the same checkout failed while - # compiling cu13-generated C++ against CUDA 12 headers. + # a build for one ABI followed by another in the same checkout failed while + # compiling generated C++ against headers from the second ABI. # - # The round trip (not just cu13 -> cu12) is what catches the second half: - # coming back to cu13 must not silently reuse the cu12 extension. + # The round trip is what catches the second half: coming back to the current + # ABI must not silently reuse the other ABI's extension. # - # cuda_core only: cuda-bindings cannot be source-built in its cu12 - # environment at all, for reasons unrelated to stale artifacts. + # cuda_core only: not every bindings package can be source-built in every ABI + # environment, for reasons unrelated to stale artifacts. build-identity-roundtrip: - name: "cu13 -> cu12 -> cu13 round trip (linux-64, CPU)" + name: "current -> maintenance -> current round trip (linux-64, CPU)" if: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository_owner == 'nvidia' }} runs-on: ubuntu-latest timeout-minutes: 60 @@ -142,26 +264,36 @@ jobs: - name: Checkout ${{ github.event.repository.name }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - # Full history + tags so setuptools-scm derives the real (13.x) + # Full history + tags so setuptools-scm derives the real # package version; a shallow checkout yields 0.1.dev1, which trips - # cuda.core's "cuda.bindings 12.x or 13.x must be installed" guard. + # cuda.core's supported cuda.bindings-version guard. fetch-depth: 0 filter: blob:none # third_party/cccl provides the CCCL headers cuda.core compiles against. submodules: true + - name: Install CI tool dependencies + run: python3 -m pip install -e ./ci + - name: Setup pixi uses: ./.github/actions/setup-pixi - - name: Build cu13, then cu12, then cu13 again in one checkout + - name: Build current, another ABI, then current again in one checkout run: | - for cuda_env in cu13 cu12 cu13; do + current_package=$(python3 -m ci.tools.bindings_config --release-status current) + bindings_packages=$(python3 -m ci.tools.bindings_config --package-roots) + current_env=$(jq -r '.cuda_variant' <<< "${current_package}") + other_env=$(jq -r --arg current_env "${current_env}" \ + '[.[] | select(.cuda_variant != $current_env) | .cuda_variant] | first // empty' \ + <<< "${bindings_packages}") + + for cuda_env in "${current_env}" "${other_env}" "${current_env}"; do echo "::group::${cuda_env}" pixi run --manifest-path cuda_core -e "${cuda_env}" \ python -c "import cuda.core; print('core import OK')" echo "::endgroup::" done - # The last build was cu13, and each major must have kept its own + # The last build was current, and each ABI must have kept its own # generated sources rather than overwriting the other's. # Stamp is ABI-scoped (.build-config.). Unset and gnu # both record as "gnu"; llvm records as "llvm". @@ -172,21 +304,21 @@ jobs: exit 1 fi stamp=$(<"${stamps[0]}") - if [[ ! "${stamp}" =~ ^cu13-(gnu|llvm)-(debug|opt)(-cov)?$ ]]; then - echo "::error::build stamp is '${stamp}', expected cu13-{gnu|llvm}-{debug|opt}" + if [[ ! "${stamp}" =~ ^${current_env}-(gnu|llvm)-(debug|opt)(-cov)?$ ]]; then + echo "::error::build stamp is '${stamp}', expected ${current_env}-{gnu|llvm}-{debug|opt}" exit 1 fi - suffix="${stamp#cu13-}" - for major in cu12 cu13; do - if [ ! -d "cuda_core/build/cython/${major}-${suffix}" ]; then - echo "::error::no ${major}-${suffix} generated-source directory" + suffix="${stamp#"${current_env}-"}" + for cuda_env in "${other_env}" "${current_env}"; do + if [ ! -d "cuda_core/build/cython/${cuda_env}-${suffix}" ]; then + echo "::error::no ${cuda_env}-${suffix} generated-source directory" exit 1 fi done # ── Nightly: full `pixi run test` on a GPU runner ── full-test: - name: "pixi run test (${{ inputs.cuda-env || 'cu13' }}, linux-64, GPU)" + name: "pixi run test (${{ inputs.cuda-env || 'current' }}, linux-64, GPU)" if: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository_owner == 'nvidia' }} runs-on: "linux-amd64-gpu-l4-latest-1" # same label scheme as test-wheel-linux.yml timeout-minutes: 90 @@ -203,21 +335,41 @@ jobs: run: | apt-get update # ca-certificates + git for checkout; libgl1/libegl1 for pyglet - # (pulled in by the test env). pixi itself is installed by setup-pixi. + # (pulled in by the test env). PyYAML and packaging drive the + # bindings registry resolver; pixi itself is installed by setup-pixi. apt-get install -y --no-install-recommends \ - ca-certificates git libgl1 libegl1 + ca-certificates git jq libgl1 libegl1 python3 python3-packaging python3-yaml - name: Checkout ${{ github.event.repository.name }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - # Full history + tags so setuptools-scm derives the real (13.x) + # Full history + tags so setuptools-scm derives the real # package version; a shallow checkout yields 0.1.dev1, which trips - # cuda.core's "cuda.bindings 12.x or 13.x must be installed" guard. + # cuda.core's supported cuda.bindings-version guard. fetch-depth: 0 filter: blob:none # third_party/cccl provides the CCCL headers cuda.core compiles against. submodules: true + - name: Select CUDA environment + env: + REQUESTED_CUDA_ENV: ${{ inputs.cuda-env || '' }} + run: | + if [[ -n "${REQUESTED_CUDA_ENV}" ]]; then + bindings_packages=$(python3 -m ci.tools.bindings_config --package-roots) + if ! jq -e --arg cuda_env "${REQUESTED_CUDA_ENV}" \ + 'any(.[]; .cuda_variant == $cuda_env)' <<< "${bindings_packages}" > /dev/null; then + registered_envs=$(jq -r '[.[].cuda_variant] | join(", ")' <<< "${bindings_packages}") + echo "::error::cuda-env must be a registered pixi environment: ${registered_envs}" + exit 1 + fi + cuda_env="${REQUESTED_CUDA_ENV}" + else + current_package=$(python3 -m ci.tools.bindings_config --release-status current) + cuda_env=$(jq -r '.cuda_variant' <<< "${current_package}") + fi + echo "CUDA_ENV=${cuda_env}" | tee -a "$GITHUB_ENV" + - name: Setup proxy cache uses: nv-gha-runners/setup-proxy-cache@main continue-on-error: true @@ -226,4 +378,4 @@ jobs: uses: ./.github/actions/setup-pixi - name: pixi run test - run: pixi run -e "${{ inputs.cuda-env || 'cu13' }}" test + run: pixi run -e "${CUDA_ENV}" test diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index aee89d41f2d..7b57ed28839 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -20,18 +20,25 @@ jobs: if: ${{ github.repository_owner == 'nvidia' }} runs-on: ubuntu-latest outputs: + 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: 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 + set -euo pipefail + current=$(python3 -m ci.tools.bindings_config --release-status current) + { + echo "bindings_root=$(jq -r '.package_root' <<< "$current")" + echo "cuda_ver=$(jq -r '.toolkit_version' <<< "$current")" + } >> "$GITHUB_OUTPUT" coverage-linux: - name: Coverage (Linux) + name: Coverage ${{ needs.coverage-vars.outputs.BINDINGS_ROOT }} + core (Linux) needs: [coverage-vars] runs-on: "linux-amd64-gpu-a100-latest-1" permissions: @@ -43,6 +50,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,10 +99,13 @@ jobs: - name: Set environment variables env: - BUILD_CUDA_VER: ${{ env.CUDA_VER }} + BINDINGS_SOURCE: local + BINDINGS_SOURCE_DIR: ${{ env.BINDINGS_ROOT }} + DEFAULT_BINDINGS_SOURCE_DIR: ${{ env.BINDINGS_ROOT }} CUDA_VER: ${{ env.CUDA_VER }} HOST_PLATFORM: ${{ env.HOST_PLATFORM }} LOCAL_CTK: ${{ env.LOCAL_CTK }} + PATHFINDER_SOURCE: artifact PY_VER: ${{ env.PY_VER }} SHA: ${{ github.sha }} run: | @@ -142,8 +153,16 @@ 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/ + bindings_wheels=(./wheels/cuda_bindings*.whl) + test "${#bindings_wheels[@]}" -eq 1 + test -f "${bindings_wheels[0]}" + bindings_pyproject="./${BINDINGS_ROOT}/pyproject.toml" + if grep -q '^\[dependency-groups\][[:space:]]*$' "${bindings_pyproject}"; then + .venv/bin/python -m pip install "${bindings_wheels[0]}" --group "${bindings_pyproject}:test" + else + .venv/bin/python -m pip install "${bindings_wheels[0]}[test]" + fi - name: Constrain cuda-core to the local cuda-bindings wheel run: | @@ -188,7 +207,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 @@ -221,7 +240,7 @@ jobs: # Build Windows wheels on GitHub-hosted runner (has VS, no GPU) build-wheel-windows: - name: Build Wheels (Windows) + name: Build ${{ needs.coverage-vars.outputs.BINDINGS_ROOT }} + core for coverage (Windows) needs: [coverage-vars] runs-on: windows-2022 permissions: @@ -232,6 +251,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: Enable Git symlinks (Windows, must precede checkout) @@ -284,8 +304,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: | @@ -340,7 +359,7 @@ jobs: # Run coverage tests on self-hosted GPU runner (no VS needed, installs pre-built wheels) coverage-windows: - name: Coverage (Windows) + name: Coverage ${{ needs.coverage-vars.outputs.BINDINGS_ROOT }} + core (Windows) needs: [coverage-vars, build-wheel-windows] runs-on: "windows-amd64-gpu-a100-latest-1" permissions: @@ -350,6 +369,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: @@ -423,7 +443,15 @@ 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 + bindings_wheels=(./wheels/cuda_bindings*.whl) + test "${#bindings_wheels[@]}" -eq 1 + test -f "${bindings_wheels[0]}" + bindings_pyproject="./${BINDINGS_ROOT}/pyproject.toml" + if grep -q '^\[dependency-groups\][[:space:]]*$' "${bindings_pyproject}"; then + .venv/Scripts/python -m pip install "${bindings_wheels[0]}" --group "${bindings_pyproject}:test" + else + .venv/Scripts/python -m pip install "${bindings_wheels[0]}[test]" + fi .venv/Scripts/pip install --group ./cuda_core/pyproject.toml:test - name: Get install root @@ -448,7 +476,7 @@ jobs: --cwd "${{ steps.install-root.outputs.INSTALL_ROOT }}" \ -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 (with 8MB stack) continue-on-error: true diff --git a/.github/workflows/test-wheel-linux.yml b/.github/workflows/test-wheel-linux.yml index 8afa3412184..e1dac360db2 100644 --- a/.github/workflows/test-wheel-linux.yml +++ b/.github/workflows/test-wheel-linux.yml @@ -13,7 +13,8 @@ on: host-platform: type: string required: true - build-ctk-ver: + bindings-config: + description: Normalized CUDA bindings package-root registry JSON. type: string required: true matrix_filter: @@ -52,17 +53,24 @@ defaults: jobs: compute-matrix: + if: >- + ${{ inputs.workplan == '' || + contains(fromJSON(inputs.workplan).jobs.test_cuda_majors.*, true) }} runs-on: ubuntu-latest env: + BINDINGS_CONFIG: ${{ inputs.bindings-config }} BUILD_TYPE: ${{ inputs.build-type }} + WORKPLAN: ${{ inputs.workplan }} + TEST_MODE: ${{ inputs.test-mode }} ARCH: ${{ (inputs.host-platform == 'linux-64' && 'amd64') || (inputs.host-platform == 'linux-aarch64' && 'arm64') }} outputs: MATRIX: ${{ steps.compute-matrix.outputs.MATRIX }} - OLD_BRANCH: ${{ steps.compute-matrix.outputs.OLD_BRANCH }} steps: - name: Checkout ${{ github.event.repository.name }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.sha || github.sha }} - name: Validate Test Type run: | @@ -83,27 +91,95 @@ jobs: # Read base matrix from YAML file for the specific architecture TEST_MATRIX=$(yq -o json ".linux[\"${MATRIX_TYPE}\"] | map(select(.ARCH == \"${ARCH}\"))" ci/test-matrix.yml) + BINDINGS_PACKAGES=$(jq -c '.package_roots' <<< "$BINDINGS_CONFIG") + CURRENT_BINDINGS_SOURCE_DIR=$(jq -r ' + .[] | select(.release_status == "current") | .package_root + ' <<< "$BINDINGS_PACKAGES") + + if [[ -z "$WORKPLAN" ]]; then + TEST_CUDA_MAJORS=$(jq -c ' + [.[].cuda_variant] | map({key: ., value: true}) | from_entries + ' <<< "$BINDINGS_PACKAGES") + PATHFINDER_SOURCE=artifact + else + TEST_CUDA_MAJORS=$(jq -c '.jobs.test_cuda_majors' <<< "$WORKPLAN") + PATHFINDER_SOURCE=$(jq -r '.sources.pathfinder' <<< "$WORKPLAN") + fi + # Apply matrix filter; reject custom DRIVER + FLAVOR=wsl (the # in-container driver swap doesn't work under WSL); add a # RUNNER_DRIVER field that maps any custom version back to # 'latest' (the install script swaps the driver itself, so we # need to land on the runner that ships with the most recent # pre-installed driver); wrap in include structure. - 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') + MATRIX=$(echo "$TEST_MATRIX" | jq -c \ + --argjson test_cuda_majors "$TEST_CUDA_MAJORS" \ + --argjson bindings_packages "$BINDINGS_PACKAGES" \ + --arg default_bindings_source_dir "$CURRENT_BINDINGS_SOURCE_DIR" \ + --arg pathfinder_source "$PATHFINDER_SOURCE" \ + --argjson workplan "${WORKPLAN:-null}" \ + --arg test_mode "$TEST_MODE" \ + '${{ inputs.matrix_filter }} + | map( + . as $row + | ("cu" + ($row.CUDA_VER | split(".")[0])) as $cuda_variant + | ($bindings_packages | map(select(.toolkit_version == $row.CUDA_VER)) | first) as $bindings_package + | $row + { + CUDA_VARIANT: $cuda_variant, + BINDINGS_SOURCE: (if $bindings_package == null then "floor" else "local" end), + BINDINGS_SOURCE_DIR: ($bindings_package.package_root // ""), + DEFAULT_BINDINGS_SOURCE_DIR: $default_bindings_source_dir, + PATHFINDER_SOURCE: $pathfinder_source + } + ) + | map(select($test_cuda_majors[.CUDA_VARIANT] == true)) + | map(. as $row | . + { + SUITES: (if $test_mode != "standard" then $test_mode else + [if $workplan == null or $workplan.modules.pathfinder.needs_test then "pathfinder" else empty end, + if $row.BINDINGS_SOURCE == "local" and ($workplan == null or $workplan.modules.bindings.package_roots[$row.BINDINGS_SOURCE_DIR].needs_test) then "bindings" + ($row.CUDA_VER | split(".")[0]) else empty end, + if $workplan == null or $workplan.modules.core.cuda_majors[$row.CUDA_VARIANT].needs_test then "core" + ($row.CUDA_VER | split(".")[0]) else empty end, + if $row.BINDINGS_SOURCE == "local" and ($workplan == null or $workplan.modules.python.package_roots[$row.BINDINGS_SOURCE_DIR].needs_test) then "meta" + ($row.CUDA_VER | split(".")[0]) else empty end] | join("+") end) + }) + | map(select(.SUITES != "")) + | 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 after CUDA-major workplan filtering\n" | halt_error(1) end') + + # Compatibility nightly modes intentionally use published floor bindings. + # Standard PR/release runs must retain each selected local bindings line. + if [[ "$TEST_MODE" == "standard" && "$BUILD_TYPE" == "pull-request" ]]; then + jq -en --argjson packages "$BINDINGS_PACKAGES" \ + --argjson enabled "$TEST_CUDA_MAJORS" --argjson matrix "$MATRIX" \ + --argjson workplan "${WORKPLAN:-null}" ' + [$packages[] | . as $package + | select($enabled[.cuda_variant] == true) + | select($workplan == null or + $workplan.modules.bindings.package_roots[.package_root].needs_test or + $workplan.modules.python.package_roots[.package_root].needs_test) + | select(all($matrix.include[]; + .BINDINGS_SOURCE != "local" or .BINDINGS_SOURCE_DIR != $package.package_root)) + | .package_root] as $missing + | if $missing | length == 0 then true else + error("filtered test matrix omits required local-wheel coverage: \($missing | join(", "))") end + ' > /dev/null + fi - echo "MATRIX=${MATRIX}" | tee --append "${GITHUB_OUTPUT}" + { + echo '### Selected wheel test coverage' + echo '| Suites | Python | CTK | Toolkit source | Bindings source | GPU |' + echo '| --- | --- | --- | --- | --- | --- |' + jq -r '.include[] | "| \(.SUITES) | \(.PY_VER) | \(.CUDA_VER) | \(if .LOCAL_CTK == "1" then "local CTK" else "wheels" end) | \(if .BINDINGS_SOURCE == "local" then .BINDINGS_SOURCE_DIR else "published floor" end) | \(.GPU) |"' <<< "$MATRIX" + } >> "$GITHUB_STEP_SUMMARY" - # This job has yq already installed, so let's do it here - OLD_BRANCH=$(yq '.backport_branch' ci/versions.yml) - echo "OLD_BRANCH=${OLD_BRANCH}" >> "$GITHUB_OUTPUT" + echo "MATRIX=${MATRIX}" | tee --append "${GITHUB_OUTPUT}" test: env: TEST_PATHFINDER: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.pathfinder.needs_test }} - TEST_BINDINGS: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.bindings.needs_test }} - TEST_CORE: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.core.needs_test }} - TEST_PYTHON: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.python.needs_test }} - name: Python ${{ matrix.PY_VER }}, CUDA ${{ matrix.CUDA_VER }} (${{ (matrix.LOCAL_CTK == '1' && 'local') || 'wheels' }}), GPU ${{ matrix.GPU }}${{ matrix.GPU_COUNT != '1' && format(' (x{0})', matrix.GPU_COUNT) || '' }}${{ matrix.FLAVOR && format(', {0}', matrix.FLAVOR) || '' }}${{ matrix.ENV.TORCH_VER && format(', {0}+{1}', matrix.ENV.TORCH_VER, matrix.ENV.TORCH_CUDA) || '' }}${{ matrix.ENV.MODE == 'nightly-numba-cuda' && ', latest' || '' }} + TEST_BINDINGS: ${{ inputs.workplan == '' || (matrix.BINDINGS_SOURCE == 'local' && fromJSON(inputs.workplan).modules.bindings.package_roots[matrix.BINDINGS_SOURCE_DIR].needs_test) }} + TEST_CORE: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.core.cuda_majors[matrix.CUDA_VARIANT].needs_test }} + TEST_PYTHON: ${{ inputs.workplan == '' || (matrix.BINDINGS_SOURCE == 'local' && fromJSON(inputs.workplan).modules.python.package_roots[matrix.BINDINGS_SOURCE_DIR].needs_test) }} + name: ${{ matrix.SUITES }} / py${{ matrix.PY_VER }}, CTK${{ matrix.CUDA_VER }} (${{ (matrix.LOCAL_CTK == '1' && 'local CTK') || 'wheels' }}; bindings ${{ matrix.BINDINGS_SOURCE == 'local' && 'PR' || 'floor' }}), GPU ${{ matrix.GPU }}${{ matrix.GPU_COUNT != '1' && format(' (x{0})', matrix.GPU_COUNT) || '' }}${{ matrix.FLAVOR && format(', {0}', matrix.FLAVOR) || '' }}${{ matrix.ENV.TORCH_VER && format(', {0}+{1}', matrix.ENV.TORCH_VER, matrix.ENV.TORCH_CUDA) || '' }}${{ matrix.ENV.MODE == 'nightly-numba-cuda' && ', latest' || '' }} timeout-minutes: 60 needs: compute-matrix strategy: @@ -111,7 +187,8 @@ jobs: matrix: ${{ fromJSON(needs.compute-matrix.outputs.MATRIX) }} runs-on: "${{ matrix.FLAVOR || 'linux' }}-${{ matrix.ARCH }}-gpu-${{ matrix.GPU }}-${{ matrix.RUNNER_DRIVER }}-${{ matrix.GPU_COUNT }}" # The build stage could fail but we want the CI to keep moving. - if: ${{ github.repository_owner == 'nvidia' && !cancelled() }} + if: ${{ github.repository_owner == 'nvidia' && !cancelled() && + (inputs.workplan == '' || contains(fromJSON(inputs.workplan).jobs.test_cuda_majors.*, true)) }} # Our self-hosted runners require a container # TODO: use a different (nvidia?) container container: @@ -142,9 +219,8 @@ jobs: # for artifact fetching, graphics libs, g++ required for cffi in # example; util-linux for `nsenter` (custom-DRIVER rows re-exec # install_gpu_driver.sh onto the host through nsenter) - # yq: ci/tools/env-vars reads ci/versions.yml with it - dependencies: "jq wget libgl1 libegl1 g++ util-linux yq" - dependent_exes: "jq wget yq" + dependencies: "jq wget libgl1 libegl1 g++ util-linux" + dependent_exes: "jq wget" - name: Install GPU driver if: ${{ matrix.DRIVER != 'latest' && matrix.DRIVER != 'earliest' }} @@ -158,10 +234,13 @@ jobs: - name: Set environment variables env: - BUILD_CUDA_VER: ${{ inputs.build-ctk-ver }} + BINDINGS_SOURCE: ${{ matrix.BINDINGS_SOURCE }} + BINDINGS_SOURCE_DIR: ${{ matrix.BINDINGS_SOURCE_DIR }} + DEFAULT_BINDINGS_SOURCE_DIR: ${{ matrix.DEFAULT_BINDINGS_SOURCE_DIR }} CUDA_VER: ${{ matrix.CUDA_VER }} HOST_PLATFORM: ${{ inputs.host-platform }} LOCAL_CTK: ${{ matrix.LOCAL_CTK }} + PATHFINDER_SOURCE: ${{ matrix.PATHFINDER_SOURCE }} PY_VER: ${{ matrix.PY_VER }} SHA: ${{ inputs.sha || github.sha }} SKIP_BINDINGS_TEST_OVERRIDE: ${{ env.TEST_BINDINGS != 'true' && '1' || '0' }} @@ -174,7 +253,8 @@ jobs: run: echo "$MATRIX_ENV" | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> "$GITHUB_ENV" - name: Download cuda-pathfinder build artifacts - if: ${{ env.TEST_PATHFINDER == 'true' || env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true' }} + if: ${{ (env.TEST_PATHFINDER == 'true' || env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && + env.PATHFINDER_SOURCE == 'artifact' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: name: cuda-pathfinder-wheel @@ -183,17 +263,17 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download cuda-python build artifacts - if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'main' }} + if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'local' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: - name: cuda-python-wheel + name: ${{ env.CUDA_PYTHON_ARTIFACT_NAME }} path: . run-id: ${{ inputs.run-id || github.run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download cuda.bindings build artifacts if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && - env.BINDINGS_SOURCE == 'main' }} + env.BINDINGS_SOURCE == 'local' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: name: ${{ env.CUDA_BINDINGS_ARTIFACT_NAME }} @@ -201,64 +281,15 @@ jobs: run-id: ${{ inputs.run-id || github.run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Download cuda-python & cuda.bindings build artifacts from the prior branch - if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && - env.BINDINGS_SOURCE == 'backport' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # See https://github.com/cli/cli/blob/trunk/docs/install_linux.md#debian-ubuntu-linux-raspberry-pi-os-apt. - # gh is needed for artifact fetching. - mkdir -p -m 755 /etc/apt/keyrings \ - && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \ - && cat $out | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ - && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ - && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ - && apt update \ - && apt install gh -y - - OLD_BRANCH=${{ needs.compute-matrix.outputs.OLD_BRANCH }} - OLD_ARTIFACT_PATTERN="cuda-bindings-python${PYTHON_VERSION_FORMATTED}-cuda*-${{ inputs.host-platform }}-*[0-9a-f]" - LOOKUP_ARGS=( - --branch "${OLD_BRANCH}" - --artifact "${OLD_ARTIFACT_PATTERN}" - ) - if ${{ env.TEST_PYTHON == 'true' }}; then - LOOKUP_ARGS+=(--artifact cuda-python-wheel) - fi - LATEST_PRIOR_RUN_ID=$(./ci/tools/lookup-run-id \ - "${LOOKUP_ARGS[@]}" NVIDIA/cuda-python "CI") - - gh run download \ - "${LATEST_PRIOR_RUN_ID}" \ - -p "${OLD_ARTIFACT_PATTERN}" \ - -R NVIDIA/cuda-python - OLD_ARTIFACT_DIR=$(compgen -G "${OLD_ARTIFACT_PATTERN}") - test -d "${OLD_ARTIFACT_DIR}" - ls -al "${OLD_ARTIFACT_DIR}" - mkdir -p "${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}" - mv "${OLD_ARTIFACT_DIR}"/*.whl "${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}"/ - rmdir "${OLD_ARTIFACT_DIR}" - - if ${{ env.TEST_PYTHON == 'true' }}; then - gh run download \ - "${LATEST_PRIOR_RUN_ID}" \ - -p cuda-python-wheel \ - -R NVIDIA/cuda-python - ls -al cuda-python-wheel - mv cuda-python-wheel/*.whl . - rmdir cuda-python-wheel - fi - - name: Display structure of downloaded cuda-python artifacts - if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE != 'floor' }} + if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'local' }} run: | pwd ls -lah cuda_python*.whl cuda_pathfinder/ - name: Display structure of downloaded cuda.bindings artifacts if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && - env.BINDINGS_SOURCE != 'floor' }} + env.BINDINGS_SOURCE == 'local' }} run: | pwd ls -lahR $CUDA_BINDINGS_ARTIFACTS_DIR @@ -297,7 +328,7 @@ jobs: if: ${{ env.TEST_CORE == 'true' && env.SKIP_CYTHON_TEST == '0' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: - name: ${{ env.CUDA_CORE_ARTIFACT_NAME }}-tests + name: ${{ env.CUDA_CORE_CYTHON_TEST_ARTIFACT_NAME }} path: ${{ env.CUDA_CORE_CYTHON_TESTS_DIR }} run-id: ${{ inputs.run-id || github.run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -393,22 +424,25 @@ jobs: run: run-tests core - name: Ensure cuda-python installable - if: ${{ inputs.test-mode == 'standard' && env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'main' }} + if: ${{ inputs.test-mode == 'standard' && env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'local' }} + env: + LOCAL_CTK: ${{ matrix.LOCAL_CTK }} + shell: bash --noprofile --norc -xeuo pipefail {0} run: | - # Package suites install their own dependencies. A metapackage-only - # run has no preceding suite, so install the exact local internal - # wheels in one transaction while resolving released dependencies - # such as cuda-core from the package index. - if ${{ env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' }}; then + # CUDA 13's metapackage also requires Core. A bindings-only release + # must resolve published Core while preserving the selected wheels. + if [[ "$TEST_CORE" == true || ("$TEST_BINDINGS" == true && "$TEST_CUDA_MAJOR" == 12) ]]; then dependency_args=(--no-deps) else - dependency_args=( - ./cuda_pathfinder/cuda_pathfinder-*.whl - "${CUDA_BINDINGS_ARTIFACTS_DIR}"/cuda_bindings-*.whl - ) + dependency_args=("${CUDA_BINDINGS_ARTIFACTS_DIR}"/cuda_bindings-*.whl) + if [[ "$PATHFINDER_SOURCE" == artifact ]]; then + dependency_args+=(./cuda_pathfinder/cuda_pathfinder-*.whl) + else + dependency_args+=("$(python -c 'from importlib.metadata import version; print("cuda-pathfinder==" + version("cuda-pathfinder"))')") + fi fi python_requirements=(cuda_python*.whl) - if [[ "${{ matrix.LOCAL_CTK }}" != 1 ]]; then + if [[ "$LOCAL_CTK" != 1 ]]; then python_requirements=("${python_requirements[@]/%/[all]}") fi pip install --only-binary=:all: "${dependency_args[@]}" "${python_requirements[@]}" diff --git a/.github/workflows/test-wheel-windows.yml b/.github/workflows/test-wheel-windows.yml index edb49f6634b..a4b77db3135 100644 --- a/.github/workflows/test-wheel-windows.yml +++ b/.github/workflows/test-wheel-windows.yml @@ -13,7 +13,8 @@ on: host-platform: type: string required: true - build-ctk-ver: + bindings-config: + description: Normalized CUDA bindings package-root registry JSON. type: string required: true matrix_filter: @@ -48,18 +49,26 @@ on: jobs: compute-matrix: + if: >- + ${{ inputs.workplan == '' || + contains(fromJSON(inputs.workplan).jobs.test_cuda_majors.*, true) }} runs-on: ubuntu-latest defaults: run: shell: bash --noprofile --norc -xeuo pipefail {0} env: + BINDINGS_CONFIG: ${{ inputs.bindings-config }} BUILD_TYPE: ${{ inputs.build-type }} + WORKPLAN: ${{ inputs.workplan }} + TEST_MODE: ${{ inputs.test-mode }} ARCH: ${{ (inputs.host-platform == 'win-64' && 'amd64') }} outputs: MATRIX: ${{ steps.compute-matrix.outputs.MATRIX }} steps: - name: Checkout ${{ github.event.repository.name }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.sha || github.sha }} - name: Validate Test Type run: | @@ -79,28 +88,100 @@ jobs: # Read base matrix from YAML file for the specific architecture TEST_MATRIX=$(yq -o json ".windows[\"${MATRIX_TYPE}\"] | map(select(.ARCH == \"${ARCH}\"))" ci/test-matrix.yml) + BINDINGS_PACKAGES=$(jq -c '.package_roots' <<< "$BINDINGS_CONFIG") + CURRENT_BINDINGS_SOURCE_DIR=$(jq -r ' + .[] | select(.release_status == "current") | .package_root + ' <<< "$BINDINGS_PACKAGES") + + if [[ -z "$WORKPLAN" ]]; then + TEST_CUDA_MAJORS=$(jq -c ' + [.[].cuda_variant] | map({key: ., value: true}) | from_entries + ' <<< "$BINDINGS_PACKAGES") + PATHFINDER_SOURCE=artifact + else + TEST_CUDA_MAJORS=$(jq -c '.jobs.test_cuda_majors' <<< "$WORKPLAN") + PATHFINDER_SOURCE=$(jq -r '.sources.pathfinder' <<< "$WORKPLAN") + fi + # Apply matrix filter; add a RUNNER_DRIVER field that maps any # custom DRIVER version back to 'latest' (install_gpu_driver.ps1 # swaps the driver itself, so the runner must be the one that # ships the most recent pre-installed driver); wrap in include. - 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') + MATRIX=$(echo "$TEST_MATRIX" | jq -c \ + --argjson test_cuda_majors "$TEST_CUDA_MAJORS" \ + --argjson bindings_packages "$BINDINGS_PACKAGES" \ + --arg default_bindings_source_dir "$CURRENT_BINDINGS_SOURCE_DIR" \ + --arg pathfinder_source "$PATHFINDER_SOURCE" \ + --argjson workplan "${WORKPLAN:-null}" \ + --arg test_mode "$TEST_MODE" \ + '${{ inputs.matrix_filter }} + | map( + . as $row + | ("cu" + ($row.CUDA_VER | split(".")[0])) as $cuda_variant + | ($bindings_packages | map(select(.toolkit_version == $row.CUDA_VER)) | first) as $bindings_package + | $row + { + CUDA_VARIANT: $cuda_variant, + BINDINGS_SOURCE: (if $bindings_package == null then "floor" else "local" end), + BINDINGS_SOURCE_DIR: ($bindings_package.package_root // ""), + DEFAULT_BINDINGS_SOURCE_DIR: $default_bindings_source_dir, + PATHFINDER_SOURCE: $pathfinder_source + } + ) + | map(select($test_cuda_majors[.CUDA_VARIANT] == true)) + | map(. as $row | . + { + SUITES: (if $test_mode != "standard" then $test_mode else + [if $workplan == null or $workplan.modules.pathfinder.needs_test then "pathfinder" else empty end, + if $row.BINDINGS_SOURCE == "local" and ($workplan == null or $workplan.modules.bindings.package_roots[$row.BINDINGS_SOURCE_DIR].needs_test) then "bindings" + ($row.CUDA_VER | split(".")[0]) else empty end, + if $workplan == null or $workplan.modules.core.cuda_majors[$row.CUDA_VARIANT].needs_test then "core" + ($row.CUDA_VER | split(".")[0]) else empty end, + if $row.BINDINGS_SOURCE == "local" and ($workplan == null or $workplan.modules.python.package_roots[$row.BINDINGS_SOURCE_DIR].needs_test) then "meta" + ($row.CUDA_VER | split(".")[0]) else empty end] | join("+") end) + }) + | map(select(.SUITES != "")) + | map(. + {RUNNER_DRIVER: (if .DRIVER == "latest" or .DRIVER == "earliest" then .DRIVER else "latest" end)}) + | if (. | length) > 0 then {include: .} else "Error: Empty matrix after CUDA-major workplan filtering\n" | halt_error(1) end') + + # Compatibility nightly modes intentionally use published floor bindings. + # Standard PR/release runs must retain each selected local bindings line. + if [[ "$TEST_MODE" == "standard" && "$BUILD_TYPE" == "pull-request" ]]; then + jq -en --argjson packages "$BINDINGS_PACKAGES" \ + --argjson enabled "$TEST_CUDA_MAJORS" --argjson matrix "$MATRIX" \ + --argjson workplan "${WORKPLAN:-null}" ' + [$packages[] | . as $package + | select($enabled[.cuda_variant] == true) + | select($workplan == null or + $workplan.modules.bindings.package_roots[.package_root].needs_test or + $workplan.modules.python.package_roots[.package_root].needs_test) + | select(all($matrix.include[]; + .BINDINGS_SOURCE != "local" or .BINDINGS_SOURCE_DIR != $package.package_root)) + | .package_root] as $missing + | if $missing | length == 0 then true else + error("filtered test matrix omits required local-wheel coverage: \($missing | join(", "))") end + ' > /dev/null + fi + + { + echo '### Selected wheel test coverage' + echo '| Suites | Python | CTK | Toolkit source | Bindings source | GPU |' + echo '| --- | --- | --- | --- | --- | --- |' + jq -r '.include[] | "| \(.SUITES) | \(.PY_VER) | \(.CUDA_VER) | \(if .LOCAL_CTK == "1" then "local CTK" else "wheels" end) | \(if .BINDINGS_SOURCE == "local" then .BINDINGS_SOURCE_DIR else "published floor" end) | \(.GPU) |"' <<< "$MATRIX" + } >> "$GITHUB_STEP_SUMMARY" echo "MATRIX=${MATRIX}" | tee --append "${GITHUB_OUTPUT}" test: env: TEST_PATHFINDER: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.pathfinder.needs_test }} - TEST_BINDINGS: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.bindings.needs_test }} - TEST_CORE: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.core.needs_test }} - TEST_PYTHON: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.python.needs_test }} - name: Python ${{ matrix.PY_VER }}, CUDA ${{ matrix.CUDA_VER }} (${{ (matrix.LOCAL_CTK == '1' && 'local') || 'wheels' }}), GPU ${{ matrix.GPU }}${{ matrix.GPU_COUNT != '1' && format(' (x{0})', matrix.GPU_COUNT) || '' }} (${{ matrix.DRIVER_MODE }})${{ matrix.ENV.TORCH_VER && format(', {0}+{1}', matrix.ENV.TORCH_VER, matrix.ENV.TORCH_CUDA) || '' }}${{ matrix.ENV.MODE == 'nightly-numba-cuda' && ', latest' || '' }} + TEST_BINDINGS: ${{ inputs.workplan == '' || (matrix.BINDINGS_SOURCE == 'local' && fromJSON(inputs.workplan).modules.bindings.package_roots[matrix.BINDINGS_SOURCE_DIR].needs_test) }} + TEST_CORE: ${{ inputs.workplan == '' || fromJSON(inputs.workplan).modules.core.cuda_majors[matrix.CUDA_VARIANT].needs_test }} + TEST_PYTHON: ${{ inputs.workplan == '' || (matrix.BINDINGS_SOURCE == 'local' && fromJSON(inputs.workplan).modules.python.package_roots[matrix.BINDINGS_SOURCE_DIR].needs_test) }} + name: ${{ matrix.SUITES }} / py${{ matrix.PY_VER }}, CTK${{ matrix.CUDA_VER }} (${{ (matrix.LOCAL_CTK == '1' && 'local CTK') || 'wheels' }}; bindings ${{ matrix.BINDINGS_SOURCE == 'local' && 'PR' || 'floor' }}), GPU ${{ matrix.GPU }}${{ matrix.GPU_COUNT != '1' && format(' (x{0})', matrix.GPU_COUNT) || '' }} (${{ matrix.DRIVER_MODE }})${{ matrix.ENV.TORCH_VER && format(', {0}+{1}', matrix.ENV.TORCH_VER, matrix.ENV.TORCH_CUDA) || '' }}${{ matrix.ENV.MODE == 'nightly-numba-cuda' && ', latest' || '' }} timeout-minutes: 60 # The build stage could fail but we want the CI to keep moving. needs: compute-matrix strategy: fail-fast: false matrix: ${{ fromJSON(needs.compute-matrix.outputs.MATRIX) }} - if: ${{ github.repository_owner == 'nvidia' && !cancelled() }} + if: ${{ github.repository_owner == 'nvidia' && !cancelled() && + (inputs.workplan == '' || contains(fromJSON(inputs.workplan).jobs.test_cuda_majors.*, true)) }} runs-on: "windows-${{ matrix.ARCH }}-gpu-${{ matrix.GPU }}-${{ matrix.RUNNER_DRIVER }}-${{ matrix.GPU_COUNT }}" steps: - name: Enable Git symlinks (Windows, must precede checkout) @@ -147,10 +228,13 @@ jobs: - name: Set environment variables env: - BUILD_CUDA_VER: ${{ inputs.build-ctk-ver }} + BINDINGS_SOURCE: ${{ matrix.BINDINGS_SOURCE }} + BINDINGS_SOURCE_DIR: ${{ matrix.BINDINGS_SOURCE_DIR }} + DEFAULT_BINDINGS_SOURCE_DIR: ${{ matrix.DEFAULT_BINDINGS_SOURCE_DIR }} CUDA_VER: ${{ matrix.CUDA_VER }} HOST_PLATFORM: ${{ inputs.host-platform }} LOCAL_CTK: ${{ matrix.LOCAL_CTK }} + PATHFINDER_SOURCE: ${{ matrix.PATHFINDER_SOURCE }} PY_VER: ${{ matrix.PY_VER }} SHA: ${{ inputs.sha || github.sha }} SKIP_BINDINGS_TEST_OVERRIDE: ${{ env.TEST_BINDINGS != 'true' && '1' || '0' }} @@ -165,7 +249,8 @@ jobs: run: echo "$MATRIX_ENV" | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> "$GITHUB_ENV" - name: Download cuda-pathfinder build artifacts - if: ${{ env.TEST_PATHFINDER == 'true' || env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true' }} + if: ${{ (env.TEST_PATHFINDER == 'true' || env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && + env.PATHFINDER_SOURCE == 'artifact' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: name: cuda-pathfinder-wheel @@ -174,17 +259,17 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download cuda-python build artifacts - if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'main' }} + if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'local' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: - name: cuda-python-wheel + name: ${{ env.CUDA_PYTHON_ARTIFACT_NAME }} path: . run-id: ${{ inputs.run-id || github.run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Download cuda.bindings build artifacts if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && - env.BINDINGS_SOURCE == 'main' }} + env.BINDINGS_SOURCE == 'local' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: name: ${{ env.CUDA_BINDINGS_ARTIFACT_NAME }} @@ -192,55 +277,15 @@ jobs: run-id: ${{ inputs.run-id || github.run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Download cuda-python & cuda.bindings build artifacts from the prior branch - if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && - env.BINDINGS_SOURCE == 'backport' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash --noprofile --norc -xeuo pipefail {0} - run: | - OLD_BRANCH=$(yq '.backport_branch' ci/versions.yml) - OLD_ARTIFACT_PATTERN="cuda-bindings-python${PYTHON_VERSION_FORMATTED}-cuda*-${{ inputs.host-platform }}-*[0-9a-f]" - LOOKUP_ARGS=( - --branch "${OLD_BRANCH}" - --artifact "${OLD_ARTIFACT_PATTERN}" - ) - if ${{ env.TEST_PYTHON == 'true' }}; then - LOOKUP_ARGS+=(--artifact cuda-python-wheel) - fi - LATEST_PRIOR_RUN_ID=$(./ci/tools/lookup-run-id \ - "${LOOKUP_ARGS[@]}" NVIDIA/cuda-python "CI") - - gh run download \ - "${LATEST_PRIOR_RUN_ID}" \ - -p "${OLD_ARTIFACT_PATTERN}" \ - -R NVIDIA/cuda-python - OLD_ARTIFACT_DIR=$(compgen -G "${OLD_ARTIFACT_PATTERN}") - test -d "${OLD_ARTIFACT_DIR}" - ls -al "${OLD_ARTIFACT_DIR}" - mkdir -p "${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}" - mv "${OLD_ARTIFACT_DIR}"/*.whl "${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}"/ - rmdir "${OLD_ARTIFACT_DIR}" - - if ${{ env.TEST_PYTHON == 'true' }}; then - gh run download \ - "${LATEST_PRIOR_RUN_ID}" \ - -p cuda-python-wheel \ - -R NVIDIA/cuda-python - ls -al cuda-python-wheel - mv cuda-python-wheel/*.whl . - rmdir cuda-python-wheel - fi - - name: Display structure of downloaded cuda-python artifacts - if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE != 'floor' }} + if: ${{ env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'local' }} run: | Get-Location Get-ChildItem cuda_python*.whl | Select-Object Mode, LastWriteTime, Length, FullName - name: Display structure of downloaded cuda.bindings artifacts if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' || env.TEST_PYTHON == 'true') && - env.BINDINGS_SOURCE != 'floor' }} + env.BINDINGS_SOURCE == 'local' }} run: | Get-Location Get-ChildItem -Recurse -Force $env:CUDA_BINDINGS_ARTIFACTS_DIR | Select-Object Mode, LastWriteTime, Length, FullName @@ -279,7 +324,7 @@ jobs: if: ${{ env.TEST_CORE == 'true' && env.SKIP_CYTHON_TEST == '0' }} uses: NVIDIA/cuda-python/.github/actions/download-artifact-retry@f20d661666a5c92b757748ca237d3592ac3e942f # introduced in #3037 with: - name: ${{ env.CUDA_CORE_ARTIFACT_NAME }}-tests + name: ${{ env.CUDA_CORE_CYTHON_TEST_ARTIFACT_NAME }} path: ${{ env.CUDA_CORE_CYTHON_TESTS_DIR }} run-id: ${{ inputs.run-id || github.run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -359,25 +404,28 @@ jobs: run: run-tests core - name: Ensure cuda-python installable - if: ${{ inputs.test-mode == 'standard' && env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'main' }} + if: ${{ inputs.test-mode == 'standard' && env.TEST_PYTHON == 'true' && env.BINDINGS_SOURCE == 'local' }} + env: + LOCAL_CTK: ${{ matrix.LOCAL_CTK }} + shell: bash --noprofile --norc -xeuo pipefail {0} run: | - # Package suites install their own dependencies. A metapackage-only - # run has no preceding suite, so install the exact local internal - # wheels in one transaction while resolving released dependencies - # such as cuda-core from the package index. - if ('${{ env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true' }}' -eq 'true') { - $dependencyArgs = @('--no-deps') - } else { - $dependencyArgs = @( - (Get-Item ./cuda_pathfinder/cuda_pathfinder-*.whl).FullName - (Get-Item "$env:CUDA_BINDINGS_ARTIFACTS_DIR/cuda_bindings-*.whl").FullName - ) - } - $pythonRequirements = @((Get-Item ./cuda_python*.whl).FullName) - if ('${{ matrix.LOCAL_CTK }}' -ne '1') { - $pythonRequirements = @($pythonRequirements | ForEach-Object { "$($_)[all]" }) - } - pip install --only-binary=:all: @dependencyArgs @pythonRequirements + # CUDA 13's metapackage also requires Core. A bindings-only release + # must resolve published Core while preserving the selected wheels. + if [[ "$TEST_CORE" == true || ("$TEST_BINDINGS" == true && "$TEST_CUDA_MAJOR" == 12) ]]; then + dependency_args=(--no-deps) + else + dependency_args=("${CUDA_BINDINGS_ARTIFACTS_DIR}"/cuda_bindings-*.whl) + if [[ "$PATHFINDER_SOURCE" == artifact ]]; then + dependency_args+=(./cuda_pathfinder/cuda_pathfinder-*.whl) + else + dependency_args+=("$(python -c 'from importlib.metadata import version; print("cuda-pathfinder==" + version("cuda-pathfinder"))')") + fi + fi + python_requirements=(cuda_python*.whl) + if [[ "$LOCAL_CTK" != 1 ]]; then + python_requirements=("${python_requirements[@]/%/[all]}") + fi + pip install --only-binary=:all: "${dependency_args[@]}" "${python_requirements[@]}" - name: Install cuda.pathfinder extra wheels for testing if: ${{ inputs.test-mode == 'standard' && env.TEST_PATHFINDER == 'true' }} diff --git a/ci/tools/run-tests b/ci/tools/run-tests index 04a5fa689d3..e2e41cedb2a 100755 --- a/ci/tools/run-tests +++ b/ci/tools/run-tests @@ -38,9 +38,11 @@ fi test_module=${1} TEST_FT_GROUP=() +TEST_FT_REQUIREMENT=() PYTEST_PARALLEL_ARGS=() if python -c 'import sys; assert not sys._is_gil_enabled()' 2> /dev/null; then TEST_FT_GROUP=(--group test-ft) + TEST_FT_REQUIREMENT=(pytest-run-parallel==0.10.0) PYTEST_PARALLEL_ARGS=(--parallel-threads=4) fi @@ -49,8 +51,21 @@ fi # all wheels together in a single pip call further below. if [[ "${test_module}" != nightly-* ]]; then pushd ./cuda_pathfinder - echo "Installing pathfinder wheel" - pip install ./*.whl --group test "${TEST_FT_GROUP[@]}" + if [[ "${PATHFINDER_SOURCE:-}" == "artifact" ]]; then + echo "Installing pathfinder wheel from CI artifacts" + pathfinder_wheels=(./*.whl) + if [[ "${#pathfinder_wheels[@]}" -ne 1 || ! -f "${pathfinder_wheels[0]}" ]]; then + echo "Expected exactly one cuda-pathfinder wheel in $(pwd)" >&2 + exit 1 + fi + pip install "${pathfinder_wheels[0]}" --group test "${TEST_FT_GROUP[@]}" + elif [[ "${PATHFINDER_SOURCE:-}" == "published" && "${test_module}" == "bindings" ]]; then + echo "Installing published pathfinder for a focused bindings release" + pip install cuda-pathfinder --group test "${TEST_FT_GROUP[@]}" + else + echo "Error: PATHFINDER_SOURCE must be artifact, or published for bindings release tests; got ${PATHFINDER_SOURCE:-}" >&2 + exit 1 + fi popd fi @@ -68,15 +83,33 @@ if [[ "${test_module}" == "pathfinder" ]]; then popd elif [[ "${test_module}" == "bindings" ]]; then echo "Installing bindings wheel" - pushd ./cuda_bindings_13 - if [[ "${LOCAL_CTK}" == 1 ]]; then - pip install "${CUDA_BINDINGS_ARTIFACTS_DIR}"/*.whl --group test "${TEST_FT_GROUP[@]}" - else + pushd "./${CUDA_BINDINGS_ROOT}" + bindings_wheels=("${CUDA_BINDINGS_ARTIFACTS_DIR}"/*.whl) + if [[ "${#bindings_wheels[@]}" -ne 1 || ! -f "${bindings_wheels[0]}" ]]; then + echo "Expected exactly one cuda-bindings wheel in ${CUDA_BINDINGS_ARTIFACTS_DIR}" >&2 + exit 1 + fi + bindings_requirement="${bindings_wheels[0]}" + bindings_toolkit_requirement=() + if [[ "${LOCAL_CTK}" != 1 ]]; then TEST_CUDA_MAJOR_MINOR="$(cut -d '.' -f 1-2 <<< "${CUDA_VER}")" # Keep the wheel-installed Toolkit aligned with the matrix-selected minor; # newer minors can grow ABI structs beyond the size compiled into this wheel. - pip install $(ls "${CUDA_BINDINGS_ARTIFACTS_DIR}"/*.whl)[all] --group test "${TEST_FT_GROUP[@]}" \ - "cuda-toolkit==${TEST_CUDA_MAJOR_MINOR}.*" + bindings_toolkit_requirement=("cuda-toolkit==${TEST_CUDA_MAJOR_MINOR}.*") + fi + if grep -q '^\[dependency-groups\][[:space:]]*$' pyproject.toml; then + if [[ "${LOCAL_CTK}" != 1 ]]; then + bindings_requirement+="[all]" + fi + python -m pip install "${bindings_requirement}" --group pyproject.toml:test \ + "${TEST_FT_GROUP[@]}" "${bindings_toolkit_requirement[@]}" + else + bindings_extras="test" + if [[ "${LOCAL_CTK}" != 1 ]]; then + bindings_extras="all,test" + fi + python -m pip install "${bindings_requirement}[${bindings_extras}]" \ + "${TEST_FT_REQUIREMENT[@]}" "${bindings_toolkit_requirement[@]}" fi echo "Running bindings tests" ${SANITIZER_CMD} pytest -rsxXfE -v --randomly-dont-reorganize --exclude-warning-annotations "${PYTEST_PARALLEL_ARGS[@]}" --junitxml "${TEST_RESULTS_DIR}/junit-bindings.xml" tests/ | fold_pytest_log @@ -86,13 +119,13 @@ elif [[ "${test_module}" == "bindings" ]]; then popd elif [[ "${test_module}" == "core" || "${test_module}" == nightly-* ]]; then # Shared setup for core and nightly modes. - TEST_CUDA_MAJOR="$(cut -d '.' -f 1 <<< ${CUDA_VER})" + TEST_CUDA_MAJOR="$(cut -d '.' -f 1 <<< "${CUDA_VER}")" TEST_CUDA_MAJOR_MINOR="$(cut -d '.' -f 1-2 <<< "${CUDA_VER}")" # Resolve bindings based on BINDINGS_SOURCE (set by env-vars): - # main/backport → local wheel from artifacts dir - # floor → the oldest cuda-bindings that the cuda-core wheel under test - # supports, read from that wheel and installed from PyPI + # local → matching configured-package wheel from this run + # floor -> the oldest supported bindings, read from the Core wheel + # and installed from PyPI BINDINGS_ARGS=() if [[ "${BINDINGS_SOURCE}" == "floor" ]]; then CORE_WHL_FOR_FLOOR=("${CUDA_CORE_ARTIFACTS_DIR}"/*.whl) @@ -114,10 +147,19 @@ elif [[ "${test_module}" == "core" || "${test_module}" == nightly-* ]]; then fi if [[ "${test_module}" == nightly-* ]]; then + if [[ "${PATHFINDER_SOURCE:-}" != "artifact" ]]; then + echo "Error: nightly tests require PATHFINDER_SOURCE=artifact" >&2 + exit 1 + fi # Resolve pathfinder wheel to absolute path before pushd. # CUDA_BINDINGS_ARTIFACTS_DIR and CUDA_CORE_ARTIFACTS_DIR are already # absolute (set via realpath in env-vars). - PATHFINDER_WHL=($(realpath ./cuda_pathfinder/*.whl)) + pathfinder_wheels=(./cuda_pathfinder/*.whl) + if [[ "${#pathfinder_wheels[@]}" -ne 1 || ! -f "${pathfinder_wheels[0]}" ]]; then + echo "Expected exactly one cuda-pathfinder wheel in ./cuda_pathfinder" >&2 + exit 1 + fi + PATHFINDER_WHL=("$(realpath "${pathfinder_wheels[0]}")") fi if [[ "${test_module}" == "core" ]]; then @@ -143,8 +185,8 @@ elif [[ "${test_module}" == "core" || "${test_module}" == nightly-* ]]; then pip list echo "Running core tests" ${SANITIZER_CMD} pytest -rsxXfE -v --randomly-dont-reorganize --exclude-warning-annotations "${PYTEST_PARALLEL_ARGS[@]}" --junitxml "${TEST_RESULTS_DIR}/junit-core.xml" tests/ | fold_pytest_log - # Currently our CI always installs the latest bindings (from either major version). - # This is not compatible with the test requirements. + # Compiled Cython tests require the bindings used to build them. Older + # toolkit rows intentionally exercise published floor bindings instead. if [[ "${SKIP_CYTHON_TEST}" == 0 ]]; then ${SANITIZER_CMD} pytest -rsxXfE -v --randomly-dont-reorganize --exclude-warning-annotations "${PYTEST_PARALLEL_ARGS[@]}" --junitxml "${TEST_RESULTS_DIR}/junit-core-cython.xml" tests/cython | fold_pytest_log fi diff --git a/ci/tools/tests/test_compute_ci_plan.py b/ci/tools/tests/test_compute_ci_plan.py index 0dfd4227468..9ea9c1aa2db 100644 --- a/ci/tools/tests/test_compute_ci_plan.py +++ b/ci/tools/tests/test_compute_ci_plan.py @@ -554,13 +554,13 @@ def test_workflow_matrix_executes_component_selection_and_coverage_guard( assert result.returncode == 0, result.stderr rows = json.loads(output.read_text().strip().removeprefix("MATRIX="))["include"] if scenario == "maintenance-only": - assert [row["SUITES"] for row in rows] == ["bindings12+core12+python12", "core12"] + assert [row["SUITES"] for row in rows] == ["bindings12+core12+meta12", "core12"] elif scenario == "nightly-floor": assert rows[0]["BINDINGS_SOURCE"] == "floor" assert rows[0]["SUITES"] == "nightly-pytorch" elif scenario == "full": - assert rows[0]["SUITES"] == "pathfinder+bindings12+core12+python12" - assert rows[1]["SUITES"] == "pathfinder+bindings13+core13+python13" + assert rows[0]["SUITES"] == "pathfinder+bindings12+core12+meta12" + assert rows[1]["SUITES"] == "pathfinder+bindings13+core13+meta13" assert rows[2]["SUITES"] == "pathfinder+core12" assert "Selected wheel test coverage" in summary.read_text() diff --git a/ci/tools/tests/test_pixi_lockfile_freshness_workflow.py b/ci/tools/tests/test_pixi_lockfile_freshness_workflow.py index d5f94837861..780f9bd87d7 100644 --- a/ci/tools/tests/test_pixi_lockfile_freshness_workflow.py +++ b/ci/tools/tests/test_pixi_lockfile_freshness_workflow.py @@ -7,10 +7,12 @@ import shlex import subprocess +import tomllib from pathlib import Path import pytest import yaml +from packaging.requirements import Requirement ROOT = Path(__file__).resolve().parents[3] WORKFLOWS = ROOT / ".github/workflows" @@ -110,11 +112,17 @@ def test_source_builds_and_tests_still_use_frozen_pixi(): assert any("pixi run" in step.get("run", "") for step in job["steps"]) -@pytest.mark.agent_authored(model="gpt-6") +@pytest.mark.agent_authored(model="gpt-6.1-sol") def test_nightly_ci_declares_workflow_test_dependencies(): workflow = _workflow("ci-nightly.yml") step = next(step for step in workflow["jobs"]["test-ci-tools-for-release"]["steps"] if "run" in step) install = next(line for line in step["run"].splitlines() if "pip install" in line) - dependencies = {argument.lower().split("==")[0] for argument in shlex.split(install)} + arguments = shlex.split(install) + if "./ci[test]" in arguments: + metadata = tomllib.loads((ROOT / "ci/pyproject.toml").read_text(encoding="utf-8"))["project"] + requirements = [*metadata["dependencies"], *metadata["optional-dependencies"]["test"]] + dependencies = {Requirement(requirement).name.lower() for requirement in requirements} + else: + dependencies = {argument.lower().split("==")[0] for argument in arguments} assert {"pytest", "pyyaml"} <= dependencies diff --git a/ci/tools/tests/test_test_sources.py b/ci/tools/tests/test_test_sources.py new file mode 100644 index 00000000000..e99c0ad4c16 --- /dev/null +++ b/ci/tools/tests/test_test_sources.py @@ -0,0 +1,340 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import os +import subprocess +import sys +import zipfile +from pathlib import Path + +import pytest + +from ci.tools.bindings_config import load_config + +REPO_ROOT = Path(__file__).resolve().parents[3] +RUN_TESTS = REPO_ROOT / "ci" / "tools" / "run-tests" +ENV_VARS = REPO_ROOT / "ci" / "tools" / "env-vars" + + +def _write_command(directory: Path, name: str, body: str) -> None: + path = directory / name + path.write_text(f"#!/usr/bin/env bash\nset -eu\n{body}\n", encoding="utf-8") + path.chmod(0o755) + + +def _run_tests_env(tmp_path: Path) -> tuple[dict[str, str], Path]: + fake_bin = tmp_path / "bin" + fake_bin.mkdir() + command_log = tmp_path / "commands.log" + _write_command( + fake_bin, + "python", + """ +if [[ "${1:-}" == "-c" ]]; then + exit 1 +fi +if [[ "${1:-}" == "ci/tools/cuda_core_bindings_floor.py" ]]; then + exec "$REAL_PYTHON" "$@" +fi +printf 'python %s\\n' "$*" >> "$COMMAND_LOG" +""".strip(), + ) + _write_command(fake_bin, "pip", 'printf \'pip %s\\n\' "$*" >> "$COMMAND_LOG"') + _write_command(fake_bin, "pytest", ":") + env = { + **os.environ, + "COMMAND_LOG": str(command_log), + "REAL_PYTHON": sys.executable, + "PATH": f"{fake_bin}{os.pathsep}{os.environ['PATH']}", + "CUDA_PATHFINDER_TEST_LOAD_NVIDIA_DYNAMIC_LIB_STRICTNESS": "see_what_works", + "CUDA_PATHFINDER_TEST_FIND_NVIDIA_HEADERS_STRICTNESS": "see_what_works", + "CUDA_PATHFINDER_TEST_FIND_NVIDIA_BITCODE_LIB_STRICTNESS": "see_what_works", + } + (tmp_path / "cuda_pathfinder").mkdir() + return env, command_log + + +def _run_env_vars( + tmp_path: Path, + *, + bindings_source: str, + pathfinder_source: str, + cuda_version: str = "13.3.0", + bindings_root: str = "cuda_bindings", +) -> subprocess.CompletedProcess[str]: + for relative in ( + f"{bindings_root}/dist", + f"{bindings_root}/tests/cython", + "cuda_core/dist", + "cuda_core/tests/cython", + "cuda_core/tests/test_binaries", + ): + (tmp_path / relative).mkdir(parents=True, exist_ok=True) + github_env = tmp_path / "github-env" + github_path = tmp_path / "github-path" + env = { + **os.environ, + "BINDINGS_SOURCE": bindings_source, + "CUDA_VER": cuda_version, + "GITHUB_ENV": str(github_env), + "GITHUB_PATH": str(github_path), + "HOST_PLATFORM": "linux-64", + "LOCAL_CTK": "0", + "PATHFINDER_SOURCE": pathfinder_source, + "PY_VER": "3.13", + "SHA": "abcdef", + "SKIP_BINDINGS_TEST_OVERRIDE": "0", + } + if bindings_source == "local": + env["BINDINGS_SOURCE_DIR"] = bindings_root + else: + env["DEFAULT_BINDINGS_SOURCE_DIR"] = bindings_root + return subprocess.run( # noqa: S603 - invokes the repository script under test + [str(ENV_VARS), "test"], + cwd=tmp_path, + env=env, + check=False, + capture_output=True, + text=True, + ) + + +def _read_github_env(path: Path) -> dict[str, str]: + return dict(line.split("=", 1) for line in path.read_text(encoding="utf-8").splitlines()) + + +@pytest.mark.agent_authored(model="gpt-5.6-sol") +def test_env_vars_rejects_unknown_mode() -> None: + result = subprocess.run( # noqa: S603 - invokes the repository script under test + [str(ENV_VARS), "unknown"], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 1 + assert "build mode must be build or test" in result.stderr + + +@pytest.mark.agent_authored(model="gpt-5.6") +def test_build_env_uses_registry_packages_without_generic_bindings_aliases(tmp_path: Path) -> None: + github_env = tmp_path / "github-env" + env = { + **os.environ, + "GITHUB_ENV": str(github_env), + "GITHUB_PATH": str(tmp_path / "github-path"), + "HOST_PLATFORM": "linux-64", + "PY_VER": "3.13", + "SHA": "abcdef0", + } + + result = subprocess.run( # noqa: S603 - invokes the repository script under test + [str(ENV_VARS), "build"], + cwd=REPO_ROOT, + env=env, + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr + values = _read_github_env(github_env) + config = load_config() + for status in ("current", "maintenance"): + package = config.package_for_release_status(status) + prefix = status.upper() + assert values[f"{prefix}_BINDINGS_ROOT"] == package.package_root + assert values[f"{prefix}_CUDA_VERSION"] == package.toolkit_version + assert values[f"{prefix}_CUDA_MAJOR"] == package.cuda_major + assert values[f"{prefix}_CUDA_VARIANT"] == package.cuda_variant + assert values[f"{prefix}_BINDINGS_ARTIFACT_NAME"].endswith("-abcdef0") + + assert ( + not { + "CUDA_BINDINGS_ARTIFACT_BASENAME", + "CUDA_BINDINGS_ARTIFACT_NAME", + "CUDA_BINDINGS_ARTIFACTS_DIR", + "CUDA_BINDINGS_CYTHON_TESTS_DIR", + } + & values.keys() + ) + + +@pytest.mark.parametrize("wheel_count", [0, 2]) +@pytest.mark.agent_authored(model="gpt-5.6") +def test_artifact_pathfinder_requires_exactly_one_wheel(tmp_path: Path, wheel_count: int) -> None: + env, _ = _run_tests_env(tmp_path) + env["PATHFINDER_SOURCE"] = "artifact" + for index in range(wheel_count): + (tmp_path / "cuda_pathfinder" / f"cuda_pathfinder-{index}.whl").touch() + + result = subprocess.run( # noqa: S603 - invokes the repository script under test + [str(RUN_TESTS), "pathfinder"], + cwd=tmp_path, + env=env, + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode != 0 + assert "Expected exactly one cuda-pathfinder wheel" in result.stderr + + +@pytest.mark.agent_authored(model="gpt-5.6") +def test_artifact_pathfinder_installs_the_only_wheel(tmp_path: Path) -> None: + env, command_log = _run_tests_env(tmp_path) + env["PATHFINDER_SOURCE"] = "artifact" + wheel = tmp_path / "cuda_pathfinder" / "cuda_pathfinder-1.0-py3-none-any.whl" + wheel.touch() + + subprocess.run( # noqa: S603 - invokes the repository script under test + [str(RUN_TESTS), "pathfinder"], + cwd=tmp_path, + env=env, + check=True, + capture_output=True, + text=True, + ) + + assert f"pip install ./{wheel.name} --group test" in command_log.read_text(encoding="utf-8") + + +@pytest.mark.agent_authored(model="gpt-5.6") +def test_published_pathfinder_is_limited_to_bindings_release_tests(tmp_path: Path) -> None: + env, _ = _run_tests_env(tmp_path) + env["PATHFINDER_SOURCE"] = "published" + + result = subprocess.run( # noqa: S603 - invokes the repository script under test + [str(RUN_TESTS), "pathfinder"], + cwd=tmp_path, + env=env, + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode != 0 + assert "published for bindings release tests" in result.stderr + + +@pytest.mark.agent_authored(model="gpt-5.6") +def test_published_pathfinder_supports_bindings_release_tests(tmp_path: Path) -> None: + env, command_log = _run_tests_env(tmp_path) + env.update( + CUDA_BINDINGS_ARTIFACTS_DIR=str(tmp_path / "bindings-artifacts"), + CUDA_BINDINGS_ROOT="cuda_bindings", + LOCAL_CTK="1", + PATHFINDER_SOURCE="published", + SANITIZER_CMD="", + SKIP_CYTHON_TEST="1", + ) + (tmp_path / "cuda_bindings").mkdir() + (tmp_path / "cuda_bindings" / "pyproject.toml").write_text("[dependency-groups]\ntest = []\n", encoding="utf-8") + (tmp_path / "bindings-artifacts").mkdir() + (tmp_path / "bindings-artifacts" / "cuda_bindings-13.3.whl").touch() + + subprocess.run( # noqa: S603 - invokes the repository script under test + [str(RUN_TESTS), "bindings"], + cwd=tmp_path, + env=env, + check=True, + capture_output=True, + text=True, + ) + + calls = command_log.read_text(encoding="utf-8").splitlines() + assert calls[0].startswith("pip install cuda-pathfinder --group test") + assert calls[1].startswith("python -m pip install ") + + +@pytest.mark.parametrize( + ("bindings_source", "cuda_version", "bindings_root", "expected_artifact"), + [ + ("local", "13.4.2", "cuda_bindings", "cuda-python-wheel-cuda13.4.2"), + ("local", "12.9.1", "cuda_bindings_12", "cuda-python-wheel-cuda12.9.1"), + ("floor", "13.0.2", "cuda_bindings", None), + ("floor", "12.6.3", "cuda_bindings", None), + ], +) +@pytest.mark.agent_authored(model="gpt-6-astra") +def test_cuda_python_artifact_name_exists_only_for_local_bindings( + tmp_path: Path, + bindings_source: str, + cuda_version: str, + bindings_root: str, + expected_artifact: str | None, +) -> None: + result = _run_env_vars( + tmp_path, + bindings_source=bindings_source, + pathfinder_source="artifact", + cuda_version=cuda_version, + bindings_root=bindings_root, + ) + + assert result.returncode == 0, result.stderr + github_env = _read_github_env(tmp_path / "github-env") + assert { + "CUDA_BINDINGS_ARTIFACT_BASENAME", + "CUDA_BINDINGS_ARTIFACT_NAME", + "CUDA_BINDINGS_ARTIFACTS_DIR", + "CUDA_BINDINGS_CYTHON_TESTS_DIR", + "CUDA_CORE_CYTHON_TEST_ARTIFACT_NAME", + } <= github_env.keys() + assert github_env["BINDINGS_SOURCE"] == bindings_source + assert github_env["CUDA_BINDINGS_ROOT"] == bindings_root + assert github_env["SKIP_CUDA_BINDINGS_TEST"] == ("1" if bindings_source == "floor" else "0") + assert github_env["SKIP_CYTHON_TEST"] == ("1" if bindings_source == "floor" else "0") + assert github_env["CUDA_CORE_CYTHON_TEST_ARTIFACT_NAME"].endswith(f"-cu{cuda_version.split('.')[0]}-tests") + if expected_artifact is None: + assert "CUDA_PYTHON_ARTIFACT_NAME" not in github_env + else: + assert github_env["CUDA_PYTHON_ARTIFACT_NAME"] == expected_artifact + + +@pytest.mark.agent_authored(model="gpt-6-astra") +@pytest.mark.parametrize(("cuda_version", "wheel_floor"), [("12.6.3", "12.9.9"), ("13.0.2", "13.4.2")]) +def test_core_install_uses_wheel_floor_with_older_toolkit(tmp_path: Path, cuda_version: str, wheel_floor: str) -> None: + env, command_log = _run_tests_env(tmp_path) + (tmp_path / "cuda_pathfinder" / "cuda_pathfinder-1.0-py3-none-any.whl").touch() + core_dist = tmp_path / "cuda_core" / "dist" + core_dist.mkdir(parents=True) + wheel = core_dist / "cuda_core-1.3.0-cp313-cp313-linux_x86_64.whl" + major = int(cuda_version.split(".")[0]) + with zipfile.ZipFile(wheel, "w") as archive: + archive.writestr( + "cuda_core-1.3.0.dist-info/METADATA", + f'Requires-Dist: cuda-bindings[all]>={wheel_floor},<{major + 1}; extra == "cu{major}"\n', + ) + tools = tmp_path / "ci" / "tools" + tools.mkdir(parents=True) + floor_script = REPO_ROOT / "ci" / "tools" / "cuda_core_bindings_floor.py" + (tools / floor_script.name).write_text(floor_script.read_text(encoding="utf-8"), encoding="utf-8") + env.update( + BINDINGS_SOURCE="floor", + CUDA_CORE_ARTIFACTS_DIR=str(core_dist), + CUDA_VER=cuda_version, + LOCAL_CTK="0", + PATHFINDER_SOURCE="artifact", + SANITIZER_CMD="", + SKIP_CYTHON_TEST="1", + ) + + result = subprocess.run( # noqa: S603 - repository script with install/test commands replaced by recording stubs + [str(RUN_TESTS), "core"], + cwd=tmp_path, + env=env, + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr + calls = command_log.read_text(encoding="utf-8").splitlines() + assert f"pip install cuda-bindings=={wheel_floor}" in calls + toolkit_minor = ".".join(cuda_version.split(".")[:2]) + assert any(f"cuda-toolkit=={toolkit_minor}.*" in call for call in calls) diff --git a/cuda_bindings_12/tests/nvml/test_cuda.py b/cuda_bindings_12/tests/nvml/test_cuda.py index d05e2634a12..aaab23e55fe 100644 --- a/cuda_bindings_12/tests/nvml/test_cuda.py +++ b/cuda_bindings_12/tests/nvml/test_cuda.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +import pytest + import cuda.bindings.driver as cuda from cuda.bindings import nvml @@ -15,10 +17,15 @@ def get_nvml_device_names(): for idx in range(num_devices): handle = nvml.device_get_handle_by_index_v2(idx) name = nvml.device_get_name(handle) - info = nvml.device_get_pci_info_v3(handle) - assert isinstance(info.bus, int) + try: + info = nvml.device_get_pci_info_v3(handle) + except nvml.NotSupportedError: + bus_id = -1 + else: + bus_id = info.bus + assert isinstance(bus_id, int) assert isinstance(name, str) - result.append({"name": name, "id": info.bus}) + result.append({"name": name, "id": bus_id}) return result @@ -54,4 +61,15 @@ def test_cuda_device_order(): cuda_devices = get_cuda_device_names() nvml_devices = get_nvml_device_names() - assert cuda_devices == nvml_devices, "CUDA and NVML device lists do not match" + for kind in ("Orin", "Thor"): + if any(kind in device["name"] for device in nvml_devices): + pytest.skip(f"Skipping test on {kind}, which has non-standard device naming") + + def compare(cuda_device, nvml_device): + return cuda_device["name"] == nvml_device["name"] and ( + nvml_device["id"] == -1 or cuda_device["id"] == nvml_device["id"] + ) + + assert len(cuda_devices) <= len(nvml_devices) + for cuda_device in cuda_devices: + assert any(compare(cuda_device, nvml_device) for nvml_device in nvml_devices) diff --git a/cuda_bindings_12/tests/nvml/test_init.py b/cuda_bindings_12/tests/nvml/test_init.py index 94b489ab45f..bcda0591bb6 100644 --- a/cuda_bindings_12/tests/nvml/test_init.py +++ b/cuda_bindings_12/tests/nvml/test_init.py @@ -6,7 +6,15 @@ import pytest -from cuda.bindings import nvml +from cuda.bindings import driver, nvml + + +def _driver_version_at_least_13_4(): + (status,) = driver.cuInit(0) + assert status == driver.CUresult.CUDA_SUCCESS + status, version = driver.cuDriverGetVersion() + assert status == driver.CUresult.CUDA_SUCCESS + return version >= 13040 def assert_nvml_is_initialized(): @@ -36,10 +44,14 @@ def test_devices_are_the_same_architecture(all_devices): @pytest.mark.skipif(sys.platform == "win32", reason="Test not supported on Windows") +@pytest.mark.thread_unsafe(reason="nvml init affects other threads") def test_init_ref_count(): """ Verifies that we can call NVML shutdown and init(2) multiple times, and that ref counting works """ + if _driver_version_at_least_13_4(): + pytest.skip("Init behavior changed in CUDA 13.4") + with pytest.raises(nvml.UninitializedError): nvml.shutdown() diff --git a/cuda_bindings_12/tests/nvml/test_pynvml.py b/cuda_bindings_12/tests/nvml/test_pynvml.py index a96b2c87575..e3ea533dd83 100644 --- a/cuda_bindings_12/tests/nvml/test_pynvml.py +++ b/cuda_bindings_12/tests/nvml/test_pynvml.py @@ -4,8 +4,6 @@ # A set of tests ported from https://github.com/gpuopenanalytics/pynvml/blob/11.5.3/pynvml/tests/test_nvml.py import os -import time - import pytest from cuda.bindings import nvml @@ -147,22 +145,6 @@ def test_device_get_power_usage(ngpus, handles): assert power_mwatts >= 0.0 -def test_device_get_total_energy_consumption(ngpus, handles): - for i in range(ngpus): - with unsupported_before(handles[i], nvml.DeviceArch.VOLTA): - energy_mjoules1 = nvml.device_get_total_energy_consumption(handles[i]) - - for j in range(10): # idle for 150 ms - time.sleep(0.015) # and check for increase every 15 ms - with unsupported_before(handles[i], nvml.DeviceArch.VOLTA): - energy_mjoules2 = nvml.device_get_total_energy_consumption(handles[i]) - assert energy_mjoules2 >= energy_mjoules1 - if energy_mjoules2 > energy_mjoules1: - break - else: - raise AssertionError("energy did not increase across 150 ms interval") - - # [Skipping] pynvml.nvmlDeviceGetGpuOperationMode # [Skipping] pynvml.nvmlDeviceGetCurrentGpuOperationMode # [Skipping] pynvml.nvmlDeviceGetPendingGpuOperationMode diff --git a/cuda_bindings_12/tests/nvml/util.py b/cuda_bindings_12/tests/nvml/util.py index 038fe58d8be..06cb1c2277a 100644 --- a/cuda_bindings_12/tests/nvml/util.py +++ b/cuda_bindings_12/tests/nvml/util.py @@ -43,5 +43,5 @@ def supports_ecc(device): def supports_nvlink(device): fields = nvml.FieldValue(1) - fields[0].field_id = nvml.FI.DEV_NVLINK_GET_STATE + fields[0].field_id = nvml.FieldId.DEV_NVLINK_GET_STATE return nvml.device_get_field_values(device, fields)[0].nvml_return == nvml.Return.SUCCESS diff --git a/cuda_bindings_13/docs/build_docs.sh b/cuda_bindings_13/docs/build_docs.sh index 199ababce60..53d0e14d0ea 100755 --- a/cuda_bindings_13/docs/build_docs.sh +++ b/cuda_bindings_13/docs/build_docs.sh @@ -16,13 +16,12 @@ fi # SPHINX_CUDA_BINDINGS_VER is used to create a subdir under build/html # (the Makefile file for sphinx-build also honors it if defined). -# If there's a post release (ex: .post1) we don't want it to show up in the -# version selector or directory structure. +# Post releases reuse their base release directory, while prerelease and +# development releases remain distinct so they cannot overwrite stable docs. if [[ -z "${SPHINX_CUDA_BINDINGS_VER}" ]]; then export SPHINX_CUDA_BINDINGS_VER=$(python -c "from importlib.metadata import version; \ - ver = '.'.join(str(version('cuda-bindings')).split('.')[:3]); \ - print(ver)" \ - | awk -F'+' '{print $1}') + ver = str(version('cuda-bindings')).split('+', 1)[0]; \ + print(ver if '.dev' in ver else ver.split('.post', 1)[0])") fi if [[ "${LATEST_ONLY}" == "1" && -z "${BUILD_PREVIEW:-}" && -z "${BUILD_LATEST:-}" ]]; then diff --git a/cuda_bindings_13/docs/source/install.rst b/cuda_bindings_13/docs/source/install.rst index 2c8213816d7..54adde64bb1 100644 --- a/cuda_bindings_13/docs/source/install.rst +++ b/cuda_bindings_13/docs/source/install.rst @@ -108,9 +108,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 ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/cuda_pathfinder/pyproject.toml b/cuda_pathfinder/pyproject.toml index 00a6f60bb1f..b3e0f309b1f 100644 --- a/cuda_pathfinder/pyproject.toml +++ b/cuda_pathfinder/pyproject.toml @@ -22,6 +22,7 @@ test-ft = [ "pytest-run-parallel==0.10.0", ] # Internal organization of test dependencies. +# The cuDSS bounds match the 0.x library ABI currently supported by the loader. cu12 = [ "cuda-toolkit[nvcc,cublas,nvrtc,cudart,cufft,curand,cusolver,cusparse,npp,nvfatbin,nvjitlink,nvjpeg,cccl,cupti,profiler]==12.*", "cuda-toolkit[cufile]==12.*; sys_platform != 'win32'", @@ -29,7 +30,7 @@ cu12 = [ "cutensor-cu12", "nvidia-cublasmp-cu12; sys_platform != 'win32'", "nvidia-cudnn-cu12>=9,<10", - "nvidia-cudss-cu12", + "nvidia-cudss-cu12<1", "nvidia-cufftmp-cu12; sys_platform != 'win32'", "nvidia-cusolvermp-cu12; sys_platform != 'win32'", "nvidia-cusparselt-cu12", @@ -45,7 +46,7 @@ cu13 = [ "nvidia-cublasmp-cu13; sys_platform != 'win32'", "nvidia-cudla; platform_system == 'Linux' and platform_machine == 'aarch64'", "nvidia-cudnn-cu13>=9,<10", - "nvidia-cudss-cu13", + "nvidia-cudss-cu13<1", "nvidia-cufftmp-cu13; sys_platform != 'win32'", "nvidia-cusolvermp-cu13; sys_platform != 'win32'", "nvidia-cusparselt-cu13", diff --git a/cuda_python/docs/environment-docs.yml b/cuda_python/docs/environment-docs.yml index 3152f0a3a93..a82fd02aa5e 100644 --- a/cuda_python/docs/environment-docs.yml +++ b/cuda_python/docs/environment-docs.yml @@ -19,6 +19,8 @@ dependencies: - sphinx-copybutton - myst-nb - enum_tools + # cuda_bindings_12/docs/source/conf.py uses the Furo theme. + - furo - sphinx-toolbox - pyclibrary - pip: diff --git a/toolshed/setup-docs-env.sh b/toolshed/setup-docs-env.sh index 9acbaa8e391..73afbbf06f9 100755 --- a/toolshed/setup-docs-env.sh +++ b/toolshed/setup-docs-env.sh @@ -51,6 +51,7 @@ conda create -y -n "${ENV_NAME}" \ sphinx-copybutton \ myst-nb \ enum_tools \ + furo \ sphinx-toolbox \ pyclibrary