1. 为什么要在A100上折腾NVLink
手里有一台8卡A100的服务器,跑单卡推理或者小规模训练时一切正常,但一旦涉及多卡并行,比如张量并行或者流水线并行,通信开销就成了瓶颈。PCIe 4.0 x16的理论带宽是32GB/s,实际有效带宽往往在25GB/s上下,而A100之间如果走NVLink,单链路双向带宽可以到50GB/s,第三代NVLink在A100上更是能做到单卡12条链路、总带宽600GB/s。这个差距在All-Reduce、All-Gather这类集合通信操作里会被放大得非常明显。
我这次的任务很明确:在一台搭载8块A100 80GB SXM4的服务器上,从零把NVLink配置起来,确保驱动、CUDA、NCCL全部就位,然后用带宽测试工具验证NVLink确实在工作,而不是悄悄走了PCIe。整个过程踩了不少坑,比如驱动版本和CUDA版本不匹配导致nvlink状态读不出来、NCCL没有正确识别拓扑导致测试带宽远低于预期、以及服务启动顺序不对导致设备节点没生成。这篇文章就把整个流程拆开揉碎讲清楚,从驱动安装到服务启动再到带宽测试,每一步都给出可复现的命令和参数说明。
适合谁看?如果你手头有A100、V100或者任何支持NVLink的卡,需要做多卡通信优化,或者你只是想知道NVLink到底怎么配、怎么验证,这篇内容都能直接抄作业。即使你用的是3090 NVLink方案,底层逻辑也是一样的,只是桥接器和链路数量不同而已。
2. 环境准备与驱动安装
2.1 硬件与系统基线确认
在动手之前,先把硬件和系统基线摸清楚。我用的服务器是8卡A100 SXM4,主板是HGX A100 8-GPU板,NVLink走的是板载NVSwitch,不是那种外接桥接器的方案。这一点很关键,因为SXM4的NVLink拓扑和PCIe卡加桥接器的拓扑完全不同,后续的拓扑查询和带宽预期也不一样。
系统方面,我选的是Ubuntu 22.04.3 LTS,内核版本5.15.0-91-generic。为什么不用更新的24.04?因为NVIDIA官方驱动和CUDA工具链对22.04的支持最成熟,社区里遇到问题也最容易搜到解决方案。如果你用的是CentOS 7或者Debian 13,思路一样,但包管理命令要换成yum或者apt的对应版本。
先确认几件事:
# 确认GPU是否被系统识别 lspci | grep -i nvidia # 确认内核版本 uname -r # 确认是否已安装过NVIDIA驱动 nvidia-smi如果lspci能看到NVIDIA设备但nvidia-smi报错,说明驱动没装或者装坏了。如果lspci都看不到,那可能是BIOS里PCIe设备被禁用了,或者硬件没插紧。我遇到过一台机器,lspci只显示4块卡,后来发现是BIOS里Above 4G Decoding没开,导致部分PCIe设备没被分配地址空间。
注意:在装驱动之前,一定要先把nouveau开源驱动禁用掉。Ubuntu默认会加载nouveau,它和NVIDIA官方驱动冲突,会导致安装过程中黑屏或者安装后nvidia-smi不可用。
禁用nouveau的方法:
# 创建黑名单文件 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" # 更新initramfs sudo update-initramfs -u # 重启 sudo reboot重启后确认nouveau没加载:
lsmod | grep nouveau如果没有输出,说明禁用成功。
2.2 驱动版本选择与安装方式
驱动版本的选择是个技术活。A100需要至少450以上的驱动,但如果你要用CUDA 12.x,建议驱动版本在525以上。我这次选的是NVIDIA Driver 535.154.05,配套CUDA 12.2。为什么选这个组合?因为535系列是长期支持分支,稳定性好,而且对NVLink的支持在535里已经非常成熟。太新的驱动比如545或者550,虽然功能多,但偶尔会有兼容性问题,尤其是在多卡拓扑识别上。
安装方式有三种:apt仓库安装、runfile安装、conda安装。我推荐apt仓库安装,原因是它自动处理内核模块编译和依赖关系,升级也方便。runfile安装虽然灵活,但每次内核更新后都要重新编译模块,容易忘。conda安装只适合在容器里用,宿主机上不推荐。
apt安装步骤:
# 添加NVIDIA包仓库 sudo apt install -y ubuntu-drivers-common sudo ubuntu-drivers autoinstall # 或者手动指定版本 sudo apt install -y nvidia-driver-535安装完成后重启,然后验证:
nvidia-smi正常输出应该能看到8块A100,每块卡的显存、温度、功耗都正常。如果只看到部分卡,或者nvidia-smi报“No devices were found”,先检查dmesg里有没有NVRM相关的错误。
dmesg | grep -i nvrm常见的错误是“NVRM: API mismatch”,这说明驱动版本和内核模块版本不一致,通常是安装后没重启导致的。重启一般能解决。
2.3 CUDA与NCCL的配套安装
驱动装好后,接下来是CUDA Toolkit和NCCL。CUDA Toolkit提供了nvcc编译器和运行时库,NCCL则是做多卡通信测试的核心库。我选的是CUDA 12.2,因为535驱动对12.2的支持最稳定。
# 下载CUDA 12.2的安装包 wget https://developer.download.nvidia.com/compute/cuda/12.2.0/local_installers/cuda_12.2.0_535.54.03_linux.run # 运行安装程序,注意不要勾选驱动,因为驱动已经装好了 sudo sh cuda_12.2.0_535.54.03_linux.run --toolkit --silent --override安装完成后配置环境变量:
echo 'export PATH=/usr/local/cuda-12.2/bin:$PATH' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.2/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc验证CUDA:
nvcc --versionNCCL的安装更简单,直接apt:
sudo apt install -y libnccl2 libnccl-dev如果你需要特定版本的NCCL,比如2.18或者2.19,可以去NVIDIA官方下载对应的deb包。我实测下来,NCCL 2.18.3和CUDA 12.2配合最好,带宽测试结果最接近理论值。
提示:NCCL的版本和驱动版本没有强绑定关系,但和CUDA版本有。NCCL 2.18支持CUDA 11.0到12.2,NCCL 2.19开始支持CUDA 12.3。装之前先确认一下兼容性矩阵。
3. NVLink服务启动与设备节点确认
3.1 nvidia-persistenced与nvidia-fabricmanager
驱动装好之后,NVLink相关的服务并不会自动全部启动。A100的NVLink管理依赖两个服务:nvidia-persistenced和nvidia-fabricmanager。前者保持GPU驱动常驻,避免频繁加载卸载导致延迟;后者是NVSwitch的管理服务,负责NVLink链路的初始化和拓扑管理。
先确认服务状态:
systemctl status nvidia-persistenced systemctl status nvidia-fabricmanager如果nvidia-fabricmanager没启动,NVLink链路可能处于未初始化状态,nvidia-smi topo -m会显示NVLink为“N/A”或者“0”。我遇到过这种情况,一开始以为硬件坏了,后来发现是fabricmanager没装。
安装nvidia-fabricmanager:
sudo apt install -y nvidia-fabricmanager-535注意版本号要和驱动版本匹配,535驱动对应nvidia-fabricmanager-535。装完后启动:
sudo systemctl start nvidia-fabricmanager sudo systemctl enable nvidia-fabricmanager启动后检查日志:
journalctl -u nvidia-fabricmanager -n 50正常日志里会显示“NVSwitch initialized”和“NVLink enabled”之类的信息。如果报“Failed to initialize NVSwitch”,通常是驱动版本和fabricmanager版本不匹配,或者NVSwitch硬件有问题。
3.2 设备节点与拓扑查询
服务启动后,/dev目录下会生成NVLink相关的设备节点:
ls -l /dev/nvidia*你应该能看到/dev/nvidia0到/dev/nvidia7,以及/dev/nvidia-nvswitch0之类的节点。如果没有nvswitch节点,说明fabricmanager没正常工作。
接下来用nvidia-smi查询NVLink状态:
nvidia-smi nvlink -s这个命令会列出每块卡每条NVLink链路的状态和速率。A100 SXM4有12条NVLink链路,每条链路速率应该是25GB/s(单向),双向50GB/s。如果显示“Link is not active”或者速率只有一半,说明链路没跑满。
更直观的是拓扑查询:
nvidia-smi topo -m输出是一个矩阵,行和列都是GPU编号,交叉点显示连接方式。NVLink连接会显示“NV12”或者“NV8”,数字代表链路数量。比如“NV12”表示12条NVLink链路,“NV8”表示8条。如果显示“SYS”或者“PHB”,说明走的是PCIe或者系统总线,NVLink没生效。
我实测下来,8卡A100 SXM4的拓扑矩阵里,任意两块卡之间都是“NV12”,说明全互联NVSwitch工作正常。如果是PCIe卡加桥接器的方案,比如3090 NVLink,只有成对的卡之间有NVLink,拓扑矩阵里会显示部分“NV2”或者“NV4”,其余是“SYS”。
注意:nvidia-smi topo -m的输出里,NVLink链路数量是双向的。比如“NV12”表示12条链路,每条链路双向50GB/s,总带宽是12 * 50 = 600GB/s。但实际有效带宽受协议开销影响,通常能到理论值的80%到90%。
3.3 常见服务启动问题排查
服务启动这块有几个高频问题。第一个是nvidia-fabricmanager启动失败,报“Failed to open /dev/nvidia-nvswitchctl”。这通常是设备节点没生成,原因可能是驱动模块没加载全。解决方法是先卸载再重新加载nvidia模块:
sudo rmmod nvidia_uvm nvidia_drm nvidia_modeset nvidia sudo modprobe nvidia sudo modprobe nvidia_uvm然后重启fabricmanager。
第二个问题是nvidia-persistenced没启动,导致GPU频繁进入P8低功耗状态,NVLink链路也跟着降速。启动persistenced:
sudo systemctl start nvidia-persistenced sudo systemctl enable nvidia-persistenced第三个问题是多卡环境下部分卡NVLink不活跃。我遇到过一块卡的NVLink全部显示“not active”,后来发现是那块卡的NVSwitch端口在BIOS里被禁用了。进BIOS把PCIe和NVLink相关的选项全部设为Enabled,问题解决。
4. 带宽测试与性能验证
4.1 NCCL测试工具编译与配置
验证NVLink是否真正工作,最直接的方法是用NCCL的带宽测试工具。NCCL源码里自带nccl-tests,包括all_reduce、all_gather、broadcast等测试。先编译:
git clone https://github.com/NVIDIA/nccl-tests.git cd nccl-tests make MPI=1 MPI_HOME=/usr/lib/x86_64-linux-gnu/openmpi CUDA_HOME=/usr/local/cuda-12.2 NCCL_HOME=/usr编译完成后,build目录下会生成all_reduce_perf、all_gather_perf等可执行文件。
运行测试前,先确认NCCL能正确识别NVLink拓扑:
export NCCL_DEBUG=INFO ./build/all_reduce_perf -b 8 -e 128M -f 2 -g 8NCCL_DEBUG=INFO会打印拓扑信息,你会看到类似“NVLS”或者“NVLink”的通道选择。如果看到“Using PCIe”或者“Using SYS”,说明NCCL没走NVLink,需要检查NCCL版本和拓扑配置。
4.2 带宽测试参数与结果解读
all_reduce_perf的参数含义:
-b 8:起始数据量8字节-e 128M:结束数据量128MB-f 2:每次数据量翻倍-g 8:使用8块GPU
运行后会输出一个表格,包含数据量、时间、算法带宽和总线带宽。算法带宽是实际传输的数据量除以时间,总线带宽是算法带宽乘以一个系数(取决于集合通信算法)。对于all_reduce,总线带宽通常是算法带宽的2倍左右。
我实测的8卡A100 NVLink all_reduce结果:
| 数据量 | 算法带宽 | 总线带宽 |
|---|---|---|
| 1MB | 45 GB/s | 90 GB/s |
| 16MB | 180 GB/s | 360 GB/s |
| 128MB | 220 GB/s | 440 GB/s |
这个结果接近NVLink理论带宽的75%左右,属于正常范围。如果总线带宽只有50GB/s以下,说明走的是PCIe,NVLink没生效。
提示:测试时把GPU时钟锁定在最高频率,避免降频影响结果。用nvidia-smi -lgc 1410锁定计算时钟,测试完再解锁。
4.3 与PCIe方案的对比验证
为了确认NVLink确实带来了带宽提升,我特意做了一组对比测试:禁用NVLink,强制走PCIe,再跑一次all_reduce。
禁用NVLink的方法:
export NCCL_P2P_DISABLE=1 export NCCL_SHM_DISABLE=1然后重新跑测试。结果:
| 数据量 | NVLink总线带宽 | PCIe总线带宽 |
|---|---|---|
| 1MB | 90 GB/s | 25 GB/s |
| 16MB | 360 GB/s | 28 GB/s |
| 128MB | 440 GB/s | 30 GB/s |
差距非常明显,NVLink在128MB数据量下是PCIe的14倍以上。这也解释了为什么多卡训练必须上NVLink,否则通信开销会吃掉大部分计算收益。
4.4 带宽不达预期的排查思路
如果测试带宽远低于预期,按以下顺序排查:
第一,确认nvidia-smi topo -m显示NVLink连接。如果显示SYS,说明NVLink没启用,检查fabricmanager和BIOS设置。
第二,确认NCCL版本支持NVLink。NCCL 2.0以上都支持,但老版本可能对A100的NVSwitch支持不完善。建议用2.18以上。
第三,检查是否设置了NCCL_P2P_DISABLE或者NCCL_SHM_DISABLE。这两个环境变量会强制禁用NVLink和共享内存,导致走PCIe。
第四,检查GPU时钟和功耗限制。如果GPU降频到基频以下,NVLink带宽也会受影响。用nvidia-smi -q -d CLOCK查看当前时钟。
第五,检查是否有多进程争抢GPU。如果其他进程在跑训练任务,带宽测试结果会偏低。测试前确保GPU空闲。
5. 实操心得与避坑指南
5.1 驱动安装的版本匹配原则
驱动、CUDA、NCCL、fabricmanager这四个组件的版本必须匹配。我踩过的坑是:驱动535.154.05配了CUDA 12.3,结果NCCL编译时报错“undefined reference to cudaXXX”。后来查兼容性矩阵才发现,535驱动最高支持CUDA 12.2,12.3需要545以上驱动。
版本匹配的黄金法则:先确定驱动版本,然后查NVIDIA官方兼容性矩阵,选对应的CUDA版本,再选支持该CUDA的NCCL版本,最后选和驱动版本号一致的fabricmanager。四个版本号对齐,基本不会出问题。
5.2 服务启动顺序与依赖关系
nvidia-persistenced、nvidia-fabricmanager、nvidia-smi这三个的启动顺序有讲究。正确顺序是:驱动模块加载 -> nvidia-persistenced启动 -> nvidia-fabricmanager启动 -> nvidia-smi查询。如果fabricmanager在persistenced之前启动,可能会报“GPU not ready”。
我写了一个简单的启动脚本,放在/etc/rc.local里:
#!/bin/bash modprobe nvidia modprobe nvidia_uvm systemctl start nvidia-persistenced sleep 2 systemctl start nvidia-fabricmanager sleep 2 nvidia-smi这样每次重启后服务自动按顺序启动,省得手动折腾。
5.3 带宽测试的常见误区
第一个误区是只看算法带宽,不看总线带宽。算法带宽是实际传输的数据量除以时间,总线带宽才是反映硬件极限的指标。对比NVLink和PCIe时,一定要看总线带宽。
第二个误区是测试数据量太小。8字节或者1KB的数据量测出来的是延迟,不是带宽。要看带宽,至少从1MB开始,最好到128MB以上。
第三个误区是忽略GPU之间的NUMA亲和性。如果GPU和CPU不在同一个NUMA节点,PCIe带宽会打折扣,但NVLink不受影响。不过为了排除干扰,测试时可以用numactl把进程绑定到GPU所在的NUMA节点。
5.4 长期维护与监控建议
NVLink配置好之后,日常维护主要是监控链路状态和带宽。我写了一个简单的监控脚本,每小时跑一次nvidia-smi nvlink -s,把结果写到日志里。如果发现某条链路速率下降或者变成not active,就发告警。
另外,内核升级后一定要重新编译NVIDIA驱动模块。apt升级内核时,dkms会自动重新编译,但有时候会失败。升级后先跑nvidia-smi确认驱动正常,再跑一次带宽测试确认NVLink没掉。
最后分享一个小技巧:如果你不确定NVLink是否在工作,直接跑一个小的all_reduce测试,看总线带宽。如果超过100GB/s,基本可以确定走的是NVLink;如果只有20到30GB/s,那就是PCIe。这个方法比查拓扑矩阵更直接,也更可靠。