
上个月我把工作室那台 RTX 3090 工作站的 CUDA 工具链从 11.4 升到了 12.8全程没有卸载旧版本系统也没有黑屏或者驱动崩掉。这台机器装的是 Ubuntu 22.04日常主要跑 PyTorch 训练和推理。说实话升级的念头早就有了——新版本的 vLLM、FlashAttention、还有 PyTorch 2.x 的 cu128 轮子都已经把 CUDA 12.x 当成默认前提而我一直停在 11.4很多新代码连编译都过不去。这次下决心动它是因为再不动整个开发环境就要被生态拖着走了。这个教程不是写给“从零安装 CUDA”的新手的而是写给已经有一台 Ubuntu 工作站、上面跑着 CUDA 11.4 或其他老版本、想在不破坏现有项目的前提下升级到 12.8 的人。整个流程核心就一句话多版本共存默认指向新版旧版留着保命。如果你也正卡在“升级怕翻车、不升级又难受”的状态这篇应该能帮你把路趟平。1. 先搞清楚升级的边界不是装上 12.8 就完事1.1 3090 在 CUDA 12.8 里的架构定位RTX 3090 用的是 Ampere 架构算力是 sm_86。CUDA 12.8 对 Ampere 的支持完全没有问题NVIDIA 对消费级 Ampere 的驱动支持周期也还很长所以硬件本身不是瓶颈。但很多人容易忽略一件事你的代码是不是按 sm_86 编译的。PyTorch 官方 cu128 的 wheel 里默认是带上 sm_86 的 kernel 的所以直接用 pip 装没问题。但如果你自己编译扩展或者从源码编译 PyTorch、FlashAttention、某些 CUDA 算子编译时不指定TORCH_CUDA_ARCH_LIST8.6编出来的 cubin 可能不含 3090 对应的架构跑起来就会出现经典的no kernel image is available for execution on the device错误。这个后面会专门讲。3090 的显存是 24GB对我们这种搞深度学习的人而言这个卡到现在依然能打。升级到 CUDA 12.8 之后显存带宽、kernel 调度这些底层能力并不会凭空翻倍但新生态工具的兼容性会直接拉开差距。比如最新版的 cuDNN 9.x 只支持 CUDA 12.x很多新框架直接把 CUDA 11.4 从依赖列表里踢掉了——这才是升级的根本动力。1.2 驱动版本决定你能跑多高这是整个升级过程中最重要、也最容易被忽视的一个检查点。NVIDIA 的显卡驱动和 CUDA Toolkit 之间不是“随意搭配”的关系。驱动自身有一个支持的最高 CUDA 版本你可以通过nvidia-smi命令看到nvidia-smi输出顶部有类似这样的信息--------------------------------------------------------------------------------------- | NVIDIA-SMI 570.124.06 Driver Version: 570.124.06 CUDA Version: 12.8 | ---------------------------------------------------------------------------------------这里显示的CUDA Version: 12.8指的是当前驱动能支持的最高 CUDA 版本。如果你现在装的是 470 系列的驱动它对应的就是 CUDA 11.4那你就算手动装了 12.8 的 Toolkit运行的时候也会因为驱动太老而直接报错。CUDA 12.8 官方要求 Linux 系统下驱动版本至少是 570.124.06。所以升级前必须先看驱动版本nvidia-smi | grep Driver Version如果驱动版本不够有两个选择一是用apt升级系统驱动二是到 NVIDIA 官网下载对应版本的驱动包手动安装。我个人推荐先用 apt 从官方源或 graphics-drivers PPA 升级驱动毕竟手动装驱动有个风险黑屏。命令大致是sudo add-apt-repository ppa:graphics-drivers/ppa sudo apt update sudo apt install nvidia-driver-570装完后重启用nvidia-smi确认驱动版本已经是 570 或更高。这里有一个我很想强调的细节驱动升级完一定要重启而且别急着装 CUDA Toolkit先把驱动跑稳了再继续。很多人为了省事跳过重启结果装完 CUDA 发现系统不稳定还会误以为是 CUDA 的问题。1.3 升级前的完整环境快照在动手之前先给当前环境拍一张“快照”。这一步不是走形式我见过太多人升级到一半发现回不去就是因为没有记录旧版本号。需要记录的信息其实不多检查项命令说明显卡驱动版本nvidia-smi确认最终 CUDA Version 支持上限当前 CUDA 版本nvcc --version记录旧 Toolkit 版本号CUDA 安装目录ls /usr/local/grep cudaPyTorch 版本pip show torch记录 wheel 对应的 CUDA 版本PyTorch 实际用的 CUDApython -c import torch; print(torch.version.cuda)检查运行时不是只看 nvcc我当时记录的结果是驱动版本470.182.03对应 CUDA 11.4现有 CUDA/usr/local/cuda-11.4软链接 /usr/local/cuda 指向它PyTorch 用的 CUDA11.4看到驱动版本还是 470 的时候我就知道这次升级必须先动驱动。另外我还顺手跑了几个旧项目里的核心测试用例确认它们在升级之前是能正常通过的。这一步很关键升级后如果旧项目挂了至少能确定是环境变动导致的而不是项目本身早已有问题。2. 选择“共存”而不是“覆盖”保留 CUDA 11.4 的真实原因2.1 老项目的霸道依赖一开始我也想过直接把 11.4 卸了干净利落装上 12.8多清爽。但冷静下来一查不行。工作室里有几个老项目里面用到了自己编译的 CUDA 扩展比如基于旧版 PyTorch 编译的 pointnet 算子还有一些依赖特定版本的 cuDNN 和 cuBLAS 的模型推理代码。这些项目在编译时直接链接的是/usr/local/cuda-11.4/lib64/libcublas.so.11、libcudart.so.11.0这类动态库。如果我直接把 11.4 删除这些库就会直接消失老项目运行时会立刻报错最常见的是undefined symbol或者libcublas.so.11: cannot open shared object file。所以结论很现实老版本不是负担而是保险。多版本共存是 Linux 下 CUDA 管理的最佳实践没有之一。NVIDIA 官方也确实支持这种结构你可以在/usr/local下同时存在cuda-11.4、cuda-12.8再通过一个cuda软链接来决定默认版本。2.2 runfile 与 deb 安装方式的取舍安装 CUDA Toolkit 有两条主流路线deb 包和 runfile。deb 包好处是能用 apt 管理升级卸载方便。坏处是它会把自己绑定到系统的 apt 源里而且经常会在/usr/local/cuda这里抢软链接的所有权。如果你已经有 11.4再装一个 12.8 的 deb容易把默认版本搅乱。runfile安装到/usr/local/cuda-12.8并可选更新/usr/local/cuda软链接。整个过程完全可控切换版本就是改一下软链接的事。我的建议是新版用 runfile旧版保持原有的安装方式不动。这样旧版没有被触碰新版独立存在二者井水不犯河水。2.3 CUDA 12.8 Toolkit 完整安装命令确认驱动已经是 570 以上之后开始安装 Toolkit。从 NVIDIA 官网下载 runfile 的命令如下wget https://developer.download.nvidia.com/compute/cuda/12.8.0/local_installers/cuda_12.8.0_570.124.06_linux.run下载完先不要急着执行。先检查一下当前软链接状态ls -l /usr/local/cuda如果它指向cuda-11.4接下来 runfile 在安装时可能会尝试把它更新成cuda-12.8。这不算错误但你要知道它会发生。然后执行安装sudo sh cuda_12.8.0_570.124.06_linux.runrunfile 会进入一个 ncurses 交互界面。重点来了有四个选项需要你手动处理Driver这一项一定要取消。因为我们已经手动把驱动版本升到 570 以上了再让 runfile 装驱动反而可能因为版本冲突搞乱系统。CUDA Toolkit保持勾选这是核心内容。CUDA Samples可选项建议勾上后面测试时用得上。Symlink在 Options 子菜单里。默认会创建/usr/local/cuda软链接我建议保留因为这就是后面我们切换默认版本的关键开关。如果你不想在交互界面里手动点也可以用静默安装效果一样sudo sh cuda_12.8.0_570.124.06_linux.run --toolkit --silent安装完成后检查目录ls -l /usr/local/ | grep cuda正常情况下你会看到cuda、cuda-11.4、cuda-12.8三个目录或其中两个加上软链接。确认cuda软链接现在指向的是cuda-12.8nvcc --version如果显示的是 12.8说明 Toolkit 本身已经装好了。如果显示的还是 11.4别慌多半是环境变量里的 PATH 顺序问题下一节专门处理。2.4 安装过程中的三个交互选项很多第一次用 runfile 的人会在交互界面里迷惑这里拆开说一下。Driver 不勾选是硬性要求。runfile 里的驱动包和系统里已有的驱动是两套东西如果混着装轻则 nvidia-smi 失效重则开机后进不了图形界面。这是我见过最多人翻车的点。Toolkit 是必选的它包含了 nvcc、cuBLAS、cuFFT、cuDNN 等核心库。Samples 看你需要如果你喜欢跑deviceQuery这类经典测试程序就勾上。Symlink 这个选项决定了/usr/local/cuda指向哪里。我个人是让它指向 12.8 的。原因很简单新项目默认用新的老项目需要时再单独切换环境变量。这比反过来维护旧版成本低得多。3. 环境变量与软链接多版本并存时最容易翻车的环节3.1 nvcc 到底会走哪条路装完 runfile 后最诡异的情况是/usr/local/cuda软链接明明已经指向 12.8 了但你打开一个新终端跑nvcc --version显示的却是 11.4。原因只有一个PATH 环境变量的优先级。如果当前 shell 的 PATH 里/usr/local/cuda-11.4/bin排在/usr/local/cuda/bin前面那系统会优先找到 11.4 的 nvcc。用这个命令可以立刻查清楚which nvcc它会告诉你当前实际调用的是哪个目录下的 nvcc。如果你看到which nvcc返回的是/usr/local/cuda-11.4/bin/nvcc那就说明 PATH 有问题。3.2 让整个系统默认使用 12.8我建议把 CUDA 相关的环境变量统一写在/etc/profile.d/下而不是直接改/etc/profile或者~/.bashrc。/etc/profile.d/下的脚本会在用户登录时被自动加载而且只影响登录 shell系统服务不会受到波及管理起来更干净。具体操作sudo tee /etc/profile.d/cuda-12.8.sh EOF export CUDA_HOME/usr/local/cuda export PATH/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda/lib64:$LD_LIBRARY_PATH EOF这里有一个很核心的设计环境变量里全部用/usr/local/cuda而不是写死/usr/local/cuda-12.8。这样以后想切回 11.4只需要改软链接不需要改环境变量。新开一个终端source /etc/profile.d/cuda-12.8.sh nvcc --version如果显示 12.8就成功了。另外如果你发现有程序加载动态库时仍然用的是旧版 CUDA不妨检查一下/etc/ld.so.conf.d/下有没有旧版 CUDA 的路径。如果运行ldconfig -p | grep cudart能看到多个版本的libcudart.so建议把 12.8 的路径放在最前面必要时执行一次sudo ldconfig刷新缓存。3.3 为老项目单独锁定 11.4默认走 12.8 之后老项目如果直接跑可能会因为动态库版本不对而报错。这时候不需要动全局配置只需要在运行老项目的终端里单独切换环境变量export CUDA_HOME/usr/local/cuda-11.4 export PATH/usr/local/cuda-11.4/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda-11.4/lib64:$LD_LIBRARY_PATH这种方式只对当前终端生效不影响系统里其他进程。对老项目来说这就是一个临时保险箱。等它升级到支持 CUDA 12.x 之后你就能彻底告别这种切换了。3.4 快速切换脚本多版本环境用久了你会发现自己经常要在两个版本之间来回切。手动输入 export 命令太容易出错了。我写了一个简单的切换脚本放在/usr/local/bin/switch_cuda#!/bin/bash TARGET$1 if [ -d /usr/local/cuda-$TARGET ]; then sudo ln -sfn /usr/local/cuda-$TARGET /usr/local/cuda echo Switched CUDA to $TARGET nvcc --version else echo CUDA $TARGET not found in /usr/local ls /usr/local/ | grep cuda fi加执行权限sudo chmod x /usr/local/bin/switch_cuda以后切换版本只需要switch_cuda 11.4 switch_cuda 12.8因为环境变量指向的是/usr/local/cuda软链接所以切换后新开的终端会立刻生效不需要再手动 export 任何东西。这套方案我用了很久目前没出过问题。4. 装上之后必须做的三件事cuDNN、PyTorch 与跑通实测4.1 cuDNN 9.x 的安装CUDA 12.8 需要搭配 cuDNN 9.x 才能发挥深度学习的完整性能。在 NVIDIA 官网下载对应 CUDA 12 的 cuDNN 包文件名大概是cudnn-linux-x86_64-9.3.0.75_cuda12-archive.tar.xz这种。安装方式不是用 apt而是把解压出来的头文件和动态库复制到 CUDA 12.8 的目录下tar -xvf cudnn-linux-x86_64-9.3.0.75_cuda12-archive.tar.xz sudo cp cudnn-*-archive/include/cudnn*.h /usr/local/cuda-12.8/include/ sudo cp cudnn-*-archive/lib/libcudnn* /usr/local/cuda-12.8/lib64/ sudo chmod ar /usr/local/cuda-12.8/include/cudnn*.h /usr/local/cuda-12.8/lib64/libcudnn*这里有个细节我建议把 cuDNN 复制到/usr/local/cuda-12.8而不是复制到/usr/local/cuda。因为/usr/local/cuda是软链接如果哪天你切回 11.4 再切回来软链接指向可能变但具体版本目录下的 cuDNN 不会丢。复制完可以验证一下版本grep CUDNN_MAJOR /usr/local/cuda-12.8/include/cudnn_version.h如果能看到#define CUDNN_MAJOR 9说明 cuDNN 已经就位。4.2 PyTorch cu128 轮子与 CUDA 可用性验证接下来处理 Python 生态。如果你用的是 Anaconda 或者虚拟环境建议新建一个环境专门测试别直接在旧环境上升级不然可能把旧项目依赖打成一锅粥conda create -n cuda128_test python3.10 -y conda activate cuda128_test然后用 pip 安装官方提供的 cu128 版本 PyTorchpip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128装完先跑一段最基础的验证脚本python -c import torch; print(torch:, torch.__version__); print(cuda:, torch.version.cuda); print(avail:, torch.cuda.is_available()); print(device:, torch.cuda.get_device_name(0))如果看到cuda: 12.8和avail: True说明 PyTorch 已经正确接上了系统里的 CUDA 12.8。我装完第一眼看到这个输出的时候心里踏实了一大半。还可以再加一行看看算力python -c import torch; print(torch.cuda.get_device_capability(0))RTX 3090 应该输出(8, 6)也就是 sm_86。如果这里出现的是其他数字就要回头检查是不是驱动没装对。4.3 真实模型推理测试光验证 torch.cuda.is_available() 还不够那个只是说明 CUDA runtime 能初始化。真正要确认的是 cuDNN、cuBLAS 这些深度学习的底层库是不是完全打通了。我用一个很小的 ResNet 测试跑了一遍前向和反向import torch import torch.nn as nn model nn.Sequential( nn.Conv2d(3, 16, kernel_size3, padding1), nn.ReLU(), nn.AdaptiveAvgPool2d((1, 1)) ).cuda() x torch.randn(8, 3, 224, 224).cuda() y model(x) loss y.sum() loss.backward() print(forward/backward OK, output shape:, y.shape)如果能正常打印出forward/backward OK就说明 PyTorch 的 CUDA 链路、cuDNN 卷积实现、以及反向传播的 kernel 都工作正常。这一步跑通之后升级才算真正意义上的完成。5. 升级后绕不开的坑从 nvcc 版本错乱到 cublas 报错5.1 坑一nvcc 版本对运行时报错 no kernel image这是我在升级过程中遇到的第一个大坑也是网上提问最多的问题之一。具体报错长这样torch.acceleratorerror: cuda error: no kernel image is available for execution on the device出现这个错误时nvcc --version显示的是 12.8torch.cuda.is_available()也返回 True但实际运行 kernel 时就是在设备上找不到可用的镜像。排查思路是这样的先确认驱动版本是 570 以上因为旧驱动根本不认识新编译出的 cubin。命令nvidia-smi。确认 PyTorch 的 wheel 是不是 cu128 版。用torch.version.cuda看。如果你是自己编译的扩展编译时一定加上TORCH_CUDA_ARCH_LIST8.6确保编译出的 cubin 覆盖 Ampere 架构。我当时是编译某个第三方算子时没有设置架构变量默认编出了 sm_90Hopper的 cubin结果在 3090 上根本跑不了。加上TORCH_CUDA_ARCH_LIST8.6重新编译后问题直接消失。5.2 坑二cublas status execution failed 的老朋友另一个高频报错长这样RuntimeError: CUDA error: CUBLAS_STATUS_EXECUTION_FAILED when calling cublas...这个报错看起来是 cuBLAS 底层执行失败了但绝大多数情况下不是显卡坏了而是动态库版本混了。原因通常是LD_LIBRARY_PATH里同时存在/usr/local/cuda-11.4/lib64和/usr/local/cuda-12.8/lib64程序运行时加载了不匹配的libcublas.so。排查方式ldd $(python -c import torch; print(torch.__file__)) | grep cublas如果看到两个路径下各有一个libcublas.so版本或者某一个库找不到基本就实锤了。解决办法是把LD_LIBRARY_PATH里旧的 CUDA 11.4 路径去掉只保留 12.8。如果旧项目必须要用 11.4那就严格用前面第二节的方法单独开终端切换别把两个版本同时暴露在全局环境变量里。这是多版本共存最核心的一条纪律。5.3 坑三/usr/local/cuda 软链接被搞乱在上手多版本共存之前我一直觉得/usr/local/cuda是一个正常的目录其实它在多版本环境里只是一个软链接。很多人稀里糊涂地用了sudo cp -r去复制文件直接把软链接替换成了普通目录或者sudo rm -rf /usr/local/cuda后软链接断了导致nvcc找不到命令。修复很简单sudo rm -rf /usr/local/cuda sudo ln -sfn /usr/local/cuda-12.8 /usr/local/cuda再执行nvcc --version验证即可。顺带提一个常见衍生问题很多人装完 CUDA 后找不到cuda samples也就是没有/usr/local/cuda-12.8/samples目录。原因多半是 runfile 安装时没有勾选 Samples 组件。解决办法有两个一是重新跑一次 runfile 并把 Samples 勾上二是到 NVIDIA 官网单独下载 CUDA Samples 包解压到本地目录后直接make不一定非要放到/usr/local下面。5.4 坑四conda 环境里的隐身版本这个坑很隐蔽因为它不会主动报错而是让你的程序在诡异的地方出错。Anaconda 里创建一个新环境时有时会默认装上cudatoolkit11.x或cudatoolkit12.x这类包。这时候即使系统 CUDA 已经切到了 12.8conda 环境里依然会优先用自己带的 CUDA 库导致版本和系统不一致。一个典型场景是你在 conda 环境里用 PyCharm 跑老项目torch.version.cuda显示的是系统 CUDA 版本但某些扩展在 import 时突然报undefined symbol或干脆是libcudart.so.11.0找不到。你用ldd一看发现很多库都来自 conda 环境而不是/usr/local/cuda-12.8。解决办法是在 conda 环境里移除自带的 cudatoolkit明确使用系统的 CUDAconda remove cudatoolkit或者新建环境时就别装cudatoolkit这个包直接用 pip 安装 PyTorch让它依赖系统 CUDA。如果你确实需要在 conda 里管理 CUDA 版本就老老实实把环境里的 CUDA 版本和系统驱动对齐别两个体系混着来。这里再补充一个和 PyCharm 相关的经验PyCharm 解释器选择 conda 环境后会把环境里的动态库路径也加入运行路径有时候即使你设置了系统LD_LIBRARY_PATHPyCharm 还是会优先加载 conda 环境里的库。所以遇到诡异问题先跑一下ldd看清楚动态库来自哪里再决定改哪边。5.5 心灵拷问升级到底值不值得升级过程中踩坑的时间成本是真实存在的。但把 11.4 升到 12.8 之后我发现整个开发体验提升了一个台阶能用上最新版的 vLLM能跑新版本的 FlashAttention也能从 PyTorch 官方直接拿到 cu128 的轮子不需要自己再折腾编译。对于要在这个领域持续深耕的人来说这笔账是划算的。更重要的是掌握了多版本共存这套方法后以后 CUDA 再升级我就不需要像第一次那样诚惶诚恐了。思路一以贯之驱动先行、runfile 共存、软链接切换、环境变量统一指向软链接。这套方法论同样适用于从 12.8 升级到 CUDA 13 甚至更远的未来。最后说一个我自己总结的小技巧升级完别急着把所有旧项目都切到新版本。让老项目在新版本和老版本之间各跑一遍对比确认没有精度和性能回退之后再逐步迁移。这一步虽然多花点时间但能避免很多“升级成功但项目报废”的尴尬局面。