1. 驱动、Toolkit、cuDNN、框架:四层责任先划清
把 Ubuntu 上的 CUDA 环境装崩,八成不是因为命令敲错,而是因为一开始就没搞清这几层东西谁管谁。我见过太多人拿着nvidia-smi右上角那行CUDA Version: 12.4,就以为自己已经装好了 CUDA 12.4,然后nvcc -V一敲发现命令不存在,接着开始怀疑人生。
先把这四个东西的职责说透:
- 显卡驱动(Driver):内核模块 + 用户态库,负责让操作系统能跟 GPU 说话。它决定了你这台机器最多能跑哪个版本的 CUDA 运行时。
- CUDA Toolkit:编译器(
nvcc)、头文件、静态/动态库、profiler、数学库(cuBLAS、cuFFT、cuSPARSE 等)。这是你写 CUDA 代码、编译 CUDA 项目时真正要用的一整套工具。 - cuDNN:在 CUDA 之上的一层深度神经网络专用库,做卷积、池化、RNN、Attention 等算子的高度优化实现。它不做编译,只提供头文件和
.so。 - 深度学习框架(PyTorch / TensorFlow):调 cuDNN 和 cuBLAS 的上层应用。有意思的是,pip 装的 PyTorch 会自带一份 CUDA runtime,它跟你系统里
/usr/local/cuda那套可以不是同一个版本。
一个特别容易误导人的点是:nvidia-smi右上角显示的CUDA Version,指的是当前驱动支持的最高 CUDA 运行时版本,不是你装了哪个版本。我第一次看到这个数字的时候也以为被系统自动装好了,结果/usr/local下面空空如也。
用个类比:驱动是家里的电源插座标准(220V 还是 110V),CUDA Toolkit 是你买的一整套电动工具,cuDNN 是这套工具里专门用来切硬木的锯片,框架就是拿这些工具干活的木工。插座标准决定了你能用哪些电器,但插座本身不会给你一把电钻。
再补一个反直觉的点:驱动是向下兼容的,不是向上兼容的。新驱动能跑旧 CUDA runtime,旧驱动跑不了新 CUDA runtime。所以「我驱动版本比较老,能不能装新版 CUDA」这个问题,答案是基本不行,正确做法是先升驱动。
| 组件 | 查看命令 | 说明 |
|---|---|---|
| 驱动 | nvidia-smi | 显示驱动版本与支持的最高 CUDA 运行时 |
| Toolkit | nvcc -V或nvcc --version | 显示实际安装的编译器/toolkit 版本 |
| cuDNN | 查cudnn_version.h里的宏 | 头文件在/usr/local/cuda/include |
| 框架 | torch.version.cuda | 框架自带编译时的 CUDA 版本 |
还有一点:Ubuntu 官方源里有个包叫nvidia-cuda-toolkit,apt install一下确实能装上,但版本通常停留在 11.x 甚至 10.x,而且在 22.04/24.04 上还会顺带拖进一堆你不想要的依赖。想省事的话这条路可以试,但如果你要跑比较新的框架或者要编译带 CUDA 的 OpenCV,这个版本基本就是自找麻烦。我的建议是:官方源只用来装驱动,Toolkit 一律走 NVIDIA 自己的源。
2. 版本矩阵怎么定:从显卡算力和框架需求倒推
选定版本这件事,很多人是从「最新的是哪个」开始的,这是个错误方向。正确的推导顺序是:先定框架版本 → 再定 CUDA 版本 → 最后确认驱动够不够新。
第一步:看你的卡是什么架构、什么算力(Compute Capability)。算力决定了 CUDA 编译器要为目标生成哪种 SASS 指令,sm_89、sm_86、sm_75这些数字就是它。
| 显卡举例 | 架构 | 算力 | 最低可用 CUDA |
|---|---|---|---|
| RTX 4090 / 4080 / 4070 / 4060 Ti | Ada Lovelace | 8.9 | 11.8 |
| RTX 3090 / 3080 / 3070 / A100(80G 除外) | Ampere | 8.6 | 11.1 |
| A100 80G | Ampere | 8.0 | 11.0 |
| RTX 2080 / 2070 / T4 | Turing | 7.5 | 10.0 |
| RTX 1080 Ti / 2080 之前 | Pascal | 6.1 | 8.0 |
猛一看 4060 Ti 只需要 CUDA 11.8,好像挺宽松,但别急,还有第二步。
第二步:看框架官方支持哪个 CUDA。PyTorch 的官网上,每个版本都绑定了几套 CUDA 编译产物。以近两年的情况为例,cu118、cu121、cu124是最常见的几个尾巴。如果你手上的项目代码用的是某个特定的 PyTorch 版本,那 CUDA 版本基本就被锁死在它支持的那几个里了。TensorFlow 更死板,它的 wheel 跟 CUDA / cuDNN 版本是一对一硬绑定的,版本差一点就ImportError。
第三步:倒查驱动。CUDA 版本对驱动有最低要求,这个表在 NVIDIA 官方的 CUDA Toolkit Release Notes 里有一份「Table 3. CUDA Toolkit and Corresponding Driver Versions」。大致规律是:
| CUDA 版本 | Linux 最低驱动(近似) |
|---|---|
| 11.8 | 520 |
| 12.0 | 525 |
| 12.1 | 530 |
| 12.2 | 535 |
| 12.3 | 545 |
| 12.4 | 550 |
| 12.5 | 555 |
| 12.6 | 560 |
注意:上面的驱动版本是「最低要求」,具体以官方 Release Notes 为准。不同小版本可能微调,而且
.run安装包自带的驱动版本会和要求略有差异。
如果你nvidia-smi显示的驱动版本低于目标 CUDA 的要求,只有两个选择:升级驱动,或者降低 CUDA 版本。不要尝试用--override强行跳过驱动检查,那只是把报错从安装阶段推迟到运行阶段,而且运行阶段的报错会更难查。
顺便说一句,很多人问 4060 Ti 能不能用。能,Ada 架构支持得很好,cu118和cu121的 PyTorch 都能跑,只是要注意 cuDNN 版本得配 8.9 以上或者 9.x。
再强调一个新手最容易搞混的事:nvidia-smi里写着CUDA Version: 13.0而你想装 PyTorch,这不代表必须装 CUDA 13。那只是驱动支持的上限,装 12.1 完全没问题,反而是最稳的选择。
3. 驱动安装的三条路线与重启这件事
驱动这块,Ubuntu 上有三条常见的路子,各有代价,我按推荐度排序。
路线一:Ubuntu 官方仓库(推荐给绝大多数人)
# 先看看系统推荐哪个版本 ubuntu-drivers devices # 自动安装推荐版本 sudo ubuntu-drivers install # 或者手动指定分支 sudo ubuntu-drivers install nvidia:550Ubuntu 22.04 和 24.04 的仓库里驱动版本更新得还算及时,而且有linux-modules-nvidia-*这类配套包,跟内核升级的配合比较好。装完重启即可。
路线二:NVIDIA 官方 CUDA 仓库(版本最全)
# 以 Ubuntu 22.04 为例 wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt update # 装一个具体分支的驱动 sudo apt install nvidia-driver-550这条路的好处是可选版本非常多(从 470 到最新的都有),适合需要精确控制驱动版本的场景,比如某台服务器上跑着老框架不能乱动。
路线三:官方.run文件(不推荐,除非有特殊理由)
手动跑.run装驱动,每次内核升级之后都可能要重装一遍 DKMS 模块,而且它不会帮你处理nouveau冲突、Secure Boot 签名这些事。除非你要在没网的机器上离线部署,否则我不太建议。
几个必须知道的坑
第一个,装完必须重启。这不是建议,是必须。nvidia-smi报Failed to initialize NVML: Driver/library version mismatch这个错,99% 的情况是 apt 升级了驱动包,用户态库已经是新的,但内核里跑的还是老模块,重启一次就好了。如果重启还没好,试试sudo rmmod nvidia_uvm nvidia_drm nvidia_modeset nvidia再sudo modprobe nvidia,不过有进程占着显存的话会失败,还是重启省事。
第二个,Secure Boot 会拦驱动。开了 Secure Boot 的机器上装 NVIDIA 驱动,DKMS 会要求你设置 MOK 密码,重启时会弹出一个蓝底界面让你 Enroll MOK。很多人直接跳过,结果驱动加载失败,nvidia-smi报NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver。要么老老实实按流程录入密钥,要么进 BIOS 关掉 Secure Boot。
第三个,nouveau 冲突。开源驱动 nouveau 如果还在加载,闭源驱动会起不来。用lsmod | grep nouveau检查,有输出的话需要加 blacklist。apt 装的方式一般会自动处理,.run方式装的时候安装器会问你要不要禁用,选是。
第四个,别在虚拟机上折腾驱动。VMware Workstation、VirtualBox 这类软件默认没有 GPU 直通能力,你装了驱动也用不上。这个后面单独说。
4. CUDA Toolkit 落地:网络 deb、本地 deb 与 .run 的取舍
驱动搞定之后,Toolkit 有三种装法。我把它们的适用场景摊开讲。
方式 A:NVIDIA 网络仓库(最省心)
如果上一步已经装了cuda-keyring,直接:
sudo apt update # 只装 toolkit,不动驱动 sudo apt install cuda-toolkit-12-4 # 或者装完整套(会连驱动一起装,慎用) sudo apt install cuda-12-4这里有个关键区别必须记牢:cuda-12-4是元包,会连带安装驱动;cuda-toolkit-12-4只装工具链,不动驱动。如果你已经装好了满意的驱动,一定选后者,否则 apt 可能把你的驱动降级或者换成一个你没预期的版本。
装完之后nvcc在/usr/local/cuda-12.4/bin/下,需要配环境变量才能直接调用。
方式 B:本地 deb 包(离线机器)
从 NVIDIA 的下载页选deb (local),会下到几个文件:
sudo dpkg -i cuda-repo-ubuntu2204-12-4-local_12.4.1-550.54.15-1_amd64.deb sudo cp /var/cuda-repo-ubuntu2204-12-4-local/cuda-*-keyring.gpg /usr/share/keyrings/ sudo apt update sudo apt install cuda-toolkit-12-4本地 deb 的优点是能塞进 U 盘带到内网机器上装,缺点是本地仓库里的包有生命周期限制,过一段时间apt update会报过期警告,得删掉/etc/apt/sources.list.d/下对应的文件。
方式 C:.run文件(最大自由度,也最容易翻车)
chmod +x cuda_12.4.1_550.54.15_linux.run sudo sh cuda_12.4.1_550.54.15_linux.run \ --toolkit \ --silent \ --override.run安装器是交互式的,跑起来之后会问你:
Install NVIDIA Accelerated Graphics Driver for Linux-x86_64?—— 已经装好驱动的,这里一定要选no。选了yes它会把你的驱动卸了重装,而且装的是它自带的那个版本。Install the CUDA 12.4 Toolkit?—— 选yes。Install the CUDA 12.4 Samples?—— 老版本会问。注意 CUDA 11.x 之后官方推荐用 GitHub 上的cuda-samples仓库,/usr/local/cuda/samples这个路径在很多新版本里已经不存在了,这也是「cuda samples 找不到」这个搜索词的来源。Enter Toolkit Location—— 默认/usr/local/cuda-12.4,不用改。Enter CUDA Samples Location—— 随意。
--toolkit参数的作用就是跳过驱动安装、只装工具链,配合--silent实现无人值守。但记得先装gcc和g++。
gcc 版本这个坑单独拎出来说
NVIDIA 每个 CUDA 版本都有一个 host compiler 支持范围,也就是它能接受的gcc版本上限。Ubuntu 22.04 自带 gcc 11,Ubuntu 24.04 自带 gcc 13。装老一点版本的 CUDA 时,安装器经常会甩一句:
unsupported GNU version! gcc versions later than 12 are not supported!网上流传的解决方案是创建软链接或者加--override跳过检查。这两个做法我都试过,都能装完,但都可能在你编译第三方项目时突然崩掉。更稳的方式是用apt install gcc-11 g++-11装一个低版本,然后:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 11 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 11编译 CUDA 项目时临时指定:
cmake -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 ..说到底,host compiler 支持范围这事,一定要去翻对应版本的 Release Notes,别信博客上抄来抄去的数字。
5. gzip: stdin: invalid compressed>ls -lh cuda_12.4.1_550.54.15_linux.run
CUDA 12.x 的.run文件通常是 3.5G 到 4.5G 的量级,具体看版本。如果你看到的是几十 KB 或者几 MB,那不用猜了,下的是错误页或者被截断的片段。
第二查:它到底是不是可执行文件
head -c 200 cuda_12.4.1_550.54.15_linux.run file cuda_12.4.1_550.54.15_linux.run正常的话,head会输出#!/bin/sh之类的内容,file会说它是 shell script。如果head打印的是<!DOCTYPE html>或者一段 JSON,说明你拿到的是 CDN 的重定向页或限流提示,重新下载即可。
第三查:SHA256/MD5 校验和
NVIDIA 的下载页面会给出每个文件的校验和。跑一遍:
sha256sum cuda_12.4.1_550.54.15_linux.run对不上就是文件坏了。这一步是硬证据,别凭感觉判断。
四大成因,按我的踩坑频率排序
| 成因 | 现象 | 处理方式 |
|---|---|---|
| 下载中途断流 | 大小明显偏小,或接近但不等于官方值 | wget -c续传,或换curl -C -重来 |
| U 盘拷贝被截断 | 文件在网络机器上正常,拷到目标机后变小 | 见下方 FAT32 说明 |
| CDN 返回了 HTML | 大小只有几十 KB | 加-O明确指定文件名,检查链接是否失效 |
| 磁盘写满 | ls大小正常但校验不过 | df -h检查剩余空间 |
FAT32 那个坑值得单独讲
CUDA 12.x 的.run文件体积已经超过 4GB,而 FAT32 文件系统的单文件上限正好是 4GB。你把安装包拷到 FAT32 格式的 U 盘上,很多系统不会报错,只是在 4GB 处悄悄截断。拷到目标机器上,文件大小看着「挺大的」,但 gzip 一解压就报这个错。
排查方法很简单,ls -l拿到字节数和官方值对比,或者直接算 sha256。解决方案是格式化成 exFAT 或 NTFS。
正确的下载姿势
# wget 务必带上 -c,断线可以续传 wget -c https://developer.download.nvidia.com/compute/cuda/12.4.1/local_installers/cuda_12.4.1_550.54.15_linux.run # 大文件用 aria2c 多线程更稳 aria2c -x 8 -s 8 -c <url> # 下完立刻校验,别急着跑 sha256sum cuda_12.4.1_550.54.15_linux.run提示:如果是在不稳定的网络环境下下 4GB 的大文件,一次性下完的概率其实不高。带
-c的wget或者aria2c是必备的,不要用浏览器直接下然后手动挪来挪去。
千万别做的事
最后提醒一句,网上有教程说「用cat分卷合并.run文件」,或者「重新跑一遍安装器就好了」。前者如果你分卷切错了字节边界,合并出来一样损坏;后者如果文件本身坏了,跑一百遍还是同一个报错。先校验文件完整性,再谈安装,这个顺序不能反。
6. cuDNN 铺装:tar 手动铺 vs deb 托管
cuDNN 的版本跟 CUDA 是绑定的。粗略规律是cuDNN 8.x 配 CUDA 11.x,cuDNN 9.x 配 CUDA 12.x。具体每个小版本支持哪些 CUDA,得看官方的 Support Matrix 页面,那上面有张很详细的表。
NVIDIA 现在提供三种 cuDNN 包:tar、deb、rpm。Ubuntu 上就是前两种。
方式一:tar 包手动铺(最直观)
tar -xvf cudnn-linux-x86_64-9.1.0.70_cuda12-archive.tar.xz cd cudnn-linux-x86_64-9.1.0.70_cuda12-archive sudo cp include/cudnn*.h /usr/local/cuda/include sudo cp -P lib/libcudnn* /usr/local/cuda/lib64 sudo chmod a+r /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn*cp -P这个参数很重要,它保留符号链接本身而不去解引用。cuDNN 的.so是带版本号的一串软链(libcudnn.so→libcudnn.so.9→libcudnn.so.9.1.0),不加-P的话cp可能把软链展开成实体文件,导致运行时找不到对应的 soname。
这里有个隐藏的坑:/usr/local/cuda通常是个软链接,指向/usr/local/cuda-12.4。你把文件拷到/usr/local/cuda/include,实际落到的是具体版本目录里。这本来没问题,但如果你之后切换了/usr/local/cuda的指向,cuDNN 就跟着「消失」了。
想避免这个情况的,两个选择:一是明确拷到具体版本目录/usr/local/cuda-12.4/include,二是每个 CUDA 版本目录里各铺一份 cuDNN。
方式二:deb 包托管(推荐)
sudo dpkg -i cudnn-local-repo-ubuntu2204-9.1.0.70_1.0-1_amd64.deb # 这一步很多人会漏,漏了 apt update 会报找不到密钥 sudo cp /var/cudnn-local-repo-ubuntu2204-9.1.0.70/cudnn-*-keyring.gpg /usr/share/keyrings/ sudo apt update # 注意包名,CUDA 12 用 cuda-12 后缀 sudo apt install libcudnn9-cuda-12deb 方式的好处是走 dpkg 数据库,升级、卸载都能追踪,后续apt upgrade也能一起更新。缺点是多版本共存时要格外小心包名冲突。
验证 cuDNN 装好了没有
老教程里流行这一句:
cat /usr/local/cuda/include/cudnn_version.h | grep CUDNN_MAJOR -A 2但 cuDNN 9 之后,CUDNN_MAJOR这些宏改到了cudnn_version_v9.h或者被重新组织过,你按老办法 grep 会一无所获,然后误以为没装成功。原因是 cuDNN 8 和 9 的头文件组织方式变了。
正确做法:
# 看头文件确实在 ls /usr/local/cuda/include/cudnn*.h # 更靠谱的是直接看库文件和版本宏 grep -r "CUDNN_VERSION" /usr/local/cuda/include/cudnn_version*.h # 动态库能不能被链接器找到 ldconfig -p | grep cudnn最后一条如果没输出,说明你需要刷新一下链接缓存:
sudo ldconfig一个实际问题:cuDNN 到底要不要装
如果你的工作流是纯 pip 装 PyTorch 然后写模型训练,那系统级别的 cuDNN 其实可以不装,因为 PyTorch 的 wheel 里已经打包了一份对应的 cuDNN。真正需要系统级 cuDNN 的场景是:自己编译 TensorFlow、编译带 DNN 模块 CUDA 加速的 OpenCV、或者直接调 cuDNN API 写 C++ 推理。搞清楚这一点能省不少时间。
7. 环境变量与多版本共存:别让 PATH 跟你作对
装完之后nvcc敲不出来,是最常见的「装好了但用不了」。根本原因是/usr/local/cuda-12.4/bin不在PATH里。
基础配置
在~/.bashrc末尾加:
export PATH=/usr/local/cuda-12.4/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH然后source ~/.bashrc或者重开终端。
写法上的三个细节
第一,$PATH要放在后面。export PATH=/usr/local/cuda-12.4/bin:$PATH和export PATH=$PATH:/usr/local/cuda-12.4/bin是两回事,后者会把 CUDA 排到最后,如果前面有 conda 或者别的工具链里也有nvcc,你调到的就不是你想要的。
第二,用${PATH}比$PATH更安全。在极少数情况下(比如你写的脚本里紧跟了其他字符串),$PATH后面直接跟字母会被解释成变量名的一部分。
第三,如果不想全局改,可以用软链接策略,让/usr/local/cuda始终指向当前想用的版本:
sudo rm -f /usr/local/cuda sudo ln -s /usr/local/cuda-12.4 /usr/local/cuda # 环境变量里直接写不带版本号的路径 export PATH=/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH这样切换版本只需要改软链接,不用改环境变量文件。
多版本共存的三种切换方式
| 方式 | 操作 | 适用场景 |
|---|---|---|
| 软链接切换 | 改/usr/local/cuda指向 | 单机全局切换,最常用 |
| 环境变量覆盖 | 脚本里临时 export | 多项目并行,不改全局 |
| environment modules | module load cuda/12.4 | 服务器多用户共享 |
第二种最适合日常开发,写个小函数扔进.bashrc:
use_cuda() { export CUDA_HOME=/usr/local/cuda-$1 export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH echo "switched to CUDA $1" }然后use_cuda 12.4或use_cuda 11.8就能在当前 shell 里切。
几个真实的坑
坑一:sudo nvcc找不到命令。因为sudo默认不继承当前用户的环境变量。解决方案是用sudo -E nvcc保留环境,或者写绝对路径sudo /usr/local/cuda-12.4/bin/nvcc,或者直接在 root shell 里操作。
坑二:conda 把你的PATH改了。conda 激活环境时会在PATH前面插一段,如果你在 conda 环境里装了cudatoolkit,那环境里就有一份 CUDA 的库文件。这时候nvcc -V显示的可能是系统那份,但实际编译链接时用到的是 conda 那份,版本对不上就会出各种莫名其妙的符号错误。排查方法:
which nvcc echo $LD_LIBRARY_PATH ldd ./your_binary | grep cudart坑三:LD_LIBRARY_PATH覆盖成空。有些安装脚本或者框架的启动脚本会直接写export LD_LIBRARY_PATH=/some/path,把原来的值全冲掉。这里一定要用LD_LIBRARY_PATH=/some/path:$LD_LIBRARY_PATH的形式追加。
坑四:ldconfig与LD_LIBRARY_PATH混用。更规范的做法是通过ld.so.conf让系统级链接器知道 CUDA 库的位置:
echo "/usr/local/cuda/lib64" | sudo tee /etc/ld.so.conf.d/cuda.conf sudo ldconfig这样就不依赖每个用户的LD_LIBRARY_PATH,系统服务、systemd 单元里也能正常找到库。如果你要跑后台推理服务,这一步基本是必须的。
坑五:环境变量里写了不存在的路径。如果/usr/local/cuda-12.4根本不存在(比如你改软链接的时候手滑),PATH里多一个无效目录不会报错,但LD_LIBRARY_PATH里多一个无效目录在某些程序里会引发链接警告。找不到nvcc的时候,第一件事是ls /usr/local/ | grep cuda确认目录真的存在。
8. 验证链条与典型报错对照
装完不验证,等于没装。我习惯分三级来确认,逐级加码。
第一级:驱动和 GPU 可见性
nvidia-smi期望输出包含驱动版本、CUDA 版本(驱动支持上限)、GPU 型号、显存占用、温度功耗。如果这一步就报command not found,说明驱动没装或者PATH有问题;如果报couldn't communicate with the NVIDIA driver,看第 3 节的重启和 Secure Boot 部分。
第二级:Toolkit 可用性
nvcc -V期望显示Cuda compilation tools, release 12.4, V12.4.xxx。如果显示的和你想装的版本不一致,那就是PATH顺序问题或者软链接指向了别的版本。
接着跑一个官方的 deviceQuery:
# CUDA 12.x 里设备查询工具在这个位置 /usr/local/cuda/extras/demo_suite/deviceQuery预期看到Result = PASS,以及你的 GPU 名称、算力版本、显存等。这一步能确认驱动和 toolkit 的对接是通的。
再进一步,写个最小的 CUDA 程序验证编译和运行:
cat > hello.cu <<'EOF' #include <stdio.h> __global__ void hello() { printf("thread %d\n", threadIdx.x); } int main() { hello<<<1, 4>>>(); cudaDeviceSynchronize(); return 0; } EOF nvcc -o hello hello.cu ./hello能打印出四行thread 0到thread 3,说明整条编译-运行链路是通的。
第三级:cuDNN 与框架
ldconfig -p | grep cudnn应该有libcudnn.so.9之类的输出。
然后是框架验证,这是最终目的:
python -c " import torch print('torch:', torch.__version__) print('built cuda:', torch.version.cuda) print('cudnn:', torch.backends.cudnn.version()) print('available:', torch.cuda.is_available()) print('device:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'N/A') "四个关键字段:torch.__version__是框架版本,torch.version.cuda是框架编译时用的 CUDA 版本,torch.backends.cudnn.version()是它链接的 cuDNN 版本,torch.cuda.is_available()是最终判决。
注意:
torch.version.cuda和你系统nvcc -V的版本可以不一样,这是正常的。pip 装的 PyTorch 自带 CUDA runtime,它只需要你的驱动够新就行,不需要系统装对应版本的 toolkit。这一点很多人理解错了,白白花时间去「对齐版本」。
典型报错对照表
| 报错 | 根因 | 处理 |
|---|---|---|
libcudart.so.12: cannot open shared object file | 库路径没配 | 加ld.so.conf.d/cuda.conf并ldconfig |
CUDA driver version is insufficient for CUDA runtime version | 驱动太老 | 升级驱动,或降低 CUDA 版本 |
no kernel image is available for execution on the device | 编译时算力参数不包含本卡 | 重编时加对-arch=sm_xx或CUDA_ARCH_BIN |
Failed to initialize NVML: Driver/library version mismatch | 内核模块与用户态库不同步 | 重启 |
ImportError: libcudnn.so.9: cannot open shared object file | cuDNN 没装或路径不对 | 检查/usr/local/cuda/lib64并ldconfig |
nvcc: command not found | PATH 未配置 | 见第 7 节 |
unsupported GNU version | gcc 版本超出支持范围 | 装低版本 gcc 并 update-alternatives |
no kernel image is available for execution on the device这个错特别值得展开。它的意思是:你的可执行文件里打包的 SASS 指令集不包含当前显卡的算力版本。比如你编译时写的是-arch=sm_75,但卡是 8.9 的 4060 Ti,运行时就找不到能用的内核。解决办法是重新编译时指定正确的sm_89,或者干脆用-arch=native让编译器自动探测。不过用native编译出来的二进制换到别的机器上就跑不了了,跨机器部署时还是显式指定更好。
9. WSL2、虚拟机、conda:三条旁路的取舍
有三个场景需要单独说,因为它们的规则和裸机不一样,照搬教程会掉坑。
WSL2
WSL2 的 CUDA 环境有个核心原则:驱动装在 Windows 上,Toolkit 装在 WSL2 里,Linux 驱动不要装。
Windows 那边装好 NVIDIA 的常规驱动之后,WSL2 里会出现一个特殊路径:
ls /usr/lib/wsl/lib/ # libcuda.so.1 nvidia-smi ...这个nvidia-smi和libcuda.so.1是从 Windows 侧透传过来的。所以你不需要在 WSL2 里apt install nvidia-driver-*,装了反而可能把透传的库覆盖掉,导致nvidia-smi报错。
WSL2 里要装的是 toolkit:
wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt update sudo apt install cuda-toolkit-12-4注意源地址里的wsl-ubuntu是专门的 WSL 源,别用普通的ubuntu2204源,那个源里的驱动包在 WSL 下用不了。
还有一点,WSL2 的显存是动态分配的,nvidia-smi里显示的显存占用会跟 Windows 任务管理器对不上,这是正常现象。可以建个.wslconfig限制一下内存占用。
虚拟机(VMware / VirtualBox)
这个必须说清楚:普通虚拟机装的 Ubuntu,CUDA 装不了,装了也跑不起来。
原因在于 VMware Workstation 和 VirtualBox 这类桌面虚拟化软件没有 GPU 直通(Passthrough)能力。你在虚拟机里lspci | grep -i nvidia什么都看不到,因为宿主机根本没把 GPU 设备暴露给虚拟机。装驱动的结果要么是装不上,要么是装上了nvidia-smi一直报错。
真要在虚拟化环境里跑 CUDA,需要的是 KVM + VFIO 直通,或者 Proxmox、ESXi 这类支持 PCIe Passthrough 的方案,而且还要主板支持 IOMMU。这是完全另一套技术栈,跟本文的话题不是一回事。
所以如果你只是想在 Linux 上写 CUDA 代码玩玩,装双系统或者直接用物理机,别绕虚拟机这条路。
conda 装 cudatoolkit
conda 生态里可以这样装 CUDA 运行时:
conda install -c nvidia cuda-toolkit # 或者老一点的写法 conda install cudatoolkit=11.8 cudnn=8.9这种方式的特点是:所有东西都装在 conda 环境目录里,不污染系统,环境删掉就完全清干净了。但有三个限制:
第一,conda 的cudatoolkit只有运行时库,通常不含nvcc。也就是说你能跑预编译好的程序,但没法编译 CUDA 代码。要nvcc的话得装cuda-nvcc之类的单独包,或者用cuda-toolkit这个 meta 包。
第二,版本选择受限于 conda 频道。有些 CUDA 小版本在 conda 源里就没有,硬要装就得混源,很容易出现依赖冲突。
第三,跟 pip 装的 PyTorch 可能打架。因为 pip 版 PyTorch 自带一份 CUDA runtime,conda 环境里又有一份,运行时到底加载哪个取决于LD_LIBRARY_PATH和 rpath,出问题时非常难查。
我的经验是:conda 适合快速起一个隔离的实验环境,长期的生产环境还是老老实实系统级安装。如果是纯粹的 Python 训练任务,直接用 pip 装 PyTorch,让框架自己管 CUDA 依赖,是最省心的路径。
10. 编译带 CUDA 的 OpenCV:算力参数最容易踩空
最后一个高频场景:自己编译带 CUDA 加速的 OpenCV。这条路踩坑的概率比装 CUDA 本身高得多,因为 CMake 会自动去探测一堆东西。
依赖先装齐
sudo apt install build-essential cmake git pkg-config \ libgtk-3-dev libavcodec-dev libavformat-dev libswscale-dev \ libv4l-dev libxvidcore-dev libx264-dev libjpeg-dev libpng-dev \ libtiff-dev gfortran openexr libatlas-base-dev \ python3-dev python3-numpy libtbb2 libtbb-devCMake 关键参数
cmake -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D WITH_CUDA=ON \ -D WITH_CUDNN=ON \ -D OPENCV_DNN_CUDA=ON \ -D WITH_CUBLAS=ON \ -D CUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-12.4 \ -D CUDA_ARCH_BIN=8.9 \ -D CUDA_ARCH_PTX= \ -D WITH_OPENCL=OFF \ -D BUILD_opencv_python3=ON \ -D OPENCV_GENERATE_PKGCONFIG=ON \ ..几个参数的作用必须讲清楚:
WITH_CUDA=ON是最基础的开关,不开的话后面全是白搭。OPENCV_DNN_CUDA=ON才会让cv2.dnn模块走 GPU。很多人只开了WITH_CUDA,结果发现 DNN 推理还是 CPU 在跑,就是这个参数没开。CUDA_ARCH_BIN是最容易出错的。它指定为目标显卡生成哪些算力的指令。CUDA_ARCH_PTX=留空表示不生成 PTX 中间码。如果你的目标是只在这台机器上跑,留空可以减小体积、加快编译;如果要分发到其他显卡上跑,建议填一个较低的算力值来生成 PTX,让运行时能 JIT 编译。
CUDA_ARCH_BIN怎么填,对照这张表:
| 显卡 | 算力 |
|---|---|
| RTX 4090 / 4080 / 4070 / 4060 Ti | 8.9 |
| A100 | 8.0 |
| RTX 3090 / 3080 / 3070 | 8.6 |
| RTX 2080 / 2070 / T4 | 7.5 |
| GTX 1080 Ti | 6.1 |
如果填错了,比如给 4060 Ti 填了7.5,编译能过,运行时cv::cuda::GpuMat一用就抛no kernel image is available for execution on the device。这个错在第 8 节的对照表里出现过,根因是同一个。
编译时容易翻车的几件事
第一,内存不够导致 nvcc 被杀。OpenCV 的 CUDA 编译会并行跑好几个nvcc进程,每个都能吃 2-4GB 内存。16GB 内存的机器上开make -j8,很容易触发 OOM Killer,报c++: fatal error: Killed signal terminated program cc1plus。解决方案是降并行度:
make -j4 # 或者更保险 make -j2虽然编译时间会长到一两个小时,但至少能编完。
第二,算力列表填了多个值时编译时间暴涨。比如你写CUDA_ARCH_BIN=7.5;8.0;8.6;8.9,编译时间大致是单值的四倍。除非你确实要跨架构分发,否则就填你实际的卡。
第三,libcudnn找不到。CMake 配置阶段如果看到Could NOT find CUDNN,说明 cuDNN 没铺到 CUDA 目录里,或者LD_LIBRARY_PATH没配好。回头检查第 6 节的拷贝步骤。
第四,Python 绑定没生成。BUILD_opencv_python3=ON开了之后,还要确认 CMake 输出里有Python 3: ... Interpreter和numpy的路径。如果只有 Interpreter 没有 numpy,绑定会静默跳过,编译完import cv2就找不到。装个python3-numpy或者用 pip 装 numpy 都行。
验证 CUDA 加速真的生效
python3 -c " import cv2 print(cv2.__version__) print('cuda devices:', cv2.cuda.getCudaEnabledDeviceCount()) "getCudaEnabledDeviceCount()返回大于 0,才算真的编进去了。返回 0 的话,翻回去看 CMake 的配置输出,多半是某个WITH_*参数没开或者算力填错导致 CUDA 模块被跳过。
我个人在编译 OpenCV 这件事上的体会是:配置阶段把 CMake 的完整输出拉到文件里存一份,出了任何问题都能回溯。
cmake ... 2>&1 | tee cmake_config.log这份日志里会告诉你每个模块是开启还是跳过、每个依赖找没找到,比编译完再猜要高效得多。编译本身可能要一两个小时,但排查配置问题只要几分钟,把时间花在前面的检查上,回报率高得多。