
多CUDA版本共存这件事我自己是踩过坑才彻底搞明白的。早期做深度学习部署的时候一台服务器上装了个CUDA 11.3结果后来复现一篇新论文人家代码要求11.8我又手贱把系统里的版本卸了重装直接把实验室另一台机器上跑着的训练任务给整崩了。从那以后我就养成了一个习惯在Ubuntu上装CUDA永远不要只有一个版本也永远不要用apt那种会互相覆盖的方式装。这篇文章讲的就是Ubuntu系统下多CUDA版本安装及切换的完整实操包括为什么要这么做、怎么把两个甚至三个版本和平共处地装在同一个/usr/local下、以及怎么在不重启、不污染环境的前提下快速切换。不管你是刚接触CUDA的新手还是被换一个项目就崩一个环境折磨过的老手这套方法都能直接抄作业。核心就三个词独立目录、环境变量、按需切换。1. 为什么一台机器要装好几个CUDA1.1 CUDA的版本不是孤立的它被三层东西同时锁死很多人以为CUDA就是个可以随便升级的工具包这是最大的误解。一个CUDA版本能不能用、该不该用实际上被三层东西同时约束着你得同时满足它们才不出问题。第一层是NVIDIA驱动。跑nvidia-smi的时候右上角会显示一个CUDA Version: 12.2这个数字是当前驱动最高支持的CUDA运行时版本不是你已经装的版本。驱动只能向下兼容也就是说新驱动能跑旧CUDA程序但老驱动跑不了太新的CUDA。如果你的驱动停留在525这一版那CUDA 12.2往上基本就别想了装上去运行时直接报错。第二层是深度学习框架的编译绑定。PyTorch、TensorFlow官方给的pip wheel包是在编译时就写死了CUDA版本的。你装torch2.0.1cu118它链接的就是CUDA 11.8的运行时库你要是给一个11.8的torch配了12.1的CUDA环境import torch的时候大概率给你来一句undefined symbol: cusolverDnXXX或者libcudart.so.11.0: cannot open shared object file。这种报错最坑因为它不是说你代码写错了而是环境根本没对上。第三层是中间库比如cuDNN、TensorRT、NCCL。它们每一个也都有自己的CUDA版本对应表而且比框架还挑剔。TensorRT 8.6可能只认CUDA 11.8和12.0cuDNN 8.9.x是专门给CUDA 12.x配的。所以一个完整可用的环境往往是CUDA cuDNN 框架三者版本都对齐了才行。明白了这三层约束你就能理解为什么装一个版本走天下根本不现实——项目A要11.3项目B要12.1这是常态不是你运气差。1.2 什么场景下必须装多个版本说几个我亲身遇到过、也一定是大多数人会遇到的场景你对号入座一下。最常见的是复现论文或开源项目。GitHub上很多仓库的README会明确写CUDA 11.3因为作者当时就是用这个版本跑出来的结果。你想一比一复现最好用一样的版本不然精度对不上、甚至跑不起来你还要花大量时间排查到底是代码的锅还是环境的锅。第二个是新旧项目并存。公司里一个稳定的老服务跑在CUDA 11.8上动它风险很大同时新接入的模型要用12.1的新特性比如某些新的算子库。你不可能为了新项目把老服务环境炸了这时候多版本共存就是刚需。第三个是多人共用服务器。这种情况太普遍了一台机器好几个人用每个人需求不一样。如果大家都在系统层面反复装卸CUDA那基本就是互相伤害。合理做法是每个版本独立装好放在那里谁用谁切。第四个是推理框架和工具链的挑剔。像某些推理引擎、视频处理SDK、以及一些CUDA加速的第三方库对版本卡得很死多备几个版本就是给自己留后路。1.3 三种共存方案的取舍我为什么选独立目录加环境变量实现多版本共存市面上一共有这么几条路我挨个说说优劣。方案一是容器化用Docker加NVIDIA Container Toolkit每个镜像里装一个固定版本环境绝对隔离、绝对干净。这是最政治正确的方案但它的代价是镜像动辄几个GB、每次都得起容器、挂载数据、配端口开发调试阶段反复改代码极其难受。所以它适合生产部署不适合日常开发折腾。方案二是conda环境隔离用conda install cudatoolkit11.8这种方式。这个方案轻、切换快conda activate就换了一套。但它有硬伤conda装的cudatoolkit是一个精简版的运行时它跟系统驱动、跟底层的NVCC、跟一些需要真正CUDA Toolkit完整工具链的编译场景比如你要自己写CUDA kernel用nvcc编译配合得并不好。很多时候conda能跑Python但你一编译C扩展就抓瞎。方案三是系统层多版本独立目录加环境变量切换这才是我的主力方案。原理特别朴素把不同版本分别装到/usr/local/cuda-11.8、/usr/local/cuda-12.1这种互不干扰的独立目录里系统里哪个都不激活然后通过改PATH和LD_LIBRARY_PATH这两个环境变量让当前shell用哪个版本。想换版本重新source一下环境变量脚本就完事秒切。我给这三种方案做了个直观对比你按自己的场景选方案隔离程度切换速度对编译工具链支持适合场景Docker容器最强慢起容器完整生产部署、CIconda cudatoolkit中等快弱无完整nvcc纯Python跑训练多版本目录环境变量较强极快完整开发调试主力后面所有实操都基于方案三展开。它不完美但对我们这种既要跑Python又要偶尔编点C扩展的人是最平衡的选择。2. 动手之前把环境摸清楚再装2.1 先确认你的驱动能撑到哪一版别急着下载先把驱动底细查清楚。我见过太多人装完CUDA发现跑不了最后发现是驱动太老白白浪费一小时。两个命令就够nvidia-smi cat /proc/driver/nvidia/versionnvidia-smi右上角的CUDA Version就是你这台机器驱动能支持的CUDA上限。比如显示12.2那你装11.8、12.0、12.1都可以装12.3就会在运行时告警或者报错。这里有个容易搞混的点NVIDIA从CUDA 11开始引入了minor version compatibility也就是说只要驱动支持11.x里的某个小版本后续11.x的小版本升级一般也能跑不需要跟着升级驱动。所以一个大版本系列11、12通常你只需要驱动能覆盖到该系列的起始版本就行。查驱动的具体版本号用第二个命令看它给你的才是真实驱动号例如535.104.05。如果驱动确实太老需要升级我强烈建议用系统发行版自带的包管理或者NVIDIA官方仓库升别直接拿CUDA自带的驱动去装——原因下一节详细说。2.2 清点一下系统里现在到底有什么不摸清家底就装新版本很容易装完发现乱套。你需要确认这几件事当前有没有装过CUDA、装在哪、是apt装的还是runfile装的。which nvcc nvcc -V ls -l /usr/local/ | grep cuda dpkg -l | grep -i cuda echo $CUDA_HOME echo $LD_LIBRARY_PATH解释一下每条命令在干什么。which nvcc告诉你当前PATH里的nvcc在哪如果它指向/usr/bin/nvcc那基本是apt装的危险信号如果指向/usr/local/cuda-XX/bin/nvcc那就是runfile装的。ls -l /usr/local/能看到有没有cuda这个软链接以及它指向哪个版本。dpkg -l | grep cuda能列出所有通过apt装过的cuda相关包这一步很关键——如果发现有apt装的cuda包后面装runfile版本前建议先清理干净否则两套东西会在/usr/lib和/usr/local里打架。echo $CUDA_HOME和echo $LD_LIBRARY_PATH看当前环境变量有没有被别的东西写死。这一步的核心目的就是搞清楚现有环境的干净程度决定是直接加装还是先清理。2.3 runfile还是deb多版本场景下答案很明确官方给CUDA提供两种安装包.deb走apt和.runrunfile。默认很多人图省事用apt但在多版本共存的场景下我明确告诉你用runfile。原因在于apt安装的机制。apt会把CUDA拆成一堆包cuda-toolkit-12-1、cuda-runtime-12-1等等它们安装时都往系统固定路径/usr/local/cuda-12.1以及/usr/lib里塞东西而且apt包之间会互相声明依赖和冲突。你想同时装11.8和12.1apt很大概率告诉你包冲突无法共存或者装完把原来版本的软链接覆盖了。更麻烦的是apt的CUDA包里会带驱动容易在升级时把你的系统驱动也顺手换了。runfile就不一样它本质是个自解压脚本你可以在命令行上明确告诉它只装toolkit、装到哪个目录、别动驱动、别动opengl。这样一个版本一个目录井水不犯河水。两种方式对比摆一下维度deb (apt)runfile多版本共存差易冲突好独立目录是否动系统驱动可能带驱动可明确跳过升级/卸载管理方便手动删目录推荐场景单版本、图省事多版本共存所以下载.run文件这一步别嫌麻烦。下载完成后务必校验一下校验和用官方给的sha256跟本地文件比对sha256sum cuda_11.8.0_520.61.05_linux.run这一步能帮你避开后面要讲的安装到一半报gzip错误那种糟心事。3. 装第一个CUDA把地基打牢3.1 runfile安装的关键选项一个都别选错假设你已经下载好了cuda_11.8.0_520.61.05_linux.run现在开始装第一个版本。我给你一条可以直接用的命令再逐项拆解为什么这么写sudo sh cuda_11.8.0_520.61.05_linux.run \ --silent \ --toolkit \ --toolkitpath/usr/local/cuda-11.8 \ --no-opengl-libs \ --override--silent是静默安装不弹那个需要你手动上下移动光标勾选组件的交互界面。--toolkit表示只装工具链不装驱动、不装example。--toolkitpath/usr/local/cuda-11.8是灵魂所在——强制把这一版装到这个独立目录绝不让它去写/usr/local/cuda那个公共软链接。--no-opengl-libs避免它动系统的OpenGL库这个在带桌面的机器上尤其重要否则可能把你的图形界面搞花。--override是让它忽略编译器版本之类的警告继续装。注意不带--toolkitpath的时候runfile默认会装到/usr/local/cuda-11.8并把/usr/local/cuda软链接指向它。多版本场景下这个默认行为就是麻烦的根源一定要手动指定路径。如果你想要交互界面自己选也可以去掉--silent进入界面后重点是把Driver那一项的勾去掉。为什么这么强调不要装CUDA自带的驱动因为驱动是全局唯一的一份多个CUDA版本共享同一个驱动。runfile里捆绑的驱动版本可能比系统现有的更旧或更新装上去会覆盖掉当前正在用的驱动运气不好重启后图形界面起不来、或者之前依赖旧驱动的其他服务全挂。记住一句话驱动归驱动CUDA归CUDA两者分开管。3.2 装完后的目录结构长什么样装好之后进目录看看心里有数ls /usr/local/cuda-11.8/你会看到bin、lib64、include、nvvm等目录。其中bin下面是nvcc这类工具lib64下面是一堆libcudart.so、libcublas.so等运行时库include是头文件。理解这个结构很重要因为后面配置环境变量、以及cuDNN拷贝文件都是围绕这三个目录转。顺便确认一下软链接状态ls -l /usr/local/ | grep cuda理想情况下应该有cuda-11.8这个实体目录以及一个cuda - cuda-11.8的软链接如果它自动建了。如果你装第二个版本时不想让它抢这个软链后面第5节讲怎么控制。3.3 环境变量怎么配才干净装完不配环境变量nvcc根本找不到。很多人直接把环境变量往~/.bashrc里一写就完事但我建议你别直接写死版本后面切换就靠它。打开~/.bashrc加三段先别急看完第5节你会知道更优雅的写法export CUDA_HOME/usr/local/cuda-11.8 export PATH$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH$CUDA_HOME/lib64:$LD_LIBRARY_PATH改完source ~/.bashrc。这里我必须提一个高频大坑LD_LIBRARY_PATH这个东西是把双刃剑。它优先级很高会覆盖系统默认的库搜索路径。如果你往里面塞了CUDA的lib64某些时候可能会让系统里其他程序加载到CUDA自带的库版本导致本来好好的命令行工具、甚至图形程序崩掉。所以我的习惯是LD_LIBRARY_PATH尽量不在全局bashrc里长期设置而是在需要跑CUDA程序的脚本或那个特定终端里临时设。日常只把CUDA_HOME和PATH设好让nvcc能用就行。3.4 怎么验证装成功了验证分三步缺一不可。第一步看编译器nvcc -V输出里会显示release 11.8之类。第二步跑编译测试。官方sample现在需要单独clone简单点可以自己写个hellonvcc --version which nvcc确认which nvcc指向的是/usr/local/cuda-11.8/bin/nvcc不是/usr/bin/nvcc这点非常重要。第三步如果你有GPU且驱动正常写个最小CUDA程序编译运行或者直接跑框架验证python -c import torch; print(torch.version.cuda); print(torch.cuda.is_available())如果这里打印出来的CUDA版本和你装的不一致那说明Python环境里装了自带cudatoolkit的conda包或者装了别的需要单独排查这个后面问题排查章节细说。4. 装第二个及更多CUDA版本4.1 换汤不换药但有两个细节要盯紧装第二个版本比如CUDA 12.1命令几乎是照抄只改版本号和路径sudo sh cuda_12.1.0_530.30.02_linux.run \ --silent \ --toolkit \ --toolkitpath/usr/local/cuda-12.1 \ --no-opengl-libs \ --override看起来简单但有两个地方必须盯。第一千万别让它动/usr/local/cuda这个软链接。有的安装包即使你指定了toolkitpath仍可能顺手更新/usr/local/cuda指向新版本装完你之前的nvcc可能就悄悄变版本了。装完立刻用ls -l /usr/local/ | grep cuda确认软链接指向哪。第二确认驱动没被覆盖。装完再跑一次nvidia-smi看看驱动版本是不是还是原来那个。如果变了说明你不小心让它装了驱动组件得想办法回退。这里分享一个我自己的习惯每装完一个版本就用readlink -f /usr/local/cuda记录一下软链接指向同时把nvcc -V的输出截个图或者存个txt。装到第三第四个版本时谁是实体、谁是软链、环境变量当前指向谁全靠这些记录才理得清。4.2 多个版本并存时系统里实际是什么状态装完两个版本后/usr/local/下大概是这样/usr/local/cuda-11.8/ - 实体目录 /usr/local/cuda-12.1/ - 实体目录 /usr/local/cuda - cuda-?? - 软链接指向谁取决于安装行为关键认知现在的系统默认用哪个版本取决于环境变量和这个软链接而不是取决于你装了哪些版本。所有版本都是躺平状态谁被激活谁干活。理解了这一点切换就变得极其简单。有一点要留意如果你之前配置了全局LD_LIBRARY_PATH指向11.8那即使你nvcc切到12.1运行时加载的库可能还是11.8的造成编译器版本和运行时库版本不一致。这是个非常隐蔽的坑排查时很容易忽略。所以再次强调多版本环境下环境变量要么用脚本成套切换PATH和LD_LIBRARY_PATH一起改要么就别在全局长期设LD_LIBRARY_PATH。5. 多版本切换的几种做法5.1 环境变量法写个切换脚本秒切不重启这是我最推荐的方案。核心思路是把每个版本的环境变量写成一个独立脚本用的时候source哪个就切到哪个。在~/cuda-switch目录下自己建放两个文件比如cuda118.sh#!/bin/bash export CUDA_HOME/usr/local/cuda-11.8 export PATH$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH$CUDA_HOME/lib64:$LD_LIBRARY_PATH echo Switched to CUDA 11.8: $(nvcc -V | grep release)cuda121.sh同理只改版本号。切换的时候source ~/cuda-switch/cuda118.sh有个极容易踩的坑连续切换时PATH会被不断叠加前一个版本还残留在里面导致nvcc有时候还指向老版本。解决办法是切换前先清理或者写得更严谨一点先unset掉再重新设#!/bin/bash # 先移除所有已知的cuda路径 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/usr/local/cuda-12.1 export PATH$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH$CUDA_HOME/lib64:$LD_LIBRARY_PATH还有个执行bash的历史缓存问题如果你在同一个终端里反复切版本nvcc的路径可能被shell缓存了。切完执行一下hash -r清掉命令缓存最保险。5.2 软链接法把cuda这个公共入口指向想要的版本有些工具和脚本会去读/usr/local/cuda这个固定路径而不是读环境变量。这时候你可以用软链接法ln -sfn /usr/local/cuda-12.1 /usr/local/cuda-sfn这几个参数含义是-s建软链、-f强制覆盖已存在的、-n把符号链接当普通文件处理避免嵌套。执行完/usr/local/cuda就指向12.1。但注意软链接法改的是系统级状态影响所有用户和所有shell而环境变量法只影响当前终端。如果你是在多人服务器上别随便动这个软链接容易影响别人。我一般只在单人机器或者明确的我要全局切到这个版本时用。5.3 update-alternatives法想装得像系统自带的那样正规如果你追求改配置就能全局切的正规感可以用update-alternatives。它本来是Debian系用来管理同一种命令多个版本的工具我们手动把它用在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.1 121之后切换sudo update-alternatives --config cuda它会列出所有版本让你选编号。这个方式适合想要一个标准切换入口的人但配置稍繁琐而且对nvcc、lib64这些还是要靠环境变量配合才完整。所以我的实际用法是软链接交给update-alternatives管环境变量用脚本管两者配合。5.4 conda方案只跑Python训练时的省事选择如果你这台机器主要就是跑Python训练几乎不碰nvcc编译那conda方案值得考虑conda create -n proj11 python3.10 conda activate proj11 conda install pytorch torchvision cudatoolkit11.8 -c pytorch每个conda环境一套独立的cudatoolkit切换就是conda activate。它的好处是彻底不污染系统坏处是这套cudatoolkit不带完整nvcc你nvcc -V可能还是系统的版本编译C扩展时容易版本对不上。所以conda方案和系统多版本方案不冲突可以叠加用但要清楚conda管的是Python运行时用的库系统管的是编译工具链。这四种切换方式放一起对比方式影响范围切换速度适合谁环境变量脚本当前shell秒级开发调试推荐软链接全局秒级单人机器全局切换update-alternatives全局秒级想要正规切换入口conda环境仅conda环境秒级纯Python场景6. cuDNN的版本匹配与部署6.1 cuDNN和CUDA的对应关系错一个版本就报错cuDNN是深度学习的加速库它跟CUDA的版本是强绑定的。装错版本轻则性能没提升重则框架直接崩溃或者报libcudnn.so version mismatch。常见的对应关系大致是CUDA 11.8一般配cuDNN 8.9.xCUDA 12.1一般配cuDNN 8.9.x或9.x。但具体到某一版还得查官方对应表因为同一个CUDA大版本下不同小版本推荐的cuDNN也可能不同。我的经验是先确定CUDA版本和框架版本再去反查cuDNN版本不要反过来。因为框架的wheel包对cuDNN是有明确要求的你以它为准去匹配最稳。6.2 手动部署解压拷贝三步走cuDNN现在从NVIDIA官网下载需要登录下到的通常是个tar包。部署到指定的CUDA版本就是解压后把文件拷进对应的cuda目录tar -xvf cudnn-linux-x86_64-8.9.x.x_cuda11-archive.tar.xz cd cudnn-*-archive sudo cp include/cudnn*.h /usr/local/cuda-11.8/include/ sudo cp -P lib/libcudnn* /usr/local/cuda-11.8/lib64/ sudo chmod ar /usr/local/cuda-11.8/include/cudnn*.h /usr/local/cuda-11.8/lib64/libcudnn*关键点是拷到对应版本的目录里给11.8用的cuDNN就拷进cuda-11.8给12.1的拷进cuda-12.1。因为是多版本共存千万别图省事只拷一份到公共路径那样切版本时就会串。chmod ar是给读权限避免权限问题导致框架加载不到。验证的话可以看看库文件在不在ls /usr/local/cuda-11.8/lib64/libcudnn*实际跑框架时torch.backends.cudnn.version()也能读出来当前用的是哪个cuDNN版本。7. 常见问题与排查实录7.1 版本显示对不上到底以谁为准这是最经典的问题nvidia-smi显示CUDA 12.2但nvcc -V显示11.8到底哪个是真的答案是它们说的不是一回事。nvidia-smi里那个是驱动支持的最高运行时版本nvcc -V是你当前工具链版本。判断我实际用的是哪个CUDA要看nvcc -V、echo $CUDA_HOME、以及框架里torch.version.cuda。三者一致才算环境干净。还有个坑是nvcc和运行时库版本不一致。nvcc是按PATH找的运行时库是按LD_LIBRARY_PATH找的。如果你PATH指向12.1、LD_LIBRARY_PATH指向11.8就会编译用12.1、链接用11.8报一堆符号错误。所以切换时PATH和LD_LIBRARY_PATH必须成套改。7.2 安装时报 gzip: stdin: invalid compressed data 怎么办这个报错很多人都遇到过cuda .run gzip: stdin: invalid compressed>echo CUDA_HOME$CUDA_HOME; which nvcc; nvcc -V 21 | grep release; \ ls -l /usr/local/cuda; python -c import torch; print(torch cuda:, torch.version.cuda, avail:, torch.cuda.is_available()) 2/dev/null这一串跑完PATH、软链接、编译器版本、框架版本全露出来四者一致环境就是干净的不一致一眼就知道该改哪儿。这个检查我几乎每次接手一台新机器都会先跑一遍。另外提醒一句系统要是以后重装ubuntu系统重装这事谁都躲不过/usr/local下的这些版本全部会没所以上面说的体检记录和下载好的.run文件平时就存一份在数据盘里。重建环境时照着记录走一遍十分钟就能把多版本环境搭回来比第一次从零摸索快得多。这套流程我自己在两台机器上完整重建过实测下来很稳照着做基本不会翻车。