跳到主要内容
新型互联与远程内存

第2章:RDMA 通信原理与 verbs —— 从 NIC 微架构到 doorbell batch

拆解 RDMA 的 verbs 编程模型、NIC 微架构(WQE/CQE/Doorbell)、QP 状态机、one-sided vs two-sided、doorbell batching、RoCE/InfiniBand 选型,从硬件到软件全栈打底,覆盖读 FORD/Motor 等论文需要的全部基础

RDMA verbs RoCE InfiniBand NCCL one-sided ConnectX WQE

RDMA 真正的价值不在”快网卡”,而在 bypass 远端 CPU——这是分离式内存能成立的物理前提。本章是模块十三的硬件/编程模型基础章,深度覆盖你做 disaggregated memory 研究 / 优化所需的所有底层基础:NIC 微架构(WQE/CQE/Doorbell)、QP 状态机、atomic op 硬件实现、doorbell batching、错误码诊断、ConnectX 代际演进。读完这章,FORD/Motor/LOTUS 论文里的每一句”我们 batch 了 N 次 CAS”、“利用 one-sided READ 拉一整 cache-line”、“绕开 atomic-IOPS 瓶颈”,你都知道底下硬件在做什么

📑 目录


1. RDMA 是什么:不是”快网卡”

很多新人第一次看到 100GbE RDMA 网卡的反应是”哦,就是更快的以太网”。这个理解错过了 RDMA 最重要的特性:两层 bypass

传统 TCP 路径:
  app → syscall → kernel TCP stack → NIC driver → 网线 →
                   远端 NIC → driver → kernel TCP stack → syscall → app

RDMA 路径(one-sided):
  app → 用户态 verbs → NIC HW → 网线 → 远端 NIC HW → 直接 DMA 到内存

                               远端 CPU 完全不参与

两层 bypass:

  • 本地 kernel bypass:用户态直接写 doorbell 寄存器触发 NIC,不进 syscall(节省 ~1 µs)
  • 远端 CPU bypass(one-sided 才有):远端 NIC 直接读写内存,远端 CPU 不知情(节省一次远端调度 + cache miss)

🧠 关键洞察:让 disaggregated memory 成立的不是带宽,是远端 CPU bypass。MN(memory node)节点上甚至可以没有 CPU 参与事务路径——这正是 FORD/Motor 这类 DM 事务系统的物理前提。如果远端必须 CPU 介入,那 MN 就退化成”远程数据库”,失去了 disaggregated 的意义。

🍎 直觉比喻:TCP 是”打电话给保姆,让她去厨房拿东西”;RDMA one-sided 是”自己有钥匙,直接进对方厨房拿”。

维度TCP/IPRDMA send/recvRDMA one-sided
单边延迟(8B)~30 µs~3 µs~2 µs
远端 CPU 参与✅ 必须✅ 必须(post recv + poll)❌ 不参与
编程模型socketverbs(消息语义)verbs(内存语义)
本地 syscall 数~2(send + recv)0(纯 user-space)0
数据拷贝次数~4(user→kernel→NIC→…)0(zero-copy)0
远端无 CPU 也能用✅(MN 可以是 CXL pool 或 SmartNIC)

数据来自 ConnectX-6 100GbE 实测,具体硬件代际会有差异。

延伸:RDMA 的”零拷贝”对小消息体感不强,但对大数据流(KV-cache blocks、训练 gradient)直接省掉两端 4 次拷贝——这是为什么 GPUDirect RDMA + NCCL 能逼近线速的根本原因。

2. Verbs 编程模型:QP、MR、CQ、PD 四件套

RDMA 的编程接口叫 libibverbs,核心抽象是四个对象,加上一个隐含主角(NIC HW context)。

                        ┌───────────────────────────────┐
                        │  Protection Domain (PD)        │
                        │  ┌────────────┐  ┌──────────┐  │
                        │  │ Memory     │  │ Queue    │  │
                        │  │ Region (MR)│  │ Pair (QP)│  │
                        │  └────────────┘  └──────────┘  │
                        │                       │        │
                        │                       ▼        │
                        │              ┌────────────┐    │
                        │              │ Completion │    │
                        │              │ Queue (CQ) │    │
                        │              └────────────┘    │
                        └───────────────────────────────┘

2.1 PD (Protection Domain)

权限沙箱。所有 MR 和 QP 都属于某个 PD,跨 PD 的访问会被 NIC 硬件直接拒绝(返回 IBV_WC_LOC_PROT_ERR)。

实际生产中往往一个进程一个 PD 就够了——它的设计初衷是”多进程共享 NIC 时互相隔离”,在 disaggregated memory 系统中用得不多。

2.2 MR (Memory Region)

预先注册到 NIC 的物理页面,产出一对 (lkey, rkey):

  • lkey:本地 key,本地发起 op 时引用本地 buffer
  • rkey:远端 key,告知远端”这片内存可被你访问”——key 加 addr 一起传给对端,对端用它发起 one-sided op

注册过程要做几件昂贵的事:

  1. Pin 物理页面(防止 OS 换页)
  2. 给 NIC 喂 IOMMU 映射(让 NIC 能 DMA 到这片物理地址)
  3. NIC 内部 TLB 分配条目

单次 ibv_reg_mr 大小决定时延:小 buffer ~µs,大 buffer(几 GB)~ms 量级。所以系统都是启动时一次性注册大块 MR,运行时不再 reg。详见 §8。

2.3 QP (Queue Pair)

发送队列 SQ + 接收队列 RQ 的组合。两个 endpoint 的 QP 配对后建立 connection。所有 op 在 QP 上排队

QP 有生命周期状态机,详见 §4。

2.4 CQ (Completion Queue)

NIC 完成 op 后写一条 CQE(Completion Queue Entry),应用 poll CQ 拿到结果。

CQE 的关键字段:

struct ibv_wc {
    uint64_t   wr_id;     // 应用自定义 id, 用于关联到原 WR
    enum ibv_wc_status status;  // SUCCESS / 各种错误
    enum ibv_wc_opcode opcode;  // RDMA_READ / RDMA_WRITE / SEND / ...
    uint32_t   byte_len;
    uint32_t   imm_data;
    uint32_t   qp_num;
    // ... 其他字段
};

CQE 是有序的(同一 QP 的 op 完成顺序与发起顺序一致,RC 模式下),但不同 QP 之间无序。

2.5 最简化的 PostSend → Poll 流程

// 1. SGE(scatter-gather entry)指向本地 buffer
struct ibv_sge sge = { .addr = (uint64_t)local_buf,
                       .length = 64,
                       .lkey = mr->lkey };

// 2. Work Request:描述这次 op
struct ibv_send_wr wr = {
    .wr_id    = 0xdeadbeef,             // 应用 id, 完成时回填到 wc.wr_id
    .opcode   = IBV_WR_RDMA_READ,
    .send_flags = IBV_SEND_SIGNALED,    // 我要 CQE
    .sg_list  = &sge, .num_sge = 1,
    .wr.rdma.remote_addr = remote_addr,
    .wr.rdma.rkey        = remote_rkey
};
struct ibv_send_wr *bad_wr;

// 3. Post(写 doorbell 触发 NIC,完全用户态)
ibv_post_send(qp, &wr, &bad_wr);

// 4. Poll(等完成)
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0) { /* spin */ }

// 5. 检查
if (wc.status != IBV_WC_SUCCESS) { /* handle error */ }

生产代码会做的额外事:

  • IBV_WR_RDMA_READ_WITH_IMM 等带 immediate data 的变种,顺路传 4B 元数据
  • send_flagsIBV_SEND_INLINE 把小 payload 直接塞进 WR(见 §9)
  • 多个 WR 用链表(wr.next = &next_wr)一次 post(doorbell batch,见 §9)
  • selective signaling:不是每个 op 都要 CQE(见 §10)

3. NIC 微架构:WQE / CQE / Doorbell 内部机制

要做 RDMA 性能优化,必须理解 NIC 看到的是什么。这一节是绝大多数中文资料里没讲透的硬核部分

3.1 NIC 顶层框图(ConnectX-6 简化)

       Host PCIe

   ┌───────┴───────┐
   │  PCIe 5.0 x16 │  ◄── 数据面 (DMA, MMIO 寄存器)
   │  (~64 GB/s)   │
   └───────┬───────┘

   ┌───────▼─────────────────────────────────────────┐
   │  NIC ASIC                                       │
   │                                                 │
   │  ┌─────────────┐    ┌────────────┐             │
   │  │ TX Engine   │    │ RX Engine  │             │
   │  │  - WQE      │    │ - 入包解析 │             │
   │  │    fetcher  │    │ - DMA write│             │
   │  │  - DMA read │    └────────────┘             │
   │  │  - Pkt build │                              │
   │  └──────┬──────┘                                │
   │         │                                       │
   │  ┌──────▼──────────────────────────────────┐    │
   │  │  Internal SRAM (有限!)                 │    │
   │  │  - QP context (per-QP state)            │    │
   │  │  - MR cache (TLB)                       │    │
   │  │  - WQE 缓冲                             │    │
   │  │  - CQ 缓冲                              │    │
   │  └─────────────────────────────────────────┘    │
   │                                                 │
   │  ┌─────────────────────────────────────────┐    │
   │  │  Atomic Engine                          │    │
   │  │  (CAS / FAA 处理单元)                  │    │
   │  └─────────────────────────────────────────┘    │
   │                                                 │
   │  ┌─────────────────────────────────────────┐    │
   │  │  Crypto / Transport offload             │    │
   │  └─────────────────────────────────────────┘    │
   └─────────────────┬───────────────────────────────┘
                     │  100 GbE / 200 GbE

                   网络

关键:NIC 内部 SRAM 是有限的(典型几十 MB)。QP 多了、MR 多了 → SRAM 装不下 → 要去 host 内存查(性能腰斩)。

3.2 WQE(Work Queue Element)结构

应用调 ibv_post_send 时,libibverbs 把 ibv_send_wr 翻译成硬件格式的 WQE,写到 SQ 的尾部。

ConnectX-6 mlx5 的 WQE 大致结构(简化):

WQE (单个 WR 占用 64-256B,具体看 op 类型):
+------+--------------+--------+----------------+--------+
| Ctrl | RDMA addr+key| LKEY   | Local addr+len | Sig    |
| 16B  | (8+4)B       | 4B     | (8+4)B         | 4B     |
+------+--------------+--------+----------------+--------+

 opcode, signaled, inline, fence, ...

字段含义:

  • Ctrl:opcode(READ/WRITE/CAS/…)、SIGNALED 标志、qp_num、wqe_size
  • RDMA addr + rkey:远端地址 + key(one-sided op 才有)
  • LKEY:本地 lkey
  • Local addr + len:本地 SGE
  • Sig:校验位

关键:WQE 写到的是 host 内存(SQ ring buffer),NIC 通过 PCIe DMA 读取。

3.3 SQ / RQ Ring Buffer

  SQ (Send Queue) ring buffer (host 内存):

  +-------+-------+-------+-------+ ... +-------+
  | WQE 0 | WQE 1 | WQE 2 | WQE 3 |     |WQE N-1|
  +-------+-------+-------+-------+ ... +-------+
   ↑                       ↑
   pi (producer index)     ci (consumer index)
   (app 写)                (NIC 读后更新)
  • 应用写 WQE 后,必须更新 producer index (pi),NIC 才知道有新 WQE
  • 更新 pi 的方式 = 写 NIC 的 doorbell 寄存器

3.4 Doorbell 机制

Doorbell 是 NIC 暴露给 host 的一个 MMIO 寄存器(每个 QP 一个,通常映射在 BAR 空间):

  ┌─────────────────────────────────────────┐
  │         Host CPU                         │
  │                                          │
  │   写 doorbell:                           │
  │    *((uint64_t*)doorbell_addr) = pi << 8 | qp_num │
  │                                          │
  │    ↓ 这是一次 PCIe MMIO Write           │
  └─────────────┬────────────────────────────┘
                │ ~80-150 ns 跨 PCIe

  ┌─────────────────────────────────────────┐
  │         NIC ASIC                         │
  │                                          │
  │   Doorbell received → schedule QP        │
  │   → DMA fetch WQE from SQ                │
  │   → process op                           │
  └─────────────────────────────────────────┘

为什么 doorbell 写贵:

  • PCIe Posted Write 本身 ~30-50 ns(单纯传输)
  • 但写后必须 fence(确保 WQE 数据先落地、再写 doorbell)→ +30-50 ns
  • NIC 调度延迟:~30-50 ns
  • 总开销 ~80-150 ns

这就是为什么 batch 这么重要:一次 doorbell 触发 N 个 op,摊销开销到每个 op 仅 ~10-20 ns

3.5 Doorbell Record(优化路径)

新版 NIC(ConnectX-5+)还提供 doorbell record(BlueFlame/UAR):

旧路径:
  写 WQE 到 SQ → fence → 写 doorbell MMIO → NIC fetch WQE
  
BlueFlame 路径:
  写完整 WQE 直接到 NIC 的 BAR(BlueFlame 寄存器)→ NIC 立即开工
  (不需要 NIC 再 DMA 读 SQ)

适用场景:单 op 小延迟(比如延迟敏感 CAS)。但 BlueFlame 一次只能塞一个 WQE,所以对 batch 不友好——生产中一般 batch 走传统 SQ + doorbell,延迟敏感单 op 走 BlueFlame。

3.6 CQE 结构

NIC 完成 op 后,写一条 CQE 到 CQ ring buffer(host 内存):

CQE (mlx5 格式, 64B):
+--------+--------+--------+--------+--------+--------+----+
| qp_num | wr_id  | opcode | status | byte_  | imm_   |... │
| 4B     | 8B     | 1B     | 1B     |  len 4B|  data 4│   │
+--------+--------+--------+--------+--------+--------+----+
                   ↑         ↑
                  IBV_WC_*  IBV_WC_SUCCESS / 各种 error code

CQE 写完后,NIC 更新 CQ producer index,应用 poll 时检查这个 index 增加。

优化:NIC 可以合并 CQE 写入(几个 op 一次 DMA),减少 PCIe 流量。但这种合并对应用透明。

3.7 关键 SRAM 数据结构

NIC 内部维护的关键状态:

数据大小影响
QP context每 QP 几 KBQP 数量 > NIC 容量 → context cache miss → 性能腰斩
MR cache(TLB)几千-几万条MR/MTT miss → 多一次 host mem fetch
WQE 预取缓冲几 KB影响 batch 处理效率
CQ 缓冲几 KBCQE 写延迟影响 poll

ConnectX-6 单卡推荐:

  • QP 数 < 100K(超过会显著降速)
  • 同时活跃 MR < 几百个(每个 MR 对应若干 TLB 条目)
  • hugepage(2MB / 1GB)减少 TLB 压力

🌟 结论:NIC 不是”无状态网卡”,它内部有大量 cache 和 SRAM 状态。RDMA 性能优化的本质,是让 NIC 内部 cache 不 miss

4. QP 状态机:从 RESET 到 RTS

QP 不是一创建就能用,要走一套状态机:

   ┌─────────┐
   │  RESET  │  ibv_create_qp 后的初始态
   └────┬────┘
        │ ibv_modify_qp(IBV_QPS_INIT)
        │  - 设 PD, port_num, qkey, access_flags

   ┌─────────┐
   │  INIT   │  本地配置就绪
   └────┬────┘
        │ ibv_modify_qp(IBV_QPS_RTR)  Ready to Receive
        │  - 设 dest_qp_num, dest_lid/gid, path_mtu, rq_psn,
        │    max_dest_rd_atomic, min_rnr_timer, ah_attr
        │  - 此时本地 RX 通路就绪,可以收对方的包

   ┌─────────┐
   │   RTR   │  能收,但不能主动发
   └────┬────┘
        │ ibv_modify_qp(IBV_QPS_RTS)  Ready to Send
        │  - 设 sq_psn, max_rd_atomic, timeout, retry_cnt, rnr_retry

   ┌─────────┐
   │   RTS   │  完全工作(可发可收)
   └────┬────┘
        │ (发生错误)

   ┌─────────┐
   │   ERR   │  出错,需要 RESET 重建
   └─────────┘

4.1 关键字段:必须从对端拿到

为了从 INIT 进入 RTR,必须知道对端 QP 的几个信息:

  • dest_qp_num:对端 QP 号
  • dest_lid(IB)或 dest_gid(RoCE):对端网卡地址
  • psn(packet sequence number):双方序列号

这些信息必须通过带外通道交换(verbs 本身没有 connection setup 协议)。常见做法:

  • TCP/socket:小集群直接开个 TCP listen,exchange 完关
  • memcached:中型集群(CREST/Motor 用这个)
  • RDMA-CM:对应 CM(Connection Manager)的标准协议
  • etcd / consul:大型集群用服务发现

4.2 Memcached 交换协议(以 CREST 为例)

Node A (准备 QP):
   create_qp() → 拿到本地 qp_num, psn

   memcached SET "node_A_qp_info" = {qp_num, psn, gid}

   memcached GET "node_B_qp_info" → 拿到 B 的信息

   modify_qp(INIT → RTR) — 用 B 的信息

   modify_qp(RTR → RTS)

   memcached SET "node_A_ready" = 1

   等待 GET "node_B_ready" == 1

   开始通信

⚠️ 同步陷阱:如果 A 已经 RTS 但 B 还在 RTR,A 发的包会被 B 当作错误包丢弃。必须双方都到 RTS 之后再开始收发——这就是为什么需要 node_X_ready 这个二次同步。

4.3 错误恢复

QP 进入 ERR 状态后,必须 RESET 重建——不能直接从 ERR 跳回 RTS。重建步骤:

  1. ibv_modify_qp 到 RESET
  2. ibv_modify_qp 到 INIT
  3. 重新交换 QP 信息(双方都重建了,QPN 可能变)
  4. 走 INIT → RTR → RTS

🌟 生产经验:RDMA 应用必须有 QP 重建机制——长跑必然出错。Mooncake、Motor 等系统都有专门的 fault-tolerance 协议。

5. 传输模式:RC / UC / UD / XRC

QP 有四种传输模式,选错了直接吞掉性能或正确性:

模式全称可靠有连接支持 op代表用途
RCReliable Connected✅(NIC 自动重传)✅(1-1 配对)READ/WRITE/SEND/CAS/FAADM 事务、KV pool(95% 系统)
UCUnreliable ConnectedWRITE/SEND(不支持 READ/CAS!)NCCL 部分场景
UDUnreliable Datagram❌(无连接,可一对多)SEND/RECV(payload ≤ MTU)集群发现、广播
XRCeXtended RC同 RC共享 SRQ 的多进程场景

5.1 关键事实

  • one-sided READ 和 atomic op 只在 RC 上可用。所以做 disaggregated memory 几乎必走 RC——这一点没得选
  • UD 的 MTU 上限是网络 MTU(典型 4096 字节),大于 MTU 必须自己分片
  • UC 比 RC 快约 5-10%(省掉 ack 路径),但 NCCL 之外几乎不用
  • XRC 主要解决”M 个进程 × N 个进程”导致 QP 数爆炸的问题——多进程共享一个 SRQ,QP 数从 M×N 降到 M+N

🌟 结论:FORD/Motor/Mooncake/AdaptX 的 QP 全是 RC,这章后面所有讨论默认 RC。

5.2 RC 的 QP 爆炸问题

N 个 CN × M 个 MN 时,要建 N×M 条 QP(因为 RC 是一对一)。当 CN/MN 都很多时:

  • 每条 QP 至少占几 KB NIC SRAM
  • ConnectX-6 单卡能撑 ~100K QP,超过会触发 QP cache miss(性能腰斩)
  • 大集群(几百 CN × 几十 MN)就容易撞墙

缓解办法:

  • SRQ(Shared Receive Queue):多个 QP 共享一个 recv 队列,recv side 内存节省
  • XRC:一个 send QP 的对应,可以发给多个 receive XRC SRQ
  • DCT(Dynamically Connected Transport,Mellanox 私有):动态连接,QP 不再固定 1-1

生产经验:小集群(<32 节点)直接 RC + N×M;大集群(>100 节点)用 DCT 或 XRC。Mooncake 在大规模部署时用了 DCT。

6. one-sided vs two-sided:谁感知谁不感知

RDMA op 分两类:

类别op远端 CPU 参与典型用途
one-sidedREAD / WRITE / CAS / FAA❌ 不参与DM 事务、disaggregated KV、远端 hash 表查询
two-sidedSEND / RECV✅ 远端必须 post recvRPC、消息队列、connection setup

6.1 one-sided 四种 op

  • READ(rkey, addr, len):从远端读 len 字节到本地 buffer。远端无感知,无 CPU 参与
  • WRITE(rkey, addr, len):往远端写 len 字节。默认应用层无 ack(NIC 之间有 ack 保证可靠传输,但应用不感知)。变种 WRITE_WITH_IMM:写完顺带触发对端 CQE
  • CAS(rkey, addr, expected, new):8B 原子比较交换,远端无感知。这是 OCC 锁的关键
  • FAA(rkey, addr, value):8B fetch-and-add,远端无感知

6.2 two-sided 的代价

SEND/RECV 是消息语义:

// 接收方必须先 post recv,提供 buffer 给 NIC 准备落地
ibv_post_recv(qp, &recv_wr, &bad);

// 发送方
ibv_post_send(qp, &send_wr, &bad);
// → NIC 把 payload 直接 DMA 到接收方 pre-posted buffer
// → 接收方 CQ 收到 RECV completion(知道收到一条消息)

如果接收方没有可用 recv WR,发送方会触发 RNR(Receiver Not Ready) 错误,默认 NIC 会重试若干次后 abort。生产中常见做法:

  • 维护一个长 recv 队列(比如 1024 个 pre-posted recv buffer)
  • 每消费一个 recv 后立即 re-post(防止队空)
  • SRQ(Shared Receive Queue):多个 QP 共用一个 recv 队列

6.3 选择决策

🌟 结论:FORD 选 one-sided CAS 而不用 send/recv,是因为远端不需要任何 CPU/线程参与就能完成”获取锁 + 读数据”两步——这把 MN 从”另一个数据库”压成”一段裸内存”,disaggregation 才成立。

但 one-sided 也不是免费午餐:

  • 数据布局必须 NIC-friendly:远端 NIC 不会做指针追逐,所有元数据必须 inline 在 cache-line 内(FORD 的 cache-line lock 设计就是为这个)
  • 远端不能做计算:聚合、过滤这种”轻量计算”在远端 CPU 上几乎免费,但 one-sided 必须把全部数据拉过来 CN 做。这是 LOTUS 把锁迁回 CN 的动因
  • CAS 只能 8B:超过 8B 的原子操作没有,只能用”锁 + 读 + 改 + 写 + 解锁”组合实现
场景推荐 op理由
读远端 hash bucketREAD远端无负担
取锁CAS原子 + 远端无感知
提交事务计数FAA原子 + 不冲突
控制面信令(很少)SEND/RECV远端要解析消息
长 RPC 协议SEND/RECV消息边界天然
集群广播(发现服务)UD SEND一对多

7. Atomic op 的硬件实现:chip-level vs memory-level

DM 事务系统离不开 atomic op,理解硬件实现决定能不能正确预测瓶颈。这一节是被论文反复提及但很少展开的内容。

7.1 两种 atomic 实现路径

Chip-level atomic(NIC 内部完成):

   CN 发 CAS 请求 →
   NIC 收到 →
   NIC 自己读 host 内存(或 NIC SRAM cache)→
   NIC 内部比较 + 替换 →
   NIC 写回 host 内存(或 cache)→
   返回结果
  • NIC 整个原子操作期间锁住该 cache-line
  • 不经过 host CPU
  • 限制:host 端访问该 cache-line(CPU read/write)与 NIC atomic 的一致性如何保证?老硬件:不保证(不能混用)
  • 现代实现:NIC 与 CPU 走 PCIe coherent 协议(ATS/ATC + IOMMU),硬件保证一致

Memory-level atomic(原子操作下放到内存控制器):

   CN 发 CAS 请求 →
   NIC 收到 →
   NIC 通过 PCIe 发 atomic 请求给 host 内存控制器 →
   内存控制器原子完成 →
   返回结果
  • 走 PCIe atomic transaction(PCIe 3.0+ 支持)
  • 完全跟 CPU 一致(CPU 同时做 atomic 也安全)
  • 但 PCIe atomic 的硬件支持参差,某些 CPU+NIC 组合下会”软件 emulate”——直接崩到 1/100 性能

7.2 ConnectX 系列 atomic 演进

NIC 代际atomic 实现理论 IOPS 上限实际中文社区报告
ConnectX-3主要 chip-level~1 M ops/s~0.5-1M
ConnectX-4~1 M~1M
ConnectX-5chip-level + 部分硬件改进~1.5 M~1.5M(LOTUS 论文背景)
ConnectX-6/6-Dx大幅优化 + ATS support5-10 M5-10M(FORD/Motor 主要测试)
ConnectX-7进一步优化估计 15-20 M暂少公开数据

7.3 atomic op 的延迟构成

单个 8B CAS:
  CN 发起        ~0.1 µs (post + DMA fetch WQE)
  网络往返        ~1-2 µs (单 LAN, 100GbE)
  NIC atomic 单元处理 ~0.2-0.3 µs
  host 内存 read+write ~0.1-0.2 µs (cached)
  返回 CQE        ~0.1 µs
  ────────────────────
  总计            ~2-3 µs

vs 简单 8B READ:
  ~1.5-2 µs

CAS 比 READ 慢约 1 µs,主要在 atomic 单元串行化。

7.4 atomic-IOPS 上限的根因

为什么 ConnectX-6 是 5-10M?

  • NIC 内部 atomic 单元的处理流水线限制
  • atomic 期间 cache-line 锁住 → 同一 cache-line 的并发 atomic 不能并行(必须串行)
  • 不同 cache-line 可以并行,但全 NIC atomic 单元也有总数上限

热点单 cache-line 的 CAS 因为不能并行,实际上限会更低(单 cache-line 估计 1-2M)。这正是 FORD 把锁/版本/数据放同一 cache-line 后还会有 hotspot 问题的原因——hot record 仍卡在单 cache-line atomic 串行

7.5 工程含义

🧠 关键洞察:atomic-IOPS 不是均匀分布的——同一 cache-line 的 atomic 远低于跨 cache-line atomic 的总和。这意味着:

  • 均匀分散负载 → 接近 5-10M
  • hot key 集中 → 单 key 上限 ~1-2M(单 cache-line)
  • 超过这个上限就 retry storm

FORD/Motor 的 cache-line 对齐设计在 cold path 上有效,但 hot key 上仍受限——LOTUS / AdaptX 必须从协议层绕开。

优化路径:

  • Padding:把热点 record 的锁字段散开到不同 cache-line(代价:多次 RDMA op)
  • Lock striping:逻辑上一个锁,物理上 N 个 stripe,CN 选其中一个(代价:协议复杂)
  • Lease-based:不是每次都 CAS,先获取一段租约,租约期内独占(代价:租约管理)

8. Memory Registration 深挖:pin / IOMMU / NIC TLB

ibv_reg_mr 是最容易出问题的 verbs 之一,因为它牵涉三层硬件:OS 页表、IOMMU、NIC TLB。

8.1 注册的内部步骤

ibv_reg_mr(pd, addr, len, flags)

   ├─ Pin pages (mlock-style):防止 OS 换页,直到 dereg

   ├─ Get physical addresses for [addr, addr+len)

   ├─ Program IOMMU:让 NIC 能用 IOVA → 物理地址
   │   └─ 没设 iommu=pt 时,这一步可能跳过 / 错误

   ├─ Allocate NIC TLB entry:NIC 内部需缓存映射(MTT, Memory Translation Table)

   └─ Return (lkey, rkey)

8.2 NIC 的 MTT 机制

NIC 内部有一个 MTT(Memory Translation Table)——存”IOVA → host 物理地址”的映射。

NIC 处理 RDMA op:
   收到 (rkey, addr, len)

   用 rkey 查 MTT,得到对应物理地址段

   DMA 读/写

MTT 是 NIC 内部 SRAM 的稀缺资源:

  • ConnectX-6 大概有几万到几十万条 MTT entry
  • 一个 MR 可能占用很多 entry(取决于 page size)

8.3 Page size 的影响

  • 4 KB page:1 GB MR 占用 256K MTT entry → 容易超 NIC 容量
  • 2 MB hugepage:1 GB MR 占用 512 MTT entry → 节省 500×
  • 1 GB hugepage:1 GB MR 占用 1 entry → 节省 256K×

🌟 生产经验:所有大型 RDMA 系统都用 hugepage——FORD/Motor/Mooncake 全是。

# 启用 1GB hugepage
echo 32 | sudo tee /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages
mount -t hugetlbfs none /mnt/huge -o pagesize=1G

应用 mmap hugepage:

fd = open("/mnt/huge/buffer", O_CREAT | O_RDWR, 0666);
ftruncate(fd, size);
buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
mr = ibv_reg_mr(pd, buf, size, ACCESS_FLAGS);

8.4 为什么注册这么贵

  • Pin 大段内存 = 几十 ms(mlock 系统调用 + 内核遍历)
  • IOMMU 编程 = µs-ms(取决于硬件)
  • NIC MTT 编程 = µs/entry × N entry

总耗时:

  • 1 GB MR + 4KB page = 几秒(很慢!)
  • 1 GB MR + 1GB hugepage = ~ms

⚠️ 生产中绝不在 critical path 调 ibv_reg_mr——只在启动时做一次。

8.5 实战策略

策略 1:启动时 reg 大块,运行时分配

// 启动时:
mr = ibv_reg_mr(pd, big_buffer, 32 GB, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | ...);

// 运行时:用 sub-allocator 在 big_buffer 上切
void *obj = my_allocator.alloc(big_buffer, size);
// 用 mr->lkey 访问 obj 即可,无需新 reg

FORD/Motor/CREST 全部走这条路。

策略 2:On-Demand Paging (ODP)

新版 OFED 支持 ODP——MR 可以不立即 pin,运行时 NIC 触发 page fault 再 fetch。代价是首次访问慢、复杂度高,生产慎用

8.6 失败模式

⚠️ 三大经典坑:

  1. MR 大小超过物理 RAM → ibv_reg_mr 返回 ENOMEM,但更可能是先 OOM kill。生产中要先估准 mr_size(CREST 的 mr_size=32 配置就是这么来的)
  2. IOMMU 没设 passthrough → reg 返回成功,但 RDMA READ 拿到全 0 数据(WC 显示 SUCCESS)。这个是本系列踩过最阴险的坑——表面没错,数据全错
  3. rkey 泄露给不可信对端 → 远端可以用这个 rkey 任意读写你的 MR 范围。生产代码绝不在公网传 rkey,只在内网带外通道(memcached / tcp)交换

8.7 rkey 安全模型

rkey唯一的访问凭证——一旦泄露,持有者可在 MR 范围内任意读写。

攻击模型:
  攻击者拿到 rkey → 用任意 RDMA op 访问该 MR 范围
  → 没有进一步认证!

生产保护:

  • rkey 只在带外通道(TCP socket 加密、memcached 不暴露公网)交换
  • 用 IDE(Integrity & Data Encryption)加密 RDMA 流量(CXL 也用同样的 IDE)
  • 多租户隔离:每个租户独立 PD + 独立 rkey 域

9. Doorbell batching 与 inline:把 100ns 抠出来

NIC 触发一次 op 的本质是:应用写 NIC 的 doorbell 寄存器(MMIO),NIC DMA 读 work request → 执行 → 写 CQE。MMIO 写本身要 ~80-150 ns,如果一次只发一个 op,这部分占总延迟很可观。

9.1 Doorbell batching

一次写多个 WR 到本地内存(用 wr.next 链),然后只敲一次 doorbell,NIC 一次性处理整批。

struct ibv_send_wr wr1, wr2, wr3, *bad;
wr1.next = &wr2;
wr2.next = &wr3;
wr3.next = NULL;

// 一次 ibv_post_send,内部只敲一次 doorbell
ibv_post_send(qp, &wr1, &bad);

时序对比:

单个 op:        WR1 → ring → MMIO  →  NIC poll → DMA → ...      [80 ns + ...]
batch=8:   WR1..WR8 → ring → MMIO  →  NIC poll → DMA(8x) → ...  [80 ns + 8x 数据]

收益:批 8 时 doorbell 摊销到每个 op 只有 10 ns,延迟下降 ~30%、吞吐上升 5-10×

9.2 Inline data

对小写,把 payload 直接塞进 WR 描述符里(IBV_SEND_INLINE flag),NIC 拿到 WR 时数据已经在,省一次 DMA(~80 ns)。代价:WR 描述符变大,batch 内总字节数受限(典型 inline cap 220-256 字节,可在 QP 创建时配)。

wr.send_flags = IBV_SEND_INLINE | IBV_SEND_SIGNALED;
sge.addr = (uint64_t)small_payload;  // 8B 锁字、版本号
sge.length = 8;
// NIC 看到 INLINE,直接拷数据进 WR 描述符

9.3 Selective signaling

每个 SIGNALED op 都会写一条 CQE(花费 64B DMA + 应用 poll 时间)。对批量 op,只让最后一个 signaled,前面的 unsignaled:

for (int i = 0; i < N - 1; i++)
    wrs[i].send_flags = 0;          // unsignaled, 不产 CQE
wrs[N-1].send_flags = IBV_SEND_SIGNALED;  // 最后一个 signaled

应用只 poll 最后一个 CQE,前面的 op 完成隐含确认(RC 模式有序保证)。节省 N-1 次 CQE 写入和 poll——延迟敏感系统(FORD/Motor)的标配优化。

⚠️ 限制:unsignaled WR 必须有上限(默认 max_send_wr / 2),否则 NIC 内部 outstanding queue 满 → 后续 post 阻塞。生产中每 N 个 op 强制一次 signaled,N 通常 8-32。

9.4 综合效果

🌟 结论:FORD/Motor/CREST 这类系统的 commit phase 都是 doorbell-batched + inline + selective signaling 三件套混用——一个 commit 同时写 N 条 record 的版本号 + 锁,batch 后总延迟就是单 op 延迟而不是 N×。

优化单 op 延迟下降适用条件
Doorbell batch=8-30%(摊销 doorbell)批量 op,延迟可微容忍
Inline send-80 nspayload < 220 B
Selective signaling-50 ns(部分 op 不写 CQE)不需要每个都 poll
Hugepage MR-100 ns(减少 TLB miss)大 MR + 随机访问
Unreliable transport(UC/UD)-50 ns应用层有重试机制

⚠️ 失败模式:batch 太大会导致 head-of-line blocking——批内某个 op 失败时整批要重做,同时 CQ 写入也变粗粒度,影响延迟敏感场景。生产中 batch 大小一般 4-16,不超过 32。

10. CQ 轮询 vs 中断:延迟敏感系统的选择

收完成事件有两种模式:

10.1 Busy-poll(纯轮询)

while (1) {
    int n = ibv_poll_cq(cq, BATCH, wcs);
    for (int i = 0; i < n; i++) handle(wcs[i]);
}
  • ✅ 延迟最低(~100ns 拿到完成事件)
  • ✅ 无 syscall、无线程切换
  • CPU 100% 占用——一个 worker 线程绑一个核 spin
  • ❌ 多核 CPU 利用率难提升(每核都在 spin)

FORD/Motor/Mooncake/AdaptX 全是 busy-poll——延迟敏感,CPU 不省。

10.2 中断驱动 (Event channel)

ibv_req_notify_cq(cq, 0);  // 让 NIC 在下次 CQE 写入时发中断
poll(&pfd, 1, -1);          // 等中断
ibv_get_cq_event(channel, &ev_cq, &ctx);
ibv_poll_cq(cq, BATCH, wcs);
ibv_ack_cq_events(ev_cq, 1);
  • ✅ CPU 可让出
  • ❌ 唤醒延迟 ~5-10 µs(中断 + 调度 + 缓存冷)
  • 适合低 QPS、延迟可容忍场景(比如管理面、低频心跳)

10.3 混合模式

  • Adaptive polling:先 spin 一段时间(几十 µs),还没事件就降级到中断
  • NAPI-style:有事件时连续 poll 到队空,空闲时降级
  • 实现复杂,但能兼顾延迟与 CPU 利用率

🌟 生产经验:DM 事务和 LLM 推理的 critical path 几乎全 busy-poll,管理面和后台任务才用中断。

11. RoCE v1/v2 vs InfiniBand:选型决策

RDMA 协议族有三条:

              Application (libibverbs)

        ┌──────────────┴──────────────┐
        │                             │
   InfiniBand               RoCE (RDMA over Converged Ethernet)
   (专用网络)                   ├── RoCE v1: L2 over Ethernet (同 LAN)
                                └── RoCE v2: L3 over UDP/IP (可跨子网)

11.1 三者协议栈差异

维度InfiniBandRoCE v1RoCE v2
物理层IB switch + IB cable标准以太网标准以太网
链路层IB LRHEthernet + IB GRHEthernet
网络层IB GRHIB GRHUDP over IPv4/v6(端口 4791)
路由IB subnet managerL2 onlyL3(可跨 VLAN/子网)
拥塞控制IB credit-based(无 PFC 也工作)PFC 必须DCQCN(基于 ECN)+ PFC
部署复杂度高(专用 fabric)低-中中(以太网 + 拥塞配置)
大集群成熟度顶级(NVIDIA HPC)退场高(超大规模 AI 集群)
跨 DC 跨 region困难不行可以(L3 路由)
价格较贵(专用)(逐渐淘汰)便宜(以太网生态)

RoCE v1 已基本退场(L2 限制太大),实际选型只剩 IB 和 RoCE v2。

11.2 RoCE v2 的拥塞控制要点

RoCE v2 性能对拥塞控制配置极度敏感,配错的 RoCE v2 比配对的 IB 慢 5-10×

PFC(Priority Flow Control):链路层暂停帧。某段链路拥塞时,下游发 PAUSE 让上游停发。问题:

  • PFC storm:多跳传播,某节点慢传染整网
  • Head-of-Line blocking:暂停优先级 X 时,该优先级所有流量停
  • Deadlock:循环 PAUSE 路径形成死锁

DCQCN(Data Center QCN):基于 ECN 的端到端拥塞控制,降速发送方。配合 PFC 使用,目标是让 PFC 几乎不触发(只做 last-resort)。

🌟 配置经验(微软 RDMA over Converged Ethernet 经验):

  • ECN 阈值要小(K_min = 几 KB,K_max = 几十 KB)
  • DCQCN 速率降速参数 alpha = 1/16 起步
  • PFC 只在 RoCE 优先级队列上启用(不影响普通 TCP)
  • buffer 充足(交换机至少 16MB shared buffer)

11.3 GID 和 LID 的区别

  • LID(Local ID):InfiniBand 地址,16-bit,subnet manager 分配
  • GID(Global ID):128-bit(类似 IPv6),InfiniBand 跨子网或 RoCE 上用
  • gid_index:网卡上有多个 GID(对应不同 IP / VLAN),用 index 选哪个

⚠️ 常见配错:RoCE v2 用 InfiniBand 的 GID index(默认 0)→ 连接建不起。一般 RoCE v2 用 gid_index = 3(实际 IP 那个),需要 show_gids 工具确认。

11.4 选型决策

  • 同机柜 + 单租户 + 性能极致 → InfiniBand(微软 Azure HPC、阿里 PAI 大规模训练、HPC 集群)
  • 跨机柜 + 多租户 + 与现有以太网共存 → RoCE v2(Meta、字节超大规模 AI 集群主流)
  • AWS p5/Azure HBv4/GCP A3 → 都是定制化(EFA / IB / RoCE 自研变种)

CloudLab(本系列实验载体)给的是 RoCE v2 + ConnectX-6 Dx,主要因为这是大规模学术集群的事实标准。

12. NCCL 怎么用 RDMA:从 collective 到底层 verbs

NCCL(NVIDIA Collective Communication Library)是 GPU 训练用的集合通信库,提供 ncclAllReducencclAllGather 等高层接口。底层调用栈:

ncclAllReduce
   └─ Channel-based ring/tree algorithm
        └─ NCCL P2P transport
             ├─ NVLink (同节点 GPU 间, ~600 GB/s)
             ├─ PCIe (退化路径, ~30 GB/s)
             └─ NET plugin
                  └─ ibv_post_send (RDMA)
                       └─ ConnectX HW

12.1 关键设计

  • GPU Direct RDMA:NIC 直接 DMA 到 GPU HBM,绕过 host RAM(否则 GPU→DDR→NIC→对端 NIC→DDR→GPU,bandwidth 减半)。需 NIC + GPU 同一 PCIe root complex 或 NVLink 域
  • Ring vs Tree algorithm:Ring 在低节点数下更高效(单 hop 简单),Tree 在大规模(>32 GPU)对 reduce 更友好
  • 多 channel 并行:一次 AllReduce 拆成 N 个 channel 并行跑(N=2-16),各自走独立 QP,提升带宽并行度
  • bucket 攒梯度:不是每个 layer gradient 单独 allreduce,而是攒到 ~25MB bucket 再一次 allreduce(摊销启动开销)

12.2 内部通信骨架

某次 AllReduce 16MB tensor:
  → 切成 8 个 2MB chunk
  → 每个 chunk 通过 ring 一圈(N 节点 = N-1 hops × 2 = ~2(N-1) 通信)
  → 每个 hop = ibv_post_send 一个 chunk + ibv_poll_cq
  → 用 immediate data 传 chunk 序号

🍎 直觉比喻:NCCL 就像快递公司的”统一调度”,而你裸调 ibv 是”自己开车送”。统一调度对常见路线(标准 collective)很优,但你的特殊需求(比如 disaggregated KV 这种”多对一拉取”)它没有现成路由。

12.3 为什么 NCCL 在某些场景慢于裸 verbs

  • NCCL 的 ring 在拓扑不均(8 卡节点 + 跨机)情况下会按”最大公约数”切分,慢卡拖快卡
  • 裸 verbs 可以做应用层调度(如 Mooncake transfer engine 直接 ibv_post_send 多个独立 QP),不受 collective 抽象约束
  • NCCL 启动开销(channel 建立、bucket 攒)在小消息场景占比高
  • NCCL bucket 默认 25MB,小 tensor 等不齐 bucket → 延迟突增

12.4 选型经验

  • 标准模型训练(同构集群、AllReduce/AllGather 主导)→ NCCL,不要轮重造
  • 推理 KV-cache 跨节点搬运、推荐 embedding lookup、disaggregated 数据库 → 裸 verbs(通信模式不是 collective)
  • MoE 训练的 expert routing → 裸 verbs(每次目标节点不同,collective 不匹配)

13. 性能微基准:perftest 实战与数字解读

perftest(github.com/linux-rdma/perftest)是 RDMA 调优的”瑞士军刀”。

13.1 三个最常用工具

# Server 端(被测远端)
ib_read_bw -d mlx5_2 -i 1 -F --report_gbits

# Client 端(发起方)
ib_read_bw -d mlx5_2 -i 1 -F --report_gbits <server_ip>
工具测什么健康指标(ConnectX-6 100GbE)异常情况
ib_send_bwtwo-sided 带宽95-98 Gb/s<80 Gb/s → MTU 没改大 / PFC 配置
ib_read_bwone-sided READ 带宽90-95 Gb/s<70 Gb/s → 远端 PCIe 瓶颈 / IOMMU
ib_atomic_bwCAS/FAA IOPS5-10 M ops/s<2M → atomic 走 software emulation
ib_write_bwone-sided WRITE 带宽95-98 Gb/s<80 Gb/s → 同 send_bw
ib_send_lat单 op 延迟~1-2 µs>5 µs → 启用了中断 / 不是 polling
ib_atomic_latCAS 延迟~2 µs>10 µs → CAS 路径未硬件 offload

13.2 典型 ib_read_bw 输出解读

 #bytes     #iterations    BW peak[Gb/sec]    BW average[Gb/sec]    MsgRate[Mpps]
 65536      5000           94.83              94.65                 0.18
  • 65536 字节包,5000 次,平均 94.65 Gb/s(健康)
  • MsgRate 0.18 Mpps = 每秒 18 万次 op(64KB 包)
  • 注意:小包(64B)时 BW 会很低,但 MsgRate 高——衡量 IOPS 看 MsgRate

13.3 ib_atomic_bw 的特殊性

 #bytes     #iterations    BW peak[Mops/sec]
 8          1000000        7.32

ConnectX-6 atomic 上限 5-10 Mops/s。这是 DM 事务系统理论的 atomic-IOPS 天花板——ConnectX-5 时代是 ~1.5M(LOTUS 提锁分离的 motivation),ConnectX-6 提升一个数量级但仍是瓶颈。

13.4 ib_read_bw 略低于 send_bw 的原因

READ 是 CN 发请求 → MN 回数据(一次往返,数据回流方向占用入向带宽)。SEND 是单向(应用层无 ack 给 wire,只在协议层有最小确认),所以 SEND 比 READ 略快。

差距通常 1-3 Gb/s,不是 bug——这是协议本身的开销,不是配置问题。

13.5 实战微基准检查清单

新集群上线时,按顺序跑这些验证:

  1. ibv_devinfo -d mlx5_2:看 active_mtu, state PORT_ACTIVE, phys_state LINK_UP
  2. ib_send_bw:两节点跑,期望 95+ Gb/s
  3. ib_read_bw:期望 90+ Gb/s
  4. ib_atomic_bw:期望 5+ Mops
  5. ib_send_lat:期望 ~1-2 µs
  6. 多对节点测试(3-4 节点之间两两测,看是否一致)

任何一项明显异常,先别跑应用,先解决底层。

14. WC 错误码完全手册

ibv_wc.status 不是 SUCCESS 时,至少有 30+ 种错误——本节给一份诊断手册。

14.1 高频错误码表

错误码原因诊断方向
IBV_WC_SUCCESS成功(无)
IBV_WC_LOC_LEN_ERR本地长度错(SGE > MR 范围)检查 SGE.length 与 MR 边界
IBV_WC_LOC_QP_OP_ERR本地 QP op 错(QP 状态不对)QP 不是 RTS,或 op 不被该 QP 类型支持
IBV_WC_LOC_PROT_ERR本地保护错(lkey 错误 / PD 不匹配)检查 lkey + PD
IBV_WC_WR_FLUSH_ERR之前的 WR 失败,后续被刷找前面的真正错误
IBV_WC_MW_BIND_ERRMemory window 绑定错一般用不到
IBV_WC_BAD_RESP_ERR远端响应格式错NIC 间协议错 / 兼容问题
IBV_WC_LOC_ACCESS_ERR本地访问权限错MR 没有 LOCAL_WRITE access
IBV_WC_REM_INV_REQ_ERR远端拒绝 — 请求无效rkey 错 / 远端 QP 状态不对
IBV_WC_REM_ACCESS_ERR远端访问拒绝远端 MR 没有 REMOTE_READ/WRITE access
IBV_WC_REM_OP_ERR远端操作错远端 NIC 内部错
IBV_WC_RETRY_EXC_ERR重试超限网络断 / 远端 QP 进 ERR / 远端 NIC 拥塞
IBV_WC_RNR_RETRY_EXC_ERRRNR 重试超限远端没 post recv
IBV_WC_LOC_RDD_VIOL_ERRRDD 违反(RD 模式专属,常用 RC 不见)
IBV_WC_REM_INV_RD_REQ_ERR远端 RD 请求无效(同上)
IBV_WC_REM_ABORT_ERR远端中止远端取消了操作
IBV_WC_INV_EECN_ERREECN 错(RD 模式专属)
IBV_WC_INV_EEC_STATE_ERREEC 状态错(RD 模式专属)
IBV_WC_FATAL_ERRNIC 致命错NIC 进入 reset 状态,需要重启
IBV_WC_RESP_TIMEOUT_ERR响应超时网络 / 远端慢
IBV_WC_GENERAL_ERR通用错看 vendor_err 字段

14.2 三个最常见的诊断套路

套路 1:IBV_WC_RETRY_EXC_ERR (远端 QP 没起来)

  • 检查远端 QP 是否到 RTS
  • 检查双方 QPN/PSN 是否匹配
  • 检查 GID/LID 是否对(RoCE v2 用 gid_index=3 这种)
  • ibv_devinfo 看 phys_state

套路 2:IBV_WC_REM_ACCESS_ERR (远端权限错)

  • 检查 MR 注册时的 access_flags 是否包含 IBV_ACCESS_REMOTE_READ / WRITE / ATOMIC
  • 检查 rkey 是不是错的(可能传错 MR 的 rkey)

套路 3:IBV_WC_LOC_PROT_ERR (本地保护错)

  • 检查 SGE.lkey 与 MR.lkey 是否匹配
  • 检查 SGE.addr + length 是否在 MR 范围内
  • 检查 QP 与 MR 是否在同一 PD

14.3 vendor_err 字段

ibv_wc.vendor_err 是 NIC 厂商定义的扩展错误码,比 status 更细。Mellanox NIC 上可以查 mlx5 source code 或文档。

🌟 生产经验:遇到 RDMA 错误,先看 status,再看 vendor_err,最后用 perftest 回退到底层验证——按这个顺序大部分问题 30 分钟内能定位。

15. HW counters 与 NIC 性能诊断

NIC 暴露大量 hw_counters,理解它们是性能诊断的关键

15.1 Mellanox hw_counters 路径

# 路径
ls /sys/class/infiniband/mlx5_2/ports/1/hw_counters/

# 关键计数器:
rx_read_requests       # 入向 READ 请求数(MN 角度)
rx_write_requests      # 入向 WRITE 请求数
rx_atomic_requests     # 入向 atomic 请求数
out_of_buffer          # 入向但没 recv buf 的次数(RNR)
duplicate_request      # 收到重复请求(对端重传)

15.2 计数器读法

# 看每秒 IOPS
while true; do
    cur=$(cat /sys/class/infiniband/mlx5_2/ports/1/hw_counters/rx_read_requests)
    echo "$(date +%s) $cur"
    sleep 1
done

差分两次值即可得到每秒 IOPS。

15.3 AdaptX Loop B 的信号源

AdaptX Loop B 就是用这套机制——MN publisher 每 5ms 读 hw_counters,把 NIC IOPS publish 到 RDMA MR slot,CN 拉取后用作 actuator 决策。

15.4 PCIe counter 与 NIC

PCIe 层的瓶颈也能监控:

# 通过 sysfs 看 PCIe link
lspci -vvv -s <bdf>
# Look for: LnkCap (capability) vs LnkSta (current state)
# 如果 LnkSta < LnkCap → 链路 degraded

15.5 perfquery 工具

# 看 IB port counters
perfquery -P 1 -a

# 关键:
# PortXmitData / PortRcvData (字节)
# PortXmitDiscards (链路丢包)

🌟 生产经验:任何”为什么我的 RDMA 慢”问题,先把 hw_counters / perfquery 跑一遍——80% 的 case 在这里能看出端倪。

16. ConnectX 代际演进:5 → 6 → 7

DM 事务论文经常提”我们用的是 ConnectX-6”——理解代际差异,你才知道为什么 LOTUS 在 ConnectX-5 上才必要、FORD 在 ConnectX-6 才能跑到接近线速。

代际物理速率单卡 atomic IOPS单 op 延迟主要新特性典型论文
ConnectX-356 Gb/s~1 M~3 µs基本 RDMAFaRM era
ConnectX-4100 Gb/s~1 M~2.5 µs改进 cacheFaRM/早期 FORD 复现
ConnectX-5100 Gb/s~1.5 M~2 µs部分 atomic 优化LOTUS 论文背景
ConnectX-6 / 6 Dx100/200 Gb/s5-10 M~1.5-2 µsatomic 大幅优化、ATS、PCIe Gen4FORD/Motor 主要测试
ConnectX-7200/400 Gb/s估计 15-20 M~1-1.5 µsPCIe Gen5、更大 SRAM2026+ 论文起步
ConnectX-8 (ROADMAP)400/800 Gb/sTBDTBDTBD早期采样

16.1 关键演进点

  • ConnectX-5 → 6:atomic IOPS 5-10× 跃迁。这是 FORD 能跑到线速、AdaptX 5ms 反馈窗口可达的硬件前提
  • ConnectX-6 → 7:PCIe Gen4 → Gen5 翻倍,带宽友好;atomic 进一步优化但比例小
  • 每一代都加 SmartNIC 化:Bluefield-3 集成 ARM 核,部分逻辑可下放到 NIC

16.2 实战意义

读 RDMA 论文时,先看实验平台的 NIC 代际:

  • ConnectX-5 上的实验数字 → atomic IOPS 1.5M 是上限,出现 hotspot 几乎是必然
  • ConnectX-6 上的实验数字 → atomic IOPS 5-10M,但 hot key 上限仍是 ~1-2M(单 cache-line atomic 串行限制)
  • ConnectX-7 上的实验数字 → 数据更宽松,但 hot key 上限改善有限

🌟 判断一篇论文的”加速”是否真有意义:看它是不是在 hot regime 上改善——cold regime 上的”接近线速”在 ConnectX-6 上几乎是 free lunch。

17. 工程踩坑清单:十大新人陷阱

按踩坑频率排序:

  1. 用了 mlx5_0 不是 mlx5_2:control 网 1Gbps,实验网 100Gbps。所有性能问题第一时间检查 ib_dev_id
  2. IOMMU 没设 passthrough:RDMA READ 返回全 0 但 WC SUCCESS。/etc/default/grubiommu=pt + reboot
  3. MTU 太小:active_mtu 默认可能 256,带宽腰斩。改成 1024 或 4096
  4. MR 太大爆内存:mr_size=64 在 192GB 节点上 reg 不出来。先估准
  5. memcached 绑 127.0.0.1:CN 远程连不上。手动起 -l 0.0.0.0
  6. rkey 没在带外通道交换好:CN 不知道 MN 的 rkey 就发 op,触发 protection error
  7. CQ 不够大:默认 cq_size 太小,溢出后 op 丢。建 CQ 时给足大小(典型 65536)
  8. selective signaling 漏写最后一个 SIGNALED:unsignaled queue 满后 post_send 阻塞,看上去像死锁
  9. PD 跨进程共享假设错误:多进程要各自 ibv_alloc_pd,不能共享句柄
  10. 用 InfiniBand 模式跑 RoCE 网卡:gid_idx 设错(InfiniBand 用 0,RoCE v2 通常 1-3),连不通且报错信息晦涩

🌟 最重要的预防措施:新集群第一次跑前,把 §13 的 perftest 全过一遍——基础不通时跑应用是浪费时间。


✅ 自我检验清单

  • 两层 bypass:能解释 RDMA 相对 TCP 的两个根本优势,以及哪个对 DM 系统更关键
  • 四件套:能徒手画 QP/MR/CQ/PD 的关系
  • NIC 微架构:能讲清 WQE / CQE / Doorbell 三者的关系
  • QP 状态机:能默写 RESET → INIT → RTR → RTS 各阶段需要的字段
  • 传输模式:能解释为什么 DM 事务必须用 RC 而不是 UC
  • op 选择:面对一个新场景,能判断该用 READ / WRITE / CAS / send/recv
  • atomic 实现:能讲清 chip-level vs memory-level atomic 的差异
  • atomic-IOPS 上限:能讲清”单 cache-line atomic 串行”为什么 hot key 上限只 1-2M
  • MR 注册成本:能讲清”为什么要启动时一次 reg 大块” + “为什么用 hugepage”
  • doorbell batch:能算出 batch=8 相比 batch=1 的延迟和带宽收益
  • selective signaling:能解释为什么不是每个 op 都要 signaled
  • busy-poll vs 中断:能讲清两种模式的 trade-off 与适用场景
  • RoCE 选型:能讲清在什么条件下 RoCE v2 不如 InfiniBand
  • NCCL 调用栈:能从 ncclAllReduce 一路追到 ibv_post_send
  • perftest 解读:看一份 ib_read_bw 输出能识别瓶颈
  • WC 错误诊断:看到 IBV_WC_RETRY_EXC_ERR 能立即想到三个可能原因
  • ConnectX 代际:能讲清为什么 LOTUS 在 ConnectX-5 上做”锁分离”特别有意义
  • 十大坑:至少能默写其中 5 个并解释原因

📚 参考资料

概念入门

  • RDMA Aware Networks Programming User Manual —— Mellanox/NVIDIA:官方手册 —— libibverbs 编程权威说明
  • Anuj Kalia 个人主页 / CMU 系列论文 —— RDMA 系统研究综合入口
  • The Linux Kernel RDMA Subsystem —— driver 视角的 RDMA(对硬件最透彻)

关键论文

  • FaRM: Fast Remote Memory(Dragojević et al., NSDI’14):USENIX 链接 —— RDMA OCC 的奠基,所有后续 DM 事务系统的”祖宗”
  • Design Guidelines for High Performance RDMA Systems(Kalia et al., USENIX ATC’16):USENIX 链接 —— one-sided/two-sided 选择的实证文献,入门必读
  • FaSST: Fast, Scalable and Simple Distributed Transactions(Kalia et al., OSDI’16):USENIX 链接 —— two-sided RPC 反例的开创工作
  • Datacenter RDMA Networks(Mittal et al., SIGCOMM’18):RoCE v2 拥塞控制(DCQCN)实证
  • RDMA over Commodity Ethernet at Scale(Guo et al., SIGCOMM’16):微软 RoCE v2 大规模部署经验
  • HPCC: High Precision Congestion Control(Li et al., SIGCOMM’19):阿里超大规模 RDMA 拥塞控制
  • Lessons from RDMA / RoCE Operational Experience(各类 NSDI / SIGCOMM tutorials) —— 工程视角

行业讨论

  • Mellanox Bluefield SmartNIC 官方文档 —— SmartNIC 与 RDMA 的结合
  • Microsoft Azure RDMA 工程博客 —— 大规模生产经验
  • NVIDIA 开发者博客 RDMA 系列 —— 包括 GPU Direct、ConnectX 演进
  • The Linux Kernel Mailing List(linux-rdma) —— driver 层问题的最权威讨论

框架文档

硬件参考

  • ConnectX-6 Dx Adapter Card Datasheet —— 实测的具体型号
  • Mellanox mlx5 driver source(Linux kernel drivers/net/ethernet/mellanox/mlx5/) —— 看 doorbell / WQE 实际编码

下一章我们看 CXL 这条更年轻的路径,理解它如何把”远端”从网络压回总线。