
1. 缘起为什么好好的 TCP/IP 突然不够用了我在做分布式存储那几年最头疼的问题不是磁盘慢而是网络慢。明明服务器里装的是闪存盘单机随机读能做到几十万 IOPS结果一走到网络层整条链路的 IOPS 直接掉到几千。问题出在哪CPU、内存、网卡三套体系在互相拖后腿——数据从应用程序的内存到对端应用程序的内存要经过用户态缓冲、内核协议栈、网卡驱动、中断处理这一路上的拷贝和上下文切换往往比真正在网线上传输的时间还长。后来我转到 AI 训练集群的运维发现这个矛盾被放得更大了。GPU 跑一轮迭代只要几十毫秒但梯度同步时如果把数据送到 TCP 协议栈里走一圈网络开销可能比计算本身还高。说白了当网络从“偶尔传点小文件”变成“每时每刻都在流动的海量数据”时TCP/IP 那个“内核代收代发”的模型就成了瓶颈。这时候RDMARemote Direct Memory Access远程直接内存访问才真正从冷门技术变成了救星。RDMA 的核心诉求非常直白让一台机器的应用可以直接读写另一台机器的内存不经过操作系统内核不经过对方 CPU也不要中间一堆拷贝。它解决的痛点说白了就是网络数据传输里的“中间商赚差价”。这篇文章我会从最底层的内存搬运讲起一直聊到它在 AI 集群网络里怎么落地全程用我实际踩过的坑和验证过的方式来讲适合正在做分布式存储、高性能计算或者大模型训练的工程师参考。2. 传统网络路径到底慢在哪2.1 一次数据发送的内在流程先别急着上 RDMA你得先理解传统网络为什么慢才知道 RDMA 省掉了什么。以最简单的 TCP 发送为例应用层调用 send() 发送数据实际操作可不像你想象的那样直接把数据丢到网卡里就完事。数据要先从应用程序的用户态缓冲区复制到内核态的 socket 发送缓冲区这个拷贝是必须的因为内核不能直接信任用户态传进来的指针。然后 TCP 协议栈开始干活切分、加 TCP 头、加 IP 头计算校验和再转给网卡驱动。网卡驱动把数据从内核缓冲区搬运到网卡自带的 DMA 描述符环里网卡才能把数据真正发出去。这个过程中还要经过多次上下文切换——应用态切到内核态处理完再切回来每一次切换都是纯开销。接收方向更离谱。数据到达网卡后网卡先要通过中断告诉 CPU“有数据来了。”CPU 得暂停手上的活儿去处理这个中断把数据从网卡 DMA 到内核缓冲区然后软中断去处理 TCP 包确认序号、重组数据最后再把数据从内核缓存复制到用户态缓冲区应用才能拿到。这是一条“应用 → 内核 → 网卡 → 网络 → 网卡 → 内核 → 应用”的链路数据被复制了至少四次上下文切换了至少四次CPU 参与到了每一次搬运中。2.2 真正拖后腿的其实是内存拷贝很多初学者以为网络慢是因为链路带宽不够实际上对于 40GbE、100GbE 的高速网络链路易损率已经很低了真正拖后腿的是内存拷贝。一次内存拷贝的吞吐量再高也经不住来回倒腾的次数太多。当网络带宽从 1GbE 提升到 100GbE 时CPU 需要搬运的数据量同步提升了 100 倍但 CPU 的主频和内存带宽却没跟上这个节奏。结果就是网络带宽翻倍CPU 先被榨干了应用层看到的吞吐量根本跑不满链路。我做性能测试时遇到过一个经典场景两台 25GbE 网卡的服务器用 iperf 打 TCP 流量只能跑到 6-7GbpsCPU 单核直接飙到 100%。链路本身完好无损但协议栈把单核 CPU 耗尽了。这时候你加再大的带宽也没用瓶颈已经不在链路上而在 CPU 参与数据搬运的能力上。RDMA 的思路就是从根本上把 CPU 从数据搬运这条路上赶走让网卡自己完成大部分工作。2.3 类比解释快递员 vs 直达专线用个通俗的类比来理解这个转变。传统网络就像你寄快递你把东西打包好用户态缓冲区快递员上门取件内核拷贝送到中转站分拣协议栈处理再运到另一个城市的中转站网络传输最后快递员派送内核拷贝到用户态收件人才能拿到包裹。整个过程每个环节都要有人经手每一手都是时间。RDMA 相当于修了一条两地之间的点对点高速传送带你的包裹直接从你的仓库送到对方仓库全程没有快递员经手没有中转站分拣。这听着很爽但代价是你得提前告诉传送带系统“我的仓库在哪、多大、什么时候能开仓接货”——这就是 RDMA 里“注册内存”和“建立连接”的核心思想。3. RDMA 的三种落地形态和它们的脾气3.1 InfiniBand、RoCE、iWARP到底选哪个RDMA 不只是某一种实现而是一族技术的统称。目前市面上主要能看到三种形态InfiniBand、RoCERDMA over Converged Ethernet融合以太网上的 RDMA、iWARPInternet Wide Area RDMA Protocol互联网广域 RDMA 协议。它们的共同点是都支持 RDMA 语义但底层承载的链路不同脾气也完全不一样。InfiniBand 是从头到尾为 RDMA 设计的专用网络硬件、协议、交换机全部是定制化性能最好延迟可以做到亚微秒级别但也有个明显的缺点——贵。一套 InfiniBand 交换机加 HCA 卡Host Channel Adapter主机通道适配器的价格比同规格以太网设备贵不少而且技术栈相对封闭跟传统以太网设备不互通。适合预算充足、追求极致性能的 HPC 中心。RoCE 是在标准以太网上实现 RDMA 的协议分 RoCEv1 和 RoCEv2 两个版本。RoCEv1 在二层以太网运行没法跨网段路由RoCEv2 把 RDMA 报文封装进 UDP/IP 包可以走三层路由灵活性大大提升。目前绝大多数 AI 集群选的都是 RoCEv2因为可以复用现成的以太网交换机和运维体系成本低一大截性能虽然不如 InfiniBand 那么极致但也足够喂饱主流 GPU 的通信需求。iWARP 则是把 RDMA 语义实现在 TCP 之上优点是兼容性最好缺点是它绕不开 TCP 协议栈的处理开销性能上限不如前两者现在用的场景越来越少了。如果完全没有特殊需求我建议直接考虑 RoCEv2这也是当下性价比最高、生态最成熟的路线。3.2 RDMA 背后的核心机制队列对、完成队列、零拷贝不管底层是哪种链路RDMA 的逻辑模型都是一样的。应用程序要和远端通信首先要创建一组队列对Queue PairQPQP 里面包含一个发送队列和一个接收队列。应用把要发送的数据描述成“工作请求”Work RequestWR丢给发送队列然后网卡自己按照描述去内存里取数据、封装、发送整个过程中应用不需要再参与。对端接收时网卡把数据直接 DMA 到预先注册好的内存区域然后在接收队列里记录一个“工作完成”Work CompletionWC事件。应用通过轮询完成队列Completion QueueCQ来感知数据是否到达。换句话说发送方和接收方做的事情都非常简单发就是“提交一个描述”收就是“轮询一个事件”真正搬数据的是网卡硬件。这个逻辑里最关键的机制是“零拷贝”。RDMA 要求应用在初始化阶段就向网卡注册一块内存区域注册时会把这块内存的物理地址映射到网卡的地址空间里。这样网卡在收发数据时直接用 DMA 读写这块物理内存不需要先把数据搬到内核缓冲也不需要 CPU 在中间做一次中转。配合“内核旁路”kernel bypass数据从应用态内存直接进入网卡再从网卡直接进入对端应用态内存全程没有一次系统调用没有一次上下文切换。3.3 到底怎么选一个快速决策表很多人问我做 AI 训练到底用 InfiniBand 还是 RoCE我给一个非常务实的判断标准维度InfiniBandRoCEv2延迟极低亚微秒级低与 IB 差距在可接受范围生态兼容专用与以太网不互通完全兼容现有以太网设施成本高交换机和网卡都贵性价比高可用普通交换机拥塞控制硬件原生支持依赖 ECN/PFC 等以太网机制配合适用场景HPC 中心、对延迟极敏感的场景AI 训练集群、分布式存储、通用高性能网络如果你是在现有机房里搭 AI 训练集群大概率会选 RoCEv2。它不完美尤其在丢包控制上比较娇气但只要配置得当RoCEv2 完全能满足大模型训练对网络吞吐和延迟的要求。4. 实操在 Linux 服务器上快速搭一套 RDMA 环境4.1 硬件选型和驱动安装注意事项实操之前先确认硬件。RDMA 不是装个驱动就能用物理网卡必须支持 RDMA 能力。NVIDIA 的 ConnectX 系列从 ConnectX-4 到 ConnectX-7是当前支持 RoCEv2 最成熟的网卡Intel 的 E810 系列也支持但生态相对没那么完善。采购时务必确认网卡型号支持 RoCE部分标着“支持 RDMA”的低端网卡只支持 iWARP买回来再折腾就麻烦了。驱动方面NVIDIA 网卡用的是 mlx5 驱动官方叫 MLNX_OFEDOpenFabrics Enterprise Distribution。安装时注意和内核版本、发行版版本的兼容性建议直接按官方软件仓库的指引来装。装完用ibstat或ibv_devinfo验证一下驱动状态看到 state 为 Active 就说明网卡已经被正确驱动起来了。4.2 注册内存区域一个最简单的实现RDMA 的零拷贝依赖内存注册这是所有后续操作的前提。在 Linux 上我们可以通过 libibverbs 库调用ibv_reg_mr()函数注册内存区域。参数有三个核心信息内存起始地址、长度、访问权限。权限至少得包含IBV_ACCESS_LOCAL_WRITE和IBV_ACCESS_REMOTE_WRITE否则对方没法往你的内存里写数据。注册完成后系统会返回一个lkey本地键和rkey远端键。lkey是本地使用网卡在本地 DMA 读写这块内存时要用rkey要告诉给对端对端拿着这个键才能直接往你的内存里写。这就相当于你把自家仓库的钥匙给了对方但只给了一把限定权限的钥匙不能打开其他房间。这里有个非常容易踩的坑注册的内存区域在 RDMA 通信期间绝对不能释放也不能改变它的用途。一旦注册了这段内存在虚拟地址、物理地址上都要保持稳定。否则网卡正在 DMA 时内存被换页或者释放轻则数据错乱重则直接 segfault而且这种问题非常难复现排查起来让人抓狂。4.3 建立连接和交换信息握手过程简化版RDMA 连接建立比 TCP 要复杂一些因为它不仅要交换 IP 地址和端口还要交换 QP 编号、内存区域信息、rkey 等信息。因为 RDMA 控制面是走软件建立的数据面才走硬件直通。最简单的用法是在两台机器上用rdma_cm库RDMA Communication ManagerRDMA 通信管理器来建立连接。用rdma_cm写程序时服务器端流程是创建 RDMA CM ID、绑定地址、监听、等待连接、接受连接。客户端流程是创建 RDMA CM ID、解析地址、连接。连接建立后两端都能通过事件回调拿到对端的 QP 信息这时候再用ibv_create_qp()创建数据面需要的 QP把 QP 信息通过已有的 CM 通道交换给对方。我自己做性能验证时会先跑一遍官方的 perftest 工具来确认链路状态。这个工具包含了ib_write_bw写带宽测试、ib_send_bw双边收发测试、ib_read_lat读延迟测试等现成工具不需要自己写代码就能快速摸清一对网卡跑到什么水平。4.4 验证 RDMA 生效的四个命令建完环境先别急着写复杂代码用这组命令快速验证# 查看网卡状态确认 link 是 Upstate 是 Active ibstat # 查看 RDMA 设备能力确认支持 RoCEv2 ibv_devinfo -v | grep -i roce # 测试两个节点间的 RDMA 写带宽服务端先跑客户端后跑 # 服务端 ib_write_bw -d mlx5_0 # 客户端 ib_write_bw -d mlx5_0 192.168.1.2 # 查看 RDMA 连接是否建立成功 rdma statistic showibstat输出里的 state 为 Active、physical state 为 LinkUp 是基本要求。如果看到 Down 或者 INIT先检查网线、光模块再看驱动日志。ib_write_bw的结果如果单线程跑到接近线速说明 RDMA 链路已经通而且性能正常发挥。rdma statistic show能看到当前活跃的 QP 数和收发字节统计用于确认程序运行时确实走了 RDMA 路径。5. AI 集群里 RDMA 到底扮演什么角色5.1 大模型训练中的通信模式AllReduce 和 AlltoAll大模型训练的通信模式说得直白点就是 GPU 之间不断在“交换梯度”和“交换激活值”。数据并行训练时每个 GPU 各自计算一部分 batch 的梯度然后需要把所有 GPU 上的梯度加起来得到全局梯度这个操作叫 AllReduce。模型并行比如张量并行、流水线并行时GPU 之间需要互相传递中间激活值这就是 AlltoAll、AllGather 等集合通信操作。通信量有多大以 1750 亿参数的大模型为例单次梯度同步就要交换几十 GB 级别的数据。如果网络是传统的 TCP/IP光是把这些梯度从 GPU 内存搬到 CPU 内存再走网络就得消耗可观的 CPU 资源和额外延迟。这也是为什么你能看到大模型训练集群里GPU 之间的互联都是动辄 400Gbps 的 InfiniBand 或 RoCEv2 网络——不是炫技是通信量已经大到不走 RDMA 根本跑不动。5.2 GPU Direct RDMA跳过 CPU 那一哆嗦AI 集群里 RDMA 还有个杀手级特性叫 GPU Direct RDMAGDR。普通的 RDMA 路径是GPU 显存 → CPU 内存 → 网卡 → 对端网卡 → CPU 内存 → 对端 GPU 显存中间还是有一道 CPU 内存的转手。GDR 直接把网卡的 DMA 引擎指向 GPU 显存的物理地址数据从 GPU 显存直接到网卡再从网卡直接进对端 GPU 显存CPU 内存完全不参与。要实现 GDR硬件层面要求网卡和 GPU 在同一棵 PCIe 树上或者至少通过高性能 PCIe Switch 互连。软件层面要启用 GPU Direct 相关的驱动参数比如 NVIDIA 官方文档里的NV_P2P_ENABLE相关环境变量。配置完成后NCCLNVIDIA Collective Communications LibraryNVIDIA 集合通信库会自动检测到 GDR 能力你不需要改代码就可以在测试日志里看到类似[0] plugin: gdr, support: 1这样的输出。GDR 带来的性能提升非常可观。我实测过一个 8 卡 A100 节点启用 GDR 后 AllReduce 带宽比纯 RDMA 路径提升 30% 以上延迟降低了 20% 微秒级别。效果虽然不是数量级的飞跃但在大模型训练这种动不动跑几百轮的场景下每轮省下来的时间累积起来非常可观。5.3 AI 集群中 RDMA 的拓扑设计要点AI 训练集群的 RDMA 网络拓扑最常见的是两层 spine-leaf 结构。叶子层连接 GPU 服务器Spine 层做无阻塞转发保证任意两台服务器之间的通信都不经历链路共享。无论是 InfiniBand 还是 RoCEv2拓扑设计的第一原则都是不要让通信路径上出现拥塞热点尤其是不能让东西向流量和南北向流量争抢链路。对于 RoCEv2 网络特别要注意 PFCPriority Flow Control优先级流量控制和 ECNExplicit Congestion Notification显式拥塞通知的配合。RoCEv2 对丢包极其敏感一个包丢失就可能引发 TCP 级别的重传风暴带宽直接断崖式下跌。所以要在交换机上为 RDMA 流量划分独立的优先级队列启用 PFC 做无损保障同时配合 ECN 做拥塞反馈让发送端动态调整速率。这一套配置如果只设 PFC 不设 ECN拥塞时容易引发 PFC 风暴导致全网链路被反向压力堵死。6. RDMA 调优的一线经验和踩坑记录6.1 带宽起不来的四个常见原因跑 RDMA 第一件事不是看配置文档而是先确认链路是不是真的“无损”。我遇到过太多次性能上不去的案例最后查来查去问题都不是出在 RDMA 本身而是底层以太网的过载丢包。原因一是 MTU 不一致。RoCEv2 依赖大 MTU 来提升带宽效率建议全链路设置 4096 字节的 MTU。如果交换机上跑了默认 1500 字节的 MTU会导致 RDMA 报文被分片带宽直接减半。这个问题的排查很简单在服务器上ping -M do -s 8972到对端能通就说明 MTU 没问题。原因二是 PFC 没配好。PFC 要求从接入交换机到核心交换机的每个端口对某个优先级队列都要启用无损配置。只要有任何一个端口漏配流量经过那个端口时一拥塞就开始丢包RDMA 的表现就是吞吐忽高忽低、延迟剧烈跳动。原因三是网卡的队列数不够。每个 QP 需要消耗网卡的硬件队列资源队列资源不足时程序创建的 QP 会被拒绝或者排队时间过长导致应用侧吞吐上不去。确认方法是通过ibv_devinfo查看max_qp、max_cq等参数和程序实际创建的数量对比。原因四是 CPU 和网卡的中断绑核没做。虽然 RDMA 的数据面不依赖 CPU但在控制面创建 QP、事件处理以及大流量初次建立连接时CPU 还是需要介入的。如果网卡中断都打在同一个 CPU 核上控制面的响应就会变慢表现为并发连接数一高延迟就飙升。6.2 拥塞控制参数怎么调才不踩雷RoCEv2 网络最核心的调优就是拥塞控制。你需要重点盯三个参数ECN 的tcp_threshold、PFC 的xoff_threshold、网卡侧的gid_index设置。其中最容易出错的是交换机的xoff_threshold——这个值设得太小PFC 会频繁触发导致大量反压帧占满链路设得太大缓冲区溢出又会在拥塞还没反馈回来之前就丢包。一个比较稳妥的取值办法先忽略 ECN把 PFC 的 xoff 阈值设置为交换机端口缓存的一半左右跑一发ib_write_bw看吞吐曲线是否平滑然后逐步调低阈值观察吞吐和延迟的 trade-off找到一个稳定点。启用 ECN 后网卡侧要开启roce.cc相关的拥塞控制算法Mellanox 卡通常用的是 DCQCNNVIDIA 新版驱动里也支持 Timely 等算法。这些参数没有一组是万能模板因为不同交换机型号的缓存大小、不同网卡芯片的响应延迟都不一样。我建议把调参过程当作一个实验流程来做先固定其他变量一次只动一个参数记录性能表现再做下一组测试。这样折腾出来的配置才是真正适合你那套环境的。6.3 多轨训练场景下 RDMA 配合 NCCL 的注意事项在大模型训练里NCCL 是实际调用 RDMA 的最上层库。NCCL 环境变量里和 RDMA 相关的有几个值得关注NCCL_IB_DISABLE是否禁用 InfiniBand、NCCL_IB_GID_INDEX设置 RoCEv2 的 GID 索引、NCCL_IB_TIMEOUT数据包传输超时参数、NCCL_IB_RETRY_CNT重传次数。最常见的坑是NCCL_IB_GID_INDEX设置不对。RoCEv2 里这个值通常要设为 3对应 IPv4 的 GID 类型。如果没设对NCCL 启动时要么报找不到设备要么性能忽上忽下。另一组容易踩的坑是NCCL_IB_TIMEOUT设置太短。大集群下流量高峰时延迟会波动超时设置太短会导致 NCCL 误判链路故障频繁触发重新建连训练直接卡死。我一般的做法是NCCL_IB_TIMEOUT22再配合NCCL_IB_RETRY_CNT7给 RDMA 链路足够的时间自我恢复。还有一点务必注意NCCL 启用 RDMA 时建议把其他网络流量管理网络、存储网络和训练流量隔离开来不要让它们共享同一个物理网卡。训练节点的管理口、存储口、RDMA 口要分开独立硬件否则存储备份等日常操作会瞬间打爆 RDMA 的 QoS 队列拖垮整个训练任务。7. 常见问题排查速查表问题现象可能原因排查命令与解决方案ib_write_bw 带宽远低于线速MTU 不一致 / PFC 未配置ping 大包测 MTU检查交换机接口 qos 配置多节点训练时 AllReduce 卡住NCCL_IB_TIMEOUT 设置太短调大超时参数检查是否有网卡降速事件偶发延迟尖峰吞吐抖动拥塞控制参数不匹配检查 ECN 阈值用 ib_write_bw 加多流压测观察创建 QP 失败资源不足网卡队列资源耗尽ibv_devinfo 查看 max_qp减少 QP 数或换高阶网卡启动时报 ibv_fork_init 相关错误进程有 fork 行为在初始化 RDMA 前调用 ibv_fork_init() 或避免 forkGDR 没生效NCCL 日志显示 gdr0GPU 和网卡不在同 PCIe switch 下检查拓扑lspci -tv 确认链路关系表格里这些场景都是我在实际维护里真真切切碰到过的不是从文档里抄来的“理论问题”。尤其第一条MTU 不匹配导致的带宽骤降我见过最离谱的一版是机房网络同事在核心交换机上调了 MTU没有同步调整接入层结果整个训练集群的 RDMA 吞吐从 190Gbps 掉到 90Gbps排查了整整一个下午最后用ping -M do一测才发现问题。8. 回到内存搬运本身的一点心得聊了这么多最后想分享一个我个人的观察。很多人一提到 RDMA 就觉得这是网络工程师的活和自己没什么关系。但我在做 AI 集群的过程中越来越觉得RDMA 的核心价值恰恰不在网络而在“内存语义”——它把网络通信简化成了一次跨节点的内存读写让分布式系统的编程模型变得干净了。我自己在调优时最受益的一个习惯是遇到性能问题永远先回到数据路径上画一遍“数据到底搬了几次”。很多看似复杂的问题比如带宽翻不上去、延迟抖动追根到底都是数据路径上多了一道无谓的拷贝或者某个环节的缓冲配置不对。RDMA 的价值就是用硬件把这条路径压缩到最短而你作为使用者和维护者要做的事情就是别再利用软件把它人为拉长。如果你正在搭建自己的第一套 RDMA 环境我的建议是从最简单的两台机器跑通ib_write_bw开始先让链路通、让性能曲线稳定下来再去碰 NCCL、GDR、多轨拓扑这些复杂玩意儿。先学会看底层数据再往上层发展你后面踩坑的概率会小很多。