找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2151

积分

0

好友

285

主题
发表于 昨天 21:35 | 查看: 3| 回复: 0

在 RDMA(远程直接内存访问)协议栈中,GID(Global Identifier,全局标识符)是标志通信终点(Endpoint)的唯一逻辑身份标签。理解 GID 对于配置高性能计算集群,尤其是在 AI 训练等依赖 NCCL 进行集合通信的场景中至关重要。

RDMA 中 GID 的核心作用

GID 是一个 128 位(16 字节)的标识符,其格式遵循 IPv6 地址规范。无论是在 InfiniBand 还是 RoCE 中,它的作用可类比为网络通信中的“身份证”:

  • 端到端身份标识(Identity):GID 唯一标识了RDMA网卡(RNIC)上的一个物理或逻辑端口。它是 RDMA 传输层(Transport Layer)识别通信双方的终极依据。
  • 连接建立的基石(Connection Setup):在 RDMA 编程中,将 QP(队列对)从 INIT 状态修改为 RTR(准备接收)状态时,必须填入目的地的 GID(DGID)。没有 GID,网卡不知道该把数据发给谁。
  • 多路径与子网穿越(Global Routing):GID 最初设计是为了让 RDMA 流量能够跨越不同的子网(Subnet)。它定义了报文的全局起始点和终点。

GID 是如何生成的

参考内核 4.9 的代码(对于不支持 IB/RoCEv1 的设备来讲):

  1. GID0 始终由 MAC 地址生成。
  2. ibdev register 时,如果设备已经有 ipv4ipv6 地址,则会按照 IPv4 -> IPv6 的地址顺序生成相应的 GID。
  3. 如果 ibdev 设备注册时,没有 IPv4/IPv6 地址,则会后续由 OFED 子系统注册到 inetinet6 的事件回调来添加或者删除 IPv4 或者 IPv6 地址对应的 GID。

不过正常情况一般 VM 内部并没有配置 IPv6 地址,ifconfig 看到的 IPv6 地址,实际上是 IPv6 的本地链路地址,IPv6 的本地链路地址生成(addr_gen_mode),主要有两种方式:

  1. EUI64,根据设备 MAC 地址来确定接口 ID,是一些旧内核的默认模式,生成的 IPv6 本地链路地址和 MAC 地址存在对应关系(也即它们生成的 GID 条目是完全一致的)。
  2. stable-privacy,则根据设备及网络环境来确定一个随机接口 ID。

为了使 GID 能够固定,则需要保证 IPv6 本地链路地址采用 EUI64 的模式生成,通过 NetworkManager 关闭 stable-privacy

GID 在 RoCEv2 中的作用

在 RoCEv2 中,原本 InfiniBand 的三层报头(GRH)被标准的 UDP/IP 报头取代了。这导致 GID 的表现形式发生了“寄生”式的变化。

GID 与 IP 的映射(逻辑与物理的统一)

在 RoCEv2 中,GID 实际上就是 IP 地址的 128 位映射形式。

  • IPv4 场景:GID 采用 IPv4-Mapped 格式,即 ::ffff:<32位IPv4地址>
  • IPv6 场景:GID 直接等同于 IPv6 地址。
  • MAC 场景:在没有配置 IP 的情况下(或者在某些旧版 RoCEv1 兼容模式下),网卡会利用 MAC 地址生成一个链路本地 GID,即基于 MAC 地址的默认 GID (EUI-64)

意义:这使得 RDMA 能够利用现有的以太网路由基础设施(基于 IP 转发),同时保持 RDMA 传输层逻辑的连续性。

如下图所示,通过 show_gids 命令可以查看当前 RDMA 设备端口上的 GID 表:
GID表查询结果示例,显示IPv4地址172.31.0.10和192.168.31.0对应的GID条目

RDMA 应用(如 NCCL 或自定义程序)并不直接操作 128 位原始字节,而是使用 GID Index

  • 查看 GID 表:通过命令 show_gidsibv_devinfo -v 查看网卡当前的 GID Table。
  • 代码调用:开发者调用 ibv_query_gid(context, port_num, index, &gid) 获取特定索引下的 GID 值。
  • 选择策略:通常 Index 0 对应 MACIndex 12 对应 IPv4 (RoCEv2)。应用需要根据网络环境选择正确的 Index

硬件层面的自动封装 (Encapsulation)

既然 RoCEv2 可以通过 GID 进行 IP 地址的自动封装,那么肯定就需要将 GID 配置到硬件上。根据使用的连接模式(RC 或 UD),提供 GID Index 的方式分为两种:

1. 对于 RC (可靠连接) 模式:在 ibv_modify_qp 时预设

这是 NCCL 和绝大多数 AI 训练场景使用的模式。在这种模式下,不需要在每次 post_send 时指定 GID Index,而是在建立连接(握手)阶段,就把这个 QP(队列对)绑定到了特定的 GID Index 上。

具体流程:

  • 设置属性:在将 QP 状态修改为 RTR (Ready to Receive) 时,需要填充 ibv_qp_attr 结构体。
  • 配置 GRH:在 ibv_qp_attrah_attr(地址句柄属性)中,必须开启 is_global
  • 指定索引:将具体的 sgid_index 写入 grh 结构中。

代码示例:

struct ibv_qp_attr attr;
memset(&attr, 0, sizeof(attr));

attr.qp_state = IBV_QPS_RTR;
// ... 其他属性 (path_mtu, dest_qp_num等) ...

// 核心部分:在这里指定使用哪一个本地 GID
attr.ah_attr.is_global = 1;             // RoCEv2 必须开启
attr.ah_attr.grh.sgid_index = 2;        // 这里的 2 就是你要提供的 GID Index
attr.ah_attr.grh.dgid = remote_gid;     // 远端 GID
attr.ah_attr.grh.hop_limit = 64;

// 修改 QP 状态,此时硬件已经记住了这个 QP 发包时必须用索引为 2 的 GID
ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_AV | ...);

一旦 QP 进入 RTS 状态,后续调用 ibv_post_send(qp, ...) 时,硬件会自动从该 QP 的上下文中提取 sgid_index=2,并据此封装 IP 头。具体而言,可以执行如下操作:

  • 提取源 IP:根据 Index 拿到 128 位 GID,转换成 IPv4 或 IPv6,填入 IP 头部的 Source IP
  • 确定源 MAC:由于 GID 通常绑定在特定的以太网接口或 VLAN 上,网卡通过 Index 知道该用哪个 MAC 地址和 VLAN Tag 封装二层头部。
  • 计算 UDP 源端口:基于 QP 信息和 GID 信息进行哈希。

2. 对于 UD (不可靠数据报) 模式:通过 ibv_ah 实时引用

UD 模式类似于 UDP,一个 QP 可以发给不同的目的地。因此,GID Index 不能死锁在 QP 上,而是绑定在 AH (Address Handle,地址句柄) 对象中。具体流程:

  • 创建 AH:使用 ibv_create_ah 创建一个目的地对象,在创建时指定 sgid_index
  • 发送时引用:在 ibv_post_send 所需的 ibv_send_wr 结构体中,指定使用哪个 AH

代码示例:

// 1. 创建地址句柄时指定 GID Index
struct ibv_ah_attr ah_attr;
memset(&ah_attr, 0, sizeof(ah_attr));
ah_attr.is_global = 1;
ah_attr.grh.sgid_index = 1; // 绑定本地 GID 索引 1
ah_attr.grh.dgid = remote_gid;
struct ibv_ah* my_ah = ibv_create_ah(pd, &ah_attr);

// 2. 在发起发送请求时,关联这个 AH
struct ibv_send_wr wr;
memset(&wr, 0, sizeof(wr));
wr.wr.ud.ah = my_ah;        // 告诉硬件:发这个包时,去 my_ah 里看 GID Index
wr.wr.ud.remote_qpn = ...;
wr.wr.ud.remote_qkey = ...;

ibv_post_send(qp, &wr, &bad_wr);

硬件校验的“防火墙”

既然 RoCEv2 通过 GID 进行 IP 地址的封装,那么同样也会进行相关的校验。不过在 RoCEv2 的实际数据传输过程中,发送端和接收端对 GID 的校验逻辑是不对称的,且分为硬件表校验(NIC Level)和连接状态校验(QP Level)两个维度。

1. 发送端(源端 / Source):主要校验 SGID

发送端的网卡(RNIC)主要确保“我是合法的”,所以源端网卡会通过 sgid_index 从本地 GID 表查找,必须是有效的本地 GID 防止冒充或使用未配置的 IP/GID 发送数据。如果 Index 无效,硬件会直接返回错误给软件,报文根本发不出去。

由于发送端硬件无法预先知道远端 GID 是否真的存在,因此对于目的 GID 它直接从 QP(队列对)的属性(ah_attr)中读取预先设定的 DGID,并将其映射到外层 IP 头部的目的地址,不做有效性校验。

2. 接收端(目的端 / Destination):双重校验 (DGID & SGID)

接收端为了保证安全性和数据的准确投递,校验过程最为严格。首先,当报文到达网卡时,硬件解析出报文里的目的 IP(映射自 DGID),网卡会将该地址与其本地 GID Table 进行匹配。如果匹配成功说明这封“信”确实是给本机的,报文进入后续处理;否则匹配失败,网卡认为这是误发的流量或非法攻击,直接丢弃报文,且通常不会向源端返回任何响应。

除了 DGID 的校验,在接受端的传输层还会有连接验证(QP Level),即校验 SGID。网卡首先根据报文中的 Dest QPN(目的 QP 编号)找到内存中的 QP Context(QP 上下文),由于 QP 上下文里预先存储了该连接合法的 Remote GID(即在建连阶段绑定的源端 GID),硬件将报文里的 source IP 转换为 128 位的 SGID 与 QP 上下文中的 Remote GID 进行比对。如果不一致则触发 Transport RetryConnection Error

说明:在 UD 模式下,源 GID 的校验逻辑有所不同。UD 模式是“一对多”或“多对一”的,QP Context 中不会固定死一个 remote_gid。网卡在接收到报文后,会将报文中的源 IP(源 GID)和源 QPN 放在完成队列实体 (WC, Work Completion) 中交给软件。软件通过 Work Completion 中的 wc->slid / wc->src_qp 获知发送者信息。

驱动拥塞控制 (ECN/DCQCN)

RoCEv2 的拥塞控制算法(DCQCN)需要在 IP 层打上 ECN 标记。网卡生成这些带有 ECN 标记的应答报文(CNP)时,需要根据原始报文的源 GID(即源 IP)来确定回发路径。

GID 在 iWARP 中的作用

和 RoCEv2 不同,iWARP 是直接运行在 TCP/IP 协议栈之上的,它完全利用了 TCP 的可靠传输能力。因此,在 iWARP 中,GID 更多地是作为一种为了兼容 InfiniBand Verbs 软件接口而存在的“逻辑映射”。在 iWARP 中 GID 的存在主要是为了 verbs 接口的兼容,为了让同一套 RDMA 应用程序(如 NCCL, MPI)能在不同硬件(IB, RoCE, iWARP)上运行,iWARP 驱动程序必须提供一个 GID Table。在 iWARP 中,当你选择 gid_index=0 时,你实际上是在告诉 iWARP 驱动:“请使用我第一个网口 IP 对应的 TCP 连接进行通信”。它作为索引,帮助软件从多个本地 IP 地址中选择一个作为通信的源。下面对比 RoCEv2 阐述一下 iWARP 中 GID 的作用和异同。

相同点:统一的 Verbs 编程模型

无论是 RoCEv2 还是 iWARP,都必须支持标准的 libibverbs 库。在应用层,都要通过 ibv_query_gid 获取索引,并在 ibv_modify_qprdma_connect 中填入 gid_index。这使得一套分布式代码(如 NCCL)可以在不同的网卡硬件上无缝运行,而不需要修改地址处理逻辑。

不同点:硬件状态机的“灵魂”与“外壳”

  1. RoCEv2:GID 是 QP 的“指纹”,在 RoCEv2 中,网卡硬件虽然在发包时将其转换成了 IP,但在网卡内部状态机里,它依然以 GID 的形式存在。当 RoCEv2 报文到达时,网卡会解析出目的 IP 并去匹配 GID Table。如果 IP 匹配不上 GID 表中的任何一项,硬件会直接丢弃报文。因此在 RoCEv2 中,IP 就是 GID,GID 就是身份。
  2. iWARP:GID 是 TCP 的“索引”,在 iWARP 中,GID 仅仅是为了告诉驱动:“我想用这个本地 IP 建立 TCP 连接”,一旦 TCP 建立,后续的 RDMA 数据包封装完全遵循 TCP 逻辑,不再需要 GID 校验。iWARP 网卡在接收报文时,完全不关心 GID。它只关心这个报文是否属于一个已经通过 TCP 三次握手建立好的 TCP 连接。在 iWARP 中,连接(Connection)才是身份,GID 只是找到这个连接入口的索引。驱动程序根据提供的 gid_index 提取出对应的 IP,然后调用底层的 TCP 栈去发起 SYN 握手。因此,如果中间 NAT 修改了 IP 或端口,只要 TCP 协议栈能够正确处理这个转换(即 TCP 连接不断),iWARP 的 RDMA 传输就完全不受影响。因为 iWARP 硬件只认 TCP 流,不认 GID 身份。

GID 在 NCCL 中的用法

下面我们以 NCCL 中 P2P 通信时 GID 相关的用法为例更具体了解它的使用方式,NCCL 代码采用 tag: v2.23.4-1

1. GID 相关数据结构

ibv_gid 是 ib verbs 中的标准数据结构如下所示,可以看到 128 位的 gid 由两部分构成,分别是子网前缀和接口标识:
ibv_gid结构体定义代码截图

在 NCCL P2P 通信中,GID 相关数据结构还有如下两个:
NCCL中本地GID信息与设备连接元数据结构体定义代码截图

2. GID 的获取

ncclIbConnect 为例,其 GID 的获取过程如下:
NCCL中获取并打包本地GID信息的代码流程截图

具体是通过 ncclIbGetGidIndex 函数设置 GID 的,其中包括 IB 和 roce 两种情况,我们只看 roce 的情况。
RoCE模式下自动选择GID索引的代码逻辑截图

如果通过 NCCL_IB_GID_INDEX 参数指定的话,则直接使用参数指定的 idx,例如:

/usr/local/bin/mpirun --allow-run-as-root -np 16 -npernode 8 -H 172.20.20.144:8,172.20.20.148:8 \
        -x NCCL_IB_GID_INDEX=3 \...

如果不指定的话 NCCL_IB_GID_INDEX 默认值为 -1,则根据 roce 版本和 ADDR_FAMILY 选择 GID,具体是在 ncclUpdateGidIndex 函数中,它公共比较候选 GID 索引与当前最佳 GID 索引,如果候选更优则更新 *gidIndex
GID索引自动更新逻辑的核心代码截图

3. 之后通过 Socket 带外连接发送 ncclIbConnectionMetadata,交换 GID
NCCL连接元数据结构体定义,包含交换的GID信息

4. 最后使用收到的对端 GID 和本地 GID 调用 ncclIbRtrQp
NCCL中将QP状态修改为RTR时配置GID的代码逻辑截图

注意,后续 ncclIbIsend 发送函数本身不直接使用 GID,因为 GID 在 QP 连接建立阶段已经配置完成。如果你在部署大规模分布式系统时遇到了网络性能问题,深入理解 GID 的机制往往能帮你快速定位到配置错误的根源。




上一篇:脑机接口与神经调控技术:如何用非侵入式脑刺激修复大脑功能?
下一篇:Hutool 5.x 实战指南:提升Java开发效率的20个核心技巧
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-17 01:54 , Processed in 0.454482 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表