尧图精选

梯度搬家:从GPU显存到对端显存的RDMA全路径解析

🕒 发布时间:2026/9/15 23:28:10 📁 来源:尧图网络
分布式训练调多了你会发现一个很朴素的事实所谓大模型训练拆到最底层就是两个动作——算梯度和搬梯度。算子优化解决“算得慢”通信库解决“搬不动”。今天这篇聊的就是后者最核心的一个动作一块梯度从我这台机器的GPU显存里怎么经过RDMA落到另一台机器的GPU显存里。在AI Infra这个系列里前几课讲完了单卡上的反向传播、梯度累积、梯度裁剪这些“算梯度”的环节这一课进入通信闭环。标题里的“Loop2”指的是一次训练迭代里第二个关键循环——数据并行下梯度在环Ring上跨机传输的完整数据面流程。你可以先这样理解第一个Loop是单卡内部“前向→反向→更新”的计算循环第二个Loop是跨卡之间“梯度归约→梯度广播”的通信循环。如果只懂前者不懂后者遇到多卡训练性能上不去的时候你会连问题出在网卡还是GPU都分不清。这篇文章适合三类人正在调多卡训练脚本但看不懂NCCL日志的算法工程师刚转AI Infra方向、想补RDMA知识的研发以及做推理训练集群运维、想搞明白网卡计数器含义的SRE。我会把从“GPU显存里的梯度”到“对端GPU显存里的梯度”这条完整路径拆开讲不绕弯子。1. 为什么梯度必须“搬家”数据并行和AllReduce逼出来的刚性需求1.1 每张卡手里的梯度只是一块“局部视图”很多人训练时只管发torch.distributed.launch并不清楚数据并行底层发生了什么。假设你用32张卡训练每张卡拿到的输入数据是同一个大batch切成32份中的一份。前向传播算出各自的loss反向传播算出各自的梯度——但请注意这32份梯度是不一样的因为每张卡看到的样本不同、模型输入不同反向传播路径也不同。如果这时候每张卡直接拿自己的梯度去更新参数会出现什么情况每张卡上维护的模型副本会沿着各自的方向漂移。训练几轮之后32份模型参数各不相同推理时随机路由到任意一张卡行为都可能有巨大差异。模型分布式训练的正确性建立在“所有副本随时保持一致”这个前提上。而这个一致性靠的就是梯度AllReduce。AllReduce这个术语很多人听说过但真问起来往往说不清。它的本质是一个集体通信操作所有参与训练的进程rank各自提供一个输入张量这里就是梯度系统对这些张量进行某种归约操作默认是求和再取平均然后把归约结果广播回每一个rank。完成之后每个rank手里拿到的梯度是全量一致的。1.2 算一笔账7B模型一个迭代要搬多少数据先别急着看通信库代码我们把账算明白。假设一个70亿参数的模型混合精度训练下梯度与激活值的主流精度是BF16每个参数对应2字节梯度。当一个训练step完成反向传播后全量梯度的体积大约是7,000,000,000 × 2 字节 ≈ 14GB也就是说每张卡在反向结束那一刻手里有14GB的梯度数据需要和其他31张卡做AllReduce。32张卡的集群里如果采用最朴素的“每张卡都发给参数服务器”方案参数服务器的入方向带宽会被撑爆——31张卡同时灌14GB就算单机有400Gbps网卡光收数据就要接近9秒这还不算归约和广播回程。所以现代训练框架绝对不会这样做。它们用的是Ring AllReduce总通信量是2(N-1)/N倍模型数据量即约28GB对32卡而言。每一张卡均匀承担不存在热点。这也是为什么梯度必须“搬家”——它要沿着环一路传下去每经过一个rank就做一次局部归约最终让每张卡都拿到全局结果。1.3 Ring AllReduce梯度在环上接力跑Ring AllReduce分两个阶段。第一阶段叫reduce-scatter归约-散布。把所有rank排成一个逻辑环每个rank只和前后邻居通信。把14GB梯度按rank数切分成N块第i轮中rank j把自己的第k块数据加邻居传过来的对应块再传给下一个邻居。N-1轮之后每个rank手里有某一个数据块的“全局归约结果”但只有其中一块。第二阶段叫all-gather全收集。把每个rank手里的完整归约块沿着环广播出去同样需要N-1轮。全部完成后每个rank手里就拥有了所有块的全局归约结果。我在工程里理解Ring AllReduce有个心得核心并不在reduce而在ring。只要环的顺序、数据切分的粒度、数据流动的方向确定下来归约操作只是每经过一个节点时“顺路”算掉的。这条环上跑的数据量非常大每一跳都要完整经过“发送侧网卡→链路→接收侧网卡→接收buffer”这条物理通道。通道效率就决定了Ring AllReduce的最终性能。而RDMA就是让这条通道跑满的关键。2. TCP的瓶颈与RDMA的破局点为什么AI集群非它不可2.1 传统内核协议栈有多“吃力不讨好”假如用传统TCP把14GB梯度从一张卡搬到另一张卡数据路径是这样的用户态程序调用send()数据先进入socket缓冲区内核协议栈开始干活——TCP分段、校验和计算、拥塞控制、路由查找然后数据被DMA到网卡发送队列对端网卡收到后数据先进内核接收缓冲区经过协议栈重组最终被recv()拷进用户态缓冲区。这条路径的问题不只是慢而是每一层都在制造拷贝和上下文切换。TCP下发生一次发送数据从应用内存到网卡中间至少经历一次用户态到内核态的拷贝一次DMA到网卡接收端则要经历DMA到内核buffer、再拷贝到用户buffer的两次搬运。更致命的是CPU参与度。内核协议栈对每个数据包都要做校验、维护TCP状态、处理ACK、触发中断。100Gbps的流量如果每个包只有标准大小每秒要处理上亿个数据包CPU很快被打满。训练过程中正好是GPU刚算完反向、最需要通信跟上的时候CPU却忙不过来结果就是通信时间被拉长GPU进入空等状态。2.2 RDMA的三板斧kernel bypass、zero-copy、CPU offloadRDMARemote Direct Memory Access解决上述问题的方式非常彻底核心设计理念可以归纳为三个词Kernel bypass绕过内核应用进程在用户态直接把数据发送指令提交给网卡硬件不需要进入操作系统内核协议栈。指令通过网卡驱动映射到用户态的内存区域来下发省掉系统调用和上下文切换。Zero-copy零拷贝发送端网卡直接从用户态应用缓冲区DMA读取数据接收端网卡直接把数据DMA写入用户态预先注册好的缓冲区。整条链路里内核不碰数据用户内存到网卡之间只有一次DMA搬运。CPU offload把CPU踢出局传输确认、超时重传、流量控制、乱序重组这些TCP/IP协议栈的职责全部由网卡硬件完成。CPU从头到尾不需要知道数据何时发出、何时到达。这三板斧合在一起的效果是CPU变成了下发指令的角色数据搬运全部由网卡和内存控制器的DMA引擎完成。梯度传输的路径上CPU不再需要为每个数据包忙碌它可以去处理下一个计算任务。2.3 帮你理解的一个类比你搬家的时候有两种方式。一种是自己租一辆货车亲自开着去新家每过一个路口要等红绿灯遇到堵车要自己绕路到了目的地还要自己扛箱子搬上楼——这是TCP路上每一步都需要你参与。另一种是找搬家公司你只告诉师傅“从A地址把这几箱东西搬到B地址放客厅”然后你就可以去做别的事了。师傅自己规划路线自己装卸自己处理路上各种状况到了地方直接把箱子放进指定位置。你全程只下了一次指令没有比这更省心的了——这是RDMA。“你”就是CPU。GPU算完反向已经消耗了大量算力通信这件事如果还要占用CPU资源训练效率一定会受影响。RDMA把CPU从数据搬运里解放出来这个类比虽然简单但理解了它后面看QP、MR、WQE这些概念时不容易被绕晕。3. 一块梯度从本卡显存到对端显存的完整数据路径3.1 梯度在显存里如何“打包待命”反向传播结束后每一个参与训练的参数张量都会有一个对应的梯度张量存放在GPU显存的某个地址上。这些梯度在显存里的分布是零散的——参数A的梯度在地址a参数B的梯度在地址b。如果逐个参数地去触发通信会产生大量小消息传输效率极低。所以NCCL、PyTorch DDP这类库的关键一步是梯度分桶gradient bucket。把模型参数按照某种规则一般是参数创建顺序聚合到连续的内存buffer里一个bucket可能对应几十上百个参数的梯度。分桶有三个目的减少通信次数避免小消息爆炸让反向传播计算到某个bucket时该bucket的梯度一就绪就可以立刻发起通信实现计算与通信重叠连续内存段可以在RDMA里一次性注册和传输以PyTorch DDP为例bucket默认大小是25MB早期版本也就是说反向传播跑到某个bucket覆盖的梯度都算完之后这个bucket会立即触发一次AllReduce不需要等整个模型反向跑完。这就是DDP能比朴素的“全模型同步”快很多的原因。3.2 GPUDirect RDMA让网卡直接读显存梯度在显存里“打包待命”之后接下来要考虑的是怎么把它弄到网卡上。如果集群不支持GPUDirect RDMAGDR传输路径是这样的GPU显存里的梯度先通过PCIe或NVLink拷贝到主机内存Host Memory网卡再从主机内存DMA读取出去。这中间多了一次“显存→DDR”的搬动而且这次搬动需要CPU发起一个固定的DMA拷贝操作。GDR的意义在于它让网卡通过PCIe总线直接访问GPU显存地址。也就是说网卡发起DMA读内存操作时目标地址可以对准显存中的梯度buffer位置数据直接“显存→网卡内存→链路”完全跳过主机内存。你可以把显存比作一个仓库主机内存比作仓库门口的中转站。没有GDR时货物要先从仓库搬到中转站再从站台装车有GDR后卡车直接开进仓库装货省了一段运输和一次装卸。这个提升在大模型训练里非常显著。梯度数据量动辄GB级别一次多余的主机内存中转意味着整条链路多了几十GB的PCIe/NVLink流量。NCCL里GDR是一个独立的开关NCCL_NET_GDR_LEVEL大部分主流GPU服务器默认开启但如果你在云主机或虚拟化环境里跑训练要确认这个路径是否真的生效。3.3 物理链路InfiniBand与RoCE网卡从显存读到的数据要走物理链路到对端。RDMA可以承载在两种物理网络上AI集群里都会遇到InfiniBandIB从物理层到传输层完全为RDMA设计网卡、交换机、线缆配套使用语义最完整性能也最稳定。IB网络里还有一个叫子网管理器Subnet ManagerSM的组件负责为所有端口分配LID地址、建路路由表。如果SM配置有问题能看到端口状态始终不是Active但链路看起来是通的。RoCERDMA over Converged Ethernet在以太网上跑RDMA协议v2版本用UDP封装。成本比IB低一个量级能复用现有交换机。但RoCE对网络质量要求极高需要启用PFC优先级流控或ECN显式拥塞通知来保证“无损”或“近无损”转发配置复杂流控参数调不好会影响性能。在NCCL日志里能看到当前通信走的是IB网卡还是RoCE网卡命令ibstat或ibdev2netdev能看到每个IB设备对应的端口、速率、链路状态。3.4 对端网卡的“逆行流程”数据在线缆里以电信号或光信号传到对端网卡后对端的处理路径是网卡识别出数据包属于哪个QP队列对检查包的完整性然后直接DMA写入接收端预先注册好的内存区域一个特定的显存buffer或主机内存buffer这一切都不经过接收端CPU。数据落地后接收端的GPU在下一轮归约操作里直接从这个buffer取数参与运算。发送端软件视角只做了一件事调用一次post_send接收端软件视角也做了一件事提前调用post_recv。中间的过程完全由网卡硬件完成。所以整体来看一块梯度的搬家路径就是GPU显存 →DMA Read→ 发送端网卡 → 链路传输 → 接收端网卡 →DMA Write→ 接收端显存整条链路可以做到全程无需CPU参与数据搬移。这也是为什么RDMA在AI Infra里如此重要——它是当前唯一能在大规模集群中把“通信时间/计算时间”比压到足够低的技术方案。4. 拆开QPRDMA连接的最小单元到底长什么样4.1 QP是硬件里的队列不是软件对象如果你去看NCCL的初始化日志会看到OpenIB设备、创建QP、交换QP号的过程。QPQueue Pair队列对是RDMA通信的核心抽象由两个队列组成发送队列Send Queue, SQ和接收队列Receive Queue, RQ。把它理解成一条TCP连接没问题功能上很像但实现完全不同TCP连接由操作系统内核管理是一个软件对象QP则是一组硬件队列直接存在于网卡内部。数据发送的时候CPU把指令写进网卡的发送队列网卡自己取出指令、自己完成数据的DMA读取和发送。CPU和网卡之间的交互被最小化到“提交WQE”和“读取CQE”两个动作。在分布式训练的环状拓扑里每个rank要和前后邻居建立RDMA连接也就是要创建多个QP。你可以想象一个32卡的Ring每个rank通常至少和两个邻居各建一个QP实际NCCL会因为拓扑优化建立更多QP来提升带宽。4.2 WQE和CQE给网卡下命令、收完成通知RDMA编程模型里软件和网卡交互主要靠两个结构WQEWork Queue Element工作队列元素。应用调用ibv_post_send时其实就是把一个WQE提交到SQ内容包含操作类型发送/接收/读/写、内存地址、长度、对端QP号、rkey等信息。网卡看到WQE后自己去执行它。CQECompletion Queue Element完成队列元素。网卡处理完一个WQE后在你的CQ完成队列里写入一条CQE表示“这个操作已经完成”。对通信库设计者来说CQE的获取策略非常关键。一种方式是中断通知网卡写CQE后触发CPU中断CPU响应另一种方式是轮询pollingCPU在CQ上不断地检查有没有新的CQE。训练场景下为了降低延迟NCCL使用了忙轮询busy polling——把CPU核空转在CQ上等待完成事件虽然占满了一个核但把从“网卡完成”到“CPU感知”的延迟压到微秒级。NCCL一张卡通常会单独绑定一个CPU核去做通信轮询这就是为什么你会看到NCCL初始化时CPU占用率高而且你调整NCCL_IB_DISABLE_CUDA_CACHE之类的参数会影响性能。4.3 从RESET到RTS一次连接怎么建起来RDMA连接建立的关键点是数据平面的连接建立依赖控制平面先交换元数据。RDMA QP有一个状态机核心状态包括RESETQP刚被创建什么都不能做INIT初始化完成可以配置QP属性但还不能收发数据RTRReady to Receive可以接收数据了RTSReady to Send可以发送数据了到底怎么从RESET走到RTS完整的流程是这样通信双方先通过某种带外通道一般是传统TCP连接交换QP信息——包括QP号、本端的GIDGlobal ID用于寻址、内存区域地址和rkey。双方拿到对方信息后各自把QP从RESET推进到INIT再设置好对端地址和内存信息推进到RTR然后再到RTS。到这一步连接才真正建立成功。此后数据平面就可以全程不经过任何CPU处理地搬运数据了。你检查一个RDMA连接是否正常时有一段代码段会很有用查看QP状态使用工具如rdma res show qp来列出QP的状态如果大量QP处于RESET而不是RTS说明建链阶段出了问题——最常见的是控制面握手信息没交换成功导致两端各等各的。4.4 内存注册MRrkey错了会让你崩溃RDMA数据平面的核心原则是“网卡直接访问内存”但网卡怎么知道哪块内存是合法的答案就是内存注册Memory RegistrationMR。发送/接收数据前应用要先把内存buffer注册给网卡驱动注册过程会锁定这段内存不许操作系统换页并分配一个rkeyRemote Key。发送端在WQE里携带rkey接收端网卡校验rkey合法后才允许把数据DMA写入缓冲区。注册内存是昂贵的它涉及页锁定、DMA地址映射、硬件状态更新。如果每次传输都注册新内存性能会大打折扣。所以高性能通信库的策略是启动时一次性注册整个通信buffer之后反复复用。NCCL初始化时就会把需要的设备内存和主机内存注册好训练全程不注销。如果你自己写RDMA通信代码遇到invalid rkey或remote access error这类错误十有八九是rkey不匹配或MR被提前注销、buffer地址发生了变化。排查思路很简单确认rkey的来源确认传输期间MR保持有效。5. 流量大了怎么扛分片、credit与多QP并行5.1 MTU、分片与流水线一块梯度可能达到几十MB甚至上百MB。RDMA网卡不管数据多大最终落到链路上都要按MTU最大传输单元拆成小包。IB的典型MTU是4096字节4KBRoCE是1500字节或9000字节巨帧。MTU拆包对用户透明但你必须在应用层把大张量切分成合适的chunk大小配合Ring环的流式传输。原因很简单如果把14GB梯度当成一个整体在Ring上“发完再算”那每一跳都要等发送端完全发完才能开始本地归约传输链路有空闲期效率极低。实际做法是把梯度张量切分成多个chunk让这些chunk在环上形成流水线。同一时刻一个chunk正在对端链路上传输另一个chunk正本卡归约还有一个chunk正在被接收。这样“发送、传输、接收、归约”四个阶段可以重叠执行链路利用率才能逼近100%。5.2 credit机制接收缓冲是有限的不能无限发RDMA接收端是直接把数据DMA写入预先注册的buffer。这个buffer的大小是有限的如果发送端无节制地灌数据接收端buffer满了之后后续数据无处安放只能丢弃。RDMA传输层防这件事的机制是credit信用流控。可以这样理解接收端预先告诉发送端“我这里给你准备了N个block的接收空间”发送端每发一个blockcredit就减一当credit用尽发送端必须停下来等待接收端处理完buffer并返还credit。这套机制保证了发送速率和接收buffer容量匹配不会出现buffer溢出。训练场景下credit管理在NCCL的协议选择里扮演重要角色。NCCL的Simple协议、Low LatencyLL协议、LL128协议本质区别之一就是对消息切分、credit同步和完成通知的处理策略不同Simple协议适合大消息带宽利用充分LL/LL128适合小消息延迟更低但有一定带宽开销。选择哪个协议需要针对具体硬件实测不能盲信默认值。5.3 多QP并行单条路不够就修几条单条QP的带宽是有上限的。受限于网卡队列深度、接收端处理速度、PCIe通道带宽单QP很难在现代高速网络上拉满线速。NCCL的做法是rank与rank之间建立多条QP配合多个CQ、多个通信线程把数据流分散到多条并行路径上。你在NCCL输出里经常能看到类似“2/4/8/16”的通道数信息这些通道会分摊到不同的QP上。通道数越多并行度越高但也会消耗更多CPU资源和网卡队列资源。所以并不是盲目调大NCCL通道数就一定能变快需要观察带宽和CPU占用曲线来找到平衡点。多QP并行本质上是“分区通信”它和Ring AllReduce的组合是分布式训练通信库的核心性能手段。这些细节看起来很深但作为AI Infra的工程师你必须知道你能调的最有力旋钮不在模型代码里而在网卡的队列结构和协议选择里。6. 实战排障我踩过的坑和一份自检清单6.1 现象一NCCL测试带宽只有标称的60%有一次我拿到一批新服务器跑all_reduce_perf测试标称200Gbps的IB网络实测带宽只有120Gbps左右非常让人沮丧。排查过程是这样的先跑nvidia-smi topo -m看GPU和网卡的拓扑关系发现这台机器的GPU之间NVLink全连接但GPU到IB网卡的PCIe路径和另一个GPU共享了同一个PCIe Switch。多个GPU同时发起RDMA通信时数据在PCIe Switch处产生争抢带宽直接被砍半。然后是NUMA binding的问题。网卡插在某个CPU的PCIe槽位上如果通信线程绑定到了另一个CPU的核上跨NUMA访问内存会引入额外延迟。调整NCCL_IB_DISABLE缓存、绑核参数、环顺序实测带宽才慢慢回到160Gbps以上。这类问题的共性原因有三类PCIe拓扑争抢、NUMA绑定错误、RoCE流控参数异常。排查顺序是先拓扑后参数。6.2 现象二小消息延迟高看起来像“网络不行”有段时间训练一个embedding很大的推荐模型每个step的通信量不大但整个训练慢得离谱。一开始怀疑网络坏了但ib_write_bw裸测延迟和带宽都正常。后来分析发现问题出在消息切得太细。embedding参数梯度数量非常多但每个都很小通信启动开销WQE提交、CQ轮询、credit同步、环同步等待远比实际传输耗时高。这也解释了为什么PyTorch DDP要做梯度分桶——就是在规避这类问题。解决思路是调大bucket size让更多小梯度聚合后再通信同时考虑用LL协议压低单次通信的延迟开销。实测调整后训练耗时明显下降。6.3 现象三偶发超时重跑就好过会儿又出现这类问题最折磨人。训练跑着跑着某个rank报NCCL timeout退出来重跑可能几十个step正常然后又随机超时。第一次遇到时走了很多弯路——怀疑过GPU故障、内存故障、驱动bug最后靠计数器揪出了真凶。方法不复杂看网卡计数器。cat /sys/class/infiniband/*/ports/*/counters/*重点看link_dropped、rpc_errors、xmit_wait这些计数。配合交换机的ECN标记计数、PFC暂停帧计数能定位到具体是哪个端口在拥塞、哪条链路在丢包。我发现过一次问题是机房光模块衰耗过大导致的偶发误码网卡触发重传后虽然没断链但训练的超时机制已经被打破。这种问题用ibping长时间ping也能测出来但一定要把时间拉长到小时级别。6.4 我整理的一份快速自检清单检查项工具/命令预期结果GPU/网卡拓扑关系nvidia-smi topo -m网卡与对应GPU同NUMA或通过NVSwitch可达无跨PCIe Switch争抢IB链路状态ibstat、ibv_devinfo链路速率符合预期如HDR 200Gbps状态为ACTIVE裸RDMA带宽ib_write_bw接近线速两端双向都要测重传/丢包计数sysfs counters长时间运行无明显增长NCCL归约性能all_reduce_perf多卡扩展性符合预期带宽随卡数提升RoCE流控状态rdma stat show、ECN/PFC计数无异常标记风暴或PFC暂停帧暴增这份清单我每次遇到“分布式训练慢”都会先跑一遍。绝大多数环境级问题都能在十分钟内定位出来。真实工作中模型代码的bug反而好修网络和拓扑层面的问题才是让人掉头发的地方。AI Infra的调试本质就是一层一层剥洋葱。今天聊的这条“梯度搬家”路径看起来只是一小块知识点但它是理解NCCL通信原理、多机训练性能调优、甚至集群故障排查的基石。你知道了数据从哪里来、经过哪些硬件、需要哪些握手信息和资源注册再遇到网卡计数器上的异常数字就不会一头雾水了。最后再分享一个小扩展思路梯度在RDMA链路上传输时如果做一次多机多卡训练除了环状AllReduce你还会遇到树形Tree归约、全对全AlltoAll通信等算法。它们在不同网络拓扑和消息大小下各有优势下一课可以沿着“归约算法选择”这个话题继续展开。到时候你会发现理解了今天这条数据路径那些算法的性能差异看起来就顺理成章了。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →