diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcf19c5..83b9aed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,98 +1,251 @@ -name: Dual Backend CI +name: Hosted Pull Request CI on: - workflow_call: pull_request: - push: - branches: [main] - schedule: - - cron: "17 9 * * 1" workflow_dispatch: concurrency: - group: dual-backend-${{ github.ref }} + group: hosted-ci-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + +env: + RAYD_CUDA_GENCODE_ARCHES: "87,120" + RAYD_CUDA_PTX_ARCH: "120" + CMAKE_CUDA_ARCHITECTURES: "87-real;120-real;120-virtual" + TORCH_CUDA_ARCH_LIST: "8.7;12.0+PTX" + jobs: - drjit: - runs-on: [self-hosted, Windows, X64, rayd-gpu] + metadata: + name: Metadata / py${{ matrix.python-version }} + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.14"] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: {python-version: "3.11"} - - run: python -m pip install "scikit-build-core>=0.10" "cmake>=3.22" ninja "nanobind==2.9.2" "drjit==1.3.1" - - run: python -m pip install --no-build-isolation -e backends/drjit - - run: python -m unittest discover -s tests -t . -v - working-directory: backends/drjit + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - run: python -m pip install "tomli>=2; python_version < '3.11'" + - name: Validate package metadata and CI policy + shell: bash + run: | + python -m unittest tests.packaging.test_project_metadata -v + (cd backends/drjit && python -m unittest tests.test_project_metadata tests.test_verify_cuda_binary_arches -v) + (cd backends/torch && python -m unittest tests.test_project_metadata -v) + + native-wheel: + name: ${{ matrix.backend }} / ${{ matrix.os }} / py3.12 / quick CUDA + needs: metadata + runs-on: ${{ matrix.os }} + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, windows-2022] + backend: [drjit, torch] + env: + CMAKE_BUILD_PARALLEL_LEVEL: ${{ matrix.backend == 'torch' && '2' || '4' }} + CMAKE_CUDA_FLAGS: ${{ matrix.backend == 'torch' && '--threads=2' || '' }} + CMAKE_C_COMPILER_LAUNCHER: sccache + CMAKE_CXX_COMPILER_LAUNCHER: sccache + CMAKE_CUDA_COMPILER_LAUNCHER: sccache + RAYD_NVCC_LAUNCHER: sccache + OPTIX_INCLUDE_DIR: ${{ github.workspace }}/.optix/include + SCCACHE_DIR: ${{ github.workspace }}/.cache/rayd-sccache + SCCACHE_CACHE_SIZE: 1G + SCCACHE_BASEDIRS: ${{ github.workspace }} + SCCACHE_IGNORE_SERVER_IO_ERROR: "1" - torch: - runs-on: [self-hosted, Windows, X64, rayd-gpu] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: {python-version: "3.11"} - - run: python -m pip install scikit-build-core cmake ninja torch - - run: python -m pip install --no-build-isolation -e backends/torch -Cbuild-dir=artifacts/skbuild - - run: python -m unittest discover -s tests/torch_backend -t . -v - working-directory: backends/torch - - name: Audit LibTorch Stable ABI library - shell: pwsh + - name: Check out OptiX 8.1 headers + if: matrix.backend == 'torch' + uses: actions/checkout@v5 + with: + repository: NVIDIA/optix-dev + ref: v8.1.0 + path: .optix + persist-credentials: false + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + cache: pip + - name: Install sccache + uses: mozilla-actions/sccache-action@v0.0.10 + with: + version: "v0.11.0" + - name: Restore compiler and cibuildwheel caches + uses: actions/cache@v5 + with: + path: | + ${{ github.workspace }}/.cache/rayd-sccache + ${{ github.workspace }}/.cache/cibuildwheel + key: rayd-sccache-v2-quick-${{ runner.os }}-${{ matrix.backend }}-${{ github.event.pull_request.head.sha || github.sha }} + restore-keys: | + rayd-sccache-v2-quick-${{ runner.os }}-${{ matrix.backend }}- + + - name: Expose sccache to manylinux + if: runner.os == 'Linux' + shell: bash run: | - $stable = Get-ChildItem backends/torch/artifacts/skbuild -Recurse -Filter _stable_ops.dll | Select-Object -First 1 - if (-not $stable) { throw "_stable_ops.dll was not built" } - python backends/torch/scripts/verify_stable_abi.py --source-root backends/torch/src/stable $stable.FullName + mkdir -p "$GITHUB_WORKSPACE/.cache/sccache-bin" + cp "$(command -v sccache)" "$GITHUB_WORKSPACE/.cache/sccache-bin/sccache" - parity: - if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-parity') - needs: [drjit, torch] - runs-on: [self-hosted, Windows, X64, rayd-gpu] - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: {python-version: "3.11"} - - run: python -m pip install "scikit-build-core>=0.10" "cmake>=3.22" ninja "nanobind==2.9.2" "drjit==1.3.1" torch - - run: python -m pip install --no-build-isolation -e backends/drjit - - run: python -m pip install --no-build-isolation -e backends/torch -Cbuild-dir=artifacts/skbuild - - run: python -m unittest tests.torch_backend.test_drjit_parity -v - working-directory: backends/torch - env: {RAYD_TORCH_RUN_DR_JIT_PARITY: "1"} + - name: Free Linux disk space + if: runner.os == 'Linux' + shell: bash + run: | + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \ + /usr/local/share/boost /opt/hostedtoolcache/CodeQL \ + /usr/local/share/powershell /usr/share/swift || true + sudo docker image prune --all --force || true - packaging: - needs: [drjit, torch] - runs-on: [self-hosted, Windows, X64, rayd-gpu] - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: {python-version: "3.11"} - - run: python -m pip install build "setuptools>=77" "scikit-build-core>=0.10" "cmake>=3.22" ninja "nanobind==2.9.2" "drjit==1.3.1" torch - - run: python -m build --wheel --no-isolation --outdir dist - - run: python -m build --wheel --no-isolation --outdir ../../dist - working-directory: backends/drjit - - run: python -m build --wheel --no-isolation --outdir ../../dist -Cbuild-dir=artifacts/skbuild - working-directory: backends/torch - - name: Audit packaged Stable ABI library + - name: Build Linux wheel + if: runner.os == 'Linux' + uses: pypa/cibuildwheel@v3.3.0 + env: + CIBW_BUILD: "cp312-manylinux_x86_64" + CIBW_SKIP: "*-musllinux_*" + CIBW_CACHE_PATH: ${{ github.workspace }}/.cache/cibuildwheel + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_BUILD_VERBOSITY: "1" + CIBW_BEFORE_ALL_LINUX: >- + dnf install -y dnf-plugins-core gcc-toolset-12 && + rpm --import https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/D42D0685.pub && + dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo && + dnf clean expire-cache && + dnf install -y + cuda-compiler-12-8 + cuda-cudart-devel-12-8 + cuda-cuobjdump-12-8 + ${{ matrix.backend == 'torch' && 'cuda-driver-devel-12-8 cuda-nvrtc-devel-12-8 libcublas-devel-12-8 libcusparse-devel-12-8 libcusolver-devel-12-8' || '' }} && + ln -sfn /usr/local/cuda-12.8 /usr/local/cuda && + /usr/local/cuda/bin/nvcc --version + CIBW_ENVIRONMENT_LINUX: >- + CUDA_HOME=/usr/local/cuda + CUDA_PATH=/usr/local/cuda + CUDA_ROOT=/usr/local/cuda + CUDA_BIN_PATH=/usr/local/cuda + CUDACXX=/usr/local/cuda/bin/nvcc + OPTIX_INCLUDE_DIR=/project/.optix/include + RAYD_SCCACHE_ROOT=/host${{ github.workspace }}/.cache + RAYD_CUDA_GENCODE_ARCHES=87,120 + RAYD_CUDA_PTX_ARCH=120 + RAYD_NVCC_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + CMAKE_CUDA_ARCHITECTURES="87-real;120-real;120-virtual" + TORCH_CUDA_ARCH_LIST="8.7;12.0+PTX" + CMAKE_BUILD_PARALLEL_LEVEL=${{ matrix.backend == 'torch' && '2' || '4' }} + CMAKE_CUDA_FLAGS=${{ matrix.backend == 'torch' && '--threads=2' || '' }} + CMAKE_C_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + CMAKE_CXX_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + CMAKE_CUDA_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + SCCACHE_DIR=$RAYD_SCCACHE_ROOT/rayd-sccache + SCCACHE_CACHE_SIZE=1G + SCCACHE_BASEDIRS=/project + SCCACHE_IGNORE_SERVER_IO_ERROR=1 + PIP_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cu128 + CC=/opt/rh/gcc-toolset-12/root/usr/bin/gcc + CXX=/opt/rh/gcc-toolset-12/root/usr/bin/g++ + CUDAHOSTCXX=/opt/rh/gcc-toolset-12/root/usr/bin/g++ + PATH=/usr/local/cuda/bin:$PATH + LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH + CIBW_REPAIR_WHEEL_COMMAND_LINUX: >- + auditwheel repair --plat manylinux_2_28_x86_64 + --exclude libcuda.so.1 + --exclude libnvoptix.so.1 + --exclude libcudart.so.12 + --exclude libc10.so + --exclude libc10_cuda.so + --exclude libtorch.so + --exclude libtorch_cpu.so + --exclude libtorch_cuda.so + --exclude libtorch_python.so + --exclude libtorch_global_deps.so + --exclude libdrjit-core.so + --exclude libdrjit-extra.so + --exclude libnanothread.so + -w {dest_dir} {wheel} && + python /project/backends/drjit/scripts/verify_cuda_binary_arches.py + ${{ matrix.backend == 'drjit' && '--stem _C' || '--stem _legacy_ops --stem _stable_ops' }} + --expected-sass 87,120 --expected-ptx 120 {dest_dir}/*.whl && + if [ "${{ matrix.backend }}" = "torch" ]; then + python /project/backends/torch/scripts/verify_stable_abi.py + --source-root /project/backends/torch/src/stable {dest_dir}/*.whl; + fi && + $RAYD_SCCACHE_ROOT/sccache-bin/sccache --show-stats && + chown -R --reference=/host${{ github.workspace }} + $RAYD_SCCACHE_ROOT/rayd-sccache + with: + package-dir: backends/${{ matrix.backend }} + output-dir: dist/${{ matrix.backend }} + + - name: Install CUDA Toolkit 12.8 on Windows + if: runner.os == 'Windows' + uses: Jimver/cuda-toolkit@v0.2.35 + with: + cuda: "12.8.1" + method: local + sub-packages: >- + ${{ matrix.backend == 'torch' + && '["nvcc", "cudart", "cuobjdump", "visual_studio_integration", "cublas", "cublas_dev", "cusparse", "cusparse_dev", "cusolver", "cusolver_dev"]' + || '["nvcc", "cudart", "cuobjdump", "visual_studio_integration"]' }} + log-file-suffix: "pr-${{ matrix.backend }}-windows" + - name: Set up MSVC + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: Export Windows CUDA paths + if: runner.os == 'Windows' shell: pwsh run: | - $stable = Get-ChildItem backends/torch/artifacts/skbuild -Recurse -Filter _stable_ops.dll | Select-Object -First 1 - if (-not $stable) { throw "_stable_ops.dll was not built" } - python backends/torch/scripts/verify_stable_abi.py --source-root backends/torch/src/stable $stable.FullName - - shell: pwsh + $cudaRoot = $env:CUDA_PATH + if (-not $cudaRoot) { + $nvcc = (Get-Command nvcc -ErrorAction Stop).Source + $cudaRoot = Split-Path (Split-Path $nvcc -Parent) -Parent + } + "CUDA_HOME=$cudaRoot" >> $env:GITHUB_ENV + "CUDA_PATH=$cudaRoot" >> $env:GITHUB_ENV + "CMAKE_GENERATOR=Ninja" >> $env:GITHUB_ENV + - name: Install Windows build dependencies + if: runner.os == 'Windows' + shell: pwsh run: | - $env:RAYD_META_WHEEL = (Get-ChildItem dist/rayd-*.whl | Where-Object { $_.Name -notmatch '^rayd_(drjit|torch)-' }).FullName - $env:RAYD_DRJIT_WHEEL = (Get-ChildItem dist/rayd_drjit-*.whl).FullName - $env:RAYD_TORCH_WHEEL = (Get-ChildItem dist/rayd_torch-*.whl).FullName - python -m unittest tests.packaging.test_project_metadata tests.packaging.test_wheel_layout tests.packaging.test_wheel_install_matrix -v - - uses: actions/upload-artifact@v4 - with: {name: rayd-wheels, path: dist/*.whl} + python -m pip install --upgrade pip build twine "scikit-build-core>=0.10" "cmake>=3.26" ninja + if ("${{ matrix.backend }}" -eq "drjit") { + python -m pip install --constraint .github/constraints/drjit-build.txt "nanobind==2.9.2" "drjit==1.3.1" + } else { + python -m pip install torch==2.10.0 --index-url https://download.pytorch.org/whl/cu128 + } + - name: Build Windows wheel + if: runner.os == 'Windows' + shell: pwsh + run: | + $output = Join-Path $env:GITHUB_WORKSPACE "dist/${{ matrix.backend }}" + python -m build "backends/${{ matrix.backend }}" --wheel --no-isolation --outdir $output -Cbuild-dir=artifacts/skbuild - coexistence: - needs: [drjit, torch] - runs-on: [self-hosted, Windows, X64, rayd-gpu] - steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 - with: {python-version: "3.11"} - - run: python -m pip install "scikit-build-core>=0.10" "cmake>=3.22" ninja "nanobind==2.9.2" "drjit==1.3.1" torch - - run: python -m pip install --no-build-isolation -e backends/drjit - - run: python -m pip install --no-build-isolation -e backends/torch -Cbuild-dir=artifacts/skbuild - - run: python -m unittest tests.test_namespace_isolation -v + - name: Verify quick CUDA architecture set + if: runner.os == 'Windows' + shell: pwsh + run: | + $wheel = (Get-ChildItem "dist/${{ matrix.backend }}/*.whl").FullName + if ("${{ matrix.backend }}" -eq "drjit") { + python backends/drjit/scripts/verify_cuda_binary_arches.py --stem _C --expected-sass 87,120 --expected-ptx 120 $wheel + } else { + python backends/drjit/scripts/verify_cuda_binary_arches.py --stem _legacy_ops --stem _stable_ops --expected-sass 87,120 --expected-ptx 120 $wheel + python backends/torch/scripts/verify_stable_abi.py --source-root backends/torch/src/stable $wheel + } + python -m pip install twine + python -m twine check $wheel + + - uses: actions/upload-artifact@v4 + with: + name: pr-rayd-${{ matrix.backend }}-${{ runner.os }}-py3.12 + path: dist/${{ matrix.backend }}/*.whl + if-no-files-found: error diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 9638406..fdb3ed6 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -4,21 +4,29 @@ on: push: branches: - main - - "codex/**" - pull_request: + schedule: + - cron: "17 9 * * 1" release: types: [published] workflow_dispatch: + inputs: + scope: + description: Manual validation scope + required: true + default: full + type: choice + options: + - full + - windows-torch-smoke concurrency: - group: distributions-${{ github.ref }} + group: distributions-${{ github.ref }}-${{ github.event_name == 'workflow_dispatch' && github.sha || github.event_name }} cancel-in-progress: ${{ github.event_name != 'release' }} permissions: contents: read env: - CMAKE_BUILD_PARALLEL_LEVEL: "2" RAYD_CUDA_GENCODE_ARCHES: "70,75,80,86,87,89,90,100,101,120" RAYD_CUDA_PTX_ARCH: "120" TORCH_CUDA_ARCH_LIST: "7.0;7.5;8.0;8.6;8.7;8.9;9.0;10.0;10.1;12.0+PTX" @@ -33,7 +41,7 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - run: python -m pip install "tomli>=2; python_version < '3.11'" @@ -45,15 +53,48 @@ jobs: (cd backends/torch && python -m unittest tests.test_project_metadata -v) build-drjit-linux: - name: rayd-drjit / manylinux_2_28 / py3.10-3.14 + name: rayd-drjit / manylinux_2_28 / py${{ matrix.python-version }} runs-on: ubuntu-22.04 - timeout-minutes: 240 + timeout-minutes: 120 needs: metadata + if: inputs.scope != 'windows-torch-smoke' + env: + SCCACHE_DIR: ${{ github.workspace }}/.cache/rayd-sccache + SCCACHE_CACHE_SIZE: 1G + SCCACHE_IGNORE_SERVER_IO_ERROR: "1" + strategy: + fail-fast: false + matrix: + include: + - {python-version: "3.10", cibw-build: "cp310-manylinux_x86_64"} + - {python-version: "3.11", cibw-build: "cp311-manylinux_x86_64"} + - {python-version: "3.12", cibw-build: "cp312-manylinux_x86_64"} + - {python-version: "3.13", cibw-build: "cp313-manylinux_x86_64"} + - {python-version: "3.14", cibw-build: "cp314-manylinux_x86_64"} steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + - name: Install sccache + uses: mozilla-actions/sccache-action@v0.0.10 + with: + version: "v0.11.0" + - name: Restore compiler and cibuildwheel caches + uses: actions/cache@v5 + with: + path: | + ${{ github.workspace }}/.cache/rayd-sccache + ${{ github.workspace }}/.cache/cibuildwheel + key: rayd-sccache-v2-full-linux-drjit-${{ github.event.pull_request.head.sha || github.sha }} + restore-keys: | + rayd-sccache-v2-full-linux-drjit- + - name: Expose sccache to manylinux + shell: bash + run: | + mkdir -p "$GITHUB_WORKSPACE/.cache/sccache-bin" + cp "$(command -v sccache)" "$GITHUB_WORKSPACE/.cache/sccache-bin/sccache" - name: Free disk space shell: bash run: | @@ -64,8 +105,9 @@ jobs: - name: Build and repair Dr.Jit wheels uses: pypa/cibuildwheel@v3.3.0 env: - CIBW_BUILD: "cp310-manylinux_x86_64 cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp314-manylinux_x86_64" + CIBW_BUILD: ${{ matrix.cibw-build }} CIBW_SKIP: "*-musllinux_*" + CIBW_CACHE_PATH: ${{ github.workspace }}/.cache/cibuildwheel CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 CIBW_BUILD_VERBOSITY: "1" CIBW_BEFORE_ALL_LINUX: >- @@ -73,7 +115,10 @@ jobs: rpm --import https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/D42D0685.pub && dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo && dnf clean expire-cache && - dnf install -y cuda-toolkit-12-8 && + dnf install -y + cuda-compiler-12-8 + cuda-cudart-devel-12-8 + cuda-cuobjdump-12-8 && ln -sfn /usr/local/cuda-12.8 /usr/local/cuda && /usr/local/cuda/bin/nvcc --version CIBW_ENVIRONMENT_LINUX: >- @@ -82,10 +127,18 @@ jobs: CUDA_ROOT=/usr/local/cuda CUDA_BIN_PATH=/usr/local/cuda CUDACXX=/usr/local/cuda/bin/nvcc + RAYD_SCCACHE_ROOT=/host${{ github.workspace }}/.cache RAYD_CUDA_GENCODE_ARCHES=70,75,80,86,87,89,90,100,101,120 RAYD_CUDA_PTX_ARCH=120 + RAYD_NVCC_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache PIP_CONSTRAINT=/project/.github/constraints/drjit-build.txt - CMAKE_BUILD_PARALLEL_LEVEL=2 + CMAKE_BUILD_PARALLEL_LEVEL=4 + CMAKE_C_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + CMAKE_CXX_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + SCCACHE_DIR=$RAYD_SCCACHE_ROOT/rayd-sccache + SCCACHE_CACHE_SIZE=1G + SCCACHE_BASEDIRS=/project + SCCACHE_IGNORE_SERVER_IO_ERROR=1 CC=/opt/rh/gcc-toolset-12/root/usr/bin/gcc CXX=/opt/rh/gcc-toolset-12/root/usr/bin/g++ CUDAHOSTCXX=/opt/rh/gcc-toolset-12/root/usr/bin/g++ @@ -100,21 +153,38 @@ jobs: --exclude libnanothread.so -w {dest_dir} {wheel} && python /project/backends/drjit/scripts/verify_cuda_binary_arches.py - --stem _C {dest_dir}/*.whl + --stem _C {dest_dir}/*.whl && + $RAYD_SCCACHE_ROOT/sccache-bin/sccache --show-stats && + chown -R --reference=/host${{ github.workspace }} + $RAYD_SCCACHE_ROOT/rayd-sccache with: package-dir: backends/drjit output-dir: dist/drjit - uses: actions/upload-artifact@v4 with: - name: release-rayd-drjit-linux + name: release-rayd-drjit-linux-py${{ matrix.python-version }} path: dist/drjit/*.whl if-no-files-found: error build-torch-linux: - name: rayd-torch / manylinux_2_28 / py3.10-3.14 + name: rayd-torch / manylinux_2_28 / py${{ matrix.python-version }} runs-on: ubuntu-22.04 - timeout-minutes: 300 + timeout-minutes: 180 needs: metadata + if: inputs.scope != 'windows-torch-smoke' + env: + SCCACHE_DIR: ${{ github.workspace }}/.cache/rayd-sccache + SCCACHE_CACHE_SIZE: 1G + SCCACHE_IGNORE_SERVER_IO_ERROR: "1" + strategy: + fail-fast: false + matrix: + include: + - {python-version: "3.10", cibw-build: "cp310-manylinux_x86_64"} + - {python-version: "3.11", cibw-build: "cp311-manylinux_x86_64"} + - {python-version: "3.12", cibw-build: "cp312-manylinux_x86_64"} + - {python-version: "3.13", cibw-build: "cp313-manylinux_x86_64"} + - {python-version: "3.14", cibw-build: "cp314-manylinux_x86_64"} steps: - uses: actions/checkout@v5 - name: Check out OptiX 8.1 headers @@ -124,9 +194,28 @@ jobs: ref: v8.1.0 path: .optix persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + - name: Install sccache + uses: mozilla-actions/sccache-action@v0.0.10 + with: + version: "v0.11.0" + - name: Restore compiler and cibuildwheel caches + uses: actions/cache@v5 + with: + path: | + ${{ github.workspace }}/.cache/rayd-sccache + ${{ github.workspace }}/.cache/cibuildwheel + key: rayd-sccache-v2-full-linux-torch-${{ github.event.pull_request.head.sha || github.sha }} + restore-keys: | + rayd-sccache-v2-full-linux-torch- + - name: Expose sccache to manylinux + shell: bash + run: | + mkdir -p "$GITHUB_WORKSPACE/.cache/sccache-bin" + cp "$(command -v sccache)" "$GITHUB_WORKSPACE/.cache/sccache-bin/sccache" - name: Free disk space shell: bash run: | @@ -137,8 +226,9 @@ jobs: - name: Build and repair Torch wheels uses: pypa/cibuildwheel@v3.3.0 env: - CIBW_BUILD: "cp310-manylinux_x86_64 cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp314-manylinux_x86_64" + CIBW_BUILD: ${{ matrix.cibw-build }} CIBW_SKIP: "*-musllinux_*" + CIBW_CACHE_PATH: ${{ github.workspace }}/.cache/cibuildwheel CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 CIBW_BUILD_VERBOSITY: "1" CIBW_BEFORE_ALL_LINUX: >- @@ -146,7 +236,15 @@ jobs: rpm --import https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/D42D0685.pub && dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo && dnf clean expire-cache && - dnf install -y cuda-toolkit-12-8 && + dnf install -y + cuda-compiler-12-8 + cuda-cudart-devel-12-8 + cuda-cuobjdump-12-8 + cuda-driver-devel-12-8 + cuda-nvrtc-devel-12-8 + libcublas-devel-12-8 + libcusparse-devel-12-8 + libcusolver-devel-12-8 && ln -sfn /usr/local/cuda-12.8 /usr/local/cuda && /usr/local/cuda/bin/nvcc --version CIBW_ENVIRONMENT_LINUX: >- @@ -156,9 +254,18 @@ jobs: CUDA_BIN_PATH=/usr/local/cuda CUDACXX=/usr/local/cuda/bin/nvcc OPTIX_INCLUDE_DIR=/project/.optix/include + RAYD_SCCACHE_ROOT=/host${{ github.workspace }}/.cache CMAKE_CUDA_ARCHITECTURES="70-real;75-real;80-real;86-real;87-real;89-real;90-real;100-real;101-real;120-real;120-virtual" TORCH_CUDA_ARCH_LIST="7.0;7.5;8.0;8.6;8.7;8.9;9.0;10.0;10.1;12.0+PTX" CMAKE_BUILD_PARALLEL_LEVEL=2 + CMAKE_CUDA_FLAGS=--threads=2 + CMAKE_C_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + CMAKE_CXX_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + CMAKE_CUDA_COMPILER_LAUNCHER=$RAYD_SCCACHE_ROOT/sccache-bin/sccache + SCCACHE_DIR=$RAYD_SCCACHE_ROOT/rayd-sccache + SCCACHE_CACHE_SIZE=1G + SCCACHE_BASEDIRS=/project + SCCACHE_IGNORE_SERVER_IO_ERROR=1 PIP_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cu128 CC=/opt/rh/gcc-toolset-12/root/usr/bin/gcc CXX=/opt/rh/gcc-toolset-12/root/usr/bin/g++ @@ -179,15 +286,18 @@ jobs: --exclude libtorch_global_deps.so -w {dest_dir} {wheel} && python /project/backends/drjit/scripts/verify_cuda_binary_arches.py - --stem _C --stem _stable_ops {dest_dir}/*.whl && + --stem _legacy_ops --stem _stable_ops {dest_dir}/*.whl && python /project/backends/torch/scripts/verify_stable_abi.py - --source-root /project/backends/torch/src/stable {dest_dir}/*.whl + --source-root /project/backends/torch/src/stable {dest_dir}/*.whl && + $RAYD_SCCACHE_ROOT/sccache-bin/sccache --show-stats && + chown -R --reference=/host${{ github.workspace }} + $RAYD_SCCACHE_ROOT/rayd-sccache with: package-dir: backends/torch output-dir: dist/torch - uses: actions/upload-artifact@v4 with: - name: release-rayd-torch-linux + name: release-rayd-torch-linux-py${{ matrix.python-version }} path: dist/torch/*.whl if-no-files-found: error @@ -199,10 +309,25 @@ jobs: strategy: fail-fast: false matrix: - backend: [drjit, torch] - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + backend: ${{ fromJSON(inputs.scope == 'windows-torch-smoke' && '["torch"]' || '["drjit","torch"]') }} + python-version: ${{ fromJSON(inputs.scope == 'windows-torch-smoke' && '["3.10"]' || '["3.10","3.11","3.12","3.13","3.14"]') }} env: CMAKE_CUDA_ARCHITECTURES: "70-real;75-real;80-real;86-real;87-real;89-real;90-real;100-real;101-real;120-real;120-virtual" + CMAKE_BUILD_PARALLEL_LEVEL: ${{ matrix.backend == 'torch' && '2' || '4' }} + RAYD_TORCH_CUDA_GENCODE_FLAGS: >- + --generate-code=arch=compute_70,code=[sm_70,sm_75] + --generate-code=arch=compute_80,code=[sm_80,sm_86,sm_87,sm_89] + --generate-code=arch=compute_90,code=sm_90 + --generate-code=arch=compute_100,code=[sm_100,sm_101] + --generate-code=arch=compute_120,code=[sm_120,compute_120] + CMAKE_C_COMPILER_LAUNCHER: sccache + CMAKE_CXX_COMPILER_LAUNCHER: sccache + CMAKE_CUDA_COMPILER_LAUNCHER: ${{ matrix.backend == 'drjit' && 'sccache' || '' }} + RAYD_NVCC_LAUNCHER: sccache + SCCACHE_DIR: ${{ github.workspace }}/.cache/rayd-sccache + SCCACHE_CACHE_SIZE: 1G + SCCACHE_BASEDIRS: ${{ github.workspace }} + SCCACHE_IGNORE_SERVER_IO_ERROR: "1" OPTIX_INCLUDE_DIR: ${{ github.workspace }}\.optix\include steps: - uses: actions/checkout@v5 @@ -214,14 +339,30 @@ jobs: ref: v8.1.0 path: .optix persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + cache: pip + - name: Install sccache + uses: mozilla-actions/sccache-action@v0.0.10 + with: + version: "v0.11.0" + - name: Restore compiler cache + uses: actions/cache@v5 + with: + path: ${{ github.workspace }}/.cache/rayd-sccache + key: rayd-sccache-v2-full-windows-${{ matrix.backend }}-${{ github.event.pull_request.head.sha || github.sha }} + restore-keys: | + rayd-sccache-v2-full-windows-${{ matrix.backend }}- - name: Install CUDA Toolkit 12.8 uses: Jimver/cuda-toolkit@v0.2.35 with: cuda: "12.8.1" method: local + sub-packages: >- + ${{ matrix.backend == 'torch' + && '["nvcc", "cudart", "cuobjdump", "visual_studio_integration", "cublas", "cublas_dev", "cusparse", "cusparse_dev", "cusolver", "cusolver_dev"]' + || '["nvcc", "cudart", "cuobjdump", "visual_studio_integration"]' }} log-file-suffix: "${{ matrix.backend }}-windows-py${{ matrix.python-version }}" - name: Set up MSVC uses: ilammy/msvc-dev-cmd@v1 @@ -237,6 +378,7 @@ jobs: } "CUDA_HOME=$cudaRoot" >> $env:GITHUB_ENV "CUDA_PATH=$cudaRoot" >> $env:GITHUB_ENV + "CMAKE_GENERATOR=Ninja" >> $env:GITHUB_ENV - name: Install Dr.Jit build dependencies if: matrix.backend == 'drjit' shell: pwsh @@ -256,9 +398,19 @@ jobs: - name: Build Torch wheel if: matrix.backend == 'torch' working-directory: backends/torch - run: >- - python -m build --wheel --no-isolation --outdir ../../dist/torch - -Cbuild-dir=artifacts/skbuild + shell: pwsh + run: | + $startedAt = Get-Date + python -m build --wheel --no-isolation --outdir ../../dist/torch ` + -Cbuild-dir=artifacts/skbuild + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + $elapsedMinutes = ((Get-Date) - $startedAt).TotalMinutes + "Windows Torch full wheel build: {0:N2} minutes" -f $elapsedMinutes + if ($elapsedMinutes -ge 60) { + throw "Windows Torch full wheel build exceeded the 60-minute release limit." + } - name: Verify Dr.Jit wheel if: matrix.backend == 'drjit' shell: pwsh @@ -271,7 +423,7 @@ jobs: shell: pwsh run: | $wheel = (Get-ChildItem dist/torch/*.whl).FullName - python backends/drjit/scripts/verify_cuda_binary_arches.py --stem _C --stem _stable_ops $wheel + python backends/drjit/scripts/verify_cuda_binary_arches.py --stem _legacy_ops --stem _stable_ops $wheel python backends/torch/scripts/verify_stable_abi.py --source-root backends/torch/src/stable $wheel python -m twine check $wheel - uses: actions/upload-artifact@v4 @@ -280,13 +432,89 @@ jobs: path: dist/${{ matrix.backend }}/*.whl if-no-files-found: error + test-stable-torch-abi: + name: Stable ABI / ${{ matrix.os }} / py${{ matrix.compatibility.python-version }} / torch${{ matrix.compatibility.torch-version }} + needs: [build-torch-linux, build-windows-wheels] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, windows-2022] + compatibility: + - {python-version: "3.10", torch-version: "2.10.0", cuda-index: "cu128"} + - {python-version: "3.11", torch-version: "2.10.0", cuda-index: "cu128"} + - {python-version: "3.12", torch-version: "2.10.0", cuda-index: "cu128"} + - {python-version: "3.13", torch-version: "2.10.0", cuda-index: "cu128"} + - {python-version: "3.14", torch-version: "2.10.0", cuda-index: "cu128"} + - {python-version: "3.14", torch-version: "2.11.0", cuda-index: "cu128"} + - {python-version: "3.14", torch-version: "2.12.0", cuda-index: "cu126"} + - {python-version: "3.14", torch-version: "2.13.0", cuda-index: "cu126"} + + steps: + - name: Set up compatibility Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.compatibility.python-version }} + + - name: Download Linux Torch wheels + if: runner.os == 'Linux' + uses: actions/download-artifact@v4 + with: + name: release-rayd-torch-linux-py3.10 + path: wheel-artifact + + - name: Download Windows Torch wheel + if: runner.os == 'Windows' + uses: actions/download-artifact@v4 + with: + name: release-rayd-torch-windows-py3.10 + path: wheel-artifact + + - name: Install compatibility Torch + shell: bash + run: | + python -m pip install --upgrade pip + python -m pip install --no-cache-dir "torch==${{ matrix.compatibility.torch-version }}" \ + --index-url "https://download.pytorch.org/whl/${{ matrix.compatibility.cuda-index }}" + + - name: Load the same Stable ABI library + shell: bash + env: + EXPECTED_TORCH: ${{ matrix.compatibility.torch-version }} + run: | + python - <<'PY' + import os + import zipfile + from pathlib import Path + + import torch + + assert torch.__version__.split("+", 1)[0] == os.environ["EXPECTED_TORCH"] + wheels = sorted(Path("wheel-artifact").glob("*.whl")) + assert wheels, "RayD Torch wheel artifact is missing" + with zipfile.ZipFile(wheels[0]) as archive: + members = [ + name + for name in archive.namelist() + if Path(name).name.startswith("_stable_ops") + and Path(name).suffix in {".dll", ".so"} + ] + assert len(members) == 1, members + archive.extract(members[0], "stable-library") + library = Path("stable-library") / members[0] + torch.ops.load_library(str(library.resolve())) + assert torch.ops.rayd_torch_stable.camera_sample_to_world is not None + assert torch.ops.rayd_torch_stable.intersection_valid is not None + print(f"Loaded {library.name} with torch {torch.__version__}") + PY + build-meta: name: rayd meta wheel and sdist runs-on: ubuntu-22.04 needs: metadata steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.12" - run: python -m pip install --upgrade pip build twine "setuptools>=77" @@ -301,10 +529,10 @@ jobs: validate-wheel-set: name: Validate complete release artifact set runs-on: ubuntu-22.04 - needs: [build-drjit-linux, build-torch-linux, build-windows-wheels, build-meta] + needs: [build-drjit-linux, build-torch-linux, build-windows-wheels, test-stable-torch-abi, build-meta] steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.12" - uses: actions/download-artifact@v4 diff --git a/.github/workflows/stable-abi-ci.yml b/.github/workflows/stable-abi-ci.yml index 2ab891b..cd83fa5 100644 --- a/.github/workflows/stable-abi-ci.yml +++ b/.github/workflows/stable-abi-ci.yml @@ -1,10 +1,6 @@ name: Stable ABI Build on: - push: - branches: - - main - - "codex/**" pull_request: workflow_dispatch: @@ -23,16 +19,24 @@ jobs: os: [ubuntu-22.04, windows-2022] env: CMAKE_BUILD_PARALLEL_LEVEL: "2" - CMAKE_CUDA_ARCHITECTURES: "70-real;75-real;80-real;86-real;87-real;89-real;90-real;100-real;101-real;120-real;120-virtual" - TORCH_CUDA_ARCH_LIST: "7.0;7.5;8.0;8.6;8.7;8.9;9.0;10.0;10.1;12.0+PTX" + CMAKE_CUDA_FLAGS: "--threads=2" + CMAKE_CUDA_ARCHITECTURES: "87-real;120-real;120-virtual" + CMAKE_C_COMPILER_LAUNCHER: sccache + CMAKE_CXX_COMPILER_LAUNCHER: sccache + CMAKE_CUDA_COMPILER_LAUNCHER: sccache + TORCH_CUDA_ARCH_LIST: "8.7;12.0+PTX" OPTIX_INCLUDE_DIR: ${{ github.workspace }}/.optix/include + SCCACHE_DIR: ${{ github.workspace }}/.cache/rayd-sccache + SCCACHE_CACHE_SIZE: 1G + SCCACHE_BASEDIRS: ${{ github.workspace }} + SCCACHE_IGNORE_SERVER_IO_ERROR: "1" steps: - name: Check out RayD - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Check out OptiX 8.1 headers - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: NVIDIA/optix-dev ref: v8.1.0 @@ -40,9 +44,23 @@ jobs: persist-credentials: false - name: Set up Python 3.10 build environment - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.10" + cache: pip + + - name: Install sccache + uses: mozilla-actions/sccache-action@v0.0.10 + with: + version: "v0.11.0" + + - name: Restore compiler cache + uses: actions/cache@v5 + with: + path: ${{ github.workspace }}/.cache/rayd-sccache + key: rayd-sccache-v2-quick-${{ runner.os }}-torch-${{ github.event.pull_request.head.sha || github.sha }} + restore-keys: | + rayd-sccache-v2-quick-${{ runner.os }}-torch- - name: Free disk space on Linux if: runner.os == 'Linux' @@ -53,12 +71,22 @@ jobs: /usr/local/share/powershell /usr/share/swift || true sudo docker image prune --all --force || true - - name: Install CUDA Toolkit 12.8 + - name: Install CUDA Toolkit 12.8 on Linux + if: runner.os == 'Linux' + uses: Jimver/cuda-toolkit@v0.2.35 + with: + cuda: "12.8.1" + method: local + log-file-suffix: "stable-abi-linux" + + - name: Install CUDA Toolkit 12.8 on Windows + if: runner.os == 'Windows' uses: Jimver/cuda-toolkit@v0.2.35 with: cuda: "12.8.1" method: local - log-file-suffix: "stable-abi-${{ runner.os }}" + sub-packages: '["nvcc", "cudart", "cuobjdump", "visual_studio_integration"]' + log-file-suffix: "stable-abi-windows" - name: Set up MSVC if: runner.os == 'Windows' @@ -88,6 +116,9 @@ jobs: shell: bash run: | cmake -S backends/torch -B artifacts/stable-abi -GNinja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_DEFAULT_CMP0141=NEW \ + -DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded \ -DRAYD_TORCH_BUILD_PYTHON_MODULE=OFF \ -DOPTIX_INCLUDE_DIR="$OPTIX_INCLUDE_DIR" @@ -116,6 +147,8 @@ jobs: run: | python backends/drjit/scripts/verify_cuda_binary_arches.py \ --stem _stable_ops \ + --expected-sass 87,120 \ + --expected-ptx 120 \ "$RAYD_STABLE_LIBRARY" - name: Upload Stable ABI library diff --git a/backends/drjit/CI_BUILD_MATRIX.md b/backends/drjit/CI_BUILD_MATRIX.md index 02b7591..0a15465 100644 --- a/backends/drjit/CI_BUILD_MATRIX.md +++ b/backends/drjit/CI_BUILD_MATRIX.md @@ -10,7 +10,7 @@ This document records the release-wheel compatibility contract shared by RayD an | GitHub runners | `ubuntu-22.04` and `windows-2022` for build/validate jobs; `ubuntu-latest` for the publish jobs | | Python | CPython 3.10, 3.11, 3.12, 3.13, and 3.14 | | CUDA build toolkit | CUDA 12.8 Update 1 on Windows; CUDA 12.8 latest update from the RHEL 8 repository in manylinux | -| Native SASS | `sm_70`, `sm_75`, `sm_80`, `sm_86`, `sm_89`, `sm_90`, `sm_100`, `sm_101`, `sm_120` | +| Native SASS | `sm_70`, `sm_75`, `sm_80`, `sm_86`, `sm_87`, `sm_89`, `sm_90`, `sm_100`, `sm_101`, `sm_120` | | Forward-compatible PTX | `compute_120` | | Oldest explicitly covered consumer GPU | Turing / RTX 2080 class (`sm_75`) | | Newest explicitly covered families | Data-center and GeForce/RTX PRO Blackwell (`sm_100`, `sm_101`, `sm_120`) | @@ -31,13 +31,24 @@ The Dr.Jit backend must not add PyTorch merely to make the matrix look identical ## RayD GitHub Actions matrix -The distribution workflow is `.github/workflows/pypi.yml`. Pushes, pull requests, and manual runs build and audit artifacts but never publish them. Only a published GitHub Release enables the trusted-publishing jobs. The existing `rayd` project uses the `pypi` GitHub Environment; the two pending backend projects use the unique `pypi-rayd-drjit` and `pypi-rayd-torch` environments required by PyPI. +Pull requests use `.github/workflows/ci.yml`: GitHub-hosted Linux and Windows +runners build representative CPython 3.12 wheels with native `sm_87` and +`sm_120` SASS plus `compute_120` PTX. This fast profile proves both native +backends compile and package without paying the full release fan-out on every +commit. + +The complete distribution workflow is `.github/workflows/pypi.yml`. It runs +on `main`, weekly, by manual dispatch, and for published releases. Every run +builds and audits the complete release artifact set; only a published GitHub +Release enables trusted publishing. The existing `rayd` project uses the +`pypi` GitHub Environment; the two backend projects use the unique +`pypi-rayd-drjit` and `pypi-rayd-torch` environments required by PyPI. | Job | Matrix | Purpose | | --- | --- | --- | | `metadata` | Python 3.10-3.14 on Ubuntu | Validate all three distributions and the release configuration on every supported interpreter | -| `build-drjit-linux` | CPython 3.10-3.14 through cibuildwheel | Build and repair five `rayd-drjit` `manylinux_2_28_x86_64` wheels inside the CUDA-enabled manylinux image | -| `build-torch-linux` | CPython 3.10-3.14 through cibuildwheel | Build and repair five full `rayd-torch` wheels; audit `_C`, `_stable_ops`, external framework dependencies, and CUDA images | +| `build-drjit-linux` | Five parallel CPython 3.10-3.14 cibuildwheel jobs | Build and repair five `rayd-drjit` `manylinux_2_28_x86_64` wheels inside CUDA-enabled manylinux images | +| `build-torch-linux` | Five parallel CPython 3.10-3.14 cibuildwheel jobs | Build and repair five full `rayd-torch` wheels; audit `_legacy_ops`, `_stable_ops`, external framework dependencies, and CUDA images | | `build-windows-wheels` | 2 backends x Python 3.10-3.14 on `windows-2022` | Build and audit ten `win_amd64` wheels | | `build-meta` | Python 3.12 on Ubuntu | Build and check the pure Python `rayd` wheel and sdist | | `validate-wheel-set` | Ubuntu, after all four build jobs | Validate the complete release artifact set via `tests.packaging.test_release_artifact_matrix`; gates every publish job | @@ -45,6 +56,49 @@ The distribution workflow is `.github/workflows/pypi.yml`. Pushes, pull requests Both native backends keep `manylinux_2_28` rather than changing to the witwin `manylinux_2_35` tag. This is a stricter backward-compatibility target and matches the Dr.Jit and PyTorch 2.10/cu128 Linux wheels used by the builds. +## Build concurrency and caching + +Standard public GitHub-hosted Linux and Windows runners have four vCPUs. +Dr.Jit uses four build-system jobs because its CUDA sources are separate custom +commands. Torch uses two build-system jobs and `nvcc --threads=2`, which lets +each multi-architecture CUDA compilation use two threads without multiplying +four outer jobs by two inner jobs. + +Both workflows install `sccache` 0.11.0. Torch routes C, C++, and CUDA compiler +invocations through CMake launchers. Dr.Jit's explicit NVCC custom commands use +the generated `RAYD_NVCC_LAUNCHER` wrapper, while Ninja still schedules four +independent translation units. Linux manylinux jobs access the host-installed +portable sccache binary and persistent cache through cibuildwheel's default +`/host` mount; `/project` is a container copy and must not hold persistent +compiler state. Before the container exits, it resets the cache tree ownership +to match the host workspace so the GitHub cache post-step can archive it. +Compiler caches are capped at 1 GiB per +OS/backend/profile namespace, restore by prefix across commits, and report +statistics at job completion. Python matrix jobs share that namespace rather +than multiplying the repository cache footprint. The Linux cibuildwheel tool +cache and the Windows pip download cache are also persisted. +Windows hosted Dr.Jit jobs use NVIDIA's network installer and install only +`nvcc`, `cudart`, `cuobjdump`, and Visual Studio integration. Torch needs the +larger cuBLAS, cuSPARSE, and cuSOLVER runtime/development packages referenced by +its public CUDA headers; it uses the action's GitHub-cached local installer but +still selects only those packages. This avoids parallel matrix jobs competing +for NVIDIA network-installer downloads while excluding profilers, samples, and +unrelated CUDA libraries from installation. +Linux manylinux jobs likewise install only `cuda-compiler`, +`cuda-cudart-devel`, and `cuda-cuobjdump`, plus those three math development +libraries and the CUDA driver stub development package for Torch, rather than +the 5 GiB full toolkit metapackage. Torch also installs the NVRTC development +package required by its Caffe2 CMake targets. + +Pull-request cache keys use the PR head commit rather than the synthetic merge +commit. Push, schedule, release, and manual builds use `github.sha`. Restore +prefixes still allow reuse across commits, while a new head can save updated +compiler results instead of being blocked by an immutable empty cache entry. + +Cache misses are always supported. Release correctness does not depend on a +warm cache, and the post-build CUDA binary verifier inspects the produced wheel +regardless of whether compilation was cached. + ## RayD CUDA configuration The Dr.Jit backend compiles CUDA translation units through explicit `nvcc` custom commands. `CMAKE_CUDA_ARCHITECTURES` does not affect those commands, so release CI supplies two dedicated environment variables: @@ -61,6 +115,7 @@ RAYD_CUDA_PTX_ARCH=120 -gencode=arch=compute_75,code=sm_75 -gencode=arch=compute_80,code=sm_80 -gencode=arch=compute_86,code=sm_86 +-gencode=arch=compute_87,code=sm_87 -gencode=arch=compute_89,code=sm_89 -gencode=arch=compute_90,code=sm_90 -gencode=arch=compute_100,code=sm_100 diff --git a/backends/drjit/CMakeLists.txt b/backends/drjit/CMakeLists.txt index b4effe4..66f84d7 100644 --- a/backends/drjit/CMakeLists.txt +++ b/backends/drjit/CMakeLists.txt @@ -82,6 +82,35 @@ find_package(CUDA 11.0 REQUIRED) set(CUDA_PROPAGATE_HOST_FLAGS OFF) mark_as_advanced(CLEAR CUDA_64_BIT_DEVICE_CODE) +# FindCUDA does not support CMAKE_CUDA_COMPILER_LAUNCHER for the explicit NVCC +# custom commands below. CI may provide a compiler launcher through a small +# generated wrapper; local builds keep invoking NVCC directly. +set(RAYD_NVCC_LAUNCHER "" CACHE STRING + "Optional compiler cache or launcher placed before NVCC.") +if(DEFINED ENV{RAYD_NVCC_LAUNCHER} + AND NOT "$ENV{RAYD_NVCC_LAUNCHER}" STREQUAL "") + set(RAYD_NVCC_LAUNCHER "$ENV{RAYD_NVCC_LAUNCHER}") +endif() +if(NOT RAYD_NVCC_LAUNCHER STREQUAL "") + set(RAYD_REAL_NVCC_EXECUTABLE "${CUDA_NVCC_EXECUTABLE}") + if(WIN32) + set(RAYD_NVCC_WRAPPER "${CMAKE_CURRENT_BINARY_DIR}/rayd_nvcc.cmd") + file(WRITE "${RAYD_NVCC_WRAPPER}" + "@\"${RAYD_NVCC_LAUNCHER}\" \"${RAYD_REAL_NVCC_EXECUTABLE}\" %*\r\n") + else() + set(RAYD_NVCC_WRAPPER "${CMAKE_CURRENT_BINARY_DIR}/rayd_nvcc") + file(WRITE "${RAYD_NVCC_WRAPPER}" + "#!/bin/sh\nexec \"${RAYD_NVCC_LAUNCHER}\" \"${RAYD_REAL_NVCC_EXECUTABLE}\" \"$@\"\n") + file(CHMOD "${RAYD_NVCC_WRAPPER}" + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) + endif() + set(CUDA_NVCC_EXECUTABLE "${RAYD_NVCC_WRAPPER}") + message(STATUS "RayD NVCC launcher: ${RAYD_NVCC_LAUNCHER}") +endif() + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") diff --git a/backends/drjit/scripts/verify_cuda_binary_arches.py b/backends/drjit/scripts/verify_cuda_binary_arches.py index bcd8c31..01d66df 100644 --- a/backends/drjit/scripts/verify_cuda_binary_arches.py +++ b/backends/drjit/scripts/verify_cuda_binary_arches.py @@ -38,18 +38,70 @@ def _collect_binaries(inputs: list[Path], stems: tuple[str, ...], extract_root: def _cuobjdump(flag: str, binary: Path) -> str: result = subprocess.run( ["cuobjdump", flag, str(binary)], - check=True, capture_output=True, text=True, ) - return f"{result.stdout}\n{result.stderr}" + if result.returncode == 0: + return f"{result.stdout}\n{result.stderr}" + + errors = [ + f"cuobjdump {flag} failed for {binary} with exit code {result.returncode}:", + result.stderr.strip() or result.stdout.strip() or "", + ] + if binary.suffix == ".so": + with tempfile.TemporaryDirectory(prefix="rayd_cuda_fatbin_") as temp_dir: + fatbin = Path(temp_dir) / f"{binary.stem}.fatbin" + extraction = subprocess.run( + ["objcopy", "--dump-section", f".nv_fatbin={fatbin}", str(binary)], + capture_output=True, + text=True, + ) + if extraction.returncode == 0 and fatbin.is_file() and fatbin.stat().st_size: + retry = subprocess.run( + ["cuobjdump", flag, str(fatbin)], + capture_output=True, + text=True, + ) + if retry.returncode == 0: + return f"{retry.stdout}\n{retry.stderr}" + errors.extend( + [ + f"cuobjdump {flag} failed for extracted {fatbin.name} " + f"with exit code {retry.returncode}:", + retry.stderr.strip() or retry.stdout.strip() or "", + ] + ) + else: + errors.extend( + [ + f"Could not extract .nv_fatbin from {binary}:", + extraction.stderr.strip() + or extraction.stdout.strip() + or "", + ] + ) + raise SystemExit("\n".join(errors)) def main() -> None: parser = argparse.ArgumentParser(description="Verify CUDA SASS and PTX targets in RayD release binaries.") parser.add_argument("inputs", nargs="+", type=Path) parser.add_argument("--stem", action="append", required=True) + parser.add_argument( + "--expected-sass", + default=",".join(EXPECTED_SASS), + help="Comma-separated native SASS targets. Defaults to the release matrix.", + ) + parser.add_argument( + "--expected-ptx", + default=EXPECTED_PTX_TARGET.removeprefix("sm_"), + help="PTX target without the sm_ prefix. Defaults to the release matrix.", + ) args = parser.parse_args() + expected_sass = tuple(arch.strip() for arch in args.expected_sass.split(",") if arch.strip()) + if not expected_sass: + raise SystemExit("--expected-sass must contain at least one architecture.") + expected_ptx_target = f"sm_{args.expected_ptx.strip().removeprefix('sm_')}" with tempfile.TemporaryDirectory(prefix="rayd_cuda_arch_verify_") as temp_dir: binaries = _collect_binaries(args.inputs, tuple(args.stem), Path(temp_dir)) @@ -58,13 +110,13 @@ def main() -> None: for binary in binaries: elf_listing = _cuobjdump("--list-elf", binary) - missing_sass = [arch for arch in EXPECTED_SASS if f"sm_{arch}" not in elf_listing] + missing_sass = [arch for arch in expected_sass if f"sm_{arch}" not in elf_listing] if missing_sass: raise SystemExit(f"{binary} is missing SASS targets: {', '.join(missing_sass)}") ptx_dump = _cuobjdump("--dump-ptx", binary) - if f".target {EXPECTED_PTX_TARGET}" not in ptx_dump: - raise SystemExit(f"{binary} is missing compute 12.0 PTX.") + if f".target {expected_ptx_target}" not in ptx_dump: + raise SystemExit(f"{binary} is missing PTX target {expected_ptx_target}.") print(f"Verified CUDA architectures in {binary}") diff --git a/backends/drjit/tests/test_project_metadata.py b/backends/drjit/tests/test_project_metadata.py index ae16a8d..3a47d0b 100644 --- a/backends/drjit/tests/test_project_metadata.py +++ b/backends/drjit/tests/test_project_metadata.py @@ -75,8 +75,10 @@ def test_release_ci_covers_supported_python_and_cuda_architectures(self): pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8") self.assertIn('requires-python = ">=3.10,<3.15"', pyproject) - for job in ("drjit:", "torch:", "parity:", "packaging:", "coexistence:"): - self.assertIn(job, workflow) + self.assertIn("native-wheel:", workflow) + self.assertIn("os: [ubuntu-22.04, windows-2022]", workflow) + self.assertIn("backend: [drjit, torch]", workflow) + self.assertNotIn("self-hosted", workflow) self.assertIn("name: pypi-rayd-drjit", release) self.assertIn("name: pypi-rayd-torch", release) self.assertEqual(release.count("name: pypi\n"), 1) @@ -89,6 +91,10 @@ def test_release_ci_covers_supported_python_and_cuda_architectures(self): ) self.assertIn("--query-gpu=compute_cap", cmake) self.assertIn("RayD local CUDA architecture: sm_", cmake) + self.assertIn("RAYD_NVCC_LAUNCHER", cmake) + self.assertIn("rayd_nvcc.cmd", cmake) + self.assertIn("rayd_nvcc", cmake) + self.assertIn("RAYD_NVCC_LAUNCHER=", release) self.assertIn("if(DEFINED ENV{VSCMD_VER})", cmake) self.assertEqual(cmake.count('call \\"${RAYD_VSDEVCMD}\\" -arch=x64'), 1) self.assertTrue((ROOT / "CI_BUILD_MATRIX.md").is_file()) @@ -197,8 +203,8 @@ def test_visibility_utilities_use_single_trace_segment_pipeline_launches(self): / "include" / "rayd" / "shared" - / "optix" - / "segment_visibility_device.cuh" + / "multipath" + / "segment_visibility_algo.h" ).read_text(encoding="utf-8") self.assertIn( "", diff --git a/backends/drjit/tests/test_verify_cuda_binary_arches.py b/backends/drjit/tests/test_verify_cuda_binary_arches.py new file mode 100644 index 0000000..88f4791 --- /dev/null +++ b/backends/drjit/tests/test_verify_cuda_binary_arches.py @@ -0,0 +1,41 @@ +from pathlib import Path +from subprocess import CompletedProcess +from unittest import TestCase +from unittest.mock import patch + +from scripts.verify_cuda_binary_arches import _cuobjdump + + +class VerifyCudaBinaryArchesTests(TestCase): + def test_linux_elf_falls_back_to_extracted_fatbin(self): + calls: list[list[str]] = [] + + def run(command, **kwargs): + calls.append(command) + if command[0] == "objcopy": + fatbin = Path(command[2].split("=", 1)[1]) + fatbin.write_bytes(b"fatbin") + return CompletedProcess(command, 0, "", "") + if command[-1].endswith(".fatbin"): + return CompletedProcess(command, 0, "ELF file: kernel.sm_87.cubin", "") + return CompletedProcess(command, 255, "", "host ELF is too large") + + with patch("scripts.verify_cuda_binary_arches.subprocess.run", side_effect=run): + output = _cuobjdump("--list-elf", Path("rayd/torch/_C.so")) + + self.assertIn("sm_87", output) + self.assertEqual([call[0] for call in calls], ["cuobjdump", "objcopy", "cuobjdump"]) + + def test_failure_reports_cuobjdump_stderr(self): + failed = CompletedProcess( + ["cuobjdump"], + 255, + "", + "unsupported input", + ) + with patch( + "scripts.verify_cuda_binary_arches.subprocess.run", + return_value=failed, + ): + with self.assertRaisesRegex(SystemExit, "unsupported input"): + _cuobjdump("--list-elf", Path("_C.pyd")) diff --git a/backends/torch/CMakeLists.txt b/backends/torch/CMakeLists.txt index e692c41..2166269 100644 --- a/backends/torch/CMakeLists.txt +++ b/backends/torch/CMakeLists.txt @@ -7,12 +7,57 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CUDA_STANDARD 17) set(CMAKE_CUDA_STANDARD_REQUIRED ON) +set( + RAYD_TORCH_CUDA_GENCODE_FLAGS + "" + CACHE STRING + "Optional explicit NVCC gencode flags used to share CUDA front-end work across compatible real architectures.") +if(DEFINED ENV{RAYD_TORCH_CUDA_GENCODE_FLAGS} + AND NOT "$ENV{RAYD_TORCH_CUDA_GENCODE_FLAGS}" STREQUAL "") + set( + RAYD_TORCH_CUDA_GENCODE_FLAGS + "$ENV{RAYD_TORCH_CUDA_GENCODE_FLAGS}" + CACHE STRING + "Optional explicit NVCC gencode flags used to share CUDA front-end work across compatible real architectures." + FORCE) +endif() +if(NOT RAYD_TORCH_CUDA_GENCODE_FLAGS STREQUAL "") + separate_arguments( + RAYD_TORCH_CUDA_GENCODE_OPTIONS + NATIVE_COMMAND + "${RAYD_TORCH_CUDA_GENCODE_FLAGS}") + set(RAYD_TORCH_CALLER_CUDA_FLAGS "$ENV{CMAKE_CUDA_FLAGS}") + set( + CMAKE_CUDA_ARCHITECTURES + OFF + CACHE STRING + "CUDA architectures are supplied by RAYD_TORCH_CUDA_GENCODE_FLAGS." + FORCE) +endif() + +function(rayd_torch_apply_cuda_gencode target) + foreach(option IN LISTS RAYD_TORCH_CUDA_GENCODE_OPTIONS) + target_compile_options( + "${target}" + PRIVATE + "$<$:${option}>") + endforeach() +endfunction() + if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type for RayD Torch native targets." FORCE) endif() set(RAYD_TORCH_DEFAULT_CUDA_ARCHITECTURES "native") -if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES +if(DEFINED ENV{CMAKE_CUDA_ARCHITECTURES} + AND NOT "$ENV{CMAKE_CUDA_ARCHITECTURES}" STREQUAL "") + set( + CMAKE_CUDA_ARCHITECTURES + "$ENV{CMAKE_CUDA_ARCHITECTURES}" + CACHE STRING + "CUDA architectures for RayD Torch native kernels." + FORCE) +elseif(NOT DEFINED CMAKE_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES STREQUAL "" OR CMAKE_CUDA_ARCHITECTURES STREQUAL "52") set( @@ -150,13 +195,41 @@ if(RAYD_TORCH_BUILD_NATIVE) endif() endif() find_package(Torch REQUIRED) - if(NOT CMAKE_CUDA_ARCHITECTURES STREQUAL "${RAYD_TORCH_REQUESTED_CUDA_ARCHITECTURES}") + if(NOT RAYD_TORCH_CUDA_GENCODE_FLAGS STREQUAL "") + string( + REGEX REPLACE + "(^|[ \t])-gencode[ \t]+arch=[^ \t]+,code=[^ \t]+" + "" + CMAKE_CUDA_FLAGS + "${CMAKE_CUDA_FLAGS}") + string(STRIP "${CMAKE_CUDA_FLAGS}" CMAKE_CUDA_FLAGS) + if(NOT RAYD_TORCH_CALLER_CUDA_FLAGS STREQUAL "") + string( + FIND + "${CMAKE_CUDA_FLAGS}" + "${RAYD_TORCH_CALLER_CUDA_FLAGS}" + RAYD_TORCH_CALLER_CUDA_FLAGS_INDEX) + if(RAYD_TORCH_CALLER_CUDA_FLAGS_INDEX EQUAL -1) + string( + PREPEND + CMAKE_CUDA_FLAGS + "${RAYD_TORCH_CALLER_CUDA_FLAGS} ") + endif() + endif() + set(CMAKE_CUDA_ARCHITECTURES OFF) + elseif(NOT CMAKE_CUDA_ARCHITECTURES STREQUAL "${RAYD_TORCH_REQUESTED_CUDA_ARCHITECTURES}") set(CMAKE_CUDA_ARCHITECTURES "${RAYD_TORCH_REQUESTED_CUDA_ARCHITECTURES}") endif() - if(NOT CMAKE_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES STREQUAL "OFF") + if(RAYD_TORCH_CUDA_GENCODE_FLAGS STREQUAL "" + AND (NOT CMAKE_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES STREQUAL "OFF")) message(FATAL_ERROR "CMAKE_CUDA_ARCHITECTURES resolved to '${CMAKE_CUDA_ARCHITECTURES}'; RayD Torch kernels would build without -gencode flags.") endif() - message(STATUS "RayD Torch CUDA architectures: ${CMAKE_CUDA_ARCHITECTURES}") + if(NOT RAYD_TORCH_CUDA_GENCODE_FLAGS STREQUAL "") + message(STATUS "RayD Torch grouped CUDA gencode flags: ${RAYD_TORCH_CUDA_GENCODE_FLAGS}") + message(STATUS "RayD Torch CUDA flags after removing Torch gencode flags: ${CMAKE_CUDA_FLAGS}") + else() + message(STATUS "RayD Torch CUDA architectures: ${CMAKE_CUDA_ARCHITECTURES}") + endif() message(STATUS "RayD Torch/Caffe2 CUDA architectures: ${TORCH_CUDA_ARCH_LIST}") # Stable ABI shims are exported by torch_cpu and torch_cuda. Link those @@ -188,6 +261,7 @@ if(RAYD_TORCH_BUILD_NATIVE) PREFIX "" OUTPUT_NAME "_stable_ops" POSITION_INDEPENDENT_CODE ON) + rayd_torch_apply_cuda_gencode(rayd_torch_stable_ops) target_compile_definitions( rayd_torch_stable_ops PRIVATE @@ -204,8 +278,7 @@ if(RAYD_TORCH_BUILD_NATIVE) PRIVATE "${RAYD_TORCH_STABLE_CPU_LIBRARY}" "${RAYD_TORCH_STABLE_CUDA_LIBRARY}" - CUDA::cudart - CUDA::cuda_driver) + CUDA::cudart) target_compile_features(rayd_torch_stable_ops PRIVATE cxx_std_17) install( TARGETS rayd_torch_stable_ops @@ -748,6 +821,7 @@ if(RAYD_TORCH_BUILD_NATIVE) add_library(rayd_torch_native_core STATIC ${RAYD_TORCH_NATIVE_CORE_SOURCES}) set_target_properties(rayd_torch_native_core PROPERTIES POSITION_INDEPENDENT_CODE ON) + rayd_torch_apply_cuda_gencode(rayd_torch_native_core) target_include_directories( rayd_torch_native_core PUBLIC diff --git a/backends/torch/tests/test_project_metadata.py b/backends/torch/tests/test_project_metadata.py index 1edd1f3..18e50fd 100644 --- a/backends/torch/tests/test_project_metadata.py +++ b/backends/torch/tests/test_project_metadata.py @@ -45,8 +45,10 @@ def test_stable_abi_slice_avoids_unstable_torch_and_python_apis(self): stable_target = cmake[stable_start:cmake.index("execute_process(", stable_start)] self.assertNotIn("TORCH_PYTHON_LIBRARY", stable_target) self.assertNotIn('"${TORCH_LIBRARIES}"', stable_target) + self.assertNotIn("CUDA::cuda_driver", stable_target) self.assertIn('"${RAYD_TORCH_STABLE_CPU_LIBRARY}"', stable_target) self.assertIn('"${RAYD_TORCH_STABLE_CUDA_LIBRARY}"', stable_target) + self.assertIn("CUDA::cudart", stable_target) def test_stable_abi_audit_script_is_packaged_with_the_backend(self): script = Path("scripts/verify_stable_abi.py") @@ -65,6 +67,7 @@ def test_local_cuda_build_targets_native_gpu(self): self.assertIn("torch.cuda.get_device_capability()", cmake) self.assertIn("print(f'{major}.{minor}')", cmake) self.assertNotIn("print(f'{major}.{minor}+PTX')", cmake) + self.assertIn("if(DEFINED ENV{CMAKE_CUDA_ARCHITECTURES}", cmake) self.assertIn("ENV{TORCH_CUDA_ARCH_LIST}", cmake) dev_build = Path("scripts/dev_build_native.ps1").read_text(encoding="utf-8") @@ -96,15 +99,80 @@ def test_ci_cuda_fat_binary_covers_witwin_platform_matrix(self): expected_cmake = "70-real;75-real;80-real;86-real;87-real;89-real;90-real;100-real;101-real;120-real;120-virtual" expected_torch = "7.0;7.5;8.0;8.6;8.7;8.9;9.0;10.0;10.1;12.0+PTX" pypi = (root / ".github/workflows/pypi.yml").read_text(encoding="utf-8") + pull_request = (root / ".github/workflows/ci.yml").read_text(encoding="utf-8") stable = (root / ".github/workflows/stable-abi-ci.yml").read_text(encoding="utf-8") - for workflow in (pypi, stable): - self.assertIn(expected_cmake, workflow) - self.assertIn(expected_torch, workflow) + self.assertIn(expected_cmake, pypi) + self.assertIn(expected_torch, pypi) + self.assertIn("87-real;120-real;120-virtual", stable) + self.assertIn("8.7;12.0+PTX", stable) + self.assertIn("--expected-sass 87,120", stable) torch_linux_env = pypi.split("CIBW_ENVIRONMENT_LINUX:", 2)[2].split( "CIBW_REPAIR_WHEEL_COMMAND_LINUX:", 1 )[0] self.assertIn(f'CMAKE_CUDA_ARCHITECTURES="{expected_cmake}"', torch_linux_env) self.assertIn(f'TORCH_CUDA_ARCH_LIST="{expected_torch}"', torch_linux_env) + for python, tag in ( + ("3.10", "cp310"), + ("3.11", "cp311"), + ("3.12", "cp312"), + ("3.13", "cp313"), + ("3.14", "cp314"), + ): + self.assertIn( + f'{{python-version: "{python}", cibw-build: "{tag}-manylinux_x86_64"}}', + pypi, + ) + self.assertIn( + "name: release-rayd-torch-linux-py${{ matrix.python-version }}", + pypi, + ) + self.assertIn("name: release-rayd-torch-linux-py3.10", pypi) + torch_verifier = "--stem _legacy_ops --stem _stable_ops" + self.assertEqual(pypi.count(torch_verifier), 2) + self.assertNotIn("--stem _C --stem _stable_ops", pypi) + self.assertIn('CMAKE_BUILD_PARALLEL_LEVEL=4', pypi) + self.assertIn('CMAKE_BUILD_PARALLEL_LEVEL=2', pypi) + self.assertIn('CMAKE_CUDA_FLAGS=--threads=2', pypi) + self.assertIn( + "github.event_name == 'workflow_dispatch' && github.sha", + pypi, + ) + self.assertIn("windows-torch-smoke", pypi) + self.assertIn( + "inputs.scope == 'windows-torch-smoke' && '[\"torch\"]'", + pypi, + ) + self.assertIn( + "inputs.scope == 'windows-torch-smoke' && '[\"3.10\"]'", + pypi, + ) + windows_wheel_job = pypi.split(" build-windows-wheels:", 1)[1] + self.assertNotIn("CMAKE_CUDA_FLAGS:", windows_wheel_job) + self.assertIn( + "CMAKE_CUDA_COMPILER_LAUNCHER: " + "${{ matrix.backend == 'drjit' && 'sccache' || '' }}", + windows_wheel_job, + ) + self.assertIn('CMAKE_CUDA_COMPILER_LAUNCHER=', pypi) + self.assertIn('mozilla-actions/sccache-action@v0.0.10', pypi) + for grouped_flag in ( + "--generate-code=arch=compute_70,code=[sm_70,sm_75]", + "--generate-code=arch=compute_80,code=[sm_80,sm_86,sm_87,sm_89]", + "--generate-code=arch=compute_90,code=sm_90", + "--generate-code=arch=compute_100,code=[sm_100,sm_101]", + "--generate-code=arch=compute_120,code=[sm_120,compute_120]", + ): + self.assertIn(grouped_flag, pypi) + self.assertIn("Windows Torch full wheel build exceeded the 60-minute release limit.", pypi) + cmake = (root / "backends/torch/CMakeLists.txt").read_text(encoding="utf-8") + self.assertIn("RAYD_TORCH_CUDA_GENCODE_FLAGS", cmake) + self.assertIn("RAYD_TORCH_CALLER_CUDA_FLAGS", cmake) + self.assertIn("-gencode[ \\t]+arch=[^ \\t]+,code=[^ \\t]+", cmake) + self.assertIn("rayd_torch_apply_cuda_gencode(rayd_torch_stable_ops)", cmake) + self.assertIn("rayd_torch_apply_cuda_gencode(rayd_torch_native_core)", cmake) + self.assertIn('CMAKE_CUDA_ARCHITECTURES: "87-real;120-real;120-virtual"', pull_request) + self.assertIn("--expected-sass 87,120", pull_request) + self.assertNotIn("self-hosted", pull_request) def test_explicit_torch_architecture_precedes_environment_and_gpu_detection(self): cmake = Path("CMakeLists.txt").read_text(encoding="utf-8")