Last active
April 9, 2026 05:15
-
-
Save loopyd/aebbd17d59f8aed7a890e704eb702de0 to your computer and use it in GitHub Desktop.
[bash] CUDA Version Manager - Easily Manage CUDA version on debian/ubuntu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| _S=0; _E=0; _U=0; _P=0 | |
| if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then | |
| _S=1 | |
| [[ "$-" == *e* ]] && _E=1 | |
| [[ "$-" == *u* ]] && _U=1 | |
| [[ "$(set -o | awk '$1=="pipefail"{print $2}')" == on ]] && _P=1 | |
| fi | |
| set -euo pipefail | |
| N=$(basename "${BASH_SOURCE[0]}"); X=install; CV=("11.8" "12.1") | |
| DF=""; MD=1; DR=0; QT=0; NC=0; FC=0; UV=""; RB=""; RC=() | |
| PF="/etc/apt/preferences.d/cuda-repository-pin-600" | |
| DP="/etc/apt/preferences.d/cuda-driver-block-pin" | |
| SF="/etc/apt/sources.list.d/cuda-nvidia.list"; AR="" | |
| ZR=""; ZI=""; ZW=""; ZE=""; ZK="" | |
| setc() { | |
| if ((NC)) || [[ ! -t 1 ]]; then | |
| ZR=""; ZI=""; ZW=""; ZE=""; ZK="" | |
| else | |
| ZR=$(tput sgr0 || echo ""); ZI=$(tput setaf 2 || echo "") | |
| ZW=$(tput setaf 3 || echo ""); ZE=$(tput setaf 1 || echo "") | |
| ZK=$(tput setaf 4 || echo "") | |
| fi | |
| } | |
| setc | |
| cln() { | |
| local s="$1" | |
| s="$(printf '%s' "$s" | sed -E $'s/\x1B\[[0-9;?]*[ -/]*[@-~]//g; s/[^[:print:]\t]/ /g')" | |
| s="${s#"${s%%[![:space:]]*}"}"; s="${s%"${s##*[![:space:]]}"}" | |
| printf '%s' "$s" | |
| } | |
| log() { | |
| local lvl="$1" col="$2" raw ln seen=0 | |
| ((QT)) && [[ "$lvl" != ERR ]] && return 0 | |
| shift 2; raw="$*" | |
| while IFS= read -r ln || [[ -n "$ln" ]]; do | |
| ln="$(cln "$ln")" | |
| [[ -z "$ln" ]] && continue | |
| printf '%s | %b[%s] %s%b\n' "$(date +"%F %T")" "$col" "$lvl" "$ln" "$ZR" | |
| seen=1 | |
| done < <(printf '%s\n' "$raw") | |
| ((seen)) || printf '%s | %b[%s] %s%b\n' "$(date +"%F %T")" "$col" "$lvl" "" "$ZR" | |
| } | |
| i() { log INFO "$ZI" "$*"; } | |
| w() { log WARN "$ZW" "$*"; } | |
| e() { log ERR "$ZE" "$*"; } | |
| ok() { log OK "$ZK" "$*"; } | |
| die() { e "$*"; exit 1; } | |
| nv() { local a b; IFS='.' read -r a b _ <<<"${1:-}"; [[ -n "$a" && -n "$b" ]] && echo "$a.$b"; } | |
| ac() { | |
| local v | |
| command -v nvidia-smi >/dev/null 2>&1 || return 1 | |
| v="$(nvidia-smi 2>/dev/null | sed -nE 's/.*CUDA Version: *([0-9]+\.[0-9]+).*/\1/p' | head -n 1)" | |
| v="$(nv "$v" || true)" | |
| [[ -n "$v" ]] || return 1 | |
| printf '%s' "$v" | |
| } | |
| us() { | |
| cat <<EOF | |
| CUDA Version Manager - Install and manage multiple CUDA versions on Debian/Ubuntu | |
| Author: loopyd <loopyd@github.com> | GPL 3.0 License | |
| Usage: | |
| ${N} install [options] [VERSION ...] | |
| ${N} uninstall [options] [VERSION ...] | |
| ${N} use [options] VERSION | |
| ${N} list | |
| ${N} help | |
| Install options: | |
| -v, --versions LIST Comma/space-separated versions (ex: "11.8,12.1") | |
| -d, --default VER Default module version | |
| -r, --dry-run Validate and print actions without system changes | |
| -f, --force Allow cuDNN major switch (may remove existing cuDNN packages) | |
| -m, --no-modules Skip environment-modules setup | |
| Uninstall options: | |
| -v, --versions LIST Comma/space-separated versions (ex: "11.8,12.1") | |
| -r, --dry-run Validate and print actions without system changes | |
| -m, --no-modules Skip environment-modules cleanup | |
| Use options: | |
| -r, --dry-run Print exports without changing shell | |
| -h, --help Show help | |
| Global options: | |
| -q, --quiet Suppress non-error output | |
| -n, --no-color Disable colored output | |
| Examples: | |
| ${N} install --versions "11.8,12.1" --default 12.1 | |
| ${N} install 12.1 --dry-run | |
| ${N} install # auto-detect via nvidia-smi when available | |
| ${N} uninstall --versions "12.1" | |
| ${N} uninstall 11.8 --dry-run | |
| ${N} use 11.8 | |
| ${N} list | |
| Notes: | |
| - 'use' must be sourced to affect your current shell. | |
| - Example: source ./${N} use 12.1 | |
| - NVIDIA apt cuDNN packages generally provide/conflict on shared virtual names, so one CUDA-major cuDNN target is installed at a time. | |
| - Use -f/--force to switch cuDNN target major during install. | |
| EOF | |
| } | |
| rt() { | |
| local tries="$1" n=1 rc=0 | |
| shift | |
| while true; do | |
| "$@" && return 0 | |
| rc=$? | |
| ((n >= tries)) && return "$rc" | |
| w "Command failed (attempt ${n}/${tries}): $*" | |
| sleep 2 | |
| ((n++)) | |
| done | |
| } | |
| au() { | |
| local -n arr="$1" | |
| local x="$2" y | |
| [[ -n "$x" ]] || return 0 | |
| for y in "${arr[@]:-}"; do [[ "$y" == "$x" ]] && return 0; done | |
| arr+=("$x") | |
| } | |
| eb() { | |
| local rc line h="" | |
| ((MD)) || return 0 | |
| line='source /usr/share/modules/init/bash' | |
| if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != root ]]; then | |
| h="$(getent passwd "$SUDO_USER" | cut -d: -f6 || true)" | |
| fi | |
| [[ -n "$h" ]] || h="${HOME:-/root}" | |
| rc="${h}/.bashrc" | |
| if [[ -f "$rc" ]] && grep -Fqx -- "$line" "$rc"; then | |
| i "Bash init already configured for modules: $rc" | |
| return 0 | |
| fi | |
| if ((DR)); then | |
| i "[dry-run] would append '$line' to $rc" | |
| return 0 | |
| fi | |
| touch "$rc" | |
| printf '\n%s\n' "$line" >>"$rc" | |
| if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != root ]]; then | |
| chown "$SUDO_USER":"$SUDO_USER" "$rc" 2>/dev/null || true | |
| fi | |
| ok "Added modules init to $rc" | |
| } | |
| pa() { | |
| local -a metas=() | |
| if ((DR)); then | |
| i "[dry-run] would write driver-protection pin: $DP" | |
| else | |
| cat >"$DP" <<'EOF' | |
| Package: libnvidia-* nvidia-* xserver-xorg-video-nvidia-* | |
| Pin: release o=NVIDIA,l=NVIDIA CUDA | |
| Pin-Priority: -1 | |
| EOF | |
| fi | |
| if [[ -f "$SF" ]] && compgen -G "/etc/apt/sources.list.d/cuda-*.list" >/dev/null; then | |
| w "Removing legacy CUDA source list to avoid duplicate apt targets: $SF" | |
| ((DR)) || rm -f "$SF" | |
| fi | |
| mapfile -t metas < <(dpkg-query -W -f='${Package}\n' 2>/dev/null | grep -E -- '^cuda-[0-9]+-[0-9]+$' || true) | |
| if ((${#metas[@]})); then | |
| w "Found legacy CUDA meta-packages that may force driver changes: ${metas[*]}" | |
| if ((DR)); then | |
| i "[dry-run] would purge: ${metas[*]}" | |
| else | |
| apt-get -y purge "${metas[@]}" || true | |
| fi | |
| fi | |
| if (( ! DR )) && dpkg --audit | grep -q .; then | |
| die "dpkg reports broken packages. Run: apt-get -y --fix-broken install (driver repo pin is now in $DP)" | |
| fi | |
| } | |
| oc() { | |
| [[ -r /etc/os-release ]] || die "Cannot read /etc/os-release" | |
| # shellcheck disable=SC1091 | |
| source /etc/os-release | |
| [[ -n "${VERSION_ID:-}" ]] || die "VERSION_ID missing" | |
| [[ "$(dpkg --print-architecture)" == "amd64" ]] || die "Only amd64/x86_64 is supported" | |
| local major="${VERSION_ID%%.*}" uv_id="${UBUNTU_VERSION_ID:-}" uv_code="${UBUNTU_CODENAME:-}" up_rel="" up_code="" r | |
| local -a cands=() | |
| [[ "${ID:-}" == ubuntu ]] && au cands "ubuntu${VERSION_ID//./}" | |
| [[ "${ID:-}" == debian ]] && au cands "debian${major}" | |
| if [[ " ${ID_LIKE:-} ${ID:-} " == *" ubuntu "* ]]; then | |
| [[ -n "$uv_id" ]] && au cands "ubuntu${uv_id//./}" | |
| if [[ -r /etc/upstream-release/lsb-release ]]; then | |
| up_rel="$(awk -F= '/^DISTRIB_RELEASE=/{gsub(/"/, "", $2); print $2; exit}' /etc/upstream-release/lsb-release || true)" | |
| up_code="$(awk -F= '/^DISTRIB_CODENAME=/{gsub(/"/, "", $2); print $2; exit}' /etc/upstream-release/lsb-release || true)" | |
| [[ -n "$up_rel" ]] && au cands "ubuntu${up_rel//./}" | |
| fi | |
| [[ -z "$uv_code" && -n "$up_code" ]] && uv_code="$up_code" | |
| case "$uv_code" in noble) au cands ubuntu2404 ;; jammy) au cands ubuntu2204 ;; focal) au cands ubuntu2004 ;; bionic) au cands ubuntu1804 ;; esac | |
| au cands ubuntu2404; au cands ubuntu2204; au cands ubuntu2004; au cands ubuntu1804 | |
| fi | |
| if [[ " ${ID_LIKE:-} " == *" debian "* ]]; then | |
| au cands "debian${major}"; au cands debian12; au cands debian11 | |
| fi | |
| RB=""; RC=() | |
| for r in "${cands[@]}"; do | |
| if wget -q --spider "https://developer.download.nvidia.com/compute/cuda/repos/${r}/x86_64/cuda-${r}.pin"; then | |
| RC+=("$r") | |
| [[ -z "$RB" ]] && RB="$r" | |
| fi | |
| done | |
| [[ -n "$RB" ]] || die "No supported NVIDIA CUDA repo found for ${ID:-unknown} ${VERSION_ID:-unknown}. Tried: ${cands[*]:-none}" | |
| i "Detected distro ${ID:-unknown} ${VERSION_ID:-unknown}; candidate repos: ${RC[*]}" | |
| } | |
| vv() { | |
| local v n; local -a out=() | |
| ((${#CV[@]})) || die "At least one CUDA version is required" | |
| for v in "${CV[@]}"; do n="$(nv "$v")" || true; [[ -n "$n" ]] || die "Invalid CUDA version '$v'"; out+=("$n"); done | |
| CV=("${out[@]}") | |
| } | |
| cvt() { | |
| local v n found=0 | |
| vv | |
| if [[ -z "$DF" ]]; then DF="${CV[0]}"; else DF="$(nv "$DF")" || true; [[ -n "$DF" ]] || die "Invalid --default format"; fi | |
| for v in "${CV[@]}"; do [[ "$v" == "$DF" ]] && found=1 && break; done | |
| ((found)) || die "Default '$DF' is not in install list: ${CV[*]}" | |
| } | |
| svi() { | |
| local list="$1"; shift; local -a pos=("$@") | |
| [[ -n "$list" && ${#pos[@]} -gt 0 ]] && die "Use either --versions or positional versions, not both" | |
| if [[ -n "$list" ]]; then list="${list//,/ }"; read -r -a CV <<<"$list"; return 0; fi | |
| ((${#pos[@]})) || return 1 | |
| CV=("${pos[@]}") | |
| } | |
| ps() { | |
| local list="" | |
| local act_set=0 | |
| local -a pos=() | |
| if (($#)); then | |
| case "$1" in | |
| install|uninstall|use|list|help) X="$1"; act_set=1; shift ;; | |
| -h|--help) us; exit 0 ;; | |
| esac | |
| fi | |
| [[ "$X" == help ]] && { us; exit 0; } | |
| while (($#)); do | |
| case "$1" in | |
| -h|--help) us; exit 0 ;; | |
| -q|--quiet) QT=1; shift ;; | |
| -n|--no-color) NC=1; setc; shift ;; | |
| -r|--dry-run) DR=1; shift ;; | |
| -f|--force) [[ "$X" == install ]] || die "Unknown option for this action: $1"; FC=1; shift ;; | |
| -v|--versions) [[ "$X" == install || "$X" == uninstall ]] || die "Unknown option for this action: $1"; (($# > 1)) || die "Missing value for $1"; list="$2"; shift 2 ;; | |
| --version) die "The use action takes VERSION positionally. Example: ${N} use 12.1" ;; | |
| -d|--default) [[ "$X" == install ]] || die "Unknown option for this action: $1"; (($# > 1)) || die "Missing value for $1"; DF="$2"; shift 2 ;; | |
| -m|--no-modules) [[ "$X" == install || "$X" == uninstall ]] || die "Unknown option for this action: $1"; MD=0; shift ;; | |
| --) shift; while (($#)); do pos+=("$1"); shift; done ;; | |
| -*) die "Unknown option: $1" ;; | |
| *) | |
| if (( ! act_set )); then | |
| case "$1" in | |
| install|uninstall|use|list|help) X="$1"; act_set=1; shift; continue ;; | |
| esac | |
| fi | |
| pos+=("$1"); shift ;; | |
| esac | |
| done | |
| case "$X" in | |
| install) | |
| if ! svi "$list" "${pos[@]}"; then | |
| list="$(ac || true)" | |
| if [[ -n "$list" ]]; then | |
| CV=("$list") | |
| [[ -z "$DF" ]] && DF="$list" | |
| i "Auto-detected CUDA version from nvidia-smi: $list" | |
| else | |
| w "No version specified and CUDA auto-detection failed; using defaults: ${CV[*]}" | |
| fi | |
| fi | |
| cvt ;; | |
| uninstall) | |
| svi "$list" "${pos[@]}" || die "Uninstall requires at least one CUDA version" | |
| vv ;; | |
| use) | |
| [[ ${#pos[@]} -eq 1 ]] || die "The use action needs exactly one positional CUDA version" | |
| UV="${pos[0]}" | |
| UV="$(nv "$UV")" || true | |
| [[ -n "$UV" ]] || die "Invalid CUDA version" ;; | |
| list) | |
| ((${#pos[@]} == 0)) || die "The list action takes no positional arguments" ;; | |
| *) die "Unknown action: $X" ;; | |
| esac | |
| } | |
| rpk() { | |
| local r="$1" b t | |
| b="https://developer.download.nvidia.com/compute/cuda/repos/${r}/x86_64" | |
| t="$(mktemp)" | |
| if rt 2 wget -qO "$t" "$b/Packages.gz" && gzip -t "$t" >/dev/null 2>&1; then gzip -dc "$t"; rm -f "$t"; return 0; fi | |
| rm -f "$t" | |
| rt 2 wget -qO- "$b/Packages" | |
| } | |
| rhp() { local r="$1" p="$2"; rpk "$r" | grep -qm1 -E -- "^Package: ${p}$"; } | |
| rha() { local p="$1" r; for r in "${RC[@]}"; do rhp "$r" "$p" && return 0; done; return 1; } | |
| rkd() { local r="$1"; rpk "$r" | awk '/^Package: cuda-keyring$/{f=1} f&&/^Filename: /{print $2; exit}' | sed 's#^\./##'; } | |
| rfm() { | |
| local major="$1" r cand | |
| for r in "${RC[@]}"; do | |
| cand="$(rpk "$r" | awk -v M="$major" ' | |
| /^Package: cudnn[0-9]+-cuda-[0-9]+$/ { | |
| pkg=$2 | |
| if (pkg ~ ("^cudnn[0-9]+-cuda-" M "$") ) { | |
| gen=pkg | |
| sub(/^cudnn/, "", gen) | |
| sub(/-cuda-.*/, "", gen) | |
| gen+=0 | |
| if (gen > best_gen) { best_gen=gen; best_pkg=pkg } | |
| } | |
| } | |
| END { if (best_pkg != "") print best_pkg } | |
| ')" | |
| [[ -n "$cand" ]] && { printf '%s' "$cand"; return 0; } | |
| done | |
| return 1 | |
| } | |
| icm() { | |
| dpkg-query -W -f='${Package}\n' 2>/dev/null \ | |
| | sed -nE 's/^cudnn[0-9]+-cuda-([0-9]+)(-[0-9]+)?$/\1/p' \ | |
| | sort -u | head -n1 | |
| } | |
| pi() { | |
| local pkg="$1" what="$2" sel="" r kdeb td | |
| for r in "${RC[@]}"; do rhp "$r" "$pkg" && { sel="$r"; break; }; done | |
| [[ -n "$sel" ]] || die "No package '$pkg' found. Tried repos: ${RC[*]}" | |
| [[ "$sel" != "$RB" ]] && w "Using fallback repo $sel for ${what}" | |
| i "Resolved package for ${what} (${sel}): ${pkg}" | |
| ((DR)) && { i "[dry-run] would install $pkg from repo $sel"; return 0; } | |
| if [[ "$AR" != "$sel" ]]; then | |
| rt 3 wget -qO "$PF" "https://developer.download.nvidia.com/compute/cuda/repos/${sel}/x86_64/cuda-${sel}.pin" | |
| kdeb="$(rkd "$sel" || true)"; [[ -n "$kdeb" ]] || die "Unable to locate cuda-keyring package for repo $sel" | |
| td="$(mktemp -d)" | |
| rt 3 wget -qO "$td/cuda-keyring.deb" "https://developer.download.nvidia.com/compute/cuda/repos/${sel}/x86_64/${kdeb}" | |
| dpkg -i "$td/cuda-keyring.deb" | |
| rm -rf "$td" | |
| rm -f "$SF" | |
| if ! compgen -G "/etc/apt/sources.list.d/cuda-${sel}-*.list" >/dev/null; then | |
| printf 'deb [signed-by=/usr/share/keyrings/cuda-archive-keyring.gpg] https://developer.download.nvidia.com/compute/cuda/repos/%s/x86_64/ /\n' "$sel" > "$SF" | |
| fi | |
| rt 3 apt-get update | |
| AR="$sel" | |
| fi | |
| rt 3 apt-get -y install "$pkg" | |
| } | |
| i1() { | |
| local v="$1" m s pkg | |
| m="$(nv "$v")" || true | |
| [[ -n "$m" ]] || { w "Skipping invalid version '$v'"; return; } | |
| s="${m//./-}"; pkg="cuda-toolkit-${s}" | |
| pi "$pkg" "CUDA $m" | |
| } | |
| u1() { | |
| local v="$1" m s major p | |
| local -a pkgs=() others=() | |
| m="$(nv "$v")" || true | |
| [[ -n "$m" ]] || { w "Skipping invalid version '$v'"; return; } | |
| s="${m//./-}" | |
| major="${m%%.*}" | |
| mapfile -t pkgs < <( | |
| dpkg-query -W -f='${Package}\n' 2>/dev/null \ | |
| | grep -E -- "^((cuda|cudnn|libcudnn|libcu|libnpp|libnv|gds|nsight)-.*-${s}(-.*)?|cuda-toolkit-${s}|cuda-${s})$" \ | |
| | sort -u || true | |
| ) | |
| mapfile -t others < <( | |
| dpkg-query -W -f='${Package}\n' 2>/dev/null \ | |
| | grep -E -- "^cuda-toolkit-${major}-[0-9]+$" \ | |
| | grep -Ev -- "^cuda-toolkit-${s}$" || true | |
| ) | |
| if ((${#others[@]} == 0)); then | |
| p="cuda-toolkit-${major}-config-common" | |
| dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q -- '^install ok installed$' && au pkgs "$p" | |
| fi | |
| if ((${#pkgs[@]} == 0)); then | |
| i "No installed CUDA packages found for ${m}" | |
| return 0 | |
| fi | |
| i "Removing CUDA ${m} packages: ${pkgs[*]}" | |
| if ((DR)); then | |
| i "[dry-run] would purge: ${pkgs[*]}" | |
| else | |
| rt 3 apt-get -y purge "${pkgs[@]}" | |
| fi | |
| } | |
| ic() { | |
| local m="$DF" major cur v vm pkg | |
| local -a old_pkgs=() | |
| local -a majors=() | |
| m="$(nv "$m")" || true | |
| [[ -n "$m" ]] || die "Unable to resolve cuDNN target from default CUDA version" | |
| major="${m%%.*}" | |
| for v in "${CV[@]}"; do | |
| vm="$(nv "$v")" || true | |
| [[ -n "$vm" ]] || continue | |
| au majors "${vm%%.*}" | |
| done | |
| if ((${#majors[@]} > 1)); then | |
| w "Multiple CUDA majors requested (${majors[*]}). cuDNN will target one major at a time via apt packages." | |
| w "Using default CUDA ${major} as cuDNN target unless an existing cuDNN install is detected" | |
| fi | |
| cur="$(icm || true)" | |
| if [[ -n "$cur" && "$cur" != "$major" ]]; then | |
| if (( ! FC )); then | |
| w "Detected installed cuDNN for CUDA ${cur}; skipping cuDNN switch to CUDA ${major} to avoid apt package removal churn" | |
| w "Use -f/--force to switch cuDNN target major" | |
| return 0 | |
| fi | |
| mapfile -t old_pkgs < <(dpkg-query -W -f='${Package}\n' 2>/dev/null | grep -E -- "^(cudnn[0-9]+-cuda-${cur}(-[0-9]+)?|libcudnn[0-9]+(-[a-z0-9]+)*-cuda-${cur})$" || true) | |
| if ((${#old_pkgs[@]})); then | |
| w "Force mode: removing existing cuDNN packages for CUDA ${cur}: ${old_pkgs[*]}" | |
| if ((DR)); then | |
| i "[dry-run] would purge: ${old_pkgs[*]}" | |
| else | |
| rt 3 apt-get -y purge "${old_pkgs[@]}" | |
| fi | |
| else | |
| w "Force mode enabled, but no installed cuDNN package set was found for CUDA ${cur}" | |
| fi | |
| fi | |
| pkg="$(rfm "$major" || true)" | |
| if [[ -n "$pkg" ]]; then | |
| pi "$pkg" "cuDNN for CUDA ${major}" | |
| return 0 | |
| fi | |
| if rha "libcudnn8" && rha "libcudnn8-dev"; then | |
| w "No versioned cuDNN meta found for CUDA ${major}; falling back to libcudnn8 packages" | |
| pi "libcudnn8" "cuDNN runtime for CUDA ${m}" | |
| pi "libcudnn8-dev" "cuDNN development files for CUDA ${m}" | |
| return 0 | |
| fi | |
| die "No supported cuDNN packages found for CUDA ${m} in repos: ${RC[*]}" | |
| } | |
| mt() { | |
| local v="$1" p="$2" d='$' | |
| cat >"$p" <<EOF | |
| #%Module1.0 | |
| ## cuda ${v} modulefile | |
| proc ModulesHelp { } { | |
| global version | |
| puts stderr "\\tSets up environment for CUDA ${d}version\\n" | |
| } | |
| module-whatis "sets up environment for CUDA ${v}" | |
| set version ${v} | |
| set root /usr/local/cuda-${v} | |
| setenv CUDA_HOME ${d}root | |
| prepend-path PATH ${d}root/bin | |
| prepend-path LD_LIBRARY_PATH ${d}root/extras/CUPTI/lib64 | |
| prepend-path LD_LIBRARY_PATH ${d}root/lib64 | |
| conflict cuda | |
| EOF | |
| } | |
| cm() { | |
| local d="/usr/share/modules/modulefiles/cuda" v | |
| ((DR)) && { i "[dry-run] would install environment-modules and write modulefiles in $d"; return 0; } | |
| rt 3 apt-get -y install environment-modules | |
| mkdir -p "$d" | |
| for v in "${CV[@]}"; do mt "$v" "$d/$v"; done | |
| cat >"$d/.version" <<EOF | |
| #%Module | |
| set ModulesVersion ${DF} | |
| EOF | |
| } | |
| rmv() { | |
| local d="/usr/share/modules/modulefiles/cuda" v p dv="" | |
| local -a rem=() | |
| ((MD)) || { w "Skipping environment-modules cleanup (--no-modules)"; return 0; } | |
| if ((DR)); then | |
| for v in "${CV[@]}"; do i "[dry-run] would remove modulefile: ${d}/${v}"; done | |
| return 0 | |
| fi | |
| [[ -d "$d" ]] || return 0 | |
| for v in "${CV[@]}"; do rm -f "${d}/${v}"; done | |
| shopt -s nullglob | |
| for p in "$d"/*; do | |
| [[ -f "$p" ]] || continue | |
| [[ "${p##*/}" == ".version" ]] && continue | |
| v="$(nv "${p##*/}")" || true | |
| [[ -n "$v" ]] && au rem "$v" | |
| done | |
| shopt -u nullglob | |
| if ((${#rem[@]})); then | |
| dv="$(printf '%s\n' "${rem[@]}" | sort -V | tail -n1)" | |
| cat >"$d/.version" <<EOF | |
| #%Module | |
| set ModulesVersion ${dv} | |
| EOF | |
| else | |
| rm -f "$d/.version" | |
| rmdir "$d" 2>/dev/null || true | |
| fi | |
| } | |
| ap() { | |
| local n="$1" x="$2" c | |
| c="${!n-}" | |
| case ":$c:" in *":$x:"*) return ;; esac | |
| [[ -n "$c" ]] && printf -v "$n" '%s:%s' "$x" "$c" || printf -v "$n" '%s' "$x" | |
| export "${n?}" | |
| } | |
| cpd() { | |
| local r="$1" p | |
| for p in "$r/extras/CUPTI/lib64" "$r/targets/x86_64-linux/lib" "$r/lib64"; do | |
| [[ -d "$p" && -e "$p/libcupti.so" ]] && { printf '%s' "$p"; return 0; } | |
| done | |
| for p in "$r/extras/CUPTI/lib64" "$r/targets/x86_64-linux/lib" "$r/lib64"; do | |
| [[ -d "$p" ]] && { printf '%s' "$p"; return 0; } | |
| done | |
| return 1 | |
| } | |
| ue() { | |
| local r="/usr/local/cuda-${UV}" cupti="" | |
| cupti="$(cpd "$r" || true)" | |
| if ((DR)); then | |
| if (( ! QT )); then | |
| local ld="$r/lib64" | |
| [[ -n "$cupti" ]] && ld="$cupti:$ld" | |
| cat <<EOF | |
| export CUDA_HOME=$r | |
| export PATH=$r/bin:\${PATH} | |
| export LD_LIBRARY_PATH=$ld:\${LD_LIBRARY_PATH:-} | |
| EOF | |
| fi | |
| ok "Dry-run complete for CUDA ${UV} env" | |
| return 0 | |
| fi | |
| [[ -d "$r" ]] || die "CUDA ${UV} is not installed at $r" | |
| [[ -d "$r/bin" ]] || die "Missing $r/bin" | |
| [[ -d "$r/lib64" ]] || die "Missing $r/lib64" | |
| [[ "${BASH_SOURCE[0]}" != "$0" ]] || die "'use' must be sourced. Run: source ${BASH_SOURCE[0]} use ${UV}" | |
| export CUDA_HOME="$r" | |
| ap PATH "$r/bin" | |
| if [[ -n "$cupti" ]]; then | |
| ap LD_LIBRARY_PATH "$cupti" | |
| else | |
| w "CUPTI path not found under $r (checked extras/CUPTI/lib64 and targets/x86_64-linux/lib)" | |
| fi | |
| ap LD_LIBRARY_PATH "$r/lib64" | |
| ok "CUDA ${UV} environment applied" | |
| } | |
| di() { | |
| ((DR)) || [[ ${EUID} -eq 0 ]] || die "Run as root (or use --dry-run)." | |
| pa | |
| oc | |
| i "CUDA versions: ${CV[*]} (default: $DF)" | |
| if ((DR)); then | |
| i "Dry-run enabled: validating repo and package availability only" | |
| i "Pin URL: https://developer.download.nvidia.com/compute/cuda/repos/${RB}/x86_64/cuda-${RB}.pin" | |
| else | |
| export DEBIAN_FRONTEND=noninteractive | |
| rt 3 apt-get update | |
| rt 3 apt-get -y install wget ca-certificates gzip sed | |
| fi | |
| local v | |
| for v in "${CV[@]}"; do i1 "$v"; done | |
| i "cuDNN is required and will be installed for default CUDA ${DF}" | |
| ic | |
| if ((MD)); then | |
| ((DR)) && i "[dry-run] would configure environment-modules" | |
| ((DR)) || cm | |
| eb | |
| else | |
| w "Skipping module setup (--no-modules)" | |
| fi | |
| if ((DR)); then ok "Dry-run completed"; return 0; fi | |
| ok "CUDA installation complete for versions: ${CV[*]}" | |
| ((QT)) && return 0 | |
| cat <<EOF | |
| Next steps: | |
| 1. Open a new shell. | |
| 2. If 'module' is missing, run: source /usr/share/modules/init/bash | |
| 3. Check modules: module avail cuda | |
| 4. Load a version: module load cuda/${DF} | |
| 5. Verify: nvcc --version | |
| Note: cuDNN is installed automatically for the default CUDA version. | |
| EOF | |
| } | |
| du() { | |
| ((DR)) || [[ ${EUID} -eq 0 ]] || die "Run as root (or use --dry-run)." | |
| export DEBIAN_FRONTEND=noninteractive | |
| i "CUDA versions to uninstall: ${CV[*]}" | |
| local v | |
| for v in "${CV[@]}"; do u1 "$v"; done | |
| rmv | |
| ok "CUDA uninstall complete for versions: ${CV[*]}" | |
| ((QT)) && return 0 | |
| cat <<EOF | |
| Optional cleanup: | |
| 1. Remove orphaned dependencies: sudo apt autoremove | |
| 2. Check remaining versions: ${N} list | |
| EOF | |
| } | |
| dl() { | |
| local p v link tgt | |
| local -a found=() | |
| shopt -s nullglob | |
| for p in /usr/local/cuda-*; do | |
| [[ -d "$p" ]] || continue | |
| v="$(nv "${p##*/cuda-}")" || true | |
| [[ -n "$v" ]] && au found "$v" | |
| done | |
| for p in /usr/share/modules/modulefiles/cuda/*; do | |
| [[ -f "$p" ]] || continue | |
| [[ "${p##*/}" == ".version" ]] && continue | |
| v="$(nv "${p##*/}")" || true | |
| [[ -n "$v" ]] && au found "$v" | |
| done | |
| shopt -u nullglob | |
| if ((${#found[@]} == 0)); then | |
| i "Installed CUDA versions: none found" | |
| else | |
| i "Installed CUDA versions:" | |
| while IFS= read -r v; do i " - ${v}"; done < <(printf '%s\n' "${found[@]}" | sort -V) | |
| fi | |
| if [[ -L /usr/local/cuda ]]; then | |
| link="$(readlink /usr/local/cuda || true)" | |
| tgt="$(readlink -f /usr/local/cuda || true)" | |
| v="" | |
| if [[ "$tgt" == /usr/local/cuda-* ]]; then | |
| v="$(nv "${tgt##*/cuda-}")" || true | |
| fi | |
| if [[ -n "$v" ]]; then | |
| i "Default /usr/local/cuda -> ${link} (CUDA ${v})" | |
| else | |
| i "Default /usr/local/cuda -> ${link}" | |
| fi | |
| else | |
| w "Default /usr/local/cuda symlink not found" | |
| fi | |
| } | |
| mn() { ps "$@"; case "$X" in install) di ;; uninstall) du ;; use) ue ;; list) dl ;; *) us; die "Unsupported action: $X" ;; esac; } | |
| zz() { | |
| unset -f setc cln log i w e ok die nv ac us rt au eb pa oc vv cvt svi ps rpk rhp rha rkd rfm icm pi i1 u1 ic mt cm rmv ap cpd ue di du dl mn zz 2>/dev/null || true | |
| unset N X CV DF MD DR QT NC FC UV RB RC PF DP SF AR ZR ZI ZW ZE ZK _S _E _U _P 2>/dev/null || true | |
| } | |
| if mn "$@"; then _R=0; else _R=$?; fi | |
| if ((_S)); then | |
| ((_E)) || set +e | |
| ((_U)) || set +u | |
| ((_P)) || set +o pipefail | |
| zz | |
| return "$_R" | |
| fi | |
| exit "$_R" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
(reserved)
I was annoyed for lack of CUDA version selector and how heavy python venv could get with CUDA binary dependencies.... so I made single bash file CUDA version manager for myself. Here you go!
It works like similar version manager with:
installto install a target CUDA/matching CuDNN major versionuseto make the CUDA/CuDNN version active usingenvironment-moduleswith config that the script injects for you.uninstallto remove a target CUDA/matching CuDNN major version.This requires working NVIDIA drivers already installed with
nvidia-smiworking, I don't automate that for you. This will pin drivers so that the installer avoids touching them in preflight.If you don't specify a CUDA version to install/uninstall, you get the one
nvidia-smipresents and its compatible CuDNN counterpart. This is a fast and convenient way to do that at your os bootstrap if you like.Verbosity and testing options:
You have
-r|--dry-runswitch to test before trying for any errors,-n|--no-colorto go without color on terminals that don't support it, and-q|--quietfor quiet mode to suppress output.