简介《InfiniBand架构规范1.4版》由IBTA于2020年发布面向数据中心网络架构师、高性能计算研发人员与RDMA学习者用于系统理解InfiniBand高带宽、低延迟互连的设计目标、协议分层与工程实现。压缩包共1个文件为12.64MB的PDF全文完整收录Volume 1通用规范涵盖概念概述、传输协议、服务质量、错误处理、连接管理与路由等章节并附虚拟化及RoCE-v1、RoCE-v2附录。文档保留1.0至1.4的修订脉络便于核对XRC整合、第9章勘误等版本变化也可作为RoCEv2部署及QP、CQ等核心机制查阅时的手册。已有2859人学习适合需要对照官方标准查漏补缺、夯实InfiniBand与RoCE基础的读者。1. 从一次 3 微秒的往返说起IB 规范为什么把「信任」写进硬件大多数人第一次翻 InfiniBand 架构规范都会卡在同一件事上一份讲报文格式和位域的文档为什么能写到上千页。直觉上互联技术把带宽和延迟讲清楚就够了真读进去才发现占篇幅最多的是「谁来保证消息不被发错、不被乱序、不被越权访问」。TCP/IP 把这类问题交给协议栈软件IB 把它们压进了网卡和交换机的状态机。IB 标准 1.4 版 2020 年 4 月定稿往前追到 1.0 隔了二十年报文格式没推倒重来只是一层层加附录。搞懂这层设计等于搞懂「硬件替软件履行协议栈职责时规范必须把哪些边界写死」——这也是 RoCEv2 能把同一套语义搬到以太网上的前提。2. 队列对为什么 IB 的发消息看起来像在操作内存IB 的通信接口不是send()/recv()而是「把一段描述符写进队列再敲一下门铃」。描述符叫 WQEWork Queue Element队列叫工作队列成对的发送队列和接收队列构成 QPQueue Pair。这个模型要解决的核心问题是零拷贝应用把用户态缓冲区的地址、长度、访问密钥直接写进 WQE网卡自己发起 DMACPU 全程不碰数据。代价是内存必须提前注册、队列上下文必须常驻网卡于是「谁在什么时候可以动哪块内存」变成了一件必须在硬件里被强制的事。规范的第三章和第九章把 ABI 固定下来各厂商驱动实现出一致行为。2.1 QP 状态机从 RESET 到 RTS 的四步在等什么QP 不是建好就能发。规范给了一条严格的状态迁移链RESET → INIT → RTR → RTS中间还夹着一个 SQDSend Queue Drained用于优雅停机。每一步都只放行一部分能力这个限制不是官僚主义而是硬件状态分配的顺序问题。INIT 之后接收队列才被激活。原因很直接对端一旦进入 RTS 就会立刻发包如果本地接收缓冲还没挂上去硬件只有两个选择——丢包或者回 NAK。IB 选了第三条路用状态机保证「能收」先于「能发」。RTRReady To Receive是真正配参数的地方。地址向量 AV目标 LID/GID、服务级别 SL、源路径位、路径 MTU、P_Key、重传超时都被锁在 QP 上下文里。注意 MTU 在这一步定死之后要改只能退回 INIT 重来——因为序列号窗口和重组缓冲是按 MTU 预分配的中途改会让在途包的 PSN 语义直接崩掉。这是「用硬件省状态」的典型代价灵活性换延迟。RTSReady To Send才允许发送。RD可靠数据报比 RC 多一道握手一端先到 RTR另一端到 RTR 后回一个通信两边都到 RTS 才通。XRC 的发送端 QP 只有 RTS 之前的半个状态机因为它没有自己的接收队列。# 简化版 QP 状态检查只在合法状态下放行对应操作 ALLOWED { RESET: set(), # 什么都不许做 INIT: {post_recv}, # 只能挂接收缓冲 RTR: {post_recv, recv}, # 能收不能发 RTS: {post_recv, recv, post_send, send}, } def check(qp_state, op): if op not in ALLOWED[qp_state]: # 真实实现里这会变成 ibv_modify_qp 返回 EINVAL # 或者 QP 进入 ERROR 态并产生一个 syndrome 非零的 CQE raise RuntimeError(f{op} not allowed in {qp_state})上面这段伪代码对应规范里状态与允许操作的对应表。post_recv在 INIT 就合法post_send只在 RTS 合法recv硬件侧接收完成从 RTR 开始合法。真实网卡不会在软件里做这个判断而是把状态编码进 QP 上下文的几个 bit门铃触发时由硬件状态机比对不匹配就直接把 QP 打进 ERROR 态并生成完成事件。所以排查「为什么 post_send 报错」时先看 QP 到底停在哪个状态比看代码管用。2.2 一次 Send 在硬件里变成什么用户调用一次发送落到硬件上是三段数据WQE、门铃、CQE。WQE 是控制段加若干数据段描述符SGE控制段里有操作码、目标 QP、WRID用户请求 ID、以及 signaling 标志。门铃是一个 MMIO 写值是新 WQE 的序号网卡读门铃记录doorbell record位于主机内存来判断这是第几次写入避免每次都要读自己的寄存器。批量提交是这里的关键优化。一次门铃可以配多个 WQE网卡按顺序处理处理完再更新消费者索引。selective signaling 则让应用只对每第 N 个 WQE 请求完成事件其余静默——这是高吞吐场景下把 CQ 从瓶颈里摘出去的标准手段。/* WQE 控制段的典型字段不同厂商布局不同语义由规范约束 */ struct wqe_ctrl { uint32_t opcode : 8; /* SEND / RDMA_WRITE / ATOMIC ... */ uint32_t op_mod : 8; uint32_t wrid_hi : 16; /* 用户请求 ID完成时原样带回 */ uint32_t wrid_lo : 32; uint32_t signaling: 1; /* 置位才产生 CQE */ uint32_t n_sge : 7; /* 后续 SGE 个数 */ uint32_t flags : 24; /* fence / inline / solicited 等 */ uint32_t imm; /* ImmDt 操作码下有效 */ };字段含义需要对着规范一起看opcode决定这个 WQE 在线上会展开成哪种传输头组合wrid是软件与硬件的唯一对账凭据CQE 回来时原样带回应用据此找回自己的上下文signaling为 0 时硬件不写 CQE只推进消费者索引。fence位值得单独留意——它要求该 WQE 之前的所有 RDMA 读操作在本次写之前对其它节点可见这是实现「先读后写」顺序保证的唯一手段乱用会让网卡流水线停顿不用又会出现数据竞争。2.3 保护域与内存注册为什么密钥必须先交给内核注册内存MR是 IB 里最容易被低估的一步。应用把一段虚拟地址交给驱动驱动做三件事pin 住物理页防止换出建立虚拟地址到物理地址的映射表返回两个键——lkey 给本地网卡用rkey 给远端用。此后所有 WQE 里的地址都是「lkey 偏移」远端 RDMA 请求里带的是「rkey 虚拟地址」网卡收到后查表验证通过才发起 DMA。保护域PD是这层验证的分组单位。一个 MR 只属于一个 PD一个 QP 也只属于一个 PD跨 PD 的引用一律拒绝。规范把这条写成硬约束原因是硬件没有进程概念如果没有 PD任何拿到 rkey 的节点都能读写整台机器的内存。有了 PD即使 rkey 泄漏能碰到的也只是同一保护域内的缓冲区。越权访问的失败形态很统一QP 进 ERROR 态CQE 带一个非零 syndrome数据一个字节都不会被写。这比软件栈里「检查了但可能漏」强得多——硬件在 DMA 发起前比对中间没有可被绕过的路径。内存窗口Memory Window则是把 rkey 的授予做成运行时可撤销的操作适合「临时让对端读一段」的场景快速注册Fast Registration允许在已有 MR 上重新绑定地址省掉重复 pin 的开销。这两者在规范里都有独立的操作码实现时要注意它们的失效语义比普通 MR 更激进。3. 数据包格式40 字节 GRH 与 8 字节 LRH 的分工是怎么定的报文的字节布局决定了硬件的流水线形状。IB 把「交换机需要看的信息」和「端点需要看的信息」切得很干净链路层头 LRH 8 字节网络层头 GRH 40 字节传输层头 BTH 12 字节再往后按操作码挂可选的扩展头。交换机的转发逻辑只解析 LRH这是它能做到极低转发延迟的物理原因路由器才看 GRH端点的传输层只看 BTH 及其后。3.1 LRH 与 GRH二层与三层的分界线LRH 定长 8 字节装的是 VL虚拟通道4 位、SL服务级别4 位、DLID目的本地标识16 位、SLID源本地标识16 位。子网内的转发全靠 DLID一张 16 位的表就能覆盖 65536 个端点交换机查表、改 VL、转发动作固定。SL 存在的意义是 QoS 分层它跟着包走VL 则是每一跳的本地决策SL→VL 的映射表在交换机每个端口上这个设计让同一股流量在不同跳上可以走不同的物理队列。GRH 长 40 字节格式直接借用 IPv6 头128 位源 GID、128 位目的 GID、流量类别、流标签、跳数限制、下一头部。跨子网时由路由器读它做三层转发。关键取舍在这里GRH 是可选的。同一子网内通信报文可以只带 LRH省掉 40 字节和一次解析只有跨子网或者需要全局寻址时才加 GRH。BTH 里没有标志位显式说明「后面有没有 GRH」接收端靠 LRH 里一个保留位加操作码约定来判断这一点在写解析器时是常见的翻车点。3.2 BTH 的 12 个字节里到底装了什么BTH 是整份规范里信息密度最高的 12 个字节字段一个都不能少字段位宽作用Opcode8决定后续扩展头的组合方式SE / M / PadCnt1 / 1 / 2solicited 事件、迁移状态、填充字节数TVer4传输头版本当前为 0P_Key16分区密钥接收端比对分区表DestQP24目的队列对编号AckReq1要求对端回 ACKPSN24包序列号可靠服务的核心DestQP 是 24 位意味着单个节点上最多约 1600 万个 QP这个数量级是按「大规模并发进程 每进程多队列」估出来的。PSN 也是 24 位可靠服务靠它做滑动窗口内的确认与重传回绕处理是硬件必须内建的能力。AckReq 单独一位而不是由 opcode 隐含是为了让应用能按需关闭确认——批量传输时每包都 ACK 会把对端 CQ 打爆。P_Key 出现在 BTH 而不是在扩展头里说明分区校验是每一跳都做的事交换机端口上有 P_Key 表不匹配的包直接丢。这是 IB 里少见的「中间设备也做安全校验」的设计跟以太网上 VLAN 的位置类似但语义更强——P_Key 是成员资格不是标签。3.3 扩展头按 opcode 拼装定长带来的确定性扩展头不是 TLV 结构没有长度字段也没有类型循环。接收端先读 BTH 的 opcode再按一张固定的组合表往下切RDMA Write 后面必跟 RETH16 字节Send 带立即数后面必跟 ImmDt4 字节UD 包后面必跟 DETH8 字节可靠数据报在 BTH 和 DETH 之间插 RDETH4 字节原子操作跟 AtomicETH28 字节ACK 包跟 AETH4 字节。XRC 的包在 BTH 后插 XRCETH。这个选择牺牲了扩展性换来了硬件解析的确定性不需要循环、不需要边界检查、每个头的位置在流水线里是编译期常量。代价是 opcode 空间成了稀缺资源新功能要么复用已有组合要么占用新 opcode。XRC 在 1.3 版被整合进正文时靠的就是新增 XRCETH 加几个 opcode而不是改 BTH 布局——二十年不破坏兼容性的秘密就在这里。def parse_headers(buf: bytes) - dict: 按 opcode 决定扩展头组合不做通用 TLV 扫描 lrh { vl: buf[0] 4, sl: buf[5] 0x0F, dlid: int.from_bytes(buf[2:4], big), slid: int.from_bytes(buf[6:8], big), } off 8 if buf[off] 4 0x0: # 简化判断无 GRH 时 BTH 紧接 LRH pass bth { opcode: buf[off], pkey: int.from_bytes(buf[off4:off6], big), dest_qp: int.from_bytes(buf[off6:off9], big), ackreq: (buf[off9] 7) 1, psn: int.from_bytes(buf[off9:off12], big) 0xFFFFFF, } return {lrh: lrh, bth: bth}这段代码只解析了固定部分opcode到扩展头的映射被刻意省略——真实实现里那是一张 256 项的表每项写明「后续头类型序列」。写解析器时最容易错的是字节序规范第 1.5.1 节明确所有多字节字段按大端解释但网卡在主机内存里写的队列元素是小端两套字节序在同一个系统里并存调试时值看起来「像反了」基本都是这个原因。填充字节PadCnt也要留意它只用于让包长对齐到 4 字节不参与任何语义但算长度时漏掉会让后续头的偏移全错。4. 传输服务RC、UD、XRC 分别在替谁还债IB 定义了五种传输服务RC可靠连接、UC不可靠连接、RD可靠数据报、UD不可靠数据报、XRC扩展可靠连接。它们不是「功能多少」的梯度而是对同一个问题给出五种答案——可靠性和连接状态该由谁维护、维护到什么粒度。选错服务的代价通常不在功能上而在扩展性和故障域上。4.1 RC 的序号与 ACK 合并AETH 里的 4 个字节RC 是最常用的服务一个 QP 对端固定PSN 连续递增接收端按序收、按需回 ACK发送端超时重传。它的可靠性完全由硬件承担应用看到的是「要么成功要么 QP 报错」。ACK 包里只有 4 字节的 AETH装的是 Syndrome8 位和 MSNMessage Sequence Number24 位。Syndrome 表示这次确认是正常 ACK 还是 NAK以及 NAK 的原因序列号错、重传超限、远程访问错。MSN 是这套机制里容易被忽略的一半PSN 确认的是「包收到了」MSN 确认的是「接收队列消费到哪了」。两个计数分开是因为接收端可能已经收下包但应用还没取走 CQE缓冲区没释放发送端不能无限发。ACK 合并是 RC 能在高带宽下不崩的原因。接收端不会每包都回 ACK而是攒够一定数量或等到超时再回一个AETH 里的 PSN 一次性确认之前所有包。发送端的重传策略是 go-back-N从第一个未确认的 PSN 开始重发。选 go-back-N 而不是选择性重传是因为 IB 的乱序窗口很小、链路误码率低维护一张重传位图的硬件成本换不来多少收益——这是典型的「按场景下注」而不是「按理论最优下注」。4.2 UD无连接换来的自由与代价UD 没有连接状态一个 QP 可以发给任意节点接收端靠 DETH8 字节里的 Q_Key 和源 QP 号识别来源。它不支持 RDMA 读写只能 Send/Recv且一个消息必须装进一个包——不能分段所以消息大小受路径 MTU 硬限制。这些限制换来的是可扩展性UD 的 QP 数量与通信对端数无关管理框架里的 MAD管理数据报全部跑在 UD 上因为子网管理器需要跟成千上万个节点通信而不想维护那么多连接。代价也很直接不保证顺序、不保证送达、没有重传。接收端没挂缓冲就直接丢发送端不会知道。用 UD 踩坑最常见的地方是把「不可靠」理解成「偶尔丢」实际是「可能大量丢」——尤其在接收队列深度不够时丢包率会随突发流量线性上升。判断 UD 是否合适看两件事消息能否单包发送以及应用能否自己处理丢失。4.3 XRC 与 SRQ省的是网卡里的 QP 上下文RC 的问题在中大规模并行场景会暴露每个进程跟每个对端通信都要一个 QPQP 上下文存在网卡缓存里几千个 QP 就把缓存挤爆性能断崖式下降。XRC 把发送侧做成共享一个 XRC 发送 QP 可以对接多个远端进程的接收 QP每个远端有自己的 XRCETH 里携带的编号发送队列只有一份。服务连接状态可靠性主要开销典型用途RC每对端一个 QP硬件重传QP 上下文随对端数增长点对点大数据传输UC每对端一个 QP无重传同 RC可容忍丢包的流UD无无单包、需自处理丢失管理报文、广播RD每对端一个 EEC硬件重传EEC 上下文多对端可靠小消息XRC发送共享硬件重传接收侧仍需 QP大规模并行SRQ共享接收队列是同一思路在接收侧的版本多个 QP 共用一个接收队列CQE 里带 QP 号指明来源。省的是接收缓冲的冗余——不为每个潜在对端预留缓冲而是按实际并发量准备。这两项加起来是 1.3 版把 XRC 从附录整合进正文的主要动机它解决的不是功能缺失而是「规模上去之后 QP 上下文成为稀缺资源」这个真实瓶颈。规范里对 XRC 的编号管理写得很细因为共享发送队列意味着错误必须能精确归因到某一个远端接收者否则一个对端的故障会污染整条共享队列。5. 排查那些看起来像网络故障的 IB 问题IB 的故障表现有个共同特点链路状态全是绿的业务不通。这是因为大量约束是在 QP 建立和包解析阶段生效的链路层根本感知不到。下面五条是按「现象 → 原因 → 解决」整理的高频踩坑记录覆盖了从建链到多路径的典型路径。5.1 现象建链到 RTR 就失败日志只说不支持原因八成是 P_Key 不匹配。默认分区键是 0xFFFF管理类流量走它但业务流量通常被分配了别的 P_Key。QP 在 RTR 阶段要指定 P_Key网卡会拿它跟端口上的 P_Key 表比对不在表里就拒绝。另一个可能是分区成员资格由子网管理器下发SM 没跑或者没配好端口上根本没有业务 P_Key。解决顺序是先看端口 P_Key 表里有没有目标值再看 SM 是否已完成子网初始化最后确认 QP 的 P_Key 和接收端一致。P_Key 的低 15 位是分区号最高位表示是否允许访问默认分区这个位搞反了会出现「单向通」的玄学现象。5.2 现象小消息正常超过某个尺寸就超时这是 MTU 不匹配的典型特征。QP 在 RTR 阶段锁定路径 MTU发送端按自己的 MTU 分段中途某条链路的最小 MTU 更小就会丢包——IB 没有分片机制不匹配就是不匹配。规范里 MTU 一旦设定只能退回 INIT 重配不允许热改。解决方法是沿路径查每一跳的 MTU 配置取最小值然后销毁 QP 重建。注意 UD 服务受这个限制最狠消息必须单包装下MTU 小一档能发的消息就小一档。5.3 现象多进程共用一块网卡偶发本地保护错误CQE 上 syndrome 指向本地访问违规但代码看起来没错。常见原因是 MR 被注销dereg时还有在途 WQE 引用它——注销只是把映射表项清掉硬件手里的 WQE 不会自动失效等它执行到那一步就查表失败。另一种是两个 PD 共用了一个 MR或者 rkey 在错误的 QP 上被使用。解决靠顺序注销 MR 之前先把 QP 打到 SQD 或销毁确保所有 WQE 都已完成跨进程共享内存时用同一个 PD 派生 QP。这类问题在压测下才暴露所以血泪经验是「所有 MR 生命周期必须晚于引用它的 QP」。5.4 现象加了第二条路径吞吐反而下降多路径不是插上线就生效。SL→VL 的映射表在交换机每个端口上独立配置默认映射常把所有 SL 压到同一个 VL两条物理路径的流量在同一个出队列里排队头阻塞比单路径还严重。另一种情况是自适应路由打开了但 VL 配置没跟上包在两条路径间来回跳。解决要落到配置先确认 SL 到 VL 的映射在每一跳一致再确认不同业务流用了不同 SL最后才看路由策略。这部分没有通用答案只能按拓扑逐跳核对。5.5 现象RoCEv2 环境里丢一个包性能断崖IB 的可靠传输假设链路本身几乎不丢包重传是兜底而不是常态。RoCEv2 把传输层搬到了以太网上但链路层的无损保证没了一旦拥塞丢包go-back-N 的重传会把有效带宽打对折。1.4 版把 RoCEv1/v2 收进附录时明确写了这个前提依赖优先级流控和显式拥塞通知来近似无损。解决是两件事一起做链路侧配好流控避免拥塞时直接丢端侧确认注入速率控制参数与链路能力匹配。只做一边症状会从「断崖」变成「抖动」更难查。6. 设计取舍二十年只加附录是因为规范本身就是一份 ABI从 1.0 到 1.4这份规范没有改过一次 BTH 布局也没有推翻过 QP 状态机。这不是保守而是它服务的对象决定的规范一旦被固化进网卡芯片任何破坏性修改都意味着存量设备变成孤儿。所以 1.4 版新增的内容全是「往边上加」——虚拟化附录、RoCEv1 与 RoCEv2 附录都不动主干语义只定义新的部署形态下怎么复用主干。虚拟化附录解决的是一个具体的边界问题一个物理网卡要被切成多个虚拟功能每个虚拟机有自己的 QP 空间和 PD。规范把「隔离在哪一层生效」写死是因为硬件没有进程上下文隔离只能靠 PD 和 QP 归属关系在 DMA 发起前判断。这条一旦定错上层再怎么补都补不回来。RoCE 的两个附录更能看出取舍的锋利。RoCEv1 把 IB 的链路层换成以太网BTH 及以上原样保留RoCEv2 再进一步把 GRH 换成真正的 IP 头加 UDPBTH 仍然保留。保留 BTH 是为了让传输层语义——PSN、ACK 合并、QP 编号——一行都不用改应用和驱动几乎无感。丢掉的则是 IB 链路层自带的东西VL 提供的无头阻塞、基于信用的流控、链路级的重传。这些在以太网上要靠优先级流控和拥塞通知来近似近似得越像配置越复杂。维度原生 IBRoCEv2传输层BTH 及其后完全相同网络层GRH可选IP UDP无损保证链路层内建依赖外部流控配置多路径VL SL 映射依赖 ECMP粒度更粗判断一份规范值不值得深读我有个笨办法看它有没有把「失败时的行为」写清楚。这份文档里大量篇幅在讲越权访问会返回什么 syndrome、MTU 不匹配会怎么失败、状态机非法迁移会进哪个态——这些细节不写厂商实现就会各做各的上层应用永远在猜。从那以后我每次评估一套 RDMA 方案都会先去翻它的错误语义章节而不是先看带宽数字因为跑得快的方案很多能让你查得清故障的很少。希望帮到你。本文还有配套的精品资源点击获取