Skip to content

Commit 74846bc

Browse files
committed
add cuda
1 parent a013e28 commit 74846bc

6 files changed

Lines changed: 336 additions & 0 deletions

File tree

.github/workflows/linux-cuda.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Build Python Wheels (Linux CUDA)
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags:
8+
- '*'
9+
pull_request:
10+
branches:
11+
- main
12+
release:
13+
types: [published]
14+
workflow_dispatch:
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }}
18+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
19+
20+
jobs:
21+
build-cuda-wheels:
22+
name: Build CUDA Wheels (Linux)
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- name: Checkout code
27+
uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 0
30+
show-progress: true
31+
32+
- name: Set up Docker Buildx
33+
uses: docker/setup-buildx-action@v3
34+
35+
- name: Build wheels
36+
uses: pypa/[email protected]
37+
env:
38+
CIBW_BUILD: "cp311-*linux*"
39+
CIBW_MANYLINUX_X86_64_IMAGE: "quay.io/pypa/manylinux_2_28_x86_64:latest"
40+
CIBW_BEFORE_BUILD: |
41+
# Install CUDA toolkit
42+
# For manylinux containers, you may need to install CUDA manually
43+
# Option 1: Use system package manager (if available)
44+
yum install -y cuda-toolkit-12-4 2>/dev/null || \
45+
apt-get update && apt-get install -y nvidia-cuda-toolkit 2>/dev/null || \
46+
echo "CUDA toolkit installation skipped - may need manual setup"
47+
48+
# Set CUDA environment variables
49+
export CUDA_HOME=/usr/local/cuda
50+
export PATH=$PATH:/usr/local/cuda/bin
51+
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
52+
53+
# Verify CUDA if available
54+
if command -v nvcc &> /dev/null; then
55+
echo "CUDA compiler version:"
56+
nvcc --version || true
57+
fi
58+
CIBW_ENVIRONMENT: "CUDA_HOME=/usr/local/cuda PATH=$PATH:/usr/local/cuda/bin LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH"
59+
CIBW_CONFIG_SETTINGS: "wheel.build-tag=${{ github.run_number }}"
60+
with:
61+
output-dir: wheelhouse
62+
package-dir: linux-cuda
63+
64+
- name: List built wheels
65+
shell: bash
66+
run: |
67+
echo "Built CUDA wheels:"
68+
ls -lh wheelhouse/*.whl || echo "No wheels found"
69+
70+
- name: Upload wheels
71+
uses: actions/upload-artifact@v4
72+
with:
73+
name: wheelhouse-linux-cuda
74+
path: wheelhouse/*.whl
75+

linux-cuda/CMakeLists.txt

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
cmake_minimum_required(VERSION 3.26)
2+
3+
function(split_version str MAJOR MINOR PATCH)
4+
# Parse the string directly into the names provided by the arguments
5+
string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" _ ${str})
6+
7+
set(${MAJOR} ${CMAKE_MATCH_1})
8+
set(${MINOR} ${CMAKE_MATCH_2})
9+
set(${PATCH} ${CMAKE_MATCH_3})
10+
11+
return(PROPAGATE ${MAJOR} ${MINOR} ${PATCH})
12+
endfunction()
13+
14+
if(DEFINED SKBUILD_PROJECT_VERSION)
15+
set(HELLO_CUDA_VERSION ${SKBUILD_PROJECT_VERSION})
16+
else()
17+
set(HELLO_CUDA_VERSION "0.9.0")
18+
endif()
19+
20+
split_version(${HELLO_CUDA_VERSION}
21+
HELLO_CUDA_VERSION_MAJOR
22+
HELLO_CUDA_VERSION_MINOR
23+
HELLO_CUDA_VERSION_PATCH)
24+
25+
# Enable CUDA language support
26+
enable_language(CUDA)
27+
28+
project(hello_cuda VERSION ${HELLO_CUDA_VERSION} LANGUAGES CXX CUDA)
29+
30+
message(STATUS "HELLO_CUDA_VERSION_MAJOR: ${HELLO_CUDA_VERSION_MAJOR}")
31+
message(STATUS "HELLO_CUDA_VERSION_MINOR: ${HELLO_CUDA_VERSION_MINOR}")
32+
message(STATUS "HELLO_CUDA_VERSION_PATCH: ${HELLO_CUDA_VERSION_PATCH}")
33+
34+
# Set C++ standard
35+
set(CMAKE_CXX_STANDARD 17)
36+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
37+
set(CMAKE_CUDA_STANDARD 17)
38+
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
39+
40+
# Find CUDA
41+
find_package(CUDA REQUIRED)
42+
message(STATUS "CUDA found: ${CUDA_FOUND}")
43+
message(STATUS "CUDA version: ${CUDA_VERSION}")
44+
message(STATUS "CUDA toolkit root: ${CUDA_TOOLKIT_ROOT_DIR}")
45+
46+
# Set CUDA architectures (can be overridden by cmake.args in pyproject.toml)
47+
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
48+
set(CMAKE_CUDA_ARCHITECTURES "75;80;86;89;90" CACHE STRING "CUDA architectures")
49+
endif()
50+
51+
# Find pybind11
52+
find_package(pybind11 CONFIG REQUIRED)
53+
54+
# Create the pybind11 module with CUDA support
55+
pybind11_add_module(hello_cuda
56+
src/hello_cuda.cpp
57+
src/cuda_kernel.cu
58+
)
59+
60+
# Link CUDA libraries
61+
target_link_libraries(hello_cuda PRIVATE
62+
${CUDA_LIBRARIES}
63+
)
64+
65+
# Include CUDA directories
66+
target_include_directories(hello_cuda PRIVATE
67+
${CUDA_INCLUDE_DIRS}
68+
)
69+
70+
# Compile definitions
71+
target_compile_definitions(hello_cuda PUBLIC
72+
HELLO_CUDA_VERSION="${HELLO_CUDA_VERSION}"
73+
CUDA_VERSION="${CUDA_VERSION}"
74+
)
75+
76+
# Set CUDA separable compilation
77+
set_property(TARGET hello_cuda PROPERTY CUDA_SEPARABLE_COMPILATION ON)
78+
79+
install(
80+
TARGETS hello_cuda
81+
LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}
82+
RUNTIME DESTINATION ${SKBUILD_PLATLIB_DIR}
83+
ARCHIVE DESTINATION ${SKBUILD_PLATLIB_DIR}
84+
)
85+

linux-cuda/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# CUDA Hello World Example
2+
3+
This directory contains a simple CUDA-enabled Python extension built with pybind11 and CMake.
4+
5+
## Structure
6+
7+
- `CMakeLists.txt` - CMake configuration with CUDA support
8+
- `src/hello_cuda.cpp` - Main C++ code with pybind11 bindings
9+
- `src/cuda_kernel.cu` - CUDA kernel implementation
10+
- `pyproject.toml` - Build configuration for scikit-build-core
11+
12+
## Features
13+
14+
- Simple hello world function that detects CUDA devices
15+
- Vector addition using CUDA kernels
16+
- Demonstrates CUDA integration with pybind11
17+
18+
## Usage
19+
20+
After building and installing:
21+
22+
```python
23+
import hello_cuda
24+
25+
# Get CUDA info
26+
print(hello_cuda.hello())
27+
28+
# Add vectors using CUDA
29+
a = [1.0, 2.0, 3.0, 4.0]
30+
b = [5.0, 6.0, 7.0, 8.0]
31+
result = hello_cuda.add_vectors(a, b)
32+
print(result) # [6.0, 8.0, 10.0, 12.0]
33+
```
34+
35+
## Building
36+
37+
This is configured to build with cibuildwheel in the GitHub Actions workflow (`.github/workflows/linux-cuda.yml`).
38+

linux-cuda/pyproject.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# CUDA-specific build configuration for Linux
2+
# This file is used when building with cibuildwheel for CUDA-enabled wheels
3+
# The actual CUDA installation and environment setup is handled in the GitHub Actions workflow
4+
5+
[tool.scikit-build]
6+
minimum-version = "0.9"
7+
cmake.version = ">=3.26"
8+
build-dir = "build/{wheel_tag}"
9+
wheel.packages = []
10+
11+
# CMake configuration for CUDA
12+
# These args will be passed to CMake during the build
13+
# Note: CUDA language support should be enabled in your CMakeLists.txt
14+
cmake.args = [
15+
"-DCMAKE_CUDA_ARCHITECTURES=75;80;86;89;90", # Common GPU architectures (adjust as needed)
16+
"-DENABLE_CUDA=ON",
17+
"-DCMAKE_CUDA_COMPILER=nvcc",
18+
]
19+
20+
# Example CMakeLists.txt additions needed for CUDA:
21+
# cmake_minimum_required(VERSION 3.26)
22+
# enable_language(CUDA)
23+
# find_package(CUDA REQUIRED)
24+
# set(CMAKE_CUDA_STANDARD 17)

linux-cuda/src/cuda_kernel.cu

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <cuda_runtime.h>
2+
3+
// CUDA kernel to add two vectors
4+
__global__ void add_vectors_kernel(float* a, float* b, float* c, int n) {
5+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
6+
if (idx < n) {
7+
c[idx] = a[idx] + b[idx];
8+
}
9+
}
10+
11+
// C interface for the CUDA kernel
12+
extern "C" void add_vectors_cuda(float* a, float* b, float* c, int n) {
13+
// Calculate grid and block dimensions
14+
int threads_per_block = 256;
15+
int blocks_per_grid = (n + threads_per_block - 1) / threads_per_block;
16+
17+
// Launch kernel
18+
add_vectors_kernel<<<blocks_per_grid, threads_per_block>>>(a, b, c, n);
19+
20+
// Wait for kernel to complete
21+
cudaDeviceSynchronize();
22+
}
23+

linux-cuda/src/hello_cuda.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include <pybind11/pybind11.h>
2+
#include <vector>
3+
#include <string>
4+
#include <cuda_runtime.h>
5+
6+
namespace py = pybind11;
7+
8+
// CUDA kernel declaration (defined in cuda_kernel.cu)
9+
extern "C" void add_vectors_cuda(float* a, float* b, float* c, int n);
10+
11+
// Simple hello function
12+
std::string hello() {
13+
int device_count = 0;
14+
cudaError_t err = cudaGetDeviceCount(&device_count);
15+
16+
std::string result = "v" + std::string(HELLO_CUDA_VERSION) + ": Hello from C++ with CUDA!";
17+
18+
if (err == cudaSuccess && device_count > 0) {
19+
result += "\nFound " + std::to_string(device_count) + " CUDA device(s)";
20+
21+
// Get device info
22+
cudaDeviceProp prop;
23+
if (cudaGetDeviceProperties(&prop, 0) == cudaSuccess) {
24+
result += "\nDevice 0: " + std::string(prop.name);
25+
result += " (Compute " + std::to_string(prop.major) + "." + std::to_string(prop.minor) + ")";
26+
}
27+
} else {
28+
result += "\nCUDA devices not available";
29+
}
30+
31+
return result;
32+
}
33+
34+
// Python function that uses CUDA kernel
35+
py::list add_vectors(py::list a, py::list b) {
36+
// Convert Python lists to vectors
37+
std::vector<float> vec_a, vec_b;
38+
for (auto item : a) {
39+
vec_a.push_back(py::cast<float>(item));
40+
}
41+
for (auto item : b) {
42+
vec_b.push_back(py::cast<float>(item));
43+
}
44+
45+
if (vec_a.size() != vec_b.size()) {
46+
throw std::runtime_error("Vectors must have the same size");
47+
}
48+
49+
int n = vec_a.size();
50+
if (n == 0) {
51+
return py::list();
52+
}
53+
54+
// Allocate device memory
55+
float *d_a, *d_b, *d_c;
56+
cudaMalloc(&d_a, n * sizeof(float));
57+
cudaMalloc(&d_b, n * sizeof(float));
58+
cudaMalloc(&d_c, n * sizeof(float));
59+
60+
// Copy data to device
61+
cudaMemcpy(d_a, vec_a.data(), n * sizeof(float), cudaMemcpyHostToDevice);
62+
cudaMemcpy(d_b, vec_b.data(), n * sizeof(float), cudaMemcpyHostToDevice);
63+
64+
// Launch CUDA kernel
65+
add_vectors_cuda(d_a, d_b, d_c, n);
66+
67+
// Copy result back
68+
std::vector<float> result(n);
69+
cudaMemcpy(result.data(), d_c, n * sizeof(float), cudaMemcpyDeviceToHost);
70+
71+
// Free device memory
72+
cudaFree(d_a);
73+
cudaFree(d_b);
74+
cudaFree(d_c);
75+
76+
// Convert to Python list
77+
py::list py_result;
78+
for (float val : result) {
79+
py_result.append(val);
80+
}
81+
82+
return py_result;
83+
}
84+
85+
PYBIND11_MODULE(hello_cuda, m) {
86+
m.doc() = "Hello world pybind11 module with CUDA support";
87+
m.def("hello", &hello, "Return a friendly greeting from C++ with CUDA info");
88+
m.def("add_vectors", &add_vectors, "Add two vectors using CUDA",
89+
py::arg("a"), py::arg("b"));
90+
}
91+

0 commit comments

Comments
 (0)