Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

AI 编译器 / AI Compilers

1. AI 编译器全景——为什么模型需要编译器 / The AI Compiler Landscape and Why Models Need Compilers

2. 编译原理速通——面向 ML 工程师的核心概念 / Compiler Fundamentals for Machine Learning Engineers

3. 中间表示基础——理解 IR 层级与 lowering 链路 / Intermediate Representation Levels and Lowering Pipelines

4. 计算图的构建与表示 / Building and Representing Computational Graphs

5. MLIR 架构、方言与渐进式降级 / MLIR Architecture, Dialects, and Progressive Lowering

6. 算子语义、广播、归约与形状推导 / Operator Semantics, Broadcasting, Reduction, and Shape Inference

7. 模型前端格式:ONNX、TFLite、HLO 与 SavedModel / Model Frontend Formats: ONNX, TFLite, HLO, and SavedModel

8. 图优化 Pass——经典优化在 ML 中的应用 / Graph Optimization Passes for Machine Learning

9. 算子融合——编译器最重要的性能优化 / Operator Fusion as a Core Compiler Optimization

10. 内存规划——Buffer 分配与生命周期管理 / Memory Planning, Buffer Allocation, and Lifetime Management

11. Layout 优化——数据排布转换与内存效率 / Layout Optimization for Data Movement and Memory Efficiency

12. 动态 Shape——符号分析与形状处理 / Dynamic Shapes, Symbolic Analysis, and Shape Processing

13. 硬件约束下的操作调度 / Operation Scheduling Under Hardware Constraints

14. 从模板、DSL 到 IR 降级的代码生成架构 / Code Generation Architectures from Templates and DSLs to IR Lowering

15. CPU 后端:SIMD、分块与多线程 / CPU Backends with SIMD, Tiling, and Multithreading

16. CUDA 后端:合并访存与 Tensor Core / CUDA Backends, Memory Coalescing, and Tensor Cores

17. NPU 后端:脉动阵列与端侧 AI 生态 / NPU Backends, Systolic Arrays, and Edge AI Ecosystems

18. Kernel 性能基础:Roofline 与 Occupancy / Kernel Performance Fundamentals with Roofline and Occupancy

19. CUTLASS 与分层 GEMM 模板 / CUTLASS and Hierarchical GEMM Templates

20. TVM Tensor Expression 与计算调度分离 / TVM Tensor Expressions and Compute-Schedule Separation

21. 使用 Triton 编写高性能 GPU Kernel / Triton for High-Performance GPU Kernels in Python

22. 基于成本模型与实测搜索的自动调度 / Automatic Scheduling with Cost Models and Measurement-Based Search

23. XLA 内部机制:HLO、融合与 SPMD / XLA Internals, HLO, Fusion, and SPMD

24. Torch-MLIR:从 PyTorch 算子到 MLIR 方言 / Torch-MLIR from PyTorch Operators to MLIR Dialects

25. torch.compile:Dynamo、AOTAutograd、Inductor 与 Triton / Torch Compile with Dynamo, AOTAutograd, Inductor, and Triton

26. 从 MLIR 经 LLVM 降级到机器码 / Lowering from MLIR Through LLVM to Machine Code

27. 量化——低精度推理的工程实践 / Engineering Low-Precision Inference with Quantization

28. 分布式编译与训练——多设备编排的编译器支持 / Compiler Support for Distributed Training and Multi-Device Orchestration

29. 生产调试——真实问题的编译器视角排查 / Production Debugging from the Compiler Perspective

30. 未来方向——AI 编译器的新挑战与机遇 / Future Challenges and Opportunities for AI Compilers

本页目录

分布式编译与训练——多设备编排的编译器支持 / Compiler Support for Distributed Training and Multi-Device Orchestration ​

📅 创建时间:2026-06-03 🏷️ 标签:#分布式 #FSDP #PipelineParallelism #TensorParallelism #CollectiveOps #AllReduce #SPMD #梯度同步 📚 前置知识:[[08-operator-fusion]](算子融合) [[12-scheduling]](调度) 📚 相关知识:[[../training-infra/02-distributed-training]](../training-infra/02-distributed-training) [[22-xla-internals]](XLA 内部)


┌──────────────────────────────────────────────────────────────────────────────┐
│  📌 场景:FSDP 训练 GPU 利用率周期性下降                                       │
├──────────────────────────────────────────────────────────────────────────────┤
│  你在用 PyTorch FSDP 训练一个 70B 模型:                                      │
│  ```python                                                                    │
│  model = FSDP(model, auto_wrap_policy=..., mixed_precision=BFloat16)          │
│  optimizer = ShardedDDPOptimizer(optimizer)                                  │
│  ```                                                                          │
│                                                                              │
│  训练过程中,GPU 利用率周期性下降——每隔几个 step,                             │
│  利用率就掉到 10%。你怀疑是梯度同步(AllReduce)导致的                          │
│  通信-计算 overlap 不好。但你不知道编译器能做什么来优化这个过程。              │
└──────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13

第1节:分布式训练的三个维度 ​

1.1 Data Parallel(数据并行) ​

核心思想:每个设备都有完整模型的副本,不同设备处理不同的数据 batch。

python
# 朴素数据并行示意
class DataParallel:
    """
    最简单的数据并行实现
    """
    def __init__(self, model, devices):
        self.models = [model.to(d) for d in devices]  # 每个设备一个模型副本
    
    def train_step(self, batch):
        # 分割数据
        batches = batch.split(len(devices))
        
        # 并行前向
        outputs = []
        for model, sub_batch, device in zip(self.models, batches, devices):
            output = model(sub_batch.to(device))
            outputs.append(output)
        
        # 汇总 loss
        losses = [out['loss'] for out in outputs]
        total_loss = sum(losses) / len(losses)
        
        # 并行反向
        for model, sub_batch, output in zip(self.models, batches, outputs):
            total_loss.backward()  # 反向传播
        
        # 问题:每个设备独立更新自己的模型副本
        # 如果不同步,模型会逐渐不一致!
        for model in self.models:
            model.step()
        
        return total_loss

# 真正的 DataParallel 需要梯度同步
# 所有设备计算完梯度后,需要对梯度做平均(AllReduce)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

梯度同步的 AllReduce 操作:

python
import torch.distributed as dist

# AllReduce:所有设备贡献自己的梯度,然后得到平均后的梯度
def distributed_all_reduce(tensor):
    """
    AllReduce 操作:
    - 所有设备调用这个函数
    - 每个设备得到结果:tensor = sum(all_tensors) / num_devices
    """
    # 创建一个与所有其他设备的连接
    tensor = tensor.clone()  # AllReduce 会修改 tensor
    
    dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
    tensor.div_(dist.get_world_size())  # 求平均
    
    return tensor

# 朴素实现的问题:
# 1. 同步等待:所有设备必须等到最慢的设备完成才能继续
# 2. 通信开销大:每次梯度同步都要等
# 3. GPU 利用率低:计算和通信不能 overlap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

1.2 Tensor Parallel(张量并行) ​

核心思想:把模型参数(权重矩阵)按维度切分到不同设备。

python
# Tensor Parallel 示意:Column Parallel Linear
class ColumnParallelLinear(torch.nn.Module):
    """
    列切分:权重矩阵按输出维度切分
    输入 X: [batch, seq_len, hidden_dim]
    权重 W: [hidden_dim, out_dim / n_devices]
    输出 Y: [batch, seq_len, out_dim / n_devices]
    """
    def __init__(self, in_features, out_features, world_size):
        super().__init__()
        self.world_size = world_size
        
        # 每个设备只持有部分权重
        self.weight = torch.nn.Parameter(
            torch.randn(in_features, out_features // world_size)
        )
    
    def forward(self, x):
        # 所有设备获得完整的输入(broadcast)
        x = x.clone()  # 避免修改原数据
        
        # 本地计算:每个设备只计算自己负责的输出列
        local_output = x @ self.weight
        
        # AllReduce:收集所有设备的输出,拼接成完整结果
        outputs = [torch.zeros_like(local_output) for _ in range(self.world_size)]
        dist.all_gather(outputs, local_output)
        output = torch.cat(outputs, dim=-1)
        
        return output

# Megatron-LM 的 Tensor Parallelism
# 核心是 3D/2D 并行中的张量切分策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

Row Parallel Linear:

python
class RowParallelLinear(torch.nn.Module):
    """
    行切分:权重矩阵按输入维度切分
    输入 X: [batch, seq_len, hidden_dim / n_devices]
    权重 W: [hidden_dim / n_devices, out_features]
    输出 Y: [batch, seq_len, out_features]
    """
    def __init__(self, in_features, out_features, world_size):
        super().__init__()
        self.world_size = world_size
        
        # 每个设备持有部分权重(按输入维度切分)
        self.weight = torch.nn.Parameter(
            torch.randn(in_features // world_size, out_features)
        )
    
    def forward(self, x):
        # x 已经按列切分了(由上一层的 Column Parallel 提供)
        
        # 本地计算
        local_output = x @ self.weight
        
        # AllReduce:求和(因为输出需要完整)
        dist.all_reduce(local_output)
        
        return local_output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

1.3 Pipeline Parallel(流水线并行) ​

核心思想:把模型按层切分到不同设备。

python
# Pipeline Parallel 示意
class PipelineParallel(torch.nn.Module):
    """
    流水线并行:把模型按层分配到不同设备
    """
    def __init__(self, layers_per_device, num_devices):
        super().__init__()
        
        # 假设模型有 80 层,4 个设备,每个设备 20 层
        self.devices = [f"cuda:{i}" for i in range(num_devices)]
        
        # 按层分配
        self.stages = []
        for i in range(num_devices):
            stage = torch.nn.Sequential(*layers_per_device[i])
            self.stages.append(stage.to(self.devices[i]))
    
    def forward(self, x):
        # Micro-batch 处理
        # Stage 0 (GPU 0) -> Stage 1 (GPU 1) -> ... -> Stage N (GPU N)
        
        x = x.to(self.devices[0])
        
        for i, stage in enumerate(self.stages):
            x = stage(x)
            if i < len(self.stages) - 1:
                # 异步发送到下一个设备
                x = x.to(self.devices[i + 1], non_blocking=True)
        
        return x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

流水线调度的两种模式:

F-then-B (Flush at every bubble):
┌─────────┬─────────┬─────────┬─────────┐
│ Forward │ Forward │ Forward │ Forward │  <- GPU 0
│   F1    │   F2    │   F3    │   F4    │
├─────────┼─────────┼─────────┼─────────┤
│   -     │ Forward │ Forward │ Forward │  <- GPU 1
│   -     │   F1    │   F2    │   F3    │
├─────────┼─────────┼─────────┼─────────┤
│   -     │   -     │ Forward │ Forward │  <- GPU 2
│   -     │   -     │   F1    │   F2    │
├─────────┼─────────┼─────────┼─────────┤
│   -     │   -     │   -     │ Forward │  <- GPU 3
│   -     │   -     │   -     │   F1    │
└─────────┴─────────┴─────────┴─────────┘
时间 ─────────────────────────────────►

1F1B (One-Forward-One-Backward):
┌─────────┬─────────┬─────────┬─────────┐
│ Forward │ Backwd  │ Forward │ Backwd  │  <- GPU 0
│   F1    │   B1    │   F2    │   B2    │
├─────────┼─────────┼─────────┼─────────┤
│   -     │ Forward │ Backwd  │ Forward │  <- GPU 1
│   -     │   F1    │   B1    │   F2    │
├─────────┼─────────┼─────────┼─────────┤
│   -     │   -     │ Forward │ Backwd  │  <- GPU 2
│   -     │   -     │   F1    │   B1    │
├─────────┼─────────┼─────────┼─────────┤
│   -     │   -     │   -     │ Forward │  <- GPU 3
│   -     │   -     │   -     │   F1    │
└─────────┴─────────┴─────────┴─────────┘

1F1B 的 bubble 更小,但需要更大的显存来容纳更多 micro-batches
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

1.4 三种并行策略对比 ​

维度Data ParallelTensor ParallelPipeline Parallel
切分方式数据切分参数切分层切分
模型副本每个设备完整每个设备部分每个设备部分
通信模式AllReduce (梯度)AllReduce/AllGather (激活)P2P (激活)
通信量O(梯度大小)O(激活大小 × 设备数)O(激活大小)
显存需求高(全模型)中(部分参数)低(单阶段)
扩展性线性(数据)有限(通信瓶颈)线性(层数)
编程复杂度低高中
最适合数据密集场景超大单层(如 Large Embedding)超深模型

第2节:FSDP 的编译支持 ​

2.1 FSDP 原理 ​

FSDP (Fully Sharded Data Parallel):将模型参数分片到所有设备,每个设备只持有 1/N 的参数。

python
import torch
import torch.distributed as dist
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp import ShardingStrategy, MixedPrecision
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy

# ============================================================
# FSDP 基础配置
# ============================================================

# 混合精度配置
mixed_precision_policy = MixedPrecision(
    param_dtype=torch.bfloat16,      # 参数精度
    reduce_dtype=torch.float32,      # AllReduce 用高精度(避免精度损失)
    buffer_dtype=torch.bfloat16,      # Buffer 精度
)

# 自动 wrap 策略:每个 transformer 层作为一个 FSDP 单元
def auto_wrap_policy(module, recurse, non_wrappable):
    wrapped = set()
    if recurse:
        return True
    if is_trainable_layer(module):
        wrapped.add(module)
    return wrapped

# 包装模型
model = FSDP(
    model,
    sharding_strategy=ShardingStrategy.FULL_SHARD,  # 分片 + 梯度 + Optimizer state
    # 或者: ShardingStrategy.SHARD_GRAD_OP (只分片梯度和优化器状态)
    # 或者: ShardingStrategy.NO_SHARD (只分片参数)
    auto_wrap_policy=transformer_auto_wrap_policy,
    mixed_precision=mixed_precision_policy,
    device_id=torch.cuda.current_device(),
)

# 优化器也需要分片
# 使用 ShardedDDPOptimizer 或 FSDP 包装
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)
optimizer = FSDP.optim_state_dict_to_load(
    optimizer, model, optimizer.state_dict()
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

2.2 FSDP 的通信-计算 Overlap ​

python
# ============================================================
# FSDP 编译优化:通信计算Overlap
# ============================================================

# 关键参数
fsdp_config = {
    "sharding_strategy": ShardingStrategy.FULL_SHARD,
    
    # Forward 时的分片策略
    "forward_prefetch": True,    # 提前获取下一个 shard
    "backward_prefetch": BackwardPrefetch.PRE_GATHER,  # 提前聚合梯度
    
    # 算子融合
    "use_orig_params": True,    # 使用原始参数(更好的融合机会)
    
    # CPU offload(内存不够时)
    "cpu_offload": None,  # or CPUOffload(offload_params=True)
}

model = FSDP(model, **fsdp_config)

# ============================================================
# 梯度检查点 + FSDP
# ============================================================

# 梯度检查点:在反向传播时重新计算前向(节省显存)
# FSDP 下需要特殊处理

from torch.distributed.algorithms._checkpoint.checkpoint_wrapper import (
    checkpoint_wrapper,
    CheckpointImpl,
    apply_activation_checkpointing,
)

# 方法 1:包装整个模型
model = checkpoint_wrapper(
    model,
    checkpoint_impl=CheckpointImpl.NO_REENTRANT,
)

# 方法 2:为特定层应用检查点
apply_activation_checkpointing(
    model,
    checkpoint_wrapper_fn=checkpoint_wrapper,
    auto_wrap_policy=...,  # 与 FSDP 的 wrap policy 一致
)

# ============================================================
# 梯度累积 + FSDP
# ============================================================

# FSDP 下做梯度累积需要注意
# 只有在梯度同步完成后才能更新参数

model = FSDP(model)

for batch in data_loader:
    # 累积足够的 micro-batches
    micro_batch_count = 0
    while micro_batch_count < grad_accum_steps:
        # 分片输入
        sharded_input = batch[micro_batch_count]
        
        # 前向(不同步梯度)
        output = model(sharded_input)
        loss = loss_fn(output, target) / grad_accum_steps
        loss.backward()  # FSDP 会自动聚合梯度
        
        micro_batch_count += 1
    
    # 优化器步骤(此时所有 micro-batches 的梯度已同步)
    optimizer.step()
    optimizer.zero_grad()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

2.3 FSDP 编译配置详解 ​

python
# torch.compile 与 FSDP 的结合
import torch

# FSDP 需要特殊处理才能与 torch.compile 一起用
# PyTorch 2.1+ 支持

# 1. 编译 FSDP 模型
compiled_model = torch.compile(model, mode="reduce-overhead")

# 2. 或者分别编译 FSDP 包装内的模型
# (需要在 FSDP 之前编译)

# ============================================================
# FSDP 性能调优参数
# ============================================================

fsdp_params = {
    # 通信优化
    "limit_all_gathers": True,         # 限制同时进行的 gather 数量
    "overlap_with_ backward": True,    # 与反向传播 overlap
    "use_border_checkpointing": True,  # 边界检查点(减少通信)
    
    # 内存优化
    "move_grads_to_cpu": False,        # 梯度放 CPU(省显存但慢)
    "move_params_to_cpu": False,       # 参数放 CPU
    
    # 调试
    "use_full_prec_state_dict": True,  # 保存/加载用全精度
}

# ============================================================
# 常见 FSDP 问题排查
# ============================================================

# 问题 1:显存不够
# 解决:减小 batch size、使用梯度检查点、增加设备

# 问题 2:GPU 利用率低
# 解决:检查通信瓶颈、增加 forward_prefetch

# 问题 3:梯度同步慢
# 解决:检查网络带宽、使用更高效的 AllReduce 实现(NCCL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

第3节:Tensor Parallelism 编译 ​

3.1 AllReduce 在计算图中的位置 ​

python
# Tensor Parallel 下 AllReduce 的位置
class TensorParallelLinear(torch.nn.Module):
    """
    Tensor Parallel Linear 层编译决策
    """
    def __init__(self, tp_size):
        self.tp_size = tp_size
        # ... 初始化代码 ...
    
    def forward(self, x):
        # 计算图分析:
        # 
        # Column Parallel:
        # x -> [local_matmul] -> [local_output] -> [AllReduce] -> output
        #                         (独立)           (通信)
        #
        # Row Parallel:
        # x -> [AllReduce] -> [local_matmul] -> output
        #   (通信)          (独立)
        
        # 编译器的优化机会:
        # 1. 如果 AllReduce 前后有其他算子,可以融合
        # 2. 如果多个连续的 AllReduce,可以合并
        # 3. 通信可以与计算 overlap
        
        return output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

3.2 Column/Row Parallel 的编译决策 ​

python
# 编译优化:融合连续的小 AllReduce
class CollectiveFusion:
    """
    Collective 融合:将多个小的通信操作合并为一个大的
    """
    
    def should_fuse_collectives(self, graph):
        """
        判断是否应该融合 collective 操作
        """
        collectives = self.find_collectives(graph)
        
        for i in range(len(collectives) - 1):
            c1, c2 = collectives[i], collectives[i + 1]
            
            # 如果两个 collective 之间没有依赖,可以融合
            if self.no_dependency_between(c1, c2):
                # 但也要考虑内存开销
                if self.memory_budget_ok(c1, c2):
                    return True
        
        return False
    
    def fuse_collectives(self, c1, c2):
        """
        融合两个 collective
        """
        # 方案 1:直接合并数据
        # 先发 c1,再发 c2
        
        # 方案 2:打包成更大的消息
        # 一次性发送合并后的数据
        
        # 方案 3:延迟合并
        # 等待更多 collective,然后一起发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

第4节:Pipeline Parallelism 编译 ​

4.1 微批次调度 ​

python
# Pipeline Parallel 调度器
class PipelineScheduler:
    """
    编译器生成的流水线调度
    """
    
    def schedule_1f1b(self, num_microbatches, num_stages):
        """
        1F1B 调度:每个设备交替执行前向和反向
        """
        schedule = []
        
        # 预热阶段:只有前向
        for m in range(num_stages):
            schedule.append(("forward", m))
        
        # 稳定阶段:交替前向和反向
        for m in range(num_microbatches - num_stages):
            schedule.append(("forward", m + num_stages))
            schedule.append(("backward", m))
        
        # 冷却阶段:只有反向
        for m in range(num_stages):
            schedule.append(("backward", m + num_microbatches - num_stages))
        
        return schedule
    
    def compute_pipeline_bubble(self, num_stages, num_microbatches):
        """
        计算流水线空闲时间(bubble)
        """
        # 1F1B 的 bubble 时间
        bubble_time = (num_stages - 1) * 2
        
        # F-then-B 的 bubble 时间
        bubble_time_ftb = (num_stages - 1)
        
        efficiency = num_microbatches / (num_microbatches + bubble_time)
        return efficiency
    
    def auto_select_schedule(self, model, num_microbatches):
        """
        编译器自动选择最优调度
        """
        # 考虑因素:
        # 1. 显存限制(1F1B 需要更多显存)
        # 2. 通信延迟(更深的流水线延迟更敏感)
        # 3. 计算不对称性(不同 stage 计算量不同)
        
        # 简单启发式:
        if self.get_available_memory() < self.estimate_1f1b_memory():
            return "F-then-B"
        else:
            return "1F1B"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

4.2 编译器流水线优化 ​

python
# 流水线并行编译优化
class PipelineOptimizer:
    """
    编译器层面的流水线优化
    """
    
    def balance_stages(self, model, num_stages):
        """
        平衡各阶段的计算量
        """
        stage_boundaries = []
        
        # 计算每层的 FLOPs
        layer_flops = []
        for layer in model.layers:
            flops = self.estimate_flops(layer)
            layer_flops.append(flops)
        
        # 动态规划找最优切分点
        target_flops = sum(layer_flops) / num_stages
        
        current_flops = 0
        for i, flops in enumerate(layer_flops):
            current_flops += flops
            if current_flops >= target_flops:
                stage_boundaries.append(i)
                current_flops = 0
        
        return stage_boundaries
    
    def optimize_schedule_for_heterogeneous(self, stages):
        """
        针对异构设备的调度优化
        """
        # 不同设备可能有不同的计算能力
        # 调度应该让慢设备承担更少的计算
        
        device_capabilities = self.get_device_capabilities()
        
        # 重新分配 stage
        rebalanced_stages = self.rebalance_stages(
            stages, device_capabilities
        )
        
        return rebalanced_stages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

第5节:Collective Operations 编译优化 ​

5.1 AllReduce 算法 ​

python
# AllReduce 的不同实现算法

# ============================================================
# Ring AllReduce
# ============================================================
# 时间复杂度: O(N),适合大消息
#
# 4 个 GPU 的 Ring AllReduce:
# Step 1: 每个 GPU 发一块给下一个 GPU(接收一块)
# Step 2: 继续传递,直到所有 GPU 都有完整数据
#
# 通信量: 2 * (N-1) / N * message_size

def ring_allreduce(rank, world_size, tensor):
    """
    Ring AllReduce 实现
    """
    # 分为两个阶段:
    # 1. Scatter Reduce:每个 GPU 获得最终结果的一部分
    # 2. AllGather:每个 GPU 获得完整结果
    
    block_size = tensor.numel() // world_size
    
    # Scatter Reduce 阶段
    for i in range(world_size - 1):
        send_idx = (rank - i - 1) % world_size
        recv_idx = (rank - i) % world_size
        
        send_block = tensor[send_idx * block_size:(send_idx + 1) * block_size]
        recv_block = tensor[recv_idx * block_size:(recv_idx + 1) * block_size]
        
        # 异步发送和接收
        send_req = dist.isend(send_block, dst=(rank + 1) % world_size)
        recv_req = dist.irecv(recv_block, src=(rank - 1) % world_size)
        
        recv_req.wait()
        recv_block += send_block  # 累加
        send_req.wait()
    
    # AllGather 阶段
    for i in range(world_size - 1):
        send_idx = (rank - i + 1) % world_size
        recv_idx = (rank - i) % world_size
        
        send_block = tensor[send_idx * block_size:(send_idx + 1) * block_size]
        recv_block = tensor[recv_idx * block_size:(recv_idx + 1) * block_size]
        
        # 发送和接收
        send_req = dist.isend(send_block, dst=(rank + 1) % world_size)
        recv_req = dist.irecv(recv_block, src=(rank - 1) % world_size)
        
        recv_req.wait()
        send_req.wait()


# ============================================================
# Tree AllReduce
# ============================================================
# 时间复杂度: O(log N),适合小消息
# 使用二叉树结构,根节点收集和分发数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

5.2 NCCL 通信原语 ​

python
import torch.distributed as dist

# ============================================================
# 常用 NCCL Collective 操作
# ============================================================

# AllReduce:所有设备求和并广播
dist.all_reduce(tensor, op=dist.ReduceOp.SUM)

# AllGather:收集所有设备的数据
output = [torch.zeros_like(tensor) for _ in range(world_size)]
dist.all_gather(output, tensor)

# ReduceScatter:求和后分发到各设备
output = torch.zeros_like(tensor)
dist.reduce_scatter(output, input_list)

# Broadcast:广播数据到所有设备
dist.broadcast(tensor, src=0)

# Gather:收集到一个设备
if rank == 0:
    output = [torch.zeros_like(tensor) for _ in range(world_size)]
else:
    output = None
dist.gather(tensor, output, dst=0)


# ============================================================
# 异步 Collective 操作
# ============================================================

# 异步发送
req = dist.isend(tensor, dst=dest_rank)
# ... 做其他计算 ...
req.wait()

# 异步接收
req = dist.irecv(tensor, src=src_rank)
# ... 做其他计算 ...
req.wait()

# 使用 barrier 同步
dist.barrier()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

5.3 Collective 融合 ​

python
# Collective 融合:将多个小操作合并为一个大操作
class CollectiveFusionOptimizer:
    """
    编译器优化:识别可以融合的 Collective 操作
    """
    
    def find_fusable_collectives(self, graph):
        """
        查找图中可以融合的 collective
        """
        fusable = []
        
        for node in graph.nodes:
            if node.is_collective():
                # 检查是否可以与下一个 collective 融合
                next_node = node.next
                
                if self.can_fuse(node, next_node):
                    # 检查融合后的内存开销是否可接受
                    fused_size = self.estimate_fused_size(node, next_node)
                    if fused_size < memory_budget:
                        fusable.append((node, next_node))
        
        return fusable
    
    def fuse(self, c1, c2):
        """
        融合两个 collective
        """
        # 方案 1:打包到单个消息
        # 先合并数据,再发送
        
        # 方案 2:流水线化
        # 第一个 collective 完成后立即开始第二个
        
        # 方案 3:延迟执行
        # 等待更多 collective 到达后再一起发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

第6节:分布式训练中的图优化 ​

6.1 梯度累积下的图简化 ​

python
# 梯度累积时的编译优化
class GradientAccumulationOptimizer:
    """
    梯度累积下的图简化
    """
    
    def optimize_for_gradient_accumulation(self, model, accum_steps):
        """
        优化梯度累积的计算图
        """
        # 1. 识别可以跨 step 共享的计算
        # 例如:LayerNorm 的统计量(训练时不能用,但 eval 可以)
        
        # 2. 优化梯度同步时机
        # 不需要每 step 都同步,可以累积多个 step
        
        # 3. 融合 optimizer 更新
        # 将多个小梯度更新合并
        
        pass
    
    def reduce_gradient_sync_frequency(self, model, sync_every_n_steps):
        """
        减少梯度同步频率
        """
        # Local SGD:多步后同步一次
        # 可以减少通信开销
        
        local_steps = 0
        for step in training_loop:
            output = model(input)
            loss.backward()
            
            local_steps += 1
            
            if local_steps >= sync_every_n_steps:
                # AllReduce 梯度
                dist.all_reduce(grad)
                optimizer.step()
                optimizer.zero_grad()
                local_steps = 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

6.2 重计算策略 ​

python
# 重计算(Recomputation)优化
class RecomputationOptimizer:
    """
    编译器决定哪些算子需要重计算
    """
    
    def select_recomputation_candidates(self, model, memory_budget):
        """
        选择需要重计算的算子
        """
        candidates = []
        
        for node in model.graph.nodes:
            # 显存节省 = 输出的显存 - 重计算的开销
            memory_saved = node.output_size
            
            # 重计算成本 = 前向 + 反向的成本
            recompute_cost = self.estimate_forward_cost(node) + \
                            self.estimate_backward_cost(node)
            
            # 只重计算那些显存节省大于成本的算子
            if memory_saved > recompute_cost:
                candidates.append((node, memory_saved - recompute_cost))
        
        # 按收益排序,选择在显存预算内的
        candidates.sort(key=lambda x: -x[1])
        
        selected = []
        total_saved = 0
        for node, benefit in candidates:
            if total_saved + node.output_size <= memory_budget:
                selected.append(node)
                total_saved += node.output_size
        
        return selected
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

升华 ​

┌──────────────────────────────────────────────────────────────────────────────┐
│                          分布式编译实践原则                                    │
├──────────────────────────────────────────────────────────────────────────────┤
│  1. 通信即计算:把通信当作计算图的一部分,编译器可以做全局优化                   │
│  2. 重叠是关键:让通信和计算尽可能 overlap,提高 GPU 利用率                     │
│  3. 分片要平衡:各设备的计算量和通信量要尽量均衡                               │
│  4. 内存换通信:重计算可以节省显存,但会增加计算量                             │
│  5. 调度决定性能:好的调度可以减少 bubble,提高流水线效率                       │
└──────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9

"AI 可查 vs 必须理解"清单 ​

必须理解(不理解就等于不会):

  • 🔴 Data Parallel / Tensor Parallel / Pipeline Parallel 的核心思想
  • 🔴 AllReduce 的作用和 Ring/Tree 算法的 trade-off
  • 🔴 FSDP 的分片策略和通信模式
  • 🔴 流水线并行的 bubble 问题和 1F1B vs F-then-B 调度
  • 🔴 通信-计算 overlap 的原理

AI 可查(知道去哪查就行):

  • ✅ FSDP API 的具体参数和用法
  • ✅ NCCL 的具体 collective API
  • ✅ Megatron-LM 的 tensor parallel 实现细节
  • ✅ 特定硬件的网络带宽和延迟
  • ✅ 各框架的分布式训练 API

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇27. 量化——低精度推理的工程实践 / Engineering Low-Precision Inference with Quantization
下一篇29. 生产调试——真实问题的编译器视角排查 / Production Debugging from the Compiler Perspective

持续记录,持续成长

Copyright © Tidenflow