侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

AI 算力网络架构深度解析:从 Spine-Leaf 到 RDMA/RoCE 的实战设计

PacketForge
2026-08-14 / 0 评论 / 4 阅读 /

AI 算力网络架构深度解析:从 Spine-Leaf 到 RDMA/RoCE 的实战设计

本篇文章基于“模块三:AI 算力网络架构”扩展而来,目标是把一份学习大纲改写成一篇可落地、可动手、可面试的中文博客式深度文档。读完本文,你应该能独立完成 16 / 64 / 128 / 1024 卡 GPU 集群的 Spine-Leaf 端口计算,理解 RDMA/RoCE 数据包与代码形态,并能把 PFC / ECN / DCQCN 的协同关系讲清楚。

目录

  1. 写在前面:为什么 AI 网络必须“另起炉灶”?
  2. Spine-Leaf 架构:AI 集群的“高速公路网”
  3. GPU 互联基础:Scale-Up 与 Scale-Out
  4. RDMA 技术:让网卡“直通”内存
  5. GPU 集合通信:网络流量的“罪魁祸首”
  6. RDMA 拥塞控制:在“无损”与“低延迟”之间走钢丝
  7. 网络拓扑演进:Rail-Only、超节点与 CPO
  8. 实战练习
  9. 总结
  10. 参考与延伸阅读
  11. 模块验收清单

1. 写在前面:为什么 AI 网络必须“另起炉灶”?

1.1 传统数据中心网络:为“南北向”而生

在传统互联网数据中心里,绝大多数流量是南北向的:用户从公网进来,请求打到负载均衡,再落到 Web 服务器、缓存、数据库;服务器之间的横向访问当然也有,但带宽需求相对分散,突发性强,且对延迟的容忍度在毫秒级。

典型拓扑是“核心—汇聚—接入”三层架构:

  • 核心层:连接外部运营商与多个汇聚域。
  • 汇聚层:做策略、安全、路由汇总。
  • 接入层:接服务器 TOR(Top of Rack)交换机。

这种架构追求的是连通性、安全、多租户隔离,并不把“单条流带宽拉满”作为第一目标。

1.2 AI 算力网络:东西向流量的“海啸”

进入大模型训练时代后,网络的瓶颈被彻底暴露:

  • 流量方向翻转:训练过程中,梯度 AllReduce、参数同步、激活值交换等操作产生海量东西向流量。
  • 单流带宽极高:单张 GPU 的网络端口已经从 25G → 100G → 200G → 400G,向 800G/1.6T 演进。
  • 延迟敏感:分布式训练是同步过程,慢节点会拖累整个集群(straggler effect),微秒级延迟差异都会被放大。
  • 流量可预测但暴烈:集合通信往往是“全对全”或“全对一”的突发模式,网络必须在极短时间内承载多路并发大流。

1.3 关键差异一览表

维度传统数据中心AI 算力集群
主要流量方向南北向东西向
带宽需求中等(10G/25G 为主)极高(400G/800G/1.6T)
延迟要求毫秒级可接受微秒级敏感
流量模式突发性、多样性集合通信、可预测、并发大流
拓扑三层架构Spine-Leaf / Rail-Only / 多维 Torus
协议TCP/IP 为主RDMA / RoCE / InfiniBand
拥塞控制TCP 拥塞控制PFC / ECN / DCQCN

一句话总结:AI 算力网络把“网络”从数据中心的“配角”变成了“算力的延伸”,带宽、延迟、稳定性任何一项掉链子,都会直接折算成训练时间和电费。


2. Spine-Leaf 架构:AI 集群的“高速公路网”

2.1 什么是 Spine-Leaf

Spine-Leaf(脊柱—叶)是一种二层的 Clos 网络拓扑:

  • Leaf 交换机:直接连接服务器(GPU 节点),作为接入层,也常被称为 TOR(Top of Rack)。
  • Spine 交换机:连接所有 Leaf,作为核心层。
  • 全连接:每个 Leaf 都与每个 Spine 相连(Full Mesh)。
  • 固定跳数:任意两台服务器之间最多经过 Leaf → Spine → Leaf,共 3 跳;如果两台服务器挂在同一台 Leaf 下,则只经过 2 跳。

下面是一张典型的 2-Spine / 2-Leaf 拓扑示意:

graph TB
    S1[Spine-1] --- L1[Leaf-1]
    S1 --- L2[Leaf-2]
    S2[Spine-2] --- L1
    S2 --- L2
    L1 --- H1[Server A<br/>GPU0-7]
    L1 --- H2[Server B<br/>GPU8-15]
    L2 --- H3[Server C<br/>GPU16-23]
    L2 --- H4[Server D<br/>GPU24-31]

2.2 为什么 AI 集群偏爱 Spine-Leaf

  1. 低延迟且可预期:任意两节点最多 3 跳,没有三层架构中不可预测的长路径。
  2. 高带宽:多路径 ECMP(Equal-Cost Multi-Path)把流量分散到多条 Spine-Leaf 链路上。
  3. 好扩展:增加 Spine 可以扩展上行带宽;增加 Leaf 可以扩展服务器数量。
  4. 无阻塞潜力:通过合理的端口配比,可以让“下行总带宽 = 上行总带宽”,实现理论上的无阻塞网络。

2.3 收敛比、无阻塞与 ECMP

收敛比(Oversubscription Ratio) 是衡量 Leaf 上下行带宽匹配程度的核心指标:

收敛比 = 下行总带宽 / 上行总带宽
  • 如果收敛比 = 2:1,意味着 Leaf 的下行带宽是上行带宽的 2 倍,当所有下行端口同时向外发送流量时,上行链路会出现拥塞。
  • 如果收敛比 = 1:1,则称为无阻塞设计(Non-blocking),任意时刻 Leaf 的上行能力都不小于下行能力。

ECMP 则负责把同一个 Leaf-Spine 之间的多路流量哈希到不同 Spine 上,避免单条上行链路被撑爆。

flowchart LR
    L1[Leaf-1] -->|流 1| S1[Spine-1]
    L1 -->|流 2| S2[Spine-2]
    L1 -->|流 3| S3[Spine-3]
    S1 --> L2[Leaf-2]
    S2 --> L2
    S3 --> L2
面试常问:ECMP 只能做到“尽力均匀”。当集合通信产生大量长流 elephant flow 时,如果哈希冲突,仍然可能出现链路负载不均。此时可以考虑更高级的负载均衡(如 flowlet、packet spraying)或 Rail-Only 架构。

2.4 数据流全景:同 Leaf 与跨 Leaf

假设 GPU0 要给 GPU1 发一个 RDMA Write 报文,数据会怎么走?

场景 A:两台 GPU 在同一台 Leaf 下(2 跳)

sequenceDiagram
    participant G0 as GPU0
    participant L1 as Leaf-1
    participant G1 as GPU1
    G0->>L1: 200G 以太帧
    L1->>G1: 直接本地转发
    Note right of G1: 同 Leaf 通信:2 跳

场景 B:两台 GPU 在不同 Leaf 下(4 跳)

sequenceDiagram
    participant G0 as GPU0
    participant L1 as Leaf-1
    participant S as Spine-1
    participant L2 as Leaf-2
    participant G1 as GPU1
    G0->>L1: 200G 以太帧
    L1->>S: 查路由 / ECMP 选 Spine
    S->>L2: 转发到目的 Leaf
    L2->>G1: 到达目的 NIC
    Note right of G1: 跨 Leaf 通信:4 跳
注意:严格说“3 跳”是从交换机视角(Leaf → Spine → Leaf),但从端点视角,报文经过了 NIC → Leaf → Spine → Leaf → NIC,是 4 跳。面试时先确认对方问的是交换机跳数还是端到端跳数。

2.5 端口计算实战:用 Python 设计你的第一张网络

下面这段代码把“端口拆分(Breakout)”统一换算成目标 NIC 速率的等效逻辑端口,然后自动求解需要多少 Leaf、多少 Spine、多少光模块。代码可直接复制到 Python 解释器里运行。

import math

def design_spine_leaf(
    total_gpus,
    gpus_per_server=8,
    nic_speed_gbps=200,
    leaf_port_speed_gbps=400,
    leaf_ports=32,
    spine_port_speed_gbps=400,
    spine_ports=32,
    oversubscription=1.0,
):
    """
    设计一个 Spine-Leaf 网络。
    所有速率都按 nic_speed 折算成等效逻辑端口,便于处理 400G 拆 2x200G 等场景。
    oversubscription=1.0 表示无阻塞设计。
    """
    server_count = math.ceil(total_gpus / gpus_per_server)
    nic_count = total_gpus  # 默认每张 GPU 配一张 NIC

    # 一台 Leaf/Spine 交换机换算成多少个 NIC 等效端口
    leaf_logical_ports = leaf_ports * (leaf_port_speed_gbps / nic_speed_gbps)
    spine_logical_ports = spine_ports * (spine_port_speed_gbps / nic_speed_gbps)

    if not leaf_logical_ports.is_integer() or not spine_logical_ports.is_integer():
        raise ValueError("Leaf/Spine 端口速率必须是 NIC 速率的整数倍,否则 breakout 不规整")
    leaf_logical_ports = int(leaf_logical_ports)
    spine_logical_ports = int(spine_logical_ports)

    # 搜索最少 Leaf 数量:每 Leaf 的下行 + 上行不能超过总逻辑端口
    leaves = 1
    while True:
        down_per_leaf = math.ceil(nic_count / leaves)
        uplink_per_leaf = math.ceil(down_per_leaf / oversubscription)
        if down_per_leaf + uplink_per_leaf <= leaf_logical_ports:
            break
        leaves += 1
        if leaves > nic_count:
            raise ValueError("单台 Leaf 端口不足,无法完成目标收敛比")

    total_uplinks = leaves * uplink_per_leaf
    spines = math.ceil(total_uplinks / spine_logical_ports)

    actual_oversub = (
        (down_per_leaf * nic_speed_gbps) / (uplink_per_leaf * nic_speed_gbps)
        if uplink_per_leaf else float("inf")
    )

    result = {
        "GPU 总数": total_gpus,
        "每台服务器 GPU 数": gpus_per_server,
        "服务器数量": server_count,
        "NIC 总数": nic_count,
        "Leaf 数量": leaves,
        "每 Leaf 下行端口数": down_per_leaf,
        "每 Leaf 上行端口数": uplink_per_leaf,
        "Spine 数量": spines,
        "Leaf 等效逻辑端口": leaf_logical_ports,
        "Spine 等效逻辑端口": spine_logical_ports,
        "总上行端口数": total_uplinks,
        "目标收敛比": f"{oversubscription}:1",
        "实际收敛比": f"{actual_oversub:.2f}:1",
        "服务器到 Leaf 线缆数": nic_count,
        "Leaf 到 Spine 线缆数": total_uplinks,
        "光模块对数": nic_count + total_uplinks,
        "光模块总数(含两端)": (nic_count + total_uplinks) * 2,
    }
    return result


def print_design(d):
    print("\n" + "=" * 60)
    print(f"  {d['GPU 总数']} GPU Spine-Leaf 设计方案")
    print("=" * 60)
    for k, v in d.items():
        print(f"  {k:<20}: {v}")


# 典型场景:16 / 64 / 128 / 1024 GPU 无阻塞设计
for gpus in [16, 64, 128, 1024]:
    print_design(design_spine_leaf(gpus))

运行结果(节选)

============================================================
  16 GPU Spine-Leaf 设计方案
============================================================
  GPU 总数              : 16
  服务器数量            : 2
  NIC 总数              : 16
  Leaf 数量             : 1
  每 Leaf 下行端口数    : 16
  每 Leaf 上行端口数    : 16
  Spine 数量            : 1
  实际收敛比            : 1.00:1
  Leaf 到 Spine 线缆数  : 16

============================================================
  64 GPU Spine-Leaf 设计方案
============================================================
  GPU 总数              : 64
  服务器数量            : 8
  NIC 总数              : 64
  Leaf 数量             : 2
  每 Leaf 下行端口数    : 32
  每 Leaf 上行端口数    : 32
  Spine 数量            : 1
  实际收敛比            : 1.00:1

============================================================
  128 GPU Spine-Leaf 设计方案
============================================================
  GPU 总数              : 128
  服务器数量            : 16
  NIC 数量              : 128
  Leaf 数量             : 4
  每 Leaf 下行端口数    : 32
  每 Leaf 上行端口数    : 32
  Spine 数量            : 2
  实际收敛比            : 1.00:1

============================================================
  1024 GPU Spine-Leaf 设计方案
============================================================
  GPU 总数              : 1024
  服务器数量            : 128
  NIC 数量              : 1024
  Leaf 数量             : 32
  每 Leaf 下行端口数    : 32
  每 Leaf 上行端口数    : 32
  Spine 数量            : 16
  实际收敛比            : 1.00:1

16 GPU 案例的两种解法

面试中常出现“16 张 GPU 两两互联”的问题。错误答案是“买一台 16 口交换机就行”。单台交换机的问题在于:如果多对 GPU 同时满带宽通信,16 口交换机的共享交换容量和缓存可能成为瓶颈,且没有冗余路径。

正确思路是把它当作一个微型 Spine-Leaf:

  • 16 张 GPU → 2 台服务器,共 16 个 200G NIC。
  • 用 1 台 32x400G Leaf,拆出 64 个 200G 逻辑端口。
  • 16 个下行接 GPU,16 个上行接 Spine(或同一台设备做 Collapsed Spine)。
  • 收敛比 1:1,无阻塞。

当然,如果预算有限、规模极小,也可以直接用一台高端 32 口交换机把 16 个 GPU 全接在一层,但要意识到这已经退化成“单层 Fat-Tree”,不是严格意义上的 Spine-Leaf。

2.6 数据流模拟:报文到底走了哪条路?

有了端口数量,我们还可以模拟任意两个 GPU 之间通信时经过的路径。下面这段代码演示了如何根据 GPU 编号计算 Leaf、Spine 选择,并区分“同 Leaf 2 跳”与“跨 Leaf 4 跳”。

def path_trace(src_gpu, dst_gpu, gpus_per_leaf, spine_count):
    """
    模拟 Spine-Leaf 网络中的端到端路径。
    spine 选择采用简化 ECMP 哈希:实际交换机通常基于 5 元组或报文字段。
    """
    src_leaf = src_gpu // gpus_per_leaf
    dst_leaf = dst_gpu // gpus_per_leaf

    if src_leaf == dst_leaf:
        return f"GPU{src_gpu} -> Leaf{src_leaf} -> GPU{dst_gpu}  (同 Leaf,2 跳)"

    # 简化哈希:实际网络中 spine 选择由交换机根据 flow key 决定
    spine = ((src_gpu * 31) + dst_gpu) % spine_count
    return (
        f"GPU{src_gpu} -> Leaf{src_leaf} -> Spine{spine} -> "
        f"Leaf{dst_leaf} -> GPU{dst_gpu}  (跨 Leaf,4 跳)"
    )


# 以 64 GPU、2 Leaf、1 Spine 的设计为例
gpus_per_leaf = 32
spine_count = 1
print(path_trace(0, 31, gpus_per_leaf, spine_count))   # 同 Leaf
print(path_trace(0, 32, gpus_per_leaf, spine_count))   # 跨 Leaf
print(path_trace(7, 56, gpus_per_leaf, spine_count))   # 跨 Leaf

输出:

GPU0 -> Leaf0 -> GPU31  (同 Leaf,2 跳)
GPU0 -> Leaf0 -> Spine0 -> Leaf1 -> GPU32  (跨 Leaf,4 跳)
GPU7 -> Leaf0 -> Spine0 -> Leaf1 -> GPU56  (跨 Leaf,4 跳)

思考题

如果 1024 GPU 场景下某条流的 5 元组哈希恰好让所有跨 Leaf 流量都走 Spine0,而 Spine1 空闲,会出现什么问题?应如何解决?

提示:ECMP 极化(Polarization)会导致链路利用率不均;现代交换机通常使用 resilient hashing、flowlet 或更随机的哈希算法来缓解。

3. GPU 互联基础:Scale-Up 与 Scale-Out

3.1 Scale-Up:单机内的“超高速总线”

Scale-Up 指在单台服务器内部增加 GPU 数量,典型代表是 NVIDIA 的 NVLink 和 NVSwitch:

  • NVLink:高带宽点对点互连,延迟极低,带宽远超 PCIe。
  • NVSwitch:一块专门用于 GPU 全互联的交换芯片,可以让 8 张 GPU 在单机内实现全连接。

单机 8 卡 A100/H100 服务器通常就是 8 GPU 通过 NVSwitch 全互联,再通过 PCIe 连到 CPU 和 NIC。

3.2 Scale-Out:跨节点的网络战场

Scale-Out 指多节点之间通过网络互联。它的上限是几千、几万张 GPU,但网络成为主要瓶颈:

  • 节点间通信走 NIC + 交换机。
  • 延迟从 NVLink 的亚微秒级跳到网络的几微秒甚至几十微秒。
  • 带宽受限于端口速率和网络拓扑收敛比。

3.3 PCIe 的角色

GPU、NIC、CPU 之间通过 PCIe 连接。现代服务器使用 PCIe 5.0,单 lane 速率 32 GT/s,x16 双向带宽约 64 GB/s。如果 NIC 是 200Gbps(约 25 GB/s),单 x16 PCIe 5.0 足够;但如果是 400Gbps 多卡并发,就需要关注 PCIe 带宽是否会成为瓶颈。


4. RDMA 技术:让网卡“直通”内存

4.1 RDMA 核心原理

RDMA(Remote Direct Memory Access,远程直接内存访问)允许一台主机的网卡直接读写另一台主机的内存,无需操作系统内核和 CPU 参与数据拷贝。它的核心收益有三点:

  1. 低延迟:绕过内核协议栈,端到端延迟可低至 1~2 µs(RoCE)或亚微秒(InfiniBand)。
  2. 高吞吐:接近线速,CPU 占用极低。
  3. 零拷贝:数据从用户态缓冲区直接 DMA 到网卡,避免内核态与用户态之间的多次拷贝。
flowchart LR
    subgraph 发送端
        APP1[应用程序]
        VERBS1[libibverbs 用户态库]
        RNIC1[RDMA SmartNIC]
    end
    APP1 -->|1. post send/recv WQE| VERBS1
    VERBS1 -->|2. 写入发送队列| RNIC1
    RNIC1 -->|3. Ethernet/IP/UDP/BTH| NET[网络]
    NET -->|4. 收到报文| RNIC2
    subgraph 接收端
        RNIC2[RDMA SmartNIC]
        MEM2[远端内存]
    end
    RNIC2 -->|5. DMA 直写| MEM2
    RNIC2 -->|6. CQE 通知| APP2[应用程序]

4.2 RDMA 技术路线对比

技术网络基础特点适用场景
InfiniBand专用网络性能确定性强、超低延迟、成本高超算、大型训练集群
RoCE v1以太网二层需要无损以太网,不能跨三层早期小型集群
RoCE v2UDP/IP可跨子网,兼容性好,企业级主流企业级 AI 集群
iWARPTCP/IP兼容性好但性能最差存储网络
目前新集群选型基本在 InfiniBand 和 RoCE v2 之间做权衡:追求极致性能选 IB,追求成本与可运维性选 RoCE v2。

4.3 InfiniBand

InfiniBand 是专为 HPC 和 AI 设计的网络技术:

  • 拥有独立的链路层、网络层和传输层协议。
  • 原生支持 RDMA,网卡称为 HCA(Host Channel Adapter)。
  • 由 Subnet Manager(SM) 集中管理子网内所有路由、QoS 和分区。
  • 主要供应商:NVIDIA Quantum/Mellanox。

4.4 RoCE v2:以太网上的 RDMA

RoCE v2 把 RDMA 封装在 UDP/IP 之上,报文结构如下:

flowchart LR
    ETH[Ethernet Header<br/>14+4 B VLAN]
    IP[IPv4/IPv6 Header]
    UDP[UDP Header<br/>dport=4791]
    BTH[IB Base Transport Header<br/>BTH 12 B]
    PAYLOAD[IB Payload]
    ICRC[ICRC 4 B]
    ETH --> IP --> UDP --> BTH --> PAYLOAD --> ICRC

关键字段:

  • UDP 目的端口 4791:RoCE v2 的 well-known port。
  • BTH(Base Transport Header):InfiniBand 传输层头部,包含操作码、 destination QP、PSN 等。
  • GRH(Global Route Header):在 IPv6 或跨子网场景下用于路由,长度 40 B。
  • ICRC(Invariant CRC):用于校验 invariant 字段,保证头部完整性。

RoCE v2 的优势:

  • 可以复用现网以太网交换机(只要支持 PFC/ECN)。
  • 可以跨三层路由,扩展性更好。
  • 成本显著低于 InfiniBand。

RoCE v2 的挑战:

  • 必须精心配置无损以太网(PFC)和拥塞通知(ECN/DCQCN)。
  • 性能一致性不如 InfiniBand,对交换机缓存和调度敏感。
  • 需要网卡、交换机、操作系统参数三端对齐。

4.5 iWARP

iWARP 把 RDMA 跑在 TCP/IP 上,依赖 TCP 的可靠传输。它的好处是兼容现有 IP 网络,但 TCP 的慢启动、拥塞控制、三次握手让它在 AI 训练这种低延迟高并发场景下竞争力很弱,目前主要见于存储网络。

4.6 RoCE v2 代码与测试实战

4.6.1 环境检查

# 列出 RDMA 设备
ibstat
rdma link

# 查看某个端口的 RoCE 版本与状态
cat /sys/class/infiniband/mlx5_0/ports/1/gid_attrs/roce_version

4.6.2 使用 perftest 做带宽与延迟测试

perftest 是 RDMA 社区最常用的基准测试套件,几乎所有 RDMA 网卡都支持。

# ========== Server 端 ==========
ib_write_bw -d mlx5_0 -R --report_gbits --duration=30

# ========== Client 端 ==========
ib_write_bw -d mlx5_0 192.168.10.10 -R --report_gbits --duration=30

常用参数说明:

  • -d mlx5_0:指定 RDMA 设备。
  • -R:使用 RoCE(RDMA over Converged Ethernet)。
  • --report_gbits:以 Gbps 为单位输出。
  • --duration=30:跑 30 秒取平均。

延迟测试:

# Server
ib_write_lat -d mlx5_0 -R

# Client
ib_write_lat -d mlx5_0 192.168.10.10 -R

4.6.3 libibverbs 最小可运行骨架

下面是一段使用 libibverbs 的 C 语言骨架,展示了如何创建 Protection Domain、Queue Pair、Memory Region,并发起一次 Send 操作。实际工程还需要通过 TCP/UDP 交换 QP 信息(QPN、PSN、GID、RKey 等)。

#include <infiniband/verbs.h>
#include <stdio.h>
#include <string.h>

#define BUF_SIZE 4096
#define CQ_ENTRIES 16

int main() {
    // 1. 获取并打开 RDMA 设备
    struct ibv_device **dev_list = ibv_get_device_list(NULL);
    if (!dev_list || !dev_list[0]) {
        fprintf(stderr, "No RDMA device found\n");
        return 1;
    }
    struct ibv_context *ctx = ibv_open_device(dev_list[0]);

    // 2. 创建保护域和完成队列
    struct ibv_pd *pd = ibv_alloc_pd(ctx);
    struct ibv_cq *cq = ibv_create_cq(ctx, CQ_ENTRIES, NULL, NULL, 0);

    // 3. 创建 Queue Pair(RC = Reliable Connected)
    struct ibv_qp_init_attr qp_attr = {0};
    qp_attr.send_cq = cq;
    qp_attr.recv_cq = cq;
    qp_attr.qp_type = IBV_QPT_RC;      // 可靠连接,类似 TCP
    qp_attr.cap.max_send_wr = 1;
    qp_attr.cap.max_recv_wr = 1;
    qp_attr.cap.max_send_sge = 1;
    qp_attr.cap.max_recv_sge = 1;
    struct ibv_qp *qp = ibv_create_qp(pd, &qp_attr);

    // 4. 注册内存区域(MR)
    char buf[BUF_SIZE];
    memset(buf, 'A', BUF_SIZE);
    struct ibv_mr *mr = ibv_reg_mr(
        pd, buf, BUF_SIZE,
        IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE
    );

    // 5. 通过 TCP/UDP 与对端交换 QP 信息后,把 QP 状态改为 RTR/RTS
    //    modify_qp_to_rtr(qp, remote_qpn, remote_gid, ...);
    //    modify_qp_to_rts(qp, ...);

    // 6. 组装 Send WR 并下发
    struct ibv_sge sge = {
        .addr   = (uint64_t)buf,
        .length = BUF_SIZE,
        .lkey   = mr->lkey,
    };
    struct ibv_send_wr wr = {0};
    wr.opcode = IBV_WR_SEND;   // 也可以是 IBV_WR_RDMA_WRITE
    wr.num_sge = 1;
    wr.sg_list = &sge;
    wr.send_flags = IBV_SEND_SIGNALED;

    struct ibv_send_wr *bad_wr = NULL;
    int ret = ibv_post_send(qp, &wr, &bad_wr);
    if (ret != 0) {
        fprintf(stderr, "ibv_post_send failed: %d\n", ret);
        return 1;
    }

    // 7. 轮询完成队列 CQE
    struct ibv_wc wc;
    do {
        ret = ibv_poll_cq(cq, 1, &wc);
    } while (ret == 0);

    if (wc.status == IBV_WC_SUCCESS) {
        printf("RDMA Send completed successfully\n");
    } else {
        fprintf(stderr, "RDMA Send failed: %s\n", ibv_wc_status_str(wc.status));
    }

    // 8. 清理资源
    ibv_dereg_mr(mr);
    ibv_destroy_qp(qp);
    ibv_destroy_cq(cq);
    ibv_dealloc_pd(pd);
    ibv_close_device(ctx);
    ibv_free_device_list(dev_list);
    return 0;
}

编译命令:

gcc rdma_send.c -o rdma_send -libverbs
注意:这段代码没有包含 QP 状态机转换(INIT → RTR → RTS)和地址交换逻辑,不能直接跑通。但它完整展示了 RDMA 编程的核心对象:Device、PD、CQ、QP、MR、SGE、WR。理解这 7 个对象,RDMA 代码就入门了一半。

4.7 RDMA Write / Read 与 Send/Receive 的区别

  • Send/Recv:接收端必须提前 post receive WR,消耗接收端 CPU 参与“接收动作”。
  • RDMA Write:发送端直接写入远端已注册的内存,接收端无需 CPU 参与。
  • RDMA Read:发送端直接读取远端内存。

在 AI 训练框架(如 NCCL)中,通常使用 RDMA Write + 自旋 poll 的方式做 GPU 间通信,以获得最低延迟。


5. GPU 集合通信:网络流量的“罪魁祸首”

5.1 常见集合通信操作

在分布式训练中,GPU 之间需要频繁进行集合通信:

操作说明
Broadcast一个节点发送数据给所有节点
AllGather所有节点互相收集数据
Reduce所有节点数据归约到一个节点
AllReduce所有节点互相归约并分发结果
ReduceScatter归约后分散到各节点
AlltoAll所有节点互相交换数据

其中 AllReduce 是训练中最频繁、最吃带宽的操作。理解 AllReduce 对网络设计至关重要。

5.2 AllReduce 算法深度解析

Ring AllReduce

Ring AllReduce 把所有 GPU 排成一个环,分两步:

  1. Scatter-Reduce:每个 GPU 把自己的梯度切成 N 份,沿环传递并累加。N-1 轮后,每个 GPU 拥有一份完整的归约结果。
  2. AllGather:每个 GPU 把归约结果沿环再传一圈,N-1 轮后所有 GPU 都拥有完整结果。
graph LR
    G0[GPU0] --> G1[GPU1]
    G1 --> G2[GPU2]
    G2 --> G3[GPU3]
    G3 --> G0
sequenceDiagram
    participant G0 as GPU0
    participant G1 as GPU1
    participant G2 as GPU2
    participant G3 as GPU3
    Note over G0,G3: Phase 1: Scatter-Reduce
    loop N-1 rounds
        G0->>G1: chunk 0
        G1->>G2: chunk 1
        G2->>G3: chunk 2
        G3->>G0: chunk 3
    end
    Note over G0,G3: Phase 2: AllGather
    loop N-1 rounds
        G0->>G1: aggregated chunk
        G1->>G2: aggregated chunk
        G2->>G3: aggregated chunk
        G3->>G0: aggregated chunk
    end

优点:通信总量与节点数无关(总通信量固定为 2(N-1)/N × 数据量),适合大规模集群。
缺点:延迟随节点数线性增长,节点多时首 token 到达慢。

Tree AllReduce

Tree AllReduce 把 GPU 组织成树形结构,先做 Reduce 到根节点,再由根节点 Broadcast 回去。

优点:延迟更低,适合小集群或对延迟敏感的场景。
缺点:根节点容易成为瓶颈,需要多级树或双树平衡。

2D-Torus / 3D-Torus

超大规模集群(万卡以上)会把 GPU 组织成 2D 或 3D Torus。每个 GPU 只和相邻 GPU 通信,利用多维拓扑降低跳数和拥塞。

5.3 流量模型对网络设计的影响

  • AllReduce 会产生大量东西向突发流量。
  • 网络必须支持高带宽、低延迟、低抖动。
  • 拥塞控制尤为重要,否则训练效率会大幅下降。
  • Rail-Only 等新型拓扑正是为了优化 AllReduce 流量路径而诞生的。

6. RDMA 拥塞控制:在“无损”与“低延迟”之间走钢丝

RoCE v2 跑在以太网上,但以太网传统上是“有损”网络:交换机缓存满了就丢包。TCP 可以通过重传恢复,但 RDMA 如果丢包,性能会断崖式下跌。因此必须引入无损以太网和端到端拥塞控制。

6.1 PFC(Priority-based Flow Control)

PFC 是 IEEE 802.1Qbb 标准,基于 802.1p 优先级做逐跳流量控制:

  • 当接收端缓存低于阈值时,向上游发送 PAUSE 帧,让上游暂停发送该优先级的流量。
  • 缓存恢复后再发 XON(Resume)帧。
  • 可以实现“无损以太网”。

风险:配置不当会导致拥塞蔓延(PFC Storm),形成全网反压,甚至死锁。

flowchart LR
    A[发送端] -->|数据| B[交换机]
    B -->|缓存不足| C[发送 PAUSE 帧]
    C --> A
    A -->|暂停发送| A

6.2 ECN(Explicit Congestion Notification)

ECN 是 IP 层的拥塞通知机制(RFC 3168):

  • 交换机检测到拥塞时,在 IP 头部的 ECN 字段 标记 CE(Congestion Experienced)。
  • 接收端在 ACK 中返回 ECE(ECN-Echo)。
  • 发送端收到 ECE 后降低发送速率。
flowchart LR
    A[发送端] -->|数据包| B[交换机]
    B -->|队列长度 > ECN 阈值| C[IP 头 CE=11]
    C --> D[接收端]
    D -->|ACK ECE| A
    A -->|降速| A

6.3 DCQCN(Data Center Quantized Congestion Notification)

DCQCN 是微软为数据中心 RDMA 设计的端到端拥塞控制算法:

  • 结合了 PFC(最后一道防线,防止丢包)和 ECN(主动降速,避免触发 PFC)。
  • 发送端根据 ECN 反馈的占比动态调整发送速率。
  • 目标是快速收敛、高吞吐、低延迟。

DCQCN 的状态机可以简化为:

  1. 正常发送,速率逐步增加。
  2. 收到 ECN 标记,进入降速阶段。
  3. 降速后继续探测,如果拥塞缓解则恢复提速。

6.4 QCN(Quantized Congestion Notification)

QCN 是 IEEE 802.1Qau 标准:

  • 交换机直接生成 CNM(Congestion Notification Message) 帧给源端。
  • 源端根据 CNM 调整速率。

QCN 比 ECN 更直接,但部署复杂,RoCE 生态中更常见的是 ECN/DCQCN。

6.5 三者的协同关系

flowchart TD
    A[发送端 RDMA 流量] --> B[交换机队列]
    B -->|队列轻微拥塞| C[ECN 标记 CE]
    C --> D[接收端]
    D --> E[ACK 带 ECE]
    E --> F[发送端 DCQCN 降速]
    F --> A
    B -->|队列即将溢出| G[PFC PAUSE 帧]
    G --> H[上游交换机/网卡暂停]
    H --> B

协同逻辑:

  • ECN/DCQCN 是“预警系统”,在拥塞初期就降速,尽量避免触发 PFC。
  • PFC 是“保险丝”,当 ECN 来不及反应、缓存真要溢出时,强制让上游停发,保证不丢包。
  • 理想情况下,PFC 应该很少触发;如果 PFC 频繁触发,说明 ECN/DCQCN 参数需要调优,或者网络带宽/收敛比设计不足。

6.6 Linux / Mellanox 配置示例

# ====== 1. 查看当前 RDMA 设备与端口 ======
ibstat
rdma link show

# ====== 2. 设置 RoCE v2 的 Traffic Class,启用 ECN ======
# 106 = 0x6A = DSCP 26 (1010) + ECN 10 (ECT(1))
echo 106 > /sys/class/infiniband/mlx5_0/tc/1/traffic_class

# ====== 3. 在 Mellanox 网卡上启用 ECN 和 DCQCN ======
# 启用 CNP(Congestion Notification Packet)
mlxreg -d mlx5_0 --reg_name ROCE_ACCL --set "roce_en=1"

# ====== 4. 在网卡出口启用 PFC(优先级 7) ======
mlnx_qos -i eth0 --pfc 0,0,0,0,0,0,0,1

# ====== 5. 交换机侧(以 Mellanox/CEM 为例)配置 ECN 阈值 ======
# interface ethernet 1/1
# priority-flow-control mode on
# dcb priority 7 pfc-cos 7
# ecn enable
# ecn threshold min 100 max 400
不同厂商(Cisco、Arista、Mellanox、H3C、华为)的命令行差异很大,但核心思想一致:选一条优先级跑 RDMA,在该优先级上启用 PFC;同时启用 ECN,并让网卡侧运行 DCQCN。

7. 网络拓扑演进:Rail-Only、超节点与 CPO

7.1 Rail-Only 架构

在大型 AI 集群中,为了减少跨机架流量、优化 AllReduce 流量路径,出现了 Rail-Only 架构:

  • 同一“轨道(Rail)”内的 GPU 直接通过 Leaf 互联。
  • 不同轨道之间通过 Spine 互联。
  • 相比传统 Spine-Leaf,Rail-Only 能让 AllReduce 流量更规律、跳数更少、更容易做 ECMP。

7.2 超节点(SuperPod)

随着 GPU 规模扩大:

  • 单个机架内的 GPU 数量受限于功耗和散热。
  • 多个机架被组合成一个逻辑计算单元,称为 SuperPod / 超节点。
  • 超节点内部通常使用高带宽、低延迟网络;超节点之间再用更经济的链路连接。

7.3 光电共封装(CPO)

当端口速率提升到 800G/1.6T 时,传统可插拔光模块的功耗和密度成为瓶颈。CPO(Co-Packaged Optics) 把光引擎和交换芯片封装在一起,显著降低功耗、提升带宽密度,是下一代 AI 网络的关键技术方向。


8. 实战练习

项目一:64 GPU Spine-Leaf 设计文档

假设:

  • 每台服务器 8 张 GPU。
  • 每张 GPU 配 1 张 200Gbps NIC。
  • 要求无阻塞网络。

输出要求:

  1. 服务器数量。
  2. Leaf 数量、每 Leaf 下行/上行端口数。
  3. Spine 数量、每 Spine 端口利用率。
  4. 端口配置(是否需要 breakout)。
  5. 光模块数量(含两端)。
  6. 收敛比。
参考答案可直接运行本文 2.5 节的 Python 代码得到。

项目二:RoCE v2 实验

环境:两台直连或经过 Spine-Leaf 互联的服务器,均安装 ConnectX-6 或同等级 RDMA 网卡。

步骤:

  1. 在两端加载内核模块:modprobe rdma_cm && modprobe mlx5_ib && modprobe ib_umad
  2. 配置 IP 地址,确保两端能互相 ping 通。
  3. Server 端执行:

    ib_write_bw -d mlx5_0 -R --report_gbits --duration=30
  4. Client 端执行:

    ib_write_bw -d mlx5_0 <server_ip> -R --report_gbits --duration=30
  5. 分别测试开启/关闭 ECN、开启/关闭 PFC 时的吞吐与延迟差异。
  6. 用 ethtool -S eth0 | grep -i ecn 或交换机 counter 观察 ECN 标记次数。

思考题:

  • 当 ECN 开启后,吞吐是否下降?延迟是否更平稳?
  • 当 PFC 触发频繁时,吞吐曲线会出现什么特征?

项目三:AllReduce 流量模拟

用 Python 或 ns-3 模拟 8 个节点 Ring AllReduce 的流量过程:

  • 统计每一轮通信经过 Spine 的次数。
  • 计算不同收敛比下的理论完成时间。
  • 观察 AllGather 阶段是否比 Scatter-Reduce 阶段更容易拥塞。

9. 总结

AI 算力网络不是传统数据中心的简单升级,而是一门围绕高带宽、低延迟、低抖动、无损传输重新设计的学问。

  • Spine-Leaf 通过固定跳数和 ECMP 满足了东西向流量的扩展需求。
  • 端口计算是网络设计的第一关,理解 breakout、收敛比和无阻塞是基本功。
  • RDMA/RoCE v2 让网卡绕过内核直接读写远端内存,是高性能通信的基石。
  • 集合通信尤其是 AllReduce,是 AI 网络流量的主要来源。
  • PFC / ECN / DCQCN 三件套共同守护 RoCE 网络的无损与高效。

把这些点串起来,你就能从“会用集群”进化到“会设计集群”。


10. 参考与延伸阅读


11. 模块验收清单

完成本模块后,请对照以下清单自检:

  • [ ] 能独立计算 16 / 64 / 128 / 1024 GPU 场景下的 Spine-Leaf 端口需求。
  • [ ] 能清晰解释收敛比、无阻塞设计、ECMP 在 Spine-Leaf 中的作用。
  • [ ] 能对比 InfiniBand、RoCE v2、iWARP 的技术特点和适用场景。
  • [ ] 能解释 AllReduce 的 Ring / Tree 算法及其对网络流量模式的影响。
  • [ ] 能说明 PFC、ECN、DCQCN 在 RDMA 拥塞控制中的作用和配合关系。
  • [ ] 能使用 ib_write_bw / ib_write_lat 测试 RDMA 带宽与延迟。
  • [ ] 能用 libibverbs 写出创建 PD、CQ、QP、MR 并发起一次 Send/Write 的骨架代码。
1

评论 (0)

取消