ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

PyTorch跑不起来?先排查CUDA、cuDNN与显卡驱动的依赖链

PyTorch跑不起来?先排查CUDA、cuDNN与显卡驱动的依赖链

先把结论放在最前面:PyTorch 跑不起来,十次里有八九次不是模型代码的问题,而是底层依赖没装对。所谓底层依赖,就是标题里这几位——CUDA、cuDNN、显卡驱动。这篇文章是我这几年反复换机器、帮朋友远程排查工作站、顺手修各种环境问题时攒下来的一份记录,不绕弯子,只讲实际遇到过的坑和验证过能跑的方案。适合刚接触 PyTorch、被各种 cu 版本绕晕的人,也适合已经在跑模型但偶尔被版本警告烦到的老手。

很多人觉得奇怪:我明明在 Python 里就是import torch,然后点.cuda()就完事了,怎么底层牵扯出这么多包?其实这正是 PyTorch 和普通 Python 库最大的区别。普通库装完就结束了,训练框架不一样,它的核心计算要靠一堆二进制库协同工作,CUDA、cuDNN、驱动程序这三样必须同时存在、互相匹配,GPU 上的张量运算才转得起来。麻烦的根源也在这:每一样单独都有不代表能拼起来,版本不对、路径不对、编译配置不对,都会以各种诡异报错的形式冒出来。

1. 先把依赖链捋清楚:PyTorch、CUDA、cuDNN、驱动到底谁是谁

1.1 从一次“装好了但用不了”的求助说起

有次一个同学求助,说刚在一台新机器上装完 PyTorch,torch.cuda.is_available()返回False,但他拍胸脯保证“CUDA 肯定装了”,因为nvidia-smi显示 CUDA 版本号是 12.4。

我当时第一反应不是怀疑显卡,而是先问他一个问题:你确认 PyTorch 装的是 GPU 版本吗?他接着执行python -c "import torch;print(torch.__version__)",输出是2.3.0+cpu。问题一下就明朗了——他装的是 CPU 版 PyTorch,后边再折腾 CUDA 都是白费力气。

这个例子非常典型,它说明一个很多人没意识到的事情:PyTorch 的安装包本身就分 CPU 版和 GPU 版,而且 GPU 版内部已经绑定了配套的 CUDA 运行库。人们以为“装了驱动就等于装完了 CUDA”,但 PyTorch 的世界里,CUDA 更像一个运行时环境,它一部分藏在 PyTorch 的包里,一部分由显卡驱动提供,还有一部分需要独立安装,三条线不搞清楚,排查起问题来就是一团乱麻。

1.2 每个组件在干什么

我把这几样东西用大白话翻译一下:

显卡驱动是最底层的软件,它直接和 GPU 硬件对话,负责把上层指令变成 GPU 能执行的硬件操作。你可以把驱动想成操作系统的“显卡通讯协议”,没有它,GPU 就是一块昂贵的电路板,谁也没法用。

CUDA是 NVIDIA 提供的并行计算平台。它分两个层面:一个是驱动层(CUDA Driver),已经包含在显卡驱动里了;另一个是运行时层(CUDA Runtime),提供cudaMalloc、cudaMemcpy、kernel 启动这些 API。PyTorch 的 GPU wheel 包会把运行时相关的那部分直接放进torch/lib目录,这就是为什么很多人从没装过 CUDA Toolkit,PyTorch 照样能调用 GPU——因为运行时被连同打包了。

cuDNN是 NVIDIA 专门为深度学习写的底层加速库,卷积、池化、RNN、attention 的很多融合算子都靠它提速。PyTorch 在编译时链接 cuDNN,安装时也会把它打包进 wheel 或通过 conda 拉下来,所以对普通用户来说,它一般不需要手动安装,但版本和 CUDA 版本是有对应关系的,这往往是环境排查里容易忽略的一环。

一句话总结这条依赖链:驱动是地基,CUDA 是中间层,cuDNN 和 PyTorch 是上层建筑。地基够不够高,决定上层能不能盖;上层和地基之间版本对不上,就会出“地基太矮”的错误。

1.3 一个最普遍的误解:nvidia-smi 显示的版本和 torch.version.cuda 为什么对不上

继续讲刚才那个同学。后来他换了 GPU 版 PyTorch,torch.__version__变成了2.3.0+cu121,但他马上又来问:nvidia-smi显示 CUDA 12.4,PyTorch 显示 11.8,是不是不匹配,会不会跑不动?

这个问题我几乎每隔一段时间就会被问一次。请记住一个关键结论:nvidia-smi右上角显示的 CUDA 版本,并不是你当前实际使用的 CUDA 版本,而是当前显卡驱动支持的最高 CUDA 版本上限。驱动是向下兼容的,驱动支持 12.4,那么它也能运行用 11.8、12.1 等更低版本编译出来的程序。

所以实际判断规则很简单:驱动支持的最高 CUDA 版本 ≥ PyTorch 编译时用的 CUDA 版本,就能跑。反过来如果驱动版本太低,比如旧驱动只支持到 CUDA 11.0,而你装了 cu121 的 PyTorch,哪怕 PyTorch 自带了 CUDA runtime,驱动也没法接住新指令,就会报“driver too old”或者加载失败。

2. 版本匹配:显卡、驱动、CUDA、PyTorch 怎么配对才稳

2.1 先确认显卡算力,再想后续版本

不同时代、不同型号的显卡,计算能力(Compute Capability)不一样,这直接决定它能不能运行某个版本的 CUDA 算子。我平时排查问题的第一步,永远是先搞清楚机器上到底是一张什么卡。

下表是常见消费级显卡的计算能力参考,大致对应关系如下:

显卡架构代表型号计算能力典型 PyTorch 组合
MaxwellGTX 750 Ti、GTX 9605.0 / 5.2老版本 cu102 / cu111
PascalGTX 10 系列6.0 / 6.1cu118 勉强可用,建议旧版本
TuringRTX 20 系列、GTX 16 系列7.5cu118、cu121
AmpereRTX 30 系列8.6cu121、cu124
AdaRTX 40 系列8.9cu121、cu124、cu128
HopperH100 系列9.0cu124、cu128

为什么这张表重要?因为 PyTorch 的 GPU wheel 只包含一定范围计算能力对应的 kernel,如果你的显卡太老,比如 Maxwell 架构的卡,cu128 的构建大概率没有对应的 kernel,一旦调用 GPU 就会报no kernel image is available for execution on the device。我见过有人拿着老卡硬装最新版 PyTorch,最后只能回头降版本,白白折腾一晚上。

2.2 驱动版本与 CUDA 版本的最低对应关系

下面这张表是我在 NVIDIA 官方驱动兼容矩阵基础上整理的,只列出目前常用的几个 CUDA 版本对应的最低驱动版本,方便你对照:

CUDA Toolkit 版本Linux 最低驱动Windows 最低驱动适合搭配的 PyTorch
11.8520.61.05521.33PyTorch 1.13 - 2.5 的 cu118
12.1530.30.02531.14PyTorch 2.1 - 2.5 的 cu121
12.4550.54.14551.61PyTorch 2.4+ 的 cu124
12.6560.28.03560.70PyTorch 2.6 / 2.7 的 cu126
12.8570.86.10572.83PyTorch 2.7+ 的 cu128

记住一个原则:驱动“买新”,PyTorch 构建“买适合”。驱动装新版本几乎不会影响旧项目,因为它向下兼容;但 PyTorch 的构建版本一旦选错,要么跑不了,要么性能不对。所以我的习惯是:先把驱动升到比较新的稳定版,然后根据实际项目需求选 PyTorch 的 cu 标签。

2.3 我的选型顺序:从需求反推版本

如果是一台新机器,我会按这个顺序决定装什么:

首先,看项目里其他人或官方代码用的 PyTorch 版本,尽量保持一致,团队协作时版本差异是最大的隐形炸弹。其次,看自己显卡的算力,上面那张表一对照,老卡自动排除太新的 cu 版本。最后,看驱动支持的上限,用nvidia-smi确认驱动版本后查对应表,确保驱动允许你选的 PyTorch cu 版本。

比如我现在常用的组合是 PyTorch 2.4 + cu121,因为它在驱动要求、算子性能和生态兼容性三方面比较均衡。RTX 30 系列和 40 系列都能用,驱动只要 530 以上就满足,而且 mmcv、transformers 这些库对它支持都不错。如果你手头是 RTX 50 系列新卡,那可能就得选 cu124 或 cu128,这时候不能死守旧组合。

3. 安装阶段最容易翻车的三个环节

3.1 用 pip 还是 conda?背后是完全不同的依赖逻辑

这个问题被问过很多次,我的回答是:如果你不是非要原生速度不可,conda 更省心,pip 更灵活,但两种方式都不能乱来。

conda 安装 PyTorch 的典型命令是:

conda create -n dl python=3.10 conda activate dl conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia

conda 的好处是它会自动解析cudatoolkit、cudnn、numpy这些依赖的兼容组合,像一个保姆一样帮你把版本关系理顺。你几乎不会遇到缺.so文件的问题,因为它把 CUDA 相关库都作为独立包放进环境里了。

pip 的典型安装是:

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

注意,这个--index-url是 PyTorch 官方提供的专用索引,PyPI 默认源拿到的 wheel 不一定带完整 CUDA runtime,或者不是你想要的 cu 版本。pip 安装的其实是打好包的 wheel,里面已经捆绑了 CUDA 运行库和 cuDNN,所以速度更快、文件更小,但如果你以后要编译自定义 CUDA 扩展,比如安装 apex 或者某些需要.cpp编译的算子库,conda 环境因为有独立的nvcc反而更顺。

两条路都通,最怕的就是混着来:conda 环境里再 pip 覆盖,或者反过来。混装出的问题往往最诡异,我后面专门写一节。

3.2 下载慢、镜像源的坑:别让加速变成踩坑源头

国内下载 PyTorch 官方 wheel 经常慢到怀疑人生。通常大家会配置国内 pip 镜像,比如把全局index-url改成清华或阿里云的 PyPI 镜像,然后执行pip install torch。这里有一个隐蔽的坑:这样装到的通常是默认源里的 wheel,很可能不是你想要的那个 cu 标签,甚至可能是 CPU 版本。我见过不止一个同学配置了全局镜像后高高兴兴装完,跑到验证阶段才发现torch.__version__带+cpu。

如果你想指定 CUDA 版本,正确的做法是用--index-url指定 PyTorch 官方源,或者使用国内第三方提供的 PyTorch wheels 镜像。实际操作中我还会先把 wheel 文件下载到本地,再离线安装,避免反复下载失败,特别是服务器上网络不稳定的时候。命令大致是:

pip download torch torchvision --index-url https://download.pytorch.org/whl/cu121 --dest ./pkgs pip install ./pkgs/torch*.whl ./pkgs/torchvision*.whl

conda 也可以配置清华镜像加速,在.condarc中加入对应的pytorchchannel 镜像即可。但无论怎么加速,都要在装完之后第一时间检查torch.__version__是否带+cu,这是最低成本的纠错动作。

3.3 环境变量 CUDA_HOME:什么时候真的要动它

很多教程上来就让你设置CUDA_HOME,导致新手以为这是必须步骤。实际上,纯运行 PyTorch 根本不需要设置 CUDA_HOME,因为 wheel 里的库已经通过 rpath 或包内路径声明好了。只有当你要编译 CUDA 扩展、跑包含 CUDA C/C++ 源码的项目时,系统才需要找到 nvcc 编译器,这时 CUDA_HOME 才发挥作用。

在 Linux 上,如果你手动装了独立 CUDA Toolkit,并把它解压到/usr/local/cuda-12.1,通常只要写进 PATH 就够了:

export PATH=/usr/local/cuda-12.1/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-12.1/lib64:$LD_LIBRARY_PATH

但在用 conda 的环境里,我反而不建议手动设置 LD_LIBRARY_PATH 指向系统 CUDA,因为可能让 Python 进程加载到系统里版本不对的libcudart,和 conda 包里的依赖打架,最后 import torch 直接段错误。我的经验是:除非明知自己在编译什么,否则别动环境变量,让安装工具自己管理依赖。

4. 装完别急着跑模型,按这个清单验证

4.1 三行代码自查依赖版本

装好之后,我通常先跑一个“体检”命令,把关键信息一次性打出来:

python -c "import torch; print(torch.__version__, torch.version.cuda, torch.backends.cudnn.version())"

输出类似:

2.4.0+cu121 12.1 8902

第一个是 PyTorch 版本,必须带+cu121或+cu118这类后缀,如果是+cpu,直接回头看安装环节。第二个是 PyTorch 内部捆绑的 CUDA 运行库版本,第三个是 PyTorch 实际链接的 cuDNN 版本号,比如 8902 对应 cuDNN 8.9.2。

再跑第二行:

python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'no gpu')"

这一步是为了确认 PyTorch 能看到 GPU。如果返回True和显卡名称,基础环境基本没问题。

4.2 能 import、能 .cuda(),不代表性能正常

有些人到这里就觉得万事大吉,但 GPU 环境还有更深一层的问题:PyTorch 确实调用起来了,但实际卷积走的是 cuDNN 还是某些回退实现,速度天差地别。我建议做一个快速压力验证,比如用大矩阵乘法看看是否有意外瓶颈:

import torch, time a = torch.randn(10000, 10000, device="cuda") b = torch.randn(10000, 10000, device="cuda") for _ in range(10): c = a @ b torch.cuda.synchronize() t0 = time.time() for _ in range(50): c = a @ b torch.cuda.synchronize() print("average time:", (time.time() - t0) / 50)

注意torch.cuda.synchronize()这行非常关键。GPU 计算是异步的,如果不做同步,测到的时间很可能只是入队时间,结果完全不真实。如果你发现同一个矩阵乘法在 GPU 上比 CPU 还慢,那多半是驱动/算力不匹配导致 PyTorch 根本没走到正确的 kernel,这时候要回头检查计算能力和 cu 版本。

另外,跑模型前我还会顺手确认一下 cuDNN benchmark 和 memory 相关设置:

torch.backends.cudnn.enabled = True torch.backends.cudnn.benchmark = True

对于固定 shape 的模型,benchmark 模式能省不少启动时间,但如果你频繁改输入尺寸,反而可能因为每次重选算法而更慢,这个要看场景决定。

4.3 完整还原一个排查链路:torch.cuda.is_available() 返回 False

这一类问题是最常见的。下面是用我自己的排查顺序走的真实链路,照着做基本能定位九成问题。

第一步,确认驱动真的活着。执行nvidia-smi,如果提示找不到命令或没有输出,考虑驱动没装、驱动服务没起来、或者这台机器根本是纯 CPU/集显环境。如果是 WSL2 环境,记住驱动装在宿主机 Windows 侧,WSL 内部不需要也不能单独装,只要宿主动了就能看到。

第二步,看 PyTorch 的构建标签。执行python -c "import torch;print(torch.__version__)",发现+cpu就说明装错包了。原因多半是全局 pip 镜像覆盖了官方源,或者安装时少加了--index-url。解决方法是按第三节说的指定官方源重装。

第三步,看驱动版本是否够老。如果 nvidia-smi 显示 Driver 版本是 450 或更早,而 PyTorch 是 cu118 以上,那很可能就是“驱动太旧”。可以尝试换成对应旧版 PyTorch 或者升级驱动,我更推荐升级驱动,因为新驱动不会破坏旧 PyTorch 的兼容性。

第四步,检查是否被集显干扰。台式机带核显、笔记本双显卡的机器,有时 PyTorch 的get_device_name(0)会返回类似Intel UHD的集显名称,或者干脆cuda:0不存在。这时要么在 NVIDIA 控制面板把 Python 进程强制指定到独显,要么检查有没有在无 GPU 的设备上跑。

第五步,检查 CUDA_HOME 是不是被人为摘坏了。如果系统里存在/usr/local/cuda指向的软链失效,或者多个版本的 libcudart 路径冲突,会导致torch.cuda.is_available()为 False。临时把它从环境变量里摘掉,或者重新安装 PyTorch 后重启终端,问题往往就消失了。

这一套按顺序走下来,绝大多数“装好不能用”的情况都能落地解决。

5. 文档里看不见的坑:版本升级与老环境维护

5.1 conda 与 pip 混装的教训

我在真实环境里踩过的最深的一个坑,是 conda 装好的 PyTorch 环境里直接pip install torch --upgrade。看起来只是升级,但 pip 会把 torch 相关的 wheel 文件和 conda 管理的外部库打得七零八落,尤其是 CUDA 运行库、libstdc++ 版本,一旦对不上,轻则警告,重则 import torch 时直接崩溃,报错信息完全看不出是版本问题。

我的做法是:一个环境只走一个包管理器。如果用 conda 创建的环境,PyTorch 也全部用 conda 装,需要额外 Python 包的时候再用 pip 装那些 conda 没有的;如果必须用某个 pip-only 的版本,那就新建一个环境,从 pip 入手一路装到底。这样虽然少了一点灵活性,但环境稳定性高太多。

5.2 多项目多环境之间的版本隔离与记录

搞深度学习的人手头经常同时跑好几个项目,模型代码版本、CUDA 版本要求都不一样。我的习惯是每个项目建一个独立 conda 环境,并且把关键依赖写进一个文本文件,而不是事后靠猜:

pytorch=2.4.0 torchvision=0.19.0 pytorch-cuda=12.1 cudatoolkit=12.1 cudnn=8.9.2

这个文件比pip freeze生成的数百行依赖清单管用得多,因为核心就那几样,谁升级了、谁降级了,一眼能看懂。等到团队协作时,这份清单也能直接用于重建环境。

如果环境实在乱到不想修,还有个更暴力的方案:直接上 Docker。拉取官方或社区维护的pytorch/pytorch镜像,按 tag 选 cu 版本,镜像里所有依赖都是配好的,不存在宿主机驱动的兼容问题,只要求宿主机的 NVIDIA 驱动和 container toolkit 支持即可。我目前在有条件用容器的地方都会优先考虑这个方案,省心程度确实不一样。

5.3 两个帮助我少踩三年的小习惯

第一个习惯是遇到任何报错,先按“驱动 → PyTorch 构建 → 运行验证”这个顺序做一遍体检,而不是直接根据报错信息搜改代码。比如RuntimeError: Found no NVIDIA driver on your system这种提示已经非常直白,看错地方只会越搜越乱。

第二个习惯是别在系统层面手动安装一堆 CUDA Toolkit。很多人以为独立装个完整 SDK 更保险,其实对纯 PyTorch 用户来说没有必要,反而容易在 PATH 和 LD_LIBRARY_PATH 里留下混乱的线索。PyTorch 官方 wheel 已经管理好运行时,我只在确需编译扩展时才装最小化的 Toolkit,并且只装到当前项目环境里,不污染系统。

最后说一句和我实际经历相关的体会:PyTorch 这三层依赖看着复杂,但一旦理顺,它其实是所有深度学习环境里最好迁移的之一。只要你记录好核心版本组合、尊重包管理器边界、坚持从官方渠道获取 wheel,之后再换机器、换服务器,基本是一遍过的事。现在我遇到环境问题,已经很少再靠运气乱试了,而是直接按这套记录里的顺序排查,效率高了很多。

RELATED READING

延伸阅读

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