Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 107 additions & 31 deletions .github/workflows/build_wheels.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build binary wheels and sdist
name: Build and Publish

on:
push:
Expand All @@ -7,52 +7,128 @@ on:
workflow_dispatch:

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ubuntu-20.04, windows-2019]
build:
name: Build wheel and sdist
runs-on: ubuntu-latest

steps:
- name: Checkout source
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Build wheels
uses: pypa/cibuildwheel@v2.1.3
env:
CIBW_BUILD: cp37-* cp38-* cp39-* cp310-*
CIBW_SKIP: "*-win32 *-manylinux_i686"
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- uses: actions/upload-artifact@v2
- name: Install build dependencies
run: |
python -m pip install -U pip
python -m pip install build twine

- name: Build wheel and sdist
run: |
python -m build

- name: Check distributions
run: |
twine check dist/*

- name: List built files
run: |
ls -la dist/

- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: wheel
path: dist/*.whl

- name: Upload sdist artifact
uses: actions/upload-artifact@v4
with:
path: ./wheelhouse/*.whl
name: sdist
path: dist/*.tar.gz

build_sdist:
name: Build sdist
# Optional: test the built wheel installs correctly
test-wheel:
name: Test wheel installation
needs: build
runs-on: ubuntu-latest

steps:
- name: Checkout source
uses: actions/checkout@v2
- name: Install Python
uses: actions/setup-python@v5
with:
fetch-depth: 0
python-version: "3.12"

- name: Install Python
uses: actions/setup-python@v2
- name: Download wheel
uses: actions/download-artifact@v4
with:
python-version: 3.9
name: wheel
path: dist/

- name: Install dependencies
- name: Install wheel and verify contents
run: |
pip install setuptools>=42 wheel
# Install without importing (rtxpy requires cupy/GPU at import time)
python -m pip install dist/*.whl --no-deps
python -m pip install numpy

- name: Build sdist
run: |
python setup.py sdist
# Verify the package files are installed correctly
python -c "
import importlib.util
import os

# Find where rtxpy was installed
spec = importlib.util.find_spec('rtxpy')
assert spec is not None, 'rtxpy package not found'
pkg_dir = os.path.dirname(spec.origin)
print(f'Package installed at: {pkg_dir}')

- uses: actions/upload-artifact@v2
# Check that required files exist
init_path = os.path.join(pkg_dir, '__init__.py')
rtx_path = os.path.join(pkg_dir, 'rtx.py')
ptx_path = os.path.join(pkg_dir, 'kernel.ptx')

assert os.path.exists(init_path), f'__init__.py not found'
assert os.path.exists(rtx_path), f'rtx.py not found'
assert os.path.exists(ptx_path), f'kernel.ptx not found'

print('All required files present:')
print(f' - __init__.py')
print(f' - rtx.py')
print(f' - kernel.ptx')
print('Wheel installation test PASSED!')
"

# Publish to PyPI (only on tag push, not workflow_dispatch)
publish:
name: Publish to PyPI
needs: [build, test-wheel]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')

# Required for trusted publishing to PyPI
permissions:
id-token: write

environment:
name: pypi
url: https://pypi.org/project/rtxpy/

steps:
- name: Download wheel
uses: actions/download-artifact@v4
with:
path: dist/*.tar.gz
name: wheel
path: dist/

- name: Download sdist
uses: actions/download-artifact@v4
with:
name: sdist
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
45 changes: 45 additions & 0 deletions .github/workflows/gpu-test-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: GPU Test Cleanup

# Remove the "GPU CI" label after GPU tests complete
# This requires maintainers to explicitly re-add the label for each test run
on:
workflow_run:
workflows: ["GPU Test"]
types: [completed]

jobs:
remove-label:
name: Remove GPU CI Label
runs-on: ubuntu-latest
# Only run if there's an associated PR
if: github.event.workflow_run.event == 'pull_request'

steps:
- name: Remove GPU CI label
uses: actions/github-script@v7
with:
script: |
// Get the PR number from the workflow run
const prNumber = context.payload.workflow_run.pull_requests[0]?.number;

if (!prNumber) {
console.log('No PR number found, skipping label removal');
return;
}

try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: 'GPU CI'
});
console.log(`Removed "GPU CI" label from PR #${prNumber}`);
} catch (error) {
// Label might already be removed or not exist
if (error.status === 404) {
console.log('Label not found, may have already been removed');
} else {
throw error;
}
}
162 changes: 162 additions & 0 deletions .github/workflows/gpu-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
name: GPU Test

# GPU tests are triggered by adding the "GPU CI" label to a PR
# This prevents expensive GPU runners from running on every commit
on:
pull_request:
types: [labeled]

jobs:
gpu-test:
name: GPU Test ${{ matrix.python-version }}
# Only run when the "GPU CI" label is added
if: github.event.label.name == 'GPU CI'

# Use the GPU runner group - you need to create this in your org settings
# Go to: Organization Settings > Actions > Runner groups > Create new runner group
# Select "NVIDIA GPU-Optimized Image for Linux" when creating the runner
runs-on:
group: gpu-runners
labels: linux-gpu

strategy:
fail-fast: false
matrix:
python-version: ["3.12", "3.13"]

steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Verify GPU
run: |
echo "=== NVIDIA GPU Info ==="
nvidia-smi
echo ""
echo "=== Driver version check ==="
echo "OptiX 7.7 requires driver 530.41+"
echo "OptiX 8.0 requires driver 535+"
echo "OptiX 9.1 requires driver 590+"

- name: Install CUDA Toolkit
uses: Jimver/cuda-toolkit@v0.2.21
id: cuda-toolkit
with:
cuda: '12.6.3'
method: 'network'
# Only install toolkit components, not drivers (runner already has drivers)
sub-packages: '["nvcc", "cudart-dev", "nvrtc-dev", "thrust"]'

- name: Verify CUDA installation
run: |
echo "=== CUDA Version ==="
nvcc --version
echo "CUDA_PATH=${CUDA_PATH:-not set}"
echo "CUDA installed to: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }}"

- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake

- name: Install OptiX SDK headers
run: |
# Clone NVIDIA's public OptiX headers repository
# This contains the minimal headers needed to build OptiX applications
# See: https://github.com/NVIDIA/optix-dev
OPTIX_DIR="/opt/optix"
sudo mkdir -p ${OPTIX_DIR}
sudo chown -R $(whoami) ${OPTIX_DIR}

echo "=== Cloning OptiX SDK headers from NVIDIA/optix-dev ==="
# Use OptiX 7.7 for broader driver compatibility (requires driver 530.41+)
# OptiX 9.x requires R590+ drivers which may not be available on all runners
git clone --depth 1 --branch v7.7.0 --verbose https://github.com/NVIDIA/optix-dev.git ${OPTIX_DIR}

# Debug: show what was cloned
echo "=== Contents of ${OPTIX_DIR} ==="
ls -la ${OPTIX_DIR}
echo "=== Contents of ${OPTIX_DIR}/include (if exists) ==="
ls -la ${OPTIX_DIR}/include/ 2>/dev/null || echo "include directory not found"

# Verify the headers are present
if [ -f "${OPTIX_DIR}/include/optix.h" ]; then
echo "OptiX headers installed successfully at: ${OPTIX_DIR}"
echo "OptiX_INSTALL_DIR=${OPTIX_DIR}" >> $GITHUB_ENV
else
echo "ERROR: OptiX headers not found after clone"
echo "Attempting alternative: checking if files are in subdirectory..."
find ${OPTIX_DIR} -name "optix.h" 2>/dev/null || echo "optix.h not found anywhere"
exit 1
fi

- name: Compile PTX for target GPU
run: |
# Detect GPU compute capability
GPU_ARCH=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | head -1 | tr -d '.')
echo "Detected GPU compute capability: sm_${GPU_ARCH}"

# Compile kernel.cu to PTX for the target architecture
echo "=== Compiling kernel.cu to PTX ==="
nvcc -ptx \
-arch=sm_${GPU_ARCH} \
-I${OptiX_INSTALL_DIR}/include \
-I cuda \
--use_fast_math \
-o rtxpy/kernel.ptx \
cuda/kernel.cu

echo "=== PTX compiled successfully ==="
head -15 rtxpy/kernel.ptx

- name: Install otk-pyoptix from source
run: |
echo "Using OptiX from: ${OptiX_INSTALL_DIR}"

# Clone and install otk-pyoptix
git clone --depth 1 https://github.com/NVIDIA/otk-pyoptix.git /tmp/otk-pyoptix
cd /tmp/otk-pyoptix/optix

# Install with OptiX path set
pip install .

- name: Install rtxpy with CUDA dependencies
run: |
python -m pip install -U pip
python -m pip install -ve .[tests,cuda12]
python -m pip list

- name: Run GPU tests
run: |
python -m pytest -v rtxpy/tests

- name: Test basic ray tracing
run: |
python -c "
from rtxpy import RTX
import numpy as np

# Simple triangle mesh test
verts = np.float32([0,0,0, 1,0,0, 0,1,0, 1,1,0])
triangles = np.int32([0,1,2, 2,1,3])
rays = np.float32([0.33,0.33,100, 0,0,0, -1,1000])
hits = np.float32([0,0,0,0])

optix = RTX()
res = optix.build(0, verts, triangles)
assert res == 0, f'Build failed with {res}'

res = optix.trace(rays, hits, 1)
assert res == 0, f'Trace failed with {res}'

print(f'Hit result: t={hits[0]}, normal=({hits[1]}, {hits[2]}, {hits[3]})')
assert hits[0] > 0, 'Expected a hit'
print('GPU ray tracing test PASSED!')
"
Loading