1. 先算一笔账:为什么CPU搬运内存会成为AI集群的瓶颈
这几年跟AI集群打交道,几乎绕不开一个词:RDMA。无论是训练框架的分布式通信库,还是存储侧的NVMe-oF,底层都在用它。我也被不少人问过,RDMA到底是啥,跟普通网卡发的数据包有什么本质区别。这个问题最好的切入方式,不是直接甩协议栈图,而是先算一笔账。
假设你手里有一台8卡GPU节点,单卡算力足够撑起一个不小的Transformer模型训练,但模型太大,一张卡放不下,需要4张卡或者8张卡做张量并行。张量并行意味着每算完一层,就要把梯度或者激活值同步给其他卡。以现代训练框架为例,AllReduce通信量动不动就是几十MB甚至几百MB一次,而且每iteration都要来一轮。这时候网络延迟和带宽直接决定训练效率,慢一倍,整个集群的GPU吞吐就跟着腰斩。
传统TCP/IP网络走的是“内核搬运”路线:数据从用户态应用拷贝到内核socket缓冲区,内核协议栈处理完,再交给网卡发出去。接收方向更复杂,数据先到内核缓冲区,再拷贝到用户态。这个过程涉及多次内存拷贝、上下文切换、中断处理。TCP能做到几千兆的带宽没问题,但延迟被拉到几十微秒甚至上百微秒,CPU占用率高得吓人。你想想,AI训练的通信模式是高频、小包、短连接式的AllReduce,CPU光忙着搬数据了,GPU反而在等数据,计算和通信彻底串行化。
这时候就轮到RDMA登场。RDMA全称Remote Direct Memory Access,远程直接内存访问。它的核心思想就四个字:绕过内核。应用向网卡注册一块内存区域,网卡可以直接从这块内存把数据发出去,接收方网卡也可以直接把数据写进目标内存,全程不需要CPU参与数据搬运。CPU只负责提前告诉网卡“你要搬哪段内存、搬到哪里去”,剩下的活全交给网卡硬件。
这里涉及一个重要的概念区分:传统DMA(Direct Memory Access)解决的是设备到内存之间搬运数据的CPU占用问题,RDMA解决的是跨节点内存访问问题,它是DMA在网络领域的延伸。用一个生活化的类比,传统网络像你委托快递员上门取件,快递员还得先到你这儿拿钥匙、开柜子、翻出包裹、打包封箱,最后才上路。RDMA相当于你提前把包裹放在门口指定位置,告诉快递员直接取走,连门都不用进。省掉的不只是几道手续,而是每一次交接的等待和确认。
现在回到AI集群的场景。大模型训练时,GPU显存里的模型参数需要跨节点同步,数据量巨大且频率极高。如果用传统网络,CPU要参与每一笔数据的拷贝,内存带宽和CPU核心都会被拖死。RDMA让GPU的数据可以直接从显存经过网卡发到对端,甚至配合GPUDirect RDMA技术,数据完全不需要经过主机内存中转,这就是AI集群网络里RDMA不可替代的根本原因。
顺带说一句,很多人看到热词里有一堆“内存”相关的搜索,比如内存映射、内存池、物理内存分配,其实它们和RDMA的关联非常紧密。RDMA要求注册内存区域,本质上就是一套映射和固定物理页的机制。后面我会专门展开讲。
2. 从内核态到用户态:RDMA到底动了哪根弦
2.1 传统收发路径与RDMA收发路径的对比
要理解RDMA的价值,得先看清传统网络路径里到底浪费了什么。你调用一次send(),数据从应用缓冲区拷到内核的socket发送缓冲区,TCP/IP协议栈逐层封装,网卡驱动把数据搬到网卡发送队列,网卡发出。接收方向反过来,网卡收到报文,DMA到内核缓冲区,硬中断通知CPU,CPU跑软中断处理协议栈,把payload拷到用户缓冲区,最后唤醒应用线程。
这条路有四个成本大头:
- 用户态与内核态切换
- 多次内存拷贝
- 协议栈CPU计算
- 中断与上下文切换
RDMA的路径完全不一样,它不是对TCP/IP的优化,而是重新设计了一种网络模型。应用创建QP(Queue Pair)队列对,本质上就是一对发送队列和接收队列。发送方应用写好消息描述符,指向一块已经注册好的内存区域,然后通过硬件门铃通知网卡,网卡直接DMA读取这块内存,组装报文发出去。接收方网卡根据收到的报文,找到对应的QP,直接把payload DMA写入预先安排好的接收缓冲区,然后通过完成队列CQ通知应用。整个过程CPU没有碰过数据,没有参与过拷贝。
2.2 内存注册机制:为什么不能随便拿一块内存就用
很多第一次接触RDMA的人会问我,为什么每次RDMA通信前都得先注册内存,不能像socket那样直接传一个buffer指针?
这是RDMA安全的基石,也是它零拷贝能成立的前提。网卡访问内存用的物理地址和IOMMU映射,应用看到的是虚拟地址,网卡不知道你这段虚拟内存对应物理页面在哪。如果应用不做处理直接提交buffer,网卡DMA读到的可能是还没有分配的页,或者是已经被换出到swap的页,结果就是数据错乱甚至机器崩溃。
所以RDMA第一步就是注册内存区域MR(Memory Region)。注册动作会锁定这些物理页,不允许换出,同时把虚拟地址到物理地址的映射表固化下来,交给网卡。网卡拿到这张映射表,才能安全地做DMA。注册时需要指定权限:本地读/写、远程读/写、原子操作等。
这个过程和应用层做内存映射(Memory Mapping)思路一致,核心都是建立虚拟地址到物理地址的映射关系,只是RDMA的映射表最终落到网卡硬件里,而且针脚固定(pinned)的内存页不能被换出。
用热词里的“内存映射”来理解,RDMA注册就是一种特殊的、面向硬件网卡的内存映射,映射表的消费者从CPU MMU变成了网卡RDMA引擎。
很多人误以为RDMA注册内存很贵,其实贵的不是注册本身,而是页表构建和pin page。如果你在通信热路径上频繁注册/注销小buffer,性能会非常难看。所以最佳实践通常是启动时一次性注册一大块内存池,从中分割缓冲块供通信使用,热路径上只做Address+ImmData的提交和回收,不做注册。
2.3 QP、CQ、WR:RDMA通信的最小三个零件
再拆一下RDMA的编程模型。QP(Queue Pair)不是物理队列,它是网卡内部一组发送队列(Send Queue,SQ)和接收队列(Receive Queue,RQ)的抽象。通信双方各有一个QP,QP与QP之间建立连接关系,类似socket概念,但所有工作模式都是“把工作请求提交给硬件”。
- 发送请求WR(Work Request):由应用通过verbs API提交给SQ
- 接收请求RR:提前预贴在RQ上,告诉网卡收到数据后往哪个buffer写
- 完成队列CQ:网卡处理完一批WR后,生成完成事件CQE,应用poll CQ拿到结果
我经常跟人打比方,QP像一条高速公路车道,WR就是一辆辆货车,货车装载的数据就是payload,CQ则是收费站出口的到站记录。你不停地往SQ塞货车,网卡自动发车,对端的RQ早就安排好了停车场,车一到就直接卸货到指定车位,完成记录推送到CQ。这个模型最核心的一点:没有acknowledge,没有流控重传,数据搬完,CQ里多了条记录,就这么简单。
理解QP还不能只看单方向。一个QP天生是双向的,SQ负责发送,RQ负责接收。如果数据量大、收发频繁,你还需要多个QP分摊流量,或者用多个CQ分摊完成事件处理。QP数量、CQ数量、WR深度这些参数,直接对应网卡内部硬件的槽位资源。资源开少了,应用层排队等待;开多了,HW占内存和Cache。这块后面讲调优时细说。
2.4 三种操作类型:SEND/READ/WRITE到底怎么选
RDMA通信不是只有一种“发送”动作,它定义了多种操作类型,区分度很关键:
IBV_WR_SEND: 类似传统send,需要接收方预先准备好接收缓冲区,数据到了直接被写入该缓冲区IBV_WR_RDMA_WRITE: 远程写,不需要接收方应用参与。本端直接指定对端内存地址和RKey,数据到达后网卡直接写入对端指定内存IBV_WR_RDMA_READ: 远程读,本端去对端指定的内存地址把数据读回来,同样不需要对端应用参与IBV_WR_ATOMIC_CMP_AND_SWP: 原子比较交换,常用于分布式一致性场景
SEND方式交互最少要两轮:发送方发数据,接收方回一个SEND确认。RDMA WRITE方式在单边操作中完全免除了接收方的CPU参与,接收方完全不知道数据来了,数据已经被写进内存。这在大规模分布式存储里非常常见,客户端直接把数据写到服务端的内存池里,服务端的CPU只负责处理控制面消息,数据面几乎零开销。
但RDMA WRITE也有代价,接收方没有在CQ里获得任何通知,除非发送方再单独发一个SEND/WRITE_WITH_IMM,或者接收方自己轮询那一段地址。所以实际工程中,经常配合“数据包+控制包分离”的设计,把真正的数据用WRITE带过去,再用一个小的SEND携带imm数据通知对端“数据已经到了、来拿”,兼顾吞吐和通知机制。
3. 从网卡硬件视角看RoCE、IB与iWARP之争
3.1 InfiniBand、RoCE、iWARP的定位
很多文章喜欢一上来就给一张协议对比表,但我觉得从硬件角度更直观。网卡上的RDMA引擎,处理的核心无外乎三件事:把用户态的内存地址翻译成物理地址,把payload切成MTU大小合适的报文,把报文可靠地送过去。这三件事和底层链路用什么协议关系不大,所以才有了三种承载方式。
- InfiniBand:从头设计的一整套网络,物理层、链路层、网络层、传输层都由IB规范定义。网卡、交换机、线缆全链路专用,性能最好,但价格也最贵,通常用于超算和高端AI集群。
- RoCE(RDMA over Converged Ethernet):把RDMA报文封装在以太网帧里,只要求网卡和交换机支持RoCE能力。RoCE v1在二层工作,RoCE v2在UDP/IP三层工作,是当前AI集群最主流的方案。
- iWARP:把RDMA语义跑在TCP/IP协议栈上,由于TCP本身的复杂性和CPU开销,实际性能通常不如前两者,应用面也窄很多。
现在主流AI集群几乎都倒向RoCE v2,核心原因是生态成熟、成本可控、性能已经逼近IB。英伟达的SuperNIC、博通的Thor网卡,甚至很多商用交换机都原生支持RoCE v2。
3.2 为什么RoCE v2打通了“RDMA跑在普通以太网上”这条路
一张支持RoCE v2的网卡,发送方应用提交WR后,RDMA引擎直接构造UDP报文:普通UDP头里塞进目的QPN、报文序号PSN等RDMA字段,然后走以太网发出。接收方网卡识别UDP目的端口4791,剥掉UDP头,把剩下的内容交给RDMA引擎处理。
正因为RoCE v2是封装在UDP里的,它就能在普通IP网络上路由,不必限于二层广播域。AI集群规模大,动辄几百上千个GPU节点,二层网络维护成本高,三层路由能力是刚需。RoCE v2天然支持IP路由,所以被各大云厂商和AI Infra团队选作主力方案。
但RoCE v2也不是万能药。它复用以太网的流控机制,但RDMA对丢包极度敏感,哪怕万分之一丢包率,吞吐都可能断崖下跌。原因很简单:RDMA可靠性依赖Go-Back-N重传,一个报文丢了,后面所有报文都得等重传。传统TCP至少还有SACK能做选择性重传,RoCE v2的丢包恢复几乎是灾难级的。所以RoCE网络必须优先保证“不丢包”,这就要用到无损以太网的PFC流控,或者ECN显示的拥塞通知。
AI集群自建RoCE网络时,我个人的经验是:
- 交换机必须开PFC,优先级队列隔离,不要让普通TCP流量跟RoCE流量混在同一条队列。
- 启用ECN,让交换机在拥塞初期打标记,让网卡自动降速,而不是等队列满了丢包。
- 检查MTU一致性,RoCE通常建议9000字节巨型帧,MTU不匹配会出现莫名其妙的小包性能差。
很多人问,为什么AI训练非要选RoCE而不是普通TCP?答案可以一句话总结:TCP的延迟和CPU开销吃不住AI通信的强度和带宽密度。在万兆以太网上,RoCE能轻松跑满线速90%以上的带宽,CPU占用率个位数,而TCP可能光跑个七八成带宽,CPU已经全核拉满。
3.3 内存带宽和网卡带宽的匹配问题
我见过不少团队,采购了200Gbps的RoCE网卡,结果单机多卡训练性能还是上不去。排查到最后,锅往往不在网卡,而在内存带宽。
RDMA接收数据需要把数据写到主机内存,发送数据需要从主机内存读出来。单张200Gbps网卡的理论吞吐大约是25GB/s,如果同一个CPU socket上同时插了两张这样的网卡,内存系统的带宽就成了瓶颈。新一代CPU的单插槽内存带宽通常在200GB/s到400GB/s之间,看着够用,但你还有GPU训练、存储IO、内核各种活动都在抢内存带宽。一旦内存带宽吃紧,CPU里的memcpy性能断崖下跌,RDMA实际吞吐自然上不去。
这也是“大内存架构”这个热词背后真正热的点:HBM、CXL、多通道DDR5,本质上都在试图拓宽内存带宽,缓解数据搬运瓶颈。AI集群里,内存带宽和网卡带宽是一对孪生兄弟,只升级网卡不升级内存,等于给跑车换了火箭发动机但轮胎还是原厂货。
4. 从单机通信到AI集群网络:RDMA如何撑起大规模训练
4.1 集合通信是RDMA最典型的AI场景
大模型训练离不开集合通信,典型的操作为AllReduce、AllGather、ReduceScatter等。以AllReduce为例,各节点把自己的梯度张量传递出去,最终每个节点都拿到所有节点的梯度求和结果。通信过程如果用点对点的send/recv实现,数据量随节点数线性增长,通信复杂度O(N^2),完全不可接受。所以实际中会先按拓扑做分阶段通信,节点间数据量被压缩到O(N log N),甚至更低。
这些通信原语通常由NCCL、RCCL、oneCCL等集合通信库实现。而这些库的高性能版本,底层都跑在RDMA上。NCCL会选择用共享内存做单机多卡通信,用网络做跨节点通信。跨节点时优先尝试RoCE/IB,拿不到RDMA才会回退到TCP socket。训练框架里经常看到一个环境变量NCCL_IB_DISABLE=1,把它设为0,就是允许NCCL使用IB/RoCE通信。在很多老教程里,为了在非RDMA环境跑通,人们会设成1禁用,但如果跑AI集群,一定不要禁用。
4.2 胖树、全互联与动态路由:集群拓扑怎么决定通信效率
AI集群不是把一堆服务器接在交换机上就完了。跨节点通信模式非常规律:AllReduce从GPU梯度聚合角度看,每轮都是全网广播或全归约,任何一对节点都有通信。如果拓扑是传统汇聚层胖树,上行带宽被多个节点争抢,训练效率会大打折扣。
所以现代AI集群多采用两种拓扑:
- Fat-Tree胖树:等价多路径ECMP在网卡哈希下做负载均衡,一定程度上能摊平流量,但对哈希冲突和流大小敏感。
- Rail-optimized Topology:把GPU按编号和机架进行对齐,通信流量走固定的、短路径的编号匹配链路,更能匹配集合通信的流量模式。
现在英伟达的NVLink+Quantum方案、RoCE方案里也大量引入“全互联”的设计思路。注意这里的全互联不是完全图网络,而是尽量保证任意两个节点之间没有共享链路的瓶颈。加上动态路由,让网络能够根据实时流量自动选路,避免某条链路被热点打爆而其他链路闲得发慌。
实战中我们搭一个96节点GPU集群,用的就是RoCE v2+ECMP的胖树。训练时通过NCCL测试拉满带宽,问题经常出在某些交换机上行端口流量不均,个别端口跑到线速,其他端口只有20%。后来开了动态负载均衡(DLB)方案,且调整网卡哈希,问题才缓解。这是RDMA集群运维里很典型的一课:物理带宽足够,网络规划不好,照样喂不满GPU。
4.3 GPUDirect RDMA:绕过主机内存的最后一步
如果数据要先从GPU显存拷贝到主机内存,再让网卡读走,那中间就有一个绕不过去的主机内存中转路径。GPUDirect RDMA解决的就是这个问题:允许网卡直接读写GPU显存地址。
实现方式是一套叫nvidia-peermem的内核模块,它把GPU显存的物理页映射给网卡,使网卡可以直接对显存做DMA。NCCL在检测到这个能力后,跨节点通信会走GPU显存到网卡的直达链路。这个路径的延迟和带宽,比经过主机内存中转好得多。以NVIDIA H100平台为例,跨节点通信延迟能减少几微秒,带宽利用率提升好几个百分点。
搭建GPUDirect RDMA时,有几个坑非常明显:
- 确保GPU驱动和网卡驱动都支持该功能
- 检查PCIe拓扑,GPU和网卡最好挂在同一颗CPU的PCIe控制器下,跨CPU访问PCIe会增加延迟
- 如果拓扑不允许,至少保证GPU显存和网卡之间的PCIe链路没有严重竞争
热词里“16g显存+32g内存能本地部署什么大模型”这类问题也常被问到,很多人以为内存够大就能跑大模型,但从RDMA视角看,内存只是模型的驻留空间,计算通信仍需要带宽来喂数据。显存不够靠内存凑的方案,DDR带宽远低于HBM带宽,即便能跑,训练速度也惨不忍睹。
4.4 内存池化与分布式存储的RDMA依赖
RDMA在高性能存储里的地位同样重要。NVMe-oF(NVMe over Fabric)是当前AI训练存储的主流方案,它的数据面依赖RDMA,让客户端能够直接读写远端NVMe SSD暴露的内存块设备或命名空间。数据路径上没有内核态文件系统,没有TCP拷贝,延迟能压到十微秒级别。
这套架构用到了RDMA的原子操作和单边RDMA WRITE/READ,服务端不需要为每笔IO分配内核缓冲区,所有目标内存来自启动时注册好的一块内存池。内存池在这里的作用极其关键:服务端把一块大内存注册成若干固定大小的IO Buffer,客户端通过RDMA WRITE直接写入,通过RDMA READ直接读取,服务质量完全由缓冲池命中率决定。
如果你去查NVMe-oF的配置文档,会发现关于memory pool、RDMA queue depth、credit管理等配置项,其实都在调RDMA的底层行为。热词里“内存池原理和实现”也是这个道理。内存池不是RDMA独有的东西,但RDMA把内存池推到了一个新的复杂度层级,因为它要求池里的内存必须预先注册,池的扩容缩容都要重新走注册流程,不是随手malloc就行的。
5. 实战笔记:第一次把RDMA跑起来会遇到哪些坑
5.1 环境准备:核验网卡、固件、驱动、License
RDMA环境对软件栈的一致性要求极高。第一次在集群上装RoCE时,我建议按这个顺序排查:
ibv_devinfo查看网卡是否被正确识别,重点看端口状态、链路速率、Active MTUibstat确认HCA处于Active状态ethtool eth0确认网卡Link Speed和Autoneg配置- 检查驱动模块,RoCE网卡通常是
mlx5_core,确认ib_umad、ib_uverbs、rdma_cm这些内核模块都加载了 - 如果是Mellanox/ConnectX系列,注意固件版本和驱动版本要搭配合适,必要时升级固件
很多问题出在License上,部分网卡的高级特性需要额外license才开启,比如某些RoCE功能默认关闭,需要通过固件工具打开。查一下mlxconfig的SRIOV_EN、ROCE_EN、LINK_TYPE_P1/P2等参数,确保RoCE打开。
5.2 验证连通性:用rping而不是直接上业务
环境搭好后,我强烈建议先用rdma_cm工具包里的rping做一轮回环测试,不要直接跑业务。因为RDMA的问题排查维度很多,先把“链路通不通”这一层拎出来验证。
服务端执行:
rping -s -a 192.168.1.10 -v客户端执行:
rping -c -a 192.168.1.10 -v -C 1000如果rping能连续跑几千次成功,说明基本链路是通的。之后再用ib_write_bw、ib_read_bw、ib_send_bw做带宽和延迟基准测试,验证单边和双边操作在不同消息大小下的表现。
rping通过时不代表业务一定能通,因为rping只验证了RC连接的基本数据搬运,业务里还有更多细节。但这个分层验证策略能帮你快速定位问题区域:链路不通查物理/驱动,rping通但业务不行,问题大概率出在应用层,比如内存注册权限、QP配置、地址处理等。
5.3 最容易踩的坑:QP状态、内存、对齐
第一个高频坑是QP状态。RDMA的QP状态机比TCP复杂,连接建立要经过INIT → RTR → RTS三个阶段,每一个都必须在两端配合完成。很多人用rdma_cm建链时,忽视了QP在RTR状态需要预贴接收WR,否则对方数据一来,接收队列是空的,网卡直接产生RNR错误,连接被断开。常见错误码ERR_RNR_RETRY_EXCEEDED就是这个问题。
第二个高频坑是内存注册和生命周期。一批WR提交给SQ之后,发送方应用不能释放buffer,因为网卡可能还没完成DMA读取。正确做法是等CQ里对应的完成事件出现后,才认为buffer可以被复用。很多初学者的第一版代码就是提交WR后马上改写buffer内容,结果数据全是错的,而且错得不稳定——有时对有时不对,跟网卡DMA进度强相关。
第三个坑是地址对齐问题。RDMA对payload的起始地址和长度有对齐要求,虽然没有TCP那么随便。不是所有buffer都能直接传给WR,通常需要按cache line(64字节)或者page(4KB)对齐。不对齐的直接后果是网卡把payload切成两个段处理,性能下降,有时还会触发某些固件的参数校验错误。
第四个坑是字节序。RDMA头部的QPN、PSN、RKey这些字段,虽然大部分由网卡固件处理,但在软件开发中,某些控制字段需要明确按网络字节序处理。用C没注意会在小端机器上翻车,常见的错误是QP号大端写小端读,连接匹配失败报IBV_WC_QP_STATE_ERR。
我把常见错误码整理成了一张表,方便排查:
| 错误码 | 含义 | 常见原因 |
|---|---|---|
IBV_WC_LOC_LEN_ERR | 本地长度错误 | buffer长度小于网卡解析出的payload长度 |
IBV_WC_LOC_QP_OP_ERR | QP操作错误 | 不支持的WR操作类型,或状态下的非法操作 |
IBV_WC_REM_ACCESS_ERR | 远端访问错误 | RKey/地址权限不匹配,或对端没有注册对应内存 |
IBV_WC_RNR_RETRY_EXCEEDED | 接收未就绪重试超限 | 对端RQ预贴接收WR不足 |
IBV_WC_RETRY_EXCEEDED | 重传超限 | 网络丢包严重,可靠连接无法恢复 |
IBV_WC_WR_FLUSH_ERR | WR被flush | QP被错误处理或设备发生错误,相关WR全部作废 |
遇到过IBV_WC_REM_ACCESS_ERR的人肯定有印象,这往往不是真的“对端权限不够”,而是你这边在构造ibv_sge时,RKey和地址填错了。排查时先打日志充一下远端MR信息是否完整。
5.4 性能上不去的常见原因:MTU、credit、中断合并
网上有数不清的“RDMA性能上不去”求助帖,我归纳了一下,绝大多数逃不出下面几个原因。
第一,MTU不匹配。RDMA要求整个网络路径的MTU一致且尽量大。如果交换机MTU设成1500,网卡设9000,iperf测试时小包没事,大包性能一落千丈。用ibping跑一个大于MTU的payload,立刻就能暴露问题。
第二,接收队列WR数量不够。接收方RQ预贴的WR数量决定了网卡可以提前接收多少报文。如果你只贴了16个WR,但发送方一波洪峰来了32个报文,后16个只能等接收方处理完前16个再接收,带宽自然被压住。一般建议WR数量开到512以上,配合CPU处理能力权衡。
第三,中断合并没调好。RDMA网卡完成事件默认可能用中断通知,高频通信时,每秒几十万次中断,CPU bezalel根本扛不住。开启adaptive coalescing(自适应中断合并)或调大完成事件合并阈值,把CPU从每个消息都打断的模式下解放出来。
第四,CPU亲和性。RDMA网卡的完成事件处理线程,必须绑在和网卡同一个NUMA节点上,否则跨NUMA访问会引入大量远端内存读写,延迟骤增。热词里“spark内存”、“内存泄露”这类问题跟这个原理也有相通之处,JVM、Spark的GC线程如果跨NUMA分配,性能同样会恶化。
6. UCX、NCCL与RDMA的耦合:AI训练里看不见的通信调度
6.1 UCX:一套统一的通信抽象
在大规模AI训练里,直接对着ibv_post_send写业务逻辑的人很少,大部分框架是接在NCCL、MPI、UCX这类通信库上面。其中UCX(Unified Communication X)是很典型的例子,它提供统一的通信原语,底层自动选择shared memory、TCP、RDMA等各种传输方式。
UCX面向高性能计算设计,底层传输层被抽象为“tag matching”、“stream”、“active message”等语义。AI训练框架里,PyTorch的gloo后端,以及很多自研分布式框架,都可以通过UCX来获得RDMA加速能力。UCX在初始化时自动检测节点间是否支持RDMA,如果支持就优先选rc_verbs或rc_mlx5传输,不支持则回退TCP。
使用UCX时,有经验的工程师会主动用环境变量控制它的行为,而不是全自动。比如:
export UCX_TLS=rc_mlx5,self,sm export UCX_NET_DEVICES=mlx5_0:1 export UCX_MAX_RNDV_RAILS=2UCX_TLS里的rc_mlx5意思是用Mellanox网卡的RC传输;self是本进程内通信;sm是共享内存传输。这样设置后,UCX就不会傻乎乎把所有数据都走网络,节点内通信直接用共享内存,节点间走RDMA,效率最高。
需要提防的是,UCX的rc_mlx5要求网卡必须是Mellanox或兼容其verbs接口的卡,如果驱动版本太老或者RDMA功能不完整,UCX在初始化时会直接报No selected devices错误。这时先用ucx_info -d看设备列表,确认传输是否被识别,再排查驱动。
6.2 NCCL的RDMA链路和拓扑感知
NCCL是NVIDIA的集合通信库,AI集群训练的事实标准。NCCL的初始化过程会检测节点内GPU拓扑(NVLink/PCIe)、节点间网络拓扑,自动选择最优通信路径。在跨节点通信时,NCCL优先通过IB/RoCE网卡做RDMA,此时每个GPU会创建一个或多个代理线程,把数据段切分后通过QP发送。
NCCL里有一个“两个层次的AllReduce”设计:节点内用NVLink做全归约,跨节点用RDMA做AllReduce。跨节点通信的带宽往往比NVLink低一到两个数量级,所以NCCL会把通信数据尽量压缩(比如梯度量化),同时把通信过程与计算过程流水线化。
实践中有几个NCCL环境变量调优经验:
NCCL_IB_DISABLE=0:允许IB/RoCENCCL_IB_GID_INDEX=3:RoCE v2需要设定正确的GID索引,部分集群不配置会导致网卡无法正确解析NCCL_BUFFSIZE:控制每次通信引擎发送的buffer大小,过小会导致小包发送频繁,过大又增加内存占用NCCL_NET_GDR_LEVEL:控制GPUDirect RDMA的应用级别,不同显卡和驱动版本该变量的支持情况不一样
一定要记住,NCCL的调优不是把一个变量调到最大就行,它是整个集群的协同优化。比如NCCL_BUFFSIZE开大会提升大信息量场景的吞吐,但同步会把NCCL的内存占用顶上去,一不小心就能触发OOM。
6.3 大规模集群中常见通信模式:ring allreduce与tree allreduce
集合通信内部有不同算法。Ring AllReduce把节点串成一个环,数据沿环流转,每个节点只跟前后邻居通信。它的特点是带宽利用均衡,通信量是常数,但延迟和环的大小挂钩。Tree AllReduce则用一棵树做聚合,数据从叶子向根流动,根聚合后再广播下传。它的延迟更低,但根节点的带宽压力大,容易成为瓶颈。
NCCL默认根据节点数和拓扑选算法。如果集群规模大且网络带宽高,Ring Allreduce通常占主导,因为它能让所有链路同时工作。这也是为什么RoCE拓扑强调“任何相邻节点之间都有高速通道”,环形逻辑通信模式必须匹配物理拓扑的低延迟链路。
看到这里你应该理解了,AI集群网络里的RDMA不是一个孤立网卡特性,而是一连串算法、拓扑、驱动、应用四层共同协作的结果。单独把某层调到最优,其余层跟不上,最终训练效率还是上不来。
7. 如何系统性排查RDMA问题:一条完整的故障链路记录
我把自己最近一次在生产集群上排查RoCE性能问题的过程写出来,里面涉及两个热词里的高频主题:内存占用过高和内存泄露。问题表面看是内存,根子却出在RDMA队列管理上。
某次训练任务跑起来后,每过一小时,节点内存占用就会以几十GB的幅度增长,最终OOM。一开始大家怀疑是训练框架的数据加载器或者缓存泄露,但杀掉训练进程后内存并不立即释放,这就很蹊跷。正常情况下用户态进程退出,malloc分配的内存会被操作系统回收,物理内存应该快速下降。
后来在节点上用ibstat看网卡状态,发现QP数量异常增长,活跃QP数从启动时的几十个涨到了几千个。继续用rdma resource show qp查详细队列信息,看到大量QP处于ERROR状态,而且它们关联的CQ、MR仍然被引用。与此同时,网卡dmesg日志里出现大量ib_poll_cq: Couldn't find CQ for QP 0x..的错误。
根因浮现了:通信库在比预期更多的网络路径上创建了QP,但业务代码和底层库在通信结束后没有正确销毁QP。而且部分QP因为错误状态没有被妥善flush,导致MR和DMA映射迟迟不能释放。RDMA的MR注册把内存pin在物理页上,只要MR不销毁,那一大块物理内存就一直被占用,哪怕应用已经不引用它了。这跟传统意义上的“应用malloc后free失败”不是一回事,它更像“内核对象没有释放导致的内存泄漏”,即RDMA资源泄漏。
整个排查链路是这样的:
- 观察内存持续增长,顶到OOM阈值
- 排除TX框架缓存,重启进程内存仍不释放
- 检查
/proc/meminfo的Mlocked、Unevictable字段迅速增长 - 用
rdma resource show mr和ibstat确认MR和QP数量异常 - 定位到通信库的错误处理分支没有销毁QP路径
- 修复后,设置
IBV_QPT_RC的错误处理回调,在错误发生时显式flush QP、销毁CQ、注销MR - 增加监控脚本定时检查MR/QP数量,一旦异常自动告警
这次教训给了我很强的经验沉淀。RDMA资源是内核pin住的物理内存,跟普通用户态内存不一样。排查“内存占用过高”问题,不要只盯着应用代码看malloc/free,还要看RDMA资源是否有泄漏。从那一刻起,所有涉及RDMA的服务,我都会在代码里主动管理队列池,并把“每次训练结束销毁全部QP和MR”写进异常处理流程。
热词里“内存泄露”、“内存检测工具”、“内存池”这些搜索词,放在RDMA场景下,其实都指向同一个核心:RDMA资源生命周期管理。内存池不只是性能优化工具,也是防止资源泄漏的机制。把QP、MR、CQ都预先分配好并常驻复用,每次通信只是借用、归还,就不存在“忘记销毁泄漏”的问题,这在长稳运行的服务里几乎是必须的。
8. 写在最后的实践心得
RDMA这套技术,从硬件网卡到软件栈,再到AI集群的网络规划,环环相扣。很多人第一次了解它,容易被一堆术语劝退。我建议真正想上手的读者,不用急着读全部协议文档,先在一台装好RoCE网卡的机器上把rping跑通,再跑ib_write_bw看带宽数字,然后往业务框架里接,遇到问题再回头补协议细节,这个路径比较高效。
如果只能留下一句话,我想说:RDMA的复杂在于它把“数据搬运”这件事的重心从CPU转移到了网卡,这大幅度提升了性能,但也把内存管理、队列管理、错误处理这些责任从操作系统转交给了应用程序。你用RDMA不只是换一种IO方式,而是换了一套更沉重但更高效的内存与通信管理模型。
做AI集群网络优化这行,越往后越发现,真正拉开训练性能差距的,往往不是网卡线速,而是底层资源生命周期管理、队列深度设置、拓扑感知调度这些看不见的细节。RDMA像一面放大镜,把内存模型和网络模型的一切优缺点都放大十倍。踩过坑、调过参、见过OOM之后,才算真正看懂它。