NCCL SHARP 和 TREE算法

H100开始一代用于机内allreduce操作。

主要流程:

Scatter->reduce-> broadcast -> gather。其中reduce和broadcast由Nvswitch做,但由GPU发起控制指令驱动。

其中GPU有一个UC(unicast)内存地址和MC(multicast) 内存地址, 都映射到同一块物理地址,可以由GPU和nvswitch共同访问。

详细流程:

复制代码
初始状态:每个 GPU 有 sendbuff(本 GPU 的输入数据)

═══════════════════════ 阶段一:Scatter ═══════════════════════
【全部 GPU 并行,纯 GPU 本地操作】

  GPU-k:  sendbuff[0..N] ──GPU拷贝──→ ucBuff[0..N]  (UC 地址,本地内存)
  更新 send.tail,通知"我已写入 ucBuff"

═══════════════════════ 阶段二:Reduce + Broadcast ═══════════
【GPU 发指令 + NVSwitch 硬件执行计算】

  Step 2a: 等待所有人 Scatter 完成
  ┌─────────────────────────────────────────────────────────┐
  │ GPU-k 发: multimem.ld_reduce.min([MC_tail])             │ ← GPU 发请求
  │ NVSwitch: min(tail_GPU0, tail_GPU1, ..., tail_GPU7)     │ ← NVSwitch 计算
  │ 返回: 全局最小 step,确认所有人写完                     │ ← NVSwitch 返回
  └─────────────────────────────────────────────────────────┘

  Step 2b: 从 MC 地址读数据(触发硬件规约)
  ┌─────────────────────────────────────────────────────────┐
  │ GPU-k 发: multimem.ld_reduce.add.f32 %reg, [MC_addr]   │ ← GPU 发请求
  │ NVSwitch: 读所有 GPU ucBuff[i],执行 add 规约            │ ← NVSwitch 计算
  │ 返回: sum(GPU0[i], GPU1[i], ..., GPU7[i]) → %reg        │ ← NVSwitch 返回
  └─────────────────────────────────────────────────────────┘
  ← GPU SM 寄存器里得到全局 sum,GPU 自身没做加法计算!

  Step 2c: 将规约结果写回 MC 地址(触发硬件广播)
  ┌─────────────────────────────────────────────────────────┐
  │ GPU-k 发: multimem.st.b32 [MC_addr], %reg              │ ← GPU 发请求
  │ NVSwitch: 将 sum 广播写入所有 GPU 的 ucBuff[i]          │ ← NVSwitch 执行
  └─────────────────────────────────────────────────────────┘

  更新 send.tail,通知"规约结果已写入 ucBuff"

═══════════════════════ 阶段三:Gather ═══════════════════════
【全部 GPU 并行,纯 GPU 本地操作】

  等待 MC_tail min 确认广播完成(GPU 发请求,NVSwitch 做 min)
  GPU-k:  ucBuff[0..N] ──GPU拷贝──→ recvbuff[0..N] (本地内存,UC 地址)

最终状态:每个 GPU 的 recvbuff = sum(所有 GPU 的 sendbuff)
二. 机间CollNet Sharp算法

目前只有NV的ib交换机支持交换机Sharp协议,CollNet有两种实现模式。

1.CollNet Direct (机内过Nvswitch场景,如全局AllReduce) , 这种是假设只有一个head网卡接交换机。

复制代码
时间 →

Node0: GPU0(leaf)  GPU1(leaf)  GPU2(Head)   IB/SHARP   GPU4(Node1-Head)
       |           |           |               |             |
       ├──Scatter─→|           |               |             |
       ├──────────Scatter──────→|               |             |
       |           ├──Scatter─→|               |             |
       |           |           |               |             |
       |           |     Reduce locally        |             |
       |           |           ├──iallreduce──→|←──iallreduce─|
       |           |           |     SHARP硬件内Reduce        |
       |           |           |←─ result ────┤             |
       |           |           |               |             |
       |←─────Bcast/Gather─────|               |             |
       |           |←──Gather─ |               |             |
       ↓           ↓           ↓               ↓             ↓
    [done]      [done]      [done]          [done]        [done]

节点内的数据通过 Head GPU做local Reduce, 然后发给 IB switch。

2.CollNet Chain (机内不过Nvswitch场景,原理和direct类似)

但是我们经常的硬件架构是一个GPU接一个网卡出,CollNet Direct变为以下情况

复制代码
Node0:   rank0(head0)  rank1(head1)  rank2(head2)  rank3(head3)
             │               │               │               │
             │               │               │               │
             └───────────────┴───────────────┴───────────────┘
                                     │
                              IB/SHARP Switch
                         (4路并行 iallreduce)
                                     │
             ┌───────────────┬───────────────┬───────────────┐
             │               │               │               │
         rank0(head0)  rank1(head1)  rank2(head2)  rank3(head3)
cpp 复制代码
const int hasUp = (direct->up[0] >= 0) ? 1 : 0;   // = 0,无叶节点需要汇聚
const int hasDn = (direct->down[0] >= 0) ? 1 : 0;  // = 0,无叶节点需要广播

// 线程分配:
// nThreadsScatter = WARP_SIZE (hasUp=0, hasDn=0,只分配最少线程)
// nThreadsGather  = 0
// nThreadsBcast   = WARP_SIZE
// nThreadsReduce  = 剩余所有线程

// Reduce 路径 (hasUp=0, hasDn=0):直接发网络,无本地 reduce 操作
} else {
  // Directly send to network
  prims.send(offset, nelem);  // 直接 send 到 out=nRanks(IB 网络出口)
}

// Bcast 路径:直接从网络接收
} else {
  prims.recv(offset, nelem, /*postOp=*/true);  // 直接从 nRanks 接收结果
}

没有任何 Scatter/Gather/LocalReduce 步骤,GPU 直接将数据送给 SHARP,再直接从 SHARP 接收。

三. Tree算法

比较简单,机内机间都可以用,如图单树

复制代码
Level 0:            GPU0 (根)
                   /    \
Level 1:        GPU4    (GPU? 未画出)
               /    \
Level 2:     GPU2   GPU6
            /  \    /  \
Level 3:   GPU1 GPU3 GPU5 GPU7

实际 NCCL 特点 :不一定是完全二叉树(取决于 GPU 拓扑),通常会使用 Kary 树(每个父节点有 4-8 个子节点)来减少层数,根节点选择:通常是离 NVSwitch 最近的 GPU

两步过程:Reduce 阶段(向上规约)、 Broadcast 阶段(向下广播)

多channel时,会生成2个树,Double Tree, 多channel共用这两颗树,这样为了更好地利用带宽。

复制代码
Tree 0 (Btree):              Tree 1 (镜像):

0---------------8            3----------------11
         ______/ \           / \______
        4         \         /         7
      /   \        \       /        /   \
    2       6       10    1        5      9
   / \     / \     /  \  / \      / \    / \
  1   3   5   7   9   11 0   2   4   6  8   10
相关推荐
C+-C资深大佬10 小时前
python while循环
服务器·开发语言·python
Tian_Hang10 小时前
eclipse ditto 学习笔记
运维·服务器·开发语言·javascript·3d
QiLinkOS10 小时前
第三视觉理解徐玉生与他的商业活动(28)
大数据·c++·人工智能·算法·开源协议
iCxhust10 小时前
linux目录是否保存在硬盘 启动后读入解析的
linux·运维·服务器
懒鸟一枚10 小时前
Linux 系统 Service 服务配置详解
linux·服务器·网络
livemetee10 小时前
【关于Spring声明式事务】
java·后端·spring
倒流时光三十年10 小时前
Java 内存模型(JMM)通俗解释
java·开发语言
wabs66610 小时前
关于动态规划【力扣1143.最长公共子序列的思考】
算法·leetcode·动态规划
敖行客 Allthinker10 小时前
企业级多台服务器组装 K3s 高性能集群实战指南
运维·服务器·团队开发
码兄科技11 小时前
Java AI智能体开发实战:从零构建企业级智能应用指南
java·开发语言·人工智能