InfiniBand 专题【左扬精讲】—— InfiniBand 打流与性能基线:perftest 工具族、参数语义与故障判读

InfiniBand 专题【左扬精讲】—— InfiniBand 打流与性能基线:perftest 工具族、参数语义与故障判读

本系列前面十四篇,没有一篇回答过运维每天真正会问的那个问题——这条链路现在到底跑得怎么样。第八篇讲子网怎么初始化,第九篇讲 SM 怎么持续监控,第十一篇讲路径怎么算,第十二篇讲软件栈怎么装,第十四篇讲固件怎么升。这些回答的都是"能不能工作",而不是"工作得好不好"。而"好不好"这件事,在 IB 上只有一种客观判据:打流,然后用计数器说话。

本篇是 InfiniBand 专题【左扬精讲】系列的 第 15 篇,主题是InfiniBand 打流:工具分层、perftest 参数语义、以及打完之后如何用端口计数器判读结果。本系列第七篇的第十一节已经介绍过 perftest 工具集与六个基础工具,本篇不重复那部分内容,而是补上三块它刻意没展开的:perftest 全部参数的适用边界、接收侧与 RNR / SRQ 这一整块、以及打流之后必须做的事——把端口计数器读对。

本篇的核心命题只有一句:打流本身不产生结论,结论产生在"打完之后按正确口径读计数器"这一步。跑出一条漂亮的带宽数字却把它当作"网络没问题"的证据,是这条链路上最常见、也最贵的错误。

开篇必读:八组必须先分清的辨析,全部有官方文档出处

本篇出现的每一条工具行为都来自文末「参考资料」列出的官方文档(perftest README、perftest man page、infiniband-diags man page、内核 sysfs ABI 文档、NVIDIA 支持文档)。下面八组辨析是全文可复现性的前提,其中有五组是直接与流传的错误说法相对立的:

#常见说法实际情况依据
1 "随便连根线就能打流,子网管理器不一定要装" 打流前必须有 SM 在跑 perftest README 的 IMPORTANT NOTE:在 InfiniBand fabric 上跑基准测试前,交换机或某个节点上必须已运行子网管理器
2 "ib_write_lat -q 4 可以测多 QP 延迟" 延迟类工具直接拒绝多 QP perftest 源码中 -q 分支的判定:非 BW 测试且 num_of_qps > 1 时打印 " Multiple QPs only available on bw tests" 并返回 FAILURE
3 "ib_write_bw --use-srq 能让 RDMA Write 走低开销接收队列" --use-srq 只对 Send 类生效 man page 对 --use-srq 的说明以 "Relevant only for Send" 结尾
4 "port_xmit_data 读出来就是字节数" 是八位字节数除以 4 perfquery(8) 与内核 sysfs ABI 文档一致表述:表示 Data 的分量指示 octets divided by 4,而不是 octets
5 "t_typical 和 --output=latency 都是延迟,差别不大" 一个取中位数,一个取平均值 README 说延迟测试报告 minimum, median and maximum,并指出中位数对高延迟波动不那么敏感;man page 对 --output 的说明则是 "Latency measurement is Average calculation"
6 "ibping 打出来的时间可以当延迟基线" 它验证连通性,不是延迟测量 NVIDIA UFM 文档对 ibping 的描述:用厂商 MAD 验证 InfiniBand 节点之间的连通性,退出时显示类 IP ping 输出
7 "两端 perftest 版本差一点没关系" 必须两端同版本 README Known Issues 第 3 条:不同版本的 perftest 可能互不兼容,请两端使用同一版本以保证基准结果的一致性
8 "perftest 跑出来的带宽就代表我的业务性能" 基准是合成操作流,不模拟真实业务 README Benchmarks Description 原文:"The benchmarks are not designed to emulate any real application traffic." 并指出真实业务流量受很多参数影响,仅凭基准结果无法预测

第 8 条是本篇最重要的纪律约束,它同时也解释了本篇为什么全文不给出任何带宽或延迟数值:性能数字只有在明确的硬件型号、链路速率、MTU、拓扑跳数、消息大小与对端配置这六项同时被记录时才可复现,缺任何一项,这个数字就无法被别人验证,也就不该被写进文章当作结论。

本篇速查卡:术语 / 命令 / 选项 / 依据
术语按第三节四层模型分组;选项只列正文解释过的,全量清单见文末「参考资料」

┌─ 术语 ① 传输层机制 — perftest 驱动的对象 ─────────────────────────────┐
│ QP Queue Pair 队列对 = 一条打流连接 │
│ WQE Work Queue Element 硬件消费的执行单元 │
│ CQE Completion Queue Element 硬件回报执行结果的那一条 │
│ PSN Packet Sequence Number 重传与乱序判定的依据 │
│ SRQ Shared Receive Queue 共享接收队列:仅对 Send 类有效 │
│ RNR Receiver Not Ready 接收方未就绪:NAK 的一种,不是错误 │
│ NAK Negative Acknowledge 否定应答 │
│ RNR NAK timer 收到 RNR 后的重试等待时长 │
└───────────────────────────────────────────────────────────────────────┘
┌─ 术语 ② 链路与 fabric — 布尔检查看不到的那一面 ───────────────────────┐
│ VL Virtual Lane 虚拟通道:各自独立仲裁 │
│ SL Service Level 决定 QoS 路径与 VL 映射 │
│ VL-to-VL 虚拟通道到虚拟通道的映射表 │
│ QoS Quality of Service 服务质量 │
│ P_Key 分区键:两侧不匹配则 QP 建不起来 │
│ credit 信用:发送方 buffering 的许可额度 │
│ MTU Multi-Transport Unit 单次传输上限:分片与拥塞的根源 │
│ LID Local Identifier 子网内路由用的 16 位地址 │
│ GID Global Identifier 子网外路由用的 128 位地址 │
│ SM Subnet Manager VL 仲裁与路由的决策者 │
└───────────────────────────────────────────────────────────────────────┘
┌─ 术语 ③ 主机侧与计数器 — 数字从哪来 ──────────────────────────────────┐
│ NUMA Non-Uniform Memory Access 非统一内存访问:决定绑核策略 │
│ uverbs 用户态 verbs 接口:perftest 入口│
│ RDMA Remote Direct Memory Access 远程直接内存访问 │
│ PMA Performance Management Agent 性能管理代理:计数器的持有者 │
│ PerfMgt 性能管理 GMP 类:perfquery 通路 │
│ Informative / Error 计数器的两类,判据完全不同 │
└───────────────────────────────────────────────────────────────────────┘
┌─ 术语 ④ 工具自身的定位 — README 原文用词 ─────────────────────────────┐
│ perftest a collection of tests written over uverbs │
│ micro-benchmark 性能微基准:本工具的自我定位 │
│ tuning 调优:横向对比,不是绝对承诺 │
│ functional testing 功能测试:顺带能做,但不是主业 │
└───────────────────────────────────────────────────────────────────────┘
┌─ 命令 ① 被动扫描层 → 输出是【事实】 ──────────────────────────────────┐
│ ibstat / ibstatus 本机端口 Rate / Width / State / LID │
│ ibnetdiscover 遍历 fabric,输出拓扑摘要 │
│ ibdiagnet 全量扫描并落盘,前后两次做 diff │
└───────────────────────────────────────────────────────────────────────┘
┌─ 命令 ② 连通性层 → 输出是【可达】 ────────────────────────────────────┐
│ ibping 默认客户端;服务端必须 -S │
│ ibtracert 逐跳路径追踪 │
│ ibroute / ibidsverify 转发表 / GUID 唯一性 │
└───────────────────────────────────────────────────────────────────────┘
┌─ 命令 ③ 传输层打流层 → 输出是【应用吞吐】 ────────────────────────────┐
│ ib_write_bw / ib_write_lat RDMA Write:最常用的一对 │
│ ib_read_bw / ib_read_lat RDMA Read │
│ ib_send_bw / ib_send_lat 唯一用得上 SRQ 的一类 │
│ ib_atomic_bw / ib_atomic_lat CMP_AND_SWAP / FETCH_AND_ADD │
└───────────────────────────────────────────────────────────────────────┘
┌─ 命令 ④ 计数器层 → 输出是【链路事实】 ────────────────────────────────┐
│ perfquery 经 PerfMgt GMP 取 PortCounters,可读远端 │
│ ibqueryerrors 只取错误类计数器 │
│ sysfs counters/ 读本机驱动暴露的同一组 PortCounters │
└───────────────────────────────────────────────────────────────────────┘
┌─ 选项索引 — 按「本篇哪一节讲它」排列 ─────────────────────────────────┐
│ §6 报告与测量 -C -H -U --report_gbits --report-both │
│ -e -Q │
│ §7 打多少/怎么打 -D -f -n -N -t -r -l -q -m -a │
│ -I -o -c -S -b -F │
│ §8 接收侧与 SRQ --use-srq -u -T │
│ §9 绑核与拓扑 --pin_cores --numa_node --disable_numa │
│ --use_hugepages │
│ §10 限速 --rate_limit --rate_units --rate_limit_type │
│ --burst_size --typical_pkt_size │
│ §11 数据校验 --data_validation --payload_file_path │
│ --use_cuda │
│ §12 报文层 -x --dlid --tclass -A -g │
└───────────────────────────────────────────────────────────────────────┘
┌─ 依据 — 完整引用见文末「参考资料」 ───────────────────────────────────┐
│ perftest README Overview / Methodology / Options │
│ post_list、data_validation 两节 │
│ perftest(1) man page RUNNING TESTS 与 OPTIONS 条目 │
│ ibv_modify_qp(3) 四个可靠性字段与状态迁移表 │
│ perfquery(8) 计数器属性列表与选项 │
│ infiniband-diags(8) Utilities list 九类分组 │
│ sysfs ABI / NVIDIA 文档 rate / state / counters 语义 │
└───────────────────────────────────────────────────────────────────────┘

★ 本篇全文不给出任何带宽或延迟数值 —— 缺六项环境记录即不可复现。

InfiniBand打流perftest性能基线带宽延迟RNRSRQNUMA限速perfquery端口计数器ibdiagnet传输层verbsPMAQoSMTUQP

★ 学完这一篇你能掌握什么(渐进式路径:1→2→3→4→5 不可跳跃)

  • 第 1 步 · 知道为什么必须打流,以及它回答不了什么(第一节、第二节)
     • What:配置正确 ≠ 性能达标;六类"布尔检查全过但性能不达标"的故障(宽度/速率未达标、MTU 偏低、信用环、P_Key 不匹配、VL 仲裁饿死、缓冲配比不足);打流的四重身份(验收 / 定位 / 回归 / 排除)
     • How:能判断一条性能问题是否属于"只在有流量时才暴露"那一类;能按身份选择该用绝对值、还是单变量对照、还是限速复现
     • Why:理解"为什么被动扫描观测控制面、打流观测数据面,两者维度没有交集"——本篇第三节会给出四层分层
  • 第 2 步 · 认清工具分层,并能把命令跑起来(第三节、第四节)
     • What:四层工具谱系;两行运行模型(Server: ./<test> <options> / Client: ./<test> <options> <server IP>);README 的五条 Prerequisites 与 man page 的四条 IMPORTANT NOTES
     • How:能独立完成从零到第一条可信带宽数字的六步路径;能复现 man page 的两个官方示例
     • Why:理解 "为什么两端选项必须逐字相同"(README 三颗星强调)与"为什么归零是唯一事后无法补救的一步"
  • 第 3 步 · 拿到日常高频命令,并说得出测量原理(第五节、第六节)
     • What:巡检 / 连通性 / 打流 / 计数器四类命令;六条日常默认写法(带宽用 -D、显式 -m、延迟读 t_typical、定位不用 -a…);三个测量动作(CPU 周期计数器取时间戳、构造流量、选择读数口径)
     • How:能照抄第五节的 10 分钟冒烟流程;能解释 -l 为什么能让消息速率跳一个数量级(post_send 约 500 ns → 软件上限约 10 Mpps)
     • Why:理解 "为什么 -C 报周期数比报微秒更可比"(消掉频率漂移)、"为什么双向带宽可以超过线速"(两个方向合计)
  • 第 4 步 · 掌握发送侧与接收侧的参数边界(第七节、第八节、第九节、第十节、第十一节、第十二节)
     • What:时长类(-D / -n / -N)、深度类(-t / -r / -l)、并行类(-q / -O)、可靠性类(min_rnr_timer / rnr_retry / retry_cnt / timeout)四组;RNR NAK 是接收侧无 WQE 时的应答,不是错误
     • How:能读懂 man page 中每一项后面的 "Relevant only for …" 限定,并据此判断一条命令在该工具上是否有效;能按 4 µs × 2timeout 换算 -u 的默认超时
     • Why:理解 "为什么 --use-srq 对 RDMA Write 无效"——SRQ 解决的是"接收方没有为这条 QP 预投递 WQE"的问题,而 RDMA Write 的接收侧本来就不需要预投递
  • 第 5 步 · 把结果翻译成证据,并串成可重复的流程(第十三节、第十四节、第十五节与全篇 FAQ)
     • What:port_xmit_data 与 port_rcv_data 是八位字节数除以 4;port_xmit_wait 的两种成因是信用不足与仲裁不足;link_downed 与 link_error_recovery 是两件事;六步流程与"改一个变量、其余全固定"的实验纪律
     • How:能打流前清零、跑流后读取、用 perfquery -r 做"读后归零";能按 /sys/class/infiniband/<dev>/ports/<port>/rate 把聚合流量折算到链路速率以判断是否打满
     • Why:理解 "为什么 perftest 的带宽数字必须与端口计数器相互印证"——前者是应用视角的吞吐,后者是链路视角的字节数,两者口径不同,差值本身携带信息;以及"为什么本系列第十二篇把"没讲怎么建立带宽与延迟基线"列为待办"——本篇正是兑现这一承诺的那一篇

★ 阅读前提 & 本篇不涉及的内容

  • 前置知识:本系列第七篇《传输层:QP、分段重组、传输服务与分区》中的 QP / WQE / CQE 流转、五种传输服务类型、MTU 五档位与 RC 分段能力,以及其第十一节已给出的 perftest 六个基础工具与 t_typical / BW average 两个读数列——本篇不重复解释这些内容,只在需要时引用;本系列第八篇《子网初始化:拓扑发现、LID 分配、路径计算与子网激活》中的 SM 六阶段初始化与端口四参数(本篇第十三节会把 Rate 参数接回这条链路);本系列第九篇《SM 监控与拓扑变化:选举、故障切换、扫描与重收敛》中的 ibstat / smpquery 字段判读;本系列第十篇《拓扑与路由引擎:Fabric 拓扑、路由引擎、自适应路由与信用环》中的信用环成因——本篇第十三节的 port_xmit_wait 是它的直接观测口
  • 信息来源:本篇出现的全部 perftest 选项与语义、默认值、"Relevant only for …" 限定、README 的方法学说明与 Known Issues、ibv_qp_attr 四个可靠性字段、perfquery 的属性列表与选项、内核 sysfs 各字段语义、mlx5 计数器名与 PortCounters 字段的映射、ibdiagnet 的输出文件清单,均引自文末「参考资料」列出的官方文档。本篇不含任何未经上述来源核实的工具行为描述,也不含任何带宽、延迟或吞吐数值
  • 本篇不涉及:多节点矩阵打流与 all-to-all 编排(本篇只覆盖点到点双端)、NCCL / RCCL 集合通信测试、GPUDirect RDMA 打流(--use_cuda / --use_rocm / --use_hl / --use_neuron 系列)、AES_XTS 加密打流(--kek_path / --credentials_path 的配置流程)、raw_ethernet_* 系列工具的以太网侧选项、IB Router 上的打流、存储侧(NVMe-oF / SPDK over RDMA)的验证口径

一、打流的定位:它在验证链上的哪一环

What — perftest 自我定位的三个关键词,以及一条必须先讲清的纪律

perftest README 的 Overview 段把自己定义为 "a collection of tests written over uverbs intended for use as a performance micro-benchmark"。这个定义里有三个信息量很大的限定词,每一个都在缩小它的适用范围:

  • written over uverbs —— 它跑在用户态 verbs 接口之上,不是内核旁路、不是硬件裸测。因此它测出来的是"应用经 verbs 能拿到多少",而不是"链路能传多少"
  • performance micro-benchmark —— 微基准。它每次只动一个维度,刻意不复现任何真实应用的并发结构与消息模式
  • may be used for HW or SW tuning as well as for functional testing —— 它同时是功能测试工具。这一点常被忽略:一条 perftest 命令跑不通,本身就是一条功能缺陷证据,不需要等带宽数字才有意义

而 Benchmark Description 段的最后两句,是整个工具集最诚实、也最容易被跳过的一段声明:

"The benchmarks generate a synthetic stream of operations, which is very useful
 for hardware and software benchmarking and analysis.
 The benchmarks are not designed to emulate any real application traffic.
 Real application traffic may be affected by many parameters, and hence
 might not be predictable based only on the results of those benchmarks."

把这段话翻成可操作的规则:perftest 的输出是"硬件与软件在受控条件下的能力上限",不是"你的应用能达到的吞吐"。因此本篇主张一条明确的纪律——perftest 的数字只用于纵向对比,不用于横向承诺。纵向对比指同一台机器、同一套线缆、同一套参数下,改动前后的比较;横向承诺指"我测出这个数,所以能承诺业务能到这个数",后者是这份文档明确否定的用法。

另一条容易被忽略的前置条件:SM 必须先跑起来。README 在 IMPORTANT NOTE 里写得很直接:在 InfiniBand fabric 上运行这些基准测试之前,交换机或 fabric 中的某个节点上必须已运行子网管理器。这条约束与本系列第八篇讲的机制完全一致——LID 是在子网初始化阶段由 SM 分配的,SM 没跑起来就没有 LID,没有 LID 就无法建立 QP 的寻址。因此"打流之前先确认 SM 在跑"不是一条流程上的繁琐建议,而是由寻址前提推出的硬性顺序。

顺带把"打流跑不通"时最容易被跳过的排查顺序也说了:先确认子网已激活,再确认链路已 LinkUp 且端口 Active,最后才怀疑工具与参数。本系列第八篇第九节给出的端口物理状态与逻辑状态判读,以及第九篇的 ibstat 字段判读,在这一步直接可用。

本节关键记忆:3 个限定词 + 1 条纪律 + 1 个前置条件

  • 3 个限定词:uverbs 之上的微基准,同时可用于调优与功能测试。因此"跑不通"是功能缺陷证据,"跑得慢"才是性能问题
  • 1 条纪律:perftest 数字只用于纵向对比,不用于横向承诺。官方原文明确其不模拟真实业务流量,真实业务仅凭基准结果无法预测
  • 1 个前置条件:打流前必须已有 SM 在跑(README IMPORTANT NOTE)。SM 负责 LID 分配,无 LID 则无法建立 QP 寻址
  • 1 个排查顺序:子网已激活 → 端口 Active → 才轮到工具与参数

二、为什么必须打流:四条不可替代的理由

What — 前十四篇证明的是"功能正确",而性能是另一个独立的命题

本系列到第十四篇为止,已经走完了一条完整的功能正确性链路:设备就绪(第十二、十四篇)→ 拓扑发现(第八篇)→ 路径计算与转发表下发(第十、十一篇)→ 子网激活(第八篇)→ 持续监控与重收敛(第九篇)。这条链路上的每一步都有客观判据,而且这些判据全是布尔式的:端口是 Active 还是不是,转发表里有没有那条 record,Master SM 在不在主控。

但"布尔全真"推不出"性能达标"。这是本节要立的第一个论点:配置正确与性能达标是两个彼此独立的命题。一个 fabric 可以在上述每一项检查上全部通过,同时只能跑出设计带宽的一小部分。原因在于这类故障不改变任何端口状态、不产生任何错误计数器、不触发任何拓扑变化——它们只在数据真正流动起来的时候才暴露。

故障现象为什么布尔检查看不出来本系列出处打流如何暴露它
链路宽度或速率未协商到设计值 端口仍然是 Active,phys_state 仍然是 LinkUp,只是 Rate 字段低于预期 第十四篇 ibstat 的 Rate 字段判读 用 -D 跑一段固定时长的绝对带宽,与 rate 字段折算出的理论上限对比
路径 MTU 档位低于设计值 端口 Active、MAD 可达,只是实际协商到的 MTU 小,所有状态字段仍然正常 第八篇端口四参数中的 MTU 显式指定 -m 4096 跑一次,若硬件在大包路径上出现异常,误差会立刻放大
信用环(credit loop) 不产生任何错误计数器,链路也不 down,控制面完全正常 第十篇《信用环的成因与致命性》 高负载下带宽上不去,同时 port_xmit_wait 显著非零——两者同时出现是它的指纹
P_Key 分区两侧不匹配 分区表不匹配的后果落在丢包上,而这类丢弃未必反映在标准错误计数器里 第七篇 P_Key 分区 --pkey_index 打非默认分区,成功即证明两侧分区表一致
VL 仲裁或 SL-to-VL 映射把某类流量饿死 控制面与管理面流量正常,只有被降级的业务面流量受损 本篇第十二节的 SL-to-VL 验证 必须两条流并发才暴露:一条管理流量、一条打流流量,观察后者是否被压制
交换机缓冲配比不足 空载观察不到,只有突发流量才会溢出 第八篇交换机侧缓冲参数 用高 -t / 高 -q 制造突发,再读 excessive_buffer_overrun_errors 与 VL15_dropped

把这张表竖起来读,会得到本节的核心结论:上面六行里,没有任何一行能被 ibnetdiscover、ibstat 或 ibdiagnet 判出来。它们不是这些工具测得不够细,而是这些故障在这些工具的观测维度里根本不产生信号。本篇第三节会把工具分成四层并说明各层的观测对象;第二节先记住一个判据:凡是"只在有流量时才出问题"的故障,都只能靠主动打流发现。

2.1 打流的四重身份:日常运维里你到底在用它干什么

"为什么要打流"这个问题,在实际操作中很少被认真回答。把它拆开,打流其实承担着四种完全不同的职责,而它们对命令的要求并不相同。把这四重身份分清,一个直接后果是:很多团队只在第一种身份下使用打流,于是把打流当成了"验收工具",而它其实同时是一把定位尺和一把回归标尺。

身份触发场景该打什么判据在哪
① 验收
(acceptance)
新 fabric 上线、扩容完成后、"这批线插好了能不能用" 带宽绝对值 + 延迟绝对值,大报文、小报文各一轮 与设计值的比值;本系列第十三篇的三维度选型里,验收是唯一必须做全量打流的场景
② 定位
(bisect)
"带宽只有设计值一半""延迟比上个季度高了一截" 一次只改一个变量:-m → -s → -q → -t → -l 哪一档一改数字就动了,瓶颈就在哪一层;本篇第十五节给出六步流程
③ 回归
(regression)
固件升级、驱动升级、交换机固件更换、参数调整之后 与升级前完全相同的命令,逐字不差 两次读数之差;这是第十四篇固件升级最缺的一环——升级成功不等于性能没变
④ 排他
(排除法)
业务报延迟高、丢包、吞吐掉,但传统网络监控一切正常 用限速与限深把流量压到与业务相当的水位复现 能否在受控条件下复现;能复现 = 打流链路有责,不能复现 = 要往别处找

四重身份对命令的要求差异,值得单独点出来,因为这解释了日常实践里最常见的一类浪费:

  • 验收场景要求"绝对值准",因此必须先归零、再固定时长、最后读计数器。直接跑一条 ib_write_bw -a 看屏幕上的最大带宽,得到的是"这条命令在这台机器上曾经达到过的最好情况",而不是"这条链路现在能提供多少"
  • 定位场景要求"变量可控",因此不能用 -a。-a 一次扫 2 字节到 8 MB 全部尺寸,它把报文大小这个变量也一起变了,而报文大小恰恰是带宽测试里最敏感的那个变量。本篇第十一节把 -a 列为与 --data_validation 互斥的选项,本质上就是这条要求
  • 回归场景要求"命令可复现",因此命令行必须归档,而且必须是两端逐字相同的那一份。本篇第十五节把"命令行原文(两端相同)"列为六类必录项之一,理由就在这里
  • 排他场景要求"条件可缩放",因此要用 --rate_limit 把峰值压到业务水位。业务报的丢包往往发生在负载峰值,而打流如果只用最大压力去复现,很可能触发的是另一类故障(缓冲溢出),反而掩盖了真正的问题

Why — 为什么打流必须是"主动"的,而不能靠被动观察替代

本篇第三节给出的四层分层里,第 ① 层(被动扫描)与第 ③ 层(传输层打流)的根本差别,可以用一句话概括:第 ① 层能证明"某样东西存在且状态正确",第 ③ 层才能证明"某样东西能承载"。

这个差别不是工具设计上的偏好,而是两种观测方式的信息论边界。被动扫描的观测对象是控制面状态——它读的是端口的 phys_state、state、rate、lid_mask,以及转发表与 SL-to-VL 表的内容。这些量在"没有数据流动"的情况下就已经全部确定了,它们与"这条链路实际能搬运多少字节每秒"之间没有任何函数关系——至少在控制面提供的信息里不存在这样的映射。

而打流的观测对象是数据面行为:它投递真实的 RDMA 操作,测量从 WQE 投递到 CQE 回来的真实耗时。这条测量路径本身就把链路上所有可能成为瓶颈的环节都串了一遍——网卡队列对、PCIe 带宽、主机内存带宽、HCA 内部交换、交换机缓冲与仲裁、VL 调度、路径跳数、链路宽度与速率。任何一个环节成为瓶颈,都会立刻反映在测得的耗时上。

这就是为什么本系列坚持"被动工具查前提、主动打流出结论"这条分工,而不是相反。用被动扫描代替打流,会漏掉上表六行里的全部六种故障;用打流代替被动扫描,则会在子网都没激活时得到一个无法解释的数字。两者不是替代关系,是各自观测维度的物理边界。

      为什么被动扫描无法替代打流:观测维度的边界

  被动扫描(第 ① 层)观测的是什么
  ────────────────────────────────────────────────────────────
    端口 phys_state / state        ← 链路训练与逻辑状态
    端口 rate / width             ← 协商结果(静态)
    转发表 / SL-to-VL / P_Key 表   ← 控制面下发的配置
    SM 状态 / 主控归属            ← 管理面选举结果
                    │
                    │  关键:以上全部在【没有数据流动】时
                    │  就已经完全确定,与吞吐/延迟无函数关系
                    ▼
        ┌───────────────────────────────────┐
        │  带宽  ?                          │
        │  延迟  ?                          │
        │  慢的那个环节,扫描器【看不到】     │
        └───────────────────────────────────┘
                    ▲
                    │  打流(第 ③ 层)观测的是什么
                    │  ─────────────────────────
                    │  投递真实 RDMA 操作 → 测量 WQE→CQE 真实耗时
                    │  这条测量路径【串起了所有可能成为瓶颈的环节】:
                    │
                    │    主机 CPU 与 NUMA 亲和
                    │      → 内存带宽 / TLB
                    │        → HCA 队列对与 QP 资源
                    │          → PCIe 宽度与带宽
                    │            → HCA 内部交换与 VL 调度
                    │              → 交换机缓冲 / 仲裁 / 信用
                    │                → 路径跳数
                    │                  → 链路宽度与速率
                    ▼
        任何一个环节是瓶颈,测得的耗时立刻变化

  ★ 结论:两层的观测维度没有交集,因此不能互相替代。
    被动扫描回答「该不该有的状态有没有」;
    打流回答「数据真跑起来时会卡在哪里」。

打流不能回答什么 — 一条必须与"为什么"同等重视的边界

把"为什么打流"讲透之后,必须紧接着讲它的边界,否则前面四条理由会被人当作"打流万能"的论证。本系列第七篇已经给出边界,README 也有原文,这里收拢成三条:

  • 它不模拟真实业务流量。README 原文是 "The benchmarks are not designed to emulate any real application traffic. Real application traffic may be affected by many parameters, and hence might not be predictable based only on the results of those benchmarks."因此 perftest 的数字只能用于纵向对比,不能用于横向承诺。说"我测出 23.5 GB/s 所以业务能跑 23.5 GB/s",是把一个被官方明确否定的用法当成了结论
  • 单机点对点的结果不代表集群的表现。perftest 的 -q 是在一个进程内开多条 QP,而多机集群的瓶颈往往出现在跨交换机的聚合上——本篇第三节说过 "③ 明显大于链路速率"往往意味着测的是聚合路径。多节点矩阵测试是本篇第二节提到的 Roadmap 独立课题
  • 它不解释链路上正在发生什么。perftest 拿到的是 CQE,而 CQE 只说"这个 WR 完成了",不说"这条链路此刻在重传还是在等信用"。本篇第十三节讲的端口计数器才是那个视角。把"打流跑通了"当成"网络没问题",跳过的正是这一层

把这三条边界与前面四条理由放在一起,打流的正确定位就清楚了:它是唯一能把"数据面真实行为"变成数字的工具,因此不可替代;但它只覆盖点对点、只覆盖合成操作流、且完全看不到链路内部状态,因此必须与被动工具和计数器配对使用。本篇第三节的四层、第十二节的 SL-to-VL 验证、第十三节的计数器判读、第十五节的六步流程,全部是围绕这个定位展开的。

本节关键记忆:2 个命题 + 6 类盲区 + 4 重身份 + 3 条边界

  • 2 个命题:配置正确 ≠ 性能达标。功能链路的判据全是布尔式的,而布尔全真推不出性能达标
  • 6 类布尔盲区:宽度/速率未达标、MTU 档位偏低、信用环、P_Key 不匹配、VL 仲裁饿死、缓冲配比不足。共同特征是不产生错误计数器,只在有流量时暴露
  • 4 重身份:验收(绝对值准)/ 定位(变量可控)/ 回归(命令可复现)/ 排他(条件可缩放)。四者对命令的要求不同,混用是最大的浪费
  • 1 个分层依据:被动扫描观测控制面,打流观测数据面,两者观测维度没有交集。打流的测量路径把网卡、PCIe、内存、交换机、路径、链路全部串了一遍
  • 3 条边界:不模拟真实业务(README 原文)/ 单机点对点 ≠ 集群聚合 / CQE 看不到链路内部状态
  • 1 条落地建议:定位场景不要用 -a——它一次扫全部尺寸,等于把最敏感的"报文大小"变量也一起变了

三、打流工具谱系:四个层次与各自的回答能力

What — infiniband-diags 自己划出的分层,以及它给出的分层依据

本篇不自己发明分层,而是采用 infiniband-diags man page 自己写下的依据,这段依据非常精确:

"The base utilities use directed route MAD's to perform their operations.
 They may therefore work even in unconfigured subnets.
 Other, higher level utilities, require LID routed MAD's and to some extent SA/SM access."

这段话把工具分成两类:基础工具走 directed route MAD,因此在未配置的子网里也能工作;高层工具需要 LID 路由 MAD,并且某种程度上需要 SA / SM 访问。把这条依据与 man page 的 Utilities list 对齐,本系列会反复用到的工具就可以按"读的是什么"归成四层。

层次代表工具读的是什么对子网状态的要求回答的问题
① 被动扫描层 ibnetdiscover
ibdiagnet
ibstat
拓扑结构与端口静态参数(GUID / LID / 宽度 / 速率 / 状态) 最低——ibnetdiscover 走 directed route "这片子网长什么样、每个口处于什么状态"
② 连通性层 ibping
ibtracert
ibidsverify
路径与可达性(逐跳响应、路径表) 中——ibtracert 需 SA/SM 访问 "这个 LID / GID 通不通、走的哪条路"
③ 传输层主动打流层 perftest 工具族
ib_write_bw / ib_read_lat …
QP 的执行结果(CQE 回报的耗时与完成情况) 高——必须已分配 LID、端口已 Active "这条 QP 能不能跑、快不快、稳不稳"
④ 计数器层 perfquery(含 -x)
/sys/class/infiniband/…/counters/
ibqueryerrors
PMA 维护的端口计数器(链路与端口两个视角的累计量) 高——需要 PerfMgt GMP 与 PMA 响应 "这条链路实际传了多少字节、错了多少、卡在哪"

这张表最重要的一列是最后一列。它把一个常见误解拆开了:"打流"这个动作只发生在第 ③ 层,但得出结论必须落到第 ④ 层。第 ③ 层给你的是应用视角的吞吐与延迟,第 ④ 层给你的是链路视角的字节数与错误计数,两个视角的口径不同,它们的差值本身就是信息。第九节会把这件事讲透。

3.1 每一层的输出该被怎么用

Why — 为什么"层次"比"命令"更值得记

命令名会随版本变化,会因发行版打包差异而缺失,也会有同名不同实现的情况。但"这条命令读的是链路、还是队列、还是 PMA 计数器"这件事是稳定的。掌握层次之后,遇到一条没见过的命令,只需要问一个问题就能定位它:它的数据来源是拓扑遍历、是路径响应、还是 CQE 与 PMA 计数器?

  1. 数据来自拓扑遍历 → 它是第 ① 层。它的输出是"事实",不是"性能"。看到它报告端口不在 LinkUp 或不在 Active,应当直接去处理物理与逻辑状态,而不是继续往下查工具
  2. 数据来自路径响应 → 它是第 ② 层。它的输出是"可达",不是"快慢"。NVIDIA UFM 文档对 ibping 的描述是验证连通性,退出时显示类 IP ping 输出——本系列第七篇已经明确 ibping 显示的时间不可用于延迟测量,本篇不再重复论证
  3. 数据来自 CQE → 它是第 ③ 层。它的输出是"应用视角的耗时与吞吐",是本篇主体,但它的成立前提是第 ① 层已经确认链路正常
  4. 数据来自 PMA 计数器 → 它是第 ④ 层。它的输出是"链路视角的累计量",它的独特价值在于它测的是那些 CQE 看不见的东西——错误、丢弃、发送等待、缓冲溢出

把四层串成一条排障链,得到的顺序是:第 ① 层确认每个端口该有的状态都有 → 第 ② 层确认路径可达 → 第 ③ 层确认传输层能跑且跑得快 → 第 ④ 层确认跑得快的过程中链路是干净的。最后这一步是前三层都给不了的:一个 QP 可以跑出很高的带宽,同时链路上 symbol_error 一直在缓慢增长——这类劣化只有在第 ④ 层才看得见。

     四层排障链:每层回答的问题与它的输出性质

  ┌────────────────────────────────────────────────────────────────┐
  │  ① 被动扫描层    ibnetdiscover / ibdiagnet / ibstat          │
  │     读:拓扑与端口静态参数                                     │
  │     答:「这个口该有的状态有没有」   →  输出是【事实】         │
  │     前提:最低(directed route MAD,未配置子网也能跑)          │
  └───────────────────────────┬────────────────────────────────────┘
                              │ 全部正常才往下
                              ▼
  ┌────────────────────────────────────────────────────────────────┐
  │  ② 连通性层      ibping / ibtracert / ibidsverify              │
  │     读:逐跳响应与路径表                                       │
  │     答:「这个对端通不通、走的哪条路」 →  输出是【可达】       │
  │     ⚠ ibping 的时间不是延迟测量值(UFM 文档:验证连通性)      │
  └───────────────────────────┬────────────────────────────────────┘
                              │ 可达才往下
                              ▼
  ┌────────────────────────────────────────────────────────────────┐
  │  ③ 传输层打流层  perftest: ib_write_bw / ib_read_lat / …       │
  │     读:CQE 回报的耗时与完成情况                               │
  │     答:「这条 QP 能不能跑、快不快」   →  输出是【应用吞吐】   │
  │     ⚠ 官方声明:不模拟真实业务流量,仅用于纵向对比              │
  └───────────────────────────┬────────────────────────────────────┘
                              │ 跑完必须往下
                              ▼
  ┌────────────────────────────────────────────────────────────────┐
  │  ④ 计数器层      perfquery -x / sysfs counters/ / ibqueryerrors│
  │     读:PMA 维护的端口计数器                                   │
  │     答:「跑的过程中链路干不干净」  →  输出是【链路事实】       │
  │     ★ 只有这一层能发现:错误、丢弃、发送等待、缓冲溢出          │
  └────────────────────────────────────────────────────────────────┘

     ③ 与 ④ 的口径差异是本篇的核心:
       ③ 说的是「应用交付了多少」
       ④ 说的是「链路搬运了多少」
       两者相等 → 传输层没有额外开销
       ③ 明显小于 ④ → 有重传、有丢弃、有排队(去查错误计数器)
       ③ 明显大于链路速率 → 测的是聚合路径或对端处理受限
分层视角 — 为什么"打流"这个词天然掩盖了一个方法论问题

把上面四层摆开会发现,"打流"这个词本身是有害的。它把一个四层的、要求严格分层的流程压缩成了一个动作名词。实际对话里几乎总能听到"我打个流看一下"这句话,而这句话背后至少省略了六个必须先确定的前提:选哪个工具(哪一层)、哪一端发哪一端收、用什么连接类型、消息多大、跑多久、两端 CPU 与 NUMA 拓扑是否一致。这六个前提每一个都会改变结论,所以"打个流看一下"这个说法在技术上是不完整的。

更值得注意的是层的顺序不可交换。第 ① 层的输出是事实——端口的物理状态与逻辑状态不是打流的结果,而是打流的前提。如果一个端口的 phys_state 还没到 LinkUp,或者逻辑状态还没到 Active,那么无论第 ③ 层跑出什么数字都不具备解释价值:它测的既不是链路能力也不是应用能力,而是一个不完整子网上的残缺行为。本系列第八篇第十一节讲端口激活时给出的六阶段,就是这条前提的完整推导。

另一个方向上的不可交换性在第 ④ 层与第 ③ 层之间。常见做法是打到带宽满意就收工,但这在方法论上是不完整的:perftest 的 CQE 只告诉程序"这个 WR 完成了",它不告诉程序这条链路上此刻正在发生什么。信用不足导致的发送等待、包被丢弃、输入缓冲溢出、链路训练重试——这些都不会让 perftest 报错,它们只会让带宽稍微低一点,而这"稍微低一点"很容易被归因为"环境波动"。第 ④ 层的存在,就是为了让"低一点"这件事变得可解释。

把这两条不可交换性合起来,本篇的分层就落到了一个很实用的判断上:每一条打流命令执行完,正确的动作不是"看那条带宽数字",而是"按顺序确认三件事"——第 ① 层:这次跑之前端口状态是正常的吗;第 ③ 层:数字与上次同条件下的数字比,是高了还是低了;第 ④ 层:跑完之后错误计数器有没有动。三件事都过,这次打流才产出了结论;只过第一件,它产出的只是一个数字。

本节关键记忆:4 个层次 + 1 条分层依据 + 1 个不完整做法

  • 4 个层次:① 被动扫描(拓扑与端口静态参数)/ ② 连通性(路径与可达性)/ ③ 传输层打流(CQE 耗时与吞吐)/ ④ 计数器(链路字节数与错误)
  • 1 条分层依据:infiniband-diags man page 原文——基础工具用 directed route MAD,未配置子网也能工作;高层工具需要 LID 路由 MAD 与 SA/SM 访问
  • 1 个顺序:① 状态是事实(前提)→ ② 可达不是快慢 → ③ 应用吞吐 → ④ 链路是否干净。① 与 ③ 之间不可交换
  • 1 个不完整做法:"打到带宽满意就收工" 不成立,因为 perftest 的 CQE 不报告链路此刻正在发生什么;错误与丢弃只会表现为"带宽低一点",容易被归因为环境波动
  • 1 组三问:跑之前端口状态正常吗 / 与同条件基线比是高是低 / 跑完错误计数器动了吗。三问全过才产出结论

四、怎么打流:从 prerequisites 到第一条命令

What — README 自己写明的运行模型,两行而已,但误解率极高

perftest 的运行模型简单到近乎一句废话,但正因为简单,实际操作中的误解反而最多。README 第 5 节 Running Tests 的全部内容就是这两行:

Server:		./<test name> <options>
Client:		./<test name> <options> <server IP address>

	o  <server address> is IPv4 or IPv6 address. You can use the IPoIB
           address if IPoIB is configured.
	o  --help lists the available <options>

这四行里藏着五个必须讲清的要点,任何一条被忽略,表现出来的现象都会是"打流起不来"或"结果很奇怪":

  1. 每个测试都需要两端,一个进程不够。这不是集群测试的复杂性,而是最小可用单元就是两个进程。perftest 测的是一条 QP 的往返行为,而 QP 是端到端的对象——没有对端就没有 QP,也就没有可测的东西
  2. 只有 client 一侧带 <server IP address> 参数。server 侧不接收地址参数,因为它只需要监听。实际操作中把地址参数误加到 server 上,是最常见的命令行错误之一
  3. 地址是 IPv4 或 IPv6,可以用 IPoIB 地址——前提是 IPoIB 已配置。如果没有以太网连接,README 在第 2 条 Special feature 里给出了办法:用 -R 让 rdma_cm 库用 IPoIB 接口来建连,此时必须把 IPoIB 接口地址作为 server IP 传进去
  4. ★ 两端选项必须完全相同。README 用了一个带三颗星的强调:"*** IMPORTANT NOTE: The SAME OPTIONS must be passed to both server and client."man page 的 IMPORTANT NOTES 第 1 条给出了更精确的表述:凡是与模式相关的选项,在 server 与 client 上必须相同。这一条是本节最重要的纪律,它是"两边数字对不上"这类问题的第一嫌疑人
  5. 不要背选项,看 --help。README 明确 "--help lists the available options"。本系列第六至十二节把选项逐个讲透,但选项数量随版本增长,权威来源永远是本机 --help

关于"为什么两端必须相同",值得多说一句:perftest 的两端在建立连接后会交换并校验一部分连接属性,其中最关键的是消息尺寸与 MTU。如果两端命令行不一致,轻则两侧按不同的消息尺寸工作而得到完全无法对照的结果,重则属性校验不通过、连接直接被拒绝。README 把这条放在三颗星强调里,说明它在实践中出问题频率很高。

4.1 跑起来之前必须满足的五个前提

README 在 Running Tests 段开头给了一份 Prerequisites 清单,这份清单讲的是"软件栈要自洽",而不是"网络要通"。两者都必要,但软件栈不自洽的表现更隐蔽:

前提README 的表述不满足时的典型表现
内核版本 kernel 2.6 verbs 库找不到设备节点,命令直接报无法打开设备
内核模块与 libibverbs 匹配 (kernel module) matches libibverbs 加载用户态库时报符号版本不匹配——这是第十二篇讲的软件栈统一问题在这里的暴露方式
内核模块与 librdmacm 匹配 (kernel module) matches librdmacm 只有用 -R(rdma_cm 建连)时才会用到;该库不匹配会在建连阶段失败,症状出现得比 -R 晚得多
内核模块与 libibumad 匹配 (kernel module) matches libibumad ibnetdiscover / ibstat 等 MAD 类工具异常——perftest 本身走 verbs,不直接依赖它,但它决定了你能不能用第 ① 层工具先做前提检查
内核模块与 libmath(lm)、pciutils(lpci) 匹配 (linux kernel module) matches libmath (lm)、(linux kernel module) matches pciutils (lpci) 影响设备枚举与 NUMA 相关查询,症状常表现为设备列表为空或 NUMA 节点识别错误——后者会连带影响本篇第九节的自动绑定

man page 的 IMPORTANT NOTES 又补了四条运行层面的注意事项,这四条是"命令能跑但结果不对"的高频来源:

  • 第 1 条:与模式相关的选项两端必须相同(即上文那颗三颗星)
  • 第 2 条:perftest 以非 root 身份运行时可能需要 sudo。这里有一个容易忽略的后果:用 sudo 跑会改变进程的 NUMA 亲和与 CPU 亲和来源,而本篇第九节会讲 perftest 的自动 NUMA 绑定逻辑——因此"加不加 sudo"可能让同一台机器给出不同的绑定结果,这在做纵向对比时必须固定
  • 第 3 条:perftest 通常安装在 /usr/bin/。这条看似废话,实际决定了该敲 ib_write_bw 还是 ./ib_write_bw——从源码目录直接跑时后者才对
  • 第 4 条:perftest 可能把一些失败信息打到 stderr,这些错误来自 rdma-core。这条在自动化场景里很关键:stderr 非空不等于失败,但完全忽略 stderr 会丢掉真正的错误线索。本篇第五节的脚本模板里会体现这一点

4.2 man page 给的两个官方示例,逐字照录并逐句拆解

man page 的 RUNNING TESTS 段给了两个示例。把它们逐字抄下来是有意的——这两条是唯一有官方出处的完整命令行,也是最值得作为模板的两条:

1- Running bidirectional bandwidth test using Write verb for 5 seconds with
8388608 as a message size and 3 qps:

 Server: ./ib_write_bw -s 8388608 -b -D 5 -q 3
 Client: ./ib_write_bw -s 8388608 -b -D 5 -q 3 1.1.1.2

2- Running latency test using Read verb for 5000 iterations with 32 as a
message size:

 Server: ./ib_read_lat -s 32 -n 5000
 Client: ./ib_read_lat -s 32 -n 5000 192.168.0.1

把这两条拆开,四个要点就自动浮出来了——它们比任何抽象讲解都更直接:

观察点示例 1(带宽)示例 2(延迟)含义
server 侧不带地址 ./ib_write_bw -s 8388608 -b -D 5 -q 3 ./ib_read_lat -s 32 -n 5000 server 只监听,两端选项字符串完全相同
client 侧才带地址 ... -q 3 1.1.1.2 ... -n 5000 192.168.0.1 地址是唯一在两端不同的部分
时长用 -D 还是 -n -D 5(按秒) -n 5000(按迭代次数) 带宽用时长、延迟用次数,这是两种测试的固有差异,本篇第七节展开
哪些选项是"模式专用"的 -b(双向)、-D、-q -n 这些必须两端一致;而 -s 虽然两端数字相同,但它是消息尺寸——不一致会直接改变测试语义

示例 1 里还有一处值得单独指出:-q 3 与 -b 同时出现。-q 是"进程内开几条 QP",-b 是"双向",两者乘在一起意味着 3 条 QP 各跑双向共 6 条流。而 -s 8388608 是 8 MB 报文——这是一条压满线速的命令,不是延迟测试命令。把它当模板去测延迟会得到毫无意义的结果,反之亦然。两个示例分别对应两种测试的两种极端配置,这本身就是最好的教材。

Why — 从零到第一条命令:为什么顺序不能颠倒

把上面所有前提拼起来,会得到一条从"什么都还没查"到"拿到第一个可信数字"的完整路径。这条路径上每一步都是下一步的前提,跳过任何一步,后续步骤产出的数字都不可解释:

       从零到第一条可信的带宽数字:六步与各自的失败模式

  ┌──────────────────────────────────────────────────────────────┐
  │ ①  软件栈自洽                                                 │
  │     检查:两端 libibverbs / librdmacm / libibumad 与内核匹配  │
  │     手段:ibv_devinfo 能列出设备;ibstat -l 能列出 CA         │
  │  ✗ 失败:符号版本不匹配 / 设备列表为空                        │
  │     → 停下,这是软件问题,不是网络问题                        │
  └──────────────────────────┬───────────────────────────────────┘
                             ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ ②  控制面就绪                                                 │
  │     检查:SM 在跑 → 已分配 LID → 端口 state: ACTIVE           │
  │     手段:ibstat(看 LID / SMLID / state / phys_state)        │
  │           本系列第八、九篇的字段判读                          │
  │  ✗ 失败:LID 为 0x0000 → SM 没跑或没扫到本节点               │
  │     ✗ 失败:phys_state: Polling → 链路没起来(先查线缆/光模块)│
  │     → 停下,README IMPORTANT NOTE:没有 LID 无法建 QP 寻址    │
  └──────────────────────────┬───────────────────────────────────┘
                             ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ ③  两端就绪                                                    │
  │     检查:端口 Active + 速率/宽度符合预期 + 两端版本相同       │
  │     手段:两端 ibstat 对比 rate / width;两端 perftest -V     │
  │  ✗ 失败:两端 rate 不一致 → 折算基准不同,结果不可比           │
  │  ✗ 失败:两端版本不同 → README Known Issues 第 3 条          │
  └──────────────────────────┬───────────────────────────────────┘
                             ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ ④  归零                                                        │
  │     检查:错误计数器与非错误计数器清零                         │
  │     手段:perfquery -r(读一次即归零)/ perfquery -R           │
  │           / sysfs counters/ 逐项写 0                          │
  │     ★ 本篇第十三节:流量计数器按测试归零,错误计数器按周期归零  │
  │  ✗ 失败:跳过这步 → 第五步的增量失去意义(不可逆)            │
  └──────────────────────────┬───────────────────────────────────┘
                             ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ ⑤  起两端                                                     │
  │     server:./ib_write_bw <options>                        │
  │     client:./ib_write_bw <options> <server IP>          │
  │     ★ 选项字符串两端逐字相同,只有 client 多一个地址            │
  │     ★ 注意 stderr:perftest 会把 rdma-core 的错误打到 stderr   │
  │  ✗ 失败:连不上 → 回到 ②③;报属性不匹配 → 两端选项不一致      │
  └──────────────────────────┬───────────────────────────────────┘
                             ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ ⑥  读计数器 + 三问                                            │
  │     读:perfquery -x / sysfs counters/                        │
  │     三问:跑之前状态正常吗?与基线比是高是低?错误计数器动了吗?│
  │  ★ 三问全过 → 这是一个结论;只过第一问 → 这只是一个数字       │
  │     本篇第十三节讲换算(×4),第十五节讲完整流程              │
  └──────────────────────────────────────────────────────────────┘

  ★ 顺序不可交换的原因:②③ 是事实,① 是软件前提,
    ④ 必须早于 ⑤,⑥ 依赖 ④ 的归零。
    从 ⑤ 跳到 ⑥ 而跳过 ④,是唯一"无法事后补救"的错误。

上图里最需要强调的是第 ④ 步的不可逆性。① ② ③ 失败可以重来,⑤ 跑错了可以重跑,只有 ④ 一旦跳过,第 ⑥ 步读出来的累计量就永久失去了参照。本篇第十五节会把这一步单列,并给出"流量计数器按测试归零、错误计数器按周期归零"这条分类归零原则——后者是为了保住"这个错误已经涨了多久"这类趋势信息。

起步用的三条命令:从能跑到能信,逐步加复杂度

如果刚接手一条链路,不要一上来就跑 -a。下面三条构成一个从最简单到最完整的阶梯,每一级都只在前一级可信之后再加:

级别命令(两端相同,client 末尾加地址)用它确认什么
第 0 级
连通即可
ib_write_lat -F QP 能不能建立、报文能不能往返。跑不起来就说明第 ② ③ 步还有问题,此时任何带宽数字都没有意义。加 -F 是为了让它不要因为 cpufreq_ondemand 而拒绝运行——注意本篇第九节:-F 只压制警告、不锁频,正式测量前仍需在操作系统层面固定频率策略
第 1 级
单流带宽
ib_write_bw -F -D 10 -m 1024 单条 QP 能不能压满一个端口。-D 用时长而非 -n,本篇第七节解释原因;-m 1024 显式固定 MTU 档位,不要依赖默认的 active_mtu,否则换一台机器默认值就变了
第 2 级
加压到极限
ib_write_bw -F -D 10 -m 1024 -q 4 -t 256 多 QP 能否继续提升带宽。关键判据是:加了 -q 之后带宽是否明显上升——若几乎不变,说明单流已经打满端口,此时继续加 -q 没有意义,瓶颈在链路或对端处理,而不在 QP 数量

三级的顺序不是随意排的,它对应本篇第二节讲的"定位"身份:从 0 到 1 是在确认能不能,从 1 到 2 是在确认到哪一层饱和。而每级跑完都要读一次错误计数器——本篇第十三节会说明为什么高 -q / 高 -t 本身可能触发缓冲溢出类计数器增长,而那种增长是"打得太猛"而不是"链路有故障"。

本节关键记忆:2 行运行模型 + 5 个要点 + 5 条前提 + 6 步路径 + 3 级阶梯

  • 2 行运行模型:Server: ./<test> <options> / Client: ./<test> <options> <server IP>。每个测试都需要两端,最小单元就是两个进程
  • 5 个要点:需要两端 / 只有 client 带地址 / 可用 IPoIB 地址、无以太网用 -R / ★两端选项必须完全相同 / 选项以 --help 为准
  • ★ 最重要的一条:README 三颗星强调 "The SAME OPTIONS must be passed to both server and client"——两端数字对不上时的第一嫌疑人
  • 5 条前提:kernel 2.6 + 模块与 libibverbs / librdmacm / libibumad / libmath / pciutils 匹配。讲的是软件栈自洽,不是网络连通
  • 4 条运行注意:模式选项两端相同 / 非 root 可能需 sudo(会改变 NUMA 亲和来源)/ 通常装在 /usr/bin/ / 失败信息会打到 stderr,来自 rdma-core
  • 6 步路径:软件栈自洽 → 控制面就绪(SM + LID + Active)→ 两端就绪(rate/width/版本)→ 归零 → 起两端 → 读计数器 + 三问
  • 1 个不可逆点:归零是唯一"事后无法补救"的一步。① ② ③ ⑤ 都能重跑,跳过 ④ 则第 ⑥ 步的增量永久失去参照
  • 3 级阶梯:ib_write_lat -F(能不能跑)→ ib_write_bw -F -D 10 -m 1024(能否压满单流)→ + -q 4 -t 256(加压还能不能涨)

五、日常高频命令手册:按场景索引

What — 把前面各节的结论压成一张可以直接照抄的清单

本节不引入新概念,只做一件事:把散落在前四节里的命令按"你在什么场景下需要它"重新组织。本篇第三节给的是分层(按数据来源分),本节给的是索引(按使用场景分)——两者的交集是同一批命令,但检索方式不同。日常排障时你手里拿的是场景而不是层次,所以按场景组织更实用。

全部命令与选项均取自文末「参考资料」列出的 perftest README、man page、infiniband-diags(8)、perfquery(8) 与 NVIDIA UFM 文档,本节不引入任何未经上述来源核实的用法。

5.1 巡检类:每天/每周跑一次,只读不写

# ---- 本机端口状态:最基础的一条,本系列第九篇给了完整字段判读 ----
ibstat                 # 全部设备全部端口:LID / SMLID / state / rate / width / phys_state
ibstat -l              # 列出所有 IB 设备(CA)名
ibstat -p              # 列出各端口的 GUID
ibstat -s              # 短输出
ibstat mlx5_0 2        # 只看 mlx5_0 的 2 号端口

# ---- 脚本实现(ibstatus 与 ibstat 的区别:ibstat 是二进制,输出字段更多)----
ibstatus               # 简版状态

# ---- 全网拓扑 ----
ibnetdiscover          # 遍历 fabric,输出节点/端口/交换机的拓扑摘要
ibnetdiscover -l       # 只列出节点

# ---- 全域体检:前后 diff 是它的正确用法(本篇第十四节)----
ibdiagnet              # 扫描并输出一整套文件(.pm / .slvl / .vl2vl / .sm / .db_csv …)

巡检类的核心判据只有三条,但足以覆盖大多数日常问题:

  • ibstat 里 phys_state 必须是 LinkUp,state 必须是 ACTIVE。前者是物理层,后者是逻辑层,两个都通过才谈得上打流
  • rate 字段要与设计值一致。本篇第二节说过:rate 低于预期是布尔检查能发现、但极容易被忽略的一类故障,因为端口状态仍然是 ACTIVE
  • 机内设备的 SMLID 必须非 0。SMLID 为 0 说明本机对 SM 不可见,这直接对应本系列第八篇讲的"缺少 SMA 的节点对 SM 不可见"——此时本机能连出去,但别人连不进来,打流会在 client 侧被拒

关于 ibdiagnet 的日常用法,本篇第十四节会给完整说明,这里只留一条纪律:它是"做两次 diff"用的,不是"看一次输出"用的。它的默认行为是两次采样求差,所以单看一次输出没有意义。

5.2 连通性类:确认"通",不回答"快"

# ---- ibping:默认是【客户端】,服务端必须加 -S ----
ibping -S                                  # 在 server 侧:进入服务端模式,不返回
ibping -c 5 10< LID>                        # 在 client 侧:发 5 个包,目标是 LID
ibping -c 5 -L                        # 显式声明目标是 LID
ibping -c 5 -G <port_guid>                 # 目标是 Port GUID
ibping -f                                  # flood:back-to-back 发包,不加延迟
ibping -C <ca_name> -P <ca_port>           # 指定本端设备与端口
ibping -e                                  # 显示发送与接收错误(超时等)

# ---- 路径类:走哪条路 ----
ibtracert -s <src_lid> -e <dst_lid>             # 逐跳路径追踪
ibroute /sys/class/infiniband/<dev>/ports/<port>/routes

三条必须记住的判读纪律,其中第 1 条最容易被忽略

  • ibping 默认以客户端模式运行。ibping(8) man page 原文是 "ibping is run as client/server. Default is to run as client.",并且指出 内核里实现了一个默认的 ping 服务器。因此服务端仍然要用 -S 显式进入服务端模式——但如果内核侧的默认 ping server 已经启用,某些版本可能不需要额外操作。本节不推测具体版本行为,实际环境请以本机 ibping -h 与 man page 为准
  • ibping 的时间不是延迟测量值。ibping(8) 说它 "uses vendor mads to validate connectivity between IB nodes. On exit, (IP) ping like output is shown."——它的定位是验证连通性。本篇第一节的八组辨析第 6 条讲的就是这件事
  • 默认端口选择规则值得知道:ibping(8) 写明当未指定设备或端口时,libibumad 库先选第一个 ACTIVE 端口,找不到则选第一个物理 UP 的端口。这条规则解释了为什么在多卡机器上 ibping 有时会"连到了不该连的那张卡"——用 -C 与 -P 显式指定可以避免

5.3 打流类:日常基线与定位

# ========== 延迟基线(本篇第六、七节给参数语义)==========
ib_write_lat -F -m 1024 -s 64 -n 1000          # 小报文延迟,最常用的一条
ib_read_lat  -F -m 1024 -s 64 -n 1000          # 读延迟
ib_send_lat  -F -m 1024 -s 64 -n 1000          # 发送延迟
ib_atomic_lat -F -m 1024 -s 64 -n 1000         # 原子操作延迟

# ========== 带宽基线 ==========
ib_write_bw -F -D 10 -m 1024                  # ★ 用 -D 不用 -n
ib_read_bw  -F -D 10 -m 1024
ib_send_bw  -F -D 10 -m 1024
ib_atomic_bw -F -D 10 -m 1024

# ========== 定位维度:一次只改一个 ==========
ib_write_bw -F -D 10 -m 4096                  # 维度一:大报文
ib_write_bw -F -D 10 -m 1024 -s 1024          # 维度一:固定 MTU 改消息尺寸
ib_write_bw -F -D 10 -m 1024 -q 8              # 维度三:并行 QP(★ 仅带宽类)
ib_write_bw -F -D 10 -m 1024 -t 512            # 维度二:发送深度
ib_write_bw -F -D 10 -m 1024 -l 64            # ★ 突破软件投递速率上限,见 5.6

# ========== 观察与报告形态 ==========
ib_write_lat -F -H                            # 打印全部结果直方图
ib_write_lat -F -U                            # 不排序输出
ib_write_lat -F -C                            # 用 CPU 周期数报告(两端频率不同时更可比)
ib_write_bw  -F --report_gbits                # 用 Gbps 而不是 MiB/sec
ib_write_bw  -F -N                            # 取消 peak-bw 计算
ib_write_bw  -F --run_infinitely              # 一直跑到被中断,每 5 秒打印一次
ib_write_bw  -F --out_json result.json        # JSON 输出,供自动化消费

# ========== 数据正确性(带宽类 + RC + tx_depth>=32 + SIMD)==========
ib_write_bw -F --data_validation -t 64 -D 10 --use_hugepages

# ========== 限速复现(压到业务水位)==========
ib_write_bw -F --rate_limit 100 --rate_limit_type HW --rate_units g
ib_write_bw -F --rate_limit 500000 --rate_limit_type SW --rate_units p

# ========== 双向 / 双端口 / 原子类型 ==========
ib_write_bw -F -D 10 -b                       # 双向带宽
ib_write_bw -F -D 10 -O                       # 双端口(两个端口都必须 Active)
ib_atomic_bw -F -D 10 -A FETCH_AND_ADD        # 原子类型:CMP_AND_SWAP / FETCH_AND_ADD

# ========== 无以太网连接时用 rdma_cm 建连 ==========
# ★ server IP 必须传 IPoIB 接口地址(README Special feature 2)
ib_write_bw -R -F -D 10 <IPoIB_addr>

# ========== 非常驻运行时的行为选项 ==========
ib_write_bw -F -D 10 -e                       # 在 CQ 事件上睡眠(默认是轮询)
ib_write_bw -F -D 10 -Q 16                    # 每 16 个完成才生成一个 CQE

这一段里最值得先记住的是六条"日常默认写法",它们覆盖了绝大多数实际使用场景:

习惯为什么这样写不这样写会怎样
带宽用 -D,不用 -n README 明确带宽测试可以按迭代次数或按固定时长运行,并专门提供 -D 指令 用 -n 跑带宽时,peak-bw 统计上限随迭代数变化,本篇第七节说明为什么这让结果不可比
显式写 -m README 说 -m 的默认值是 active_mtu from ibv_devinfo,也就是"取决于本机协商结果" 换一台机器默认值就变了,纵向对比失效。本篇第四节第 1 级阶梯因此明确要求 -m 1024
延迟用 -n,并且读 t_typical README 说延迟测试报告 minimum, median and maximum,并指出中位数对高延迟波动不如平均值敏感 读 t_max 会把 warmup 效应当成结论。README 明说第一次测到的值通常是最大值,原因是 warmup 效应
定位时不用 -a -a 一次扫 2 到 223 全部尺寸,等于同时改变了报文大小这个变量 得不到"哪个变量起作用"的结论。-a 的正确用途是验收时的尺寸扫描
加 -F 只为让它跑起来 man page 说它 "Do not fail even if cpufreq_ondemand module is loaded, and cpu-freq is not on max" 误以为它锁了频。本篇第九节专门辨析:它只压制警告,测得的数据不会因此变稳
正式测量前在 OS 层固定频率策略 perftest 用 CPU 周期计数器取时间戳,频率漂移直接进入测量结果 两次测量之间的差异可能来自频率而非设备。频率不同时用 -C 以周期数报告

5.4 计数器类:把数字变成证据

# ========== PMA 性能计数器(本篇第十三节讲换算与分类)==========
perfquery -p 1 -d mlx5_0 -i 1               # 读 PortCounters
perfquery -x -p 1 -d mlx5_0 -i 1             # 读扩展计数器(ExtendedCounters)
perfquery -r -p 1 -d mlx5_0 -i 1             # ★ 读一次即完成归零(reset)
perfquery -R -p 1 -d mlx5_0 -i 1             # 按 reset_mask 分类别重置
perfquery -X -p 1 -d mlx5_0 -i 1             # 按 SL 细分的发送计数器
perfquery -S -p 1 -d mlx5_0 -i 1             # 按 SL 细分的接收计数器
# 端口号 255 表示对所有端口执行操作(perfquery(8) man page)

# ========== sysfs 直读(不需要 PerfMgt GMP 往返)==========
cat /sys/class/infiniband/<dev>/ports/<port>/rate
cat /sys/class/infiniband/<dev>/ports/<port>/state
cat /sys/class/infiniband/<dev>/ports/<port>/phys_state

# 逐项清零:把需要归零的计数器写 0
for c in port_xmit_data port_rcv_data port_xmit_wait; do
    echo 0 > /sys/class/infiniband/mlx5_0/ports/1/counters/$c
done

# 只看关键项:目录里是逐项一个文件的布局
grep . /sys/class/infiniband/mlx5_0/ports/1/counters/port_rcv_errors
cat  /sys/class/infiniband/mlx5_0/ports/1/counters/port_xmit_wait

# ========== 错误聚合与 ibdiagnet 阈值告警 ==========
ibqueryerrors
ibdiagnet -P symbol_error=1                  # 达到阈值就打印
ibdiagnet --skip pm                          # ★ 跳过计数器采集(打流前不能跳)

这一段有三个"必须知道"的细节,否则读出来的数字会骗人:

  • port_xmit_data 与 port_rcv_data 的读数要乘以 4 才是字节数。perfquery(8) man page 原文:"components that represent Data (e.g. PortXmitData and PortRcvData) indicate octets divided by 4 rather than just octets";内核 ABI 文档表述一致(divided by 4 (lanes))。把读数当字节数直接用,会得到偏小 4 倍的值,足以把一次正常测试误判为链路劣化。本篇第十三节会给出完整的换算与交叉验证方法
  • perfquery -r 的语义是"读一次即完成归零",而 -R 是按分类重置。本篇第十五节据此给出"流量计数器按测试归零、错误计数器按周期归零"这条原则——后者是为了保住"一直涨了多久"这类趋势信息
  • 扩展计数器有启用条件。本篇第十三节说明:只有 cap_mask2 的 IB_PM_IS_ADDL_PORT_CTRS_EXT_SUPPORTED 位置位时才会解码,所以 perfquery -x 读不到某些字段时,不一定是权限问题

5.5 一条 10 分钟冒烟流程:把上面三类串起来

本小节把 5.1 到 5.4 拼成一条可执行的顺序流程。它的编排原则是本篇第四节那张六步图的浓缩版:只读的检查全部放在前面,任何要写端口状态的动作(归零)放在打流之前。

      10 分钟冒烟:把 5.1~5.4 串成一条可执行流程

  ═══ 阶段 A:只读检查(server 与 client 各做一次,结果必须一致)═══

  A1  软件栈是否自洽
      $ ibstat -l                              # 能列出 CA
      $ ibv_devinfo | head -20                 # 能列出设备
      ✗ 失败 → 软件问题,停止(不要怀疑网络)

  A2  控制面是否就绪
      $ ibstat
        看两行:phys_state 必须是 LinkUp
                state     必须是 ACTIVE
                SMLID     必须非 0
        ✗ LID=0x0000    → SM 没跑或没扫到本机(本系列第八篇)
        ✗ SMLID=0        → 本机对 SM 不可见,别人连不进来
        ✗ Polling       → 链路没起来,先查线缆与光模块

  A3  速率与宽度是否符合设计
      $ cat /sys/class/infiniband/mlx5_0/ports/1/rate
        → 拿这个值做后面带宽结果的折算基准(本篇第十三节)
        ✗ 两端 rate 不一致 → 基准不同,结果不可比

  A4  两端版本是否一致
      $ ib_write_bw -V                         # 两端各执行一次
      ✗ 不一致 → README Known Issues 第 3 条,不可比

  ═══ 阶段 B:归零(★ 写端口状态,必须在打流之前完成)═══

  B1  流量计数器归零(服务本次测量)
      $ perfquery -r -p 1 -d mlx5_0 -i 1
      # 或逐项写 0 到 sysfs counters/
      # ★ 流量计数器按【测试】归零

  B2  错误计数器单独处理(服务趋势观察)
      $ grep . /sys/class/infiniband/mlx5_0/ports/1/counters/port_rcv_errors
        → 记录当前值,【不要清零】
      # ★ 错误计数器按【周期】归零;频繁清零就看不到"涨了多久"

  ═══ 阶段 C:打流(server 与 client 两端,选项逐字相同)═══

  C1  先确认能跑(第 0 级)
      server: $ ib_write_lat -F
      client: $ ib_write_lat -F <server_ip>
      ✗ 跑不通 → 回 A2/A3,不要往下走

  C2  单流带宽(第 1 级)
      server: $ ib_write_bw -F -D 10 -m 1024
      client: $ ib_write_bw -F -D 10 -m 1024 <server_ip>
      → 记录 BW average(不是 BW peak)

  C3  加压(第 2 级,看还能不能涨)
      server: $ ib_write_bw -F -D 10 -m 1024 -q 4 -t 256
      client: $ ib_write_bw -F -D 10 -m 1024 -q 4 -t 256 <server_ip>
      → 与 C2 对比:几乎不变 = 单流已打满端口

  ═══ 阶段 D:读计数器 + 三问(结论在这里产生)═══

  D1  字节数交叉验证
      $ cat /sys/class/infiniband/mlx5_0/ports/1/counters/port_xmit_data
        ★ 读数 × 4 才是字节数;用它 × 8 与 C2 的结果对照

  D2  发送等待
      $ cat /sys/class/infiniband/mlx5_0/ports/1/counters/port_xmit_wait
        ≈0 且带宽低       → 问题在发送侧投递或接收侧处理
        显著非零且带宽低   → 查交换机缓冲 / VL 仲裁 / 信用环

  D3  错误类是否动了
      $ grep . /sys/class/infiniband/mlx5_0/ports/1/counters/port_rcv_errors
      ★ 先排除"打得太猛"(-q/-t 过高),再判定链路问题

  D4  ★ 三问
      1. 跑之前端口状态正常吗?        (阶段 A)
      2. 与同条件基线比,是高了还是低了?(纵向对比,不是绝对值)
      3. 跑完之后错误计数器动了吗?     (阶段 D)
      → 三问全过 = 结论;只过第 1 问 = 只是一个数字

  ★ 全流程唯一纪律:C 阶段一次只改一个变量。
    同时改 -m 与 -q 得到的高带宽,无法归因。

5.6 五条最容易被当成"工具问题"的参数误解

本节列出的五条,是日常使用中出现频率最高、且"看起来像工具坏了"的参数行为。它们的详细语义分别在后面的章节里,这里只做速查:

现象真实原因正确做法详见
延迟测试加 -q 8 直接失败,退出码非 0 perftest 源码对非带宽测试且 num_of_qps > 1 的情况打印 " Multiple QPs only available on bw tests" 并返回 FAILURE 并发延迟用多进程实现,不是单进程多 QP 第七节
加 --use-srq 带宽没变化 man page 对 --use-srq 的说明以 "Relevant only for Send" 结尾 只在 Send 类(ib_send_*)上用;RDMA Write 接收侧不预投递资源,SRQ 无从生效 第八节
-m 3000 结果异常或被拒绝 范围 256 - 4096 是范围,但 IB 的 MTU 只有 256 / 512 / 1024 / 2048 / 4096 五个离散档位 只用这五个值 第十二节
小消息带宽上不去,加 -l 64 后消息速率跃升 README 说 单次软件投递约 500 ns,因此纯软件投递速率约 10 Mpps 量级 这说明瓶颈原来在软件投递侧;-l 让每次投递让硬件执行多条消息,测到的才是硬件真实速率 第七节
--data_validation 报参数冲突 README 给了明确的互斥列表:-a、--post_list > 1、--run_infinitely、--mr_per_qp、GPU 相关、--payload_file_path,以及非 RC 连接 "要校验还是要极限性能"必须显式选一边;校验还需要 RC + tx_depth ≥ 32 + SIMD 支持 第十一节

本节关键记忆:4 类命令 + 1 条流程 + 6 条默认写法 + 3 个换算陷阱 + 5 条误解速查

  • 4 类命令:巡检(ibstat / ibnetdiscover / ibdiagnet,只读)/ 连通性(ibping / ibtracert,答"通不通")/ 打流(perftest,答"快不快")/ 计数器(perfquery 与 sysfs,答"链路干不干净")
  • 6 条默认写法:带宽用 -D / 显式写 -m / 延迟读 t_typical / 定位不用 -a / -F 只压制警告 / 正式测量前 OS 层固定频率
  • 3 个换算陷阱:port_xmit_data 与 port_rcv_data 读数 × 4 才是字节;-r 读一次即归零、-R 按分类重置;扩展计数器需要 cap_mask2 位置位才解码
  • 1 条冒烟流程:只读检查(A)→ 归零(B)→ 三级打流(C)→ 读计数器 + 三问(D)。写端口状态的动作全在打流之前
  • 1 条纪律:C 阶段一次只改一个变量
  • 2 个易错默认值:-m 默认取 ibv_devinfo 的 active_mtu(随机器而变,必须显式指定);ibping 默认是客户端模式,服务端要 -S

六、perftest 的测量原理:它到底在测什么

What — 前面讲的所有参数,最终都服务于三个测量动作

前面五节讲的是"什么时候用、用哪个参数、结果怎么读"。本节补上更底下一层:这些工具究竟在执行什么动作,数字是怎么产生的。理解这一层的好处很直接——很多参数从"背不下来"变成"推得出来",而推导出的默认值往往是不可改的。

perftest 的全部测量可以还原为三个动作:

  1. 取时间戳——它怎么知道一条操作花了多久
  2. 构造流量——它怎么让链路进入需要测量的状态
  3. 选择读数口径——它怎么把一堆样本变成一个可以汇报的数字

README 的 Notes on Testing Methodology 一节把三者都写明了,下面逐条拆解。

6.1 时间从哪来:CPU 周期计数器,以及它为什么让 -C 变得可靠

README 的第一句方法学说明就是:"The benchmarks use the CPU cycle counter to get time stamps without context switch."

这句话有两个细节值得注意。第一个是"without context switch"——它强调取时间戳这个动作本身不引入调度抖动,这与直接读时钟相比是一个优势。第二个更关键:perftest 用的是周期计数器,而周期计数器的读数是"周期数",换算成时间需要除以当前频率。

于是"CPU 频率是否稳定"这个问题,就从环境配置问题变成了测量方法问题。本篇第九节讲 -F 时说过:man page 明确它"Do not fail even if cpufreq_ondemand module is loaded, and cpu-freq is not on max",它只压制警告、不锁频。那么频率漂移怎么处理?README 给的答案是 -C,man page 对它的说明是 Report times in CPU cycle units——直接报告周期数,不换算成时间。

Why — 为什么 -C 在频率不一致时反而更可信

把两种报告方式并排看,差别就清楚了。设某条操作实际消耗 N 个 CPU 周期,在两次测量中 CPU 频率分别是 f1 与 f2:

  时间报告(默认)              周期报告(-C)
  ─────────────────────         ─────────────────────
  结果 = N / f                   结果 = N
  f = f1  →  N/f1
  f = f2  →  N/f2

  若 f1 != f2,两次结果差异里                若 N 相同,-C 报出的数字
  混入了频率变化的贡献                        完全相同
       ↓                                        ↓
  ★ 频率漂移被误读成性能变化                ★ 差异只能来自真实行为
    (这正是 -F 压不住的部分)                   (设备、拓扑、配置)

所以 -C 的真正用途不是"看得更准",而是"把不可控的共同因素消掉"。本篇第七节把 -C 列为延迟类专用选项,man page 对它的限定是 "Relevant only for latency",原因也就清楚了:只有延迟测试是逐次计时的,带宽测试是聚合吞吐,套不上"周期"这个口径。

一个务实的用法:如果你的两台机器 CPU 型号或频率策略不同,把 -C 报出的周期数作为归档基线的主口径,同时保留微秒数作为可读参考。周期数是跨机器可比的口径,微秒数不是。

6.2 延迟怎么算:为什么报的是"往返的一半"

README 的第二句方法学说明给出了一个必须记住的换算:"The latency benchmarks measure round-trip time but report half of that as one-way latency. This means that the results may not be accurate for asymmetrical configurations."

这个换算有三个层次:

  • 它测的是往返时间 RTT,报出来的是 RTT / 2,标注为单向延迟。因此输出的单位是微秒,但语义是"单向"
  • 为什么除以 2:perftest 的延迟测试是严格往返的——发一条,等 CQE 回来,再发下一条。没有流水化。在对称链路上把 RTT 平分给两个方向是合理近似
  • 但 README 紧接着给了警告:非对称配置下这个结果不准确。非对称意味着两个方向的时延不同,而 RTT / 2 这个算术平均无法反映这种不对称。最典型的非对称场景就是双向打流(-b)——但那属于带宽测试,不涉及延迟口径

把这条和"延迟测试默认 -t 为 1"这件事放在一起,就得到了延迟测试的完整形态:一条 QP、深度为 1、逐次往返、每次取时间戳。它测的是"单次操作在无并发条件下的往返时间",这也正是它被称为"延迟"而不是"吞吐"的原因——本篇第七节会说明 -t 的默认值为什么带宽类是 128 而其余是 1。

还有一条容易被忽略的方法学说明,它直接解释了日常读数时该看哪一列:

- Latency tests report minimum, median and maximum latency results.
  The median latency is typically less sensitive to high latency
  variations, compared to average latency measurement.
  Typically, the first value measured is the maximum value,
  due to warmup effects.

这四行里有两个直接可用的结论:

  • 中位数比平均值更抗高延迟波动。README 明确 "The median latency is typically less sensitive to high latency variations, compared to average latency measurement."——这就是 t_typical 被本系列第七篇定为基线列的原因
  • 第一次测到的值通常是最大值,原因是 warmup 效应。这句话解释了为什么延迟测试的迭代次数不能太小——README 接着说 "Long sampling periods have very limited impact on measurement accuracy. The default value of 1000 iterations is pretty good.",所以日常用默认的 1000 次即可,不要为了"更快"而砍到几十次

但 README 紧接着给了一个关于内存的警告,它是"不要把迭代次数调到很大"的原因:"Note that the program keeps data structures with memory footprint proportional to the number of iterations. Setting a very high number of iteration may have negative impact on the measured performance which are not related to the devices under test. If a high number of iterations is strictly necessary, it is recommended to use the -N flag (No Peak)."

这条警告的机制值得想清楚:迭代次数过多会引入"与被测设备无关的性能下降"——因为数据结构占用内存随迭代数线性增长,内存占用上升会改变缓存行为与 TLB 压力,从而拖慢测量本身。换句话说,测得的数据被测量过程本身污染了。此时应该用 -N 取消 peak-bw 计算——因为 peak 统计本身需要保存全部样本,正是内存增长的来源。本篇第七节会把 -N 的默认值细节讲清楚(README 侧的默认是带 peak,-N 取消它)。

6.3 带宽怎么算:谁在测,以及双向时如何合并

README 的第三句方法学说明回答了"谁在测"这个问题:"On all unidirectional bandwidth benchmarks, the client measures the bandwidth. On bidirectional bandwidth benchmarks, each side measures the bandwidth of the traffic it initiates, and at the end of the measurement period, the server reports the result to the client, who combines them together."

这句话有三个工程含义:

含义细节实践影响
单向带宽由 client 测量 发流的是 client,所以 client 知道自己的投递速率与完成速率 server 侧的输出不作为带宽结论。归档时以 client 输出为准
双向时两边各自测自己发起的流量 -b 下两个方向独立统计 双向结果里包含两个方向的合计值,因此它可以超过单端口的线速——本篇第三节说过,"③ 明显大于链路速率"往往意味着测的是聚合路径,看到双向数字超过线速时不要判为异常
测量结束时 server 把结果报告给 client,由 client 合并 server 的数字在测量期间并不在 server 终端上,它被送到 client 侧合并 因此双向测试必须等 client 输出完整结果才有意义;只看 server 侧输出会漏掉合并后的最终值

把 6.2 与 6.3 合起来,perftest 的两个测试家族的形态差异就非常清楚了:

        延迟测试与带宽测试:两种测量形态的完整对照

  ┌────────────────────────────┬────────────────────────────────┐
  │      延迟测试(*_lat)      │      带宽测试(*_bw)          │
  ├────────────────────────────┼────────────────────────────────┤
  │ 度量对象:单次操作耗时      │ 度量对象:稳态吞吐            │
  │ 计时方式:CPU 周期计数器    │ 计时方式:总字节 / 总时间      │
  │ 报文节奏:发一条等一条      │ 报文节奏:持续流水            │
  │ -t 默认值:1               │ -t 默认值:128                │
  │ -n 默认值:1000            │ -D 按时长;-n 按次数          │
  │ 报告列:min / median / max │ 报告列:BW average / BW peak   │
  │ 基线列:t_typical(中位数) │ 基线列:BW average            │
  │ 报告单位:usec(= RTT/2)  │ 报告单位:MiB/sec 或 Gbit/sec │
  │ 测者:发起方(两端对称)    │ 单向:client 测              │
  │                            │ 双向:各自测 + client 合并    │
  │ -C 报告周期数:可用        │ -C 报告周期数:不可用(无口径)│
  └────────────────────────────┴────────────────────────────────┘
       ★ 两者不是"同一件事的两种精度",而是两种不同的测量:
         延迟问「一条消息要多久」,带宽问「链路能同时搬多少」

  为什么 -t 默认值差这么多:
  ──────────────────────────────────────────────────────────
  延迟测的是【单次】往返,深度 >1 就变成流水,
    测到的就不是单次延迟而是「流水线出流速率」的倒数了。
  带宽测的是【满载】吞吐,深度 1 根本无法填满链路,
    64 条在途请求才可能让链路满负荷运转。

6.4 post_list 的原理:为什么它能突破软件速率上限

本篇第五节把 -l 64 列进了日常命令,第七节会讲它的参数边界。本小节讲原理,因为它解释了 perftest 里最反直觉的一个现象:加一个"批量大小"参数,消息速率能跳一个数量级。

README Special feature 第 1 条把机制讲得很完整:

1. Usage of post_list feature (-l, --post_list=<list size> and
   --recv_post_list=<list size>)
   In this case, each QP will prepare <list size> WQEs (instead of 1),
   and will chain them to each other.
   In chaining we mean allocating <list_size> array, and setting 'next'
   pointer of each WQE in the array to point to the following element in
   the array. the last WQE in the array will point to NULL.
   In this case, when posting the first WQE in the list, will instruct
   the HW to post all of those WQEs.
   Which means each post send/recv will post <list_size> messages.

   This feature is good if we want to know the maximum message rate of
   QPs in a single process.
   Since we are limited to SW posts (for example, on post_send ~ 10 Mpps,
   since we have ~ 500 ns between each SW post_send), we can see the
   true HW message rate when setting <list_size> of 64 (for example)
   since it's not depended on SW limitations.

这段话里有两个层次的机制,拆开看就完全清楚了:

  1. 软件侧(当前是瓶颈):单次 post_send 约 500 ns,因此软件投递速率上限约 10 Mpps。这个上限与网卡能力无关——无论 HCA 有多强,只要每条消息都要软件投递一次,就撞在这个 10 Mpps 的墙上
  2. 硬件侧(真实能力):把 list_size 设为 64 之后,每次软件投递让硬件执行 64 条消息,于是测到的消息速率不再受软件速率限制,这才是硬件真实消息速率

实现方式是 WQE 链表:perftest 分配一个长度为 list_size 的数组,把每个 WQE 的 next 指针指向数组里的下一个元素,最后一个指向 NULL。投递第一个 WQE,就等于告诉硬件"按这个链把整条链都发出去"。

原理视角 — 为什么"软件投递速率"会成为带宽测试的隐形天花板

把 post_list 的现象放回传输层看,会发现它不是一个工具技巧,而是传输层工作队列语义的一个直接后果。本系列第七篇讲过 WQE 与 CQE 的流转:一次 WQE 投递需要软件写入队列描述符并敲响 doorbell,这个过程是软件逐条执行的。因此"每条消息的成本"里天然包含一份与硬件能力无关的固定软件开销,README 把它量化为约 500 ns。

这个固定开销决定了测量的分辨率下限。如果链路能以 10 Mpps 的速率处理消息,而软件只能投递 10 Mpps,那么测量结果里硬件的真实能力与软件投递能力被完全混在一起,无法区分。此时测出的 10 Mpps 究竟是"网卡的上限"还是"CPU 的上限",答案是无法判断——而这恰恰是打流定位最需要回答的问题。

post_list 的作用是一次性把这个混淆拆开:它不改变单条消息的硬件处理成本,只改变"一次软件投递对应多少条硬件操作"。当 list_size = 64 时,软件侧每 500 ns 投递一次、每次 64 条,软件速率折算到 10 Mpps / 64 ≈ 0.156 Mpps 的投递频次,这个频次远低于任何硬件的处理能力,于是软件不再是瓶颈。此时测出的消息速率,其瓶颈必然在硬件侧。

因此 -l 提供的是一个判据而不只是一个参数:加与不加 -l 的两次测量之差,直接指出了瓶颈所在的层。差值大 = 原来受软件投递限制;差值小 = 已经打满硬件,与软件无关。本篇第七节把这个判据进一步展开,第十五节把它放进"分层加压"的维度四。

顺带一个容易被忽略的推论:--recv_post_list 与 -l 是对称的两个方向。前者一次性投递多条接收 WQE,直接对应本篇第八节的 RNR NAK 问题——接收侧预投递不足就会收到 RNR NAK,而批量投递接收 WQE 是减少 RNR 的最直接手段。本篇第八节给出的二分判读法("加 --recv_post_list 后减少 = 投递开销问题")正是基于这个机制。

6.5 完成通知的两个开关:-Q 与 -e

本篇第五节的命令清单里有 -Q 与 -e 两个选项,它们都作用于"什么时候、以什么方式知道操作完成了",因此放在一起讲。

选项README / man page 的语义改变了什么什么时候用
-e, --events README:"Sleep on CQ events (default poll)" 默认是轮询完成队列;-e 改为在完成事件上阻塞等待 轮询会持续占用 CPU 并引入忙等;当测试机需要与其他业务共享 CPU、或多进程并行打流时,用 -e 减少对同机其他负载的干扰。但它可能影响延迟读数——阻塞唤醒比忙等慢
-Q, --cq-mod README:"Generate Cqe only after <cq-mod> completion" 完成元素的生成频率:累积 N 个完成才生成一个 CQE,而不是每个操作完成都生成 降低 CQ 处理频度,在高消息速率测试中减少软件侧开销。它与 -l 的区别值得注意:-l 减少的是投递次数,-Q 减少的是完成通知次数

把这两者与 6.4 放在一起,可以看到 perftest 对"软件开销"这件事的系统性处理:

  • -l —— 减少投递侧的软件调用次数(一次投递多条)
  • --recv_post_list —— 同样减少接收投递次数,并降低 RNR NAK 概率
  • -Q —— 减少完成通知次数
  • -e —— 改变等待方式,从忙等变成阻塞

这四个选项指向同一个目的:把软件从瓶颈位置上挪开。而本篇第五节的命令清单里没有把它们放进日常默认写法,原因也在这里——它们改变的是测量条件本身,用它们测出来的数字与不用它们测出来的数字不是同一个口径,不能混在同一张基线表里。凡是改变了软件开销的选项,基线必须重新建立。

本节关键记忆:3 个动作 + 1 个 500 ns 天花板 + 2 组公式 + 1 张形态对照表

  • 3 个测量动作:取时间戳(CPU 周期计数器)/ 构造流量(决定报文节奏与深度)/ 选择读数口径(决定汇报哪一列)。所有参数都服务于这三个动作
  • 1 个时间戳事实:用 CPU 周期计数器取时间戳、不经上下文切换。因此 频率漂移会直接进入时间读数,-C 报周期数可消掉这一项
  • 1 个延迟换算:测的是 RTT,报的是 RTT / 2,标注为单向。README 明确非对称配置下不准确
  • 2 个读数纪律:中位数比平均值更抗波动(所以基线列是 t_typical);第一次测到的值通常是最大值,原因是 warmup 效应(所以迭代次数不能砍到几十次)
  • 1 个内存警告:数据结构占用随迭代数线性增长;迭代数过大会引入"与被测设备无关的性能下降"。必须加大迭代数时用 -N 取消 peak 统计
  • 3 条带宽归属:单向由 client 测 / 双向各自测自己发起的 / 结束时 server 报告给 client 合并。所以双向数字超过线速不是异常,且必须看 client 侧输出
  • 1 个软件天花板:单次 post_send 约 500 ns,纯软件投递上限约 10 Mpps。-l 64 让每次投递执行 64 条消息,测到的才是硬件真实速率
  • 1 个 WQE 链机制:分配 list_size 数组、next 指针串成链、末元素指向 NULL;投递第一个即让硬件发完整条链
  • 1 个判据:加 -l 前后差值大 = 原来受软件限制;差值小 = 已打满硬件
  • 4 个挪开软件的选项:-l(投递次数)/ --recv_post_list(接收投递 + 降 RNR)/ -Q(完成通知次数)/ -e(等待方式)
  • 1 条纪律:这四个选项改变了测量条件本身,用它们测出的数字与不用它们测的不是同一口径——基线必须重新建立,不能混在同一张表里

七、发送侧参数:把"打多少"和"怎么打"分开

What — 时长类与深度类两组参数,以及它们各自的默认值差异

perftest 的选项可以按"它控制的是测试多大规模"与"它控制的是队列多深"分成两组。这个划分不是本篇发明,而是 man page 自己的分类:README 与 man page 都把 Options for BW tests 单独列出,而 Options for latency tests 只有三项。

先看时长类,因为它们的默认值本身就是一组结论。man page 对三个选项的说明分别是:-D, --duration 是"Run test for a customized period of seconds";-n, --iters 是"Number of exchanges (at least 5, default for write 5000 else 1000)";-N, --noPeak 是"Cancel peak-bw calculation (default with peak up to iters=20000)"。

这三条默认值里藏着两个必须知道的细节:

  • 延迟与带宽的默认交换次数不同:write 类默认 5000,其余默认 1000。这个差异不是随意定的——延迟测试要的是统计量稳定,而 README 专门说明了这一点:"Long sampling periods have very limited impact on measurement accuracy. The default value of 1000 iterations is pretty good.",同时给出了一个反向约束:程序保持的数据结构内存占用与迭代次数成正比,设一个非常高的迭代次数可能对测得的性能产生负面影响,而且这些负面影响与被测设备无关
  • 峰值带宽计算有次数上限:-N 的说明写明"default with peak up to iters=20000"。这意味着超过 20000 次迭代时峰值统计本身可能被取消,而 README 建议此时"如果确实需要很高的迭代次数,推荐使用 -N 标志(No Peak)"

这里有一条容易被忽略的耦合关系:迭代次数与 -N 不是两个独立选择。README 的逻辑是"高迭代次数 + 峰值统计"这个组合本身有问题,因此给出的建议是用 -N 关掉峰值统计。也就是说,看到一条命令带了很大的 -n,就应该预期它的输出里可能没有峰值列——而峰值列的缺失会影响历史数据的可对比性,因为本系列第七篇已经确认基线应当避开 BW peak 与 t_max,但"这一列不存在"与"这一列不可信"是两种不同的情况,需要在记录基线时区分清楚。

再看深度类。man page 给出的默认值是:-t, --tx-depth 为"Size of tx queue (default 128 for bw else 1)",-r, --rx-depth 为"Rx queue size (default 512)",-l, --post_list 为"Post list of send WQEs of size (instead of single post)",另外还提供一个 --recv_post_list 做接收侧的对应动作。

tx-depth 的默认值差异是本系列第七篇已经指出过的,这里补上 man page 层面的完整依据,并说明它为什么是打流时最该先动的参数。带宽测试默认 128、延迟测试默认 1——这两个默认值本身就是"带宽要靠队列深度堆出并发、延迟要靠队列深度保持单条在途"这一取舍的编码。latency 与 bw 共用同一套选项名却给出不同默认值,正是因为两种目标的物理需求相反。

而 post_list 的机制,README 的 Special feature 段讲得最清楚,这是本系列前面各篇都没有覆盖过的一块:

"In this case, each QP will prepare <list size> WQEs (instead of 1), and will chain
 them to each other. In chaining we mean allocating <list_size> array, and setting
 'next' pointer of each WQE in the array to point to the following element in the
 array. the last WQE in the array will point to NULL.
 In this case, when posting the first WQE in the list, will instruct the HW to post
 all of those WQEs. Which means each post send/recv will post <list_size> messages."

这段描述里有一个非常关键的性能论断,它解释了 post_list 存在的根本理由:

"This feature is good if we want to know the maximum message rate of QPs in a
 single process. Since we are limited to SW posts (for example, on post_send
 ~ 10 Mpps, since we have ~ 500 ns between each SW post_send), we can see the
 true HW message rate when setting <list_size> of 64 (for example) since it's not
 depended on SW limitations."

把这段拆开:单次软件投递的间隔约 500 纳秒,因此纯软件投递速率的上限约在 10 Mpps 量级;而把 list_size 设成 64 之后,每次软件投递会让硬件执行 64 条消息,此时测到的才是硬件真实的消息速率,因为它不再受软件投递速率限制。这是一个非常实用的判据:当消息速率上不去时,先判断瓶颈在软件投递还是在硬件——方法就是加 -l,如果速率明显上升,说明原来卡在软件侧。

7.1 发送侧参数总表:按"它改变什么"归类

参数man page / README 的语义默认值适用限定它回答什么问题
-D --duration Run test for a customized period of seconds 无(不指定则按 -n) 仅带宽类 按时间而非次数收敛,结果比按次数稳定
-f --margin measure results within margins (default=2sec) 2 秒 仅在 -D 模式下有意义 测量窗口前后各留多少不计入的边距,用于排除起停瞬态
-n --iters Number of exchanges (at least 5, default for write 5000 else 1000) write 5000
其余 1000
全部 交换次数。有下限 5,也有数据结构的内存代价
-N --noPeak Cancel peak-bw calculation (default with peak up to iters=20000) 关闭 仅带宽类 是否统计峰值带宽;高迭代次数时建议显式关闭
-t --tx-depth Size of tx queue bw 128
其余 1
仅带宽类与 raw_ethernet_burst_lat 单 QP 在途消息数上限;小消息场景提升吞吐最有效的旋钮
-l --post_list Post list of send WQEs of size (instead of single post) 1(不链) 仅带宽类 一次软件投递让硬件执行几条消息——用于绕开软件投递速率限制
--recv_post_list Post list of receive WQEs of size (instead of single post) 1(不链) 仅带宽类 接收侧对应动作,验证接收侧投递开销是否为瓶颈
-I --inline_size Max size of message to be sent in inline 无 Not relevant for Read and Atomic 内联阈值,对应 WQE 内联机制
-e --events Sleep on CQ events (default poll) 轮询 Not relevant for Write and RawEth 用事件替代轮询,是"延迟上升"的一个常见诱因
-Q --cq-mod Generate Cqe only after <cq-mod> completion 立即 仅带宽类 把多个 WR 攒到指定数量再生成一个 CQE,减少 CQ 压力
-o --outs Number of outstanding read/atomic requests 无 仅 Read 与 Atomic 读与原子操作的并发在途请求数——读类测试的核心旋钮
-q --qp Num of QPs running in the process 1 仅带宽类(延迟类多 QP 被拒绝) 进程内 QP 数,验证"多 QP 能否打满链路"
-O --dualport Run test in dual-port mode (2 QPs). Both ports must be active 关闭 仅带宽类;需要系统支持 双端口模式,一个 HCA 的两个端口各建一个 QP
-b --bidirectional Measure bidirectional bandwidth (default unidirectional) 单向 仅带宽类 双向带宽;配合 --report-both 分别看 RX 与 TX
-S --sl SL (default 0) 0 非 raw_ethernet_fs_rate 服务级别,决定 QoS 路径与 VL 映射
--tclass Set the Traffic Class in GRH (if GRH is in use) 无 非 raw_ethernet_fs_rate 跨子网时的 GRH 流量类别
-F --CPU-freq Do not show a warning even if cpufreq_ondemand module is loaded, and cpu-freq is not on max 警告开启 全部 它只是压制警告,不改变任何行为——压制之后测到的数据频率未必稳定
--reversed Reverse traffic direction - Server send to client 客户端发起 非 raw_ethernet_fs_rate 由服务器端发起发送,用于排除单端瓶颈
-R --rdma_cm Connect QPs with rdma_cm and run test on those QPs 直接 verbs 非 RawEth 改由 rdma_cm 建立连接,走 IPoIB 接口协商
-T --tos Set <tos_value> to RDMA-CM QPs 关闭 必须配合 -R 使用 给 RDMA-CM QP 设置 IP ToS
--retry_count= Set retry count value in rdma_cm mode 无 仅 rdma_cm 模式 在 rdma_cm 路径下设置重试次数
-u --qp-timeout QP timeout, timeout value is 4 usec * 2^(timeout), default 14 14 全部 ACK 超时定时器,见第八节
-p --port Listen on/connect to port (default 18515) 18515 全部 TCP 控制端口;全部工具同默认,因此同机不能并行跑
-d -i -x 设备 / 端口号 / GID 索引 第一块设备
端口 1
全部 多卡、多端口、RoCE 环境下必须显式指定

关于 -F, --CPU-freq 这一条,需要特别强调它的真实作用范围。man page 的原文是"Do not show a warning even if cpufreq_ondemand module is loaded, and cpu-freq is not on max"。它的语义是"不再显示警告",而不是"把频率锁到最大"。因此加与不加这个标志,perftest 测得的数据本身不会因为这个标志而变得稳定——变化的是"你是否会被提醒"。

本系列第七篇已经指出过 perftest 要求两端 CPU 时钟频率一致,并给出 -C, --report-cycles 作为频率不同时的缓解手段。把这两点与 -F 放在一起,正确的做法顺序是:先在操作系统层面把频率策略固定(例如禁用 cpufreq_ondemand 或设置 performance 策略),再跑 perftest。用 -F 来消除警告,只是把提示关掉,不解决频率漂移本身——这一条是本篇认为最需要说清的选项语义。

本节关键记忆:2 组参数 + 3 个默认值差异 + 1 个换算 + 1 个警告抑制

  • 2 组参数:时长类(-D / -f / -n / -N)与深度类(-t / -r / -l / --recv_post_list / -Q / -o / -q)
  • 3 个默认值差异:-n write 5000 / 其余 1000;-t bw 128 / 其余 1;-r 默认 512。前两个差异本身就是"带宽靠深度、延迟靠浅队列"这一取舍的编码
  • 1 个换算:-u 的超时 = 4 µs × 2timeout,默认 -u 14
  • 1 个性能论断:单次软件投递约 500 ns,故纯软件投递速率约 10 Mpps 量级;-l 64 之所以能测到硬件真实速率,是因为它不再受软件投递速率限制
  • 1 个警告抑制:-F 只是"不显示警告",不锁频。正确做法是先在操作系统层面固定频率策略

八、接收侧与 SRQ:RNR NAK 与四把可靠性旋钮

What — 本系列前十四篇从未涉及的一块:接收侧未就绪时的协议行为

本系列第七篇讲可靠性时,列出的三个组件是 PSN + ACK / NAK + 超时定时器,其中 NAK 只被笼统地提到"否则回 NAK"。但 NAK 至少有两种语义完全不同的类型,而其中一种不是错误、而是背压信号——这正是本节要补上的内容。

先从 libibverbs 的数据结构说起。ibv_qp_attr 中有四个字段,man page 对每一个都标注了 "valid only for RC QPs",它们的分工是:

字段man page 注释原文控制的对象可设置的状态迁移
timeout Local ack timeout for primary path (valid only for RC QPs) ACK 超时:多久没等到 ACK 就重传 RTS(掩码 IBV_QP_TIMEOUT)
retry_cnt Retry count (valid only for RC QPs) ACK 超时重传次数上限 RTS(IBV_QP_RETRY_CNT)
min_rnr_timer Minimum RNR NAK timer (valid only for RC QPs) 收到 RNR NAK 后至少等多久再重试 RTR(IBV_QP_MIN_RNR_TIMER)
rnr_retry RNR retry (valid only for RC QPs) RNR NAK 重试次数上限 RTS(IBV_QP_RNR_RETRY)

这张表最值得注意的不是字段本身,而是"可设置的状态迁移"这一列的分布。min_rnr_timer 是唯一一个只能在 RTR 迁移时设置的字段,其余三个都在 RTS 迁移时设置。这与 man page 给出的 RC 状态迁移属性表完全一致:RTR 需要 IBV_QP_STATE、IBV_QP_AV、IBV_QP_PATH_MTU、IBV_QP_DEST_QPN、IBV_QP_RQ_PSN、IBV_QP_MAX_DEST_RD_ATOMIC、IBV_QP_MIN_RNR_TIMER;RTS 需要 IBV_QP_STATE、IBV_QP_SQ_PSN、IBV_QP_MAX_QP_RD_ATOMIC、IBV_QP_RETRY_CNT、IBV_QP_RNR_RETRY、IBV_QP_TIMEOUT。这个分布不是实现细节,它规定了"哪些可靠性参数可以在连接建立后调整"。

一个必须说清的边界:man page 对这四个字段的定性是"在某个状态迁移上必须提供的属性",不是"可随时修改的参数"。同一条 man page 明确写着:如果任何修改属性或修改掩码无效,则不会有任何属性被修改(包括 QP 状态)。NVIDIA 的 RDMA Aware Programming 文档在讲 Queue Pair Bringup 时也提醒,这个命令名有误导性,因为你不能随意修改 QP 属性——每个状态迁移可修改的属性集合是严格受限的,且迁移必须按正确顺序发生。

因此本篇不给出"运行时怎么改 RNR 参数"的做法,因为这依赖具体驱动与传输类型,本篇无法核实。本篇给出的是这四个字段的语义与它们在状态机中的位置,以及——更重要——RNR NAK 本身在打流时意味着什么。

8.1 RNR NAK 是什么,以及为什么它不是错误

Why — 一条打流命令挂住不动,最常见的原因不是链路坏了,而是接收侧没准备好

RNR NAK 的触发条件非常明确:接收方收到了一个需要它准备接收资源的消息,但此时没有可用的接收 WQE。在通道语义下这对应 SEND 类操作——接收方必须先投递接收 WQE 才能收下这条消息;没有投递,硬件就回一个 RNR NAK。

关键在于这个 NAK 的性质:它是一个"我还没准备好,请稍后重试"的信号,不是一个"我收到了坏东西"的信号。因此发送方收到 RNR NAK 时的正确反应是等待 min_rnr_timer 指定的时间后重发,最多重试 rnr_retry 次。把 RNR NAK 当成错误去排查,方向就完全错了——它描述的是接收侧的调度时机问题,不是链路或硬件问题。

那什么情况会真的产生 RNR NAK?把上面这个机制与本系列第七篇的接收侧预投递结论接起来,至少有三种:

  1. 接收侧投递速度跟不上发送侧速度。这是最常见的一种,而且是"设计上正常"的——perftest 在带宽测试里提供 --recv_post_list 正是为了应对这个:把接收 WQE 成批投递,减少每条消息的投递次数
  2. 发送侧在途请求过多,超过了接收侧缓冲的承载能力。此时 RNR NAK 是一种有效的背压:它阻止发送方无限制地往前推,让接收侧有机会赶上
  3. 接收侧应用处理逻辑有问题,比如投递了但没有及时补投。这种才是真正的缺陷

把这三种情况与打流工具的选项对应起来,可以得到一条很实用的判读规则:

  • 加 --recv_post_list 之后 RNR 减少 → 属于第 1 种,是投递开销问题,通过成批投递解决
  • 加 --recv_post_list 之后 RNR 依旧,或减少 -t 之后 RNR 减少 → 属于第 2 种,是在途请求数与接收缓冲不匹配。此时正确的做法是把发送侧并发调低,而不是继续加深接收队列
  • 两者都无效,且 RNR 持续增长 → 属于第 3 种,需要回到接收侧应用或驱动层面排查

最后这条规则的实践价值在于:它把"RNR 出现了"这个现象,从一个模糊的"好像有点问题"变成了一个二分的实验设计。而 RNR NAK 的可观测性有一个便利条件——它不一定会以错误的形式出现在 perfquery 的错误计数器里,但它一定会反映为带宽上不去或者延迟长尾,因此必须靠 -H 看延迟分布才能发现。

8.2 SRQ:它解决什么问题,以及为什么对 RDMA Write 无效

机制视角 — SRQ 的设计前提是"接收方不知道数据什么时候到",而 RDMA Write 的接收方本来就知道

SRQ(Shared Receive Queue,共享接收队列)解决的问题可以用一句话说清:在通道语义下,接收方必须在数据到达之前就把接收缓冲区准备好,这意味着接收方的接收 WQE 投递时机必须与发送方的发送时机耦合。多条 QP 各自维护自己的接收队列时,这种耦合意味着接收方要为每一条 QP 单独管理投递节奏,而连接数一多,这个管理开销就成为问题。

SRQ 的做法是把接收队列从 QP 上剥离出来,让多条 QP 共享同一个接收队列。共享之后,接收方不再需要为每条 QP 精确对齐投递节奏——它只需要保证共享队列里有足够多的可用 WQE 即可。man page 对 -r, --rx-depth 的说明精确地体现了这个机制:"if using srq, rx-depth controls max-wr size of the srq",即启用 SRQ 之后,rx-depth 控制的不再是单个 QP 的接收队列深度,而是整个共享队列的最大 WQE 数。这是一个语义上的根本变化。

那么为什么 --use-srq 对 RDMA Write 无效?man page 对这个选项的说明以 "Relevant only for Send" 结尾。原因可以从 SRQ 的设计前提反推出来:SRQ 存在的意义是解除"接收投递时机必须与发送时机对齐"这个约束,而内存语义的 RDMA Write 根本不存在这个约束。本系列第七篇第八节讲过 RDMA Write 的语义是单边的——发送方知道对端的地址与 rkey,直接把数据写到对端内存的指定位置,接收方的 HCA 不需要为这条消息准备任何接收缓冲区。

把这个推导与 RNR NAK 的触发条件接起来,结论是自洽的:RNR NAK 之所以会出现,是因为"需要接收方准备接收资源而它没有准备";而 RDMA Write 不需要接收方准备任何接收资源,因此 RNR NAK 在 RDMA Write 上根本没有触发机会,共享接收队列也就无从发挥作用。SRQ 与 RNR 是同一个问题的两面:一个是"如何让多条 QP 共享接收资源",一个是"接收资源不足时如何告知发送方"。二者都建立在通道语义之上。

这个理解直接给出 SRQ 的适用判据:当业务或测试使用 SEND / SEND_WITH_IMM 这类通道语义操作,且连接数多到让接收侧管理成为负担时,SRQ 才有价值;而当业务以 RDMA Write 为主时,启用 SRQ 不会带来任何收益,只会在 man page 的 "Relevant only for Send" 限定之外被误用。本篇把这条写在正文里,是因为"给 ib_write_bw 加 --use-srq 以提速"是一个看起来非常自然、实际无效的误用。

本节关键记忆:4 个字段 + 1 个状态迁移分布 + 1 个 NAK 性质 + 3 类 RNR 判读

  • 4 个字段:timeout(ACK 超时)/ retry_cnt(ACK 重试次数)/ min_rnr_timer(RNR 后最小等待)/ rnr_retry(RNR 重试次数),man page 四个都标注 "valid only for RC QPs"
  • 1 个状态迁移分布:min_rnr_timer 只能在 RTR 设置,其余三个在 RTS 设置。这规定了哪些可靠性参数可在连接建立后调整,也说明它们不能随意改——无效属性与掩码会导致包括 QP 状态在内都不被修改
  • 1 个 NAK 性质:RNR NAK 不是错误,是"我还没准备好"的背压信号。触发条件是收到需要接收资源的消息但无可用接收 WQE
  • 3 类 RNR 判读:加 --recv_post_list 后减少 = 投递开销问题;减 -t 后减少 = 在途数与接收缓冲不匹配;两者都无效 = 接收侧应用或驱动缺陷
  • 1 个 SRQ 结论:--use-srq 仅对 Send 生效。因为 SRQ 与 RNR 都建立在通道语义之上,而 RDMA Write 接收侧不需要预投递任何资源。启用 SRQ 后 -r 的语义变为共享队列最大 WQE 数
  • 1 个超时换算:-u 14 对应 4 µs × 214

九、NUMA 与 CPU 亲和:四选项的适用边界

What — README 对 CPU / NUMA 亲和这一整块的完整描述

perftest README 的 Special feature 段第 7 条专门讲 CPU / NUMA 亲和,标题就是四个选项:--pin_cores、--numa_node、--disable_numa。这一块是打流时最容易被忽略、也最容易造成"数字不可重复"的因素,本节按 README 原文把它的行为讲全。

先说最重要的一条:默认行为不是"不绑定",而是"自动绑定"。README 原文是:perftest 自动检测 IB 设备的 NUMA 节点,并把基准测试线程与内存分配绑定到该节点,目的是让 CPU、内存缓冲与网卡共置,消除跨节点内存访问的代价。并且明确说明当前生效的 NUMA 节点会出现在测试输出头部。

把这条与本系列第六篇讲过的"信用环"放在一起,会看到一个完整的因果:NUMA 绑核解决的是主机侧的内存访问路径,信用环发生在 fabric 内部的网络侧。本篇第十三节要讲的 port_xmit_wait 之所以能观测到信用问题,正是因为它记录在链路上。NUMA 绑核与路由引擎配置是排查带宽上不去时的两条独立线索,必须分别检查。

README 给出的行为清单,逐条抄录如下:

  • 默认:perftest 从 sysfs 读取网卡的 NUMA 节点并自动绑定,不需要任何标志
  • 无法确定时:如果网卡的 NUMA 节点无法确定,perftest 继续运行且不做绑定(不会报错、不会拒绝运行)
  • --pin_cores:改为绑定到指定 CPU 核心(可给范围)
  • --numa_node:改为绑定到指定 NUMA 节点
  • --disable_numa:完全禁用自动 NUMA 绑定,恢复原始行为(由操作系统调度器决定放置位置)
  • 互斥关系:--numa_node 与 --pin_cores、--disable_numa 三者互斥,在参数解析阶段就会被拒绝
  • 尊重外部绑定:perftest 会尊重外部工具预先设置的 CPU 亲和性(例如 taskset 或 numactl);检测到外部限制时,自动 NUMA 绑定被跳过;显式传入 --pin_cores 或 --numa_node 会覆盖任何外部绑定
  • 依赖:--numa_node 与自动检测需要 libnuma;--pin_cores 在任何 Linux 系统上都可用;Raw Ethernet 基准不支持这一整块

README 随后给出了五组命令行示例,其中第四组与第五组构成了一个容易踩的坑:

# Automatic NUMA binding (default, no flags needed)
Server: ./ib_write_bw -d mlx5_0
Client: ./ib_write_bw -d mlx5_0 <server_ip>

# Pin to specific CPU core
Server: ./ib_write_bw --pin_cores=5 -d mlx5_0
Client: ./ib_write_bw --pin_cores=5 -d mlx5_0 <server_ip>

# Pin to a range of cores
Server: ./ib_write_bw --pin_cores=0-3,8 -d mlx5_0
Client: ./ib_write_bw --pin_cores=0-3,8 -d mlx5_0 <server_ip>

# Bind to NUMA node 1
Server: ./ib_write_bw --numa_node=1 -d mlx5_0
Client: ./ib_write_bw --numa_node=1 -d mlx5_0 <server_ip>

# External tool respected automatically (no perftest flag needed)
Server: numactl --cpunodebind=1 ./ib_write_bw -d mlx5_0
Client: numactl --cpunodebind=1 ./ib_write_bw -d mlx5_0 <server_ip>

# Explicitly disable NUMA binding
Server: ./ib_write_bw --disable_numa -d mlx5_0
Client: ./ib_write_bw --disable_numa -d mlx5_0 <server_ip>

关于第四组与第五组并存,需要说清它们的关系。第四组是"用 perftest 自己的选项绑定",第五组是"用外部工具绑定,perftest 检测到之后跳过自动绑定"。两者达到的目的相同,但适用范围不同:第四组需要 perftest 编译时带 libnuma 支持;第五组不依赖 perftest 的编译选项,因此在没装 libnuma 的环境里也能用。

而真正需要注意的坑在覆盖关系上。README 说显式传入 --pin_cores 或 --numa_node 会覆盖任何外部绑定。这句话的实际含义是:如果两端都用 numactl 绑好了,此时又在命令行里加了 --numa_node,那么 numactl 的设置会被 perftest 的设置取代——而不是"两者都生效"。在多节点测试脚本里同时写这两种绑定方式是常见的冗余写法,应当只保留一种。

另一条容易被忽略的细节:perftest 会把当前生效的 NUMA 节点打印在输出头部。这条设计的价值在于它把"这次测试到底绑没绑、绑到哪"变成了输出里可核对的一行字,而不是一个需要人去猜的假设。记录基线时把这一行一并记下来,是本篇建议的做法。

本节关键记忆:4 个选项 + 1 个默认行为 + 2 条互斥与覆盖规则 + 1 行输出

  • 4 个选项:--pin_cores(绑核)/ --numa_node(绑 NUMA 节点)/ --disable_numa(禁用)/ libnuma(依赖项)
  • 1 个默认行为:默认自动绑定到网卡所在 NUMA 节点,目的是让 CPU / 缓冲 / 网卡共置;检测不到时静默跳过,不报错
  • 2 条规则:--numa_node 与 --pin_cores / --disable_numa 三者互斥,解析阶段即拒绝;显式选项覆盖外部 taskset / numactl 绑定,而非叠加
  • 1 行输出:当前生效的 NUMA 节点打印在输出头部,建基线时应一并记录
  • 1 条边界:Raw Ethernet 基准不支持这一整块;--pin_cores 任何 Linux 都可用,--numa_node 与自动检测需要 libnuma

十、限速:--rate_limit 与三种限速器

What — man page 列出的五个限速相关选项

perftest man page 有一整组以限速为目的的选项,它们全部标注 "Relevant only for bandwidth and raw_ethernet_burst_lat":

选项man page 语义可用值 / 单位
--rate_limit= Set the maximum rate of sent packages. default unit is [Gbps]. use --rate_units to change that 数值 + 单位由 --rate_units 决定
--rate_units= [Mgp] Set the units for rate limit to MiBps (M), Gbps (g) or pps (p). default is Gbps (g) M / g / p
--rate_limit_type= [HW/SW/PP] Limit the QP's by HW, PP or by SW. Disabled by default. When rate_limit is not specified HW limit is Default. HW / SW / PP
--burst_size= Set the amount of messages to send in a burst when using rate limiter 消息条数
--typical_pkt_size= Set the size of the packet to send in a burst. Only supports PP rate limiter 字节

这组选项里最需要说清的是 --rate_limit_type 的默认值规则,因为它不是"一个固定默认值",而是"依赖另一个选项是否给值"。man page 的原文分成两句:"Disabled by default. When rate_limit is not specified HW limit is Default." 拆开就是:

  • 不指定 --rate_limit 时,限速功能整体是关闭的,此时无论 --rate_limit_type 填什么都不生效
  • 一旦指定了 --rate_limit 而没有指定 --rate_limit_type,默认限速器是 HW——注意这个"默认"只在限速已启用时才有意义

三种限速器的区别,man page 没有展开,本篇也不推测其内部实现差异——因为这一层差异依赖具体 HCA 固件实现,本篇无法核实,因此只列出可选值,不做行为描述。可以确定的只有一条:--typical_pkt_size 只支持 PP 限速器,这一点是 man page 明确写出的,因此可以反推出:使用 PP 时另两个参数有额外含义,使用 HW 或 SW 时 --typical_pkt_size 不可用。

限速在打流中的真正用途:它不是用来"测上限"的,而是用来"制造可控负载"的。这一点与前面几节的思路是同一套——逐级加压,观察性能曲线在哪里转折,比一次性打满然后猜测原因有效得多。而限速提供的正是连续可调的负载强度,因此它天然属于本系列第十一篇讲路由引擎时用的那套方法:先构造一个已知条件下的负载,再看系统如何响应。

把本节与第九节的计数器接起来,限速还有一个非常实用的组合:用 --rate_limit 把负载压在低于链路上限的位置,然后观察 port_xmit_wait 是否接近零。如果不接近零,说明"链路发不出去"的原因不是负载太高,而是链路本身有问题——这是一个能把"性能不足"和"链路故障"分开的判据。

本节关键记忆:5 个选项 + 1 条默认值依赖规则 + 1 条组合判据

  • 5 个选项:--rate_limit / --rate_units(M=MiBps、g=Gbps、p=pps)/ --rate_limit_type(HW / SW / PP)/ --burst_size / --typical_pkt_size
  • 1 条默认值规则:不指定 --rate_limit 则限速整体关闭;指定了但没给 --rate_limit_type,默认才是 HW。两个"默认"是条件关系,不是并列关系
  • 1 条硬限定:--typical_pkt_size 只支持 PP 限速器,因此可用于反推另外两个限速器下的参数可用性
  • 1 条边界声明:三种限速器的内部实现差异依赖 HCA 固件,本篇不做描述、不给推测
  • 1 个组合判据:限速压到链路以下后看 port_xmit_wait 是否接近零——不接近零说明问题在链路本身,而非负载过高

十一、数据校验:--data_validation 与四个计数器的正确读法

What — README 第 8 条:一条把"带宽测试"变成"正确性测试"的选项

README Special feature 段第 8 条讲 Data Validation。man page 对 --data_validation 的说明是:在 ib_write_bw 与 ib_read_bw 期间对 RDMA 数据传输做实时校验;接收方验证每一个字节是否匹配期望的模式,从而检测数据损坏、DMA 竞争与陈旧数据。

这一条改变了 perftest 的性质,值得单独讲。前面几节反复强调 perftest 是性能工具,而这一条把它变成了性能与正确性二合一的工具——在跑带宽的同时校验数据内容。这对本篇的核心命题是一个有意义的补充:如果一条链路的带宽很漂亮但数据有错,那么"重打一遍"这个动作本身就掩盖了问题。

使用条件有一组硬约束,README 列得很明确:

  • 需要主机内存,或使用 --use_cuda
  • 需要 RC 连接类型——README 明确说明它使用 RDMA FETCH_AND_ADD 做信号通知
  • 仅带宽类测试:ib_write_bw、ib_read_bw
  • tx_depth ≥ 32(-t),且两端必须相同
  • 需要带 SIMD 支持的硬件:AVX2 / NEON / SSE4.2
  • 主机侧校验始终编译进包,无需额外依赖;GPU 侧插件在存在 CUDA Toolkit 时自动检测并构建

而这些限制也直接决定了它与哪些选项互斥。README 给出的不兼容列表是这一节最实用的部分:-a(跑全部尺寸)、--post_list 大于 1、--run_infinitely、--mr_per_qp、--gpu_touch、--use-null-mr、--payload_file_path、非 RC 连接类型。把这张互斥表与第三节的 -l 对照,可以得到一条明确结论:--data_validation 与"追求极限性能"的那些选项在设计上就是互斥的,因为校验本身要花 CPU。

11.1 四个输出计数器的正确读法(本节最重要的一张表)

Why — 为什么"报了非零"不等于"有问题"

README 在 Output Counters 段定义了四个计数器,并且为其中三个明确说明了"这是正常行为,不表示错误"。如果按常规直觉把"非零即异常"来读这段输出,会得到完全相反的结论。下表逐条抄录并给出读法:

计数器README 的定义性质正确的读法
errors Actual data corruption detected. The received data did not match what was expected. Any non-zero value means something went wrong with the RDMA transfer. The location and byte values of the first error are reported 唯一的真错误 非零即真问题,且会报出第一个出错位置与字节值
races The checker found a temporary mismatch because new data arrived while the previous data was still being verified. This is expected under normal operation and does not indicate corruption 正常现象 不作为判据
skips Some data chunks completed faster than the checker could verify them, so they were skipped. This is normal under high throughput and does not indicate errors — it simply means the transfer rate exceeded the verification rate 正常现象 不作为判据;但它高说明带宽跑在校验能力之上
retries A brief inconsistency was detected at the tail end of a data chunk, but a quick re-check confirmed the data was correct. This happens because the network adapter writes data over PCIe in order, so the tail marker may arrive before the last payload bytes are fully flushed to host memory. The checker detects this and retries automatically. Not an error. 正常现象 不作为判据;根因是 PCIe 写序,不是网络

README 的总结句写得非常清楚:"In summary: only 'errors' indicates a real problem. The races, skips, and retries counters are informational and reflect normal behavior at high speed." 示例输出也印证了这一点:

VALIDATION: PASSED - 12500 chunks, 102400000 bytes [races=3, retries=7, skips=42]

这张表的实践结论是:如果把 races / skips / retries 当成错误指标,就会得出"每次跑都有问题"的结论;而实际上这三个计数器的绝对值毫无意义,只有 errors 需要被当作判据。反过来,skips 有一个"非判据但有用"的读法:它高说明数据到达速度超过了校验速度,此时校验覆盖率实际上是下降的,因此"skips 很高但 errors 为零"应当被读作"没有发现问题,但也没有校验到位"。

方法论视角 — 为什么"带宽测试"与"正确性测试"合一是打流工具设计上的一个重要转折

把 --data_validation 放到本篇的核心命题下看,会发现它改变的不只是功能,而是打流这件事的证据结构。

先看没有校验时证据链的形态。打流跑完得到一个带宽数字,这个数字是一个聚合结果:它可能很高,也可能很低。无论是高还是低,它都是对"总共搬了多少数据、用了多久"的一次统计,而这个统计对"数据是否正确"完全不敏感。这意味着一个能跑出漂亮带宽的链路,与一个能跑出漂亮带宽且数据正确的链路,在带宽数字上无法区分。

再看有校验时证据链的形态。加上 --data_validation 之后,同一次运行同时产生了两个结论:性能结论(带宽多少)与正确性结论(errors 是否为零)。关键在于这两个结论来自同一次运行——不需要跑两遍,不需要两个工具,不需要在两次运行之间假设"环境没变"。这在方法论上的差别比看起来大得多:两遍测试之间,环境可能已经变了;一遍测试内部的两个结论则共享同一个环境快照。

但这一节更值得讲的是它揭示的一个结构性代价:校验与极限性能在设计上互斥。README 的互斥列表已经把这件事说得很直接——校验不能与 -a 共存、不能与大于 1 的 --post_list 共存、必须 -t ≥ 32、只支持 RC、只支持带宽类。而这些选项在打流实践中的分量并不一样:-a 是逐尺寸扫描的入口,一次运行覆盖从 2 字节到 8 MB 的全部报文尺寸;-l 则是第三节讲的那个"绕过软件投递速率上限、看到硬件真实消息速率"的旋钮。换句话说,与校验互斥的恰恰是打流里最常用的两个选项,因此"要校验还是要极限性能"必须显式选一边,不存在"两个都要"的默认路径。这条能力在实践中因此呈现出一个明确的结构:

  --data_validation 把打流分成两个互斥的目的

  ┌────────────────────────────┬─────────────────────────────┐
  │  目的:证明「能跑多快」      │  目的:证明「数据是对的」      │
  ├────────────────────────────┼─────────────────────────────┤
  │ -a  扫全部消息尺寸          │ 与 -a 互斥                    │
  │ -l  链式投递,绕开软件速率  │ 与 -l > 1 互斥               │
  │ --run_infinitely 长跑观察    │ 与 --run_infinitely 互斥      │
  │ --mr_per_qp 分散内存区域     │ 与 --mr_per_qp 互斥           │
  │ -c RC 之外也可试其它类型     │ 只支持 RC                    │
  │ -t 可低于 32                │ 必须 -t >= 32(两端相同)      │
  ├────────────────────────────┼─────────────────────────────┤
  │ 成本:几乎没有额外开销       │ 成本:校验线程与 GPU/CPU 争资源 │
  │                            │  README 明确:会预期带宽下降    │
  │                            │  尤其 READ 模式              │
  └────────────────────────────┴─────────────────────────────┘

  ★ 唯一同时指认两个结论的地方:一次运行同时给出
    带宽数字与 errors 计数,二者共享同一环境快照。
    这是它相对「跑两遍」的根本优势。

  ★ 但 skips 计数高时要读作:
    「没有发现问题,但也没有校验到位」——
    校验覆盖率下降,PASSED 不等于全覆盖。

把最后这一点放在整节末尾作为结论:校验类工具的输出天然带着一个"覆盖率"的自陈机制——skips 就是这个自陈。而绝大多数性能与正确性工具没有这个机制,它们只在"通过"与"不通过"之间给出一个二元结论。因此在读这类工具的输出时,先找那个告诉你"我可能漏了"的字段,再看它给出的结论,应当成为固定动作。本篇把它写出来,是因为 --data_validation 恰好把这件事做对了,可以作为一个可参照的样本。

关于二进制兼容性的一个实用提醒(README 原文):如果用 SIMD 路径构建的二进制被运行在一个不具备该 SIMD 指令集、但支持另一档 SIMD 的系统上,主机侧 --data_validation 可能不可用。README 给出的解决办法是在目标系统上重新构建 perftest,让 configure 选出受支持的那条 SIMD 校验路径。

关于大页有一条容易混淆的地方,README 原文与 man page 需要分开看。README 给的是内核层面的系统配置:在系统上配置 2MB 或 1GB 大页可以在多数情况下改善带宽,原因是减少 TLB 压力,命令是 echo 2048 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages。而 man page 里的 --use_hugepages 是 perftest 自己的分配行为:"Use Hugepages instead of contig, memalign allocations"——它决定的是 perftest 用 hugepage 还是用普通 contig / memalign 去分配测试缓冲区,与内核里预留了多少大页是两回事。前者是 perftest 的一个开关,后者是操作系统的系统配置。两者的正确关系是:--use_hugepages 要求系统里确实已经预留了大页,否则这个开关打开也没有可用的池子;系统预留了大页但不带这个开关,perftest 仍然走 contig / memalign 路径。因此二者的顺序是先配系统、再开开关,而不是二选一。

本节关键记忆:1 个选项 + 5 条使用条件 + 8 项互斥 + 4 个计数器的读法

  • 1 个选项:--data_validation —— 在 ib_write_bw / ib_read_bw 期间实时校验 RDMA 数据,检测损坏、DMA 竞争与陈旧数据
  • 5 条使用条件:RC 连接类型(用 RDMA FETCH_AND_ADD 做信号)/ 仅带宽类 / -t ≥ 32 且两端相同 / SIMD 硬件(AVX2 / NEON / SSE4.2)/ 主机内存或 --use_cuda
  • 8 项互斥:-a / --post_list>1 / --run_infinitely / --mr_per_qp / --gpu_touch / --use-null-mr / --payload_file_path / 非 RC 类型
  • 4 个计数器的读法:只有 errors 是真错误;races / skips / retries README 明确写为正常行为。retries 的根因是PCIe 写序,不是网络
  • 1 个覆盖率读法:skips 高 = 没有发现问题但也没校验到位,PASSED 不等于全覆盖
  • 1 个性能建议:开启校验会带宽下降,READ 模式更明显;配 2MB / 1GB 大页可因减少 TLB 压力而改善

十二、MTU、SL 与报文层参数:-m / -S / --tclass / -x / --dlid

What — 这一组参数共同的特点:它们改的不是"打多少",而是"打出去的包长什么样"

前面几节的参数都在调节流量形态。这一节的参数调节的是报文本身,因此它们与本系列前面各篇的机制直接对应:-m 对应 MTU 档位,-S 对应 SL-to-VL 映射,--tclass 对应 GRH,-x 对应 GID 索引,--pkey_index 对应分区。

参数man page 语义默认值它改变的是什么
-m --mtu MTU size : 64 - 9600 (default port mtu) for RawEth else 256 - 4096. Not relevant for raw_ethernet_fs_rate 端口 MTU 单个包能携带的载荷上限;超过则触发分段
-S --sl SL (default 0). Not relevant for raw_ethernet_fs_rate 0 服务级别 → 经 SL-to-VL 映射决定走哪条 VL
-L --hop_limit Set hop limit value (ttl for IPv4 RawEth QP). Values 0-255 (default 64). Only for RawEth 64 仅 Raw Ethernet 工具
-x --gid-index Test uses GID with GID index. Not relevant for RawEth 无 选哪一个 GID 表项,RoCE 环境下决定走哪张表
--pkey_index PKey index to use for QP. Not relevant for raw_ethernet_fs_rate 无 用哪个分区号发送
--tclass Set the Traffic Class in GRH (if GRH is in use). Not relevant for raw_ethernet_fs_rate 无 跨子网时 GRH 的流量类别字段
--dlid Set a Destination LID instead of getting it from the other side. Not relevant for raw_ethernet_fs_rate 从对端获取 直接指定目的 LID,不与对端协商
-g --mcg Send messages to multicast group with 1 QP attached to it. When there is no multicast gid specified, a default IPv6 typed gid will be used. Relevant only for send non fsRate 无 组播测试;默认 GID 是 IPv6 类型
-M --MGID In multicast, uses <multicast_gid> as the group MGID. can be either decimal or hexadecimal 默认 GID 指定组播 MGID,十进制与十六进制均可
--ipv6 / --ipv6-addr= Use IPv6 GID. Default is IPv4. / Use IPv6 address for parameters negotiation. Default is IPv4 IPv4 协议族选择,影响 GID 形态与参数协商地址
--bind_source_ip Source IP of the interface used for connection establishment. By default taken from routing table 路由表 多网卡机器上明确指定建链用的源 IP
--force-link= Force the link(s) to a specific type: IB or Ethernet 不强制 VPI 卡上强制走哪一层
-A --atomic_type Type of atomic operation from {CMP_AND_SWAP, FETCH_AND_ADD} (default FETCH_AND_ADD). Relevant only for Atomic FETCH_AND_ADD 原子操作类型,见下
-pkey_index / -I 见上表;-I 的说明是 "Not relevant for Read and Atomic" 内联大小对读与原子无效

关于 -m 的取值范围,这里要修正一个流传很广的不准确说法。本系列第七篇已经确认 InfiniBand 的 MTU 是 256 / 512 / 1024 / 2048 / 4096 五个离散档位(RFC 4391 / RFC 4392),而不是任意连续取值。perftest man page 对 -m 的说明正是 "else 256 - 4096",它给的是范围,而实际可选的只有那五档。因此给 -m 传一个落在范围内的非档位值(例如 3000)是没有意义的——具体如何归一化到最近档位,取决于 QP 路径 MTU 的协商实现,本篇不做推测。正确做法是只用这五个值。

另外注意 man page 给出的 -m 范围是分工具的:Raw Ethernet 工具是 64 - 9600 且默认取端口 MTU,其余工具是 256 - 4096。这两个范围不能混用,把 RawEth 的 9600 传给 ib_write_bw 是无效的。

关于 --pkey_index,本篇只给出它的存在与语义,不展开分区测试的组织方式。本系列第七篇第九节已经讲清 P_Key 的 16 位结构(bit15 成员类型 + bit14~0 分区编号,范围 0x0001~0x7FFF)与硬件强制丢弃的机制,并给出了 Linux 上的分区表路径。本篇要补的只有一条方法论:用 --pkey_index 显式指定分区号,是在验证"分区隔离是否真的生效"的最直接手段——指定一个对端 P_Key 表里不存在的分区号,包应当被必然丢弃,这条现象可以直接用来确认硬件强制的存在。

报文视角 — 为什么"改报文"是打流里性价比最高的一类实验

把这一节的参数与前面几节并排看,会发现一个结构上的差别:时长类、深度类、并行类参数改的是"引擎怎么踩油门",而这一节的参数改的是"车上装的是什么"。前者改变的是流量在时间轴上的分布形态,后者改变的是每一个包本身的属性。这两类参数的实验设计逻辑完全不同,而绝大多数打流实验之所以得不到明确结论,原因就是把它们混在了一起改。

先说为什么"改报文"这一类性价比最高。它的原因在于因果关系直接、可解释性强。-m 从 1024 改到 4096,你会看到带宽曲线在某个点之后明显趋平;这个趋平点就是分段开销消失的位置。-S 从 0 改到 7,如果这条路径的 QoS 配置确实把高 SL 映射到了独立 VL 与独立缓冲,你会在延迟分布上看到变化;如果看不到变化,那么结论同样是明确的——这条路径上的 SL 与 VL 配置没有真正起作用。无论结果朝哪个方向,它都产出了一个可以继续往下问的问题。相比之下,同时改 -t 与 -q 却只看到一个"带宽上去了"的结果,它没有告诉你哪一个起了作用,也没有告诉你哪一个已经饱和。

第二个理由是这一类参数与本系列前文机制的一一对应关系。-m 对应第七篇第四节的 MTU 与分段;-S 对应第八篇第十节的 SL-to-VL 映射与 VL 仲裁;--pkey_index 对应第七篇第九节的 P_Key 硬件强制;--tclass 对应第一篇的 GRH 结构。换句话说,这一节每一个参数背后本系列都已经讲过它的机制。用打流去验证自己已经理解的机制,是学习成本最低、也最容易形成闭环的一类实验。

第三个理由,也是最需要小心的一点:这一类参数有一个"效果可能被完全屏蔽"的特性。最典型的例子是 -S。如果两个端口的 SL-to-VL 映射最终落到同一条 VL 上,那么把 -S 从 0 改到 7 会观察到"没有任何变化"——这不是工具失效,也不是 QoS 没配,而是本次测试根本没有触发 QoS 机制。要区分"QoS 没起作用"与"测试没碰到 QoS",必须回到交换机侧确认 SL-to-VL 表,再看 perftest 用的 -S 值是否真的能映射到目标 VL。这正是本篇反复强调"层不能交换"的原因——报文层的参数,其可观测效果定义在 fabric 内部,只有交换机能给出直接证据。

把这一节的结论提炼成一条实验纪律:改报文类参数时,必须先写下预期,再看实测,两者不一致才是信息。没有预期的一组参数改动,得到的结果无法被解读——因为不知道它应该变还是不该变。而"应该变却没变"与"不该变却变了"这两种情形,指向的排查方向完全相反:前者要怀疑机制没启用(去查配置),后者要怀疑配置被绕过(去查路径)。

本节关键记忆:1 个范围修正 + 1 个范围分界 + 3 个报文体参数 + 1 个 QoS 屏蔽陷阱

  • 1 个范围修正:-m 范围是 256 - 4096,但可选值只有 256 / 512 / 1024 / 2048 / 4096 五档。非档位值没有意义,本篇不推测其归一化行为
  • 1 个范围分界:RawEth 工具 64 - 9600 且默认取端口 MTU;其余 256 - 4096。两个范围不可混用
  • 3 个报文体参数:-S(SL → VL 映射)/ --pkey_index(分区,硬件强制丢弃)/ --tclass(GRH 流量类别)。-A 只影响原子类,默认 FETCH_AND_ADD
  • 1 个 QoS 屏蔽陷阱:SL 相同或 SL-to-VL 落到同一 VL 时,改 -S 观察不到任何变化。此时要先查交换机 SL-to-VL 表,才能区分"QoS 未生效"与"测试未触发 QoS"
  • 1 条实验纪律:改报文类参数必须先写预期再实测——"应变未变"与"不应变却变了"指向相反的排查方向

十三、打流之后必须做的事:端口计数器的读法与换算

What — 端口计数器的两个来源,以及它们必须配对使用的原因

本系列前面十五篇没有系统讲过端口计数器,而它是打流闭环里最重要的一环。计数器有两个读取入口,指向同一份数据:

  • 网络侧:perfquery,通过 PerfMgt GMP 向指定节点 / 端口的 PMA(Performance Management Agent,性能管理代理)取数。它可以读远端节点——用法是 perfquery [<lid|guid> [[port] [reset_mask]]]
  • 主机侧:sysfs 路径 /sys/class/infiniband/<dev>/ports/<port>/counters/,读的是本机驱动暴露的同一组 PortCounters

把本篇第三节的四层框架与这两个入口对齐:第 ④ 层"计数器层"就是这两条路径。而它们必须配对使用的原因是——它们读的是累计量,不是增量。要得到"这次打流搬了多少字节",必须先归零再读,或者做前后相减。

13.1 perfquery 的选项与"读后归零"能力

perfquery(8) man page 给出的选项里,与本篇最相关的是这几个(UFM 文档给出的 nonstandard flags 列表与之吻合):

选项含义打流场景下的用途
-x Shows extended port counters 本节核心:基础 PortCounters 之外的扩展计数器
-a Shows aggregated counters for all ports of the destination lid 多端口节点看聚合值
-r Resets counters after read "读后归零"——打流前读一次即完成归零
-R Resets only counters 只归零不读,用于清场
-X -S Shows Xmt SL / Rcv SL port counters 按 SL 分别看收发计数——验证 -S 是否真的生效
-D -E Shows Xmt Discard Details / Rcv Error Details 丢弃与接收错误的细分
-c --smplctl Shows samples control 采样控制
-l --loop_ports Iterates through each port 遍历本节点所有端口

man page 里的典型用法示例(这些是文档给出的命令行原样,不是本地实测):

perfquery               # read local port's performance counters
perfquery 32 1         # read performance counters from lid 32, port 1
perfquery -a 32        # read from lid 32 aggregated performance counters
perfquery -r 32 1     # read performance counters from lid 32 port 1 and reset
perfquery -R 32 1     # reset performance counters of lid 32 port 1 only
perfquery -R -a 32    # reset performance counters of all lid 32 ports
perfquery -R 32 2 0xf000  # reset only non-error counters of lid 32 port 2

其中 -x 与 -R 的组合最实用:man page 的示例里有 perfquery -x -R 0x20 1,语义是只重置 lid 0x20 端口 1 的扩展计数器。为什么推荐"按类别分别归零"而不是一次 -R -a 全部清零?因为错误计数器与流量计数器在排障中的意义完全不同——流量计数器用来算吞吐,错误计数器用来判断链路是否干净,把它们分开归零,可以保证"归零"这个动作本身不掩盖任何一类数据。

一个必须知道的计数器语义陷阱(本篇认为这是整节最重要的一条)。perfquery(8) man page 明确写着:"In PortCounters, PortCountersExtended, PortXmitDataSL, and PortRcvDataSL, components that represent Data (e.g. PortXmitData and PortRcvData) indicate octets divided by 4 rather than just octets." 内核 sysfs ABI 文档的表述一致:port_xmit_data 是"Total number of data octets, divided by 4 (lanes), transmitted on all VLs. This is 64 bit counter";port_rcv_data 同理。

而 NVIDIA 支持文档给出的原因更具体:这两个字段"counting in double words, 32 bits"。也就是说,除以 4 是因为它们以 32 位双字为单位计数,而不是以字节计数。因此把 port_xmit_data 直接当字节数使用,会得到一个偏小 4 倍的数字——这足以让"接近线速"看起来像"远低于线速",从而把一次正常的测试误判为链路劣化。

13.2 计数器全表:按 Informative 与 Error 分类读

NVIDIA 支持文档《Understanding mlx5 Linux Counters and Status Parameters》给出了一张权威的对应表,其中最有价值的一列是最后一列——"Informative" 与 "Error" 的分类。这个分类直接告诉了你哪些计数器可以拿来判断"链路有没有问题"。下表是这份文档与内核 ABI 文档的合并整理:

sysfs 文件名PortCounters 字段内核 ABI 文档的语义分类
port_xmit_data PortXmitData Total number of data octets, divided by 4, transmitted on all VLs(64 bit) Informative
port_rcv_data PortRcvData Total number of data octets, divided by 4, received on all VLs(64 bit) Informative
port_xmit_packets PortXmitPkts Total number of packets transmitted on all VLs from this port. This may include packets with errors.(64 bit) Informative
port_rcv_packets PortRcvPkts Total number of packets received on all VLs(64 bit) Informative
port_xmit_wait PortXmitWait The number of ticks during which the port had data to transmit but no data was sent during the entire tick (either because of insufficient credits or because of lack of arbitration) Informative
symbol_error SymbolErrorCounter Total number of minor link errors detected on one or more physical lanes Error
link_downed LinkDownedCounter Total number of times the Port Training state machine has failed the link error recovery process and downed the link Error
link_error_recovery LinkErrorRecoveryCounter 链路错误恢复次数;与 link_downed 是两件事——恢复成功则只增长前者 Error
local_link_integrity_errors LocalLinkIntegrityErrors The number of times that the count of local physical errors exceeded the threshold specified by LocalPhyErrors Error
excessive_buffer_overrun_errors ExcessiveBufferOverrunErrors This counter indicates an input buffer overrun. It indicates possible misconfiguration of a port, either by the Subnet Manager (SM) or by user intervention. It can also indicate hardware issues or extremely… Error
port_rcv_errors PortRcvErrors Total number of packets containing an inbound raw filtering or failing inbound partition or IP version check Error
port_rcv_remote_physical_errors PortRcvRemotePhysicalErrors 远端物理错误 Error
port_rcv_switch_relay_errors PortRcvSwitchRelayErrors 交换机中继错误 Error
port_xmit_discards PortXmitDiscards 发送丢弃 Error
port_xmit_constraint_errors PortXmitConstraintErrors 发送约束错误 Error
port_rcv_constraint_errors PortRcvConstraintErrors 接收约束错误 Error
VL15_dropped VL15Dropped Number of incoming VL15 packets dropped due to resource limitations (e.g., lack of buffers) of the port Error
unicast_rcv_packets / unicast_xmit_packets UnicastRcvPkts / UnicastXmitPkts 单播包计数(64 bit) Informative

三个必须单独强调的字段,因为它们各自对应一类完全不同的故障。

第一,port_xmit_wait —— 这是本篇第十三节的核心字段。内核 ABI 文档的原文是:"The number of ticks during which the port had data to transmit but no data was sent during the entire tick (either because of insufficient credits or because of lack of arbitration)"。这句话里有两个成因,而它们的排查方向完全相反:insufficient credits(信用不足)指向下游交换机缓冲不足或信用环——本系列第十篇专门讲过信用环的成因与致命性;lack of arbitration(仲裁不足)指向本端口要与其他 VL 竞争链路。而它被归为 Informative 这一点很重要:它不是错误,而是"链路未被充分利用"的正常观测项——只有在高负载打流时它才应该有值;低负载下它增长说明链路有问题。

第二,link_downed 与 link_error_recovery 的区别。link_downed 的定义是"Port Training state machine has failed the link error recovery process and downed the link"——失败并把链路打 down;link_error_recovery 记录的是恢复动作本身发生的次数。因此正确的判读是:link_error_recovery 增长而 link_downed 不增长,说明链路抖动了但每次都恢复成功;link_downed 增长则说明链路真的掉过——后者会触发本系列第九篇讲的端口状态变化,业务侧通常能直接感知到。

第三,excessive_buffer_overrun_errors 的一条特殊语义。内核 ABI 文档明确写着:它指示输入缓冲溢出,可能意味着端口被错误配置——由 SM 造成或由用户干预造成;也可能指示硬件问题或极端情况。这条描述把排障方向分成了三类:SM 配置、用户配置、硬件。在本系列第八篇与第十一篇的语境下,"SM 造成"这一类最值得优先怀疑——因为 SM 负责给每个端口配置缓冲相关参数(本系列第八篇第九节讲的四个参数:LID / Width / MTU / Speed)。

最后一条与第七节呼应:excessive_buffer_overrun_errors 与 VL15_dropped 都属于"资源不足导致丢弃"这一类,而这一类在打流时恰好是可以通过参数避免的——-t 过高导致在途请求堆积、-q 过高导致单节点占用过多资源。因此打流时如果观察到这两类计数增长,第一反应应该是"打流本身是不是太猛了",而不是"设备坏了"。这个判断顺序能把大量误报挡掉。

13.3 用计数器给带宽数字做交叉验证

Why — 为什么 perftest 的带宽必须与端口计数器相互印证,而不是相互替代

把前两小节的内容合起来,可以构造出一个具体的验证方法。设打流的持续时间为 T 秒,链路聚合带宽为 R,则链路上应当出现的数据量有一个理论上限。把 perftest 报出的带宽与这个上限对比,可以发现三类问题。

  带宽数字与端口计数器的三种对照关系

  设 T = 打流时长,R = 该端口 rate 决定的链路带宽
  则链路在 T 时间内可搬运的数据上限 = R × T

  ┌────────────────────────────────────────────────────────────────┐
  │ 情形 A:perftest 带宽 ≈ R                                    │
  │   → 链路已打满,测试达到该配置下的能力上限                    │
  │   → 继续加 -q 或 -t 不应再有提升;若还有提升,说明之前      │
  │     瓶颈在软件投递或队列深度,不在链路                       │
  │   → 判据:port_xmit_wait 应显著非零(有数据等不到信用/仲裁) │
  ├────────────────────────────────────────────────────────────────┤
  │ 情形 B:perftest 带宽 ≪ R                                    │
  │   → 链路未打满,瓶颈在别处,必须继续分层定位                 │
  │   → 若 port_xmit_wait 接近 0:说明包根本没排队,             │
  │     问题在发送侧没把请求投出去,或在接收侧处理不过来          │
  │   → 若 port_xmit_wait 显著非零:说明包在等信用或等仲裁,     │
  │     指向交换机侧缓冲、VL 仲裁或信用环(见本系列第十篇)       │
  ├────────────────────────────────────────────────────────────────┤
  │ 情形 C:perftest 带宽 > R                                     │
  │   → 该端口的 rate 读数不是这条聚合路径的全部                  │
  │   → 典型成因:-O 双端口、-b 双向、多 QP 分散在不同端口、     │
  │     或 rate 字段本身需要按倍数换算(见下小节)                 │
  │   → 此时不能拿 R 作为参照,必须先确定真实聚合带宽             │
  └────────────────────────────────────────────────────────────────┘

  ★ 无论落入哪种情形,都必须先确认一件事:
    port_xmit_data 的读数要乘以 4 才是字节数(九点一节的陷阱)

而"rate 字段怎么读"这件事本身也需要说清,因为它是上表中 R 的来源。内核 sysfs ABI 文档对 rate 的定义只有一句:"rate: (RO) Port data rate (active width * active speed)"。注意它是一个乘积,不是单一速率——这正是"200"这个数字有歧义的原因:它既可能是一条 4 通道 HDR 链路(4 × 50 Gb/s),也可能是两条 NDR 通道的组合。内核把结果算成一个数字打印出来,而不打印它的分解,因此只看 rate 的数值无法判断链路的代次与通道数。

本篇能确认的上界是:rate 的值就是这条端口当前生效的聚合数据率,量纲是 Gb/sec。而要把这个数字与"链路代次 + 通道宽度"对应起来,需要额外的设备侧信息——本系列第十四篇讲的 ibstat 输出里就有一个 Rate 字段,它是同一个语义的另一个呈现。因此本篇的做法是:把 rate 的数值当作上界使用,而不去推测它由什么代次与几通道构成——需要分解时,以设备型号与实际链路协商结果为准。

闭环视角 — 一次合格的打流应当产出一份记录,而不是一条数字

把本篇九节串起来,会发现一件此前没有明说的事:打流的产出物不应该是"一个带宽数字",而应该是一份可以归档、可以在半年后重新解读的记录。这不是形式主义,而是由前面几节的内容共同推出来的。

第一,第五节已经证明环境本身是会变的。perftest 默认会做 NUMA 绑定,而绑定结果会打印在输出头部;CPU 频率会漂移,-F 这个选项的存在本身就说明工具认为频率可能不在最大值上;cpufreq_ondemand 被加载时会有警告。这些都不记录下来,半年后没人能解释"为什么当初是那个数"。

第二,第九节证明单个数字无法自证。同样是一个 200 Gb/sec 的 perftest 输出,可能对应"4 通道链路已打满",也可能对应"多端口聚合的其中一路"。不带端口信息与计数器读数的带宽数字,在事后是不可解读的。而port_xmit_wait 是否显著非零、错误计数器是否为零这两个事实,能把上面两种情形彻底区分开——这正是本篇主张"必须做交叉验证"的实际理由:不是追求严谨,是为了半年后还能读懂。

第三,第七节给出了一个可以照抄的格式样本。--data_validation 的输出把四个计数器全部打印出来,并明确说明"只有 errors 指示真问题"。这是一个很好的范例:工具主动告诉你结论的可信边界在哪里,而不是只给一个结论。我们自己做基线记录时应当模仿这个做法——把"这个数字在什么条件下可信"与"哪些字段不可信"一起写进记录。

把这三条合起来,一份合格的打流记录至少要包含六类信息:① 硬件与环境(HCA 型号、Rate、Width、两端 CPU 与频率策略、NUMA 节点号——从输出头部抄);② 子网状态(SM 是否在跑、端口逻辑与物理状态、MTU 档位);③ 命令行原文(两端完全相同这一条必须显式确认);④ 关键读数列(延迟用 t_typical,带宽用 BW average,与本系列第七篇的结论一致);⑤ 计数器读数(字节数类记得乘 4、port_xmit_wait、以及全部 Error 类计数器的起止值);⑥ 读数口径声明(哪些列是基线列、哪些列不可信、峰值列是否存在)。

最后一条是本篇最想强调的:口径声明比数字本身更重要。本系列第七篇已经确认基线应当避开 t_max 与 BW peak,而第三节又指出高迭代次数会让峰值列消失。如果不把"本记录使用 t_typical 与 BW average"这句话写进记录,半年后另一个人很可能拿 t_max 来对比,从而得出"性能劣化了 50 倍"这种荒谬结论。一句话的口径声明,胜过后面十页的论证。

本节关键记忆:2 个入口 + 1 个换算 + 3 个关键字段 + 3 种对照情形 + 6 类记录项

  • 2 个入口:perfquery(PerfMgt GMP → PMA,可读远端)与 /sys/class/infiniband/<dev>/ports/<port>/counters/。同一份 PortCounters
  • 1 个换算:port_xmit_data / port_rcv_data 的读数要 × 4 才是字节数(以 double words 32 bits 计数)。误用会得到偏小 4 倍的值
  • 3 个关键字段:port_xmit_wait(两种成因:信用不足 / 仲裁不足,Informative 非 Error);link_downed vs link_error_recovery(失败打 down 与恢复动作本身);excessive_buffer_overrun_errors(三类成因:SM 配置 / 用户配置 / 硬件)
  • 3 种对照情形:带宽 ≈ R 且 xmit_wait 非零 = 已打满;带宽 ≪ R 且 xmit_wait 近零 = 发送侧或接收侧问题;带宽 ≪ R 且 xmit_wait 非零 = 交换机侧缓冲 / VL 仲裁 / 信用环;带宽 > R = 聚合路径,先确认真实带宽
  • 1 个 rate 语义:rate = active width × active speed,是一个乘积。本篇只把它当上界用,不推测其代次与通道分解
  • 6 类记录项:硬件与环境(含 NUMA 号)/ 子网状态 / 命令行原文(两端相同)/ 基线读数列 / 计数器读数(× 4、xmit_wait、Error 类起止)/ 读数口径声明

十四、被动工具与主动打流的分工:ibdiagnet 的位置

What — UFM 文档对 ibdiagnet 的定义与它产出的文件清单

把 ibdiagnet 单独讲一节,是因为它在整个工具谱系里位置特殊:它既不是第 ① 层那种"只读静态参数"的工具,也不产生任何性能数字,但它产出的东西比两者都更完整。NVIDIA UFM Enterprise 文档对它的定义只有两句,但信息量很足:

ibdiagnet scans the fabric using directed route packets and extracts
all the available information regarding its connectivity and devices.

第一句确认了它的分层归属——它使用 directed route packets,因此属于本篇第三节所说的"基础工具"那一类,意味着它可以在未配置的子网里工作,对子网状态的要求与 ibnetdiscover 同级。第二句说明了它的覆盖面——它提取的是"关于连通性与设备的全部可得信息",注意 "all the available information" 这个措辞:它的定位是穷尽式采集,而不是抽样检查。

文档随后列出了它产出的文件清单,这份清单本身就是一份"fabric 全景快照目录",按 UFM 文档逐条抄录:

输出文件文档给出的内容本篇用途归类
ibdiagnet2.log A log file with detailed information 执行过程记录
ibdiagnet2.db_csv A dump of the internal tool database 内部数据库导出,可做前后两次快照的 diff
ibdiagnet2.lst A list of all the nodes, ports and links in the fabric 全 fabric 节点 / 端口 / 链路清单
ibdiagnet2.pm A dump of all the nodes PM counters 全部节点的 PM 计数器——本篇第 ④ 层
ibdiagnet2.mlnx_cntrs A dump of all the nodes Mellanox diagnostic counters 厂商诊断计数器,与 PM 计数器是不同来源
ibdiagnet2.net_dump A dump of all the links and their features 链路及其特性
ibdiagnet2.pkey A list of all pkeys found in the fabric 全 fabric P_Key 清单——本系列第七篇第九节的验证入口
ibdiagnet2.aguid A list of all alias GUIDs found in the fabric Alias GUID 清单,IPoIB 地址依赖它
ibdiagnet2.sm A dump of all the SM (state and priority) in the fabric 全 fabric 的 SM 状态与优先级——本系列第九篇选举机制的验证入口
ibdiagnet2.fdbs A dump of unicast forwarding tables of the fabric switches 单播转发表——本系列第十一篇的验证入口
ibdiagnet2.mcfdbs A dump of multicast forwarding tables of the fabric switches 组播转发表
ibdiagnet2.slvl A dump of SLVL tables of the fabric switches SL-to-VL 映射表——本篇第十二节 QoS 陷阱的验证入口
ibdiagnet2.nodes_info 厂商专有的节点通用信息(for nodes who supports it) 注意限定:只有支持的节点才有
ibdiagnet2.plft A dump of Private LFT Mapping of the fabric switches 私有 LFT 映射
ibdiagnet2.ar A dump of Adaptive Routing configuration of the fabric switches 自适应路由配置——本系列第十篇的验证入口
ibdiagnet2.vl2vl A dump of VL to VL configuration of the fabric switches VL-to-VL 配置——信用环判定的关键输入

文档还给出了它的选项,其中三条与本篇直接相关:

  • --skip <stage>:跳过指定阶段。文档列出的可跳过阶段是 vs_cap_smp / vs_cap_gmp / links / pm / speed_width_check / all。其中 pm 正是采集 PM 计数器的阶段,而 speed_width_check 正是校验速率与宽度的阶段——这两项恰好是打流前最需要的两项,因此"跳过哪些阶段"必须是有意识的选择
  • -P|--counter <PM>=<value>:如果任何给定的 PM 超过提供的值就打印出来。这是一个阈值告警机制,用于一次扫描就发现异常计数器
  • --pm_pause_time <seconds>:指定第一次计数器采样与第二次计数器采样之间等待的秒数,默认值为 1;如果给 0 则不做第二次采样。这意味着默认情况下它做的是"两次采样求差",而不是"读一次绝对值"——这正是第九节所说的"累计量必须配对"这一原则在工具层面的体现

另外几条与打流验证相关的选项:--ber_test 为每个端口提供 BER 测试并检查是否有 BER 值超过阈值;--sc 报告 Mellanox 计数器,--scr 则重置所有 Mellanox 计数器;--pc 重置所有 fabric PM 计数器。这三个选项组合起来,构成了一套与 perftest 配合的完整动作:--scr 与 --pc 归零 → 打流 → --sc 读取。

Why — 有了 perftest,为什么还需要 ibdiagnet

这个问题必须回答清楚,否则读者会认为本篇在推荐两个功能重叠的工具。答案是:它们覆盖的层不同,而在打流这个动作的周边,有大量事实 perftest 完全看不到。

  1. perftest 是"局部的",ibdiagnet 是"全域的"。perftest 只看得到它那条 QP 两端发生的事;而打流过程中真正可能出问题的地方——某条链路的符号错误、某台交换机的端口宽度协商异常、某个节点的 SM 状态异常——perftest 一个都看不到。ibdiagnet2.pm 给出的是全部节点的 PM 计数器,ibdiagnet2.slvl 与 ibdiagnet2.vl2vl 给出的是全部交换机的映射表
  2. perftest 需要"前提已成立",ibdiagnet 用来"检查前提"。本篇第三节已经确立:第 ③ 层跑出数字的前提是第 ① 层确认了状态。ibdiagnet 的一次运行就覆盖了第 ① 层与第 ④ 层的全域快照,因此"打流前跑一次、打流后再跑一次并 diff"是一个成本很低、效果很好的做法——尤其是 ibdiagnet2.db_csv 这个内部数据库导出,天然适合做前后对比
  3. 本篇第十二节与第十节的多个陷阱,都需要 ibdiagnet 来解。-S 改了不起作用要查 ibdiagnet2.slvl;port_xmit_wait 显著非零要查 ibdiagnet2.vl2vl 与自适应路由配置;打流跑不通要查 ibdiagnet2.sm 看 SM 状态与优先级

因此本篇给出的分工结论是一句话:perftest 用来产生性能数据,ibdiagnet 用来保证这些数据所处的环境是可信的。把两者接成"扫描 → 打流 → 再扫描 → diff"这个闭环,是本篇推荐的标准做法,第十一节会把它展开成完整流程。

一条必须写明的资源前提:ibdiagnet 依赖插件,而插件有明确的版本号。UFM 文档给出了它的插件加载路径与两条默认插件名:libibdiagnet_cable_diag_plugin-2.1.1 与 libibdiagnet_phy_diag_plugin-2.1.1,并说明可以用 IBDIAGNET_PLUGINS_PATH 环境变量指定额外路径;--skip_plugin 选项则可以跳过指定插件。这意味着实际运行时的输出格式会随插件版本变化——因此用它做前后 diff 时,必须确认两次运行使用的是同一套插件版本,否则 diff 的差异可能来自工具本身而不是 fabric 状态。这是本篇对"用 ibdiagnet 做前后对比"这一做法补充的唯一前提条件。

本节关键记忆:1 个定义 + 16 项输出 + 3 个关键选项 + 1 个分工结论 + 1 个前提

  • 1 个定义:ibdiagnet 用 directed route 包扫描并提取全部可得信息——因此属基础工具层,未配置子网也能工作
  • 16 项输出:本篇最关心的四项是 .pm(PM 计数器)/ .slvl(SL-to-VL 表)/ .vl2vl(VL-to-VL 配置)/ .sm(SM 状态与优先级)
  • 3 个关键选项:--skip pm 可跳过计数器采集(打流前不能跳);-P|--counter <PM>=<value> 是阈值告警;--pm_pause_time 默认 1 秒,即默认做两次采样求差,给 0 则只采一次
  • 1 个分工结论:perftest 产生性能数据,ibdiagnet 保证数据所处的环境可信。perftest 是局部的,ibdiagnet 是全域的
  • 1 个前提:插件带版本号,做前后 diff 时必须确认两次使用同一套插件,否则差异可能来自工具版本

十五、一条完整的打流与判读流程

把前面各节的内容收束成一条可执行的流程。这一节的每一步都对应前面某一节的结论,没有任何一步是新增要求。

流程共六步,核心原则是"归零在前、判读在后,中间只改一个变量"。

        InfiniBand 打流与判读的六步流程

  ┌──────────────────────────────────────────────────────────────────┐
  │ 第 1 步  前提检查(对应第 1、2 节)                              │
  │   · SM 是否在跑?(决定有没有 LID —— README IMPORTANT NOTE)      │
  │   · 本端与对端端口是否 Active + LinkUp?(第 1 层是事实)        │
  │   · 两端 perftest 版本是否一致?(README Known Issues 第 3 条)   │
  │   · CPU 频率策略是否已固定在操作系统层面?(-F 只压制警告)       │
  │   · 不通过 → 停止,回到子网侧排查,不要继续往下                   │
  └───────────────────────────┬──────────────────────────────────────┘
                              ▼
  ┌──────────────────────────────────────────────────────────────────┐
  │ 第 2 步  归零(对应第 9、10 节)                                  │
  │   · 交换机侧:错误 / 非错误分类清,参考 perfquery -R 的分类做法   │
  │   · 主机侧:读一次即完成归零(perfquery -r 的语义)              │
  │   · 若用 ibdiagnet:--scr 与 --pc 重置对应计数器                 │
  │   ⚠ 归零动作本身必须先完成,否则增量无从算起                      │
  └───────────────────────────┬──────────────────────────────────────┘
                              ▼
  ┌──────────────────────────────────────────────────────────────────┐
  │ 第 3 步  建立或引用基线(对应第 3、5、8 节)                      │
  │   · 延迟:ib_write_lat / ib_read_lat,固定 -m -s -n -c            │
  │   · 带宽:ib_write_bw / ib_read_bw,优先 -D 而非 -n              │
  │   · 记录:命令原文两端逐字相同 + 输出头部的 NUMA 节点号          │
  │   · 基线列:t_typical[usec] 与 BW average,避开 t_max / BW peak   │
  └───────────────────────────┬──────────────────────────────────────┘
                              ▼
  ┌──────────────────────────────────────────────────────────────────┐
  │ 第 4 步  分层加压(对应第 3、4、6、8 节)—— ★ 每次只改一个变量   │
  │                                                                  │
  │   维度一 报文:-m(仅五档)/ -s / -I                             │
  │   维度二 深度:-t(bw 默认 128,其余 1)/ -r / -l                │
  │   维度三 并行:-q(★ 仅带宽类)/ -O / -b                         │
  │   维度四 可靠:--use-srq(★ 仅 Send)/ --recv_post_list / -e      │
  │   维度五 限速:--rate_limit + --rate_limit_type                  │
  │                                                                  │
  │   每一档都记录:perftest 读数 + 是否出现 RNR 迹象(靠 -H 看长尾)│
  └───────────────────────────┬──────────────────────────────────────┘
                              ▼
  ┌──────────────────────────────────────────────────────────────────┐
  │ 第 5 步  读计数器(对应第 13 节)                                  │
  │   · 字节数:port_xmit_data / port_rcv_data —— ★ 读数 × 4 才是字节│
  │   · 利用率:port_xmit_wait —— ★ 两种成因要分开判                 │
  │       ≈ 0 且带宽低 → 问题在发送侧投递或接收侧处理               │
  │       显著非零且带宽低 → 查交换机缓冲 / VL 仲裁 / 信用环          │
  │   · 错误类:symbol_error / link_downed / link_error_recovery /  │
  │       local_link_integrity_errors / excessive_buffer_overrun_   │
  │       errors / port_rcv_errors / VL15_dropped / port_xmit_discards│
  │       —— ★ 增长即为问题;但先排除"打流本身太猛"这一解释          │
  │   · 按 SL 细分:perfquery -X / -S —— 验证 -S 是否真的生效        │
  └───────────────────────────┬──────────────────────────────────────┘
                              ▼
  ┌──────────────────────────────────────────────────────────────────┐
  │ 第 6 步  判读与归档(对应第 13.3 节的六类记录项)                  │
  │   · 三问:跑之前状态正常吗 / 与基线比是高是低 / 错误计数器动了吗 │
  │   · 三问全过 → 产出结论;只过第一件 → 只产出一个数字              │
  │   · 归档必须含"读数口径声明":哪些列是基线列、哪些列不可信        │
  └──────────────────────────────────────────────────────────────────┘

  ★ 全流程只有一条纪律:第 4 步里一次只改一个变量。
    同时改 -t 与 -q 得到的高带宽,无法归因,也无法判断谁已饱和。

  ★ 若要确认"环境可信",可在第 1 步与第 6 步各跑一次 ibdiagnet,
    对 .db_csv 做 diff —— 但须确认两次插件版本一致(第 14 节)。

15.1 六个常见误判,以及它们各自属于哪一步的失职

常见误判错在哪一步正确做法
"带宽很高,说明网络没问题" 第 6 步:跳过了第 5 步 高带宽与错误计数器可以同时成立。必须看 Error 类计数器的增量
"延迟比上次高,说明链路劣化" 第 3 步:口径不一致 先确认两次用的是同一列(t_typical 而非 t_max)、同一 NUMA 绑定、同一 MTU 档位
"加 --use-srq 就能提速" 第 4 步:用错了维度 man page 明确 "Relevant only for Send"。RDMA Write 接收侧不预投递资源,SRQ 无从发挥作用
"ib_write_bw -q 8 测多 QP 延迟" 第 4 步:越界使用 源码对非 BW 测试且 num_of_qps > 1 直接拒绝并返回 FAILURE。多 QP 延迟需多进程并行
"-m 3000 试试" 第 4 步:用了非档位值 只有 256 / 512 / 1024 / 2048 / 4096 五档可选。范围与档位是两件事
"-F 已经处理了 CPU 频率问题" 第 1 步:误解了选项作用 它只压制警告,不锁频。应在操作系统层面固定频率策略
"VALIDATION: PASSED 就说明数据全对" 第 5 步:忽略了覆盖率 检查 skips 计数:它高说明传输速率超过了校验速率,没有发现问题但也没校验到位
"改 -S 没反应,说明 QoS 没配" 第 5 步:结论下得太早 先查 ibdiagnet2.slvl 确认 SL-to-VL 映射。也可能只是本次流量没触发 QoS
流程视角 — 为什么"归零"这一步值得被单列,而不是当作打流的附带动作

把六步流程摊开后,会发现一个不太对称的现象:第 2 步"归零"在整条流程里是最短的一步,却是唯一一步做错就无法补救的一步。第 3 步跑坏了可以重跑,第 4 步变量选错了可以重选,只有第 2 步一旦跳过或做错,第 5 步读出来的数字就永远失去了意义——因为它们是累计量。

这个不可逆性有一个容易被低估的后果:它意味着"归零"这个动作必须发生在"打流开始之前",而这个时序要求在并发场景下是难以保证的。考虑一个很常见的情况——这台机器不是专门用来打流的。生产流量、监控采集、健康检查可能随时在跑。如果在归零之后、打流开始之前有任何其它流量通过这个端口,那么第 5 步读到的增量里就混进了不属于这次测试的字节。而这个污染是无法事后识别的——因为增量本身是一个正确的数字,它只是不代表你想测的那部分。

因此本篇认为,归零的正确性依赖于一个问题:这条端口上是否只有你在产生流量?而这个问题不能靠 perftest 自己回答,只能靠环境层面的保证——专用测试节点、隔离的测试 fabric、或者一个明确的窗口期。这就把一件看似只跟测试有关的事,接到了"打流环境是否存在"这个更根本的问题上。本系列第十三篇讲子网管理器形态时讨论过故障影响面,那一节的思路在这里同样适用:测试环境的隔离程度,决定了测试结论的可信程度。

第二个关于归零的细节,是分类归零而不是整体归零。本篇第十三节已经说明 perfquery -R 支持按 reset_mask 分类别重置,-x 与 -R 组合还能把范围限定到扩展计数器。这个能力存在的意义,正是让"错误计数器"与"流量计数器"可以分开清、分开读。理由是这两类数据在排障中的生命周期不同:流量计数器在读完之后就该继续累计或重新归零,因为下一次打流还要用;而错误计数器的价值在于"它一直涨了多久"——如果每次打流前都把错误清零,就永远失去了"这个链路上个月就开始有符号错误"这类判断依据。

把这一点提炼成一条实践建议:流量计数器按测试归零,错误计数器按周期归零。前者服务于单次测量的准确性,后者服务于跨时间窗口的趋势观察。而趋势观察恰恰是发现"缓慢劣化"这类问题的唯一手段——本篇第三节说过,perftest 的 CQE 不会报告链路正在发生什么;那么链路正在发生什么,就必须靠一个不被频繁清零的计数器来记录。

最后一点,也最有价值的一点:这条流程让"打流"从一次性动作变成了可重复的实验。单次打流产出一个数字,这个数字没有参照系,本系列第七篇已经讲清了这一点。而六步流程的真正价值在于它是可重复的——归零、加压、读计数器这三个动作每次都按同样的顺序做,那么两次之间的差异就可以归因到第 4 步改的那个变量上。这才是"实验"与"测一下"的本质区别。

本节关键记忆:6 步流程 + 1 条纪律 + 8 个误判 + 2 条归零原则

  • 6 步:前提检查 → 归零 → 建基线 → 分层加压(只改一个变量)→ 读计数器 → 三问判读并归档
  • 1 条纪律:第 4 步一次只改一个变量。同时改 -t 与 -q 得到的高带宽无法归因,也无法判断谁已饱和
  • 5 个加压维度:报文(-m / -s / -I)/ 深度(-t / -r / -l)/ 并行(-q / -O / -b)/ 可靠(--use-srq / --recv_post_list / -e)/ 限速(--rate_limit)
  • 2 条归零原则:流量计数器按测试归零(服务单次测量);错误计数器按周期归零(服务趋势观察,频繁清零就看不到"一直涨了多久")
  • 1 个前提:归零的正确性依赖"这条端口上只有你在产生流量"。混入的其它流量无法事后识别——因为增量本身是正确的数字
  • 1 个本质区别:六步流程之所以是"实验"而不只是"测一下",是因为它可重复——差异可归因到唯一改变的那个变量上

十六、FAQ 高频问答(20 组)

本节围绕 InfiniBand 打流的 20 个高频易错点展开,覆盖前置条件、工具谱系、参数边界、可靠性机制、计数器判读与实验纪律六类。答案中的选项语义、默认值、"Relevant only for …" 限定、man page 原文表述与计数器字段定义,均引自文末「参考资料」列出的 perftest README、perftest man page、ibv_modify_qp(3)、infiniband-diags(8)、perfquery(8) 与内核 sysfs ABI 文档。

  1. Q1. 装好驱动、插好线就能打流了吗?还需要什么前提?

    还需要 子网管理器已在运行。perftest README 的 IMPORTANT NOTE 明确写着:在 InfiniBand fabric 上运行这些基准测试之前,交换机或 fabric 中某个节点上必须已运行子网管理器。原因与本系列第八篇一致——LID 由 SM 在子网初始化阶段分配,没有 LID 就无法建立 QP 的寻址。此外本篇第一节给出的完整前提清单是:SM 在跑、端口 Active 且 LinkUp、两端 perftest 同版本、CPU 频率策略已在操作系统层面固定。这四条要按顺序检查,任一条不满足就停下,不要继续调参数。

  2. Q2. perftest 跑出来的带宽,能代表我的业务性能吗?

    不能,官方文档明确否定了这个用法。README 的 Benchmarks Description 段原文是:"The benchmarks are not designed to emulate any real application traffic. Real application traffic may be affected by many parameters, and hence might not be predictable based only on the results of those benchmarks."把这句话转成规则:perftest 的数字只用于纵向对比,不用于横向承诺。纵向对比指同机、同线缆、同参数下改动前后的比较;横向承诺指"我测出这个数所以能保证业务到这个数",后者是文档明确否定的用法。

  3. Q3. 两端 perftest 版本不一致会有什么问题?

    可能互不兼容,README 把它列为 Known Issues 第 3 条,并给出明确要求:不同版本的 perftest 可能互不兼容,请两端使用同一版本以保证基准结果的一致性。README 的 Known Issues 第 4 条还补充了一条升级断点:测试版本 5.3 及以上无法与更早版本工作,5.70 及以上同样如此。实践上这意味着"客户端还是旧版、服务端刚升级"这种半升级状态是最容易出问题的组合,而这类问题通常表现为连接失败或结果异常,不会给出明确提示。

  4. Q4. 延迟测试能不能用 -q 测多 QP 延迟?

    不能,perftest 直接拒绝。源码中 -q 的处理分支对非 BW 测试且 num_of_qps > 1 的情况,打印 " Multiple QPs only available on bw tests" 并返回 FAILURE。而 man page 对 -q 的说明也确实标注 "Relevant only for bandwidth"。因此想测并发条件下的延迟,办法是开多个进程——每个进程一条自己的 QP 与连接,这是多进程而非单进程多线程,两者在结果口径上不等价,这一点不要混淆。

  5. Q5. --use-srq 能给 RDMA Write 提速吗?

    不能,man page 对这个选项的说明以 "Relevant only for Send" 结尾。原因可以从 SRQ 的设计前提反推:SRQ 存在的意义是解除"接收投递时机必须与发送时机对齐"这个约束,而内存语义的 RDMA Write 接收方不需要为这条消息准备任何接收缓冲区。同一个道理也解释了为什么 RDMA Write 上不会触发 RNR NAK——RNR NAK 的触发前提是"需要接收资源而没有",Write 路径不满足这个前提。SRQ 与 RNR 是同一问题的两面,二者都建立在通道语义之上。

  6. Q6. RNR NAK 是错误吗?该按故障处理吗?

    不是错误,它是"我还没准备好,请稍后重试"的背压信号。触发条件是接收方收到了需要它准备接收资源的消息,但此时没有可用的接收 WQE。发送方收到后等待 min_rnr_timer 指定的时间再重发,最多 rnr_retry 次。本篇给出的二分判读法是:加 --recv_post_list 后减少 = 投递开销问题;减 -t 后减少 = 在途数与接收缓冲不匹配;两者都无效 = 接收侧应用或驱动缺陷。把 RNR 当错误排查,方向就完全错了。

  7. Q7. timeout / retry_cnt / min_rnr_timer / rnr_retry 四个字段各管什么?能在运行中改吗?

    四个字段 man page 都标注 "valid only for RC QPs"。timeout 是 ACK 超时,retry_cnt 是 ACK 超时重传次数,min_rnr_timer 是收到 RNR NAK 后的最小等待,rnr_retry 是 RNR 重试次数。但能不能在运行中改,答案是否定的——min_rnr_timer 只能在 RTR 状态迁移时设置,其余三个在 RTS 迁移时设置,这是 man page 状态迁移属性表规定的。NVIDIA 文档也提醒 ibv_modify_qp 这个名字有误导性,不能随意修改 QP 属性,且无效的属性或掩码会导致包括 QP 状态在内都不被修改。

  8. Q8. perftest 的 -u 默认是多少?换算成时间是多少?

    默认 14,man page 给出的换算公式是 "QP timeout, timeout value is 4 usec * 2^(timeout), default 14"。代入即 4 µs × 214 = 4 µs × 16384 = 65536 µs,即 65.536 毫秒。这个值是"多久没等到 ACK 就重传"的门限,不是延迟指标。它的工程意义在于:如果链路劣化到 RTT 已经接近这个量级,QP 会开始大量重传,此时 perftest 测到的延迟会被重传机制放大——因此看到延迟异常高时,应同时检查链路上是否有错误计数器增长。

  9. Q9. -m 的范围是 256-4096,那我能传 3000 吗?

    没有意义。范围与档位是两件事。man page 给的是范围 else 256 - 4096,但 InfiniBand 的 MTU 只有 256 / 512 / 1024 / 2048 / 4096 五个离散档位(RFC 4391 / RFC 4392,本系列第七篇已确认)。给一个落在范围内但不在档位上的值,具体如何归一化取决于 QP 路径 MTU 的协商实现,本篇不做推测。正确做法是只用这五个值。另外注意范围是分工具的:Raw Ethernet 工具是 64 - 9600 且默认取端口 MTU,与其余工具不能混用。

  10. Q10. -F, --CPU-freq 加了之后 CPU 频率就稳定了吗?

    没有。man page 原文是 "Do not show a warning even if cpufreq_ondemand module is loaded, and cpu-freq is not on max",它的语义是"不再显示警告",不是"把频率锁到最大"。加与不加这个标志,perftest 测得的数据本身不会因为这个标志而变稳,变化的是你是否会被提醒。正确做法是在操作系统层面固定频率策略后再跑;如果两端频率确实不同,可以用 -C, --report-cycles 以 CPU 周期数报告,此时周期数的可比性远好于时间可比性(本系列第七篇结论)。

  11. Q11. perftest 会自动做 NUMA 绑定吗?我的 numactl 会被覆盖吗?

    会自动绑定,而显式选项会覆盖 numactl。README 的表述是:默认从 sysfs 读取网卡 NUMA 节点并自动绑定,目的是让 CPU、内存缓冲与网卡共置;若无法确定则静默跳过绑定,不报错。关于覆盖关系,README 明确写着 perftest 会尊重外部工具预先设置的 CPU 亲和性,检测到外部限制时自动 NUMA 绑定被跳过;而显式传入 --pin_cores 或 --numa_node 会覆盖任何外部绑定。所以两者是覆盖关系而非叠加关系,脚本里只应保留一种写法。另外 --numa_node 与 --pin_cores、--disable_numa 三者互斥,解析阶段即被拒绝。

  12. Q12. 小消息场景带宽上不去,先调哪个参数?

    先调 -t 与 -l,因为带宽测试的 -t 默认值虽然已是 128,但小消息场景瓶颈通常在"每条消息摊到的开销"上。判断瓶颈在软件投递还是在硬件,方法是加 -l——README 解释了原因:单次软件投递间隔约 500 纳秒,因此纯软件投递速率上限约在 10 Mpps 量级;把 list_size 设成 64 之后每次软件投递让硬件执行 64 条消息,此时测到的才是硬件真实消息速率,因为它不再受软件投递速率限制。如果加 -l 后消息速率明显上升,瓶颈原来在软件侧;否则继续看 -q 与链路侧。

  13. Q13. --rate_limit 不加 --rate_limit_type 会怎样?默认值是什么?

    不加 --rate_limit,限速整体关闭;加了但不给 --rate_limit_type,默认是 HW。man page 的原文分成两句,两个"默认"是条件关系而非并列关系:"Disabled by default. When rate_limit is not specified HW limit is Default."即不指定 --rate_limit 时限速是关闭的,此时 --rate_limit_type 填什么都不生效;只有指定了速率值而没指定限速器时,HW 才是那个"默认"。另外 --rate_units 支持 M=MiBps、g=Gbps、p=pps,默认单位是 Gbps。

  14. Q14. --data_validation 输出里 races / skips / retries 非零,说明数据错了吗?

    不说明,README 明确把三者都定义为正常行为,只有 errors 指示真问题。逐条:races 是校验时新数据到达导致的暂时性不匹配,README 写明"expected under normal operation and does not indicate corruption";skips 是数据块完成速度超过校验速度而被跳过,README 写明"does not indicate errors — it simply means the transfer rate exceeded the verification rate";retries 是数据块尾部的不一致,根因是网卡按序写 PCIe,导致尾部标记可能先于最后一批数据字节刷到主机内存,README 写明 "Not an error" 且校验器会自动重试。但 skips 有一个非判据但有用的读法:它高说明校验覆盖率下降,此时 PASSED 不等于全覆盖。

  15. Q15. --data_validation 能和 -a、-l 一起用吗?

    不能,README 给了明确的互斥列表。不能共用的选项是:-a(跑全部尺寸)、--post_list 大于 1、--run_infinitely、--mr_per_qp、--gpu_touch、--use-null-mr、--payload_file_path,以及非 RC 连接类型。此外它还有一组使用前提:仅带宽类(ib_write_bw / ib_read_bw)、需要 RC(使用 RDMA FETCH_AND_ADD 做信号)、tx_depth ≥ 32 且两端相同、需要 SIMD 支持的硬件(AVX2 / NEON / SSE4.2)。这里的关键在于:与校验互斥的 -a 与 -l,恰好是打流里最常用的两个选项——-a 一次覆盖 2 字节到 8 MB 的全部尺寸,-l 才能看到硬件真实消息速率。所以"要校验还是要极限性能"必须显式选一边,没有"两个都要"的默认路径。

  16. Q16. port_xmit_data 读出来比预期少很多,是掉包了吗?

    不是掉包,是这个字段以双字为单位计数,读数要乘以 4 才是字节数。perfquery(8) man page 明确写着:"components that represent Data (e.g. PortXmitData and PortRcvData) indicate octets divided by 4 rather than just octets";内核 ABI 文档表述一致:port_xmit_data 是 "Total number of data octets, divided by 4 (lanes), transmitted on all VLs"。NVIDIA 支持文档给出的原因是它"counting in double words, 32 bits"。把读数当字节数直接使用会得到偏小 4 倍的值,足以让"接近线速"看起来像"远低于线速",从而把一次正常测试误判为链路劣化。

  17. Q17. port_xmit_wait 增长说明什么?它算错误吗?

    它不算错误,是 Informative 类计数器,含义是"有数据要发但整个 tick 都没发出去"。内核 ABI 文档原文:"The number of ticks during which the port had data to transmit but no data was sent during the entire tick (either because of insufficient credits or because of lack of arbitration)"。两个成因的排查方向相反:信用不足指向下游交换机缓冲不足或信用环(本系列第十篇专题);仲裁不足指向本端口与其他 VL 竞争链路。实用判据是:低负载下它不该增长;高负载打流时它显著非零是正常的(说明链路已打满);带宽偏低而它接近零,说明包根本没排队,问题在发送侧投递或接收侧处理。

  18. Q18. link_downed 和 link_error_recovery 有什么区别?

    前者是"恢复失败并把链路打 down",后者是"恢复动作发生了几次"。link_downed 的定义是 "Total number of times the Port Training state machine has failed the link error recovery process and downed the link";link_error_recovery 记录的是链路错误恢复本身发生的次数。因此判读规则是:link_error_recovery 增长而 link_downed 不增长 = 链路抖动了但每次都恢复成功;link_downed 增长 = 链路真的掉过,后者会触发本系列第九篇讲的端口状态变化,业务侧通常能直接感知。

  19. Q19. 打流时 excessive_buffer_overrun_errors 或 VL15_dropped 增长,第一反应应该是查什么?

    先排除"打流本身是不是太猛了",再怀疑配置与硬件。内核 ABI 文档对 excessive_buffer_overrun_errors 的描述是:它指示输入缓冲溢出,可能意味着端口被错误配置——由 SM 造成或由用户干预造成;也可能指示硬件问题或极端情况,这条描述把排障方向分成三类:SM 配置、用户配置、硬件。而 VL15_dropped 的定义是因端口资源限制(例如缓冲不足)而丢弃的入向 VL15 包数。这两类计数增长在打流时常常是"打得太猛"的直接结果——-t 过高导致在途请求堆积、-q 过高导致单节点占用过多资源。把这个判断顺序摆正,能挡掉大量误报。

  20. Q20. 有了 perftest 还需要 ibdiagnet 吗?两者分工是什么?

    需要,两者覆盖的层不同。分工可以概括为一句:perftest 用来产生性能数据,ibdiagnet 用来保证这些数据所处的环境是可信的。perftest 只看得到它那条 QP 两端的事,而打流过程中真正可能出问题的地方——某条链路的符号错误、某台交换机的宽度协商异常、某个节点的 SM 状态异常——perftest 一个都看不到。ibdiagnet 产出的 .pm(全部节点 PM 计数器)、.slvl(SL-to-VL 表)、.vl2vl(VL-to-VL 配置)、.sm(SM 状态与优先级)恰好覆盖这些盲区。但用 .db_csv 做前后 diff 时必须确认两次运行的插件版本一致,否则差异可能来自工具版本而非 fabric 状态。

FAQ 总纲(口诀式速记)

  • 1 个前提:打流前 SM 必须在跑(README IMPORTANT NOTE);没 LID 就没法建 QP 寻址
  • 1 条纪律:perftest 数字只用于纵向对比,不用于横向承诺。官方原文:不模拟真实业务流量
  • 1 个版本要求:两端必须同版本;5.3 与 5.70 都是升级断点
  • 3 条硬边界:-q 仅带宽类(延迟类多 QP 直接 FAILURE);--use-srq 仅 Send;-I 对 Read 与 Atomic 无效
  • 5 个 MTU 档位:256 / 512 / 1024 / 2048 / 4096。范围是 256-4096,但只有五档可选;RawEth 另为 64-9600
  • 1 个换算:-u 14 = 4 µs × 214 = 65.536 ms
  • 1 个性能论断:单次软件投递约 500 ns,纯软件速率约 10 Mpps 量级;-l 64 能测到硬件真实速率
  • 1 个反直觉项:-F 只压制警告,不锁频。应在 OS 层面固定频率策略
  • 3 条 NUMA 规则:默认自动绑定并打印节点号;三者互斥;显式选项覆盖 numactl 而非叠加
  • 1 个条件默认值:不给 --rate_limit 则限速关闭;给了但不给 type 才是 HW
  • 4 个校验计数器:只有 errors 是真错误;skips 高 = 没校验到位
  • 1 个换算陷阱:port_xmit_data 读数 × 4 才是字节数(double words 计数)
  • 3 个关键计数器:port_xmit_wait(信用 / 仲裁两成因,Informative 非 Error);link_downed vs link_error_recovery;excessive_buffer_overrun_errors(SM / 用户 / 硬件三成因)
  • 1 个分工:perftest 产生数据,ibdiagnet 保证环境可信。diff 前须确认插件版本一致

十七、Roadmap 后续预告

本篇是 InfiniBand 专题的第十五篇。它的位置是:本系列第七篇《传输层:QP、分段重组、传输服务与分区》第十一节引入了 perftest 工具集与六个基础工具,本系列第十二篇《MLNX_OFED 到 DOCA-OFED》在 Roadmap 里明确写下"本篇把固件版本列为独立验证项,但没讲怎么建立带宽与延迟的基线",并把它列为待办。本篇正是兑现这一承诺的那一篇——它把"打完之后如何读计数器"这一环补齐,使"设备就绪 → 拓扑发现 → 路径计算 → 子网激活 → 持续监控与重收敛 → 性能基线与判读"这条链路第一次完整闭合。

接下来的方向,按由单点到多节点、由主机到整机的顺序包括:

  • 多节点矩阵打流与 all-to-all 编排:本篇只覆盖点到点双端,而真实集群的性能问题常常出现在多节点并发条件下。如何编排 N×N 的测试矩阵、如何识别聚合带宽的瓶颈节点、如何避免测试本身成为负载源,是一个需要单独设计的工程问题
  • GPUDirect RDMA 打流:perftest man page 已经给出 --use_cuda / --use_cuda_dmabuf / --use_rocm / --use_hl / --use_neuron 系列选项,README 也记录了 CUDA DMA-BUF 的三项前提(CUDA Toolkit 11.7 或更高、Open-Source GPU Kernel Modules 515 或更高、两个环境变量)与 MLX5_SCATTER_TO_CQE=0 这条排障手段,但本篇没有展开 GPU 侧内存与主机侧内存的差异如何影响打流结论
  • 集合通信库与 perftest 的关系:NCCL / RCCL 这类集合通信测试测的是多卡多节点协同,与 perftest 的"点对点单 QP"是两种不同的能力维度。两者的结果为什么经常不一致、哪个更接近真实业务,需要专门讨论
  • QoS 与 VL 的实测验证:本篇第十二节指出了 -S 观察不到变化的陷阱,但没有给出完整的验证方法——如何用 perfquery -X / -S 按 SL 细分的计数器、结合 ibdiagnet2.slvl 与 .vl2vl,构成一条可复现的 QoS 验证链
  • 拥塞控制的观测:本系列第七篇第六节讲了重传是坏流量,第十篇讲了信用环,但两者共同的上游成因——拥塞——一直没有实测口径。本篇第十三节的 port_xmit_wait 只能看到"发不出去",看不到"为什么开始发不出去";交换机侧的拥塞指标如何采集与解读,需要单独展开
  • 性能基线的自动化与回归:本篇第十五节把六步流程手工化了。如何把它脚本化、如何定义"偏离基线超过多少算告警"、如何在 CI 里跑 fabric 性能回归,是把本篇方法论落到工程上的下一步
  • 存储侧与应用的验证口径:NVMe-oF / SPDK over RDMA 这类场景关心的是尾延迟与 IOPS 而非平均带宽,本篇讲的 t_typical 中位数口径需要扩展到 P99 / P999。这类口径差异决定了基准结论能否迁移到业务结论
  • 固件与驱动版本对性能的影响:本系列第十四篇把固件升级讲得非常细,但没有回答"固件版本对带宽与延迟有多大影响"。这类问题必须用固定拓扑上的前后对照测量来回答,不能推测——而本篇的六步流程正是做这种测量的标准方法

如果你在实践中遇到具体问题——例如 ib_read_lat 跑不起来、带宽数字忽高忽低、-q 加不上去、延迟长尾异常、错误计数器在打流时增长、或者不确定某个计数器该不该信——欢迎在评论区留言,我会挑出共性问题单独扩展一篇专题。


posted @ 2026-10-05 23:28  左扬  阅读(4)  评论(0)    收藏  举报