分布式训练深度解析:Data / Tensor / Pipeline / Sequence Parallelism
6694 字
33 分钟
分布式训练深度解析:Data / Tensor / Pipeline / Sequence Parallelism
1. 引言:为什么需要分布式训练
1.1 模型规模的增长
┌─────────────────────────────────────────────────────────────┐│ LLM 参数规模增长趋势 │├─────────────────────────────────────────────────────────────┤│ ││ 2018: ELMo ~ 94M 参数 ││ 2018: BERT-Large ~ 340M 参数 ││ 2019: GPT-2 ~ 1.5B 参数 ││ 2020: GPT-3 ~ 175B 参数 ││ 2021: PaLM ~ 540B 参数 ││ 2023: GPT-4 ~ 1.8T 参数 (估计) ││ 2024: Gemini Ultra ~ 1.5T 参数 ││ ││ 内存需求(FP16): ││ ───────────────────────────────────────────────────────── ││ 1B 参数 → 2 GB ││ 7B 参数 → 14 GB ││ 70B 参数 → 140 GB ││ 175B 参数 → 350 GB ││ 1T 参数 → 2 TB ││ ││ 单卡 A100 80GB:仅能放下 ~35B 参数的模型 ││ │└─────────────────────────────────────────────────────────────┘1.2 计算需求
class ComputeRequirements: """ 计算需求分析 """
def flops_estimation(self): """ 训练 LLM 的 FLOPs 估算
对于 decoder-only transformer: FLOPs ≈ 2 × N × D × G
其中: - N: 模型参数量 - D: 训练 tokens 数 - G: 训练轮数 """ return { "gpt3_175b": { "params": "175B", "tokens": "300B", "flops": "~3.1 × 10^23 FLOPs", "a100_petaflops": "~3.1 × 10^5 PF", "a100_8x_90_days": "~1.5 × 10^6 PF-days", }, "llama2_70b": { "params": "70B", "tokens": "2T (预训练)", "flops": "~1.4 × 10^24 FLOPs", }, }
def time_estimation(self): """ 训练时间估算 """ return """ 单卡 A100 (312 TFLOPS BF16):
LLaMA-2 70B, 2T tokens:
Theoretical time = FLOPs / FLOPS = 1.4 × 10^24 / 3.12 × 10^14 = 4.5 × 10^9 seconds = ~144 years
结论: 单卡训练不现实
需要的 GPU 数量:
1000 GPUs → ~53 天 4000 GPUs → ~13 天 """1.3 并行策略全景
┌─────────────────────────────────────────────────────────────┐│ 分布式训练并行策略 │├─────────────────────────────────────────────────────────────┤│ ││ Data Parallelism (DP) ││ ───────────────────────────────────────────────────────── ││ → 复制模型,并行处理不同数据分片 ││ → 最简单,提升吞吐量 ││ → 通信: AllReduce (梯度) ││ ││ Tensor Parallelism (TP) ││ ───────────────────────────────────────────────────────── ││ → 切分单个层的权重到多卡 ││ → 用于超大型单层 (如 LLM 的 FFN/Attention) ││ → 通信: AllReduce / AllGather ││ ││ Pipeline Parallelism (PP) ││ ───────────────────────────────────────────────────────── ││ → 按层切分模型到多卡 ││ → 减少单卡显存需求 ││ → 通信: P2P (点对点) ││ ││ Sequence Parallelism (SP) ││ ───────────────────────────────────────────────────────── ││ → 按序列维度切分 ││ → 配合 TP 处理超长上下文 ││ → 通信: AllReduce ││ ││ 组合: 2D / 3D Parallelism ││ → TP × PP × DP ││ → 千卡级别训练的必备 ││ │└─────────────────────────────────────────────────────────────┘1.4 本系列文章关联
| 文章 | 关联 |
|---|---|
| PEFT/LoRA 深度解析 | 高效微调减少计算需求 |
| 模型量化深度解析 | 训练后量化减少显存 |
| vLLM 深度解析 | 分布式推理技术 |
2. Data Parallelism
2.1 核心原理
class DataParallelism: """ Data Parallelism """
def basic_idea(self): """ 核心思想
将训练数据分成多个 shard,每个 GPU 持有完整模型副本, 并行处理不同数据,最后同步梯度。 """ return """ Data Parallelism 示意:
GPU 0: [Model Copy] → Batch 0 → Forward → Backward → Grad 0 GPU 1: [Model Copy] → Batch 1 → Forward → Backward → Grad 1 GPU 2: [Model Copy] → Batch 2 → Forward → Backward → Grad 2 GPU 3: [Model Copy] → Batch 3 → Forward → Backward → Grad 3
↓ Gradient AllReduce ↓
Avg(Grad 0, Grad 1, Grad 2, Grad 3)
↓ 每个 GPU 用平均梯度更新 ↓
特点: → 每个 GPU 有完整模型 → 只通信梯度 → 增加 GPU 数量可线性提升吞吐量 """
def gradient_synchronization(self): """ 梯度同步 """ return ''' class DataParallelGradientSync: """梯度同步实现"""
def allreduce_sync(self, gradients, world_size): """ 同步 SGD (所有 GPU 同步等待)
每个 GPU 计算梯度 → AllReduce 同步梯度 → 所有 GPU 都得到相同的平均梯度 → 各自更新
优点: 收敛行为与单卡相同 缺点: 最慢的 GPU 拖慢整体 """ import torch.distributed as dist
# 对每个参数梯度执行 AllReduce for param in model.parameters(): dist.all_reduce(param.grad, op=dist.ReduceOp.SUM) param.grad /= world_size
def distributed_sampler(self, dataset, num_gpus): """数据分片""" return torch.utils.data.DistributedSampler( dataset, num_replicas=num_gpus, rank=self.rank, ) '''
def memory_analysis(self): """ 显存分析 """ return { "model_weights": "N GB (完整副本)", "optimizer_states": "2N GB (Adam: m + v, FP32)", "gradients": "2N GB (FP32)", "activations": "取决于 batch size", "total_per_gpu": "~6N GB + activations",
"example_7b_fp16": { "model": "14 GB", "optimizer": "28 GB", "gradients": "28 GB", "activations_8": "16 GB (batch=8)", "total": "~86 GB per GPU", "a100_80gb": "不够,需要进一步优化", }, }2.2 DistributedDataParallel (DDP)
class DDPImplementation: """ PyTorch DDP 实现 """
def basic_usage(self): """ 基本用法 """ return ''' import torch import torch.distributed as dist import torch.multiprocessing as mp from torch.nn.parallel import DistributedDataParallel as DDP
def setup(rank, world_size): os.environ["MASTER_ADDR"] = "localhost" os.environ["MASTER_PORT"] = "12355" dist.init_process_group("nccl", rank=rank, world_size=world_size) torch.cuda.set_device(rank)
def train(rank, world_size): setup(rank, world_size)
# 创建模型 model = MyModel().cuda()
# 包装为 DDP model = DDP(model, device_ids=[rank])
# 数据 dataset = MyDataset() sampler = DistributedSampler(dataset) dataloader = DataLoader(dataset, sampler=sampler)
# 训练循环 for epoch in range(num_epochs): for batch in dataloader: loss = model(batch).sum() loss.backward() optimizer.step() optimizer.zero_grad()
dist.destroy_process_group()
# 启动 mp.spawn(train, args=(world_size,), nprocs=world_size) '''
def gradient_bucket(self): """ Gradient Bucketing 优化
DDP 使用 bucket 机制减少通信开销 """ return """ 问题: → 逐参数同步梯度效率低 → 每个小梯度都需要完整的 AllReduce 通信
解决: Gradient Bucketing
┌─────────────────────────────────────────────────────┐ │ Gradients │ │ [g1][g2][g3][g4][g5][g6][g7][g8]... │ └─────────────────────────────────────────────────────┘ ↓ 分桶 ┌─────────────────────────────────────────────────────┐ │ Bucket 0: [g1][g2][g3][g4] (size = 4 params) │ │ Bucket 1: [g5][g6][g7][g8] (size = 4 params) │ │ ... │ └─────────────────────────────────────────────────────┘ ↓ 异步 AllReduce ┌─────────────────────────────────────────────────────┐ │ Bucket 0: AllReduce 完成 │ │ Bucket 1: AllReduce 进行中... │ │ ... │ └─────────────────────────────────────────────────────┘
效果: → 减少通信次数 (从 N 次到 N/bucket_size 次) → 重叠计算和通信 → 带宽利用率更高 """
def broadcast_buffers(self): """ Buffer 广播 """ return """ DDP 的 Buffer 处理:
1. 同步 Buffers - BatchNorm 的 running_mean/std - 使用 AllReduce 同步 - 每个 GPU 广播自己的 buffers
2. 非同步 Buffers - Dropout 的随机状态 - 不需要同步
3. 注册 Hook - 可以在梯度同步时执行自定义操作 - 用途: gradient clipping, monitoring """2.3 ZeRO 优化
class ZeROOptimization: """ ZeRO (Zero Redundancy Optimizer)
切分优化器状态以减少显存冗余 """
def stages(self): """ ZeRO 三个阶段 """ return { "ZeRO-1": { "name": "分片优化器状态", "description": "将 Adam optimizer states (m, v) 分片到各 GPU", "memory_reduction": "4x (75% reduction)", "communication": "与 DDP 相同", }, "ZeRO-2": { "name": "分片优化器状态 + 梯度", "description": "ZeRO-1 + 梯度也分片", "memory_reduction": "8x (~87.5% reduction)", "communication": "略增 (allreduce 变成reduce-scatter + allgather)", }, "ZeRO-3": { "name": "分片所有状态", "description": "ZeRO-2 + 模型参数也分片", "memory_reduction": "线性减少 (N 倍)", "communication": "显著增加 (需要动态 gather 参数)", }, }
def zero_stage1(self): """ ZeRO-1 实现 """ return ''' # ZeRO-1: 只分片优化器状态
# 优化器状态 (Adam): # m (first moment) - FP32 # v (second moment) - FP32
# 每个参数 p 的优化器状态: # p_fp32, m, v = 4 + 4 + 4 = 12 bytes per param
# 原来 (DDP): 每个 GPU 持有完整的 m, v # ZeRO-1: 每个 GPU 只持有 1/N 的 m, v
class ZeRO1Optimizer: def __init__(self, model, world_size, rank): self.world_size = world_size self.rank = rank self.param_partitions = {} # rank → params
# 将参数均匀分配到各 GPU all_params = list(model.parameters()) for i, param in enumerate(all_params): target_rank = i % world_size self.param_partitions[target_rank].append(param)
def step(self): # 只更新本地 partition 的参数 for param in self.param_partitions[self.rank]: # 更新 m, v (只存储本地部分) self._update_adam_state(param) '''
def zero_vs_ddp_memory(self): """ 显存对比 """ return """ 70B 参数模型,FP16 权重,Adam 优化器:
DDP (完全副本): ┌──────────────────────────────────────────────────┐ │ 权重 (FP16): 140 GB │ │ 梯度 (FP32): 280 GB │ │ 优化器状态 (FP32): 280 GB │ │ 激活值: ~50 GB │ │ ───────────────────────────────────────────── │ │ 总计: ~750 GB │ │ 需要: A100 80GB × 10 │ └──────────────────────────────────────────────────┘
ZeRO-3 + 8 GPUs: ┌──────────────────────────────────────────────────┐ │ 权重分片: 140 / 8 = 17.5 GB │ │ 梯度分片: 280 / 8 = 35 GB │ │ 优化器状态分片: 280 / 8 = 35 GB │ │ 激活值: ~50 GB │ │ ───────────────────────────────────────────── │ │ 总计: ~137.5 GB per GPU │ │ 需要: A100 80GB × 8 + Offload │ └──────────────────────────────────────────────────┘ """3. Tensor Parallelism
3.1 原理与动机
class TensorParallelism: """ Tensor Parallelism
将单层权重矩阵沿某个维度切分到多个 GPU """
def why_tensor_parallel(self): """ 为什么需要 Tensor Parallelism
对于 LLaMA-2 70B: """ return { "embedding": "4096 × 32000 = 131M", "attention": { "q_proj": "4096 × 4096 = 16.8M × 3 = 50.4M", "k_proj": "4096 × 4096", "v_proj": "4096 × 4096", "o_proj": "4096 × 4096 = 16.8M", "total_attention": "67.2M per layer × 80 layers", }, "ffn": { "gate_proj": "11008 × 4096 = 45M", "up_proj": "4096 × 11008 = 45M", "down_proj": "11008 × 4096 = 45M", "total_ffn": "135M per layer × 80 layers", },
"largest_layer": "FFN up_proj: 45M params = 90 GB FP16", "problem": "单层 FFN 比单卡显存还大!", "solution": "切分到多卡", }
def matrix_partitioning(self): """ 矩阵分块策略 """ return """ 矩阵乘法 Y = XW
W: (M, N) X: (B, M) Y: (B, N)
┌─────────────────────────────────────────────────┐ │ W (M×N) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ W0 │ │ W1 │ │ W2 │ │ │ │(M, N/3) │ │(M, N/3) │ │(M, N/3) │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ GPU 0 GPU 1 GPU 2 │ └─────────────────────────────────────────────────┘
X: (B, M) 复制到所有 GPU
Y0 = X @ W0 (B, N/3) → GPU 0 Y1 = X @ W1 (B, N/3) → GPU 1 Y2 = X @ W2 (B, N/3) → GPU 2
输出拼接: Y = [Y0, Y1, Y2] """3.2 Column Parallel 与 Row Parallel
class ParallelismTypes: """ 列并行与行并行 """
def column_parallel(self): """ Column Parallel
切分权重矩阵的列维度 """ return """ 用于 Linear 层的 QKV 投影:
W_qkv: (d_model, 3 * d_hidden)
Column Parallel (切分 3*d_hidden):
GPU 0: W_q0 (d_model, d_hidden) GPU 1: W_k0 (d_model, d_hidden) GPU 2: W_v0 (d_model, d_hidden) ...
前向: X: (B, d_model) 复制 ↓ X @ W_q0 = Q0 (B, d_hidden) X @ W_k0 = K0 (B, d_hidden) X @ W_v0 = V0 (B, d_hidden)
通信: 无 (输出已经是分片的)
注意: 需要 AllGather 合并 Q/K/V 以进行 Attention """
def row_parallel(self): """ Row Parallel
切分权重矩阵的行维度 """ return """ 用于 Linear 层的输出投影:
W_o: (d_hidden, d_model)
Row Parallel (切分 d_hidden):
GPU 0: W_o0 (d_hidden/2, d_model) GPU 1: W_o1 (d_hidden/2, d_model)
前向: X0: (B, d_hidden/2) ← 从 Attention 来 X1: (B, d_hidden/2) ← 从 Attention 来 ↓ X0 @ W_o0 = Y0 (B, d_model) X1 @ W_o1 = Y1 (B, d_model)
Y = Y0 + Y1 ← AllReduce (求和)
通信: AllReduce """
def attention_parallel(self): """ Attention 中的 Tensor Parallelism """ return """ Attention 层并行化:
┌─────────────────────────────────────────────────────────┐ │ Attention Layer │ ├─────────────────────────────────────────────────────────┤ │ │ │ Input X │ │ ↓ │ │ ┌─────────────────┐ │ │ │ QKV Projection │ Column Parallel (每个 GPU 3 heads) │ │ └─────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Q 分布到所有 GPU │ │ │ │ K, V 需要 AllGather 收集 │ │ │ │ 各自计算部分 attention │ │ │ │ Output 经过 AllReduce │ │ │ └─────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────┐ │ │ │ Output Proj │ Row Parallel │ │ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘
Megatron-LM 风格: 将 heads 分组,每组分配给不同 GPU """3.3 通信分析
class TPCommunication: """ Tensor Parallelism 通信分析 """
def communication_pattern(self): """ 通信模式 """ return """ Transformer 层的 TP 通信:
┌──────────────────────────────────────────────────────────┐ │ Layer N │ ├──────────────────────────────────────────────────────────┤ │ │ │ Input X │ │ ↓ │ │ QKV: Column Parallel → 无通信 │ │ ↓ │ │ Q AllGather (all TP ranks) → N-1 次广播 │ │ K AllGather (all TP ranks) → N-1 次广播 │ │ V AllGather (all TP ranks) → N-1 次广播 │ │ ↓ │ │ Attention Score (本地) │ │ ↓ │ │ Softmax → 无通信 │ │ ↓ │ │ Attention × V → AllReduce (求和输出) │ │ ↓ │ │ Output Proj: Row Parallel → AllReduce │ │ ↓ │ │ FFN Gate: Column Parallel → 无通信 │ │ ↓ │ │ FFN Up: Column Parallel → 无通信 │ │ ↓ │ │ FFN AllGather → AllGather │ │ ↓ │ │ FFN Down: Row Parallel → AllReduce │ │ ↓ │ │ Output X │ │ │ └──────────────────────────────────────────────────────────┘
每层通信: 4 AllGather + 2 AllReduce """
def bandwidth_requirement(self): """ 带宽需求 """ return { "per_layer_per_gpu": "2 × seq_len × hidden_size × tp_size (bytes)", "for_70b_seq_4096": "~128 MB per layer per forward", "forward_per_epoch": "~128 MB × 80 layers × 1M steps", "backward": "2x forward",
"bandwidth_analysis": { "A100_NVLink": "900 GB/s bidirectional", "Gen1_InfiniBand": "25 GB/s", "Gen2_InfiniBand": "50 GB/s", "Gen4_InfiniBand": "200 GB/s", },
"recommendation": "TP 需要 NVLink 或高速互联", }4. Pipeline Parallelism
4.1 基本原理
class PipelineParallelism: """ Pipeline Parallelism
将模型按层切分到多个 GPU """
def layer_distribution(self): """ 层分布 """ return """ 32 层模型,4-GPU Pipeline Parallel:
GPU 0: Layers 0-7 (embedding + first 8 layers) GPU 1: Layers 8-15 (middle layers) GPU 2: Layers 16-23 (middle layers) GPU 3: Layers 24-31 (last 8 layers + output)
Forward: ┌─────────────────────────────────────────────────────────┐ │ GPU 0: [L0][L1][L2][L3][L4][L5][L6][L7] → hidden_7 │ │ ↓ │ │ GPU 1: [L8][L9][L10][L11][L12][L13][L14][L15]→hidd_15│ │ ↓ │ │ GPU 2: [L16]...[L23] → hidden_23 │ │ ↓ │ │ GPU 3: [L24]...[L31] → output │ └─────────────────────────────────────────────────────────┘ """
def pipeline_bubble(self): """ Pipeline Bubble 问题 """ return """ Naive Pipeline (无 interleaving):
时间 →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→
Microbatch 1: [GPU0][GPU1][GPU2][GPU3] Microbatch 2: [GPU0][GPU1][GPU2][GPU3] Microbatch 3: [GPU0][GPU1][GPU2][GPU3] Microbatch 4: [GPU0][GPU1][GPU2][GPU3]
Bubble (空闲): [ ][ ][ ][GPU0][ ][ ][ ][GPU1][ ][ ][ ][GPU2][ ][ ][ ][GPU3]
问题: → Bubble 数量 ≈ (P-1) / num_microbatches → P=4, microbatches=4 → 75% bubble! → GPU 利用率极低 """
def interleaving(self): """ Interleaving 减少 Bubble """ return """ Interleaving (degree=4):
每个 GPU 处理多个 chunks:
GPU 0: [c0_0][c1_0][c2_0][c3_0][c4_0][c5_0]... GPU 1: [c0_1][c1_1][c2_1][c3_1][c4_1][c5_1]... GPU 2: [c0_2][c1_2][c2_2][c3_2][c4_2][c5_2]... GPU 3: [c0_3][c1_3][c2_3][c3_3][c4_3][c5_3]...
每个 microbatch 在不同 GPU 上轮流执行 Bubble 大幅减少
Bubble 比例 ≈ 1 / (num_microbatches × degree) """4.2 调度策略
class ScheduleStrategies: """ 调度策略 """
def forward_backward(self): """ Forward-Backward-Pipeline """ return """ GPipe 调度 (Channeh et al., 2019):
1F1B 模式 (One Forward, One Backward):
Forward: [F0][F1][F2][F3] Backward: [B3][B2][B1][B0]
完整调度 ( microbatches=8 ):
Stage 0: [F0][F1][F2][F3][B3][B2][B1][B0] Stage 1: [F0][F1][F2][F3][B3][B2][B1][B0] Stage 2: [F0][F1][F2][F3][B3][B2][B1][B0] Stage 3: [F0][F1][F2][F3][B3][B2][B1][B0]
特点: → Forward 完成后立即开始 Backward → 稳定状态时每个 GPU 只做一个操作 → Bubble 只出现在开始和结束 """
def interleaved_schedule(self): """ Interleaved Schedule """ return """ Interleaved 1F1B:
每个 stage 被分成多个 sub-stages:
GPU 0: [S0_0][S0_1]...[S0_k] (layers 0..k, k+1..2k) GPU 1: [S1_0][S1_1]...[S1_k] (layers 2k..3k, ...)
调度: F0_0, F0_1, F1_0, F0_2, F1_1, F1_2, ...
优势: → 减少通信等待 → 更好利用计算和通信重叠 """
def async_pipeline(self): """ 异步 Pipeline (实验性) """ return """ Chimera / PipeDream 风格:
思想: Forward 和 Backward 可以异步
GPU 0: [F0][F1][F2][F3]...[F0][F1][F2][F3]... (连续 forward) GPU 1: [F0][F1][F2][F3]...[B0][B1][B2][B3]... (backward 滞后)
权衡: → 更高 GPU 利用率 → 收敛性可能受影响 → 需要 careful weight versioning """4.3 PipeDream 实现
class PipeDreamImplementation: """ PipeDream 风格实现 """
def weight_stashing(self): """ Weight Stashing
保存每个 microbatch 对应的权重版本 """ return ''' class PipeDreamModel: """PipeDream 实现"""
def __init__(self, stages, num_microbatches): self.stages = stages self.num_microbatches = num_microbatches self.weights = {} # microb_id → weights self.optimizer = Adam(self.stages.parameters())
def forward_backward(self, input_batch, microb_ids): """ 1F1B 调度 """ # Forward for mb_id in microb_ids: self.stages[mb_id % len(self.stages)].forward( self.weights[mb_id] # 使用对应版本的权重 )
# Backward for mb_id in reversed(microb_ids): self.stages[mb_id % len(self.stages)].backward( self.weights[mb_id] # 梯度累积到正确版本 )
# Update (每隔一段时间) self.optimizer.step() self.optimizer.zero_grad() '''
def microbatch_vs_pipeline(self): """ Microbatch 与 Pipeline 对比 """ return { "small_microbatch": { "pros": "更好的负载均衡,减少 bubble", "cons": "更多通信开销,更多 activation 显存", }, "large_microbatch": { "pros": "减少通信,简化调度", "cons": "bubble 比例增加", },
"recommendation": { "global_batch_size": "根据 GPU 数量调整", "microbatch_size": "尽量让 pipeline 填满", "num_microbatches": "P × 4 ~ P × 10", }, }5. Sequence Parallelism
5.1 动机与场景
class SequenceParallelism: """ Sequence Parallelism
沿序列维度切分 """
def why_sequence_parallel(self): """ 为什么需要 Sequence Parallelism
当 TP 处理超长序列时: """ return """ 场景: 100K tokens 序列, TP=8
问题: 1. Attention 计算: O(N²) 显存 → 即使 TP 切分 Q/K/V,单个 head 仍需处理 N/8 tokens
2. LayerNorm / Dropout → 需要看到完整序列 → 成为瓶颈
3. 显存分布不均 → embedding / output 层: 参数量大 → attention / ffn: 激活值大
解决: Sequence Parallelism
沿序列维度切分: seq_len = 100K SP = 8 每个 GPU 处理 12.5K tokens """
def layerwise_operations(self): """ 需要跨序列通信的操作 """ return { "attention": "需要 AllGather Q/K/V,然后 AllReduce 输出", "layernorm": "需要 AllReduce 计算 mean/variance", "dropout": "可以在本地处理", "embedding": "需要 AllGather (输入) 和 AllReduce (输出)", "loss": "需要 AllReduce", }5.2 环形通信
class RingCommunication: """ 环形通信实现 """
def ring_allreduce(self): """ Ring AllReduce """ return """ Ring AllReduce 用于 Sequence Parallelism:
4 GPUs, 每个持有 N/4 tokens 的部分和
Step 1: Reduce Scatter ┌─────────────────────────────────────────────────────┐ │ GPU 0: [a0] → [a0+b0+c0+d0] │ │ GPU 1: [b0] → [b0+c0+d0+a0] │ │ GPU 2: [c0] → [c0+d0+a0+b0] │ │ GPU 3: [d0] → [d0+a0+b0+c0] │ └─────────────────────────────────────────────────────┘
Step 2: AllGather ┌─────────────────────────────────────────────────────┐ │ GPU 0: [sum] → [sum, sum, sum, sum] │ │ GPU 1: [sum] → [sum, sum, sum, sum] │ │ GPU 2: [sum] → [sum, sum, sum, sum] │ │ GPU 3: [sum] → [sum, sum, sum, sum] │ └─────────────────────────────────────────────────────┘
通信量: 2 × (N-1) / num_gpus """
def ring_attention(self): """ Ring Attention """ return """ Ring Attention 实现:
4 GPUs, 序列长度 N, 每个 GPU 处理 N/4 tokens
Q[i]: 本地查询 K[i], V[i]: 本地键值
1. Q 保持在本地
2. K, V 通过环形传递: GPU 0: Q0 + [K0,V0] → 计算 [K0,V0] 的 attention GPU 0: Q0 + [K1,V1] → 计算 [K1,V1] 的 attention (接收自 GPU 1) GPU 0: Q0 + [K2,V2] → 计算 [K2,V2] 的 attention (接收自 GPU 2) GPU 0: Q0 + [K3,V3] → 计算 [K3,V3] 的 attention (接收自 GPU 3)
3. 累加所有 attention 结果
通信: 每个 GPU 接收 K,V N-1 次 """6. 混合并行策略
6.1 2D / 3D Parallelism
class HybridParallelism: """ 混合并行策略 """
def tensor_pipeline_2d(self): """ TP × PP (2D Parallelism) """ return """ 2D Parallelism: TP × PP
例子: 70B 模型, 16 GPUs
TP = 4, PP = 4:
GPU Layout: ┌─────────┬─────────┬─────────┬─────────┐ │ Stage 0 │ Stage 1 │ Stage 2 │ Stage 3 │ │ (TP=4) │ (TP=4) │ (TP=4) │ (TP=4) │ └─────────┴─────────┴─────────┴─────────┘
每个 Stage 有 4 GPUs (TP group) 4 个 Stages (Pipeline)
通信: - TP 内部: AllReduce (NVLink) - PP 跨 stage: P2P (IB) """
def dp_tp_pp_3d(self): """ DP × TP × PP (3D Parallelism) """ return """ 3D Parallelism: DP × TP × PP
例子: 175B 模型, 64 GPUs
DP = 8, TP = 4, PP = 2: 总计: 8 × 4 × 2 = 64 GPUs
结构: ┌─────────────────────────────────────────────────────┐ │ DP Groups (8) │ │ ┌──────────┬──────────┬──────────┬──────────┐ │ │ │ PP 0 │ PP 1 │ PP 0 │ PP 1 │ ... │ │ │ (TP=4) │ (TP=4) │ (TP=4) │ (TP=4) │ │ │ └──────────┴──────────┴──────────┴──────────┘ │ │ ↑ │ │ Pipeline Communication │ └─────────────────────────────────────────────────────┘
内存估算 (175B FP16): - TP=4: 每个 GPU 持有 1/4 的单层权重 - PP=2: 每 GPU 持有 1/2 的层数 - DP=8: 每个 DP replica 独立
每 GPU: 175B / (4 × 2) × 2 bytes = ~44 GB + activations + optimizer """
def communication_topology(self): """ 通信拓扑感知 """ return """ 现代 GPU 集群的拓扑:
Node 0: [GPU 0] [GPU 1] [GPU 2] [GPU 3] Node 1: [GPU 4] [GPU 5] [GPU 6] [GPU 7] ...
带宽层次: ┌─────────────────────────────────────────────────────┐ │ NVLink (within node): ~900 GB/s │ │ PCIe (within node): ~64 GB/s │ │ InfiniBand (cross node): ~50 GB/s │ └─────────────────────────────────────────────────────┘
最佳并行策略: ┌─────────────────────────────────────────────────────┐ │ TP: within node (NVLink) │ │ PP: across nodes (IB) │ │ DP: across all (梯度同步相对较小) │ └─────────────────────────────────────────────────────┘ """6.2 Megatron-LM 架构
class MegatronArchitecture: """ Megatron-LM 架构 """
def parallel_layout(self): """ 并行布局 """ return ''' # Megatron-LM 3D Parallel
# 配置: # tensor_model_parallel_size = 4 (TP) # pipeline_model_parallel_size = 4 (PP) # data_parallel_size = N / (TP × PP)
# 模型构建 model = TransformerBlock( tensor_parallel=TensorParallel, pipeline_parallel=PipelineParallel, )
# Forward 流程: # 1. TP group 内广播输入 # 2. Pipeline 调度 # 3. 各 GPU 计算本地层 # 4. PP 跨 GPU 传递 hidden states '''
def memory_estimation(self): """ 显存估算 """ return """ LLaMA-2 70B, 4096 sequence, batch=1, BF16:
单 GPU (无并行): - 模型: 140 GB - 激活值: ~50 GB - 优化器: 280 GB - 总计: ~470 GB
TP=8, PP=4: - TP: 每层权重 / 8 - PP: 层数 / 4 - 每 GPU 权重: 140 / 32 = 4.4 GB - 每 GPU 激活: 50 / 8 = 6.25 GB (SP 配合) - 每 GPU 优化器: 280 / 32 = 8.75 GB - 总计: ~19.4 GB per GPU
结论: 可以在 A100 80GB 上训练 """7. 主流框架实战
7.1 PyTorch FSDP
class FSDPImplementation: """ FSDP (Fully Sharded Data Parallel) """
def basic_usage(self): """ FSDP 基本用法 """ return ''' import torch from torch.distributed.fsdp import ( FullyShardedDataParallel as FSDP, ShardingStrategy, MixedPrecision, BackwardPrefetch, ) from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
# 混合精度 fp16_policy = MixedPrecision( param_dtype=torch.float16, reduce_dtype=torch.float16, buffer_dtype=torch.float16, )
# 定义模型 model = Transformer()
# FSDP 包装 fsdp_model = FSDP( model, sharding_strategy=ShardingStrategy.FULL_SHARD, # ZeRO-3 mixed_precision=fp16_policy, auto_wrap_policy=transformer_auto_wrap_policy, backward_prefetch=BackwardPrefetch.BACKWARD_PRE, device_id=torch.cuda.current_device(), )
# 训练 for batch in dataloader: loss = fsdp_model(batch) loss.backward() # FSDP 自动分片梯度 fsdp_model.optimize_step() '''
def sharding_strategies(self): """ 分片策略 """ return { "FULL_SHARD": "参数 + 梯度 + 优化器状态全分片 (ZeRO-3)", "SHARD_GRAD_OP": "梯度 + 优化器状态分片 (ZeRO-2)", "NO_SHARD": "不分片,类似 DDP", "HYBRID_SHARD": "本地分片 + 跨节点通信",
"recommendation": { "small_model": "SHARD_GRAD_OP (低通信开销)", "large_model": "FULL_SHARD (最低显存)", "multi_node": "HYBRID_SHARD (减少跨节点通信)", }, }
def cpu_offload(self): """ CPU Offload """ return ''' # CPU Offload 配置
from torch.distributed.fsdp import CPUOffload
fsdp_model = FSDP( model, sharding_strategy=ShardingStrategy.FULL_SHARD, cpu_offload=CPUOffload( offload_params=True, # 将参数卸载到 CPU pin_memory=True, ), )
# 效果: # → 进一步减少 GPU 显存 # → 但增加 CPU-GPU 传输开销 # → 适合超大型模型 '''7.2 DeepSpeed
class DeepSpeedImplementation: """ DeepSpeed 实现 """
def basic_config(self): """ DeepSpeed 配置 """ return ''' # ds_config.json
{ "train_batch_size": 32, "gradient_accumulation_steps": 4, "fp16": { "enabled": true }, "zero_optimization": { "stage": 3, "offload_optimizer": { "device": "cpu", "pin_memory": true }, "offload_param": { "device": "cpu" }, "overlap_comm": true, "contiguous_gradients": true, "sub_group_size": 1e9 }, "gradient_clipping": 1.0, "wall_clock_breakdown": false } '''
def training_loop(self): """ 训练循环 """ return ''' import deepspeed
# 初始化 DeepSpeed model, optimizer, _, _ = deepspeed.initialize( model=model, optimizer=optimizer, config=ds_config, dist_init_required=True, )
# 训练 for batch in dataloader: loss = model(batch) model.backward(loss) model.step() '''
def pipeline_parallel(self): """ DeepSpeed Pipeline Parallelism """ return ''' # Pipeline 配置
{ "pipeline": { "pipeline_parallel_size": 4, "num_stages": 4, "pipeline_checkpoint": true }, "zero_optimization": { "stage": 1, # ZeRO-1 (配合 PP 使用) "reduce_bucket_size": 5e8, "allgather_bucket_size": 5e8 } }
# 注意: PP 和 ZeRO-1/2 配合效果最好 # ZeRO-3 会增加 PP 的通信量 '''7.3 ColossalAI
class ColossalAIImplementation: """ ColossalAI 实现 """
def hybrid_parallel(self): """ 混合并行配置 """ return ''' from colossalai.booster import Booster from colossalai.booster.plugin import HybridParallelPlugin from colossalai.cluster import DistCoordinator
# 插件配置 plugin = HybridParallelPlugin( tp_size=4, # Tensor Parallelism pp_size=2, # Pipeline Parallelism zero_stage=1, # ZeRO-1 num_microbatches=8, # Pipeline microbatches )
# 启动 Booster booster = Booster(plugin=plugin)
model, optimizer, dataloader, criterion = booster.enable( model=model, optimizer=optimizer, dataloader=dataloader, criterion=criterion, )
# 训练循环 for batch in dataloader: outputs = model(batch) loss = criterion(outputs, batch["labels"]) booster.backward(loss, optimizer) booster.optimize_step(optimizer) '''8. 通信优化
8.1 通信计算重叠
class CommunicationComputationOverlap: """ 通信与计算重叠 """
def gradient_computation_overlap(self): """ 梯度计算与 AllReduce 重叠 """ return """ 异步梯度同步:
传统 (同步): ┌─────────────────────────────────────────────────────┐ │ [Forward] [AllReduce Grad] [Backward] [Optimizer] │ └─────────────────────────────────────────────────────┘
重叠 (异步): ┌─────────────────────────────────────────────────────┐ │ [F] [B1] [AR1] [B2] [AR2] [B3] [AR3] ... │ │ ↑ │ │ Backward 和 AllReduce 并行 │ └─────────────────────────────────────────────────────┘
实现: - 使用梯度 bucket - 前一个 bucket 的 AllReduce 和后一个 bucket 的 backward 并行 - DDP 默认启用 """
def pp_communication_overlap(self): """ Pipeline Parallelism 通信重叠 """ return """ P2P 通信与计算重叠:
┌─────────────────────────────────────────────────────┐ │ GPU 0: [F0][B0][F1][B1]... │ │ ↕ P2P │ │ GPU 1: [F0][B0][F1][B1]... │ │ ↕ P2P │ │ GPU 2: [F0][B0][F1][B1]... │ └─────────────────────────────────────────────────────┘
关键: 使用 CUDA 流并行执行计算和通信
# CUDA 流 compute_stream = torch.cuda.Stream() comm_stream = torch.cuda.Stream()
with torch.cuda.stream(compute_stream): compute()
# 异步发送 dist.isend(data, comm_stream) """8.2 通信原语
class CommunicationPrimitives: """ 通信原语 """
def allreduce(self): """ AllReduce """ return """ AllReduce: 所有 GPU 获得相同的归约结果
X = [x0, x1, x2, x3]
AllReduce(SUM): → [x0+x1+x2+x3, x0+x1+x2+x3, x0+x1+x2+x3, x0+x1+x2+x3]
用途: - DDP 梯度同步 - TP Row Parallel 输出求和 """
def allgather(self): """ AllGather """ return """ AllGather: 收集所有 GPU 的数据
X = [x0, x1, x2, x3]
AllGather: → [x0, x1, x2, x3, x0, x1, x2, x3, x0, x1, x2, x3, x0, x1, x2, x3]
用途: - TP QKV 合并 - Sequence Parallel 中收集完整张量 """
def reducescatter(self): """ ReduceScatter """ return """ ReduceScatter: 归约后分散
X = [x0, x0, x0, x0, x1, x1, x1, x1, x2, x2, x2, x2, x3, x3, x3, x3]
ReduceScatter(SUM): → [sum(x0), sum(x1), sum(x2), sum(x3)]
用途: - ZeRO 梯度分片 """8.3 NCCL 优化
class NCCLOptimization: """ NCCL 优化 """
def nccl_tuning(self): """ NCCL 调优 """ return { "NCCL_IB_CUDA": "使用 CUDA 加速 IB 传输", "NCCL_SHM_DISABLE": "禁用共享内存 (可能导致问题)", "NCCL_NVLS_ENABLE": "启用 NVLink 优化", "NCCL_MIN_NCHANNELS": "最小化 channel 数",
"best_practices": [ "使用 NCCL 2.12+", "启用 CUDA 加速", "配置合适的 PXN (NVSwitch)", ], }
def topology_aware(self): """ 拓扑感知通信 """ return ''' # PyTorch 拓扑感知
import torch.distributed as dist
# 检测 GPU 拓扑 if torch.cuda.is_available(): # 使用 NVLink/PCIe 拓扑 dist.init_process_group( backend="nccl", init_method="env://", timeout=timedelta(seconds=1800), )
# NCCL 自动检测拓扑 # 优化 GPU 对等连接 '''9. 显存优化技术
9.1 激活值重计算
class ActivationRecomputation: """ 激活值重计算 (Activation Checkpointing) """
def concept(self): """ 概念 """ return """ 激活值重计算: 用计算换显存
Forward: 保存部分激活值 Backward: 重新计算未保存的激活值
例子: ┌─────────────────────────────────────────────────────┐ │ Forward: │ │ x1 = Layer1(input) │ │ x2 = Layer2(x1) ← 保存 │ │ x3 = Layer3(x2) ← 保存 │ │ x4 = Layer4(x3) ← 保存 │ │ x5 = Layer5(x4) ← 保存 │ └─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐ │ Backward without recompute: │ │ grad_x4 = softmax(x5) ← 需要 x4, x3, x2 的激活值 │ └─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐ │ Backward with recompute: │ │ x2 = recompute(Layer2, input) ← 重新计算 │ │ grad_x4 = softmax(x5) │ └─────────────────────────────────────────────────────┘ """
def memory_savings(self): """ 显存节省 """ return { "full_activations": "~40% 显存用于激活值", "with_checkpointing": "节省 ~60-70% 激活值显存", "cost": "增加 ~20-30% 计算量",
"selective_checkpointing": { "recommendation": "只对大层启用", "effect": "平衡显存和计算", }, }
def implementation(self): """ 实现 """ return ''' # PyTorch 实现 from torch.utils.checkpoint import checkpoint
# 全量重计算 model = Transformer() model = torch.utils.checkpoint.checkpoint_sequential( model.layers, chunks=4, )
# 选择性重计算 class MyLayer(nn.Module): def forward(self, x): return checkpoint(self.heavy_op, x)
# FSDP 中的重计算 fsdp_model = FSDP( model, activation_checkpointing=checkpoint, ) '''9.2 混合精度训练
class MixedPrecisionTraining: """ 混合精度训练 """
def bf16_training(self): """ BF16 训练 """ return """ BF16 vs FP16:
FP16: - 1 sign, 5 exponent, 10 mantissa - 动态范围: ~6×10^4 - 精度较高但可能溢出
BF16: - 1 sign, 8 exponent, 7 mantissa - 动态范围: ~3×10^38 - 动态范围与 FP32 相同 - 训练更稳定
推荐: → Ampere 架构 (A100, RTX 3090+) 支持 BF16 → H100 原生支持 BF16 → 大模型训练优先使用 BF16 """
def loss_scaling(self): """ Loss Scaling """ return ''' # FP16 训练需要 Loss Scaling
# PyTorch 自动管理 scaler = torch.cuda.amp.GradScaler()
for batch in dataloader: with torch.cuda.amp.autocast(): outputs = model(batch) loss = criterion(outputs, labels)
scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()
# BF16 通常不需要 loss scaling with torch.cuda.amp.autocast(dtype=torch.bfloat16): outputs = model(batch) '''10. 实践指南
10.1 配置选择
class ConfigurationGuide: """ 配置选择指南 """
def model_size_to_config(self): """ 根据模型大小选择配置 """ return { "7b_model": { "single_node_8x_a100": { "strategy": "ZeRO-2 or ZeRO-3", "tp": 1, "pp": 1, "dp": 8, "notes": "可以直接用 FSDP", }, }, "13b_model": { "single_node_8x_a100": { "strategy": "ZeRO-2 + TP=2", "tp": 2, "pp": 1, "dp": 4, "notes": "PP 可能引入 bubble", }, }, "70b_model": { "single_node_8x_a100_80gb": { "strategy": "ZeRO-3 + TP=4 + PP=2", "tp": 4, "pp": 2, "dp": 1, "notes": "需要仔细调优", }, "multi_node": { "strategy": "ZeRO-3 + TP=8", "tp": 8, "pp": 1, "dp": "根据节点数", "notes": "8节点训练", }, }, "175b_model": { "strategy": "3D Parallel: TP × PP × DP", "tp": 8, "pp": 8, "dp": "根据 GPU 总数", "notes": "需要数百 GPU", }, }
def batch_size_recommendation(self): """ Batch Size 建议 """ return """ Global Batch Size (GBS):
GBS = micro_batch_size × gradient_accumulation_steps × dp_size
经验公式: - 小模型 (<10B): GBS ≈ 500K - 1M tokens - 中模型 (10-70B): GBS ≈ 1M - 4M tokens - 大模型 (>70B): GBS ≈ 4M+ tokens
示例: 模型: LLaMA-2 70B GPU: 32 × A100
目标 GBS: 4M tokens micro_batch_size: 1 (GPU 显存限制) gradient_accumulation: 128 dp_size: 32
GBS = 1 × 128 × 32 = 4096 tokens (需要调整)
调整: micro_batch_size: 4 gradient_accumulation: 256 dp_size: 8 (TP=4, PP=2)
GBS = 4 × 256 × 8 = 8192 tokens """10.2 性能调优清单
class TuningChecklist: """ 性能调优清单 """
def pre_training(self): """ 训练前检查 """ return """ □ 验证模型正确性 - 单 GPU 测试 small model - 对比参考实现 loss
□ 验证并行策略 - TP=2 验证正确性 - PP=2 验证流水线 - ZeRO 验证梯度同步
□ 性能基准 - MFU (Model FLOP Utilization) - 通信带宽利用率 - GPU 利用率 """
def during_training(self): """ 训练中监控 """ return """ □ 监控指标 - Loss 收敛曲线 - Gradient norm (检查梯度爆炸) - Learning rate schedule
□ 资源利用 - GPU 利用率 (nvidia-smi) - 通信带宽 (NCCL debug) - CPU-GPU 数据传输
□ 异常检测 - Loss NaN/Inf - GPU OOM - 通信超时 """11. 核心公式汇总
11.1 计算 FLOPs
其中 是参数量, 是 tokens 数。
11.2 MFU 计算
11.3 通信量
| 操作 | 通信量 |
|---|---|
| DDP AllReduce | |
| TP AllReduce | |
| PP P2P |
12. 总结
12.1 策略对比
┌─────────────────────────────────────────────────────────────┐│ 并行策略对比 │├─────────────────────────────────────────────────────────────┤│ ││ Data Parallelism ││ ───────────────────────────────────────────────────────── ││ 显存减少: 1x (不变) ││ 通信量: 中 (梯度) ││ 通信模式: AllReduce ││ 适用: 增加吞吐 ││ ││ Tensor Parallelism ││ ───────────────────────────────────────────────────────── ││ 显存减少: TPx ││ 通信量: 高 (每层多次) ││ 通信模式: AllReduce/AllGather ││ 适用: 超大单层 ││ ││ Pipeline Parallelism ││ ───────────────────────────────────────────────────────── ││ 显存减少: PPx ││ 通信量: 低 (P2P) ││ 通信模式: 点对点 ││ 适用: 减少层数 ││ ││ Sequence Parallelism ││ ───────────────────────────────────────────────────────── ││ 显存减少: SPx ││ 通信量: 中 (attention) ││ 通信模式: Ring AllReduce ││ 适用: 超长序列 ││ │└─────────────────────────────────────────────────────────────┘12.2 最佳实践
分布式训练最佳实践:
1. 从简单开始 → 先用 DDP/ZeRO 验证 → 再加入 TP/PP
2. 通信拓扑感知 → TP 在节点内 (NVLink) → PP 跨节点 (InfiniBand) → DP 可跨节点
3. 显存优化 → 启用混合精度 (BF16) → 使用激活值重计算 → ZeRO-3 配合 PP 时注意通信
4. 性能监控 → MFU 目标 > 50% → 监控 GPU 利用率和通信 → 识别瓶颈 (计算 vs 通信)
5. 故障恢复 → 保存 checkpoint → 支持从 checkpoint 恢复 → 处理 GPU 故障推荐阅读
- Megatron-LM Paper—— Tensor Parallelism 经典实现
- GPipe Paper—— Pipeline Parallelism
- ZeRO Paper—— 显存优化
- ColossalAI Docs—— 混合并行框架
参考资料
- Shoeybi, M., et al. (2020). “Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism.” arXiv.
- Huang, Y., et al. (2019). “GPipe: Efficient Training of Giant Neural Networks with Pipeline Parallelism.” NeurIPS.
- Rajbhandari, S., et al. (2020). “ZeRO: Memory Optimizations Toward Training Trillion Parameter Models.” SC20.
- Narayanan, D., et al. (2021). “Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM.” SC21.
- Li, S., et al. (2021). “Megatron-CM3: Multi-Context Prompted Training.” arXiv.
- Zhao, J., et al. (2023). “PipeDream: Balanced Pipeline Parallelism for DNN Training.” SOSP.
- Narayanan, D., et al. (2019). “Pipedream: Weight-Aware Pipeline Parallelism.” MLSys.
- Rasley, J., et al. (2020). “DeepSpeed: System Optimizations for Training and Inference.” Blog.
- Dao, T., et al. (2022). “FlashAttention: Fast and Memory-Efficient Exact Attention.” NeurIPS.
- Chen, K., et al. (2023). “ColossalAI: A Unified Training Engine for Large-Scale Models.” GitHub.
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
分布式训练深度解析:Data / Tensor / Pipeline / Sequence Parallelism
https://aiattnstudio.link/posts/distributed-training/
