Skip to content

Commit b91e7ed

Browse files
authored
Add CUDA 12.9 support (toolkit 12.9.1, cuDNN 9.20.0.48) (#2)
1 parent 745fabb commit b91e7ed

8 files changed

Lines changed: 337 additions & 1 deletion

File tree

.github/workflows/test-install.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
package: [cuda12.6, cuda12.8, cuda13.2]
15+
package: [cuda12.6, cuda12.8, cuda12.9, cuda13.2]
1616

1717
name: ${{ matrix.package }}
1818

cuda12.9/DESCRIPTION

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Package: cuda12.9
2+
Title: CUDA 12.9 Toolkit Libraries
3+
Version: 1.0.0
4+
Authors@R:
5+
person("Daniel", "Falbel", , "daniel@posit.co", role = c("aut", "cre"))
6+
Description: Provides CUDA 12.9 native libraries (runtime, cuBLAS, cuDNN, cuFFT,
7+
cuSOLVER, cuSPARSE, CUPTI, NVRTC, NvJitLink, NCCL, NVSHMEM, and NVCC) by
8+
downloading pre-built binaries from PyPI at install time. Intended to be used
9+
as a dependency by packages that require CUDA 12.9.
10+
License: MIT + file LICENSE
11+
URL: https://github.com/mlverse/cudatoolkit
12+
BugReports: https://github.com/mlverse/cudatoolkit/issues
13+
Encoding: UTF-8
14+
Roxygen: list(markdown = TRUE)
15+
RoxygenNote: 7.3.2

cuda12.9/LICENSE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
YEAR: 2026
2+
COPYRIGHT HOLDER: Daniel Falbel

cuda12.9/NAMESPACE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by roxygen2: do not edit by hand
2+
3+
export(all_lib_paths)
4+
export(bin_path)
5+
export(cuda_path)
6+
export(include_path)
7+
export(lib_path)

cuda12.9/R/paths.R

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Read component -> wheel_subdir mapping from installed components.tsv
2+
.read_components <- function() {
3+
tsv <- system.file("components.tsv", package = packageName(), mustWork = TRUE)
4+
df <- read.delim(tsv, stringsAsFactors = FALSE)
5+
setNames(df$wheel_subdir, df$component)
6+
}
7+
8+
# Lazily cached component map
9+
.component_subdir_env <- new.env(parent = emptyenv())
10+
11+
.component_subdir <- function(component) {
12+
if (is.null(.component_subdir_env$map)) {
13+
.component_subdir_env$map <- .read_components()
14+
}
15+
subdir <- .component_subdir_env$map[[component]]
16+
if (is.null(subdir)) {
17+
stop(sprintf("Unknown component: '%s'. Available: %s",
18+
component, paste(names(.component_subdir_env$map), collapse = ", ")))
19+
}
20+
subdir
21+
}
22+
23+
#' Path to a CUDA component installation
24+
#'
25+
#' @param component Component name (e.g., "runtime", "cublas", "cudnn").
26+
#' @return A character string with the path to the installed component files.
27+
#' @export
28+
cuda_path <- function(component) {
29+
system.file(file.path("nvidia", .component_subdir(component)),
30+
package = packageName(), mustWork = TRUE)
31+
}
32+
33+
#' Path to the shared library directory
34+
#'
35+
#' All CUDA component libraries are installed into a single shared directory.
36+
#'
37+
#' @return A character string with the path to the lib directory.
38+
#' @export
39+
lib_path <- function() {
40+
system.file("lib", package = packageName(), mustWork = TRUE)
41+
}
42+
43+
#' Path to a CUDA component's headers
44+
#'
45+
#' @param component Component name (e.g., "runtime", "cublas", "cudnn").
46+
#' @return A character string with the path to the include directory.
47+
#' @export
48+
include_path <- function(component) {
49+
file.path(cuda_path(component), "include")
50+
}
51+
52+
#' Path to a CUDA component's binaries
53+
#'
54+
#' @param component Component name (e.g., "nvcc").
55+
#' @return A character string with the path to the bin directory.
56+
#' @export
57+
bin_path <- function(component) {
58+
file.path(cuda_path(component), "bin")
59+
}
60+
61+
#' Path to the shared library directory
62+
#'
63+
#' Alias for \code{\link{lib_path}}. All CUDA component libraries are in a
64+
#' single directory, so this simply returns that path.
65+
#'
66+
#' @return A character vector of length one with the library path.
67+
#' @export
68+
all_lib_paths <- function() {
69+
lib_path()
70+
}

cuda12.9/configure

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#!/bin/sh
2+
3+
# Downloads CUDA toolkit components from PyPI during package installation.
4+
# Component names and pinned versions are read from inst/components.tsv
5+
# (generated at build time by CI).
6+
#
7+
# Environment variables:
8+
# CUDA_COMPONENTS Comma-separated list of components to install.
9+
# Default: all components. Example: "runtime,cublas,cudnn"
10+
# Use @version to override a component's pinned version:
11+
# "runtime,cublas,cudnn@9.10.0.56"
12+
# CUDA_PLATFORM Override platform detection (linux-x64, linux-arm64, windows-x64).
13+
14+
detect_platform() {
15+
if [ -n "$CUDA_PLATFORM" ]; then
16+
case "$CUDA_PLATFORM" in
17+
linux-x64) echo "x86_64" ;;
18+
linux-arm64) echo "aarch64" ;;
19+
windows-x64) echo "win_amd64" ;;
20+
*)
21+
echo "ERROR: Unknown CUDA_PLATFORM: $CUDA_PLATFORM" >&2
22+
exit 1
23+
;;
24+
esac
25+
return
26+
fi
27+
28+
os=$(uname -s)
29+
arch=$(uname -m)
30+
31+
case "$os" in
32+
Linux)
33+
case "$arch" in
34+
x86_64) echo "x86_64" ;;
35+
aarch64) echo "aarch64" ;;
36+
*)
37+
echo "ERROR: Unsupported Linux architecture: $arch" >&2
38+
exit 1
39+
;;
40+
esac
41+
;;
42+
MINGW*|MSYS*|CYGWIN*|Windows_NT)
43+
echo "win_amd64"
44+
;;
45+
*)
46+
echo "ERROR: Unsupported OS: $os" >&2
47+
exit 1
48+
;;
49+
esac
50+
}
51+
52+
is_windows() {
53+
case "$PLATFORM" in
54+
win_amd64) return 0 ;;
55+
*) return 1 ;;
56+
esac
57+
}
58+
59+
download_file() {
60+
url="$1"; dest="$2"
61+
if command -v curl > /dev/null 2>&1; then
62+
curl -sL -o "$dest" "$url"
63+
elif command -v wget > /dev/null 2>&1; then
64+
wget -q -O "$dest" "$url"
65+
else
66+
echo "ERROR: Neither curl nor wget found." >&2
67+
exit 1
68+
fi
69+
}
70+
71+
if ! command -v unzip > /dev/null 2>&1; then
72+
echo "ERROR: unzip not found. Please install unzip." >&2
73+
exit 1
74+
fi
75+
76+
PLATFORM=$(detect_platform) || exit 1
77+
TMPDIR=$(mktemp -d)
78+
trap "rm -rf '$TMPDIR'" EXIT
79+
80+
# Parse CUDA_COMPONENTS filter and version overrides
81+
SELECTED_COMPONENTS="${CUDA_COMPONENTS:-}"
82+
83+
is_selected() {
84+
comp="$1"
85+
# If no filter set, install all
86+
[ -z "$SELECTED_COMPONENTS" ] && return 0
87+
# Check if component (with or without @version) is in the comma-separated list
88+
echo ",$SELECTED_COMPONENTS," | grep -q ",${comp}," || echo ",$SELECTED_COMPONENTS," | grep -q ",${comp}@"
89+
}
90+
91+
# Get version override for a component, or empty if not overridden
92+
get_version_override() {
93+
comp="$1"
94+
[ -z "$SELECTED_COMPONENTS" ] && return
95+
override=$(echo ",$SELECTED_COMPONENTS," | grep -o ",${comp}@[^,]*," | sed "s/,//g;s/${comp}@//")
96+
echo "$override"
97+
}
98+
99+
echo "=== CUDA Toolkit ==="
100+
echo "* Platform: ${PLATFORM}"
101+
if [ -n "$SELECTED_COMPONENTS" ]; then
102+
echo "* Components: ${SELECTED_COMPONENTS}"
103+
fi
104+
echo ""
105+
106+
# Shared lib directory for all components
107+
mkdir -p inst/lib
108+
109+
# Read components.tsv (skip header) and install each component
110+
tail -n +2 inst/components.tsv | while IFS=" " read -r component pypi_package version wheel_subdir extract linux_only; do
111+
[ -z "$component" ] && continue
112+
113+
# Skip components not in the selection
114+
if ! is_selected "$component"; then
115+
echo "* Skipping ${component} (not selected)"
116+
continue
117+
fi
118+
119+
# Skip linux-only components on Windows
120+
if [ "$linux_only" = "yes" ] && is_windows; then
121+
echo "* Skipping ${component} (Linux-only)"
122+
continue
123+
fi
124+
125+
# Check for version override
126+
ver_override=$(get_version_override "$component")
127+
if [ -n "$ver_override" ]; then
128+
version="$ver_override"
129+
echo "* Installing ${component} ${version} (overridden)..."
130+
else
131+
echo "* Installing ${component} ${version}..."
132+
fi
133+
134+
# Fetch PyPI simple index for this package
135+
INDEX_FILE="${TMPDIR}/index_${pypi_package}.html"
136+
download_file "https://pypi.org/simple/${pypi_package}/" "$INDEX_FILE"
137+
138+
# Find matching wheel URL
139+
WHEEL_URL=$(grep -o "href=\"[^\"]*${pypi_package}-${version}[^\"]*${PLATFORM}[^\"]*\"" "$INDEX_FILE" | head -1 | sed 's/href="//;s/"//')
140+
141+
if [ -z "$WHEEL_URL" ]; then
142+
echo " ERROR: Could not find wheel for ${pypi_package}==${version} on ${PLATFORM}" >&2
143+
exit 1
144+
fi
145+
146+
WHEEL_URL=$(echo "$WHEEL_URL" | sed 's/#.*//')
147+
WHEEL_NAME=$(basename "$WHEEL_URL")
148+
WHEEL_PATH="${TMPDIR}/${WHEEL_NAME}"
149+
150+
echo " Downloading ${WHEEL_NAME}..."
151+
download_file "$WHEEL_URL" "$WHEEL_PATH"
152+
153+
if [ ! -f "$WHEEL_PATH" ]; then
154+
echo " ERROR: Download failed." >&2
155+
exit 1
156+
fi
157+
158+
# Extract requested directories from the wheel
159+
for dir in $(echo "$extract" | tr ',' ' '); do
160+
unzip -q -o "$WHEEL_PATH" "nvidia/${wheel_subdir}/${dir}/*" -d inst/
161+
done
162+
163+
# Move shared libraries into the unified lib directory
164+
if [ -d "inst/nvidia/${wheel_subdir}/lib" ]; then
165+
mv inst/nvidia/${wheel_subdir}/lib/* inst/lib/ 2>/dev/null || true
166+
rmdir inst/nvidia/${wheel_subdir}/lib 2>/dev/null || true
167+
fi
168+
169+
# Ensure binaries are executable (for nvcc/ptxas)
170+
if echo "$extract" | grep -q "bin"; then
171+
chmod +x inst/nvidia/${wheel_subdir}/bin/* 2>/dev/null || true
172+
fi
173+
174+
rm -f "$WHEEL_PATH"
175+
echo " Done."
176+
done
177+
178+
# Patch RUNPATH on Linux so each .so resolves dependencies from its own
179+
# directory first, preventing LD_LIBRARY_PATH from pulling in wrong versions
180+
# (e.g. conda's older cuDNN).
181+
get_patchelf() {
182+
command -v patchelf > /dev/null 2>&1 && { echo "patchelf"; return 0; }
183+
184+
# Download patchelf from PyPI wheel (static binary, no Python needed)
185+
PATCHELF_VERSION="0.17.2.2"
186+
case "$(uname -m)" in
187+
x86_64)
188+
PATCHELF_URL="https://files.pythonhosted.org/packages/f2/f9/e070956e350ccdfdf059251836f757ad91ac0c01b0ba3e033ea7188d8d42/patchelf-0.17.2.2-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.musllinux_1_1_x86_64.whl"
189+
;;
190+
aarch64)
191+
PATCHELF_URL="https://files.pythonhosted.org/packages/56/0d/dc3ac6c6e9e9d0d3e40bee1abe95a07034f83627319e60a7dc9abdbfafee/patchelf-0.17.2.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.musllinux_1_1_aarch64.whl"
192+
;;
193+
*) return 1 ;;
194+
esac
195+
196+
PATCHELF_DIR="${TMPDIR}/patchelf"
197+
PATCHELF_WHL="${PATCHELF_DIR}/patchelf.whl"
198+
mkdir -p "$PATCHELF_DIR"
199+
download_file "$PATCHELF_URL" "$PATCHELF_WHL"
200+
unzip -q -o "$PATCHELF_WHL" "patchelf-${PATCHELF_VERSION}.data/scripts/patchelf" -d "$PATCHELF_DIR" || return 1
201+
chmod +x "${PATCHELF_DIR}/patchelf-${PATCHELF_VERSION}.data/scripts/patchelf"
202+
echo "${PATCHELF_DIR}/patchelf-${PATCHELF_VERSION}.data/scripts/patchelf"
203+
}
204+
205+
if ! is_windows; then
206+
PATCHELF_BIN=$(get_patchelf)
207+
if [ -n "$PATCHELF_BIN" ]; then
208+
echo "* Patching RPATH with \$ORIGIN (--force-rpath so it takes precedence over LD_LIBRARY_PATH)..."
209+
for so in inst/lib/*.so*; do
210+
[ -f "$so" ] || continue
211+
"$PATCHELF_BIN" --force-rpath --add-rpath '$ORIGIN' "$so" 2>/dev/null || true
212+
done
213+
echo " Done."
214+
else
215+
echo "* WARNING: Could not obtain patchelf, skipping RUNPATH patching."
216+
echo " Install patchelf for reliable CUDA library loading when"
217+
echo " LD_LIBRARY_PATH contains other CUDA installations."
218+
fi
219+
fi
220+
221+
echo ""
222+
echo "=== All components installed successfully ==="

cuda12.9/inst/components.tsv

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
component pypi_package version wheel_subdir extract linux_only
2+
runtime nvidia_cuda_runtime_cu12 12.9.79 cuda_runtime lib,include no
3+
cublas nvidia_cublas_cu12 12.9.1.4 cublas lib,include no
4+
cupti nvidia_cuda_cupti_cu12 12.9.79 cuda_cupti lib,include no
5+
nvrtc nvidia_cuda_nvrtc_cu12 12.9.86 cuda_nvrtc lib,include no
6+
cufft nvidia_cufft_cu12 11.4.1.4 cufft lib,include no
7+
cusolver nvidia_cusolver_cu12 11.7.5.82 cusolver lib,include no
8+
cusparse nvidia_cusparse_cu12 12.5.10.65 cusparse lib,include no
9+
nvjitlink nvidia_nvjitlink_cu12 12.9.86 nvjitlink lib,include no
10+
nvcc nvidia_cuda_nvcc_cu12 12.9.86 cuda_nvcc bin,include,nvvm no
11+
cudnn nvidia_cudnn_cu12 9.20.0.48 cudnn lib,include no
12+
nccl nvidia_nccl_cu12 2.29.7 nccl lib,include yes
13+
nvshmem nvidia_nvshmem_cu12 3.6.5 nvshmem lib,include yes

cuda12.9/inst/cuda-version.env

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CUDA_MINOR="12.9"
2+
CUDA_MAJOR="12"
3+
TOOLKIT_VERSION="12.9.1"
4+
PKG_VERSION="1.0.0"
5+
CUDNN_VERSION="9.20.0.48"
6+
NCCL_VERSION="2.29.7"
7+
NVSHMEM_VERSION="3.6.5"

0 commit comments

Comments
 (0)