简介:这是一份面向高性能计算与数据中心网络工程师、研究人员和IT架构师的技术规范文档,对应InfiniBand架构第一卷Release 1.9草案(2024年8月31日版)。文档以修订历史为主线,完整覆盖从2000年1.0版到1.9版的主要变更:包括1.1的系统架构与CM类更新、1.3整合XRC、1.4新增虚拟化与RoCE-v1/v2附录、1.5的NDR与最小带宽保证、1.6的扩展操作码和VERIFY操作、1.7的网络探测A20附录及XDR支持,直至1.8引入NeVerMore解决方案和最大64K端口的大规模交换机管理能力,并明确1.9为最终发布于2025年7月的草案版。资源为单个PDF文件,共1份,压缩包约14.39MB,适合需要跟踪IBTA规范演进、核对协议细节或设计兼容方案的读者阅读。目前已有841人浏览学习,可帮助快速建立对InfiniBand最新特性的系统认知。
1. IB Specification Vol 1.9:为什么 400G 网卡会协商成 2.5G
先给结论:IB Specification Vol 1-Release-1.9-Draft-2024-08-31 不是一份拿来通读的文档,而是一张对 InfiniBand 集群做体检的检查单。很多人压着 MLNX_OFED 版本、插着标称 400G 的线缆,ping 通了就觉得万事大吉,结果 HPC 作业一多就在丢包和降速上翻车。回头查原因,答案基本都能在这份草稿里找到:端口状态、链路宽度、QP 超时字段、子网管理属性,规范里白纸黑字给了边界,只是没人对着查。这篇笔记就按协议栈顺序拆一遍,把规范里的关键参数和日常运维命令对齐,适合被性能问题缠住的 HPC 运维、刚上手 IB 网络的新手,以及想给 RoCE 配置找依据的存储工程师。
2. 规范里的三层落点:从链路状态、QP 状态机到 timeout 参数的物理含义
IB 规范 Vol 1 的核心是 Channel Adapter(HCA)加子网管理,这部分和以太网完全不是一套逻辑。理解它才能看懂后面所有命令输出。
2.1 端口与子网模型:SM 是 IB 子网的交警
Vol 1.9 最开头定义的三个实体:CA(Channel Adapter)、交换机(Switch)、路由器(Router)。一个 CA 端口就是一个节点,每个端口由子网管理器(Subnet Manager,SM)分配一个唯一的 LID。LID 是 16 位字段,实际可用范围是 0x0001 到 0xBFFF,0xC000 到 0xFFFF 预留给组播,0x0000 保留。每次插拔线缆后,LID 都可能变,这正是 IB 和以太网 IP 配置最大的区别。
端口状态机也是 Vol 1 的重点。普通端口有 Down、Init、Armed、Active 四个主状态,规范里用PortState属性承载,数值分别是 1/2/3/4。端口从 Down 走到 Active,中间必须经过 SM 的管理报文交换:SM 通过定向管理报文(QP0,SMA)下发PortInfo和SubnetRoutingTable,端口拿到有效服务名(Service ID)和路径信息后才进入 Active。所以如果一台机器ibstat显示State: Init或者Armed,不用查线,先看 SM 是否活着,这是规范定义的状态依赖关系带来的最直接排查方向。
管理报文本身走 QP0 和 QP1。QP0 是子网管理接口(SMA),QP1 是通用服务接口(GSI),这两个 Queue Pair 由硬件保留,普通用户程序碰不到。规范里对这两个特殊 QP 的行为描述非常详细,但运维层面只需要记住:如果 opensm 没跑,ibstat看到的所有 State 都会停在 Down 或 Init,这就是规范层面给出的判断依据。
2.2 物理层与链路层:SDR 到 NDR 的速率谱系
链路层参数是 Vol 1.9 里最有实操价值的部分,因为它直接解释了为什么速率会“掉档”。IB 物理层的速率演进是固定倍数关系:SDR 2.5 GT/s、DDR 5.0、QDR 10.0、FDR 14.0625、EDR 25、HDR 50、NDR 100,单位是每秒每 lane 的 Giga transfers。链路宽度又分 x1、x2、x4、x8、x12。所以一根标称 NDR 400G 的线,物理上是 4 lane 每 lane 100G。只要有一个 lane 训练失败,链路宽度自动降级,速率就变成 100G,甚至更低。
规范里对应的属性有三个:LinkSpeedActive、LinkWidthActive、PortPhysState。PortPhysState是物理层状态,和上面说的逻辑状态不同,它描述的是链路训练过程:Sleep、Polling、Training、LinkUp、LinkErrorRecovery。运行ibstat时能看到两行:
State: Active:逻辑状态,表示 SM 已经完成子网融入Physical state: LinkUp:物理状态,表示链路训练完成
这两个必须同时满足才算健康。常见翻车现场是State: Active但Physical state: LinkErrorRecovery,这在规范里对应物理层连续发生链路错误后自动恢复的中间态。如果频繁抖动,大概率是信号质量差,和 upper layer 的丢包率直接相关。
链路层的另一个关键项是 MTU。IB 的 MTU 只有五档:256、512、1024、2048、4096。路径上所有端口的 MTU 必须取最小值,而且 IB 流控的 credit 是以 64 字节为单位的,MTU 越大,单次 credit 能发送的数据越多,吞吐越高。但注意,MTU 和消息大小没有绝对关系,比如写 1 字节到对端,线路上也要消耗一个完整报文的最小开销。规范要求所有端口配置的 MTU 在路径上一致,否则连接建立时ibv_modify_qp会报EPERM。
2.3 传输层:QP 状态机与 timeout 字段才是性能分水岭
传输层是 Vol 1 里信息密度最高的部分。IB 通信的基本单位是 Queue Pair,一个 QP 由发送队列和接收队列组成。QP 类型分四类:可靠连接(RC)、不可靠连接(UC)、不可靠数据报(UD)、原始数据报(Raw)。RC 提供保序、可靠、拥塞控制的端到端语义,是 MPI 和存储主路径上最常用的类型;UD 更轻量,适合多对多通信,但不保证可靠。
QP 状态机是排障的关键。QP 依次经过 Reset、Init、RTR、RTS 四个状态,出问题后会进入 SQErr 或 Error。每一步都有前置条件:Init 状态必须指定 PKey 和端口号;转 RTR 必须带上path_mtu、rq_psn、max_dest_rd_atomic;转 RTS 必须带timeout、retry_cnt、rnr_retry。这些字段不是随便填的,规范对每个字段都给了边界范围。
以timeout字段为例,它占 5 bit,实际值的意思是本地等待 ACK 的定时器,换算公式是4.096us * 2^timeout。这个参数直接影响 RC 语义下链路故障的恢复时长。我一般用下面这段代码设置 QP 的 RTR 和 RTS 参数:
struct ibv_qp_attr attr = {0}; attr.qp_state = IBV_QPS_RTR; attr.path_mtu = IBV_MTU_4096; attr.rq_psn = 0; attr.max_dest_rd_atomic = 1; attr.min_rnr_timer = 0x12; ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_PATH_MTU | IBV_QP_RQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER); attr.qp_state = IBV_QPS_RTS; attr.timeout = 20; /* 4.096us * 2^20 ≈ 4.29s */ attr.retry_cnt = 7; /* 本地重试,7 次 */ attr.rnr_retry = 7; /* RNR 重试,7 次 */ ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_SQ_PSN | IBV_QP_MAX_ATA);这里的逻辑有两层。第一,timeout = 20对应约 4.29 秒,适合跨机房长链路;如果是机架内短链路,timeout = 14约 67 毫秒更合理,太大了故障切换慢,太小了轻微拥塞就误判。第二,rnr_retry是解决接收端没有预置 receive buffer 时的重试次数,这个值设成 0 表示无限重试,生产环境千万别设 0,否则接收进程死锁时发送端会永远挂着。handler 部分的min_rnr_timer = 0x12表示 RNR 等待的初始退避时间,这也是规范里的固定换算,值越大退避越激进,对共享链路的公平性影响很明显。
这个片段在配置 RoCE 时同样适用,因为 RoCE v1 和 v2 在传输层复用了 IB 的定义。区别只在网络层:RoCE v2 把 GRH 换成了 UDP/IP。所以 Vol 1.9 里所有关于 QP 状态、超时、重试的参数,RoCE 场景下一字不差地照用。
3. 把规范对到命令:用 ibstat、ibv_devinfo、ibdiagnet 验证子网一致性
规范读完了,接下来是落地动作。这一章把 Vol 1.9 里的属性名和 Linux 命令行输出做映射,让读者拿到手就能照着查。
3.1 端口属性:ibstat每一行对应规范哪个字段
ibstat是排查 IB 端口最常用的命令。它输出的字段其实直接照搬规范中的属性定义。实例如下:
$ ibstat mlx5_0 CA 'mlx5_0' CA type: MT4129 Number of ports: 1 Firmware version: 16.33.1000 Hardware version: 0 Node GUID: 0x248a070300xxxxxx System image GUID: 0x248a070300xxxxxx Port 1: State: Active Physical state: LinkUp Rate: 200 Base Lid: 0x26 LMC: 0 SM lid: 0x1 Capability mask: 0x0259486a Port GUID: 0x248a070300xxxxxx Link layer: InfiniBandState对应PortState(注意 ibstat 显示的是字符串,数值 4 才是 Active)。Physical state对应PortPhysState。Rate: 200对应LinkSpeedActive,200 表示 200 Gbit/s,即 EDR 4x;如果是 400,表示 NDR 4x。Base Lid对应PortLid。LMC对应LidMaskControl,表示这个端口分配了几个 LID,0 代表只有一个。
参数说明:LMC大于 0 时,端口会占用一段连续的 LID 地址,规格书里叫多 LID 支持,用于自适应路由。
看到State: Active+Rate正常还不够,还要确认Base Lid在 0x0001~0xBFFF 范围内。如果看到Base Lid: 0x0000,说明 SM 还没给这个端口分 LID,等于端口在子网里不可达,这是规范里 LID 合法范围的使用场景。
3.2 子网一致性:ibdiagnet怎么检查规范约束
ibdiagnet是 OFED 自带的子网级巡检工具,作用是扫描整个 IB 子网,把拓扑、LID、路径、链路情况全部 dump 下来,然后对照规范做一致性检查,包括 LID 是否冲突、链路宽度是否两端一致、速率是否两端一致、是否有设备没有 SM 分配的路径。
我常用的检查命令如下:
$ ibdiagnet -r -c -o /tmp/ibd_dump > /tmp/ibd.log 2>&1 $ grep -iE "bad|conflict|error|warn" /tmp/ibd.log | head -50 $ ls /tmp/ibd_dump ibdiagnet.fdbs ibdiagnet.lid2port.map ibdiagnet.links ibdiagnet.mcasts ibdiagnet.output需要说明的是,-r表示 dump 路由表,-c表示做一致性检查,-o指定输出目录。扫描完成后,ibdiagnet.lid2port.map是每个 LID 对应的物理端口,ibdiagnet.links是链路协商出来的速率/宽度,ibdiagnet.output里会给出各类 error 摘要。
实际使用中我把它当作规范合规性的“体检报告”。比如ibdiagnet.links里能看到每对链路的WidthActive和SpeedActive。两端不一致时,规范要求以较低端为准,体现在文件里就是某条链路width_active: x1,但再看线缆标称是x4。这时候不用怀疑规范,直接查物理层训练状态即可。这个工具每次硬件变更后跑一遍,是最低成本的合规验证方式。
3.3 巡检脚本:从规范字段到可复用的一键检查
下面这段脚本是我在现网用的简化版检查逻辑:先遍历 CA 查端口字段,再跑ibdiagnet抓子网异常。建议放到 cron 里每天一次。
#!/bin/bash # check_ib_health.sh for ca in $(ibstat -l); do echo "=== $ca ===" ibstat "$ca" | grep -E "State|Physical state|Rate|Base Lid|Link layer" done # 子网一致性 ibdiagnet -r -c -o /tmp/ibd_dump > /tmp/ibd.log 2>&1 # 提取异常行 grep -iE "bad|conflict|error|warn" /tmp/ibd.log | head -30这里有个细节:ibstat -l列出的是 CA 实例名,不是物理端口,所以脚本里先获取 CA,再逐个调用ibstat。如果机器上有双端口 HCA,ibstat会把 Port 1 和 Port 2 都输出,不需要额外处理。脚本的核心思想就是让规范字段变成可观测的文本:ibstat回答“端口状态对不对”,ibdiagnet回答“子网规则守不守”。如果输出里出现State: Down或者 grep 到bad,下一步就该按第 5 章的坑位去排。
4. 把边界值变成配置:opensm、LMC、FEC 与超时字段的落地参数
看完规范之后,紧接着的问题是:这些字段在生产环境怎么设。我给读者梳理三条主线:子网管理器参数、流控与 FEC、超时重试边界。
4.1 opensm 参数与规范对齐
opensm 是 OpenFabrics 自带的子网管理器,是网络上所有 LID 分配、路径计算、状态推进的源头。规范里规定 SM 要维护全县拓扑视图,而 opensm 的参数控制具体怎么做。启动命令我常用这样:
$ opensm -R ftree -l 1 -Q --qos_policy /etc/opensm/qos-policy.conf-R ftree指定路由算法为 Fat Tree,适合对称拓扑;-l 1把 LMC 设为 1,意味着给每个端口分配 2 个 LID,这样可以用自适应路由(AR)做粗粒度的多路径分担;-Q加上--qos_policy启用 QoS 策略文件。QoS 策略里最关键的是 SL2VL 映射:把不同 SL 映射到不同 Virtual Lane,避免存储流量和高性能计算流量互相挤占。
从规范角度解释:VL 是链路层资源,VL0~VL14 是数据流,VL15 固定给子网管理。如果不开 QoS,所有流量默认走 VL0,拥塞时管理报文和业务互相干扰。这个参数在机架规模不大时看不出差别,一旦跨交换机聚合,拥塞丢包的影响会被放大很多。所以我在生产环境都建议至少给存储服务单独分一个 SL 和 VL。
4.2 timeout、retry 与 RNR 的边界值
第 2 章给了代码,这里用一个换算表把核心边界值列清。表里的 timeout 字段是 5 bit,取值 0~31;retry_cnt 和 rnr_retry 都是 3 bit,取值 0~7。注意 retry_cnt=0 不是不重试,而是“重试 0 次之后就报错”,和很多人的直觉相反。
| 场景 | timeout 字段 | 实际超时 | retry_cnt | rnr_retry |
|---|---|---|---|---|
| 机架内短链路(<5m) | 14 | ~67 ms | 7 | 7 |
| 跨机柜铜缆(<30m) | 17 | ~536 ms | 7 | 7 |
| 跨机房长链路 | 20 | ~4.29 s | 7 | 7 |
| 接收端未预置 buffer(RNR 场景) | 17 | ~536 ms | 7 | 3 |
| 不推荐的生产配置 | 0 | 特殊处理 | 0 | 0 |
这个表怎么用?两个原则。第一,timeout 的换算单位是 4.096 微秒乘 2 的幂次,选值必须大于对端最长响应时间,否则链路正常但响应稍慢就会误判超时。第二,rnr_retry 不要设 0,因为 0 表示无限重试,接收端一旦僵死,发送端会挂住,最终靠更高层看门狗去救,排障成本极高。
4.3 FEC 与链路宽度:物理层边界值
物理层的配置项不多,最容易踩的是 FEC。FDR 以上速率,IB 链路默认要求开启 Reed-Solomon FEC(RS-FEC),具体前向纠错的编解码方式在 Vol 1.9 物理层章节。Mellanox 网卡上可以用mlxconfig查询和修改:
$ mlxconfig -d mlx5_0 q | grep FEC FEC_MODE_P1 RS_544_528_P1(6)逻辑说明:FEC_MODE_P1表示端口 1 的 FEC 模式,常见的值是RS_544_528,意思是使用 544 比特 RS 码块内含 528 比特有效数据。这个模式在两端的设置必须一致,否则链路协商会退化。参数边界是:EDR 和 HDR 速率下,RS FEC 带来的编码开销约 3%,但换来了对信号劣化的容错能力。如果两端一台交换机只支持 Fire Code 而网卡设成 RS-FEC,协商结果就会掉到较低速度档。
链路宽度边界也和 FEC 相关:宽度越高,并行 lane 越多,对信号质量的要求越高。因此一根短距离 DAC 线,2 米内可以跑 NDR x4;如果同一根线拉到 5 米,信号质量不够,链路宽度可能协商成 x2。这种物理层行为规范管不了,但通过对LinkWidthActive的持续监控可以发现。
5. 避坑与常见问题排查:链路协商、LID 冲突、CRC 增长三个现场
这一章是我实际排障里遇到最多的三类问题,每条都按“现象→原因→解决”写清楚。
5.1 现象:ibstat显示速率掉到 2.5G
实例如下:
State: Active Physical state: LinkUp Rate: 2.5 Base Lid: 0x32原因:这代表端口协商到了 SDR x1,而不是线缆标称的 HDR x4。两类原因最常见:一类是物理层训练失败后自动降级,比如轧线导致的单 lane 信号劣化,链路宽度降为 x1,速度同时降到最低支持档 2.5G;另一类是两端 FEC 配置不匹配,比如一端强制 Fire Code,另一端自动协商,双方只能退到最保守的 SDR。
解决:先用ibstatus看两端各自的rate和width_active,确认是不是两端一致。然后检查交换机那侧的 FEC 配置,尽量让两端都设成 Auto 或同样的 RS-FEC。如果线缆是 DAC,把线换到另一个端口排除物理损坏。最后用mlxconfig -d mlx5_0 s FEC_MODE_P1=6显式指定 RS-FEC,等一分钟重新 link up。从那之后我遇到 Rate 异常,第一步永远是先看width_active,因为下降的速度档位是由 lane 数决定的。
5.2 现象:LID 冲突,新机器插上后业务不通
现象:把一个新节点接入子网,ibdiagnet输出里写duplicate LID,或者新节点端口状态停在 Init 不进入 Active。
原因:这是静态 LID 分配和动态分配打架的典型场景。某些集群为了固定存储节点的地址,会在 opensm 的配置文件里给特定 GUID 手写 LID,但新节点插上来时网卡写入过非易失性静态 LID,或者和已有静态配置段重叠,SM 在检查时发现同一 LID 被两个端口占用。
解决:先停止 opensm,执行ibdiagnet确认当前 LID 分配表。然后进入 opensm 配置目录,通常/etc/opensm下有osm.conf,检查routing_engine和固定的 GUID-to-LID 映射;把新节点的 Node GUID 写进配置并指定一个未被占用的 LID,或者干脆清掉网卡上持久化的lid属性:
$ mlxconfig -d mlx5_0 s LID_MASK_CONTROL_P1=0这条命令的作用是把端口上的 LID mask 重置为 0,让 SM 重新分配。注意执行后需要重启驱动或 reboot 才会生效。核心教训是:物理机上层的 LID 和 GUID 不是等价的,GUID 才是设备的唯一身份,LID 只是 SM 分配的临时地址。
5.3 现象:CRC 错误计数持续上涨
现象:用perftest跑ib_write_bw时,持续吞吐不稳定;ibdiagnet输出里看到symbol_error或者link_error_recovery计数非零;更直观的是/sys/class/infiniband/mlx5_0/ports/1/counters/symbol_error数字增长。
原因:CRC 计数涨有两个方向。一个是物理层信号质量问题,常见于光模块老化、光纤脏污、DAC 线弯折半径太小,这类现场伴随LocalLinkIntegrityErrors增长,并且往往只是单条链路。另一个是 FEC 配置不匹配导致的残余错误,报错会在link_error_recovery上体现,链路本身还是 Active,但会频繁进入错误恢复状态。
解决:先确认是哪一类错误。cat /sys/class/infiniband/mlx5_0/ports/1/counters/symbol_error,如果数值持续增加,重点查物理光路;如果link_error_recovery增加,改用两端一致的 FEC。光模块问题就用光功率计扫,DAC 问题直接换线。排查完硬件后,用ibstat确认Physical state没有频繁跳变为LinkErrorRecovery。我认为最大的坑是很多人看到 CRC 上涨就去调 QP 的重试参数,这是本末倒置——重试参数只能掩盖错误,不会消除物理层的根源。
6. 进阶技巧:用 Vol 1.9 的计数器口径给集群建健康基线
前面都是按“问题发生时怎么查”的思路,更实用的做法是把这张规范变成一张长期健康表。
6.1 把计数器变成基线
IB 端口在 sysfs 里暴露了一组和 Vol 1.9 直接对应的计数器,常见的有symbol_error、link_error_recovery、link_integrity_errors、excessive_buffer_overrun、vl15_dropped。这些不是随便起的名字,每个在规范里都有明确定义。比如vl15_dropped表示因为接收缓冲不足而丢弃的 VL15 管理报文数,这个值一旦增长,说明子网管理报文被淹了,拓扑发现会不正常。
我一般会在每次新集群上线、版本升级后抓一把基线:
$ for counter in symbol_error link_error_recovery link_integrity_errors excessive_buffer_overrun vl15_dropped; do echo "$counter=$(cat /sys/class/infiniband/mlx5_0/ports/1/counters/$counter)" done把这组值存成文件,之后每两周对比一次。这里的关键是理解规范对计数字段的定义:symbol_error只在物理层链路训练完成后统计,链路掉线时计数会清零,所以看到数字不涨反降,不一定是好消息,要先确认端口物理状态。对比基线时只看“端口稳定处于 Active 期间”的增量,否则会被状态切换干扰。
6.2 把基线变成告警
进阶一步,在计算节点上写一个以 5 分钟为周期的轮询脚本,把增量反馈给监控系统。日志格式统一为hostname counter delta,告警阈值我通常这样定:link_integrity_errors的 delta 大于 10 就要检查物理链路;vl15_dropped的 delta 大于 1 就要检查 SM 负载;symbol_error只要非零就值得报出来看。这些阈值不是规范给的,但规范定义的计数语义让这些阈值有了可解释性。
6.3 和版本变更结合
Vol 1.9 是 2024-08-31 的草稿,不是最终版,所以线上抓基线时要留意当前固件和驱动版本是否支持这些计数器的全部语义。比如旧固件对link_error_recovery的上报粒度可能和新版本不一样。读字段时先看ibv_devinfo -v打印的active_speed和active_width,确认端口工作状态,再读计数器才有意义。
从那以后我每次交付 IB 网络,第一件事就是把所有端口的 LID、速率、宽度、MTU 和计数器基线存成一份 JSON,而不是等报障了才去翻ibdiagnet。这套动作成本很低,但能帮你把“软故障”变成“早期信号”。希望这篇笔记能帮到你。
本文还有配套的精品资源,点击获取