简介:《InfiniBand架构规范1.4版》是InfiniBand贸易协会(IBTA)于2020年发布的官方技术标准,面向数据中心、超算与高性能网络领域的架构师、工程师、运维人员及学习者,用于系统理解InfiniBand互连、RoCEv2等关键技术。包内为单个PDF文件,压缩后约12.64MB。内容不仅包含第1章概念总览,还详细说明HCA、CA、Switch与点对点串行连接等组成,覆盖物理层与数据链路层、传输协议、服务质量、错误处理、连接管理、路由机制、队列对与完成队列等核心内容,并新增虚拟化附录及RoCE v1/v2附录,解释无损以太网与支持IPv4/IPv6的部署要求。这份规范已有2856人浏览学习。读者可借此完整查阅Vol.1通用规范原文,快速定位InfiniBand分层架构、RoCE部署要点与术语定义,也可对照历史版本理解从1.0到1.4的功能演进,支撑日常开发、运维与学习中的快速查阅,是深入学习InfiniBand技术及现代高速网络的重要参考。
1. InfiniBand 规范 1.4:为什么一张协议文档值得逐字段啃
先给一个反直觉结论:InfiniBand 敢说“绕过 CPU”,靠的不是网卡硬件多么玄学,而是把内存地址、访问密钥、队列状态全部摊在报文头里,让协议机器自己在对端内存上完成“精准打击”。2020 年定稿的《InfiniBand 架构规范 1.4》(InfiniBand Architecture,即 IB 标准)把 RoCEv2、虚拟化和 XRC 这些现代数据中心互连的拼图正式收进规范体系。很多人把这本上千页的文档当字典,但真正读懂它的人,能一眼看穿一次 RDMA 读为什么能落到对端某块内存、16 条虚拟通道如何化解流控死锁、为什么 RoCEv2 能跑在普通 IP 网络上。这不是泛泛的协议科普,而是理解高性能互连底层设计决策的钥匙。
2. 通信模型:从队列对到一次 RDMA 读,数据是怎么“绕过 CPU”的
2.1 队列对 QP 与完成队列 CQ:发送/接收/完成的三段式
队列对(QP)是 InfiniBand 通信的绝对核心。一个 QP 并不是一条网络连接,而是端点内由硬件维护的一对工作队列:发送队列(SQ)和接收队列(RQ)。软件通过动词接口把工作请求(WQE)投递到 SQ 或 RQ,硬件自行把它变成线上报文,再由完成队列(CQ)统一回收结果。换句话说,QP 是「发送/接收/完成」三段式流水线的状态机,不是 socket 那种每次操作都让内核参与的数据结构。
WQE 的关键字段值得记清楚:Scatter/Gather Entry(SGE)里有本地内存地址、长度和 lkey;OpCode 指明操作类型;QP 上下文则保存 PSN、PKey、访问控制状态。当驱动把 WQE 写入内存后,只需要再写一次门铃寄存器,HCA 就会自行取回 WQE、解析 SGE、发起 DMA。整个过程 CPU 只负责队列维护和完成检查,数据本身不经过内核,这是「绕过 CPU」的第一层含义。
QP 状态机则是设计上容易被忽略的细节。Reset 到 Init 阶段,只能修改 QP 上下文;Init 之后允许发起子网管理相关报文;Ready-to-Receive(RTR)表示这个 QP 可以处理对端请求;Ready-to-Send(RTS)之后才能主动发起 SEND、RDMA 读等操作。SQD(Send Draining)、SQE(Send Queue Error)和 Error 状态则负责清理路径。为什么一定要这么严格?因为两个端点的状态必须一致,目标 QP 没有进入 RTR 之前,任何数据都不该出现在链路上。曾经有开发者把所有 QP 随手设成 RTS 就能跑通,但一旦遇到异常排查,就发现状态错乱带来的问题比链路丢包更隐蔽。
2.2 传输服务类型:可靠连接、不可靠数据报与 XRC 的取舍
规范定义了五种传输服务:可靠连接(RC)、不可靠连接(UC)、可靠数据报(RD)、不可靠数据报(UD)和扩展可靠连接(XRC)。它们的差异集中在这两个维度:QP 之间的连接关系、是否做端到端重传。理解这一点,才能看懂为什么高性能集群里 RC 泛滥、UD 只用来跑管理消息。
四种常用服务的差别可以用一张表说清:
| 服务类型 | 连接关系 | 端到端可靠性 | 典型用途 |
|---|---|---|---|
| RC | 一个 QP 只对应一个远端 QP | ACK/NAK,超时重传 | RDMA 读/写、原子操作、消息传递 |
| UC | 一个 QP 只对应一个远端 QP | 无 ACK/NAK 重传 | 低延迟但允许有限丢失的流媒体 |
| UD | 一个 QP 可向多个 QP 发送 | 无端到端重传 | 管理报文、事件通知、通用服务 |
| XRC | 一个接收 QP 可被多个远端 QP 共享 | 按连接做可靠重传 | 大规模 MPI 集群减少 QP 数量 |
RC 最好理解:建连时双方交换初始 PSN,之后每个数据包都带 PSN,接收端用 ACK/NAK 反馈,发送端对未确认包超时重传。它的代价是一个 QP 只能对应一个远端 QP。N 个进程做全两两通信,就需要 N 的平方个 QP。XRC 就是冲着这个来的:把接收端的 QP 做成共享资源,多个发送端 QP 通过 XRC 的扩展头携带目标进程的队列上下文,让每台机器只需要极少数 QP,把复杂度从 N² 压到 N。XRC 在 1.3 版本中被整合进正文,1.4 延续了这套共享接收队列的模型,并在此后的集群方案里被广泛采用。
UD 走了另一个方向:不需要建连,每个包的 DETH 头直接写远端 QP 号,包长被限制在 MTU 内,也没有端到端确认。这恰好适合子网管理、通用服务这类短报文。许多人问为什么不能全部用 UD,答案也很直接:UD 没有可靠性重传,也没有 RDMA 读写的 RKey 校验机制,真正的高性能传输服务靠它撑不住。
2.3 一次 RDMA 读的协议时序:请求、ACK 与重传
把一次 RDMA 读拆开看,才能理解「绕过 CPU」的完整含义。假设节点 A 要从节点 B 的内存 0x10000 处读 64KB。A 在本地 SGE 里指定一块接收缓冲区,然后提交一条 RDMA_READ WQE。硬件 A 构造 Read Request 报文,BTH 的 OpCode 置为 RDMA_READ,PSN 为当前发送序号,RETH 里写入 B 的远程虚拟地址、RKey 和长度。
报文到达 B 后,B 的 HCA 依次校验:目的 QP 处于 RTS 状态、报文的 PKey 与 QP 的 PKey 匹配、RKey 对应一块注册过的内存区域。全部通过后,B 的 HCA 直接 DMA 读取自身内存,封装成 Read Response 报文发回 A,这个响应同时承担 ACK 的功能。A 收到后把 payload 写入本地 SGE 缓冲区,写 CQE,随后软件才被通知。整条路径中 B 的 CPU 完全无感,这才是 RDMA 读与普通远程读取的本质区别。
可靠性由 PSN 机制保证。RC 连接使用的 PSN 是一个 24 位循环计数器;发送端每发包 PSN 加一,接收端维护期望 PSN,发现空洞就反馈 NAK。规范要求发送端必须保存未确认包,收到 ACK 之后才能释放缓冲,超时则按原始顺序重传。这里有个容易误读的点:接收端遇到乱序包会选择丢弃,而不是尝试重排。原因很实际——RDMA 要求顺序交付,硬件顺序重排的开销比丢弃大得多,靠发送端重传补齐才是低成本方案。
提示:调试重传问题时,先确认两端 QP 的初始 PSN 是否一致。很多看似网络丢包的问题,其实是建连阶段 PSN 协商没完成。
3. 寻址与报文格式:LRH、GRH、GID/LID 到底在解决什么问题
3.1 LID 与 GID:子网内与跨子网的两套寻址
InfiniBand 的寻址体系和 TCP/IP 完全不同。规范同时定义了两套地址:LID(Local Identifier)和 GID(Global Identifier)。LID 是 16 位,由子网管理器在子网初始化时动态分配,交换机转发包时只查 LID;GID 是 128 位,采用 IPv6 的格式:高 64 位是子网前缀,低 64 位通常是端口 GUID。交换机和路由器分别消费这两类地址信息。
为什么需要双层地址?最直接的原因是转发效率。处理 16 位 LID 的线性转发表可以做得很大且查找很快,一次查表就能确定出端口;如果每个 hop 都拿 128 位 GID 做路由查找,成本会高得多。LID 是子网内的高速路牌,GID 是跨子网的全球定位。第 4 章明确要求:路由器除了 LID 路由,必须在 GRH 里读 DGID 做决策。
这里有个关键细节:LID 不是端口硬件自带的物理地址,而是子网管理器分配的临时身份。每个节点的 HCA 端口至少有一个 LID,交换机的每个端口也有一个;子网重配置后 LID 可能改变。初学者在 SMI 查询结果里看到 LID,就把它当成 MAC 地址用,跨子网时翻车,发现 DLID 和 SLID 完全不参与 L3 路由。正确的心智模型是:LID 是子网拓扑内的局部标签,GID 才是地址的稳定身份。
3.2 数据包格式:LRH 到 BTH 字段为什么存在
数据包是协议机制落地的实体。1.4 规范里的数据包格式非常克制:基础包由 LRH、可选 GRH、BTH 和扩展头组成,每个头都有自己的设计动机,值得逐个看。
| 头 | 长度 | 关键字段 | 设计动机 |
|---|---|---|---|
| LRH | 8 字节 | VL、SL、LVer、LNH、DLID、SLID | 子网内一跳转发所需的全部信息 |
| GRH | 40 字节 | SGID、DGID、HopLimit、TrafficClass | 跨子网的全局路由 |
| BTH | 12 字节 | OpCode、PKey、DestQP、PSN | 传输层可靠性和寻址 |
| RETH | 16 字节 | VA、RKey、长度 | 远程内存读写的 DMA 描述 |
| AETH | 4 字节 | ACK/NAK、MSN、SYN | 确认与流量窗口控制 |
LRH 里的 VL 字段直接对应物理链路上的虚拟通道,流控按 VL 排队;SL 是上层标记的服务等级,交换机要依据 SL 做映射。DLID 是转发的核心依据,SLID 用于源端口识别。这些字段统一用网络字节序,规范专门写了 byte ordering。字节序错位是极其隐蔽的调试陷阱:两端解析出来的 VL、SL 全反,报文长度越界却不知道根因。
BTH 是所有传输服务必须的头。OpCode 区分 SEND、RDMA_WRITE、RDMA_READ、ATOMIC、ACK 等;PKey 是 16 位分区键;DestQP 告诉 HCA 这个包交给哪个 QP;PSN 支撑排序和重传。注意一点:驱动构造 WQE 时不会手写 BTH,它由 HCA 根据 QP 上下文自动生成。所以调试时抓到 PSN 对不上,问题多半在 QP 状态机初始化的早期阶段,而不是链路丢包。方向错了,花几天也找不出原因。
3.3 GRH 与 RoCEv2:以太网上的 InfiniBand
1.4 规范的两个重要附录,RoCEv1 和 RoCEv2,把 IB 的传输层搬到了以太网上。RoCEv1 的做法最直接:保留 GRH、BTH 及以上的全部 IB 报文,去掉 LRH,把它们直接当作以太网 payload,以太网类型字段设为 0x8915。它的前提是以太网必须无损,要用 PFC 优先级流控模拟 IB 链路层的 credit 机制,否则拥塞丢包会让上层可靠性迅速崩溃。
RoCEv2 更彻底:在 BTH 前面套一个 UDP 头和一个 IPv4/IPv6 头,UDP 目的端口固定 4791。UDP 在这里的身份不是可靠传输协议,而是一个轻量多路复用器:目的端口标记这是 RoCEv2 流量,源端口由软件设置,可以被交换机拿去做 ECMP 的哈希因子。为什么不选 TCP?因为 TCP 的连接状态和重传会与 IB 传输层自己的重传机制打架,而且 TCP 的拥塞控制会给 RDMA 引入不可控的延迟。UDP 虽然不可靠,但 IP 层负责路由,payload 完整交给 IB 传输层处理。
对比 native IB 和 RoCEv2 的转发路径能看清设计:native IB 里,交换机看 LRH 的 DLID 转发;RoCEv2 里,普通以太网交换机看 IP 头路由,支持动态负载均衡的交换机看 UDP 五元组做逐流分流。上层 QP、PKey、RKey、PSN 机制完全不变。这正是 1.4 把 RoCEv2 写成附录而不是另立新协议的原因:它复用的是 IB 传输语义,只是换了一层物理转发底座。
4. 服务质量与隔离机制:虚拟通道、服务等级、分区键如何保证“不互相干扰”
4.1 虚拟通道 VL 与流控
InfiniBand 物理链路上并不是一条单车道,而是被切分成多条虚拟通道(VL)。规范允许最多 16 条 VL,编号 0 到 15,其中 VL15 保留给子网管理,其余用于数据。每个 VL 在链路接收端都有一组独立缓冲区和 credit 计数器。发送端向某个 VL 发包时,要先确认该 VL 的 credit 充足,否则必须等待。
credit 流控是 InfiniBand 低延迟的基石。以太网依赖丢包后重传,IB 则试图通过链路层信用机制从物理上避免中间队列耗尽导致的丢包:接收端把自己可用缓冲的大小换算成 credit,发送端严格按照 credit 发包。每个 VL 独立结算,意味着高优先级的 VL 不会被低优先级流量占满缓冲。这就是「虚拟通道隔离」的底层含义:隔离的不是带宽,而是缓冲和流控状态。
为什么需要 16 条而不是一条?经典动机是避免死锁和头阻塞。设想两条方向交叉的流量共用一条缓冲队列,一旦缓冲区占满且双方都在等对方释放 credit,就会形成死锁。多个 VL 提供多份独立缓冲池,让不同语义的流量走不同的通道,把死锁概率压到极低。这个设计在 1.4 里没有改动,因为它是链路层稳定性的基座。
4.2 SL 到 VL 映射与注入速率限制
SL(Service Level)和 VL 是常被混淆的一对概念。SL 是端到端的 QoS 标签,放在 LRH 里,报文沿路径不变;VL 是每一跳链路局部的转发通道,在交换机上可以重新映射。规范规定每个交换机的每个输入端口都有一张 SL-to-VL 映射表,决定某个 SL 的包进入哪个 VL 队列。SL 本身不直接决定优先级,真正决定调度优先级的是 VL 在端口仲裁器中的配置。
这种两层 QoS 映射的目的,是把业务等级和物理通道解耦。上层看到的 SL 是稳定的业务属性:控制流量 SL 0,存储流量 SL 1;底层网络管理员可以针对每条链路设置不同映射,甚至把一个 SL 映射到多条 VL 做负载分担。排障时也要用到这个视角:两个 SL 映射到同一个 VL 时,它们共享流控和调度域,会互相抢占;映射到不同 VL,才能获得真正意义上的隔离。很多 QoS 调优无效,根因就是把 SL 和 VL 当成一回事。
注入速率限制(Injection Rate Limiting)则从源头治理拥塞。规范允许端点按配置的速率平滑发包,避免多个节点同时向同一热点猛灌数据。实现上,硬件维护令牌桶,以字节或包为单位补充 token。它和流控是互补关系:流控是链路级的接收端反馈,速率限制是发送端自主限速。只配流控不配限速,热点交换机仍可能被瞬时突发压塌。
4.3 分区与保护域:密钥机制如何做访问控制
InfiniBand 的隔离不只有 QoS,还有访问控制:分区键(PKey)和保护域(PD)。PKey 是 16 位整数,每个 QP 关联一个 PKey,多播组也关联 PKey。当报文到达对端 HCA 时,接收端校验报文 PKey 与 QP 上下文的 PKey 是否匹配。不匹配的包会被直接丢弃,甚至不回错误确认——这是刻意为之的「安静失败」,避免信息泄露。
分区机制类似 VLAN 隔离,但更细致。规范规定编号 0xFFFF 代表默认全分区,任何 QP 都可以属于它。很多 API 实现里,创建 QP 时若不显式指定 PKey,就会默认落到 0xFFFF。在 RoCEv2 环境中,这会导致一个隐蔽的安全隐患:只要目标网卡有可用的 QP,同一二层域里的其他节点可能把报文送进来。1.4 的虚拟化附录进一步把 PKey 用作虚拟隔离网络的标识,相当于每个租户分配独立广播域。
PD 则是 verbs 层的内存保护对象。注册内存时得到一个 lkey/rkey,它们内部关联 PD;创建 QP 或 Memory Window 时也关联 PD。当 RDMA 请求中的 RKey 指向某块内存,HCA 会同时校验 RKey 和 PD 一致性。这个机制的动机很直接:不允许另一个进程的 QP 访问受保护进程的内存。只盯 PKey、忽略 PD 的人,踩到跨进程 RKey 访问被拒时,常常发现日志里什么也没有,其实失败点在 PD 校验。
5. 常见问题与避坑:读规范时最容易理解偏的几个点
读规范最容易走偏的,不是看不完,而是把某一层的语义套到另一层。以下五条是我在真实环境里反复踩过的坑,每一条都符合“现象-原因-解决”三段式,按这个顺序排查,能省下不少血泪时间。
5.1 现象:把 LID 当成全局地址,跨子网一试就懵
现象:在子网 A 里通过 sa query 拿到某个 HCA 端口的 LID 是 0x0002,于是把它写进对端连接的地址属性,期望跨过路由器直接互通。结果报文在子网 A 的边界路由器上被正常转发,到了子网 B 却无人接收。
原因:LID 是子网管理器为每个端口临时分配的局部标签,只在分配它的那个子网内有意义。子网 B 的端口可能也有 LID 0x0002,但它属于另一个管理域;路由器根据 GRH 的 DGID 做跨子网决策,DLID 只对子网内一跳有效。很多人把 LID 类比为 MAC 地址,这个类比在单子网里勉强成立,跨子网立刻失效。
解决:设计多子网集群时,把跨子网通信的端点信息统一收敛到 GID 上。GID 的高 64 位是子网前缀,天然标识了归属,低 64 位是端口 GUID,不会因为子网重配置而变化。至于 LID,留给子网内的线性转发表和故障排查。抓包看到 DLID 变化时,先问自己这个包当时处于第几跳,而不是直接怀疑交换机表项坏掉。
5.2 现象:以为不可靠连接完全不重传,出错了就是玄学
现象:用不可靠连接 UC 跑 RDMA 写,线上偶尔出现数据不一致;应用层重试一次又好了,于是把它当成某种玄学。检查物理链路又是正常的,误以为链路偶发误码。
原因:UC 排除了端到端 ACK/NAK 与超时重传,只保留连接关系和分片语义。链路层虽然能做 CRC 校验,但校验失败后能做的只是丢弃坏包;没有上层重传,丢失的 payload 就彻底没了。高层的重试之所以有效,是因为应用重新提交了 WQE,而不是网络自己恢复了数据。相比之下 RC 的发送端保存未确认包并超时重传,对上层表现为「几乎没有偶发错误」。
解决:选传输服务前先回答:这条数据流能否容忍端到端丢包?存储、分布式事务这类对一致性强敏感的业务直接上 RC 或 XRC;UC 只适合流媒体、监控画面这类丢一个帧可以接受的场景。另外,即使是 UC,也不代表错误码完全不可见,HCA 会记录本地错误和远端丢弃计数,排查时先看这些计数器再下结论。
5.3 现象:RoCEv1 和 RoCEv2 混为一谈
现象:项目规划时写「网络支持 RoCE」,结果上线时发现交换机不路由 RoCEv1 帧,流量一出接入层就消失。
原因:RoCEv1 帧没有 IP 头,以太网类型 0x8915 只能被二层交换机处理,普通三层交换机不认识这个类型,不能路由。而且 RoCEv1 依赖无损以太网,需要 PFC 在所有链路上一致配置,否则拥塞丢包直接触发上层超时。RoCEv2 把 IB 报文封装进 UDP/IPv4/IPv6,目的端口 4791,才具备跨子网能力和 ECMP 路径选择能力。
解决:在任何「RoCE 支持矩阵」里都明确标注 v1 还是 v2。抓包时看以太网类型:0x8915 是 v1,0x0800 后面的 UDP 负载就是 v2。规划网络时优先选 v2,因为它可以复用现有 IP 路由和负载均衡;但要注意 RoCEv2 仍建议配合 PFC 或 DCQCN 这类拥塞控制,否则在满负载交换机上一样会丢包。
5.4 现象:把 SL 当成优先级,忽略 SL 到 VL 映射
现象:给高优先级业务打上 SL 1,低优先级打上 SL 0,预期高优先级带宽更高、延迟更低。实际测试看两个流几乎平分端口带宽,排队延迟也没有差别。
原因:SL 只是 LRH 里的一个 4 位标签,交换机不会凭 SL 直接调度。它必须先去查本端口的 SL-to-VL 映射表,得到一个 VL 编号,再进入该 VL 的缓冲队列;队列的调度权重由 VL 的仲裁配置决定。如果两个 SL 映射到同一个 VL,它们的优先级差异在交换机内部根本不存在。
解决:配置 QoS 时至少要完成三步:建立 SL-to-VL 映射表,为不同 VL 分配独立的缓冲和调度权重,再把应用流量正确标记到 SL。排查时先看端口计数器,区分不同 SL 实际落入的 VL;若发现落入同一 VL,再回头查映射表。很多 SL 优先级的失效问题,最后都落在这张表上。
5.5 现象:忽略分区键和保护域导致队列对访问控制失效
现象:两个服务在同一台机器上各自使用不同进程,A 进程注册内存后把 rkey 和地址交给 B 进程的 QP 使用,B 端 RDMA 写一直得到访问错误,日志里还没有任何说明。
原因:PKey 和 PD 双重校验没通过。QP 上配置的 PKey 与目标分区的 PKey 不一致,HCA 会静默丢弃;如果 PKey 一致,但 rkey 对应的内存区域所属 PD 与 QP 所属 PD 不同,HCA 同样拒绝访问。RoCEv2 环境下默认 PKey 0xFFFF 掩盖了很多问题,让两者看似都通,一旦有人改成其他值,队列对访问立刻失效。
解决:把 PKey 和 PD 当作权限上下文来管理:创建 QP 时显式指定 PKey,不依赖默认值;注册内存、创建 QP、创建 Memory Window 时统一用同一个 PD。排查时先读 HCA 的端口错误计数和 QP 的错误状态寄存器,不对就从 PKey 到 PD 逐层核对。这类问题不会产生传统的网络错误包,只能靠计数器和上下文来定位。
6. 设计取舍与现代延伸:为什么 1.4 是理解数据中心互连的“分水岭”
1.4 规范最值得玩味的设计决策,是它没有推倒重来,而是选择了向后兼容的增量演进。从 1.0 到 1.3,规范在 SA 与 CM、XRC、子网管理等方向上持续叠加;到 1.4,RoCEv1/RoCEv2 被正式纳入附录。这种做法的代价是报文头里保留了相当多的保留字段和兼容位,实现者必须处理大量「合法但很少用」的分支;收益则是十多年的硬件实现可以平滑升级,数据中心不需要一夜之间更换全部网卡。
现代延伸里最典型的是 RoCEv2 在高性能计算和存储网络中的地位:它把 IB 的传输语义和 IP 网络的运维基础设施合在一起,让 ECMP、传统交换机、监控体系都能复用。另一个值得关注的方向是规范里的虚拟化附录:它把 PKey、PD、虚拟队列这些抽象提升为多租户隔离的正式手段,说明 InfiniBand 已经不只是超算内部的高速总线,而是云基础设施的底层互连选项。
我建议手头常备这份 1.4 规范的 PDF,精读顺序是:第 3 章架构、第 5 章数据包格式、链路层相关章节,再回来看 RoCE 附录。验证我是否读懂一份协议,方法很固定:先在纸上画出 QP 状态机和一次 RDMA 读的时序,再上真实环境抓包,按 LRH、BTH、RETH 逐字段和规范对照一遍。曾经有一次,我发现抓包里的 P_Key 与配置不一致,翻遍代码才发现是默认值在作怪。从那以后,我每次处理互连问题都强制走一遍「状态机 → 报文 → 配置」的检查顺序,很多自认玄学的问题,最后都落在一两个字段的语义错位上。希望帮到你。
本文还有配套的精品资源,点击获取