分布式训练全栈地图

目录

结论:先建立这张"分布式训练全栈地图"

[一、Layer 1:你真实拥有的东西其实是 GPU、内存和互联](#一、Layer 1:你真实拥有的东西其实是 GPU、内存和互联)

[二、GPU 为什么需要 NVLink / PCIe?](#二、GPU 为什么需要 NVLink / PCIe?)

三、单机和多机是两个不同通信世界

单机多卡

多机多卡

[四、Layer 2:CUDA 到底是什么](#四、Layer 2:CUDA 到底是什么)

[五、CUDA Driver 和 CUDA Toolkit 要严格区分](#五、CUDA Driver 和 CUDA Toolkit 要严格区分)

[NVIDIA Driver](#NVIDIA Driver)

[CUDA Toolkit / Runtime](#CUDA Toolkit / Runtime)

[六、一个 kernel 到底是什么?](#六、一个 kernel 到底是什么?)

[七、Layer 3:NCCL 出场](#七、Layer 3:NCCL 出场)

NCCL

[八、NCCL 最重要的几个通信原语](#八、NCCL 最重要的几个通信原语)

[1. Broadcast](#1. Broadcast)

[九、AllReduce ------ DDP 的灵魂](#九、AllReduce —— DDP 的灵魂)

十、AllGather

十一、ReduceScatter

[十二、Send / Recv](#十二、Send / Recv)

[十三、Layer 4:为什么分布式训练里全是"进程"](#十三、Layer 4:为什么分布式训练里全是“进程”)

[十四、torchrun 到底干嘛?](#十四、torchrun 到底干嘛?)

[十五、rank / world_size 到底是什么?](#十五、rank / world_size 到底是什么?)

world_size

rank

[十六、实际代码:看看 torchrun 给你的东西](#十六、实际代码:看看 torchrun 给你的东西)

[十七、local_rank 为什么又出来了?](#十七、local_rank 为什么又出来了?)

[Node 0](#Node 0)

[Node 1](#Node 1)

[十八、再补一个 node_rank](#十八、再补一个 node_rank)

[十九、ProcessGroup 才是 rank 真正强大的地方](#十九、ProcessGroup 才是 rank 真正强大的地方)

[二十、自己写一个 AllReduce,你马上就懂 DDP 一半了](#二十、自己写一个 AllReduce,你马上就懂 DDP 一半了)

[二十一、Layer 6:现在才真正进入"并行训练"](#二十一、Layer 6:现在才真正进入“并行训练”)

到底切哪个维度?

[二十二、数据并行 DP:切 Batch](#二十二、数据并行 DP:切 Batch)

[二十三、真正可运行的 DDP 代码](#二十三、真正可运行的 DDP 代码)

[二十四、DDP 里面真正发生了什么?](#二十四、DDP 里面真正发生了什么?)

[二十五、DDP 最大的问题](#二十五、DDP 最大的问题)

二十六、训练显存到底花在哪里?

[二十七、ZeRO/FSDP:切 Model States](#二十七、ZeRO/FSDP:切 Model States)

ZeRO-1

ZeRO-2

ZeRO-3

[二十八、但是参数都被切了,Linear 怎么算?](#二十八、但是参数都被切了,Linear 怎么算?)

[二十九、TP 与 FSDP 有本质区别](#二十九、TP 与 FSDP 有本质区别)

FSDP

[Tensor Parallel](#Tensor Parallel)

[三十、为什么 Megatron 很重要?](#三十、为什么 Megatron 很重要?)

[三十一、Pipeline Parallel:切 Layer](#三十一、Pipeline Parallel:切 Layer)

[三十二、为什么还需要 microbatch?](#三十二、为什么还需要 microbatch?)

[三十三、Context Parallel:切 Sequence](#三十三、Context Parallel:切 Sequence)

[三十四、Expert Parallel:切 MoE Expert](#三十四、Expert Parallel:切 MoE Expert)

三十五、现在可以得到真正完整的"大模型并行坐标系"

[三十六、最复杂情况下,一张 GPU 到底是什么身份?](#三十六、最复杂情况下,一张 GPU 到底是什么身份?)

[三十七、这时候 ProcessGroup 就真正派上用场了](#三十七、这时候 ProcessGroup 就真正派上用场了)

[三十八、因此 rank 和 group 的真正关系是](#三十八、因此 rank 和 group 的真正关系是)

[三十九、Layer 4 还有一个容易混淆的东西:Ray](#三十九、Layer 4 还有一个容易混淆的东西:Ray)

torchrun

Ray

[四十、这就是 verl 为什么同时出现 Ray + FSDP/Megatron + vLLM](#四十、这就是 verl 为什么同时出现 Ray + FSDP/Megatron + vLLM)

[四十一、完整训练 Step 从顶层到底层走一遍](#四十一、完整训练 Step 从顶层到底层走一遍)

四十二、把所有常见名词重新归位

四十三、实际工程里常见的几套组合

[方案 A:普通小模型](#方案 A:普通小模型)

[四十四、方案 B:模型单卡训练放不下](#四十四、方案 B:模型单卡训练放不下)

[四十五、方案 C:超大模型预训练](#四十五、方案 C:超大模型预训练)

[四十六、方案 D:LLM RL / verl](#四十六、方案 D:LLM RL / verl)

第一层:角色级资源分配

第二层:角色内部模型并行

四十七、把它画成你目前最需要的一张图

[四十八、我建议你现在不要急着学 Megatron 参数](#四十八、我建议你现在不要急着学 Megatron 参数)

四十九、真正建议你动手做的第一个实验

[Experiment 1:观察 rank](#Experiment 1:观察 rank)

[五十、Experiment 2:手写 NCCL AllReduce](#五十、Experiment 2:手写 NCCL AllReduce)

[五十一、Experiment 3:自己模拟 DDP](#五十一、Experiment 3:自己模拟 DDP)

[五十二、再做 Experiment 4:换成真正 DDP](#五十二、再做 Experiment 4:换成真正 DDP)

五十三、最后你应该形成一个"两个世界"的认知

[世界 A:计算世界](#世界 A:计算世界)

[世界 B:通信世界](#世界 B:通信世界)

五十四、最终把整套地基压缩成这一张脑图

[你现在先牢牢记住 8 句话](#你现在先牢牢记住 8 句话)

我建议接下来按这个顺序继续


结论:先建立这张"分布式训练全栈地图"

以后看到 torchrun / NCCL / CUDA / DDP / FSDP / DeepSpeed / Megatron / Ray / verl / vLLM,应该能马上判断:

它在哪一层?它管什么?它不管什么?它下面依赖谁?

我建议你先把分布式训练理解成下面 7 层:

复制代码
┌─────────────────────────────────────────────────────────────┐
│ Layer 7:训练 / RL 框架                                     │
│ HuggingFace Trainer / verl / TRL / NeMo / Megatron-LM      │
│                                                             │
│ 负责:训练流程、loss、optimizer、rollout、checkpoint 等     │
├─────────────────────────────────────────────────────────────┤
│ Layer 6:并行策略                                            │
│ DDP / FSDP / ZeRO / TP / PP / CP / EP                      │
│                                                             │
│ 负责:模型、数据、状态到底怎么切                            │
├─────────────────────────────────────────────────────────────┤
│ Layer 5:分布式运行时                                        │
│ torch.distributed / Megatron-Core / DeepSpeed               │
│                                                             │
│ 负责:ProcessGroup、rank、通信调用、并行组                  │
├─────────────────────────────────────────────────────────────┤
│ Layer 4:进程与任务调度                                      │
│ torchrun / Ray / Slurm / Kubernetes                         │
│                                                             │
│ 负责:在哪些机器启动多少进程、占哪些 GPU                    │
├─────────────────────────────────────────────────────────────┤
│ Layer 3:GPU 通信                                            │
│ NCCL                                                        │
│                                                             │
│ AllReduce / AllGather / ReduceScatter / Send / Recv         │
├─────────────────────────────────────────────────────────────┤
│ Layer 2:GPU 计算                                            │
│ CUDA Runtime / CUDA Kernel / cuBLAS / FlashAttention        │
│                                                             │
│ GEMM、Attention、Kernel 执行                                │
├─────────────────────────────────────────────────────────────┤
│ Layer 1:硬件                                                │
│ GPU HBM / NVLink / NVSwitch / PCIe / CPU / RAM / NIC       │
│ InfiniBand / RoCE                                           │
└─────────────────────────────────────────────────────────────┘

这张图是地基。

其中最重要的一条链是:

复制代码
训练代码
  ↓
DDP / FSDP / Megatron TP
  ↓
torch.distributed
  ↓
NCCL
  ↓
CUDA
  ↓
NVLink / PCIe / IB
  ↓
GPU

PyTorch 官方现在也把 distributed 体系划分成 DDP、FSDP2、TP、DeviceMesh 等并行组件;Megatron-Core 则进一步提供 DP、TP、PP、CP、EP 等组合式大模型并行能力。(PyTorch Docs)

下面从最底层开始打通。


一、Layer 1:你真实拥有的东西其实是 GPU、内存和互联

先暂时忘掉 PyTorch。

假设一台机器:

复制代码
                    CPU
                     │
                System RAM
                     │
            ─────── PCIe ───────
             │       │       │
           GPU0    GPU1    GPU2    GPU3
             │       │       │       │
             └──── NVLink / NVSwitch ─┘

每张 GPU 里面大致有:

复制代码
GPU
├── SM / Tensor Core       ← 真正计算矩阵乘法
├── HBM 显存               ← 模型参数 / activation / gradient
├── L2 Cache
├── CUDA Core
├── Tensor Core
└── NVLink / PCIe Interface

例如你用 A100 80GB:

复制代码
GPU 0
├─ HBM: 80GB
└─ compute

GPU 1
├─ HBM: 80GB
└─ compute

这里最容易产生一个错误认知:

4 × 80GB GPU ≠ 自动得到一张 320GB GPU。

实际上:

复制代码
GPU0 HBM 80GB
GPU1 HBM 80GB
GPU2 HBM 80GB
GPU3 HBM 80GB

四块独立内存

GPU0 默认不能直接把:

复制代码
tensor_on_gpu1

当成本地 tensor 使用。

必须发生通信。

所以分布式训练的本质问题开始出现:

怎么让很多拥有独立显存的 GPU 像一个计算系统一样协作?


假设:

复制代码
GPU0:
gradient = [1, 2, 3]

GPU1:
gradient = [4, 5, 6]

DDP 要得到:

复制代码
sum gradient = [5, 7, 9]

那么数据就必须:

复制代码
GPU0  ─────────→ GPU1
GPU1  ─────────→ GPU0

这个数据走哪里?

可能走:

复制代码
NVLink
PCIe
NVSwitch

跨机器之后可能变成:

复制代码
GPU
 ↓
PCIe
 ↓
NIC
 ↓
InfiniBand / RoCE Ethernet
 ↓
NIC
 ↓
PCIe
 ↓
另一台 GPU

三、单机和多机是两个不同通信世界

单机多卡

例如:

复制代码
Node 0

GPU0 ─┐
GPU1 ─┼── NVSwitch
GPU2 ─┤
GPU3 ─┘

通常特点:

复制代码
延迟低
带宽高
通信相对便宜

多机多卡

例如:

复制代码
Node 0                         Node 1

GPU0 ─┐                      GPU4 ─┐
GPU1 ─┼─ NVLink              GPU5 ─┼─ NVLink
GPU2 ─┤                          ...
GPU3 ─┘
   │                              │
   PCIe                           PCIe
   │                              │
  NIC ===== InfiniBand/RoCE ===== NIC

所以:

通信拓扑会直接影响并行策略。

比如 TP 每一层 Transformer 都可能频繁通信。

因此:

复制代码
TP group

通常特别希望放在:

复制代码
同一节点
同一 NVLink/NVSwitch domain

而不是随便跨机器。

这就是后面所谓:

复制代码
topology-aware parallelism

的来源。


四、Layer 2:CUDA 到底是什么

很多初学分布式训练的人会把:

复制代码
CUDA
PyTorch
NCCL
GPU Driver

混在一起。

实际上:

复制代码
PyTorch

  ↓ 调 CUDA API

CUDA Runtime
  ↓

CUDA Driver
  ↓

GPU Hardware

例如:

复制代码
x = torch.randn(4096, 4096, device="cuda")
y = torch.randn(4096, 4096, device="cuda")

z = x @ y

你写的是:

复制代码
z = x @ y

但底下可能经过:

复制代码
PyTorch
   ↓
ATen
   ↓
cuBLAS / cuBLASLt
   ↓
CUDA Kernel
   ↓
Tensor Core

最终 GPU 才真正执行矩阵乘法。


五、CUDA Driver 和 CUDA Toolkit 要严格区分

这一点跟你之前云服务器环境问题直接相关。

大概是:

复制代码
操作系统
│
├── NVIDIA Driver
│
│     └── 和物理 GPU 通信
│
├── CUDA Runtime
│
├── cuBLAS
├── cuDNN
├── NCCL
│
└── PyTorch

其中:

NVIDIA Driver

更接近系统层:

复制代码
Linux Kernel
   ↓
NVIDIA Driver
   ↓
GPU

云服务器通常:

你不能随便改。


CUDA Toolkit / Runtime

例如:

复制代码
CUDA 12.4
CUDA 12.6

属于用户空间生态。

很多情况下 Conda / pip package / Docker 可以提供对应 runtime library。

所以之前我们讨论的:

复制代码
Driver
≠
CUDA Runtime
≠
PyTorch CUDA build

就是这个原因。


六、一个 kernel 到底是什么?

例如:

复制代码
y = torch.relu(x)

GPU 不会理解 Python。

PyTorch 最终启动:

复制代码
CUDA Kernel

可以把 kernel 粗略理解为:

GPU 上运行的一段函数。

例如概念代码:

复制代码
__global__ void relu(float* x) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    x[i] = max(x[i], 0.0f);
}

GPU:

复制代码
Thread 0 → x[0]
Thread 1 → x[1]
Thread 2 → x[2]
...

深度学习训练本质上就是不停启动:

复制代码
GEMM kernel
Attention kernel
LayerNorm kernel
Softmax kernel
optimizer kernel
...

七、Layer 3:NCCL 出场

现在问题来了。

CUDA 能让:

复制代码
GPU0

计算。

但是:

GPU0 和 GPU1 怎么一起算?

NVIDIA 提供:

NCCL

全称:

复制代码
NVIDIA Collective Communications Library

它专门处理:

复制代码
GPU ↔ GPU communication

NCCL 本身不是训练框架,而是 GPU collective communication library。官方提供 AllReduce、Broadcast 等 collective,并把 GPU 通过 communicator/rank 组织起来。(NVIDIA Docs)


八、NCCL 最重要的几个通信原语

这一块必须真正理解。

后面:

复制代码
DDP
FSDP
TP
PP
CP

本质上都是这些操作的不同组合。


1. Broadcast

初始:

复制代码
GPU0: [1 2 3]
GPU1: [? ? ?]
GPU2: [? ? ?]
GPU3: [? ? ?]

执行:

复制代码
broadcast(src=0)

结果:

复制代码
GPU0: [1 2 3]
GPU1: [1 2 3]
GPU2: [1 2 3]
GPU3: [1 2 3]

用途:

复制代码
参数初始化同步
配置同步

九、AllReduce ------ DDP 的灵魂

假设每张 GPU 都算出了 gradient:

复制代码
GPU0: [1, 2]
GPU1: [3, 4]
GPU2: [5, 6]
GPU3: [7, 8]

执行:

复制代码
AllReduce(SUM)

得到:

复制代码
GPU0: [16, 20]
GPU1: [16, 20]
GPU2: [16, 20]
GPU3: [16, 20]

数学上:

g=∑i=0N−1gig = \sum_{i=0}^{N-1} g_i

然后通常再:

gavg=1Ngg_{\text{avg}}=\frac{1}{N}g

NCCL 官方定义 AllReduce 就是:所有 rank 提供输入,对它们执行 reduction,并让每个 rank 都获得结果。(NVIDIA Docs)

这直接解释了 DDP:

复制代码
GPU0 forward/backward → gradient0 ─┐
GPU1 forward/backward → gradient1 ─┼→ AllReduce → same gradient
GPU2 forward/backward → gradient2 ─┤
GPU3 forward/backward → gradient3 ─┘

十、AllGather

假设:

复制代码
GPU0: [A]
GPU1: [B]
GPU2: [C]
GPU3: [D]

AllGather:

复制代码
GPU0: [A B C D]
GPU1: [A B C D]
GPU2: [A B C D]
GPU3: [A B C D]

记法:

每个人拿一块,最后每个人拿全部。

FSDP 会大量使用这一类思想。


十一、ReduceScatter

它基本可以理解为:

复制代码
Reduce
+
Scatter

例如:

复制代码
GPU0: A0 A1 A2 A3
GPU1: B0 B1 B2 B3
GPU2: C0 C1 C2 C3
GPU3: D0 D1 D2 D3

先:

复制代码
A0+B0+C0+D0
A1+B1+C1+D1
...

但不让所有 GPU 都保存全部结果。

而是:

复制代码
GPU0 → result0
GPU1 → result1
GPU2 → result2
GPU3 → result3

所以:

结果本身也是 shard 的。

这正好非常适合:

复制代码
FSDP / ZeRO

十二、Send / Recv

这是点对点通信。

复制代码
GPU0
 │
 │ send activation
 ▼
GPU1

Pipeline Parallel 特别典型:

复制代码
Stage 0
GPU0
 │
 │ activation
 ▼
Stage 1
GPU1
 │
 │ activation
 ▼
Stage 2
GPU2

所以可以先形成这个映射:

并行机制 常见通信
DDP AllReduce
FSDP AllGather + ReduceScatter
TP AllReduce / AllGather / ReduceScatter
PP Send / Recv
CP AllGather / Ring P2P 等
EP All-to-All

后面看论文的时候,你会发现:

新的并行算法很多时候其实就是在重新设计"谁存什么、谁算什么、什么时候通信什么"。


十三、Layer 4:为什么分布式训练里全是"进程"

现在终于进入:

复制代码
rank
world_size
local_rank

这是你目前最需要补牢的地方。

PyTorch GPU 分布式最常见的运行模型是:

一个 GPU 对应一个 Python process。

例如:

复制代码
4 GPUs

GPU0 ← Python Process 0
GPU1 ← Python Process 1
GPU2 ← Python Process 2
GPU3 ← Python Process 3

不是:

复制代码
一个 Python process
    ├── GPU0
    ├── GPU1
    ├── GPU2
    └── GPU3

虽然后者技术上也能实现,但主流 distributed training 通常使用前面的 one-process-per-GPU 模式。


十四、torchrun 到底干嘛?

假设你执行:

复制代码
torchrun \
    --standalone \
    --nproc-per-node=4 \
    train.py

你不是运行一次:

复制代码
python train.py

而是 torchrun 帮你启动:

复制代码
Process 0 → train.py
Process 1 → train.py
Process 2 → train.py
Process 3 → train.py

所以:

复制代码
                  torchrun
                     │
        ┌────────────┼────────────┐
        ↓            ↓            ↓
    process 0    process 1    process 2 ...
        │            │
      GPU0          GPU1

关键点:

每个进程运行的是同一份 Python 代码。

代码自己通过:

复制代码
rank

知道:

"我是谁?"


十五、rank / world_size 到底是什么?

最简单的情况:

复制代码
1 machine
4 GPUs

于是:

复制代码
WORLD_SIZE = 4

Process 0 → rank 0 → GPU0
Process 1 → rank 1 → GPU1
Process 2 → rank 2 → GPU2
Process 3 → rank 3 → GPU3

因此:

world_size

整个 distributed group 中:

复制代码
process 数量

这里:

复制代码
world_size = 4

rank

每个 process 的全局编号:

复制代码
0 ~ world_size - 1

所以:

复制代码
rank = 0
rank = 1
rank = 2
rank = 3

十六、实际代码:看看 torchrun 给你的东西

创建:

复制代码
# dist_info.py

import os
import torch
import torch.distributed as dist

dist.init_process_group(backend="nccl")

rank = dist.get_rank()
world_size = dist.get_world_size()
local_rank = int(os.environ["LOCAL_RANK"])

torch.cuda.set_device(local_rank)

print(
    f"rank={rank}, "
    f"local_rank={local_rank}, "
    f"world_size={world_size}, "
    f"gpu={torch.cuda.current_device()}"
)

dist.destroy_process_group()

执行:

复制代码
torchrun --standalone --nproc-per-node=4 dist_info.py

你会看到类似:

复制代码
rank=0 local_rank=0 world_size=4 gpu=0
rank=1 local_rank=1 world_size=4 gpu=1
rank=2 local_rank=2 world_size=4 gpu=2
rank=3 local_rank=3 world_size=4 gpu=3

这里:

复制代码
dist.init_process_group(backend="nccl")

做的是:

复制代码
PyTorch Processes
       ↓
建立 ProcessGroup
       ↓
NCCL Communicator
       ↓
GPU 通信建立

十七、local_rank 为什么又出来了?

单机时:

复制代码
global rank == local rank

看起来似乎没有区别。

多机就不同了。

例如:

复制代码
2 nodes × 4 GPU

Node 0

复制代码
GPU0 → global_rank 0, local_rank 0
GPU1 → global_rank 1, local_rank 1
GPU2 → global_rank 2, local_rank 2
GPU3 → global_rank 3, local_rank 3

Node 1

复制代码
GPU0 → global_rank 4, local_rank 0
GPU1 → global_rank 5, local_rank 1
GPU2 → global_rank 6, local_rank 2
GPU3 → global_rank 7, local_rank 3

所以:

复制代码
global rank
    ↓
整个 cluster 中的进程身份

local rank
    ↓
当前机器内部的进程/GPU身份

这也是为什么通常:

复制代码
torch.cuda.set_device(local_rank)

而不是:

复制代码
torch.cuda.set_device(rank)

否则:

复制代码
rank = 5

你会去找:

复制代码
cuda:5

但当前节点可能只有:

复制代码
cuda:0~3

十八、再补一个 node_rank

完整关系:

复制代码
                 Cluster

          Node rank 0
       ┌───────────────┐
       │ GPU0 rank 0   │
       │ GPU1 rank 1   │
       │ GPU2 rank 2   │
       │ GPU3 rank 3   │
       └───────────────┘

          Node rank 1
       ┌───────────────┐
       │ GPU0 rank 4   │
       │ GPU1 rank 5   │
       │ GPU2 rank 6   │
       │ GPU3 rank 7   │
       └───────────────┘

一般:

global_rank=node_rank×GPUsPerNode+local_rankglobal\_rank = node\_rank \times GPUsPerNode + local\_rank

例如:

复制代码
node_rank = 1
local_rank = 2
GPUs/node = 4

那么:

rank=1×4+2=6rank=1\times4+2=6


十九、ProcessGroup 才是 rank 真正强大的地方

默认:

复制代码
dist.init_process_group()

建立:

复制代码
WORLD GROUP

[0,1,2,3,4,5,6,7]

但是我们可以自己创建:

复制代码
group_a = dist.new_group([0, 1, 2, 3])
group_b = dist.new_group([4, 5, 6, 7])

现在:

复制代码
WORLD
│
├── Group A
│   └── ranks [0 1 2 3]
│
└── Group B
    └── ranks [4 5 6 7]

于是通信可以只发生在某个 group 内。

这正是:

复制代码
Megatron TP Group
PP Group
DP Group
CP Group
EP Group

的底层基础。


二十、自己写一个 AllReduce,你马上就懂 DDP 一半了

复制代码
import os
import torch
import torch.distributed as dist

dist.init_process_group("nccl")

rank = dist.get_rank()
local_rank = int(os.environ["LOCAL_RANK"])

torch.cuda.set_device(local_rank)

x = torch.tensor(
    [float(rank + 1)],
    device=f"cuda:{local_rank}"
)

print(f"Before rank={rank}: {x.item()}")

dist.all_reduce(
    x,
    op=dist.ReduceOp.SUM
)

print(f"After rank={rank}: {x.item()}")

4 GPU:

初始:

复制代码
rank0 → 1
rank1 → 2
rank2 → 3
rank3 → 4

执行:

复制代码
all_reduce()

之后:

复制代码
rank0 → 10
rank1 → 10
rank2 → 10
rank3 → 10

因为:

1+2+3+4=101+2+3+4=10

这个函数表面是:

复制代码
dist.all_reduce()

底层大概:

复制代码
PyTorch
   ↓
ProcessGroupNCCL
   ↓
NCCL AllReduce
   ↓
NVLink / PCIe
   ↓
GPU

二十一、Layer 6:现在才真正进入"并行训练"

前面讲的:

复制代码
CUDA
NCCL
rank
ProcessGroup

都还没规定:

模型到底怎么切。

这是 parallel strategy 的职责。

核心可以归结为一句话:

到底切哪个维度?


二十二、数据并行 DP:切 Batch

例如:

复制代码
Global Batch = 128
4 GPUs

那么:

复制代码
GPU0 → batch 0:32
GPU1 → batch 32:64
GPU2 → batch 64:96
GPU3 → batch 96:128

而模型:

复制代码
GPU0 → 完整 Model
GPU1 → 完整 Model
GPU2 → 完整 Model
GPU3 → 完整 Model

图:

复制代码
              Dataset
                 │
        ┌────────┼────────┐
        ↓        ↓        ↓
     batch0   batch1   batch2 ...
        │        │
        ↓        ↓
      GPU0      GPU1
      Model     Model
        │        │
        ↓        ↓
     grad0     grad1
        └────┬───┘
             ↓
         AllReduce
             ↓
        same gradient

二十三、真正可运行的 DDP 代码

复制代码
import os

import torch
import torch.distributed as dist

from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data import DataLoader
from torch.utils.data.distributed import DistributedSampler


def main():
    dist.init_process_group("nccl")

    local_rank = int(os.environ["LOCAL_RANK"])
    torch.cuda.set_device(local_rank)

    model = torch.nn.Sequential(
        torch.nn.Linear(1024, 4096),
        torch.nn.ReLU(),
        torch.nn.Linear(4096, 10),
    ).cuda()

    model = DDP(
        model,
        device_ids=[local_rank]
    )

    dataset = MyDataset()

    sampler = DistributedSampler(
        dataset,
        shuffle=True
    )

    dataloader = DataLoader(
        dataset,
        batch_size=32,
        sampler=sampler,
    )

    optimizer = torch.optim.AdamW(
        model.parameters(),
        lr=1e-4
    )

    for epoch in range(10):

        sampler.set_epoch(epoch)

        for x, y in dataloader:

            x = x.cuda(non_blocking=True)
            y = y.cuda(non_blocking=True)

            optimizer.zero_grad()

            pred = model(x)

            loss = torch.nn.functional.cross_entropy(
                pred,
                y
            )

            loss.backward()

            optimizer.step()

    dist.destroy_process_group()

运行:

复制代码
torchrun \
    --standalone \
    --nproc-per-node=4 \
    train.py

二十四、DDP 里面真正发生了什么?

重点是:

复制代码
loss.backward()

你没手写:

复制代码
dist.all_reduce()

为什么?

因为:

复制代码
DDP(model)

会给 parameter gradient 注册 hook。

于是 backward:

复制代码
Layer 32 backward
     ↓
gradient ready
     ↓
放进 gradient bucket
     ↓
NCCL AllReduce

然后继续:

复制代码
Layer 31 backward
     ↓
gradient ready
     ↓
AllReduce

所以可以实现:

复制代码
Backward Compute
      +
Communication

部分 overlap。

这里你第一次看到:

训练框架实际上是在替你自动安排通信。


二十五、DDP 最大的问题

假设:

复制代码
70B model

每张 GPU 都保存:

复制代码
完整 parameter
完整 gradient
完整 optimizer state

那么:

复制代码
GPU0: Model 70B
GPU1: Model 70B
GPU2: Model 70B
...

显然很浪费。

所以产生:

复制代码
ZeRO
FSDP

二十六、训练显存到底花在哪里?

这个公式非常重要。

训练大模型时 GPU memory 大致:

Memory=Parameters+Gradients+OptimizerStates+Activations+TemporaryBuffersMemory = Parameters + Gradients + OptimizerStates + Activations + TemporaryBuffers

假设 Adam:

通常还需要:

复制代码
parameter
gradient
first moment m
second moment v

所以训练所需显存远大于:

复制代码
模型权重大小

例如你看到:

复制代码
7B × BF16 ≈ 14GB

绝对不能理解成:

7B 模型训练只需要 14GB。

14GB 只是:

复制代码
parameters

的一部分账。


二十七、ZeRO/FSDP:切 Model States

假设 4 GPU。

普通 DDP:

复制代码
GPU0: P P P P | G G G G | O O O O
GPU1: P P P P | G G G G | O O O O
GPU2: P P P P | G G G G | O O O O
GPU3: P P P P | G G G G | O O O O

其中:

复制代码
P = parameter
G = gradient
O = optimizer state

大量重复。


ZeRO-1

切:

复制代码
Optimizer State

GPU0: full P | full G | O0
GPU1: full P | full G | O1
GPU2: full P | full G | O2
GPU3: full P | full G | O3

ZeRO-2

再切:

复制代码
gradient

GPU0: full P | G0 | O0
GPU1: full P | G1 | O1
GPU2: full P | G2 | O2
GPU3: full P | G3 | O3

ZeRO-3

连 parameter 一起切:

复制代码
GPU0: P0 | G0 | O0
GPU1: P1 | G1 | O1
GPU2: P2 | G2 | O2
GPU3: P3 | G3 | O3

DeepSpeed 官方对 ZeRO 三阶段的定义基本就是:Stage 1 shard optimizer states,Stage 2 进一步 shard gradients,Stage 3 再 shard parameters。(DeepSpeed)


二十八、但是参数都被切了,Linear 怎么算?

这是 FSDP/ZeRO-3 最关键的问题。

假设:

复制代码
Linear.weight

分成:

复制代码
GPU0: W0
GPU1: W1
GPU2: W2
GPU3: W3

forward 到这一层的时候:

复制代码
GPU0
GPU1
GPU2
GPU3
 ↓
AllGather
 ↓
完整 W
 ↓
forward
 ↓
释放不需要的完整 W

backward 后:

复制代码
gradient
 ↓
ReduceScatter
 ↓

GPU0 → grad0
GPU1 → grad1
GPU2 → grad2
GPU3 → grad3

所以:

复制代码
FSDP

forward:
shard parameters
      ↓
AllGather
      ↓
temporary full parameter
      ↓
compute
      ↓
reshard

backward:
gradient
      ↓
ReduceScatter
      ↓
sharded gradient

因此:

FSDP 的本质不是"参数永远不完整出现"。

而是:

只在真正计算需要它时临时 gather,算完马上 shard 回去。


二十九、TP 与 FSDP 有本质区别

这是大模型分布式最容易混乱的地方之一。

FSDP

参数:

复制代码
平时 shard

但计算 Linear 时:

复制代码
临时 gather 完整 W

每个 GPU 基本还是在执行完整 layer 的计算,只不过处理不同 batch。


Tensor Parallel

则是:

计算本身就是分布式的。

例如:

Y=XWY=XW

把:

W=W0,W1W=W_0,W_1

那么:

复制代码
GPU0:
Y0 = XW0

GPU1:
Y1 = XW1

最后:

复制代码
Y = concat(Y0, Y1)

GPU0 永远没必要计算:

复制代码
XW1

GPU1 也不计算:

复制代码
XW0

所以:

复制代码
FSDP
= shard storage/state

TP
= shard computation

这是一个非常重要的区别。


三十、为什么 Megatron 很重要?

因为 Transformer 特别适合做 Tensor Parallel。

例如 MLP:

H=GELU(XA)H = \text{GELU}(XA) Y=HBY = HB

Megatron 可以:

复制代码
             X
             │
       ┌─────┴─────┐
       ↓           ↓

GPU0: XA0       GPU1: XA1
       ↓           ↓
      H0          H1
       ↓           ↓
    H0B0        H1B1
       └─────┬─────┘
             ↓
         AllReduce
             ↓
             Y

因此一个 layer 被多个 GPU 共同计算

Megatron-Core 官方目前支持 DP、TP、PP、CP、EP,以及 FSDP 等组合并行。(NVIDIA Docs)


三十一、Pipeline Parallel:切 Layer

如果模型:

复制代码
Layer 0
Layer 1
...
Layer 79

可以:

复制代码
GPU0:
Layer 0 ~ 19

GPU1:
Layer 20 ~ 39

GPU2:
Layer 40 ~ 59

GPU3:
Layer 60 ~ 79

于是:

复制代码
tokens
 ↓
GPU0
 ↓ activation
GPU1
 ↓ activation
GPU2
 ↓ activation
GPU3
 ↓
loss

这里通信是:

复制代码
P2P send/recv

三十二、为什么还需要 microbatch?

如果只有一个 batch:

复制代码
时间 →

GPU0  ████████
GPU1          ████████
GPU2                  ████████
GPU3                          ████████

大量 GPU 闲着。

于是切:

复制代码
Batch
 ↓
microbatch0
microbatch1
microbatch2
microbatch3
...

变成 pipeline:

复制代码
time →

GPU0 M0 M1 M2 M3
GPU1    M0 M1 M2 M3
GPU2       M0 M1 M2 M3
GPU3          M0 M1 M2 M3

GPU 利用率明显提高。

这就是:

复制代码
pipeline scheduling
pipeline bubble
microbatch

这些词的来源。


三十三、Context Parallel:切 Sequence

现在长上下文:

复制代码
sequence length = 128K

activation 很大。

于是:

复制代码
Tokens:
0 ───────────────────────────────── 127999

切:

复制代码
GPU0: tokens 0 ~ 31K
GPU1: tokens 32K ~ 63K
GPU2: tokens 64K ~ 95K
GPU3: tokens 96K ~ 127K

这就是:

复制代码
Context Parallelism

Megatron 文档中 CP 正是沿 sequence dimension 切输入和 activation;attention 因为 Q 需要访问其他 token 的 KV,因此需要额外通信。(NVIDIA Docs)


三十四、Expert Parallel:切 MoE Expert

例如:

复制代码
64 Experts
8 GPUs

可以:

复制代码
GPU0 → expert 0-7
GPU1 → expert 8-15
GPU2 → expert 16-23
...

token routing:

复制代码
Token
 ↓
Router
 ↓
Expert 37
 ↓
发送到拥有 Expert37 的 GPU

因此会出现:

复制代码
All-to-All

通信。


三十五、现在可以得到真正完整的"大模型并行坐标系"

一个 Transformer Tensor:

复制代码
[batch, sequence, hidden]

模型还有:

复制代码
layers
experts

因此你可以沿不同维度切:

复制代码
                    大模型

Batch
  ↓
Data Parallel
DP

Hidden
  ↓
Tensor Parallel
TP

Layers
  ↓
Pipeline Parallel
PP

Sequence
  ↓
Context Parallel
CP

Experts
  ↓
Expert Parallel
EP

这张表建议直接记:

并行 切什么 解决什么
DP batch 提升吞吐
FSDP/ZeRO model states 降低参数/梯度/optimizer 显存
TP hidden / layer tensor 单层太大
PP layers 模型太深
CP sequence 长上下文
EP experts MoE

三十六、最复杂情况下,一张 GPU 到底是什么身份?

假设:

复制代码
64 GPU

TP = 4
PP = 2
CP = 2

那么:

DP=644×2×2=4DP= \frac{64}{4\times2\times2} =4

于是:

64=DP(4)×PP(2)×CP(2)×TP(4)64 = DP(4) \times PP(2) \times CP(2) \times TP(4)

一张 GPU:

复制代码
global rank = 37

可能同时有:

复制代码
TP rank = 1
PP rank = 0
CP rank = 1
DP rank = 2

所以:

rank 37 不是只有一个身份。

它同时属于:

复制代码
WORLD group

TP group
PP group
CP group
DP group

三十七、这时候 ProcessGroup 就真正派上用场了

想象:

复制代码
World ranks

0 1 2 3 4 5 6 7

假设:

复制代码
TP = 2
PP = 2
DP = 2

可能形成:

复制代码
TP Groups:

[0,1]
[2,3]
[4,5]
[6,7]


PP Groups:

[0,2]
[1,3]
[4,6]
[5,7]


DP Groups:

[0,4]
[1,5]
[2,6]
[3,7]

那么:

复制代码
rank 1

同时属于:

复制代码
TP: [0,1]

PP: [1,3]

DP: [1,5]

如果现在:

复制代码
Tensor Parallel AllReduce

只调用:

复制代码
TP Group

也就是:

复制代码
rank 0 ↔ rank 1

不会和:

复制代码
rank 4

通信。

如果 gradient DP:

复制代码
rank 1 ↔ rank 5

三十八、因此 rank 和 group 的真正关系是

不要再理解成:

复制代码
rank = GPU 编号

准确来说:

rank 是 distributed process 在某个通信世界中的逻辑编号。

通常:

复制代码
Process
 │
 ├── global_rank
 ├── local_rank
 │
 ├── TP rank
 ├── DP rank
 ├── PP rank
 ├── CP rank
 └── EP rank

而 GPU 只是这个 process 当前绑定的计算设备。

这是你理解后续 Megatron 和 verl 的关键。


三十九、Layer 4 还有一个容易混淆的东西:Ray

现在再看:

复制代码
torchrun
Ray

两者不是一个层面的东西。

torchrun

核心:

复制代码
启动 distributed process

例如:

复制代码
rank0
rank1
rank2
rank3

Ray

核心是:

复制代码
distributed task / actor scheduling
resource management

例如:

复制代码
             Ray Cluster

                 Head
                   │
       ┌───────────┼────────────┐
       ↓           ↓            ↓
Actor Worker   Reward Worker  Rollout Worker
 GPU 0-3          GPU4         GPU5-7

Ray 解决:

谁占 GPU?

Megatron 解决:

拿到这些 GPU 后怎么切模型?

NCCL 解决:

GPU 之间怎么传 Tensor?

这三个绝对不要混。


四十、这就是 verl 为什么同时出现 Ray + FSDP/Megatron + vLLM

现在终于可以重新看 verl:

复制代码
                        verl
              RL algorithm / workflow
                         │
                         ▼
                        Ray
             resource & worker scheduling
                         │
        ┌────────────────┼─────────────────┐
        │                │                 │
        ▼                ▼                 ▼
 ActorWorker       RolloutWorker      RewardWorker
        │                │
        ▼                ▼
 FSDP/Megatron          vLLM
        │                │
        ▼                ▼
 torch.distributed   distributed runtime
        │                │
        └───────┬────────┘
                ▼
               NCCL
                │
                ▼
         NVLink / PCIe / IB
                │
                ▼
               GPU

所以:

复制代码
verl

不是 NCCL 替代品。

复制代码
Ray

也不是 Megatron 替代品。

它们负责的是完全不同的问题。


四十一、完整训练 Step 从顶层到底层走一遍

这部分最重要。

假设:

复制代码
loss.backward()

你只写一行。

下面可能发生:

复制代码
① Python
loss.backward()

       ↓

② PyTorch Autograd
构建/执行 backward graph

       ↓

③ Transformer Layer backward

       ↓

④ GEMM backward
cuBLAS

       ↓

⑤ CUDA kernel

       ↓

⑥ Tensor Core
计算 gradient

       ↓

⑦ DDP gradient hook

       ↓

⑧ torch.distributed.all_reduce()

       ↓

⑨ ProcessGroupNCCL

       ↓

⑩ NCCL AllReduce

       ↓

⑪ NVLink / NVSwitch / IB

       ↓

⑫ 其他 GPU

       ↓

⑬ gradient 同步

       ↓

⑭ AdamW optimizer.step()

       ↓

⑮ CUDA optimizer kernel

       ↓

⑯ GPU 更新 parameter

所以你表面写:

复制代码
loss.backward()
optimizer.step()

底下实际已经发生了:

复制代码
Autograd
CUDA
cuBLAS
NCCL
NVLink
DDP
ProcessGroup
GPU Kernel

四十二、把所有常见名词重新归位

这张表你后面可以一直拿来查。

名词 层级 核心职责
GPU 硬件 计算
HBM 硬件 GPU 显存
PCIe 硬件 CPU/GPU/设备互联
NVLink 硬件 高速 GPU-GPU
NVSwitch 硬件 多 GPU 高速交换
InfiniBand 网络 跨节点高速通信
CUDA Driver 系统 控制 NVIDIA GPU
CUDA Runtime 运行时 CUDA API
cuBLAS Kernel Library GEMM
FlashAttention Kernel 高效 Attention
NCCL 通信 GPU collective
torch.distributed distributed runtime rank/group/communication
torchrun launcher 启动 distributed processes
DDP 并行策略 replica + gradient sync
FSDP 并行策略 shard model states
ZeRO 并行策略 shard optimizer/grad/param
TP 并行策略 shard layer computation
PP 并行策略 shard layers
CP 并行策略 shard sequence
EP 并行策略 shard experts
Megatron-Core 大模型并行库 TP/PP/CP/EP/DP
DeepSpeed distributed training system ZeRO 等
Ray scheduler worker/resource
vLLM inference engine LLM inference/rollout
verl RL framework RL workflow/orchestration

四十三、实际工程里常见的几套组合

方案 A:普通小模型

复制代码
PyTorch
+
DDP
+
torchrun
+
NCCL

例如:

复制代码
torchrun --nproc-per-node=8 train.py

适合:

复制代码
模型单 GPU 放得下
主要想提升训练吞吐

这是最应该先掌握的方案。


四十四、方案 B:模型单卡训练放不下

常见:

复制代码
PyTorch
+
FSDP
+
NCCL

或者:

复制代码
DeepSpeed ZeRO-3

适合:

复制代码
7B / 14B / 32B
SFT
LoRA / Full FT

具体哪个更合适要看生态。


四十五、方案 C:超大模型预训练

例如:

复制代码
Megatron-Core
+
TP
+
PP
+
DP/FSDP
+
CP
+
NCCL

例如:

复制代码
TP = 8
PP = 4
CP = 2
DP = 8

总 GPU:

8×4×2×8=5128\times4\times2\times8 = 512

这种就是典型大型训练集群玩法。

Megatron 官方的 parallelism guide 也是按这种多维组合方式组织大规模模型训练。(NVIDIA Docs)


四十六、方案 D:LLM RL / verl

你目前更相关的是:

复制代码
                    verl
                     │
                    Ray
                     │
    ┌────────────────┼────────────────┐
    ↓                ↓                ↓
 Actor            Rollout          Reference
    │                │                │
 FSDP/           vLLM              FSDP/
 Megatron                          Megatron
    │                │                │
    └────────────────┼────────────────┘
                     ↓
                    NCCL
                     ↓
                    GPU

这里有两套并行问题同时存在


第一层:角色级资源分配

例如:

复制代码
Actor
Rollout
Reference
Reward

谁用哪些 GPU。

这是:

复制代码
verl / Ray

层面的事情。


第二层:角色内部模型并行

例如 Actor 拿到:

复制代码
GPU 0~7

之后:

复制代码
TP=2
PP=2
DP=2

这是:

复制代码
Megatron / FSDP

层面的事情。

这就是你之前问:

verl rank/world_size 和 group_worker 到底什么关系

为什么会觉得乱。

实际上你同时碰到了:

复制代码
Ray Worker world
+
PyTorch distributed world
+
Megatron parallel groups

三套概念。


四十七、把它画成你目前最需要的一张图

复制代码
                        ┌──────────────┐
                        │    verl      │
                        │ PPO / GRPO   │
                        └──────┬───────┘
                               │
                      Role orchestration
                               │
                        ┌──────▼──────┐
                        │     Ray     │
                        └──────┬──────┘
                               │
             ┌─────────────────┼─────────────────┐
             │                 │                 │
             ▼                 ▼                 ▼
        ActorWorker      RolloutWorker      RewardWorker
             │                 │
             │                 └────── vLLM
             │
             ▼
        Megatron/FSDP
             │
    ┌────────┼─────────┬─────────┐
    ▼        ▼         ▼         ▼
   TP       PP        DP        CP
    │        │         │         │
    └────────┴─────────┴─────────┘
             │
        ProcessGroup
             │
       torch.distributed
             │
             ▼
            NCCL
             │
      ┌──────┼────────┐
      │      │        │
    NVLink   PCIe     IB
      │      │        │
      └──────┼────────┘
             ▼
            GPU
             │
        CUDA Kernel
             │
      Tensor Core / HBM

这基本就是你目前所有分布式问题的总地图。


四十八、我建议你现在不要急着学 Megatron 参数

这是我对你当前学习路径的一个修正。

直接继续研究:

复制代码
tensor_model_parallel_size
pipeline_model_parallel_size
worker_group
rank
world_size

很容易变成:

配置项记住了,但不知道为什么存在。

正确顺序应该是:

复制代码
第一阶段
GPU / CUDA / HBM
        ↓
理解"计算在哪发生"

第二阶段
Process / Rank / World Size
        ↓
理解"谁在执行代码"

第三阶段
NCCL / Collective
        ↓
理解"进程之间怎么交换 Tensor"

第四阶段
DDP
        ↓
理解"为什么 gradient 要同步"

第五阶段
FSDP / ZeRO
        ↓
理解"model state 怎么 shard"

第六阶段
TP
        ↓
理解"计算本身怎么 shard"

第七阶段
PP / CP / EP
        ↓
理解"大模型其他维度怎么切"

第八阶段
Megatron
        ↓
理解"如何把这些并行维度组合"

第九阶段
Ray
        ↓
理解"模型之外的 worker 怎么调度"

第十阶段
verl
        ↓
理解"RL 中 Actor/Rollout/Reference 如何组合"

四十九、真正建议你动手做的第一个实验

不要一上来跑 Megatron。

直接写三个文件。

Experiment 1:观察 rank

复制代码
# 01_rank.py

import os
import torch
import torch.distributed as dist

dist.init_process_group("nccl")

rank = dist.get_rank()
world_size = dist.get_world_size()
local_rank = int(os.environ["LOCAL_RANK"])

torch.cuda.set_device(local_rank)

print(
    f"PID={os.getpid()} "
    f"rank={rank} "
    f"local_rank={local_rank} "
    f"world_size={world_size} "
    f"device={torch.cuda.current_device()}"
)

dist.destroy_process_group()

运行:

复制代码
torchrun --standalone --nproc-per-node=4 01_rank.py

五十、Experiment 2:手写 NCCL AllReduce

复制代码
# 02_allreduce.py

import os
import torch
import torch.distributed as dist

dist.init_process_group("nccl")

rank = dist.get_rank()
local_rank = int(os.environ["LOCAL_RANK"])

torch.cuda.set_device(local_rank)

x = torch.tensor(
    [rank + 1.0],
    device=f"cuda:{local_rank}"
)

print(
    f"rank {rank} before: {x.item()}"
)

dist.all_reduce(x)

print(
    f"rank {rank} after: {x.item()}"
)

dist.destroy_process_group()

运行:

复制代码
torchrun --standalone --nproc-per-node=4 02_allreduce.py

看到:

复制代码
before:

rank0 = 1
rank1 = 2
rank2 = 3
rank3 = 4

after:

rank0 = 10
rank1 = 10
rank2 = 10
rank3 = 10

这一刻:

复制代码
rank
ProcessGroup
NCCL
AllReduce
GPU

五个概念就连起来了。


五十一、Experiment 3:自己模拟 DDP

不要马上:

复制代码
DDP(model)

先自己做。

每张 GPU:

复制代码
loss.backward()

之后:

复制代码
for param in model.parameters():

    dist.all_reduce(
        param.grad,
        op=dist.ReduceOp.SUM
    )

    param.grad /= dist.get_world_size()

也就是:

复制代码
optimizer.zero_grad()

output = model(x)

loss = criterion(output, y)

loss.backward()


# 手写 gradient synchronization
for param in model.parameters():

    dist.all_reduce(
        param.grad,
        op=dist.ReduceOp.SUM
    )

    param.grad /= world_size


optimizer.step()

你突然就会发现:

DDP 并不神秘。

它核心思想就是:

复制代码
每个 GPU
自己 forward
自己 backward

         ↓

gradient AllReduce

         ↓

所有 GPU
得到相同 gradient

         ↓

所有 GPU
执行相同 optimizer.step()

         ↓

因此参数继续保持一致

这个实验的价值非常高。


五十二、再做 Experiment 4:换成真正 DDP

把:

复制代码
for param in model.parameters():
    dist.all_reduce(...)

删除。

改:

复制代码
model = DDP(
    model,
    device_ids=[local_rank]
)

然后比较。

你会真正理解:

复制代码
DDP

只是在替你:

复制代码
gradient hook
bucket
communication overlap
AllReduce

等工作自动化和优化。


五十三、最后你应该形成一个"两个世界"的认知

以后看任何分布式训练代码,先问两个问题。

世界 A:计算世界

复制代码
谁算什么?

DP → 不同 batch

TP → 不同 hidden shard

PP → 不同 layers

CP → 不同 sequence

EP → 不同 experts

世界 B:通信世界

复制代码
计算结果如何交换?

AllReduce

AllGather

ReduceScatter

Broadcast

Send/Recv

AllToAll

所有分布式框架其实都是:

Distributed Training=Compute Partition+Communication+Scheduling\boxed{ Distributed\ Training = Compute\ Partition + Communication + Scheduling }

再展开:

分布式训练=切什么+在哪算+谁和谁通信+什么时候通信\boxed{ 分布式训练 = 切什么 + 在哪算 + 谁和谁通信 + 什么时候通信 }

这是比背:

复制代码
DDP
FSDP
TP
PP

更重要的认知。


五十四、最终把整套地基压缩成这一张脑图

复制代码
                       LLM Training
                            │
             ┌──────────────┼──────────────┐
             │              │              │
           Compute         Memory        Scheduling
             │              │              │
             ▼              ▼              ▼
            CUDA      Parameter/Grad      Process
             │        Optimizer/Act        Worker
             │              │              │
             ▼              │              ▼
            GPU             │          torchrun/Ray
             │              │
      ┌──────┴──────┐       │
      │             │       │
     HBM        Tensor Core │
                            │
                            ▼
                    Parallel Strategy
                            │
         ┌────────┬─────────┼────────┬────────┐
         ▼        ▼         ▼        ▼        ▼
        DP       TP        PP       CP       EP
         │        │         │        │        │
         └────────┴─────────┼────────┴────────┘
                            ▼
                    torch.distributed
                            │
                     ProcessGroup
                            │
             ┌──────────────┼──────────────┐
             ▼              ▼              ▼
         AllReduce       AllGather      Send/Recv
             │              │              │
             └──────────────┼──────────────┘
                            ▼
                           NCCL
                            │
               ┌────────────┼────────────┐
               ▼            ▼            ▼
            NVLink        PCIe          IB/RoCE
               │            │            │
               └────────────┼────────────┘
                            ▼
                        GPU Cluster

你现在先牢牢记住 8 句话

  1. GPU 是独立计算设备,每张卡有自己的 HBM。

  2. CUDA 负责让程序在 NVIDIA GPU 上执行计算。

  3. NCCL 负责 GPU 之间高效交换 Tensor。

  4. PyTorch 分布式训练通常一个进程绑定一张 GPU。

  5. rank 是进程身份,不应该简单等同于物理 GPU 编号。

  6. ProcessGroup 决定哪些 rank 相互通信。

  7. DDP/FSDP/TP/PP 本质区别是"切什么"。

  8. Megatron/verl/Ray 位于不同层,不能放在一起比较。

其中第 5~7 条真正掌握以后,你后面看 Megatron 和 verl 的源码会容易非常多。

我建议接下来按这个顺序继续

下一块最值得单独拆出来的是 「PyTorch 分布式运行时」,把下面这条链完全用代码跑通:

复制代码
Linux process
    ↓
torchrun
    ↓
RANK / LOCAL_RANK / WORLD_SIZE
    ↓
init_process_group
    ↓
ProcessGroup
    ↓
NCCL communicator
    ↓
all_reduce
    ↓
DDP backward hook

然后再进入:

复制代码
DDP
→ FSDP
→ TP
→ Megatron 多维 ProcessGroup
→ verl WorkerGroup

这样你之后遇到 rank 0 为什么占 GPU2world_size 到底是谁的 world_sizeTP group 和 Ray WorkerGroup 什么关系为什么 vLLM 又有自己的 TP rank 这些问题,就不是靠背配置,而是可以从底层自己推出答案。(PyTorch Docs)

相关推荐
国科安芯1 小时前
星载CAN总线通信网络中抗辐射MCU的通信可靠性设计分析
网络·人工智能·分布式·单片机·嵌入式硬件·架构
随遇而安zx4 小时前
SpringCloud---Spring Cloud 分布式任务调度(XXL-Job / ElasticJob)
分布式·spring cloud
天远Date Lab7 小时前
分布式微服务实战:基于天远车辆估值构建自动化车价评估网关
人工智能·分布式·微服务·自动化
随遇而安zx8 小时前
SpringCloud 分布式链路追踪 设计思想与源码深度解析
分布式·spring cloud
迪飞特科技9 小时前
分布式事务下 Spring 声明式事务失效的 3 种修复方案
分布式·spring·wpf
这个DBA有点耶1 天前
2026年分布式数据库有哪些主流选择?先评估这5个维度再决定
数据库·分布式·dba
Lost of 程序猿2 天前
ASP.NET Core Saga 分布式事务深度实战:备件采购跨服务长流程,如何保证“要么全成,要么全回“
分布式·后端·asp.net
XiYang-DING2 天前
地图城市缓存优化:ApplicationReadyEvent + Caffeine + Redis + Redisson 分布式锁
redis·分布式·缓存
2601_962175662 天前
RabbitMQ 的工作模式
分布式·rabbitmq