Skip to content

Commit 4828417

Browse files
authored
Merge pull request #21 from makepath/experimental-pyoptix-backend
Experimenting with removing cpp extension in favor of pyoptix
2 parents a6be16c + 4a82eec commit 4828417

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1261
-23856
lines changed

.github/workflows/build_wheels.yml

Lines changed: 107 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Build binary wheels and sdist
1+
name: Build and Publish
22

33
on:
44
push:
@@ -7,52 +7,128 @@ on:
77
workflow_dispatch:
88

99
jobs:
10-
build_wheels:
11-
name: Build wheels on ${{ matrix.os }}
12-
runs-on: ${{ matrix.os }}
13-
14-
strategy:
15-
matrix:
16-
os: [ubuntu-20.04, windows-2019]
10+
build:
11+
name: Build wheel and sdist
12+
runs-on: ubuntu-latest
1713

1814
steps:
1915
- name: Checkout source
20-
uses: actions/checkout@v2
16+
uses: actions/checkout@v4
2117
with:
2218
fetch-depth: 0
2319

24-
- name: Build wheels
25-
uses: pypa/cibuildwheel@v2.1.3
26-
env:
27-
CIBW_BUILD: cp37-* cp38-* cp39-* cp310-*
28-
CIBW_SKIP: "*-win32 *-manylinux_i686"
20+
- name: Install Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: "3.12"
2924

30-
- uses: actions/upload-artifact@v2
25+
- name: Install build dependencies
26+
run: |
27+
python -m pip install -U pip
28+
python -m pip install build twine
29+
30+
- name: Build wheel and sdist
31+
run: |
32+
python -m build
33+
34+
- name: Check distributions
35+
run: |
36+
twine check dist/*
37+
38+
- name: List built files
39+
run: |
40+
ls -la dist/
41+
42+
- name: Upload wheel artifact
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: wheel
46+
path: dist/*.whl
47+
48+
- name: Upload sdist artifact
49+
uses: actions/upload-artifact@v4
3150
with:
32-
path: ./wheelhouse/*.whl
51+
name: sdist
52+
path: dist/*.tar.gz
3353

34-
build_sdist:
35-
name: Build sdist
54+
# Optional: test the built wheel installs correctly
55+
test-wheel:
56+
name: Test wheel installation
57+
needs: build
3658
runs-on: ubuntu-latest
59+
3760
steps:
38-
- name: Checkout source
39-
uses: actions/checkout@v2
61+
- name: Install Python
62+
uses: actions/setup-python@v5
4063
with:
41-
fetch-depth: 0
64+
python-version: "3.12"
4265

43-
- name: Install Python
44-
uses: actions/setup-python@v2
66+
- name: Download wheel
67+
uses: actions/download-artifact@v4
4568
with:
46-
python-version: 3.9
69+
name: wheel
70+
path: dist/
4771

48-
- name: Install dependencies
72+
- name: Install wheel and verify contents
4973
run: |
50-
pip install setuptools>=42 wheel
74+
# Install without importing (rtxpy requires cupy/GPU at import time)
75+
python -m pip install dist/*.whl --no-deps
76+
python -m pip install numpy
5177
52-
- name: Build sdist
53-
run: |
54-
python setup.py sdist
78+
# Verify the package files are installed correctly
79+
python -c "
80+
import importlib.util
81+
import os
82+
83+
# Find where rtxpy was installed
84+
spec = importlib.util.find_spec('rtxpy')
85+
assert spec is not None, 'rtxpy package not found'
86+
pkg_dir = os.path.dirname(spec.origin)
87+
print(f'Package installed at: {pkg_dir}')
5588
56-
- uses: actions/upload-artifact@v2
89+
# Check that required files exist
90+
init_path = os.path.join(pkg_dir, '__init__.py')
91+
rtx_path = os.path.join(pkg_dir, 'rtx.py')
92+
ptx_path = os.path.join(pkg_dir, 'kernel.ptx')
93+
94+
assert os.path.exists(init_path), f'__init__.py not found'
95+
assert os.path.exists(rtx_path), f'rtx.py not found'
96+
assert os.path.exists(ptx_path), f'kernel.ptx not found'
97+
98+
print('All required files present:')
99+
print(f' - __init__.py')
100+
print(f' - rtx.py')
101+
print(f' - kernel.ptx')
102+
print('Wheel installation test PASSED!')
103+
"
104+
105+
# Publish to PyPI (only on tag push, not workflow_dispatch)
106+
publish:
107+
name: Publish to PyPI
108+
needs: [build, test-wheel]
109+
runs-on: ubuntu-latest
110+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
111+
112+
# Required for trusted publishing to PyPI
113+
permissions:
114+
id-token: write
115+
116+
environment:
117+
name: pypi
118+
url: https://pypi.org/project/rtxpy/
119+
120+
steps:
121+
- name: Download wheel
122+
uses: actions/download-artifact@v4
57123
with:
58-
path: dist/*.tar.gz
124+
name: wheel
125+
path: dist/
126+
127+
- name: Download sdist
128+
uses: actions/download-artifact@v4
129+
with:
130+
name: sdist
131+
path: dist/
132+
133+
- name: Publish to PyPI
134+
uses: pypa/gh-action-pypi-publish@release/v1
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: GPU Test Cleanup
2+
3+
# Remove the "GPU CI" label after GPU tests complete
4+
# This requires maintainers to explicitly re-add the label for each test run
5+
on:
6+
workflow_run:
7+
workflows: ["GPU Test"]
8+
types: [completed]
9+
10+
jobs:
11+
remove-label:
12+
name: Remove GPU CI Label
13+
runs-on: ubuntu-latest
14+
# Only run if there's an associated PR
15+
if: github.event.workflow_run.event == 'pull_request'
16+
17+
steps:
18+
- name: Remove GPU CI label
19+
uses: actions/github-script@v7
20+
with:
21+
script: |
22+
// Get the PR number from the workflow run
23+
const prNumber = context.payload.workflow_run.pull_requests[0]?.number;
24+
25+
if (!prNumber) {
26+
console.log('No PR number found, skipping label removal');
27+
return;
28+
}
29+
30+
try {
31+
await github.rest.issues.removeLabel({
32+
owner: context.repo.owner,
33+
repo: context.repo.repo,
34+
issue_number: prNumber,
35+
name: 'GPU CI'
36+
});
37+
console.log(`Removed "GPU CI" label from PR #${prNumber}`);
38+
} catch (error) {
39+
// Label might already be removed or not exist
40+
if (error.status === 404) {
41+
console.log('Label not found, may have already been removed');
42+
} else {
43+
throw error;
44+
}
45+
}

.github/workflows/gpu-test.yml

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
name: GPU Test
2+
3+
# GPU tests are triggered by adding the "GPU CI" label to a PR
4+
# This prevents expensive GPU runners from running on every commit
5+
on:
6+
pull_request:
7+
types: [labeled]
8+
9+
jobs:
10+
gpu-test:
11+
name: GPU Test ${{ matrix.python-version }}
12+
# Only run when the "GPU CI" label is added
13+
if: github.event.label.name == 'GPU CI'
14+
15+
# Use the GPU runner group - you need to create this in your org settings
16+
# Go to: Organization Settings > Actions > Runner groups > Create new runner group
17+
# Select "NVIDIA GPU-Optimized Image for Linux" when creating the runner
18+
runs-on:
19+
group: gpu-runners
20+
labels: linux-gpu
21+
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
python-version: ["3.12", "3.13"]
26+
27+
steps:
28+
- name: Checkout source
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 0
32+
33+
- name: Install Python ${{ matrix.python-version }}
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: ${{ matrix.python-version }}
37+
38+
- name: Verify GPU
39+
run: |
40+
echo "=== NVIDIA GPU Info ==="
41+
nvidia-smi
42+
echo ""
43+
echo "=== Driver version check ==="
44+
echo "OptiX 7.7 requires driver 530.41+"
45+
echo "OptiX 8.0 requires driver 535+"
46+
echo "OptiX 9.1 requires driver 590+"
47+
48+
- name: Install CUDA Toolkit
49+
uses: Jimver/cuda-toolkit@v0.2.21
50+
id: cuda-toolkit
51+
with:
52+
cuda: '12.6.3'
53+
method: 'network'
54+
# Only install toolkit components, not drivers (runner already has drivers)
55+
sub-packages: '["nvcc", "cudart-dev", "nvrtc-dev", "thrust"]'
56+
57+
- name: Verify CUDA installation
58+
run: |
59+
echo "=== CUDA Version ==="
60+
nvcc --version
61+
echo "CUDA_PATH=${CUDA_PATH:-not set}"
62+
echo "CUDA installed to: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }}"
63+
64+
- name: Install build dependencies
65+
run: |
66+
sudo apt-get update
67+
sudo apt-get install -y cmake
68+
69+
- name: Install OptiX SDK headers
70+
run: |
71+
# Clone NVIDIA's public OptiX headers repository
72+
# This contains the minimal headers needed to build OptiX applications
73+
# See: https://github.com/NVIDIA/optix-dev
74+
OPTIX_DIR="/opt/optix"
75+
sudo mkdir -p ${OPTIX_DIR}
76+
sudo chown -R $(whoami) ${OPTIX_DIR}
77+
78+
echo "=== Cloning OptiX SDK headers from NVIDIA/optix-dev ==="
79+
# Use OptiX 7.7 for broader driver compatibility (requires driver 530.41+)
80+
# OptiX 9.x requires R590+ drivers which may not be available on all runners
81+
git clone --depth 1 --branch v7.7.0 --verbose https://github.com/NVIDIA/optix-dev.git ${OPTIX_DIR}
82+
83+
# Debug: show what was cloned
84+
echo "=== Contents of ${OPTIX_DIR} ==="
85+
ls -la ${OPTIX_DIR}
86+
echo "=== Contents of ${OPTIX_DIR}/include (if exists) ==="
87+
ls -la ${OPTIX_DIR}/include/ 2>/dev/null || echo "include directory not found"
88+
89+
# Verify the headers are present
90+
if [ -f "${OPTIX_DIR}/include/optix.h" ]; then
91+
echo "OptiX headers installed successfully at: ${OPTIX_DIR}"
92+
echo "OptiX_INSTALL_DIR=${OPTIX_DIR}" >> $GITHUB_ENV
93+
else
94+
echo "ERROR: OptiX headers not found after clone"
95+
echo "Attempting alternative: checking if files are in subdirectory..."
96+
find ${OPTIX_DIR} -name "optix.h" 2>/dev/null || echo "optix.h not found anywhere"
97+
exit 1
98+
fi
99+
100+
- name: Compile PTX for target GPU
101+
run: |
102+
# Detect GPU compute capability
103+
GPU_ARCH=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | head -1 | tr -d '.')
104+
echo "Detected GPU compute capability: sm_${GPU_ARCH}"
105+
106+
# Compile kernel.cu to PTX for the target architecture
107+
echo "=== Compiling kernel.cu to PTX ==="
108+
nvcc -ptx \
109+
-arch=sm_${GPU_ARCH} \
110+
-I${OptiX_INSTALL_DIR}/include \
111+
-I cuda \
112+
--use_fast_math \
113+
-o rtxpy/kernel.ptx \
114+
cuda/kernel.cu
115+
116+
echo "=== PTX compiled successfully ==="
117+
head -15 rtxpy/kernel.ptx
118+
119+
- name: Install otk-pyoptix from source
120+
run: |
121+
echo "Using OptiX from: ${OptiX_INSTALL_DIR}"
122+
123+
# Clone and install otk-pyoptix
124+
git clone --depth 1 https://github.com/NVIDIA/otk-pyoptix.git /tmp/otk-pyoptix
125+
cd /tmp/otk-pyoptix/optix
126+
127+
# Install with OptiX path set
128+
pip install .
129+
130+
- name: Install rtxpy with CUDA dependencies
131+
run: |
132+
python -m pip install -U pip
133+
python -m pip install -ve .[tests,cuda12]
134+
python -m pip list
135+
136+
- name: Run GPU tests
137+
run: |
138+
python -m pytest -v rtxpy/tests
139+
140+
- name: Test basic ray tracing
141+
run: |
142+
python -c "
143+
from rtxpy import RTX
144+
import numpy as np
145+
146+
# Simple triangle mesh test
147+
verts = np.float32([0,0,0, 1,0,0, 0,1,0, 1,1,0])
148+
triangles = np.int32([0,1,2, 2,1,3])
149+
rays = np.float32([0.33,0.33,100, 0,0,0, -1,1000])
150+
hits = np.float32([0,0,0,0])
151+
152+
optix = RTX()
153+
res = optix.build(0, verts, triangles)
154+
assert res == 0, f'Build failed with {res}'
155+
156+
res = optix.trace(rays, hits, 1)
157+
assert res == 0, f'Trace failed with {res}'
158+
159+
print(f'Hit result: t={hits[0]}, normal=({hits[1]}, {hits[2]}, {hits[3]})')
160+
assert hits[0] > 0, 'Expected a hit'
161+
print('GPU ray tracing test PASSED!')
162+
"

0 commit comments

Comments
 (0)