nccl分析(二)——RDMA带外建链过程

前序

关于RDMA带外建联过程,博客[1]已经分析的很好了。本篇结合nccl的源码,分析RDMA的建链过程。

发送端和接收端的qp状态迁移流程:

text 复制代码
发送端 QP 迁移流程:

   RESET --[create]--> RESET --[modify INIT]--> INIT --[modify RTR]--> RTR --[modify RTS]--> RTS
                          ^                      ^                       ^
                          |                      |                       |
                    设置本地端口            提供远端地址、QPN       设置发送序列号、重试
                    P_Key、访问权限          MTU、GID/LID


接收端 QP 迁移流程(类似,但角色相反):

   RESET --[create]--> RESET --[modify INIT]--> INIT --[modify RTR]--> RTR --[modify RTS]--> RTS
                          ^                      ^                       ^
                          |                      |                       |
                    设置本地端口            使用发送端提供的         设置发送序列号
                    P_Key、访问权限          远端 QPN、地址           (准备发送 CTS)

发送端处理流程

QP 状态迁移总览(结合 NCCL 步骤)

text 复制代码
                 ncclIbQpCreate()
                        │
                        ▼
                  ┌──────────┐
                  │  RESET   │
                  └──────────┘
                        │
                        │ ncclIbQpInit()
                        │ (set port, pkey, access)
                        ▼
                  ┌──────────┐
                  │   INIT   │
                  └──────────┘
                        │
                        │ ncclIbQpRtr()
                        │ (提供 dest_qpn, ah_attr, MTU, ...)
                        ▼
                  ┌──────────┐
                  │   RTR    │  ← 可接收消息
                  └──────────┘
                        │
                        │ ncclIbQpRts()
                        │ (设置 psn, timeout, retry)
                        ▼
                  ┌──────────┐
                  │   RTS    │  ← 可发送/接收
                  └──────────┘

NCCL 流程中:
- RESET → INIT 在 ncclIbSenderQpsCreate 中完成(发送端)
- INIT → RTR → RTS 在 ncclIbSenderQpsToRts 中完成(收到对端元数据后)

ncclIbConnect 总体流程(发送端视角)

ncclIbConnect

text 复制代码
ncclIbConnect()
│
├─ 1. 解析句柄,获取远端监听地址
│   handle->connectAddr  →  ncclSocketInit()
│
├─ 2. 异步连接 Socket
│   ncclSocketConnect()  →  等待 ncclSocketReady()
│
├─ 3. 交换设备虚拟属性
│   ├─ 发送本地 ncclNetVDeviceProps_t + ncclIbDevExtraProps
│   ├─ 接收远端相同结构
│   └─ 计算最终 ndevs / nqps = max(local, remote)
│
├─ 4. 为每个物理设备初始化基础资源 (ncclIbInitCommDevBase)
│   ├─ 分配 PD (wrap_ibv_alloc_pd)
│   ├─ 创建 CQ (wrap_ibv_create_cq)
│   └─ 注册 MR: ctsFifoMr, cmplsRecordsMr, putSignalScratchpadMr
│
├─ 5. 创建并初始化 QP 至 INIT 状态
│   └─ ncclIbSenderQpsCreate()  → 见图2
│
├─ 6. 交换连接元数据 (ncclIbConnectionMetadata)
│   ├─ 发送本地元数据(QPN, GID, LID, MTU, ECE...)
│   └─ 接收远端元数据
│
├─ 7. 将 QP 迁移至 RTR → RTS 状态
│   └─ ncclIbSenderQpsToRts()  → 见图3
│
├─ 8. 最终握手 (发送 ready 标志)
│
└─ 9. 返回 sendComm

QP 创建与初始化至 INIT

ncclIbSenderQpsCreate

text 复制代码
ncclIbSenderQpsCreate()
│
├─ for each qpIndex in [0 .. nqps-1]
│   │
│   ├─ devIndex = qpIndex % ndevs      // 条带化分配
│   ├─ ibDev = ncclIbDevs[ devIndex ]
│   │
│   ├─ 准备创建属性 qpCreateAttrs
│   │   ├─ type = IBV_QPT_RC
│   │   ├─ max_send_wr = 2*NET_IB_MAX_REQUESTS
│   │   ├─ max_recv_wr = 0
│   │   ├─ cq, pd, qp_context
│   │   └─ oooRq = (localOooRq && remOooRq && MLX5)
│   │
│   ├─ ncclIbQpCreate()
│   │   └─ wrap_ibv_create_qp()  →  QP 状态 = RESET
│   │
│   ├─ 填充元数据:qpInfo[qpIndex].qpn, devIndex
│   │
│   ├─ ncclIbQpInit()   // RESET → INIT
│   │   ├─ ibv_qp_attr:
│   │   │   ├─ qp_state = IBV_QPS_INIT
│   │   │   ├─ pkey_index = ncclParamIbPkey()
│   │   │   ├─ port_num = ibDev->portNum
│   │   │   └─ qp_access_flags = IBV_ACCESS_REMOTE_WRITE
│   │   └─ wrap_ibv_modify_qp( , IBV_QP_STATE|IBV_QP_PKEY_INDEX|IBV_QP_PORT|IBV_QP_ACCESS_FLAGS)
│   │
│   └─ 若启用 ECE:
│       wrap_ibv_query_ece()  →  保存到本地 qpInfo[].ece, ece_supported
│
└─ 所有 QP 处于 INIT 状态,等待远端地址信息

QP 迁移至 RTR 与 RTS

ncclIbSenderQpsToRts

text 复制代码
ncclIbSenderQpsToRts(remMeta)
│
├─ for each qpIndex in [0 .. nqps-1]
│   │
│   ├─ 获取远端 QP 信息:remQpInfo = &remMeta->qpInfo[qpIndex]
│   ├─ 获取远端设备信息:remDevInfo = &remMeta->devs[remQpInfo->devIndex]
│   │
│   ├─ 若双方支持 ECE:
│   │   wrap_ibv_set_ece( , &remQpInfo->ece)   // 协商 ECE 参数
│   │
│   ├─ 1. 迁移至 RTR (ncclIbQpRtr)
│   │   ├─ 填充 ibv_qp_attr:
│   │   │   ├─ qp_state = IBV_QPS_RTR
│   │   │   ├─ path_mtu = min(本地MTU, 远端MTU)
│   │   │   ├─ dest_qp_num = remQpInfo->qpn
│   │   │   ├─ rq_psn = 0
│   │   │   ├─ max_dest_rd_atomic = 1
│   │   │   ├─ min_rnr_timer = 12
│   │   │   └─ ah_attr (地址属性)  → 见图4
│   │   └─ wrap_ibv_modify_qp( , IBV_QP_STATE|IBV_QP_AV|IBV_QP_PATH_MTU|IBV_QP_DEST_QPN|IBV_QP_RQ_PSN|... )
│   │
│   └─ 2. 迁移至 RTS (ncclIbQpRts)
│       ├─ 填充 ibv_qp_attr:
│       │   ├─ qp_state = IBV_QPS_RTS
│       │   ├─ sq_psn = 0
│       │   ├─ timeout = ncclParamIbTimeout()
│       │   ├─ retry_cnt = ncclParamIbRetryCnt()
│       │   ├─ rnr_retry = 7
│       │   └─ max_rd_atomic = 1
│       └─ wrap_ibv_modify_qp( , IBV_QP_STATE|IBV_QP_SQ_PSN|IBV_QP_TIMEOUT|IBV_QP_RETRY_CNT|IBV_QP_RNR_RETRY|IBV_QP_MAX_QP_RD_ATOMIC)
│
└─ 所有 QP 进入 RTS 状态,可以发送/接收数据

地址关联 -- ah_attr 填充细节(RTR 阶段)

ncclIbQpRtr

text 复制代码
构建 ah_attr (struct ibv_ah_attr)
│
├─ 基础设置
│   ├─ ah_attr.sl = remMeta->sl          // 服务等级
│   ├─ ah_attr.src_path_bits = 0
│   └─ ah_attr.port_num = rtrAttr->localIbPort
│
├─ 判断链路类型
│   │
│   ├─ 如果是 RoCE (linkLayer == IBV_LINK_LAYER_ETHERNET)
│   │   ├─ ah_attr.is_global = 1
│   │   ├─ ah_attr.grh.dgid = rtrAttr->remoteGid
│   │   ├─ ah_attr.grh.sgid_index = rtrAttr->localGidIndex
│   │   ├─ ah_attr.grh.hop_limit = 255
│   │   └─ ah_attr.grh.traffic_class = rtrAttr->tc  (DSCP)
│   │
│   └─ 如果是 InfiniBand
│       ├─ 比较子网前缀:
│       │   local_subnet = extract_subnet(localGid)
│       │   remote_subnet = extract_subnet(remoteGid)
│       │
│       ├─ 若 local_subnet == remote_subnet  (同一子网)
│       │   ├─ ah_attr.is_global = 0
│       │   └─ ah_attr.dlid = rtrAttr->remoteLid
│       │
│       └─ 若 local_subnet != remote_subnet  (跨子网)
│           ├─ ah_attr.is_global = 1
│           ├─ ah_attr.dlid = ncclIbExtractFlid(&remoteGid)  // FLID
│           ├─ ah_attr.grh.dgid = remoteGid
│           └─ ah_attr.grh.sgid_index = rtrAttr->localGidIndex
│
└─ 最终该 ah_attr 传递给 ibv_modify_qp 的 IBV_QP_AV 掩码

接收端处理流程

接收端 QP 状态迁移遵循 IB 规范:RESET → INIT → RTR → RTS。

ncclIbAccept 总体流程

ncclIbAccept

text 复制代码
ncclIbAccept(listenComm, &recvComm, ...)
│
├─ 1. 从 listenComm 获取监听 socket 和 stage
│   lComm->stage 用于保存握手状态
│
├─ 2. 接受 socket 连接 (异步)
│   ncclSocketInit() → ncclSocketAccept()
│   等待 ncclSocketReady()
│
├─ 3. 接收发送端设备虚拟属性
│   ├─ 接收 ncclNetVDeviceProps_t + ncclIbDevExtraProps
│   ├─ 本地虚拟设备与远端做交集 (ncclIbCheckVProps)
│   └─ 计算最终 ndevs / nqps = max(local, remote)
│
├─ 4. 回发本地设备虚拟属性
│   └─ 发送本地 ncclNetVDeviceProps_t + ncclIbDevExtraProps
│
├─ 5. 接收发送端连接元数据 (ncclIbConnectionMetadata)
│   └─ 获得远端 QPN、GID、LID、MTU、ECE、RKey 等
│
├─ 6. 初始化本地 IB 资源 (为每个物理设备)
│   ├─ 分配 PD (wrap_ibv_alloc_pd)
│   ├─ 创建 CQ (wrap_ibv_create_cq)
│   └─ 注册 MR: ctsFifoMr, cmplsRecordsMr
│
├─ 7. 创建并配置 QP 至 RTS 状态
│   └─ ncclIbReceiverQpsCreateToRts()  
│
├─ 8. 预投递接收 Work Request (可选)
│   └─ ncclIbReceiverPrePostReceiveWorkRequests()
│
├─ 9. 填充本地元数据并发送回发送端
│   ├─ meta.ndevs, meta.devs[].rkey, lid, gid, mtu...
│   └─ ncclSocketProgress() 发送
│
├─ 10. 等待发送端的 ready 标志
│
└─ 11. 返回 recvComm

接收端 QP 创建与配置

ncclIbReceiverQpsCreateToRts

text 复制代码
ncclIbReceiverQpsCreateToRts(remMeta, &meta)
│
├─ for each qpIndex in [0 .. nqps-1]
│   │
│   ├─ devIndex = qpIndex % ndevs          // 条带化分配
│   ├─ ibDev = ncclIbDevs[ devIndex ]
│   ├─ remQpInfo = &remMeta->qpInfo[qpIndex]
│   │
│   ├─ 1. 创建 QP (ncclIbQpCreate)
│   │   ├─ type = IBV_QPT_RC
│   │   ├─ max_recv_wr = NET_IB_MAX_REQUESTS
│   │   ├─ max_send_wr = 2*NET_IB_MAX_REQUESTS
│   │   └─ wrap_ibv_create_qp()  → QP 状态 RESET
│   │
│   ├─ 2. 迁移至 INIT 状态 (ncclIbQpInit)
│   │   └─ 设置 pkey_index, port_num, qp_access_flags
│   │
│   ├─ 3. 若远端支持 ECE,设置 ECE (wrap_ibv_set_ece)
│   │
│   ├─ 4. 迁移至 RTR 状态 (ncclIbQpRtr)
│   │   ├─ 填充 ah_attr:
│   │   │   ├─ 根据链路类型 (RoCE/IB) 设置 dgid/lid/sgid_index
│   │   │   └─ sl, port_num
│   │   ├─ dest_qp_num = remQpInfo->qpn
│   │   ├─ path_mtu = min(localMTU, remDevInfo->mtu)
│   │   └─ wrap_ibv_modify_qp()
│   │
│   ├─ 5. 迁移至 RTS 状态 (ncclIbQpRts)
│   │   ├─ timeout, retry_cnt, rnr_retry
│   │   └─ wrap_ibv_modify_qp()
│   │
│   ├─ 6. 查询协商后的 ECE (wrap_ibv_query_ece)
│   │   └─ 填充 meta.qpInfo[qpIndex].ece
│   │
│   └─ 7. 记录 QPN 到 meta
│
├─ 若 flushEnabled:
│   └─ 为每个设备额外创建用于 GPU Direct Flush 的 QP
│
└─ 所有 QP 进入 RTS 状态

总结

发送端在 RTR 阶段使用从接收端元数据中获得的 dest_qp_num 和 ah_attr。

接收端在 RTR 阶段使用从发送端元数据中获得的 dest_qp_num 和 ah_attr。

双方都需要知晓对端的 QPN 和 GID/LID 才能完成地址关联。

Reference

1\] [RDMA带外建联过程](https://zhuanlan.zhihu.com/p/655663006)

相关推荐
呉師傅1 小时前
将CD音频抓轨转换成MP3的两种方法【图文解释】
运维·服务器·网络·windows·电脑·音视频
一路往蓝-Anbo1 小时前
第一章:嵌入式TDD-环境搭建
网络·stm32·单片机·嵌入式硬件·tdd
H Journey2 小时前
TCP断开连接四次挥手
网络·tcp/ip·四次挥手
闲人编程2 小时前
Agent的安全边界:如何防止AI失控(对齐问题)
网络·python·ai·agent·权限·智能体·cai
2401_8734794010 小时前
运营活动被薅羊毛怎么防?用IP查询+设备指纹联动封堵漏洞
java·网络·tcp/ip·github
应用市场10 小时前
Android A/B 无缝更新机制深度剖析
android·网络
rosemary51211 小时前
SOME/IP初试
网络·网络协议·tcp/ip·someip
不知名的老吴11 小时前
认识Python网络套接字编程
网络