
摘要
大模型训练早已不是"堆 GPU"就能解决的问题:成百上千张卡之间的梯度同步(All-Reduce、All-Gather)对网络的带宽、时延和丢包率极度敏感,网络反而成了压榨算力的最后一道瓶颈。RoCEv2(RDMA over Converged Ethernet v2)把 RDMA 的低时延、低 CPU 开销能力搬到了标准以太网和 IP 网络之上,配合 PFC、ECN、DCQCN 三大机制构建"无损网络",已经成为千卡乃至万卡级大模型训练集群的主流互联方案之一。本文从原理讲起,梳理网络规划思路,并给出从交换机、网卡到应用层的完整配置流程,交换机以华为设备为例,网卡以主流的 NVIDIA/Mellanox ConnectX 系列智能网卡为例。
背景与问题
大模型训练的分布式并行(数据并行、张量并行、流水线并行)需要 GPU 之间频繁交换梯度和中间激活值,这些通信集中在训练的每一步里发生,稍有延迟就会让整卡整卡的算力干等。
问题在于:以太网天生是"有损"的------一旦交换机队列拥塞,就会直接丢包,而 TCP/IP 协议栈的拷贝和中断开销又会进一步放大时延。RDMA(远程直接内存访问)可以绕过操作系统内核和 CPU,让网卡直接读写对端内存,把时延压到微秒级;但 RDMA 原本运行在 InfiniBand 这种专用网络上,价格贵、生态封闭。
RoCEv2 的意义就是把 RDMA 的能力"嫁接"到大家更熟悉、更便宜、更开放的以太网和 IP 网络上:它把 RDMA 报文封装进 UDP/IP,使用 IANA 保留的 4791 端口,这样报文既能享受 RDMA 的高效传输,又能像普通 IP 报文一样被三层路由转发,具备了跨机架、跨 Pod 组网的能力(这也是 RoCEv2 相对只能在二层工作的 RoCEv1 的关键进步)。
但代价是:以太网本身不保证不丢包,而 RDMA 协议栈对丢包极其敏感------在 400G 链路上,哪怕只丢一个包,都可能触发整个发送窗口的重传,造成以秒级计的 GPU 空闲。所以要在以太网上跑好 RoCEv2,必须先把网络改造成事实上的"无损网络"。
核心思路与优势
无损网络主要依赖三个互相配合的机制,可以理解成"三道防线":
PFC(Priority Flow Control,基于优先级的流量控制):当某个优先级队列快满的时候,接收端向发送端逐跳发送"暂停"帧,让上游先别发了,从而避免丢包。它的问题是作用比较"粗暴"------一暂停就是整条链路上该优先级的所有流量都停,容易把拥塞逐跳向上游传导,甚至在环形依赖下引发死锁。
ECN(Explicit Congestion Notification,显式拥塞通知):在交换机队列长度触达阈值时,不直接丢包,而是在报文头打上"拥塞发生"标记;接收端网卡看到标记后,会给发送端回一个拥塞通知包(CNP),提示对方主动降速。相比 PFC 的一刀切,ECN 更早、更精细地介入。
DCQCN(Data Center Quantized Congestion Notification):这是端到端的闭环拥塞控制算法,发送端网卡收到 CNP 后,按算法量化调整发送速率,之后再逐步恢复。可以理解为 ECN 负责"发现问题",DCQCN 负责"解决问题",而 PFC 是兜底的最后一道安全网------理想状态下 ECN+DCQCN 就能把拥塞控制住,PFC 极少真正触发。
三者配合的关键在于阈值设计:ECN 的标记阈值必须明显低于 PFC 的暂停阈值(Xoff),这样才能留出时间让 DCQCN 先把发送速率降下来,避免每次拥塞都直接触发 PFC 暂停、甚至造成暂停帧在多台交换机间循环等待的死锁。
和 InfiniBand 相比,RoCEv2 的优势是生态更开放、交换机和网卡选择更多、单端口成本更低;短板是配置和运维复杂度更高、多租户隔离和故障定位工具链的成熟度目前还有差距。所以现阶段的经验规律大致是:千卡级集群用 RoCEv2 性价比更高,追求万卡级极致规模和稳定性时,InfiniBand 仍是更成熟的选择------但这个边界正随着 RoCEv2 生态的演进不断被推高。
面向人群
- 负责规划和搭建 AI 训练集群网络的基础设施工程师、网络工程师
- 需要为大模型训练/推理集群选型(RoCEv2 vs InfiniBand)的架构师
- 已经用上 RoCEv2 但训练吞吐上不去、想系统排查 PFC/ECN 配置的运维人员
- 希望系统了解无损以太网原理的后端/AI Infra 工程师
实践步骤
一、网络规划
1. 拓扑选型:三层无收敛 CLOS
大规模训练网络普遍采用 Spine-Leaf(或加一层 SuperSpine 的三层 CLOS)架构:服务器接入 Leaf 交换机,Leaf 再全连接到 Spine。规划时要重点关注收敛比------即交换机下行(连服务器)带宽与上行(连 Spine)带宽的比例。训练网络通常要求做到 1:1 无收敛,避免多台服务器同时往上游打流量时在 Spine 层产生拥塞热点。
2. Rail-optimized 设计
单台 GPU 服务器一般配备多张网卡(比如 8 张,与 8 张 GPU 一一对应)。Rail-optimized 的做法是:所有服务器上编号相同的网卡(比如都是 0 号网卡)接入同一台 Leaf/ToR 交换机,编号为 1 的网卡接入另一台,以此类推。这样同号 GPU 之间的跨机通信只需要经过同一台 Leaf 交换机,跳数最少、延迟最低,也天然把不同"轨道"(Rail)的流量隔离开,减少互相争抢。
3. 双网分离
GPU 间 RDMA 通信流量和业务/管理流量(SSH、监控、存储访问等)建议物理或逻辑分离,各走各的网络平面,避免管理流量的突发抢占训练流量的缓存和带宽,也方便分别做 QoS 和故障定位。
4. ToR 双归与可靠性
有条件的话,服务器网卡可以双归到两台 ToR 交换机,任意一台故障只影响一半带宽而不是整机下线;GPU Server 内的多张网卡全部下联同一台 ToR,即使这台 ToR 故障,受影响的服务器数量也是可控的,反而比"打散接入多台 ToR"更容易做故障隔离和快速定位。
5. 端到端一致性
无损网络能不能生效,取决于从服务器网卡、Leaf、Spine 到所有中间设备的 DSCP/优先级映射、PFC 优先级、ECN 阈值是否完全一致。只要有一段链路没有正确配置,无损链路就会在那个节点"破功",所以规划阶段就要把优先级和阈值方案写成统一的标准,交换机和网卡两侧对齐执行。
二、交换机配置(以华为交换机为例)
重要提示 :不同型号、不同软件版本的华为交换机,命令关键字和支持范围可能存在差异(例如
dcb pfc dscp-mapping enable仅部分单板支持)。以下流程是对照华为官方《智能无损网络配置指南》《命令参考》的功能描述,并与多份独立技术资料交叉核对整理得出,不是凭记忆臆造;但在生产环境执行前,仍必须打开你所用设备型号、软件版本对应的官方命令参考逐条核实语法和取值范围,并先在测试环境验证一遍,再上生产。
第 1 步:创建 ECN(WRED)拥塞控制模板
在系统视图下创建一个 drop-profile,定义队列在什么长度区间开始按百分比标记 ECN:
bash
system-view
drop-profile dcqcn
color green low-limit 1 high-limit 30 discard-percentage 2
commit
这条 color 命令有两种写法:上面用的是百分比 形式,low-limit 1 high-limit 30 表示队列缓存占用从 1% 涨到 30% 这个区间内,按 discard-percentage 指定的 2% 概率对绿色报文做 ECN 标记(队列越满,标记越密);另一种是绝对值形式,写成 color green buffer-size low-limit <低阈值> high-limit <高阈值> discard-percentage <概率>,直接指定缓存大小。具体取值需要结合网卡缓存、链路带宽时延积去压测调优,不能照抄。
第 2 步:为 RDMA 流量分配 PFC 优先级
在全局的 PFC 视图下,指定用于 RoCEv2 流量的优先级队列(示例中用优先级 3):
bash
dcb pfc
priority 3
第 3 步:在接口上信任 DSCP 并绑定 ECN 模板
进入连接 GPU 服务器/其他交换机的接口视图,让接口按 DSCP 值映射优先级,并把队列 3 绑定到刚才创建的 ECN 模板:
bash
interface 100GE1/0/1
trust dscp
qos queue 3 wred dcqcn
qos queue 3 ecn
commit
注意 qos queue ecn 在不同系列/版本上有两种形态:一种是这里用的"先用 qos queue <n> wred <模板名> 绑定模板、再用 qos queue <n> ecn 打开 ECN";另一种是把模板名直接写进 ECN 命令,形如 qos queue <n> ecn <模板名>。执行前用 ? 补全看一下自己设备支持哪种写法,不要两种混着敲。另外,如果设备上启用了 AI ECN(动态阈值)功能,它与这里的静态 ECN 阈值是互斥的,不能同时配在同一个队列上。
第 4 步:在接口上使能 PFC,并设置缓存阈值
bash
interface 100GE1/0/1
dcb pfc enable mode manual
dcb pfc buffer 3 xoff dynamic 6 hdrm 330
commit
这条命令的完整语法是 dcb pfc buffer <队列号> { xoff static <值> | xoff dynamic <权重> | xon offset <值> | hdrm <值> },几个阈值的含义:
xoff:触发发送 PFC 暂停帧的队列水位阈值。它有两种写法,必须二选一,不能省略关键字 ------xoff static <值>是写死的绝对阈值,xoff dynamic <权重>是按当前可用共享缓存动态计算(权重越大,允许占用的缓存越多)。大规模训练网络里更常用dynamic,因为多端口同时拥塞时它能自适应分配缓存。xon offset:注意不是直接写"恢复阈值",而是相对xoff的回差偏移量 ,实际的停止反压阈值取xon与xoff 减去该偏移量两者中较大的那个。hdrm(headroom):从发出暂停帧到对端真正停下来这段时间里,为了不丢在途报文而预留的缓存空间,和链路长度、端口速率直接相关。
这几个值后面还可以跟 cells/bytes/kbytes/mbytes 指定单位(不写则用设备默认单位,通常是 cell),例如 dcb pfc buffer 3 xoff dynamic 4 hdrm 75 kbytes。具体数值必须结合端口速率、线缆长度带来的时延、芯片缓存大小重新计算,示例数值只是示意,不能直接套用到不同型号的交换机上。
第 5 步(可选):让 PFC 按 DSCP 映射后的优先级反压
如果希望 PFC 的背压依据 DSCP 映射后的优先级而不是原始 802.1p 优先级生效,可以在支持的单板上全局开启:
bash
dcb pfc dscp-mapping enable
执行前请先确认自己的单板型号是否在支持列表内,不支持的单板执行会失败或不生效。
第 6 步:配置 PFC 死锁检测与恢复
PFC 逐跳暂停的机制在环形拓扑或配置不当的情况下有死锁风险,建议同步开启死锁检测:
bash
dcb pfc
deadlock-detect interval 10
priority 3 deadlock-detect time 10
priority 3 deadlock-recovery time 10
priority 3 turn-off threshold 5
deadlock-detect time 用于设置检测周期、deadlock-recovery time 设置恢复周期,turn-off threshold 设置连续检测到死锁多少次后临时关闭该优先级的 PFC 以打破死锁循环。这几个参数的默认值和取值范围因设备型号和软件版本差异较大,务必对照命令参考核实。
第 7 步:交换机侧初步检查
交换机配置下发后,先做一轮设备自身的检查,再往下配网卡和应用层:
- 用
display dcb pfc statistics一类命令(具体命令名以设备命令参考为准)查看端口是否出现异常频繁的 PFC 暂停计数; - 检查 Leaf、Spine 全链路的 DSCP 映射、PFC 优先级、ECN 阈值是否完全一致,任何一跳配置不一致都可能让无损链路在那一跳"破功"。
只有交换机侧配置自洽,才有必要往下配置网卡------网络规划里强调的"端到端一致性",接下来就体现在交换机与网卡的参数要严格对应。
三、网卡配置(以 NVIDIA/Mellanox ConnectX 系列智能网卡为例)
目前大模型训练集群的 GPU 服务器上,绝大多数使用 NVIDIA(原 Mellanox)ConnectX 系列智能网卡做 RDMA 网卡。下面命令以 Linux 下的
mlnx_qos(属于mlnx-tools/ MLNX_OFED 驱动包)和标准 sysfs 接口为例,交叉核对自网卡厂商文档描述与多篇独立技术资料。不同代际网卡(ConnectX-5/6/7)、不同驱动版本的参数细节可能有出入,执行前请对照自己网卡的厂商文档和--help输出核实,并先在单机/测试环境跑通,再批量下发到训练集群。
第 1 步:确认网卡型号与当前 RoCE 模式
先确认这张卡跑的是 RoCEv2 而不是 RoCEv1(如果误留在 v1,交换机侧再怎么配置也不会生效):
bash
cma_roce_mode -d mlx5_0 -p 1
# 需要设置为 RoCE v2 时:
cma_roce_mode -d mlx5_0 -p 1 -m 2
mlx5_0 是网卡设备名,需要替换成 ibdev2netdev 或 ibstat 查出来的实际设备名;-p 1 是端口号,多端口网卡要按实际端口调整。
第 2 步:设置 DSCP 信任模式和 PFC
对应网口(用 ibdev2netdev 查出网卡对应的 Linux 网口名,如 enp216s0f1np1):
bash
mlnx_qos -i <网口名> --trust dscp
mlnx_qos -i <网口名> --pfc 0,0,0,1,0,0,0,0
--pfc 后面 8 个数字依次对应优先级 0~7 是否开启 PFC,这里把第 4 位(优先级 3)设为 1,要与前面交换机侧 dcb pfc priority 3 的优先级编号保持一致,两边任何一边选错优先级号,PFC 都无法端到端生效。
这里补充一个很多人没搞清楚的对应关系:为什么业界习惯用 DSCP 26 配优先级 3 这一组?因为网卡默认的 DSCP→优先级映射就是按 DSCP / 8 分组的,DSCP 24~31 这一档默认全部落到优先级 3,而 DSCP 26(即 AF31)正好在这一档里,所以用 26 时不需要额外配映射就能落到优先级 3,省掉一处容易配错的地方。如果你的规划里要用别的 DSCP 值,或者想显式写死这个映射关系,可以用:
bash
mlnx_qos -i <网口名> --dscp2prio set,26,3
反过来,如果交换机侧信任的 DSCP 值和网卡这边落到的优先级对不上,流量就会走到没配 PFC/ECN 的普通队列里------这是排障时最常见的"配置都做了但就是不生效"的原因之一。
第 3 步:设置 RoCE 流量的 ToS/Traffic Class,与交换机 DSCP 对齐
RDMA 连接建立时使用的 ToS 字段需要和交换机侧信任、匹配的 DSCP 值对应(IP 报文里 ToS 字段高 6 位就是 DSCP,换算关系是 ToS ≈ DSCP × 4,如果网卡/驱动的 ECN 能力位也参与编码,实际数值可能是 DSCP × 4 + 2 这样的形式,需要用抓包或厂商工具实测确认,不要直接按公式硬套):
bash
cma_roce_tos -d mlx5_0 -t 106
例子中的 106 对应交换机侧配置的 DSCP 26(即前面 drop-profile/PFC 优先级 3 那条链路),实际值必须和交换机、其他所有节点保持一致,否则会落到错误的队列,PFC/ECN 配置形同虚设。
第 4 步:开启 ECN(配合交换机侧完成 DCQCN 闭环)
ConnectX 系列网卡把 ECN 拆成两个方向:roce_np(Notification Point,收到拥塞标记后生成 CNP 的接收端逻辑)和 roce_rp(Reaction Point,收到 CNP 后执行 DCQCN 降速的发送端逻辑),两个方向都要按优先级打开:
bash
echo 1 > /sys/class/net/<网口名>/ecn/roce_np/enable/3
echo 1 > /sys/class/net/<网口名>/ecn/roce_rp/enable/3
这里的 3 同样对应优先级 3,需要跟交换机、PFC 配置的优先级一致。
第 5 步:查找 RoCEv2 对应的 GID Index
网卡的 RDMA 子系统会给每种地址类型(IB 原生、RoCEv1、RoCEv2 IPv4/IPv6)分配不同的 GID Index:
bash
show_gids
在输出里找 RoCE v2 且地址类型匹配你训练网络所用协议(一般是 IPv4)的那一行,记下它的 GID Index 列。如果系统没有 show_gids 命令,通常是缺少 mlnx-tools 包,需要先安装。
这个值有两个用途:一是下一节用 ib_write_bw -x <索引> 做裸链路测试时要用;二是作为核对基准 ------现在的 NCCL 已经能自动选出正确的 RoCEv2 GID,不需要手工指定,但你需要知道正确答案是几,才能在看 NCCL_DEBUG=INFO 日志时判断它自动选的对不对(详见下一节)。
第 6 步:网卡侧验证
bash
mlnx_qos -i <网口名>
ethtool -S <网口名> | grep prio3
cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/np_cnp_sent
第一条确认 trust/PFC 配置已生效,第二条看优先级 3 队列有没有异常的暂停计数,第三条看这张卡有没有在发送 CNP(拥塞通知包)------如果长时间为 0,可能是 ECN 没生效或者压根没跑到拥塞的流量。
这些配置默认不是持久化的,重启机器或重新加载驱动后通常需要重新执行(也可以写成开机脚本),是否支持持久化保存请查阅具体驱动版本的文档。
四、应用层配置(以 NCCL 为例)
大模型训练框架(PyTorch、Megatron-LM 等)底层通常都用 NCCL 做 GPU 间集合通信,NCCL 通过一组环境变量来决定怎么用底层的 RDMA 网卡,这一层配置错了,即便交换机和网卡都配置正确,实际训练也可能走了错误的队列或退化到 TCP。
第 1 步:确认走 RDMA 而不是回退到 TCP
bash
export NCCL_IB_DISABLE=0
NCCL_IB_DISABLE=0 是默认值,明确写出来是为了防止有人在排障时临时设成 1(强制退化到 TCP Socket)之后忘记改回来------这种情况下训练能跑但完全没用上 RDMA,吞吐会断崖式下降,却不容易第一时间发现。
第 2 步:设置一组针对 RoCEv2 的核心变量
bash
export NCCL_IB_HCA==mlx5_0,=mlx5_1,=mlx5_2,=mlx5_3,=mlx5_4,=mlx5_5,=mlx5_6,=mlx5_7
export NCCL_IB_TC=106
export NCCL_IB_SL=0
export NCCL_SOCKET_IFNAME=eth0
export NCCL_NET_GDR_LEVEL=SYS
export NCCL_DEBUG=INFO
NCCL_IB_HCA:显式列出参与通信的 RDMA 网卡设备名(用ibdev2netdev或ibstat查出实际设备名后填入),避免 NCCL 自动探测时选错卡------一台服务器上如果同时有管理网卡和多张 RoCE 网卡,不显式指定很容易选错。这里有个容易踩的坑:不加=前缀时,写的值是前缀匹配 ,mlx5_1会同时匹配到mlx5_1和mlx5_10;加上=才是精确匹配,所以卡多的机器建议按上面这样逐个写=mlx5_N。需要指定端口时可以写成=mlx5_0:1这种形式。NCCL_IB_TC:对应网卡侧cma_roce_tos设置的 ToS 值(本文示例中对应交换机侧 DSCP 26),必须和网卡、交换机三方完全一致,否则流量不会进入之前配置好 PFC/ECN 的那个队列。NCCL_IB_SL:InfiniBand/RoCE 的 Service Level 字段,默认 0,多数场景保持默认即可,只有网络规划里明确划分了多个服务等级才需要调整。NCCL_SOCKET_IFNAME:注意这个变量控制的是 NCCL 建链阶段的带外(out-of-band)控制通道走哪张网卡,不是训练时的大流量数据通道------数据面走的是上面NCCL_IB_HCA指定的 RDMA 网卡,两者不要混淆。同样支持=精确匹配和^排除写法(比如^docker排除容器网桥)。NCCL_NET_GDR_LEVEL:控制 GPUDirect RDMA(网卡直接读写 GPU 显存,跳过 CPU 内存拷贝)在 GPU 与网卡相隔多远的 PCIe 拓扑距离内还允许启用,可选值从严到宽依次是LOC(从不启用)、PIX(同一 PCIe 交换芯片下)、PXB(跨多级 PCIe 交换芯片)、PHB(同一 NUMA 节点)、SYS(跨 CPU 之间的互联总线也允许)。SYS是最宽松的一档,在 Rail-optimized、网卡与 GPU 已经按亲和性配对的机型上通常没问题,但如果机型的 GPU 和网卡分属不同 CPU、跨 UPI/SMP 总线通信,放到SYS反而可能比走主机内存更慢,建议对照机型的 PCIe 拓扑(nvidia-smi topo -m)实测比较后再定。NCCL_DEBUG=INFO:让 NCCL 在启动时打印实际选用的网卡、GID Index、传输方式等信息,是确认上面几个变量真正生效的关键手段;排障之外不建议长期开启,日志量会比较大(可选值还有VERSION、WARN、TRACE)。
关于 NCCL_IB_GID_INDEX:新版本不要再手动写死。 很多流传的配置模板里都有一句 export NCCL_IB_GID_INDEX=3,那是早期版本的经验做法。这个变量的默认值是 -1,即由 NCCL 自动根据网卡链路层类型选择------识别到以太网(RoCE)时会自动挑选支持 RoCEv2 的那个 GID(在多数驱动/固件组合下恰好就是 3,但并不总是)。从 NCCL 2.21 起自动选择已经能覆盖绝大多数 RoCEv2 环境,硬编码 3 反而可能在某些驱动版本上选到错误的地址类型、导致建链失败。正确做法是:先不设这个变量 ,用 NCCL_DEBUG=INFO 看日志里 NCCL 实际选中的 GID Index 是不是第三节 show_gids 里那个 RoCEv2 条目,只有在确认自动选择选错了的时候,才显式覆盖它。
同理,NCCL_IB_ADDR_FAMILY(可选 AF_INET/AF_INET6,默认 AF_INET)只在 NCCL_IB_GID_INDEX 未设置 、由 NCCL 自动挑 GID 时才起作用------它用来告诉 NCCL 自动选择时优先挑哪个地址族的 GID。如果你已经显式写死了 GID Index,这个变量就不会生效,两个一起设是自相矛盾的。IPv6 组网时才需要把它设成 AF_INET6。
第 3 步:在真正的多机启动命令里带上这些变量
如果用 OpenMPI 风格的 mpirun 启动(Megatron-LM、许多自建训练框架的常见方式),变量要通过 -x 逐个显式导出给远端进程,而不是只在本机 export:
bash
mpirun --allow-run-as-root \
--hostfile hostfile \
-np 64 -N 8 \
--bind-to none \
-x LD_LIBRARY_PATH -x PATH \
-x NCCL_DEBUG=INFO \
-x NCCL_IB_DISABLE=0 \
-x NCCL_IB_HCA==mlx5_0,=mlx5_1,=mlx5_2,=mlx5_3,=mlx5_4,=mlx5_5,=mlx5_6,=mlx5_7 \
-x NCCL_IB_TC=106 \
-x NCCL_SOCKET_IFNAME=eth0 \
-x NCCL_NET_GDR_LEVEL=SYS \
python train.py
(注意 -x NCCL_IB_HCA==mlx5_0,... 里的两个等号不是笔误:第一个是 -x 传参的赋值符号,第二个是 NCCL 用来表示"精确匹配设备名"的前缀。)
其中 hostfile 是一个文本文件,每行一台机器加上这台机器要用的进程数(对应 GPU 数),格式类似:
bash
10.0.0.1 slots=8
10.0.0.2 slots=8
...
-np 64 -N 8 表示总共 64 个进程、每台机器 8 个(对应 8 张 GPU),需要按集群实际的机器数和单机 GPU 数调整。
如果用 torchrun(PyTorch 原生分布式启动器,DeepSpeed/Megatron-DeepSpeed 也常用)拉起,环境变量按标准 shell export 传给每个节点即可,PyTorch 会读取这些环境变量交给底层 NCCL:
bash
export NCCL_DEBUG=INFO
export NCCL_IB_DISABLE=0
export NCCL_IB_HCA==mlx5_0,=mlx5_1,=mlx5_2,=mlx5_3,=mlx5_4,=mlx5_5,=mlx5_6,=mlx5_7
export NCCL_IB_TC=106
export NCCL_SOCKET_IFNAME=eth0
export NCCL_NET_GDR_LEVEL=SYS
torchrun \
--nnodes=8 --nproc_per_node=8 \
--node_rank=$NODE_RANK \
--master_addr=$MASTER_ADDR --master_port=29500 \
train.py
$NODE_RANK、$MASTER_ADDR 通常由集群调度系统(Slurm、Kubernetes 训练算子等)在拉起每个节点的进程时自动注入,具体取值方式以所用的调度框架文档为准。如果用的是 DeepSpeed 的启动器,也可以把这些变量写进 ~/.deepspeed_env(每行一个 变量名=值),DeepSpeed 会自动把这个文件里的变量连同 NCCL 相关的环境变量一起透传给所有节点,不需要在每台机器上手动 export。
不管用哪种启动方式,具体版本的 NCCL 支持的变量名和默认值会随版本演进(比如更早期版本没有 NCCL_IB_ADDR_FAMILY),执行前建议对照当前安装的 NCCL 版本文档逐条确认,而不是照抄某个特定版本的示例。
五、端到端验证
单独测通某一层不代表整条链路真的无损,建议按从底层到应用层的顺序逐层验证:
1. RDMA 裸链路测试(perftest / ib_write_bw)
先跳过 GPU 和 NCCL,只测网卡到网卡之间纯 RDMA 的带宽和时延,把硬件、驱动、交换机层面的问题排除掉,再往上排查应用层。ib_write_bw 是 perftest 工具包(多数发行版可以直接 yum install perftest / apt install perftest 安装,或者随 MLNX_OFED 驱动一起装好)里的 RDMA 写带宽测试工具,在两台机器上分别执行:
服务器端(先启动,等待客户端连接):
bash
ib_write_bw -d mlx5_0 -x 3 -F --report_gbits -q 4 -s 1048576 -D 30
客户端(连接到服务器 IP):
bash
ib_write_bw -d mlx5_0 -x 3 -F --report_gbits -q 4 -s 1048576 -D 30 <服务器IP>
参数说明:
-d mlx5_0:使用的 RDMA 设备名(不指定时默认用找到的第一个设备),与前面网卡配置里的设备名保持一致;-x 3:GID Index,同样要填第三节show_gids查到的 RoCEv2 那一行的索引值,两台机器要选同一类型(不能一边选 RoCEv2 一边选 RoCEv1);-F:即使系统开着 CPU 变频(cpufreq_ondemand之类的调频策略)也不因此报错退出。perftest 用 CPU 周期计时,变频会让时延测量不准,所以默认会直接报错拒绝跑;加-F是"我知道有变频,先跑起来"。如果你要的是准确的时延数字,正确做法是把 CPU 调成 performance 固定频率,而不是用-F把告警压下去;只测带宽时影响相对小;--report_gbits:结果按 Gbit/s 汇报,方便直接和网卡线速(比如 100G/200G/400G)对比(不加时默认按 MB/s 汇报);-q 4:并发的 QP(Queue Pair)数量,默认 1,多 QP 通常更容易跑满大带宽链路,具体数值可以从 1 开始逐步加大观察变化;-s 1048576:单次传输的消息大小(字节),这里是 1MB,测大包吞吐用大一些的值,测小包时延可以换成ib_write_lat工具并用更小的-s;-D 30:测试持续 30 秒,时间太短容易被瞬时波动干扰,看不出稳定态的表现。
另外有一个在 RoCE 场景下很实用的参数 -R(--rdma_cm):加上它之后,两端改用 RDMA CM 来建立连接,由 CM 自己完成地址解析和 GID 选择,不用手工指定 -x。当你不确定该填哪个 GID Index、或者想验证"应用不手工指定时会协商成什么"时,用 -R 跑一遍更贴近 NCCL 等上层应用的实际行为:
bash
# 服务器端
ib_write_bw -d mlx5_0 -R -F --report_gbits -q 4 -s 1048576 -D 30
# 客户端
ib_write_bw -d mlx5_0 -R -F --report_gbits -q 4 -s 1048576 -D 30 <服务器IP>
还有一点要注意:服务器端和客户端的参数(-q、-s、-D 等)必须写成一致,否则两端协商不上会直接报错;两端默认监听/连接的是 18515 端口,被占用时可以用 -p 换一个。
正常情况下,实测带宽应该能跑到链路线速的 90% 以上;如果明显偏低,先怀疑网卡 PCIe 插槽/带宽是否够、线缆/光模块是否有问题、交换机端口是否协商到了预期速率,而不是急着去改 PFC/ECN 参数。
2. 制造拥塞、观察 PFC/ECN 是否按预期工作
裸链路带宽正常之后,用多条并发的 ib_write_bw(比如同时对多个目的地跑、或者用更大的 -q 把单端口打满)制造持续拥塞,分别在交换机和网卡两侧观察计数器:
- 交换机侧:用前面第 7 步提到的
display dcb pfc statistics类命令看 PFC 暂停帧计数; - 网卡侧:
bash
watch -n 1 'ethtool -S <网口名> | grep -i pfc; cat /sys/class/infiniband/mlx5_0/ports/1/hw_counters/np_cnp_sent /sys/class/infiniband/mlx5_0/ports/1/hw_counters/np_ecn_marked_roce_packets 2>/dev/null'
健康的状态是:能看到一定量的 ECN 标记(np_ecn_marked_roce_packets,具体计数器名称因驱动版本而异,需要用 ls /sys/class/infiniband/mlx5_0/ports/1/hw_counters/ 自行确认实际存在哪些文件)和 CNP 发送(np_cnp_sent)在持续增长,但 PFC 暂停计数增长很慢甚至不动------说明 ECN/DCQCN 在拥塞早期就把发送端降速了,PFC 只是很少触发的最后一道保险;如果 PFC 暂停计数快速持续增长,说明 ECN 阈值设置得比 PFC Xoff 阈值高(或者太接近),拥塞控制没有提前介入,需要回头调低 ECN 标记阈值。
3. 集合通信压测(nccl-tests / all_reduce_perf)
裸链路和拥塞控制都验证过之后,再上到 NCCL 这一层,用 NVIDIA 官方的 nccl-tests 项目做贴近真实训练通信模式的压测。先编译(需要已经装好 CUDA、NCCL 和一个 MPI 实现,比如 OpenMPI):
bash
git clone https://github.com/NVIDIA/nccl-tests.git
cd nccl-tests
make MPI=1 MPI_HOME=/usr/local/openmpi CUDA_HOME=/usr/local/cuda -j$(nproc)
单机内先测一遍(比如单台 8 卡机器,只走机内 NVLink/PCIe,不经过网络,作为跨机测试的参照基线):
bash
./build/all_reduce_perf -b 8 -e 8G -f 2 -g 8 -w 10 -n 20
确认单机结果符合预期后,再做跨机测试。用前面提到的 hostfile(每行 IP slots=单机GPU数),结合上一步配置好的 NCCL 环境变量一起跑:
bash
mpirun --allow-run-as-root \
--hostfile hostfile \
-np 64 -N 8 \
--bind-to none \
-x LD_LIBRARY_PATH -x PATH \
-x NCCL_DEBUG=INFO \
-x NCCL_IB_DISABLE=0 \
-x NCCL_IB_HCA==mlx5_0,=mlx5_1,=mlx5_2,=mlx5_3,=mlx5_4,=mlx5_5,=mlx5_6,=mlx5_7 \
-x NCCL_IB_TC=106 \
-x NCCL_SOCKET_IFNAME=eth0 \
-x NCCL_NET_GDR_LEVEL=SYS \
./build/all_reduce_perf -b 8 -e 8G -f 2 -g 1 -w 10 -n 100
参数说明:-b/-e 是测试的最小/最大消息大小(这里从 8 字节到 8GB),-f 2 表示每次按 2 倍递增,-g 1 表示每个 MPI 进程对应 1 张 GPU(因为已经用 -np/-N 把进程数和每机进程数定好了,这种"一进程一卡"的写法是多机场景下的标准用法,不要和单机测试里 -g 8、单进程管多卡的写法混用),-w 是预热(不计入统计)的迭代次数,-n 是正式统计的迭代次数。
输出里重点看两列:busbw(总线带宽,已经按 All-Reduce 的通信量模型折算过,可以直接和网卡理论线速对比,是否达到线速的 70%~90% 以上是判断网络是否健康的一个粗略参考区间,具体应该达到的比例因拓扑、GPU 数、消息大小而异)和 time(对应消息大小下的平均耗时,看是否随节点数增加而异常膨胀)。同时打开的 NCCL_DEBUG=INFO 日志里要确认实际选用的网卡列表、GID Index 和前面配置的一致,如果日志显示走的是 Socket 而不是 IB/NET/IB,说明前面某一步配置没生效,NCCL 悄悄退化到了 TCP。
4. 真实训练任务观察
最终以一次小规模的真实训练任务收尾,观察每步(step)耗时是否稳定、GPU 利用率是否维持在高位。如果 nccl-tests 跑分正常但真实训练还是有周期性的耗时抖动,通常说明问题不在网络本身,而是训练任务的通信模式(比如某种并行策略下的额外同步点)和纯 All-Reduce 压测的流量模式不完全一样,需要结合训练框架自带的 profiling 工具进一步定位。
检查交换机、网卡、应用层三处的日志和计数器,把它们对照着看,比只看某一层的输出更容易定位问题出在哪一跳、哪一层。
RoCEv2 的价值不在于某一条命令本身,而在于把交换机的 PFC/ECN、网卡的 DCQCN、应用层的 NCCL 参数,结合网络拓扑,端到端调成一套自洽的系统------任何一层配置遗漏或者参数对不上,前面几层再正确也无法体现出无损网络的效果。理解原理、按规范规划拓扑、逐层逐项核实配置命令,比照抄任何一份"标准答案"都更重要。