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
2 changes: 1 addition & 1 deletion .github/workflows/test-install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
package: [cuda12.6, cuda12.8, cuda13.2]
package: [cuda12.6, cuda12.8, cuda12.9, cuda13.2]

name: ${{ matrix.package }}

Expand Down
15 changes: 15 additions & 0 deletions cuda12.9/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Package: cuda12.9
Title: CUDA 12.9 Toolkit Libraries
Version: 1.0.0
Authors@R:
person("Daniel", "Falbel", , "daniel@posit.co", role = c("aut", "cre"))
Description: Provides CUDA 12.9 native libraries (runtime, cuBLAS, cuDNN, cuFFT,
cuSOLVER, cuSPARSE, CUPTI, NVRTC, NvJitLink, NCCL, NVSHMEM, and NVCC) by
downloading pre-built binaries from PyPI at install time. Intended to be used
as a dependency by packages that require CUDA 12.9.
License: MIT + file LICENSE
URL: https://github.com/mlverse/cudatoolkit
BugReports: https://github.com/mlverse/cudatoolkit/issues
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
2 changes: 2 additions & 0 deletions cuda12.9/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
YEAR: 2026
COPYRIGHT HOLDER: Daniel Falbel
7 changes: 7 additions & 0 deletions cuda12.9/NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Generated by roxygen2: do not edit by hand

export(all_lib_paths)
export(bin_path)
export(cuda_path)
export(include_path)
export(lib_path)
70 changes: 70 additions & 0 deletions cuda12.9/R/paths.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Read component -> wheel_subdir mapping from installed components.tsv
.read_components <- function() {
tsv <- system.file("components.tsv", package = packageName(), mustWork = TRUE)
df <- read.delim(tsv, stringsAsFactors = FALSE)
setNames(df$wheel_subdir, df$component)
}

# Lazily cached component map
.component_subdir_env <- new.env(parent = emptyenv())

.component_subdir <- function(component) {
if (is.null(.component_subdir_env$map)) {
.component_subdir_env$map <- .read_components()
}
subdir <- .component_subdir_env$map[[component]]
if (is.null(subdir)) {
stop(sprintf("Unknown component: '%s'. Available: %s",
component, paste(names(.component_subdir_env$map), collapse = ", ")))
}
subdir
}

#' Path to a CUDA component installation
#'
#' @param component Component name (e.g., "runtime", "cublas", "cudnn").
#' @return A character string with the path to the installed component files.
#' @export
cuda_path <- function(component) {
system.file(file.path("nvidia", .component_subdir(component)),
package = packageName(), mustWork = TRUE)
}

#' Path to the shared library directory
#'
#' All CUDA component libraries are installed into a single shared directory.
#'
#' @return A character string with the path to the lib directory.
#' @export
lib_path <- function() {
system.file("lib", package = packageName(), mustWork = TRUE)
}

#' Path to a CUDA component's headers
#'
#' @param component Component name (e.g., "runtime", "cublas", "cudnn").
#' @return A character string with the path to the include directory.
#' @export
include_path <- function(component) {
file.path(cuda_path(component), "include")
}

#' Path to a CUDA component's binaries
#'
#' @param component Component name (e.g., "nvcc").
#' @return A character string with the path to the bin directory.
#' @export
bin_path <- function(component) {
file.path(cuda_path(component), "bin")
}

#' Path to the shared library directory
#'
#' Alias for \code{\link{lib_path}}. All CUDA component libraries are in a
#' single directory, so this simply returns that path.
#'
#' @return A character vector of length one with the library path.
#' @export
all_lib_paths <- function() {
lib_path()
}
222 changes: 222 additions & 0 deletions cuda12.9/configure
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#!/bin/sh

# Downloads CUDA toolkit components from PyPI during package installation.
# Component names and pinned versions are read from inst/components.tsv
# (generated at build time by CI).
#
# Environment variables:
# CUDA_COMPONENTS Comma-separated list of components to install.
# Default: all components. Example: "runtime,cublas,cudnn"
# Use @version to override a component's pinned version:
# "runtime,cublas,cudnn@9.10.0.56"
# CUDA_PLATFORM Override platform detection (linux-x64, linux-arm64, windows-x64).

detect_platform() {
if [ -n "$CUDA_PLATFORM" ]; then
case "$CUDA_PLATFORM" in
linux-x64) echo "x86_64" ;;
linux-arm64) echo "aarch64" ;;
windows-x64) echo "win_amd64" ;;
*)
echo "ERROR: Unknown CUDA_PLATFORM: $CUDA_PLATFORM" >&2
exit 1
;;
esac
return
fi

os=$(uname -s)
arch=$(uname -m)

case "$os" in
Linux)
case "$arch" in
x86_64) echo "x86_64" ;;
aarch64) echo "aarch64" ;;
*)
echo "ERROR: Unsupported Linux architecture: $arch" >&2
exit 1
;;
esac
;;
MINGW*|MSYS*|CYGWIN*|Windows_NT)
echo "win_amd64"
;;
*)
echo "ERROR: Unsupported OS: $os" >&2
exit 1
;;
esac
}

is_windows() {
case "$PLATFORM" in
win_amd64) return 0 ;;
*) return 1 ;;
esac
}

download_file() {
url="$1"; dest="$2"
if command -v curl > /dev/null 2>&1; then
curl -sL -o "$dest" "$url"
elif command -v wget > /dev/null 2>&1; then
wget -q -O "$dest" "$url"
else
echo "ERROR: Neither curl nor wget found." >&2
exit 1
fi
}

if ! command -v unzip > /dev/null 2>&1; then
echo "ERROR: unzip not found. Please install unzip." >&2
exit 1
fi

PLATFORM=$(detect_platform) || exit 1
TMPDIR=$(mktemp -d)
trap "rm -rf '$TMPDIR'" EXIT

# Parse CUDA_COMPONENTS filter and version overrides
SELECTED_COMPONENTS="${CUDA_COMPONENTS:-}"

is_selected() {
comp="$1"
# If no filter set, install all
[ -z "$SELECTED_COMPONENTS" ] && return 0
# Check if component (with or without @version) is in the comma-separated list
echo ",$SELECTED_COMPONENTS," | grep -q ",${comp}," || echo ",$SELECTED_COMPONENTS," | grep -q ",${comp}@"
}

# Get version override for a component, or empty if not overridden
get_version_override() {
comp="$1"
[ -z "$SELECTED_COMPONENTS" ] && return
override=$(echo ",$SELECTED_COMPONENTS," | grep -o ",${comp}@[^,]*," | sed "s/,//g;s/${comp}@//")
echo "$override"
}

echo "=== CUDA Toolkit ==="
echo "* Platform: ${PLATFORM}"
if [ -n "$SELECTED_COMPONENTS" ]; then
echo "* Components: ${SELECTED_COMPONENTS}"
fi
echo ""

# Shared lib directory for all components
mkdir -p inst/lib

# Read components.tsv (skip header) and install each component
tail -n +2 inst/components.tsv | while IFS=" " read -r component pypi_package version wheel_subdir extract linux_only; do
[ -z "$component" ] && continue

# Skip components not in the selection
if ! is_selected "$component"; then
echo "* Skipping ${component} (not selected)"
continue
fi

# Skip linux-only components on Windows
if [ "$linux_only" = "yes" ] && is_windows; then
echo "* Skipping ${component} (Linux-only)"
continue
fi

# Check for version override
ver_override=$(get_version_override "$component")
if [ -n "$ver_override" ]; then
version="$ver_override"
echo "* Installing ${component} ${version} (overridden)..."
else
echo "* Installing ${component} ${version}..."
fi

# Fetch PyPI simple index for this package
INDEX_FILE="${TMPDIR}/index_${pypi_package}.html"
download_file "https://pypi.org/simple/${pypi_package}/" "$INDEX_FILE"

# Find matching wheel URL
WHEEL_URL=$(grep -o "href=\"[^\"]*${pypi_package}-${version}[^\"]*${PLATFORM}[^\"]*\"" "$INDEX_FILE" | head -1 | sed 's/href="//;s/"//')

if [ -z "$WHEEL_URL" ]; then
echo " ERROR: Could not find wheel for ${pypi_package}==${version} on ${PLATFORM}" >&2
exit 1
fi

WHEEL_URL=$(echo "$WHEEL_URL" | sed 's/#.*//')
WHEEL_NAME=$(basename "$WHEEL_URL")
WHEEL_PATH="${TMPDIR}/${WHEEL_NAME}"

echo " Downloading ${WHEEL_NAME}..."
download_file "$WHEEL_URL" "$WHEEL_PATH"

if [ ! -f "$WHEEL_PATH" ]; then
echo " ERROR: Download failed." >&2
exit 1
fi

# Extract requested directories from the wheel
for dir in $(echo "$extract" | tr ',' ' '); do
unzip -q -o "$WHEEL_PATH" "nvidia/${wheel_subdir}/${dir}/*" -d inst/
done

# Move shared libraries into the unified lib directory
if [ -d "inst/nvidia/${wheel_subdir}/lib" ]; then
mv inst/nvidia/${wheel_subdir}/lib/* inst/lib/ 2>/dev/null || true
rmdir inst/nvidia/${wheel_subdir}/lib 2>/dev/null || true
fi

# Ensure binaries are executable (for nvcc/ptxas)
if echo "$extract" | grep -q "bin"; then
chmod +x inst/nvidia/${wheel_subdir}/bin/* 2>/dev/null || true
fi

rm -f "$WHEEL_PATH"
echo " Done."
done

# Patch RUNPATH on Linux so each .so resolves dependencies from its own
# directory first, preventing LD_LIBRARY_PATH from pulling in wrong versions
# (e.g. conda's older cuDNN).
get_patchelf() {
command -v patchelf > /dev/null 2>&1 && { echo "patchelf"; return 0; }

# Download patchelf from PyPI wheel (static binary, no Python needed)
PATCHELF_VERSION="0.17.2.2"
case "$(uname -m)" in
x86_64)
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"
;;
aarch64)
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"
;;
*) return 1 ;;
esac

PATCHELF_DIR="${TMPDIR}/patchelf"
PATCHELF_WHL="${PATCHELF_DIR}/patchelf.whl"
mkdir -p "$PATCHELF_DIR"
download_file "$PATCHELF_URL" "$PATCHELF_WHL"
unzip -q -o "$PATCHELF_WHL" "patchelf-${PATCHELF_VERSION}.data/scripts/patchelf" -d "$PATCHELF_DIR" || return 1
chmod +x "${PATCHELF_DIR}/patchelf-${PATCHELF_VERSION}.data/scripts/patchelf"
echo "${PATCHELF_DIR}/patchelf-${PATCHELF_VERSION}.data/scripts/patchelf"
}

if ! is_windows; then
PATCHELF_BIN=$(get_patchelf)
if [ -n "$PATCHELF_BIN" ]; then
echo "* Patching RPATH with \$ORIGIN (--force-rpath so it takes precedence over LD_LIBRARY_PATH)..."
for so in inst/lib/*.so*; do
[ -f "$so" ] || continue
"$PATCHELF_BIN" --force-rpath --add-rpath '$ORIGIN' "$so" 2>/dev/null || true
done
echo " Done."
else
echo "* WARNING: Could not obtain patchelf, skipping RUNPATH patching."
echo " Install patchelf for reliable CUDA library loading when"
echo " LD_LIBRARY_PATH contains other CUDA installations."
fi
fi

echo ""
echo "=== All components installed successfully ==="
13 changes: 13 additions & 0 deletions cuda12.9/inst/components.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
component pypi_package version wheel_subdir extract linux_only
runtime nvidia_cuda_runtime_cu12 12.9.79 cuda_runtime lib,include no
cublas nvidia_cublas_cu12 12.9.1.4 cublas lib,include no
cupti nvidia_cuda_cupti_cu12 12.9.79 cuda_cupti lib,include no
nvrtc nvidia_cuda_nvrtc_cu12 12.9.86 cuda_nvrtc lib,include no
cufft nvidia_cufft_cu12 11.4.1.4 cufft lib,include no
cusolver nvidia_cusolver_cu12 11.7.5.82 cusolver lib,include no
cusparse nvidia_cusparse_cu12 12.5.10.65 cusparse lib,include no
nvjitlink nvidia_nvjitlink_cu12 12.9.86 nvjitlink lib,include no
nvcc nvidia_cuda_nvcc_cu12 12.9.86 cuda_nvcc bin,include,nvvm no
cudnn nvidia_cudnn_cu12 9.20.0.48 cudnn lib,include no
nccl nvidia_nccl_cu12 2.29.7 nccl lib,include yes
nvshmem nvidia_nvshmem_cu12 3.6.5 nvshmem lib,include yes
7 changes: 7 additions & 0 deletions cuda12.9/inst/cuda-version.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CUDA_MINOR="12.9"
CUDA_MAJOR="12"
TOOLKIT_VERSION="12.9.1"
PKG_VERSION="1.0.0"
CUDNN_VERSION="9.20.0.48"
NCCL_VERSION="2.29.7"
NVSHMEM_VERSION="3.6.5"
Loading