From 28d3bb484a2be96f9590ba3ebaaf5ca33ee46f9e Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Mon, 6 Apr 2026 09:12:28 -0300 Subject: [PATCH] Add CUDA 12.9 support (toolkit 12.9.1, cuDNN 9.20.0.48) --- .github/workflows/test-install.yaml | 2 +- cuda12.9/DESCRIPTION | 15 ++ cuda12.9/LICENSE | 2 + cuda12.9/NAMESPACE | 7 + cuda12.9/R/paths.R | 70 +++++++++ cuda12.9/configure | 222 ++++++++++++++++++++++++++++ cuda12.9/inst/components.tsv | 13 ++ cuda12.9/inst/cuda-version.env | 7 + 8 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 cuda12.9/DESCRIPTION create mode 100644 cuda12.9/LICENSE create mode 100644 cuda12.9/NAMESPACE create mode 100644 cuda12.9/R/paths.R create mode 100755 cuda12.9/configure create mode 100644 cuda12.9/inst/components.tsv create mode 100644 cuda12.9/inst/cuda-version.env diff --git a/.github/workflows/test-install.yaml b/.github/workflows/test-install.yaml index 21fd9c1..3c6bb7e 100644 --- a/.github/workflows/test-install.yaml +++ b/.github/workflows/test-install.yaml @@ -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 }} diff --git a/cuda12.9/DESCRIPTION b/cuda12.9/DESCRIPTION new file mode 100644 index 0000000..76b16f2 --- /dev/null +++ b/cuda12.9/DESCRIPTION @@ -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 diff --git a/cuda12.9/LICENSE b/cuda12.9/LICENSE new file mode 100644 index 0000000..fc752b6 --- /dev/null +++ b/cuda12.9/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2026 +COPYRIGHT HOLDER: Daniel Falbel diff --git a/cuda12.9/NAMESPACE b/cuda12.9/NAMESPACE new file mode 100644 index 0000000..bdd0706 --- /dev/null +++ b/cuda12.9/NAMESPACE @@ -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) diff --git a/cuda12.9/R/paths.R b/cuda12.9/R/paths.R new file mode 100644 index 0000000..ec50eed --- /dev/null +++ b/cuda12.9/R/paths.R @@ -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() +} diff --git a/cuda12.9/configure b/cuda12.9/configure new file mode 100755 index 0000000..550d4bd --- /dev/null +++ b/cuda12.9/configure @@ -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 ===" diff --git a/cuda12.9/inst/components.tsv b/cuda12.9/inst/components.tsv new file mode 100644 index 0000000..9805c77 --- /dev/null +++ b/cuda12.9/inst/components.tsv @@ -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 diff --git a/cuda12.9/inst/cuda-version.env b/cuda12.9/inst/cuda-version.env new file mode 100644 index 0000000..ad0ec76 --- /dev/null +++ b/cuda12.9/inst/cuda-version.env @@ -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"