ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

图解RDMA:从远程直接内存访问到AI集群网络优化

图解RDMA:从远程直接内存访问到AI集群网络优化 我入行做分布式系统那会儿最头疼的问题不是 CPU 不够快也不是硬盘不够大而是数据在机器之间“挪”得太慢。后来在 GPU 集群上调大模型训练这个问题直接变成生死攸关——几千张卡在那里等网络每一秒的等待都是白花花的算力在烧。而这一切的突破口就是 RDMA一个全称叫 Remote Direct Memory Access 的技术。用大白话说它就是让一台机器直接去读、写另一台机器的内存把“内存搬运”这个动作做到了极致。这篇内容我会从“为什么传统网络不行”开始讲再把 RDMA 的核心机制拆开揉碎最后落到 AI 集群里怎么用、怎么调、踩过哪些坑。适合三类人看正在做高性能计算或分布式训练的同学、准备给数据中心换网络方案的技术负责人以及单纯想搞明白“为什么大模型训练需要特殊网络”的工程师。看完之后你会对 RDMA 有一个从概念到实战的完整认知。1. 从一次训练任务卡顿说起RDMA 到底在解决什么去年我在调一个多机多卡的大语言模型训练任务时遇到一个特别典型的症状GPU 利用率在开始训练后的前几分钟还能跑到 90% 以上然后就开始断崖式下跌甚至掉到 20% 上下过一会又弹上去。监控面板上 CPU 占用不高内存也够磁盘读写几乎没有怎么看都不像资源不足。后来把网络监控打开才发现瓶颈在集体通信——所有 GPU 在做梯度同步的时候网络成了那个唯一堵车的路口。这个场景跟 RDMA 的关系非常直接。大模型训练本质上是一个反复执行“前向计算—反向传播—梯度同步”的过程。前向和反向都在 GPU 内部完成速度极快但梯度同步需要把每一张卡算出来的梯度数据分发给集群里的其他卡。模型越大参数越多梯度数据量就越大通信时间占比就越高。如果通信链路本身延迟高、带宽利用率低GPU 就只能干等着。传统网络在这个场景下有几个硬伤。第一个硬伤是数据拷贝太多。走 TCP/IP 协议栈数据要从用户态应用拷贝到内核态的 socket 缓冲区再经内核协议栈层层封装由网卡发出去。接收端反过来内核收完包再拷贝给应用。一次网络传输数据在内存里被抄来抄去好几遍内存带宽和 CPU 时间都被白白消耗掉了。第二个硬伤是 CPU 参与太重。网卡每收一个包就可能触发一次中断CPU 要停下来处理中断、调度协议栈、拷贝数据然后接着干活。一个 100Gbps 的网卡跑满时可以吃掉多个 CPU 核在 AI 集群里这些 CPU 核本来是要去处理数据预处理和数据加载的。第三个硬伤是延迟。TCP 的可靠传输依赖确认和重传加上内核协议栈的排队端到端延迟轻松超过 50 微秒甚至上到毫秒级。而梯度同步是一个典型的“多次往返”过程每次同步都卡在网络上几百次迭代下来累计的等待时间相当惊人。RDMA 的出发点正是把这三座大山全部搬走数据从用户态内存直接进网卡不进内核网络收发不需要 CPU 逐个包处理延迟从毫秒级降到微秒级。这也是为什么现在 AI 集群网络几乎把“必须上 RDMA”写进了默认配置里。要理解 RDMA 为什么能实现这种效果得先记住一个关键词DMA也就是 Direct Memory Access 这个名字里的 Direct。DMA 是计算机体系里很成熟的技术了——外设比如硬盘、显卡、网卡可以绕过 CPU直接读写内存。RDMA 的 “R” 把 DMA 从单机扩展到了远程让一台机器的网卡直接读写另一台机器的内存。这个“远程内存搬运”的思路就是整个 RDMA 技术体系的起点。2. 内核是最大的绊脚石传统网络收发的完整代价要真正理解 RDMA 的价值得先搞清楚传统路径上那些看不见的代价到底有多高。我用一个最简单的场景来说明机器 A 上的进程要把一个 4KB 的缓冲区发给机器 B 上的进程。在传统 TCP 路径上这个“简单”的发送动作经历的东西远超想象。应用调用 write() 或者 send() 之后数据首先从用户态地址空间拷贝到内核态的 socket 发送缓冲区内核的 TCP 协议栈把数据切片、加 TCP 头IP 层加 IP 头链路层加以太网头然后网卡驱动把这些描述符交给网卡网卡 DMA 读走真正的数据内容发到线上。接收方向更是重量级网卡 DMA 写入内核缓冲区触发中断驱动在软中断里处理收包协议栈逐层解包最终数据从内核缓冲区拷贝到用户态接收缓冲区进程才能读到。这一趟走完数据至少经历了两次完整的用户态/内核态拷贝加上若干次上下文的切换。如果你在perf的剖析结果里看到大量时间消耗在copy_user_enhanced_fast_string、tcp_v4_rcv、irq上基本就是这个路径的写照。延迟方面同样不堪。数据在内核里排队、在调度器里等待每一跳网络设备还可能引入排队延迟。当然现代 TCP 也做了很多优化比如 GSO/GRO、RSS、Busy Polling这些技术确实把 CPU 占用和延迟压低了不少但根子上的问题没解决协议栈还在内核里数据还得拷贝CPU 还是要盯着每一个包的收发。对于追求极致性能的高性能计算和 AI 训练场景这些代价是致命的。更微妙的问题是所谓的“内存搬运”语义。应用真正想要的事情其实是“把我的这段数据放到远端那位进程指定的内存里”。TCP 世界里数据必须先进入远端的内核远端应用再自己拷贝走没有任何机制能让数据直接落到应用预定的内存地址。而 RDMA 从设计上就是干这个的发送方直接在内存里准备好数据网卡自己把数据取走放到远端网卡远端网卡根据数据包里的内存地址信息直接把数据写入预先注册好的应用内存区域。全程不需要内核协议栈参与也不需要接收端 CPU 做任何事。这种“别人直接把内容写进你的内存”的做法刚开始听起来确实有点危险但其实内核和硬件做了一批机制来保证安全后面讲到内存注册的时候你会看到具体怎么防护。总之RDMA 的思路可以简单概括为一句话把内核从数据路径上彻底请出去让网卡和应用内存直接对话。所谓“零拷贝”不是不拷贝而是没有内核参与的拷贝数据只在“网卡”和“应用内存”之间搬运。3. 咬文嚼字拆 RDMARemote / Direct / Memory Access 背后的工程含义RDMA 不是单一标准而是一套能实现“远程直接内存访问”的技术家族。从工程选型的角度你必须知道市面上的主流实现有三种InfiniBand简称 IB、RoCEv2RDMA over Converged Ethernet version 2和 iWARPInternet Wide Area RDMA Protocol。InfiniBand 是根正苗红的高性能互联方案从物理层、链路层到网络层、传输层它都有自己的完整规范路由器、交换机、网卡全线都是为 RDMA 设计的。它的优点是性能、可靠性和生态都最好但缺点是贵需要专门的 IB 交换机和线缆。RoCEv2 是把 IB 的报文封装在 UDP/IP 里跑在普通以太网上。这是 AI 集群里最流行的方案原因很简单它既拿到了 RDMA 的绝大部分能力又不用抛弃数据中心里已有的以太网生态。但它有一个前提条件——底层的以太网必须做到“无损”否则一个包丢了RDMA 的效率会断崖式下跌。为了让以太网变得“无损”网络里需要开启 PFC优先级流控和 ECN显式拥塞通知机制这是最考验运维能力的地方。iWARP 是跑在 TCP 之上的 RDMA它的优势是完全利用标准 TCP 栈不用无损网络但代价是 TCP 协议栈的处理开销同样拖累了延迟和性能在 AI 高带宽场景里用得比较少。从架构角度看无论哪种实现RDMA 最终都落到几个基础概念上。首先是最核心的 QPQueue Pair可以理解为一对收发队列每个 QP 由发送队列和接收队列组成应用往发送队列里放工作请求Work RequestWR硬件排着队执行。然后是 CQCompletion Queue硬件完成一个请求之后往 CQ 里塞一个完成事件Completion应用轮询 CQ 就知道这个操作做完了。然后是 MRMemory Region。使用 RDMA 发送数据之前应用必须先把自己要读写的那段内存“注册”给网卡。注册过程会锁定物理内存页防止被换出或者被改映射同时建立硬件可以识别的内存映射表并为该内存区域生成两个关键标识lkey本地 key和 rkey远端 key。本端使用 lkey 来访问本地内存对端则必须持有 rkey 才能写数据过来。可以把它想象成把家里的保险箱装上一个只能由指定钥匙开启的锁——钥匙就是 rkey没有它远端硬件直接拒绝对应的数据包。数据类型的视角也很重要。RDMA 提供了两类基础语义一类是 SEND/RECV跟消息传递类似收发双方都要参与接收方必须提前发布接收请求数据到达时硬件自动匹配另一类是 RDMA READ 和 RDMA WRITE这是更纯粹的“内存搬运”语义发送方可以主动指定远端的内存地址直接读或写对端的内存接收方完全不知道也不关心数据就“偷偷”被放进来了。AI 集群里大部分通信走的是 RDMA WRITE 和 RDMA READ因为这类语义最贴合梯度同步和参数拉取的模式。4. 一条 RDMA READ 请求的完整旅程把“图解”落到文字上标题既然是“图解 RDMA”我用一段模拟时序来展示一次 RDMA READ 从发起到完成的全过程。为了说得具体假设节点 A 想从节点 B 的某块注册内存里读 1MB 数据到自己本地的 buffer全程不需要节点 B 的应用参与。第一步初始化。节点 B 的应用预先创建好 QP注册了一块内存区域拿到了 rkey并且通过某种带外通信方式把这个 rkey 和内存地址告诉给了节点 A 的应用。注意这个带外通道可以是 TCP、共享文件、或者分布式协调服务RDMA 本身不管这个“地址交换”的过程需要上层自己解决。第二步发布请求。节点 A 的应用把本地的目标内存地址、远端节点 B 的内存地址、rkey、数据长度等信息打包成一个 Work Request塞进节点 A 的 QP 发送队列。之后调用 doorbell门铃机制在硬件寄存器上写一笔通知网卡“队列里有新活要干”。这里的“门铃”是一个很有意思的低层机制——它不需要给网卡发完整的数据包只是往网卡的 MMIO 寄存器区写一个非常轻量的命令等于“敲个门”告诉硬件可以开工了。第三步硬件接管。节点 A 的 RDMA 网卡开始从发送队列里取出 WR解析出远端内存地址和 rkey。然后网卡通过 DMA 直接访问节点 A 的内存把请求相关元数据打包成一个或多个 RDMA 报文通过交换机送到节点 B 的网卡。第四步远端执行。节点 B 的网卡收到报文后不交给 CPU而是由硬件里的 RDMA 引擎直接解析报文用报文里的 rkey 和地址对节点 B 的内存做权限校验。校验通过后网卡直接执行 DMA 读把节点 B 内存里那 1MB 数据读出来组装成响应报文发回节点 A。第五步本地完成。节点 A 的网卡收到响应报文再次绕过 CPU通过 DMA 将数据直接写入节点 A 应用预先指定的本地内存地址。确认无误后硬件在节点 A 的 CQ完成队列里生成一个完成事件。节点 A 的应用通过轮询 CQ发现这个请求完成了直接拿到结果。我们看到整个过程中没有一次数据拷贝进入内核没有一次系统调用也没有对端 CPU 的参与。CPU 只做了三件事发起请求、敲门铃、轮询完成队列。剩余所有数据搬运全部由网卡硬件完成。这也是 RDMA 能达到微秒级延迟和接近线速带宽的根本原因。如果你用perf或者火焰图去观察一个 RDMA 程序会发现内核态 CPU 占用几乎可以忽略不计。那这个流程里最容易出问题的地方在哪里答案是内存注册管理不当、rkey 泄露、以及队列深度不够导致硬件空转。我见过不少新手把内存注册放在每一次数据发送里面做注册本身的代价要几十上百微秒直接把 RDMA 的低延迟优势抵消掉了。正确做法是程序启动时注册一块足够大的内存池之后反复复用注册一次多次使用。5. 当 RDMA 遇上 AI 集群网络RoCE 无损网络是被“逼”出来的现在把视野从单次内存搬运拉高到整个 AI 集群网络。大模型训练集群动辄几百上千台 GPU 服务器每台机器里又有 4 到 8 张 GPU。一张 GPU 上的数据要送到另一台机器的 GPU 上传统走法是GPU 显存 - 通过 PCIe 拷到 CPU 内存 - CPU 内存交给网卡发出去。这个路径里的每一次拷贝和 PCIe 往返都是延迟。于是行业里出现了 GPUDirect RDMAGDR技术网卡通过 PCIe 交换或 NVLink 桥接直接访问 GPU 显存数据全程不经过 CPU 内存。这等于把“内存搬运”的粒度从系统内存推进到了显存——两类“内存”在这里被统一看待了。在这种场景下RoCEv2 胜出是必然的。它跑在 UDP 之上既有 RDMA 的效率和硬件卸载能力又能复用已经成熟的以太网。但 RoCEv2 有一个天生的缺点建立在“底层网络不能丢包”的假设上。因为 RDMA 本身的重传是非常粗糙的一旦丢包性能可能刹不住车地往下掉从几百 Gbps 掉到几 Gbps 都不是新闻。为了让以太网“不丢包”网络界祭出了 PFC 和 ECN 两大法宝。PFC 是一种逐跳流控机制它在以太网交换机上为流量划分优先级队列。以 RoCEv2 部署为例通常会把 RoCE 流量映射到一个优先级队列再给这个队列开启 PFC。当交换机检测到某个端口的接收缓冲区快满时它会向上游端口发送一个暂停帧Pause Frame让上游暂时停止向这个端口发数据等缓冲区压力缓解后再恢复。这个机制保证了队列不会溢出丢包但也埋了一个大坑一旦一个慢速接收者把暂停帧打上去整个链路都会被卡住其他即使不拥塞的流量也会被阻塞这就是著名的“线头阻塞”Head-of-Line Blocking。ECN 则是端到端的拥塞信号。交换机发现队列深度超过阈值时不会暂停上游而是把数据包的 ECN 标志位置为 1。接收端网卡收到打了 ECN 标记的包后会生成一个拥塞通知包 CNP 回传给发送端发送端收到 CNP 后按照 DCQCN 等拥塞控制算法降速等拥塞缓解再逐步恢复速率。用生活化的话来说PFC 是“前面的路堵了后面的车全部靠边等”ECN 是“前面的路堵了后面的车先减速看情况再慢慢提速”。实际部署中通常两者搭配使用ECN 负责端到端降速PFC 作为兜底保命处理 ECN 反应不及时的极端情况。配置好 PFC 和 ECN 之后RoCE 网络被称为无损网络Lossless Network意思是在任何拥塞情况下都不应该丢包。但无损网络也导致了一个经典悖论它用巨大的缓冲区换取“不丢包”一旦拥塞无休止缓冲区被填满队列延迟持续拉高最终引发 PFC 风暴。我调试过的一个集群就出现过这种情况——一个大模型训练任务的同步操作在某些交换机上产生了大量 pause 帧计数链路利用率一塌糊涂最后发现是某些节点上的 ECN 阈值设得太严发送端提前降速反而让慢速节点的队列越积越长。看看ethtool -S里那一堆pause_rx和pause_tx计数就知道网络在承受多大的流控压力。那 AI 集群是否真的那么需要 RDMA用数字说话。GPT-3 一类的大模型单次迭代就需要在数百 GB 甚至 TB 级别的梯度数据上做全局同步如果靠传统 TCP 传输通信时间能占整个迭代时间的一半以上。而上了 RDMA 之后GPU 之间同步梯度的时间可以压到整个迭代时间的百分之几。这也是为什么现在头部 AI 训练集群铺的都是 400Gbps 或 800Gbps 的 RoCE 或者 IB 网络。可以说RDMA 不是“网络工程师的偏好”而是 AI 算力发展到一定规模后的必然选择。6. 想在家复现和上手硬件选型与最小验证环境搭建很多读者肯定会问我不搞机房能不能在本地试试 RDMA答案是能但要注意门槛。如果只是做纯软件原生体验可以用 Soft-RoCErxe模块——它用软件模拟 RDMA 硬件跑在普通以太网上。在 Ubuntu 系统里用modprobe rdma_rxe加上对应配置就能创建出一个软件 RDMA 设备然后用ibv_devinfo看到它。这种方案适合学习 API 和跑通逻辑但性能上不去因为它的本质是拿 CPU 去模拟硬件卸载。想看到真实效果最经济的方式还是用一块支持 RoCEv2 的网卡比如 Mellanox/NVIDIA ConnectX 系列或者国产厂商的支持 RoCE 的 25G/100G 网卡配合一台支持 PFC 的交换机。如果只有两台机器用网线直连也能勉强跑起来不需要交换机但这时候没有 PFC 和 ECN 保护一旦跑大流量就很容易丢包性能惨不忍睹。环境准备好之后第一件事不是写代码而是用官方工具验证链路状态。Mellanox 网卡驱动装好会附带 perftest 工具集里面有几个最常用的工具ib_write_bw测试带宽、ib_write_lat测试延迟、ib_read_bw和ib_read_lat对应 RDMA READ 操作。跑法很直接一台机做ib_write_bw -d mlx5_0 --report_gbits等服务端另一台同参数加上服务端 IP 做客户端。跑到接近网卡线速说明链路和驱动没问题。有一个调试细节我特意想提醒perftest 默认的输出信号有时会被 CPU 频率调节干扰。建议跑之前用taskset把进程绑到物理核上必要时锁定 CPU 频率否则测出来的延迟抖动会大得让人误以为硬件有问题。还要注意端口的 MTURoCEv2 一般建议开启 9000 字节巨型帧Jumbo FrameMTU 太小时同样带宽下包数更多CPU 消耗更高延迟也可能升高。如果打算再进一步读内核代码或者写书代码可以从 Linux 内核里的include/uapi/rdma/rdma_user_ioctl_cmds.h和rdma/ib_verbs.h开始看这两个头文件把 QP、CQ、MR 这些核心对象都定义得清清楚楚。代码操作流程可以概括成ibv_open_device打开设备ibv_alloc_pd申请保护域ibv_reg_mr注册内存区域ibv_create_qp创建队列对然后ibv_post_send发布请求ibv_poll_cq轮询完成。这套流程是所有 RDMA 应用的地基。7. 实测中容易踩的坑PFC 风暴、内存注册与 CPU 绑核写到这里我再分享几个在集群里实测踩过的坑每一个都是花了不少时间和算力换来的。第一个坑是 PFC 风暴。开启无损网络之后如果某条链路上的接收端处理不过来交换机就会发暂停帧反压上游。这种反压是逐跳传播的如果拥塞的根因没有解法暂停帧会一级一级传到发送端最终影响所有经过这条链路的流量。排查的时候用ethtool -S看本机和交换机端口的pause_rx/pause_tx计数或者用 Mellanox 的mlxlink工具查看链路状态数值异常增大就是 PFC 在频繁动作。缓解方案通常是调大 ECN 的标记阈值让发送端在队列满之前就主动降速避免靠 PFC 来兜底。还有一招是把 PFC 只应用在 RoCE 的优先级队列上不属于 RoCE 的流量不受暂停帧影响减小影响面。第二个坑是内存注册和锁页。RDMA 的 MR 会锁定物理内存页这意味着你的内存不能被 swap 换出。如果程序申请了一个超大 MR把物理内存的很大一部分都锁住了系统整体的页回收能力会大幅下降甚至 OOM Killer 都有可能被激活。我见过一个案例程序注册了上百 GB 的 MR结果同一台机器上的其他进程全部响应缓慢。解决思路是尽量控制 MR 的规模并且要显式地在程序退出前释放 MR。另一个相关问题是注册开销每次ibv_reg_mr都有固定成本频繁注册会让高吞吐应用很吃亏建议的做法是启动时注册一个内存池运行期在里面分配复用。第三个坑是 CPU 绑核。RDMA 模式虽然把数据路径从内核里挪出去了但用户态还是需要有一个 CPU 去轮询 CQ。这个轮询线程不能随便让操作系统调度否则延迟抖动会非常明显。我在实践中会把轮询线程绑定到网卡所在 NUMA 节点对应的物理核上同时把网络中断的 IRQ affinity 也绑定到其他空闲核防止中断和用户态轮询竞争同一颗核。这个过程通常要看/proc/interrupts找到对应网卡队列的中断号再用smp_affinity设置。数据流和用户态、内核态负载互相隔离之后延迟稳定了很多。第四个坑是 RoCE 的 VLAN 和优先级配置不对导致 PFC 不生效。很多数据中心的业务网络走的是 Trunk 口RoCEv2 流量需要带上正确的 VLAN ID同时把 802.1p 优先级设成与 PFC 开启的队列对应。如果优先级不匹配交换机上的 PFC 策略根本匹配不到 RoCE 流量丢包率一上来RDMA 性能立刻化为泡影。排查这种问题时可以先在服务器上把 RoCE 流量固定到一个独立优先级再用交换机的流控队列检查是否命中。以上这些坑都在正常工作文档里很难找到但它们才是真实环境里影响成败的关键。RDMA 之所以在 AI 时代被反复提及正是因为它把“绕开内核、直连内存”这件事做到了极致。而一旦地基挖好、网络调顺你就能看到成千上万张 GPU 在一个大模型任务里像一台巨型机器一样协同工作等待通信的时间被压缩到几乎看不见。这个感觉真正跑过大型训练任务的人都会懂。
返回列表