
上周帮同事收拾一台跑深度学习的机器系统是 Ubuntu 22.04显卡 4090驱动版本 550。他手上同时压着两个活一个是两年前的老项目依赖里死死钉着 torch 1.10 配 cu113另一个是刚开的新项目要用 torch 2.3 配 cu121。他做的第一件事是准备重装系统因为在他的认知里CUDA 只能装一个想装第二个就必须先把第一个卸干净。这个误区我这几年至少遇到过七八次每次都得从头讲一遍驱动、Toolkit、运行时三者的关系。其实 Ubuntu 上同时养三四个 CUDA 版本是很常规的操作老服务器上留着 10.2、11.3、11.8、12.4 四份 toolkit 的情况我见得太多了。关键不在于能不能装多个而在于你是否清楚驱动只认一个Toolkit 可以并存切换靠的是路径和软链接。这篇就把 Ubuntu 多 CUDA 版本安装及切换这件事从头到尾讲透驱动和 Toolkit 到底谁管谁、runfile 和 deb 源该怎么选、怎么把 11.8 和 12.4 并排塞进 /usr/local、切换的三个层次分别适合什么场景、装完怎么验证、以及那个让无数人卡住的gzip: stdin: invalid compressed>ls -lh cuda_11.8.0_520.61.05_linux.run head -c 200 cuda_11.8.0_520.61.05_linux.run | strings | head -n 5 md5sum cuda_11.8.0_520.61.05_linux.run第三行的 md5 要跟官方页面上的值对上官方一般给的是 md5部分版本给 sha256用sha256sum算。第二行是用来快速判断文件是不是被替换成了别的东西——正常文件开头是 shell 脚本头如果打出来是一堆 HTML 标签说明你下到的是错误页面这时候sh它必然会报 gzip 相关的错。3.2 runfile 静默安装的参数怎么给确认文件没问题之后用下面这条命令装 CUDA 11.8sudo sh cuda_11.8.0_520.61.05_linux.run \ --silent \ --toolkit \ --toolkitpath/usr/local/cuda-11.8 \ --override \ --no-opengl-libs \ --tmpdir/var/tmp参数逐个解释这些是我踩过坑之后才真正理解的部分--silent全程无人值守不弹那个 ncurses 界面。交互界面在 SSH 断线或者终端尺寸异常时会渲染错乱静默模式省心。--toolkit只装工具包。这是多版本场景下最重要的一个参数不加它安装器会问你要不要装驱动手一抖就装上了。--toolkitpath指定安装目录这就是多版本共存的基础。--override忽略安装器的兼容性检查也允许覆盖已有安装。驱动偏旧或者有旧版本残留时很有用。--no-opengl-libs不装 OpenGL 相关的库。服务器上通常不需要还能避免和系统自带的 GL 库冲突。--tmpdir指定临时目录。安装器会先把自解压内容释放到/tmp如果/tmp挂的是小容量的 tmpfs装到一半报空间不足换到/var/tmp或者大容量分区就能解决。装完之后ls /usr/local | grep cuda应该能看到cuda-11.8。这时候先别急着配环境变量等第二个版本也装完一起弄。3.3 第二个版本怎么装才不互相踩把 12.4 装到/usr/local/cuda-12.4命令结构完全一样只换文件名和路径sudo sh cuda_12.4.1_550.54.15_linux.run \ --silent --toolkit \ --toolkitpath/usr/local/cuda-12.4 \ --override --no-opengl-libs这里有个细节值得说清楚runfile 安装时默认会创建或更新/usr/local/cuda这个软链接。具体行为跟版本有关有的版本是如果不存在则创建有的是无条件覆盖。所以装完第二份之后务必确认一下当前指向ls -l /usr/local/ | grep cuda如果发现cuda - /usr/local/cuda-12.4而你的默认版本想留 11.8那么后面用 update-alternatives 接管即可。不要让两份 runfile 安装去争这个软链接那是给自己找麻烦。另外提一句安装器还会往/etc/ld.so.conf.d/里塞配置文件不同版本行为有差异装完之后确认一下ls /etc/ld.so.conf.d/ | grep -i cuda ldconfig -p | grep libcudart如果能看到两个版本的libcudart.so说明两个 Toolkit 的动态库都能被系统找到这是好事——因为它们的 soname 不同libcudart.so.11.0和libcudart.so.12不会互相覆盖。3.4 cuDNN 按 CUDA 版本分开放cuDNN 从 8.x 之后官方改成了 tar 包分发以前还有 deb下载下来是一个cudnn-linux-x86_64-8.9.7.29_cuda11-archive.tar.xz这样的文件。注意文件名里带cuda11或cuda12的就是它对应的主版本cuDNN 8.9 的 cuda11 版本和 cuda12 版本是两个不同的包不能混用。装法很直接解压之后把文件拷进对应 Toolkit 的目录tar -xvf cudnn-linux-x86_64-8.9.7.29_cuda11-archive.tar.xz cd cudnn-linux-x86_64-8.9.7.29_cuda11-archive sudo cp include/* /usr/local/cuda-11.8/include/ sudo cp -P lib/* /usr/local/cuda-11.8/lib64/ sudo chmod ar /usr/local/cuda-11.8/include/cudnn*.h sudo chmod ar /usr/local/cuda-11.8/lib64/libcudnn*cp -P里的-P是必须的cuDNN 的 lib 目录里有大量软链接libcudnn.so指向具体版本号那个文件不加-P可能会把软链接解引用成实体文件一下子多出几百兆。验证是否装对cat /usr/local/cuda-11.8/include/cudnn_version.h | grep CUDNN_MAJOR -A 2这个思路就一句话每个 CUDA 版本配一份自己的 cuDNN放在同一个目录下跟着 CUDA 一起切换。千万别把 cuDNN 放到/usr/lib/x86_64-linux-gnu/这种全局路径那是多版本环境里最容易埋雷的地方。4. 切换这件事的三个层次软链接、alternatives、环境变量4.1 手动改软链接最直观也最容易忘最朴素的做法就是维护/usr/local/cuda这一个软链接让所有工具都通过它来找 CUDA。前提是你的环境变量统一写成/usr/local/cuda而不是写死/usr/local/cuda-11.8export CUDA_HOME/usr/local/cuda export PATH$CUDA_HOME/bin:$PATH切换的时候sudo rm -f /usr/local/cuda sudo ln -s /usr/local/cuda-11.8 /usr/local/cuda这么写有个隐藏的好处PATH 里存的是路径字符串软链接的解析是在命令真正执行时发生的。所以你在 bashrc 里写了/usr/local/cuda/bin之后只要改软链接新开的终端立刻生效不需要重新 source 任何东西。坏处也很实在rm -f后面跟着ln -s两条命令之间如果软链接不存在这段时间里任何依赖它的构建都会失败而且这属于系统级改动多用户机器上你改一下别人也跟着变。所以它适合单人机器不适合共享环境。4.2 update-alternatives一次配置长期省心Debian 系自带的update-alternatives就是为这类场景设计的把多个候选版本注册进一个统一入口用一条命令切换。配置 CUDA 只要三条命令sudo update-alternatives --install /usr/local/cuda cuda /usr/local/cuda-11.8 118 sudo update-alternatives --install /usr/local/cuda cuda /usr/local/cuda-12.4 124 sudo update-alternatives --config cuda第二行的数字是优先级不影响手动选择只在你选了 auto 模式时决定默认值。执行第三条命令会出一个交互列表有 2 个候选项可用于替换 cuda (提供 /usr/local/cuda)。 选择 路径 优先级 状态 ------------------------------------------------------------ * 0 /usr/local/cuda-12.4 124 自动模式 1 /usr/local/cuda-11.8 118 手动模式 2 /usr/local/cuda-12.4 124 手动模式输入序号回车即可。之后任何时候sudo update-alternatives --config cuda都能切回来比手敲rm和ln靠谱得多。要查当前指向用readlink -f /usr/local/cuda提示如果/usr/local/cuda已经是一个真实存在的软链接或者目录--install会报错或行为异常。先把它删掉再配置别在残留状态上折腾。4.3 环境变量级隔离conda activate 钩子才是精细做法系统级软链接解决的是整台机器默认用哪个但实际工作中更常见的是这个终端窗口用 11.8那个窗口用 12.4。这时候软链接就不够了得上环境变量。conda 有个很好用但很少人用的机制$CONDA_PREFIX/etc/conda/activate.d/和deactivate.d/目录。放在这两个目录里的*.sh脚本会在激活/退出环境时自动执行。你可以给每个环境定制自己的 CUDA 路径mkdir -p $CONDA_PREFIX/etc/conda/activate.d cat $CONDA_PREFIX/etc/conda/activate.d/cuda.sh EOF export CUDA_OLD_PATH$PATH export CUDA_OLD_LD$LD_LIBRARY_PATH export CUDA_HOME/usr/local/cuda-11.8 export PATH$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH$CUDA_HOME/lib64:$LD_LIBRARY_PATH EOF mkdir -p $CONDA_PREFIX/etc/conda/deactivate.d cat $CONDA_PREFIX/etc/conda/deactivate.d/cuda.sh EOF export PATH$CUDA_OLD_PATH export LD_LIBRARY_PATH$CUDA_OLD_LD unset CUDA_OLD_PATH CUDA_OLD_LD CUDA_HOME EOF这样conda activate my_old_project之后nvcc -V自动变成 11.8退出环境又恢复原样。项目之间彻底隔离不需要任何手动切换动作。这套做法我在维护多个并行项目时用了两年多是目前最省心的方案。4.4 一个可以直接抄的临时切换脚本如果不想动 conda 配置也不想改系统软链接可以放一个脚本在~/bin里用 source 执行#!/usr/bin/env bash # 用法: source use-cuda 11.8 use_cuda() { local ver$1 local root/usr/local/cuda-${ver} if [ ! -d $root ]; then echo 未找到 $root 2 return 1 fi # 先把旧的 CUDA 路径从 PATH 里剔干净避免叠加 export PATH$(echo $PATH | tr : \n | grep -v ^/usr/local/cuda | paste -sd:) export LD_LIBRARY_PATH$(echo $LD_LIBRARY_PATH | tr : \n | grep -v ^/usr/local/cuda | paste -sd:) export CUDA_HOME$root export PATH$root/bin:$PATH export LD_LIBRARY_PATH$root/lib64:$LD_LIBRARY_PATH echo CUDA_HOME$CUDA_HOME nvcc --version | grep release }要注意里面那句先把旧路径剔干净。如果每次切换都无脑往 PATH 前面塞来回切几次之后 PATH 里会堆一串/usr/local/cuda-11.8/bin:/usr/local/cuda-12.4/bin:/usr/local/cuda-11.8/bin...最终谁生效取决于顺序非常容易迷惑人。这个清洗步骤看着啰嗦但省掉了后面无数次我明明切了怎么没用的排查。5. 装完必须做的验证三个版本号要对得上5.1 nvcc、驱动、框架三个数字分别代表什么装完不验证等于没装。我固定看三个数字它们分别来自三套体系命令输出示例含义nvidia-smiCUDA Version: 12.4驱动的能力上限nvcc -Vrelease 11.8当前 PATH 里 nvcc 所属的 Toolkitpython -c import torch;print(torch.version.cuda)12.1PyTorch wheel 编译时链接的 CUDA 运行时这三个数字经常不一致而不一致本身是正常的。真正需要警惕的只有一种情况nvcc -V显示的版本高于nvidia-smi里的上限那说明 Toolkit 太新、驱动带不动编译能过但一跑就报 driver 版本不足。顺便说一句nvcc -V和nvcc --version的区别——两个都能用--version输出更完整带 build 信息和 host compiler 版本排查编译问题时用后者更清楚。5.2 用 deviceQuery 做一次真实编译运行nvcc -V只能证明编译器在证明不了运行时库能正确链接。真正靠谱的验证是编译并跑一个 CUDA sample。Toolkit 的 samples 在 11.x 之后不再随安装包分发需要单独从官方仓库拉git clone https://github.com/NVIDIA/cuda-samples.git cd cuda-samples git checkout v11.8 # 分支名和 CUDA 版本对应 cd Samples/1_Utilities/deviceQuery make ./deviceQuery最后看到Result PASS说明从编译到加载再到实际调用这一整条链路都是通的。如果make报找不到cuda_runtime.h说明CUDA_HOME或者 PATH 配错了如果编译过了但运行时报libcudart.so.11.0: cannot open shared object file说明LD_LIBRARY_PATH或者ldconfig没生效。这两种错分别指向不同环节比盯着 nvcc 输出瞎猜高效得多。5.3 真正决定编译结果的是 CUDA_HOME不是 PATH这一点很多人绕不过来PyTorch 的cpp_extension、以及绝大多数基于 CMake 的 CUDA 项目找 nvcc 靠的是CUDA_HOME或者CUDA_PATH这个变量不是 PATH。你把 PATH 配得再漂亮CUDA_HOME还指着旧版本编译出来的扩展就是旧版本编译的。验证方法echo $CUDA_HOME ls $CUDA_HOME/bin/nvcc如果CUDA_HOME为空PyTorch 会退而求其次去 PATH 里找但找到哪个版本就不好说了。所以我的建议是CUDA_HOME必须显式设置和 PATH 保持一致这两个变量要么一起切要么一起别动。前面第 4.4 节那个脚本就是同时改这两个这是有意的。6. 踩坑记录从报错到定位的完整链路6.1 gzip: stdin: invalid compressed data 的五种成因和排查顺序这个报错在多版本安装的过程中出现频率极高典型输出是gzip: stdin: invalid compressed>export LD_LIBRARY_PATH/usr/local/cuda/lib64:$LD_LIBRARY_PATH用了几个月之后系统里开始出现一些莫名其妙的崩溃某些 Python 命令行工具启动时报符号找不到某些 Conda 环境装的包行为异常Matplotlib 偶尔抽风。排查了很久才定位到原因是全局的LD_LIBRARY_PATH会影响所有进程的动态库查找顺序CUDA 的 lib64 目录里有libstdc.so之类的基础库版本和系统的不一致时就会把更早加载的、正确的版本顶掉。修正是两个方向。一是尽量不在~/.bashrc里设置全局的LD_LIBRARY_PATH改用/etc/ld.so.conf.d/配合ldconfig来做系统级的库路径注册这样只在链接阶段影响不动运行时顺序。二是如果确实需要就把它限制在具体的 conda 环境或者具体的终端会话里用完就清掉别让它污染整个登录会话。6.4 驱动被顺手升级之后整个环境全乱最后说一个场景化的问题。有时候你只是想装个新版本的 CUDA结果 apt 或者某个安装脚本顺手把驱动也升了。重启之后nvidia-smi报错、GPU 不可见、甚至进不了图形界面这种情况在老机器上升级大版本驱动时特别容易出现。预防措施有两条。第一装 Toolkit 时永远带--toolkit参数从源头杜绝装驱动。第二如果用的是 apt先把驱动相关包单独 hold 住sudo apt-mark hold nvidia-driver-550 cuda-drivers这样 apt 在处理依赖时就不会顺手把它们升级或者删除。要升级驱动的时候再apt-mark unhold手动放行升级完成确认 GPU 正常之后再重新 hold 住。这个习惯能省掉很多深夜救火的麻烦。真出了问题的救援思路是先在文本模式下用ubuntu-drivers devices看看有哪些可用驱动用apt purge清掉冲突的驱动包重新安装确认可用的版本重启验证。整个过程驱动版本一定要和 Toolkit 的版本要求对照别修完驱动又发现 Toolkit 跑不动了。7. 一点个人体会多 CUDA 版本管理这件事折腾过几轮之后我发现真正需要记住的其实就三句话。驱动只有一个升级要谨慎Toolkit 可以并存靠目录区分切换的本质是环境变量和软链接而不是重装系统。把这三句话想明白了剩下的都是查表和抄脚本的活。我现在维护的两台机器上/usr/local下面长期挂着 cuda-11.8 和 cuda-12.4 两份系统级软链接交给 update-alternatives 管日常默认指向 12.4涉及老项目的 conda 环境用 activate.d 脚本自动切到 11.8从来不用手动干预。cuDNN 也是每个版本各一份跟着 CUDA 一起走。这套配置运行两年多除了偶尔因为新项目要装第三个版本之外基本没再出过状况。如果你现在正准备给机器装第一个 CUDA我的建议是别一开始就图省事用 deb 源直接上 runfile、装到带版本号的目录里哪怕当下只有一个版本也这么做。等第二个需求来的时候你会感谢当初这个决定。