做GPU集群的都知道,单机8卡跑模型的时候,NCCL AllReduce在PCIe/NVLink上基本没什么压力,但一旦扩展到多机节点,网络就会成为最大的短板。我见过不少项目,InfiniBand还没普及,所有网卡都是25G/100G以太网,这时候RoCEv2几乎是唯一能把GPU集合通信推到线速的答案。这篇文章就围绕RoCEv2网络里GPU集合通信、RDMA、以太网这三者的协作关系,把原理、配置、调优和常见坑一次讲透。
很多人一开始会把RoCEv2当成一个单纯的“网卡设置”,其实它是完整的数据中心协作体系:NCCL这些集合通信库负责把GPU显存里的数据组织成高效通信模式,RDMA负责绕过内核零拷贝传输,而以太网则承担最终承载与无损保障。这三层缺一不可,任何一层配置不对,最终表现就是训练速度上不去、GPU利用率像过山车。适合AI基础设施工程师、HPC运维、网络管理员以及对分布式训练感兴趣的人参考。
1. GPU集合通信的瓶颈与RoCEv2的登场
1.1 为什么多机GPU通信必须依赖RDMA
先算一笔账。假设8台服务器,每台8张A100,搞一个40B模型,切分后每个GPU需要交换的梯度数据量少说10GB量级。AllReduce过程中,每个GPU都要把自己的梯度分发给其他7台服务器,同时从别人那里收回72GB(8台×9GB)数据。如果所有流量都拥塞在一个TCP连接上,网卡就算有100Gbps,内核协议栈也会先“热死”。
传统TCP/IP传输的代价在哪里?数据从GPU显存拷贝到CPU内存,CPU再通过内核协议栈封装成TCP段,触发中断,调用网卡发送。接收端要经过解封装、CPU内存拷贝再拷入GPU显存。每一步都消耗CPU周期和内存带宽,而且延迟在几十微秒到上百微秒波动。对于集合通信这种海量小包的场景,CPU根本来不及处理,GPU只能干等。
RDMA(Remote Direct Memory Access)把协议栈装卸移出CPU,由网卡硬件完成。发送端网卡可以直接从GPU显存DMA数据,封装成RDMA报文;接收端网卡收到报文后,直接DMA写入GPU显存。双方都绕过了CPU和内核socket,延迟降到1~3微秒级别,CPU几乎可以全程不参与数据搬运。
但是RDMA只是传输方式,它需要具体载体。InfiniBand当然好,但它需要专用交换机、专用线缆,价格通常是同规格以太网的3倍以上。在预算有限、又想复用现有25G/100G以太网机架的场景,就必须在以太网上实现RDMA。RoCEv2就是这个中间解。
1.2 RoCEv2在IB与以太网之间找到了什么平衡
RoCE有两个版本。RoCEv1直接在以太网二层(EtherType 0x8915)上封装RDMA包,不能跨VLAN和路由器,只能在同一个二层广播域里玩。RoCEv2把RDMA报文封装进UDP/IP里,UDP目的端口固定为4791,这样它就可以像普通UDP包一样被三层路由转发,跨越多个交换机,甚至跨机房。同时保留了RDMA的用户态生态,应用不需要修改就能从IB迁移到RoCEv2。
RoCEv2的设计哲学是“用成熟的以太网基础设施,做不成熟的RDMA传输”。它跑在UDP上,意味着没有TCP的可靠重传、流量控制、拥塞控制,这些全部要交给底层的以太网机制和网卡固件来兜底。所以RoCEv2必须运行在无损以太网(Lossless Ethernet)上,也就是要启用优先级流控(PFC)和显式拥塞通知(ECN),否则一个丢包就会引发大量超时重传,性能瞬间腰斩。
这也解释了为什么RoCEv2的调优不是简单改网卡IP就能完事的。它要同时管好三个层面的协作:GPU集合通信库(如何生成流量)、RDMA网卡(如何发送/接收)、以太网交换机(如何转发和保障无丢包)。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RoCEv2的传输机制:以太网如何承接RDMA的“暴力”传输
2.1 报文封装与处理路径:从GPU显存到远端GPU显存
RoCEv2报文的实际封装顺序是:IB BTH(Base Transport Header,12字节) + UDP Header(8字节) + IP Header(20字节) + 以太网Header(14字节)。BTH里面包含操作码、目的QP号、PSN等RDMA状态信息。UDP包的目的端口必须是4791,这是IANA分配的RoCEv2标志,交换机可以依据这个端口识别RoCE流量并做专属QoS策略。
以典型的单边写(RDMA Write)为例:发送端NCCL库调用ibv_post_send,把指向GPU显存中某块缓冲区的WR提交给网卡。网卡从PCIe直接DMA读取数据,在硬件里填上BTH/UDP/IP/以太网头,按ARP/路由查找到目的MAC,发往交换机。交换机只按目的IP查表转发,根本不区分这是不是RDMA,只在队列调度上给高优先级。接收端网卡收到报文后,解析BTH里的目的QP号,找到已经注册好的内存区域,直接把数据DMA到对应GPU显存的物理地址。整个过程中,发送端和接收端的CPU除了提交/轮询队列之外,不做字节拷贝。
这里有个细节容易被忽略:RoCEv2的IP头部和普通IP包没有区别,所以它可以在启用ECMP的多路径网络里走不同链路。但是ECMP的哈希算法如果只看UDP端口,而所有RoCEv2报文的UDP端口都是4791,就会导致哈希不均衡,所有流量都堆到同一条链路上。后面调优部分我还会专门说这个问题。
2.2 无损网络的基石:PFC优先级流控与死锁问题
PFC(Priority-based Flow Control)定义了8个IEEE 802.1p优先级队列。交换机可以针对特定优先级队列发送PFC Pause帧,通知对端网卡暂停发送该优先级的数据,避免接收队列溢出丢包。RoCEv2流量一般被映射到优先级3或4,存储流量映射到其他优先级,这样RoCE流和普通TCP流可以物理共用同一根线缆,但互相不干扰。
PFC的原理很简单,但实际部署中要非常小心“死锁”。假设交换机A发给交换机B的RoCE流量占满了B的缓冲区,B发出Pause让A暂停。同时A的另一个端口也有流量要发给B的另一端口,而B的相关队列也满了,也发出Pause,这样两个交换机互相等待,所有流量都被堵死。解决思路是给每个端口预留足够的headroom缓冲,同时在交换机上启用死锁检测和恢复机制。
我在实际机柜里就遇到过PFC配置不对称的情况:交换机上配置了PFC,但网卡的优先级映射没配,于是RoCE流量走了无PFC的优先级,一旦拥塞交换机直接丢包,GPU训练每几百个step就卡一下。那时看NCCL日志,失败率不高,但整个训练时长被拉长了近3倍。所以判断无损网络配置是否生效,不能只听交换机说,要在网卡侧确认优先级映射是否正确。
2.3 拥塞控制:ECN标记与DCQCN如何让RoCEv2在动态网络中不崩溃
PFC能防丢包,但它没法主动消除拥塞,只会让拥塞蔓延。ECN(Explicit Congestion Notification)和DCQCN(Data Center Quantized Congestion Notification)是RoCEv2拥塞控制的核心组合。
过程是这样的:流量在网卡发出时,IP头部的ECT(ECN Capable Transport)位被置上。交换机在某个队列的深度超过阈值时,会把IP头的CE(Congestion Experienced)位标记为1,而不是直接丢弃。接收端网卡收到CE标记的报文后,每隔一段时间向发送端网卡发一个CNP(Congestion Notification Packet,也是RoCEv2报文,但只有BTH和UDP/IP头)。发送端收到CNP后,会根据DCQCN算法降低自己的发送速率,之后周期性尝试恢复。
这里面涉及几个关键参数:交换机侧的ECN阈值(如队列长度超过32KB就标记),网卡侧的降速步长和恢复步长,以及CNP上报间隔。调得太激进会导致带宽利用率低,调得太保守会导致延迟大甚至丢包。我习惯先采用NVIDIA/Mellanox推荐的DCQCN参数基线,再通过跑NCCL allreduce测试观察实际吞吐来微调。
这里要强调一点,ECN和PFC必须配合使用,不是二选一。ECN负责“软反馈”,让发送端主动降速;PFC是“硬防丢包”,当ECN还没生效或降速不及时的时候保护最后一跳。如果只开ECN不开PFC,瞬时突发流量仍然可能造成丢包;只开PFC不开ECN,拥塞会扩散到整个网络,最终谁都快不了。
3. 集合通信库与RoCEv2的拧成一股绳:NCCL的视角
3.1 AllReduce等集合操作如何在GPU、显存、网卡之间搬数据
NCCL(NVIDIA Collective Communications Library)是目前GPU集合通信的事实标准。它把AllReduce、AllGather、Broadcast、Reduce-Scatter等操作拆成一个个通信原语,在节点内优先使用NVLink,节点间使用RoCEv2或IB。
以经典的Ring-AllReduce为例,假设4台机器,每台机器4张卡,Ring把所有GPU(逻辑上16个节点)串成一个环。算法分成两步:第一步是Reduce-Scatter,每个GPU把自己梯度切分成15块,然后沿着环发送给下一个GPU,每个GPU在本地累计接收到的部分块;第二步是AllGather,每个GPU把累计好的完整块沿环转发,经过15轮后,所有GPU都拿到完整的归约结果。
如果网络没有RoCEv2,这个Ring的每一步都要经过TCP,累计延迟和CPU开销不可控。RoCEv2提供一对一的QP连接,NCCL会在每对GPU之间建QP,让数据点对点直传。NCCL还支持Tree算法,对跨机带宽大于节点内带宽时的场景更友好,因为Tree能减少数据绕行的距离。选哪种算法可以通过NCCL_ALGO环境变量指定,默认是自动试探。
3.2 NCCL如何感知RoCE拓扑并选择路径
NCCL最让人头疼也最强大的一点就是它会自动做拓扑探测。启动时,NCCL会扫描PCIe拓扑,读取每个GPU和网卡的相对位置,比如哪张卡离哪个PCIe switch更近,哪个网卡挂在那个root complex下。这种拓扑感知的目的是为了让节点内通信尽量走NVLink,节点间通信选择最优的网卡,并避免数据在PCIe总线上横跳。
在RoCEv2网络中,NCCL常用的几个环境变量直接影响性能和稳定性:
NCCL_IB_DISABLE=0:表示允许使用IB/RoCE传输。NCCL_IB_HCA=mlx5_0,mlx5_1:指定使用哪些RDMA网卡,多个网卡用逗号分隔。NCCL_IB_GID_INDEX=3:RoCEv2在IPv4下通常需要指定GID索引为3,表示使用RoCEv2的UDP封装且不启用GRH。这个值如果不对,连接可能建立但数据传不过去。NCCL_IB_QPS_PER_CONNECTION=4:每对连接使用多个QP,可以提升带宽利用率,尤其是对多队列网卡。NCCL_SOCKET_IFNAME=eth0:NCCL建立控制通道时使用的IP接口,要写物理网卡名,不要写docker0之类的虚拟网卡。
我遇到过几次“节点间通信只有1Gbps”的问题,最后都是因为NCCL没有正确识别RoCE网卡,所有跨机流量都走了socket通信路径。检查方法很简单:训练日志里会输出NCCL_IB和NCCL_NET相关信息,如果看到NCCL_IB_DISABLE=1或[include] ... NET/IB : 0,就说明RoCE根本没启用。
3.3 GPU Direct RDMA:让网卡直接读写GPU显存
GPU Direct RDMA(GDR)是让网卡通过PCIe直接访问GPU显存的技术。没有GDR时,即使RDMA网卡能走内核旁路,CPU还是要把数据从GPU显存拷贝到主机内存的注册缓冲区,再通知网卡发送;接收侧也是先到主机内存再拷回GPU显存。这个拷贝会占用PCIe带宽,还会增加大约10到20微秒延迟,对于AllReduce里动不动就GB级的数据量,代价非常明显。
开启GDR以后,NCCL在分配通信Buffer时会用cuMemAlloc在GPU显存上分配,然后通过nv_peer_mem模块把这些显存地址注册到网卡。发送时网卡直接发起对GPU显存的DMA read,接收时网卡直接DMA write到GPU显存,中间不经过CPU。要做到这一点,需要检查三个层面:
- 网卡驱动是否支持GDR补丁(比如NVIDIA的MLNX_OFED自带的nv_peer_mem)。
- BIOS里是否开启PCIe AER、ACS和较大的BAR映射(通常需要65536MB以上)。
- NCCL是否设置
NCCL_NET_GDR_LEVEL=PHB或PIX,代表允许网卡和GPU在同一个PCIe switch或root complex下使用GDR。
GDR并非总是越快。有时虚拟化环境或老旧内核下,GDR反而因为DMA页表映射开销变大而变慢。建议在真实集群上跑nvidia-smi topo -m和ibv_*工具做AB对比,再决定开不开。
4. 从能通到跑满:我踩过的RoCEv2调优坑
4.1 物理链路与交换机配置:PFC/ECN不是默认开启的
大部分交换机出厂配置不会自动在RoCEv2流量上打PFC/ECN,你以为的无损网络其实是有损网络。我见过不少团队,网卡配好了RoCE,NCCL报错也没有,但多机训练效率只比TCP好了10%,根源就是交换机完全没做QoS。
以一个常见的100Gbps RoCEv2机架为例,需要在交换机侧做这几件事:
- 开启DCBX或手动配置优先级映射,把DSCP值为特定码点的报文映射到队列3。
- 给队列3开启PFC,并设置
headroom缓冲区。通常队列的xoff阈值要设置为端口缓冲区大小的60%~70%,另一半留给ECN标记和burst缓冲。 - 给队列3配置ECN阈值,比如
min_threshold=50000 cells(约25MB),max_threshold=100000 cells。当队列占用超过min时会按概率标记ECN,超过max时所有报文标ECN。 - 将DSCP码点与802.1p优先级做映射,RoCEv2标准常使用DSCP 26或者自定义值。
配置完以后,用ethtool -x查看网卡队列,用rdma link show查看RoCE状态,再从另一台服务器连续ping加跑流测试。如果PFC生效,在拥塞时交换机上show queue可以看到这个队列的pause帧计数在增长。如果数值一动不动,说明PFC根本没对接上。
4.2 NCCL环境变量与网卡参数的取舍
跑多机RoCEv2,我最常调整的NCCL参数就下面几个:
| 参数 | 作用 | 我的推荐值 |
|---|---|---|
NCCL_IB_HCA |
指定使用哪个RDMA网卡 | mlx5_0或具体网卡名 |
NCCL_IB_GID_INDEX |
控制RoCEv2报文GID类型 | IPv4场景设为3 |
NCCL_IB_QPS_PER_CONNECTION |
每个连接QP数 | 4~8 |
NCCL_IB_TIMEOUT |
QP超时倍数 | 22左右 |
NCCL_IB_RETRY_CNT |
丢包重试次数 | 7 |
NCCL_NET_GDR_LEVEL |
GPU与网卡GDR合法性 | 根据拓扑选PIX/PHB |
NCCL_BUFFSIZE |
通信缓冲区大小 | 默认即可,必要时调大 |
NCCL_ALGO |
集合通信算法 | 自动,多机异常时试Tree |
网卡侧也要同步调整。以Mellanox/NVIDIA ConnectX系列为例,用mlxconfig或mstconfig设置:
code复制mstconfig -d /dev/mst/mt4123_pciconf0 set LINK_TYPE_P1=ETH
mlxconfig -d /dev/mst/mt4123_pciconf0 set ROCE_CC_ALG=DCQCN
mlxconfig -d /dev/mst/mt4123_pciconf0 set ROCE_CC_PRIO_MASK=0x8
mlxconfig -d /dev/mst/mt4123_pciconf0 set PFC_ENABLE=1
然后通过echo 2000 > /sys/class/infiniband/mlx5_0/ports/1/cc_parameters/...调整网卡拥塞控制参数。由于新版驱动参数路径会变,建议用rdma tool查看:
code复制rdma link show
rdma statistic show
调完参数后,用nccl-tests跑一个all_reduce_perf,比如:
bash复制./build/all_reduce_perf -b 128M -e 8G -f 2 -g 8 -c 0
观察busbw(总线带宽)是否接近理论值的80%以上。如果多机all_reduce_perf的带宽只有单机的1/2,优先检查网络QoS和GDR。
4.3 抓包定位问题:为什么RoCEv2丢一个包就全线崩溃
RoCEv2是“一次丢包,性能插水”的典型。因为UDP没有滑动窗口和选择性重传,一旦底层丢包,等待重传和超时的时间可能达到毫秒级,而正常的传输延迟只有1~2微秒,相当于性能掉到1/1000。
在排查性能问题的时候,我通常习惯在发送端和接收端同时抓包。RoCEv2用UDP 4791端口,可以直接抓:
bash复制tcpdump -i eth0 -s 100 udp port 4791 -w roce.pcap
然后用Wireshark打开,重点看三类信息:
- 有没有重传?RoCE没有类似TCP的重传标记,但如果有大量重复的PSN、或者连续发送超时,基本说明下层丢包。
- 有没有CNP包?CNP是目的端口为4791、且BTH OpCode为CNP(0x81)的报文,如果你在高负载下看到大量CNP,说明网络拥塞已经触发了慢启动降速。
- 有没有PFC pause帧?在网卡上抓包含以太网type 0x8808的包,如果持续收到pause,大概率是某一跳缓冲不足或QoS映射错误。
另外,不要只盯着链路带宽。RoCEv2的包很小,PSN和ACK的交互非常依赖小包转发能力,交换机的报文转发率(pps)也很关键。如果交换机CPU负荷过高、ACL规则过多,小包一样会被丢弃。我遇到过一次莫名其妙多机通信慢的问题,最后发现在交换机上加了大量ACL导致转发表查表变慢,去掉后恢复了线速。
4.4 一个真实的调优案例:从4.2GB/s到18.3GB/s
最后分享一个两周前刚处理过的案例,供你参考。客户的集群是4台8卡A100,网卡为CX-6 DX 100G,交换机为某主流厂商25G/100G接入。原始状态跑NCCL AllReduce多机busbw只有4.2GB/s,NCCL日志显示IB传输建立成功,但延迟很高。
排查过程:
- 用
tcpdump抓包,发现发送端收到了大量CNP,但网卡发送速率没有明显下降,说明DCQCN参数有问题。 - 用
mstconfig检查网卡配置,发现ROCE_CC_PRIO_MASK是默认的0xFF,覆盖了所有优先级,导致非RoCE优先级也被拥塞控制降速。 - 交换机侧查看ECN命中计数,发现ECN阈值设置得太小,突发流量一上来就全部被标记,DCQCN错误降速。
- 调整方案:交换机ECN阈值从
20000/40000改为50000/100000;网卡ROCE_CC_PRIO_MASK=0x8只保留Priority 3;NCCL设置NCCL_IB_TIMEOUT=22、NCCL_IB_RETRY_CNT=7。
调整后重新跑all_reduce_perf,busbw到了18.3GB/s,接近100Gbps上限的90%。所以很多“疑难杂症”背后不是玄学,就是优先级没隔离、ECN过激、PFC没接对这三个老问题。
这行做久了你会发现,RoCEv2的协作链条里,真正决定上限的不是某一块硬件,而是所有环节的目标是否一致:集合通信库的目标是让数据在GPU间高效完成归约,RDMA的目标是绕过CPU减少拷贝,以太网的目标是用最少的丢包和延迟把物理带宽发挥出来。下一次你在配置RoCEv2集群时,不妨沿着这条链路逐层看:先看网卡有没有建立QP,再看有无PFC/ECN标记,最后才去纠结NCCL参数——顺序对了,很多问题会自己浮出来。
