ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ubuntu 系统 CUDA 安装全指南:驱动、Toolkit 与运行库的版本协同实践

Ubuntu 系统 CUDA 安装全指南:驱动、Toolkit 与运行库的版本协同实践

前两天有个同事拿着一段编译报错来找我,说他觉得自己已经把 CUDA 环境装好了,但代码一跑就提示找不到设备。我让他先执行nvidia-smi,再执行nvcc -V,两个输出一对,问题十秒钟就清楚了——他把驱动界面右上角显示的最高 CUDA 版本,当成了已经安装好的 Toolkit 版本。这个误解在 Ubuntu 装 CUDA 的场景里太常见了。今天这篇就把整个安装过程掰开揉碎讲清楚,从概念、选型、实操到排错,基本是我自己一路踩坑之后整理出来的经验。适合刚接触 GPU 环境配置的人,也适合被各种版本号折腾到想重装系统的人。

先说结论:Ubuntu 上装 CUDA 这件事本身不难,难的是搞清楚你装的到底是哪一层东西,以及驱动、工具包、运行库这三者之间到底谁依赖谁。下面按我的习惯,从最容易被带偏的地方讲起。

1. 安装前先分清:驱动、Toolkit 和 Runtime 不是一回事

1.1 三个概念到底是什么关系

我在群里看到过不少类似提问:“我已经在 conda 里装了 cudatoolkit,为什么还报错?”“我跑nvidia-smi明明显示 CUDA Version 是 12.4,为什么程序说没有 CUDA?”这类问题的根源,都是没分清下面三层。

  • NVIDIA 驱动(Driver):这是运行在操作系统内核层的模块,负责让系统识别 GPU、管理显存、调度计算单元。它的状态由nvidia-smi体现,装好驱动之后,这张显卡才算真正被系统接管。
  • CUDA Toolkit:这是给开发者用的完整工具包,里面包含nvcc编译器、CUDA 核心库(如 cudart、cublas)、头文件、以及 nvprof、Nsight 之类的分析工具。它会安装到/usr/local/cuda目录下,对应版本号得用nvcc -V查看。
  • CUDA Runtime:可以理解成 Toolkit 里的运行库部分,也就是程序在运行时需要链接的libcudart、libcublas等动态库。它既跟着 Toolkit 一起装,也可以被其他发行渠道单独打包,比如 conda 里的 cudatoolkit、pip 里的某些 nvidia 前缀包。

1.2 为什么很多人装完了也没发现自己装错了

最经典的误解是nvidia-smi右上角那行CUDA Version: 12.4。这行字的意思是“当前驱动最高可以支持 CUDA 12.4 的 Runtime”,它只代表驱动的能力上限,不代表你的系统里已经装了 CUDA 12.4。有些人看到这行字就以为万事大吉,结果运行nvcc -V提示找不到命令,才意识到自己根本没有 Toolkit。

反过来的坑也很常见:在 conda 环境里执行了conda install cudatoolkit=11.8,然后以为系统级 CUDA 也装好了。实际上 conda 的 cudatoolkit 只是把运行库放进当前环境,它不会给你装驱动,也不会把nvcc加进你的 shell 环境。你用 conda 里的 Python 跑深度学习框架也许没问题,但一旦去编译自定义算子,系统还是不知道该用哪个编译器。

我整理了一张表格,可以对照自己的需求:

使用场景需要驱动需要系统级 ToolkIT需要 conda/pip 里的 CUDA 库
跑 PyTorch/TensorFlow 预编译包是否是
编译 PyTorch 自定义算子(CUDA 扩展)是是通常需要版本一致
直接用 CUDA C/C++ 写程序是是否
用 Nsight 做 GPU 性能分析是是否
只做 CPU 推理否否否

1.3 版本兼容矩阵到底怎么读

NVIDIA 官方有一张驱动和 Toolkit 的兼容表,核心原则是:驱动决定你最多能用哪个 CUDA 版本,Toolkit 决定你编译出来的程序需要哪个驱动。Toolkit 对驱动是“向后兼容”的,新版驱动可以运行旧版 Toolkit 编译出来的程序,但旧驱动跑不了新版 Runtime。

举个实际例子:我的机器驱动是 550.x,nvidia-smi右上角显示 CUDA 12.4,那我装 CUDA 11.8、12.1、12.3 的 Toolkit 都没问题;但如果驱动只有 535.x,右上角显示 12.2,那我装 CUDA 12.4 的 Toolkit 之后,程序一运行就会报CUDA driver version is insufficient for CUDA runtime version。这个报错我见过至少十次,全是版本匹配没对上。

所以我的习惯是:先装最新稳定版驱动,然后按项目需求装对应版本的 Toolkit。驱动在能力范围内向下兼容,Toolkit 通常用项目锁定的版本,这样两头都不别扭。

2. 选安装方式:runfile、apt、conda 各有什么坑

2.1 三种安装方式的本质区别

CUDA Toolkit 在 Ubuntu 上的安装渠道主要有三条,我分别说下它们的脾性。

  • runfile 方式:从官网下载一个.run的大文件,脚本式安装。它会把 Toolkit 装到独立的/usr/local/cuda-12.4目录,再通过软链/usr/local/cuda指向当前激活的版本。优点是可控性最强,装多版本互不干扰,卸载也干净;缺点是要手动配环境变量,对新手来说多了一步。
  • deb 方式:把 NVIDIA 的 apt 源加进系统,用apt install cuda安装。优点是自动处理依赖,升级方便,跟系统包管理融为一体;缺点是自由度低,apt 源里通常捆绑驱动,一不小心就把系统驱动也换了,而且多版本共存比较别扭。
  • conda 方式:在 conda 环境里装 cudatoolkit。它解决的是运行库和部分编译工具的隔离问题,但它永远替代不了驱动,也管不到系统级 PATH。适合“只要跑框架,不折腾系统”的人。

2.2 不同场景我推荐什么

如果你问我个人推荐,我的答案很固定:驱动用 apt 装,Toolkit 用 runfile 装。

驱动用 apt 的原因很简单:ubuntu-drivers会自动检测硬件并推荐合适版本,装完重启就能用,后续内核升级时 attached 的 DKMS 模块也会自动重新编译,省心。Toolkit 用 runfile 的原因是多版本切换太方便了,我在不同项目之间经常要横跳 CUDA 11.8 和 12.4,runfile 方式下只需要改一个软链。

如果你是第一次装、完全没经验,直接走ubuntu-drivers autoinstall装驱动,再用 runfile 的静默参数装 Toolkit,这是我觉得最稳的一条路。

2.3 动手前必须完成的检查清单

不管选哪条路,装之前都要先看几样东西,别着急下载安装包。

  • 检查 GPU 是否被系统识别:lspci | grep -i nvidia
  • 检查系统版本:cat /etc/os-release,Ubuntu 20.04、22.04、24.04 对应的 CUDA 安装指南略有差异
  • 检查是否加载了开源显卡驱动 nouveau:lsmod | grep nouveau,如果有,后面要处理黑名单
  • 检查磁盘空间:df -h /usr/local,完整 Toolkit 加示例代码大概要占 4~5 GB,别把根目录塞爆
  • 检查 gcc 是否存在:gcc --version,编译 CUDA 代码需要它,没有就apt install build-essential

这些检查全部一分钟内能跑完,但能帮你避免后面出现“装到一半发现磁盘不够”“重启后黑屏”“编译报不支持 gcc”这种连环坑。

3. 从零到可用:驱动与 Toolkit 的完整安装过程

3.1 装驱动:两条路选一条

先说最简单的一条路。确认 GPU 被识别后,执行:

sudo apt update sudo ubuntu-drivers autoinstall sudo reboot

重启之后跑nvidia-smi,看到类似下面的输出就说明驱动工作了:

+---------------------------------------------------------------------------------------+ | NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 | +---------------------------------------------------------------------------------------+

ubuntu-drivers会根据你的显卡挑选一个推荐版本。如果你有特定驱动版本需求,比如某些老项目只认 470 系列,可以先跑ubuntu-drivers devices看看可选项,再手动apt install nvidia-driver-470。

另一条路是下载 NVIDIA 官方驱动.run文件手动装。这条路的坑比较多:需要先禁用 nouveau、停掉图形界面服务、在纯文本终端里执行安装,完了还要处理 Secure Boot。没有特殊理由我不建议新手走这条路,用 apt 装省下的时间够你多踩好几个坑。

3.2 处理 Secure Boot 和 nouveau 的问题

如果你在安装驱动重启后,系统卡在蓝屏的 MOK 管理界面,说明你开了 Secure Boot。这时候要选 “Enroll MOK”,输入你设置的一次性密码,确认导入密钥后,下一轮重启驱动才会被正常加载。很多服务器默认没开 Secure Boot,但个人台式机默认是开的,这个环节经常被忽略。

如果你发现系统里 nouveau 模块还在,nvidia-smi报Unable to load the 'nvidia-drm' module,那就手动把它禁掉。创建/etc/modprobe.d/blacklist-nouveau.conf:

blacklist nouveau options nouveau modeset=0

然后执行:

sudo update-initramfs -u sudo reboot

重启后再lsmod | grep nouveau,没有输出就干净了。这一步在新版 Ubuntu 上遇到的不多,但在一些定制版或者老内核上还是会碰到,知道怎么处理就行。

3.3 用 runfile 装 Toolkit:关键是不动驱动

驱动就绪后,去官网下载对应版本的 runfile 安装包。页面上会给你生成一条 wget 链接,比如这种格式:

wget https://developer.download.nvidia.com/compute/cuda/12.4.1/local_installers/cuda_12.4.1_550.54.15_linux.run

下载完后先别急着双击执行,注意一个关键点:你的驱动已经装好了,runfile 里捆绑的驱动千万别再装一遍。交互式安装界面里,用方向键把 Driver 那一项取消掉(取消 X 标记),只保留 Toolkit、Samples 这些组件,然后选 Install。

如果不想走交互界面,直接静默安装更省事:

sudo sh cuda_12.4.1_550.54.15_linux.run --toolkit --silent --override

--toolkit表示只装 Toolkit 组件,自动跳过驱动;--silent跳过交互确认;--override是防止某些场景下版本检测不通过。这条命令执行完,Toolkit 就落到了/usr/local/cuda-12.4目录,同时/usr/local/cuda软链也会指向它。

装完之后顺手验证一下目录结构:

ls -l /usr/local/cuda

会看到类似cuda -> /usr/local/cuda-12.4的软链,这就是后面切换版本的关键入口。

3.4 环境变量:不配 PATH 等于白装

runfile 方式不会自动帮你配环境变量,这也是它和 apt 方式最大的区别。你得让 shell 知道nvcc在哪里、动态库在哪里。我的做法是创建一个系统级配置文件:

sudo bash -c 'cat > /etc/profile.d/cuda.sh << EOF export PATH=/usr/local/cuda/bin\${PATH:+:\${PATH}} export LD_LIBRARY_PATH=/usr/local/cuda/lib64\${LD_LIBRARY_PATH:+:\${LD_LIBRARY_PATH}} EOF'

然后重新登录终端,或者执行source /etc/profile.d/cuda.sh。验证两个命令:

nvcc -V

看到类似下面的信息就成功了:

nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2024 NVIDIA Corporation Built on ... Cuda compilation tools, release 12.4, V12.4.131

别忘了一起验证运行库。在 Toolkit 安装目录里有现成的测试程序:

cd /usr/local/cuda/extras/demo_suite ./deviceQuery

如果最后一行是Result = PASS,恭喜,你的 CUDA 环境从编译到运行全链路都通了。这个测试程序比单纯看nvcc -V靠谱得多,因为它真正调用了驱动和运行库。

4. 多版本共存与切换:/usr/local/cuda 软链接的玩法

4.1 为什么需要多版本共存

深度学习项目的 CUDA 版本诉求常常互相打架:某个旧仓库只支持 CUDA 11.8,新项目又要求 12.1 以上。如果你只有一套环境,每次切换都得卸载重装,那体验简直酸爽。runfile 方式天然支持多版本共存,每个版本独立住在自己的目录里,互不干扰。

4.2 安装第二个版本的步骤

假设你现在装了 12.4,还想装一个 11.8。流程很简单:下载 11.8 的 runfile,执行:

sudo sh cuda_11.8.0_520.61.05_linux.run --toolkit --silent --override

它会自动检测到系统里已有其他版本,并把新版本装到/usr/local/cuda-11.8。此时关键动作来了:这个安装过程会把/usr/local/cuda软链改为指向新装的 11.8。也就是说,安装第二个版本后,系统默认激活版本会自动切换成新装的这个,不一定是你想要的。

4.3 切换版本的正确姿势

我习惯用一个命令搞定切换:

sudo rm -f /usr/local/cuda sudo ln -s /usr/local/cuda-11.8 /usr/local/cuda

然后验证:

nvcc -V

如果你想更规范一点,可以用update-alternatives来管理软链:

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

这个工具的好处是版本列表和启停状态一目了然,适合机器上版本特别多的情况。

切换软链时有几个容易踩的坑,我必须单独说一下。第一,nvcc -V显示的版本只代表编译器版本,不代表你程序运行时链接的库版本。如果你编译时用的是 11.8,但LD_LIBRARY_PATH还在指 12.4 的 lib64,程序运行时就可能加载到错误的libcudart,报一些莫名其妙的符号错误。所以切换版本时,PATH 和 LD_LIBRARY_PATH 要一起切。第二,驱动是全局共享的,不管你怎么切 Toolkit 版本,nvidia-smi显示的内容都不变,别以为驱动坏了。

5. 卸载与排障:那些报错到底在说什么

5.1 两种卸载姿势别搞混

runfile 装出来的 Toolkit,自带卸载器。执行:

sudo /usr/local/cuda-12.4/bin/cuda-uninstaller

它会列出当初安装的组件,勾选全部然后卸载。卸载完把残留目录清掉:

sudo rm -rf /usr/local/cuda-12.4

apt 装出来的就用 apt 卸载,但这里有个大坑:apt purge nvidia-*会把驱动也一并卸掉。如果你只是想卸 Toolkit,应该精确指定包名,比如sudo apt purge cuda-toolkit-12-4。如果实在分不清装了哪些包,先跑dpkg -l | grep cuda看看列表再动手。

另外别忘了检查~/.bashrc或/etc/profile.d/cuda.sh里残留的 PATH 配置。软链删了、目录删了,环境变量还留在里面,下次新装版本时容易产生干扰。

5.2 常见报错速查表

我把这几年遇到的高频报错整理成一张表,按症状、原因、处理方式三列对照:

报错或现象大概率原因处理方式
nvcc: command not foundPATH 没配置或没刷新重新配置/etc/profile.d/cuda.sh,重开终端
CUDA driver version is insufficient驱动太旧,撑不起当前 Toolkit升级驱动,或换旧版 Toolkit
No CUDA-capable device is detected驱动没加载,或设备被占用跑nvidia-smi确认,必要时modprobe nvidia
Unsupported gcc versiongcc 太新,超过 Toolkit 支持范围用CC=/usr/bin/gcc-11指定旧版 gcc 编译
libcudart.so.X: cannot open shared objectLD_LIBRARY_PATH 没配好确认环境变量指向当前激活版本的 lib64
装驱动重启后黑屏多半是 Secure Boot 或 nouveau 冲突进 recovery 模式重新导入 MOK,禁用 nouveau

排查这类问题有个固定套路:先nvidia-smi看驱动,再nvcc -V看编译器,然后echo $LD_LIBRARY_PATH看库路径,最后看程序本身链接了哪个版本的库。按这个顺序逐层排查,基本没有查不出来的。

5.3 一个典型的排查链路

说个真实例子。某同学装完 CUDA 12.1 后,编译通过,但运行时报LIBRARY_PATH相关的libcublas.so.11找不到。他查了驱动、查了 nvcc,都没问题,最后我让他打印程序链接信息:

ldd ./my_program | grep cuda

结果发现程序链接的是 conda 环境里的libcublas.so.11,而不是系统/usr/local/cuda/lib64里的libcublas.so.12。原因是他之前装过 conda 的 cudatoolkit 11.8,conda 环境的 LD_LIBRARY_PATH 优先级比系统高,把老版本库顶到了前面。处理方式很简单:在运行脚本里显式把系统的 lib64 放到最前面,或者干脆不激活那个 conda 环境。这个例子说明,很多“CUDA 装坏了”的报错,其实是多套环境叠加造成的路径污染。

6. conda 和容器环境下的 CUDA:系统里没有 Toolkit 也没关系

6.1 conda 环境:运行库和编译器的分离

很多人搞不清 conda 里的 cudatoolkit 和系统 CUDA 的关系。我的理解方式很简单:conda 里的 cudatoolkit 本质上是一堆预编译的 CUDA 运行库和部分工具,它带在 conda 环境里,是为了让 PyTorch、TensorFlow 这类框架能动态链接到对应版本的 CUDA 库,而不需要依赖系统装了什么。

所以一个非常常见的现象是:系统里根本没装 CUDA Toolkit,但 conda 环境里跑深度学习训练一切正常。这是因为框架的预编译包内部已经带了 CUDA runtime。只有在下面两类情况里,你才必须有一个系统级或环境内的完整 Toolkit:

  • 你要编译自定义 CUDA 扩展,需要nvcc
  • 你要链接 CUDA 库到自己的 C/C++ 项目里,需要头文件和libcuda

另外注意一个版本匹配细节:如果你用 conda 环境里的 PyTorch 是 CUDA 11.8 构建的,但你系统驱动的nvidia-smi只支持到 11.4,那即使 conda 里库再全,程序跑起来也会报驱动版本不足。驱动永远是那条最低的底线。

6.2 容器环境:宿主机只需要驱动

Docker 场景下,很多人也会被“宿主机没有 CUDA”这件事吓到。其实容器里的镜像通常自带整套 CUDA Toolkit 和 Runtime,宿主机只需要安装好驱动,再装一个 rust 容器工具链(比如 nvidia-container-toolkit),让容器能访问到 GPU 设备就行。镜像里是什么 CUDA 版本,容器里就是什么版本,跟宿主机装没装 Toolkit 没有关系。

这时候反而建议宿主机保持干净:只装驱动,别装一堆 Toolkit 版本占空间。需要什么 CUDA 版本,直接用对应版本的镜像即可。这条思路在多人共享服务器上尤其好用,因为每位用户都能在自己的容器里选任意 CUDA 版本,互不打架。

6.3 我最终的建议:装完做一次“最小闭环验证”

不管用哪种方式装完,我都强烈建议做一次最小闭环验证,而不是只跑nvcc -V就收工。所谓最小闭环,就是写一个最简单的向量相加程序,编译、运行、检查结果。示例代码在/usr/local/cuda/samples/1_Utilities/vectorAdd或/usr/local/cuda/extras里。跑完deviceQuery显示Result = PASS,再把官方示例编译一两个,基本就能确定整套链路没问题。

我自己每次配置新机器,都会在装完后把版本信息写到一个文件里,包括驱动版本、Toolkit 版本、gcc 版本、以及nvcc -V的完整输出。别小看这个习惯,三个月后再去排查环境问题,这张“底账”能帮你省下大量时间。至少在 Ubuntu 上装 CUDA 这件事,所有诡异问题的第一步都是“先确认你的版本到底是多少”。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进