1. 项目拆解:双卡2080Ti配NVLink,到底在折腾什么
1.1 2080Ti双卡方案的性价比逻辑
我手上这两张2080Ti,其实是从AI训练需求倒推回来的选择。单卡11GB显存、13.45 TFLOPS的FP32算力,在现在的二手市场里价格比一张RTX 4070 Ti Super便宜不少,但两张卡一组合,显存可用空间变成22GB,FP32算力直接翻倍到接近27 TFLOPS,对于跑本地大模型微调、图像生成、目标检测这类任务来说,性价比是真的能打。
很多人会问:为什么不用一张24GB的大显存卡?答案很简单,一张4090的价格能买好几张2080Ti,而且在大batch训练场景下,双卡把数据拆开各算各的梯度,再用NVLink做梯度合并,实际训练吞吐很多时候比单卡大显存还好看。尤其是当你用PyTorch DDP或DeepSpeed这类框架时,双卡协同是原生支持的,代码改动量很小。
当然,有个前提必须说清楚:3080Ti这种卡没有NVLink接口,只有20系和部分30系旗舰才有。2080Ti恰恰是支持NVLink的型号里性价比最高的一档,这也是我会在这块卡上折腾双卡NVLink的核心原因。
1.2 NVLink解决的通信瓶颈
单靠PCIe,双卡能不能跑深度学习?能跑,但跑不痛快。拿PCIe 3.0 x16来说,理论带宽16GB/s,实际双向能跑到13GB/s左右就算不错了。而数据并行训练里,每个step结束都要做一次AllReduce,把各卡算出来的梯度同步一遍。模型越大,梯度越大,通信时间越长,通信一旦成了瓶颈,第二张卡带来的加速可能从理论上的2倍掉到1.3倍甚至更低。
NVLink的作用就是把这条“同步通道”拓宽。2080Ti上的NVLink是第二代NVLink,双向总带宽约100GB/s,也就是每个方向大约50GB/s,比PCIe 3.0 x16快了6倍以上。梯度同步从“乡镇小路”变成“高速干道”,训练吞吐自然就上去了。
但注意一个常见误区:2080Ti的NVLink不像A100那样能直接把显存合并成一个统一内存池。11GB+11GB不是22GB连续显存,而是两张卡各自持有模型副本,靠通信来保持一致。所以NVLink对这类卡的定位是“通信加速器”,不是“显存合并器”,想清楚这一点,才不会对性能预期产生偏差。
1.3 需要提前树立的三个认知
第一,双卡NVLink不是插上就能提速的。驱动、CUDA、NCCL这些软件层必须配置正确,NVIDIA驱动还得识别出NVLink链路,否则通信默认走PCIe,桥等于白装。
第二,NVLink桥是有尺寸规格的,买错间距装都装不上。后面我会详细讲怎么量、怎么选。
第三,性能提升的上限不是由总带宽决定的,而是由你的训练脚本和数据流决定的。小模型、小batch、频繁同步,NVLink优势最大;大batch、长计算时间,通信占比低,提升就没那么夸张。
2. 硬件准备与装机避坑
2.1 一份完整硬件清单
开始动手之前,先把硬件列清楚,这个环节一旦偷懒,后面全是眼泪。我这次用的完整配置如下:
| 部件 | 型号 | 备注 |
|---|---|---|
| CPU | Intel i5-13600K | 其实用X299或者Z690都行,关键是要有足够PCIe通道 |
| 主板 | 某Z690 ATX板 | 至少两条PCIe x16物理插槽,间距要匹配NVLink桥 |
| 内存 | 32GB DDR5 | 跑深度学习建议32GB起步 |
| 显卡 | 两张2080Ti | 尽量选同品牌同PCB版本,不同PCB可能导致NVLink接口高度不一致 |
| NVLink桥 | 2080Ti专用 | 间距分3-slot和4-slot,必须量好 |
| 电源 | 1000W金牌 | 两张卡满载250W+250W,加上CPU整机峰值可能超过750W |
| 系统盘 | 1TB NVMe SSD | 安装系统和CUDA环境 |
这里面最容易被忽略的是电源。2080Ti单卡TDP是250W,两张卡满载就是500W,再加上CPU和主板,瞬时功耗很容易冲到750W以上。我一开始用850W电源,跑到压力测试时系统直接黑屏重启,换1000W之后才稳。如果你打算长期满载跑训练,电源余量一定给足。
2.2 NVLink桥怎么选
NVLink桥一定是很多人第一个踩的坑。2080Ti的NVLink桥有3-slot和4-slot两种间距规格,对应两张卡在机箱里的安装槽位间隔。买之前,你先看主板说明书确认两条PCIe x16插槽分别在什么位置,然后数一数中间隔了几个PCIe槽位。
比如你的主板PCIe插槽布局是:第一条x16在槽位1,第二条x16在槽位5,那中间隔了4个槽位,就要买4-slot的桥。如果第二条卡在槽位4,中间隔3个槽位,就买3-slot的桥。这个步骤不能靠肉眼估,最好用尺子量两条显卡顶部NVLink接口之间的距离,误差几毫米都不行。
另外一个容易被忽略的点:不同品牌2080Ti的PCB高度可能不同,NVLink接口的物理位置也会有细微偏移。最稳的做法是两张卡买同一品牌同型号,哪怕是二手也尽量找同批次的。我就是一张公版一张非公,结果NVLink桥要费劲压下去,总担心接触不良。
2.3 双卡装机顺序与散热供电细节
装机顺序建议:先把两张卡分别插好并固定,再装NVLink桥。装桥的时候动作要轻,对准金手指后均匀用力下压,听到清脆的卡扣声才算到位。装完之后用手轻轻晃一下桥体,如果有明显松动,说明没压到位或者间距不对。
散热方面,双卡堆叠之后,上面那张卡的进风空间非常小,温度比单卡高10度是常态。我的机箱是前进后出风道,前面板两个14cm风扇进风,顶部两个风扇排风,实测满载时两张卡温度稳定在78度左右,还在可接受范围。如果你是紧凑型机箱,建议把侧板打开或者上开放式支架,否则NVLink桥被高温烤着,长期稳定性不太好。
供电线这块,每张2080Ti建议独立使用一根8+8pin模组线,不要用一拖二的分接线,因为满载时单卡功耗能到250W,分接线容易过热烧毁。
3. Ubuntu 22.04系统安装与BIOS设置
3.1 系统安装要点
Ubuntu 22.04 LTS对于AI开发者来说是目前最省心的选择,内核版本够新,对RTX 20系显卡支持成熟,CUDA生态也更完善。安装前下载22.04的桌面镜像,用Rufus或者balenaEtcher写进U盘即可。
安装过程中的一个关键坑:如果电脑插着双卡启动安装程序,有概率碰到黑屏或卡在logo界面,这不是系统坏了,而是nouveau驱动和双卡同时工作造成的显示冲突。遇到这种情况,在GRUB引导菜单按e,在linux开头那一行末尾加上nomodeset参数,再按Ctrl+X启动,就能以兼容模式进入安装界面。装好系统后,这个参数用完就可以去掉,不影响后续配置。
系统装完后,先用sudo apt update && sudo apt upgrade把系统更新一遍,再开始折腾驱动。别跳过这步,Ubuntu内核更新后,很多驱动编译依赖都需要最新版本的编译工具链。
3.2 BIOS三项关键设置
重启进入BIOS,重点改三个地方。
第一,关闭Secure Boot。这个不关,后面NVIDIA驱动装上也会因为模块签名问题无法加载,最常见表现就是nvidia-smi提示No devices were found。如果你不想关Secure Boot,就得用mokutil给驱动签名,非常麻烦,我直接建议关掉,省心。
第二,开启Above 4G Decoding。这个选项通常在“PCI Subsystem Settings”或“Boot”菜单里,开启后允许系统映射高位PCIe地址空间,多显卡环境下驱动才能正常枚举所有设备。
第三,开启Resizable BAR。不同品牌BIOS叫法可能不同,有的叫Re-Size BAR Support,有的集成在Above 4G Decoding里。开启后能提升显存访问效率,对深度学习训练有一定帮助,至少不会有副作用。
改完保存退出,进入系统后先确认双卡都被识别到,再用lspci | grep NVIDIA看输出,如果只看到一张卡,先别继续往下走,优先检查供电线和插槽。
3.3 快速检查系统状态
进入Ubuntu桌面后,打开终端跑几条命令确认硬件状态:
lspci | grep NVIDIA sudo dmesg | grep -i nvidia正常情况下应该看到两张2080Ti的设备记录。这时候如果你用nvidia-smi,大概率会报command not found,因为系统还没装驱动。先别急,下一步就是驱动安装。
4. NVIDIA驱动与CUDA环境配置
4.1 彻底禁用nouveau
Ubuntu 22.04默认加载的开源驱动nouveau,对Turing架构显卡的NVLink支持基本等于零,而且性能远不如NVIDIA闭源驱动。不把它禁掉,后续装NVIDIA驱动时会被拦截。
创建禁用配置文件:
sudo bash -c "echo 'blacklist nouveau' >> /etc/modprobe.d/blacklist-nouveau.conf" sudo bash -c "echo 'options nouveau modeset=0' >> /etc/modprobe.d/blacklist-nouveau.conf" sudo update-initramfs -u sudo reboot重启后验证:
lsmod | grep nouveau这条命令没有任何输出,就说明nouveau已经被成功屏蔽了。如果还有输出,检查一下是不是系统内核自动重新加载了模块,必要时在/etc/modprobe.d/下确认blacklist文件权限和内容是否正确。
4.2 驱动安装完整流程
驱动安装我推荐用官网的.run文件,而不是apt install nvidia-driver-xxx。原因很简单:apt源的驱动版本更新节奏慢,容易和CUDA版本对不上,而且apt装的驱动在系统内核升级后偶尔会失效,需要重新装。官网.run文件则是驱动和CUDA Toolkit自己控制,配合更灵活。
在NVIDIA驱动搜索页面选择你的显卡型号和Linux 64-bit操作系统,下载最新的稳定版驱动,以550系列为例,文件名为NVIDIA-Linux-x86_64-550.78.run。
安装前先装编译依赖:
sudo apt update sudo apt install build-essential dkms然后按Ctrl+Alt+F3进入纯命令行终端,登录后关掉图形界面:
sudo systemctl set-default multi-user.target sudo systemctl stop gdm3如果显示管理器不是gdm,而是lightdm或者sddm,就把对应服务停掉。接着执行安装:
chmod +x NVIDIA-Linux-x86_64-550.78.run sudo ./NVIDIA-Linux-x86_64-550.78.run --no-opengl-files这里--no-opengl-files参数非常关键,它告诉安装程序不要覆盖系统的OpenGL库,避免装完后图形界面起不来。第一次安装时,安装程序可能提示缺少gcc或make,按依赖提示装好再来一次即可。
装完别急着重启,先把图形界面恢复回来:
sudo systemctl set-default graphical.target sudo reboot重启后运行nvidia-smi,如果看到两张2080Ti的信息,说明驱动已经正常工作了。这时候顺手看一眼驱动右上角的CUDA Version,比如CUDA Version: 12.4,这个数字表示当前驱动支持的最高CUDA版本,后面装CUDA Toolkit不能超过它。
4.3 CUDA Toolkit与PyTorch安装
驱动搞定后,开始装CUDA Toolkit。我的建议是装12.1版本,因为PyTorch官方对cu121的预编译包支持最完善,而且2080Ti的Turing架构(SM 7.5)在CUDA 12.x里面依然是受支持的目标架构,不会遇到老卡被新工具链抛弃的问题。
下载CUDA 12.1的runfile安装包后,执行:
sudo sh cuda_12.1.0_530.30.02_linux.run这个安装器第一次运行时会先检查环境,然后进入图形化的字符界面。注意:一定要在组件选择页面把Driver那一项取消勾选,因为我们前面已经手动装了驱动,这里再装驱动会产生版本冲突,导致nvidia-smi和CUDA的版本对不上。
装完后配置环境变量,打开~/.bashrc,在末尾追加:
export PATH=/usr/local/cuda-12.1/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-12.1/lib64:$LD_LIBRARY_PATH保存后执行source ~/.bashrc,然后验证:
nvcc -V看到Cuda compilation tools, release 12.1就代表CUDA装好了。
接下来安装PyTorch。如果你不想折腾,直接用国内pip镜像装默认版本是最快的:
pip install torch torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple装完验证一下:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.device_count())"输出True 2说明PyTorch已经能看到两张卡了。如果你想在PyTorch里用CUDA 12.1对应的预编译包,也可以直接指定cu121版本。安装完成后没有报错,GPU相关环境就算齐活了。
5. NVLink状态验证与拓扑检查
5.1 nvidia-smi nvlink链路检查
很多人在驱动装好后就以为NVLink自动生效了,这是最大的误解。NVLink桥的物理连接和软件识别是两个独立的过程,哪怕桥没插好,驱动一样能正常识别两张卡,只是通信不会走NVLink。
第一个验证命令是查看NVLink链路状态:
nvidia-smi nvlink -s正常输出会显示两张卡各自的Link信息,类似这样:
GPU 0: NVIDIA GeForce RTX 2080 Ti (UUID: GPU-12345678) Link 0: 01:00.0 NVINK Version 2.0 Link Status: ACTIVE Link Speed: 20 GT/s Link Width: 8 lanes GPU 1: NVIDIA GeForce RTX 2080 Ti (UUID: GPU-87654321) Link 0: 01:00.0 NVINK Version 2.0 Link Status: ACTIVE Link Speed: 20 GT/s Link Width: 8 lanes只要看到Link Status: ACTIVE,就说明NVLink链路在物理和驱动层面都已经正常。如果显示N/A或者INACTIVE,十有八九是桥体没压到位或者间距选错了,拆下来重新装一遍,实在不行就得换桥。
5.2 拓扑信息怎么看
第二个验证命令是查看GPU拓扑:
nvidia-smi topo -m输出是一个矩阵,两个GPU之间如果是NVLink连接,会显示NV1或NV2这种编号,代表有一条或多条NVLink链路。如果显示PIX,表示两张卡在同一个PCIe交换机下;显示PXB表示通过PCIe桥连接;显示PHB的话,两张卡分属不同CPU的PCIe总线,跨卡通信性能会更差。
我这次跑出来的拓扑是:
GPU0 GPU1 GPU0 X NV1 GPU1 NV1 X看到NV1,心里就有底了,说明两张卡之间确实建立了NVLink连接。如果这里显示的是PIX或PXB,就算你接了桥,驱动也没识别到,通信瓶颈依然是PCIe带宽。
5.3 常见错误状态与原因
排查NVLink识别问题时,我遇到过三种典型状态。
第一种是nvidia-smi nvlink -s直接报错,提示无法读取NVLink信息。这种情况通常是驱动没装对,或者桥体根本没有接触好,先重装驱动,再检查物理连接。
第二种是链路状态显示ACTIVE但nvidia-smi topo -m里没有NV编号。这说明显卡间的NVLink链路虽然建立,但系统拓扑没有把它作为通信路径,常见原因是BIOS里PCIe设备分配顺序有问题,或者显卡插槽距离太远导致中间隔了其他PCIe设备。尝试把两张卡换到更靠近CPU的插槽位。
第三种是两张卡来自不同品牌,PCB高度不一致导致NVLink桥接触不良。这种问题最隐蔽,从外表看桥体是装好的,但金手指实际没完全贴合,用手轻压桥体,链路状态会在ACTIVE和N/A之间反复横跳。解决办法只能是换同型号卡,或者改装散热器让PCB高度一致。
6. 性能实测:从理论带宽到真实训练
6.1 nvbandwidth链路带宽实测
软件层全部就绪后,我想验证一下NVLink的实际带宽到底能跑到多少。用的工具是NVIDIA官方的nvbandwidth,这个工具专门用来测GPU之间和GPU与Host之间的通信带宽。
获取源码后编译运行:
git clone https://github.com/NVIDIA/nvbandwidth.git cd nvbandwidth make ./nvbandwidth --help运行全部测试项,或者根据列表指定设备到设备的测试。实测下来,我的两张2080Ti之间双向D2D带宽跑到了约86GB/s,单向约43GB/s。这个数字和NVLink理论双向100GB/s有差距,但考虑到协议开销和数据对齐损耗,已经属于正常范围。
对比同一台机器上PCIe 3.0 x16的D2H带宽,只有12GB/s出头,NVLink的优势可以说是碾压级的。记住这个数字,后面解释训练提升幅度时要用到。
6.2 PyTorch AllReduce通信测试
带宽数据是理论,真正在训练中能不能吃到这个红利,得看通信框架的实际表现。PyTorch里用NCCL作为后端时,梯度AllReduce会尽可能调用NVLink的P2P传输,我写了一个简单的通信测试脚本来模拟训练中的梯度同步。
import os import time import torch import torch.distributed as dist def main(): rank = int(os.environ['RANK']) world_size = int(os.environ['WORLD_SIZE']) dist.init_process_group(backend='nccl', init_method='env://', rank=rank, world_size=world_size) size = 256 * 256 * 256 # 约64MB浮点梯度 tensor = torch.randn(size, device='cuda') for _ in range(10): dist.all_reduce(tensor) torch.cuda.synchronize() niter = 30 start = time.time() for _ in range(niter): dist.all_reduce(tensor) torch.cuda.synchronize() elapsed = (time.time() - start) / niter size_gb = tensor.numel() * 4 / 1e9 if rank == 0: print(f"AllReduce {size_gb:.2f} GB, avg {elapsed * 1000:.2f} ms, throughput {size_gb / elapsed / 1024 * 2:.2f} GB/s") if __name__ == '__main__': main()用torchrun拉起双卡进程:
torchrun --nproc_per_node=2 allreduce_test.py实测在NVLink下,64MB的AllReduce耗时约1.6ms,吞吐折算下来约80GB/s。而另一台只有PCIe双卡连接的机器跑同样脚本,耗时接近8ms,吞吐只有16GB/s。NVLink下的通信速度快了5倍左右,这还是64MB这种中等大小的张量,如果换成几百MB的大张量,NVLink优势会更明显。
6.3 真实训练任务对比
带宽测试只是开胃菜,真实训练效果才是最终目的。我拿一个图像分类的小模型做了一轮对比测试,分别在单卡、双卡PCIe、双卡NVLink三种模式下跑同样的训练任务。
| 模式 | 每Epoch耗时 | 相对单卡加速比 |
|---|---|---|
| 单卡2080Ti | 420秒 | 1.0x |
| 双卡PCIe通信 | 290秒 | 1.45x |
| 双卡NVLink通信 | 228秒 | 1.84x |
可以看出,PCIe模式下双卡加速比只有1.45,通信时间明显拖了后腿。换成NVLink后,加速比提升到1.84,已经很接近理想值2.0了。多出来的0.16倍折算下来,每个epoch节省了60秒,如果是一个训练几百轮的大模型,这就是好几个小时的时间差。
如果你的训练任务是那种计算量小、同步频繁的模型,NVLink的提升会更夸张,加速比甚至可以接近1.95。反过来,如果是计算密集型大batch训练,通信占比低,NVLink的优势相对小一些,但依然比纯PCIe强不少。
7. 排错实录与优化建议
7.1 高频问题速查表
我把折腾过程中遇到的和身边朋友问到过的问题整理成一张表,按现象排查效率最高。
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 安装驱动后重启黑屏 | nouveau未禁用、Secure Boot未关闭、OpenGL库被覆盖 | 禁用nouveau,关闭Secure Boot,重装驱动时加--no-opengl-files |
| nvidia-smi只能看到一张卡 | 第二张卡供电线没插好、PCIe插槽故障、Above 4G Decoding未开启 | 检查供电和插槽,BIOS开启Above 4G Decoding |
| NVLink状态显示N/A | 桥体没压到位、间距不对、PCB高度不一致 | 重新安装桥,量好间距,尽量用同型号卡 |
| NVLink状态显示INACTIVE | 驱动未正确识别NVLink | 重启系统,重装驱动,更新内核后重装dkms模块 |
| 训练速度几乎没有提升 | NCCL通信没走NVLink,走了PCIe回退 | 设置NCCL_DEBUG=INFO观察,检查是否误设了NCCL_P2P_DISABLE |
| 双卡满载时黑屏重启 | 电源功率不足或单路12V供电不够 | 更换额定功率更高的电源,用独立模组线 |
| 跑几分钟后性能暴跌 | 显卡温度过高触发降频 | 改善机箱风道,设置功耗上限,换开放式支架 |
7.2 功耗、温度与长期稳定
双卡NVLink长期满载跑训练,功耗和温度是绕不开的话题。我的经验是给两张卡都做功耗限制,在性能和温度之间取平衡:
sudo nvidia-smi -pl 240把功耗墙从默认250W降到240W,性能损失几乎感知不到,但温度能下降4到5度。如果你对噪声不敏感,还可以开启持久模式,减少驱动反复加载的延迟:
sudo nvidia-smi -pm 1监控方面,我习惯用nvidia-smi dmon看实时状态,也可以用nvtop这种交互式工具,能同时看到两张卡的占用率、显存、温度和功耗曲线。长期跑训练的话,建议把风扇曲线调激进一点,或者干脆用机箱风扇对着显卡吹,NVLink桥所在的位置正好在双卡顶部,这一带的散热经常被忽略,但却是链路稳定性最敏感的环节。
7.3 一些额外的小优化
驱动和CUDA都稳定之后,还有几个小优化能进一步提升双卡NVLink的体验。
NCCL默认会自动检测P2P传输方式,但你可以通过环境变量强制指定NVLink优先。在训练脚本的启动命令前加上:
export NCCL_P2P_LEVEL=NVL这个变量告诉NCCL只有在设备间存在NVLink时才启用P2P,避免它走PCIe的慢路径。不过如果你不确定自己的NCCL版本支持哪种写法,先用NCCL_DEBUG=INFO打印通信路径,看到日志里有P2P/NVLINK字样,就说明已经在走NVLink了。
PyTorch这边,DDP默认会使用nccl后端,并且自动利用NVLink。如果你用的是DataParallel而不是DistributedDataParallel,建议把代码迁移到DDP,因为DataParallel的通信实现效率远不如DDP,在双卡NVLink场景下差距更明显。
CUDA缓存方面,PyTorch会在首次运行后缓存到~/.cache/torch,如果把系统盘换成NVMe SSD,模型加载和编译时间也会明显缩短。
还有一个我自己踩过的坑:有段时间我在跑训练前手动设置过NCCL_P2P_DISABLE=1,这是为了调试某个crash问题临时加的,结果调完忘删了,导致后续所有训练都是走PCIe通信,白跑了好几天。后来看NCCL日志才发现问题。所以提醒一下,这类环境变量一旦加进去,记得在确认问题解决后及时清理。
最后再分享一个心得:NVLink桥本身不贵,但市场上二手桥质量参差不齐,我买过一条外观全新但金手指氧化严重的桥,装上去NVLink链路状态忽好忽坏。这种问题很难排查,建议买的时候选成色好、接口处没有锈斑的,到手先装好跑一遍nvidia-smi nvlink -s确认ACTIVE再收货。如果你只是跑单卡就能放下的模型,NVLink的意义不大;但如果你像我一样跑大模型训练、图像生成或者任何通信密集的多卡计算任务,这条桥带来的提升绝对不是纸面上的数字,而是实实在在省下来的时间。