pretrain() 总览:训练的总调度器
在 pretrain_gpt.py 中,__main__ 最终调用的就是 pretrain()。这个函数是 Megatron-LM 整个训练流程的总调度器,按顺序执行 5 个阶段。
Step 1: initialize_megatron()
解析参数 → 设置全局变量 → 初始化 torch.distributed → 创建 TP/PP/DP/CP/EP 通信组 → 设置随机种子 → 编译 C++ 依赖
Step 2: setup_model_and_optimizer()
调用 get_model() 构建模型 → 创建优化器 (Adam/Muon) → 创建 LR 调度器 → 加载 Checkpoint
Step 3: build_train_valid_test_data_iterators()
构建 train/valid/test 数据迭代器。VPP 模式下每个 vp_stage 有独立的迭代器。
Step 4: train()
执行训练主循环。每个 iteration 调用 train_step(),周期性做 evaluation 和 checkpoint。详见训练循环篇。
Step 5: 收尾
保存最终 checkpoint → 在 valid/test 集上评估 → 关闭 WandB/TensorBoard → 异步存盘完成 → 退出
def pretrain(
train_valid_test_dataset_provider, # 回调①:数据怎么来
model_provider, # 回调②:模型怎么建
model_type, # 回调③:模型类型标识
forward_step_func, # 回调④:前向怎么算
...
):
# ---- Step 1: 初始化 ----
initialize_megatron(...) # 分布式环境 + 通信组 + 随机种子
args = get_args()
# ---- Step 2: 模型 + 优化器 ----
model, optimizer, opt_param_scheduler = setup_model_and_optimizer(
model_provider, model_type, ...
)
# ---- Step 3: 数据迭代器 ----
train_data_iterator, valid_data_iterator, test_data_iterator = \
build_train_valid_test_data_iterators(train_valid_test_dataset_provider)
# ---- Step 4: 训练 ----
if not args.skip_train and args.do_train:
iteration, num_flops = train(
forward_step_func, model, optimizer, opt_param_scheduler,
train_data_iterator, valid_data_iterator, config, ...
)
# ---- Step 5: 收尾 ----
if args.do_valid:
evaluate_and_print_results(...)
if args.do_test:
evaluate_and_print_results(...)
maybe_finalize_async_save(blocking=True, terminate=True)
pretrain_gpt.py 负责定义回调(get_batch、loss_func、forward_step、model_provider),然后一行 pretrain(...) 把所有回调交给训练框架。从这里开始,控制权完全转移到 training.py。
initialize_megatron():分布式环境初始化
这是 pretrain() 的第一步。它负责将一组独立的 GPU 进程组织成一个协作训练集群。
2.1 _initialize_distributed():创建通信组
这个函数做两件事:
- 初始化 PyTorch 分布式:调用
torch.distributed.init_process_group(),建立进程间的 NCCL 通信后端。 - 创建 Megatron 并行通信组:调用
mpu.initialize_model_parallel(),根据配置的 TP/PP/DP/CP/EP 尺寸,把所有 GPU 划分为多个通信组。
# 1. 初始化 PyTorch 分布式(NCCL 后端)
torch.distributed.init_process_group(
backend=args.distributed_backend, # 通常是 'nccl'
world_size=args.world_size,
rank=args.rank,
timeout=timedelta(minutes=args.distributed_timeout_minutes),
)
# 2. 创建 Megatron 的多维并行通信组
mpu.initialize_model_parallel(
tensor_model_parallel_size=args.tensor_model_parallel_size,
pipeline_model_parallel_size=args.pipeline_model_parallel_size,
virtual_pipeline_model_parallel_size=args.virtual_pipeline_model_parallel_size,
context_parallel_size=args.context_parallel_size,
expert_model_parallel_size=args.expert_model_parallel_size,
expert_tensor_parallel_size=args.expert_tensor_parallel_size,
order='tp-cp-ep-dp-pp', # 默认 rank 排列顺序
...
)
mpu.initialize_model_parallel() 内部会调用 RankGenerator 来根据 order='tp-cp-ep-dp-pp' 的映射公式,为每种并行维度创建对应的 ProcessGroup。详细的 rank 映射算法和通信组划分逻辑,请参阅 Rank 与并行组 一文。
2.2 _set_random_seed():TP-aware 随机数
分布式训练中,不同并行维度对随机数的需求不同:
- DP 副本必须看到相同的模型初始化 → 需要相同的种子
- TP 副本中的 Dropout 需要不同的随机数(否则 TP 分区间的 dropout 会完全相关,削弱正则化效果)
Megatron 维护两套 CUDA RNG,在不同场景切换使用:
def _set_random_seed(seed_, data_parallel_random_init, ...):
seed = seed_
if data_parallel_random_init:
seed = seed_ + mpu.get_data_parallel_rank()
# ① 默认 RNG:所有 TP rank 种子相同
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
# ② TP 专用 RNG:每个 TP rank 种子不同(加上 tp_rank 偏移)
_CUDA_RNG_STATE_TRACKER.add(
'model-parallel-rng',
seed + mpu.get_tensor_model_parallel_rank()
)
| 场景 | 各 TP rank 的数据 | 用哪套 RNG | 原因 |
|---|---|---|---|
| Attention 后的 residual dropout | 相同(AllReduce 后) | 默认 RNG(种子相同) | 必须 drop 同样位置,否则 rank 间数据不一致 |
| Attention 内部的 attention dropout | 不同(不同 head) | TP RNG(种子不同) | 各 head 独立 drop,保持正则化效果 |
| MLP 中间层 dropout | 不同(不同 neuron 分区) | TP RNG(种子不同) | 各 neuron 独立 drop |
为什么 TP 分区内的 Dropout 需要不同种子?具体示例
以 TP=2,MLP 中间层有 4 个神经元为例。g0 持有 [n0, n1],g1 持有 [n2, n3],Dropout p=0.5。
两个 rank 用同一个种子 → 产生同一个随机序列 → 同一个 mask:
2.3 _compile_dependencies():编译 C++ 数据集索引
Megatron 的数据集使用了 memory-mapped indexed dataset,其索引构建器是用 C++ 实现的。_compile_dependencies() 在 rank 0 上编译这些 C++ 扩展,其他 rank 等待编译完成。
此外,还会检查自定义 fused kernel(如 scaled_masked_softmax)的约束条件,不满足时 fallback 到 unfused 版本。
2.4 Rerun 状态机初始化
initialize_rerun_state_machine() 在初始化阶段就创建好容错重试的状态机。它会保存/恢复 CudaRNGStatesTracker 的状态,确保重试时的随机数可以精确重放或切换到不同 GPU。
重试机制的详细工作原理(三级诊断),已在 pretrain_gpt.html — loss_func() 中介绍。
get_model():模型构建与并行包装
get_model() 负责把 pretrain_gpt.py 提供的 model_provider 回调变成一个可以训练的分布式模型。这个过程分为 4 层:
rank,通过 if 分支走不同路径。代码中频繁出现的
mpu 就是 megatron.core.parallel_state 模块,它在 initialize_model_parallel() 后存储了当前 rank 在并行网格中的位置。同一个函数在不同 GPU 上返回不同的值:
mpu.get_tensor_model_parallel_rank() # g0→0, g1→1, g2→0, g3→1, ...mpu.is_pipeline_first_stage() # g0→True, g1→True, 其他→Falsempu.is_pipeline_last_stage() # g6→True, g7→True, 其他→False
3.1 build_model():每张卡构建自己的模型片段
model_provider_func 就是 pretrain_gpt.py 传进来的"造模型的函数"。pre_process 和 post_process 标志决定造模型的哪一段——同一份代码在不同 GPU 上,因为 mpu.is_pipeline_first_stage() 返回值不同,造出来的模型也不同:
def build_model():
if pp_size > 1 and virtual_pp_size is not None:
# VPP 模式:构建 virtual_pp_size 个 model chunk
model = []
for i in range(args.virtual_pipeline_model_parallel_size):
pre_process = mpu.is_pipeline_first_stage(
ignore_virtual=False, vp_stage=i
)
post_process = mpu.is_pipeline_last_stage(
ignore_virtual=False, vp_stage=i
)
this_model = model_provider_func(
pre_process=pre_process,
post_process=post_process,
vp_stage=i # 告诉 provider 这是第几个虚拟 chunk
)
model.append(this_model)
else:
# 标准模式:只构建 1 个 model
pre_process = mpu.is_pipeline_first_stage()
post_process = mpu.is_pipeline_last_stage()
model = model_provider_func(
pre_process=pre_process,
post_process=post_process
)
pre_process=True(PP 第一个 stage)→ 实例化 Embedding 层post_process=True(PP 最后一个 stage)→ 实例化 Output Head + Loss- 中间 stage 两者都是
False→ 只有 Transformer layers
Virtual Pipeline Parallelism (VPP) 详解 vp_stage
问题:标准 PP 中,每个 stage 必须等上游 stage 算完才能开始,产生流水线气泡(GPU 空等)。气泡比例 ≈ (PP-1) / num_microbatches。
VPP 的做法:把每张卡的连续层块拆成多个不连续的小块,交错排列。数据在流水线中来回穿梭多次,气泡比例降为 (PP-1) / (num_microbatches × VPP)。
代码中的 vp_stage 就是虚拟 chunk 的编号。以 g0(pp_rank=0)为例,循环 2 次造 2 个 model chunk:
所以 VPP 模式下 model 不再是一个模型,而是一个 list。每个元素是这张卡负责的一个 chunk,前向时依次执行。
3.2 GPU 放置与精度转换
模型构建后,依次执行两步转换:
# 1. 移动到 GPU(FSDP2 除外,它在 fully_shard 时分配显存)
for model_module in model:
model_module.cuda(torch.cuda.current_device())
# 2. FP16/BF16 精度包装
if args.fp16 or args.bf16:
config = get_model_config(model[0])
model = [Float16Module(config, model_module) for model_module in model]
Float16Module 是一个包装器,它将模型参数转为半精度,但保留 master weights(FP32 副本)用于优化器更新,避免精度损失。
3.3 数据并行包装:DDP vs FSDP
Megatron 支持三种数据并行策略,通过命令行参数选择:
| 策略 | 参数 | 特点 |
|---|---|---|
| Megatron DDP | 默认(不加额外参数) | 自研 DDP,支持 overlap_grad_reduce 梯度通信重叠、自定义 bucket 大小 |
| Megatron FSDP | --use-megatron-fsdp |
自研 Fully Sharded DP,将参数+梯度分片到各 DP rank,节约显存 |
| Torch FSDP2 | --use-torch-fsdp2 |
PyTorch 官方 FSDP2 实现,需要 torch≥2.4 |
# 选择 DP 实现
if args.use_torch_fsdp2:
DP = torch_FSDP
elif args.use_megatron_fsdp:
DP = megatron_FSDP
else:
DP = DDP # Megatron 自研 DDP
# DDP 配置(仅 Megatron DDP)
ddp_config = DistributedDataParallelConfig(
grad_reduce_in_fp32=args.accumulate_allreduce_grads_in_fp32,
overlap_grad_reduce=args.overlap_grad_reduce, # 梯度通信与计算重叠
overlap_param_gather=args.overlap_param_gather, # 参数收集与计算重叠
bucket_size=args.ddp_bucket_size or auto_bucket_size,
...
)
# 包装每个 model chunk
model = [
DP(
config=config,
ddp_config=ddp_config,
module=model_chunk,
# VPP 模式下,model_chunk 1+ 的通信已经和计算重叠了
# 所以关闭它们的 bucketing
disable_bucketing=(model_chunk_idx > 0),
)
for model_chunk_idx, model_chunk in enumerate(model)
]
--ddp-bucket-size,Megatron 会自动计算:bucket_size = max(40_000_000, 1_000_000 × dp_world_size)大 DP 集群需要更大的 bucket,确保 NCCL ring-reduce 的 chunk 足够大,维持在带宽瓶颈(而非延迟瓶颈)区间。
什么是 Bucketing?为什么需要它?计算-通信重叠
朴素做法:等所有层的梯度都算完,再发起一次大的 AllReduce。计算和通信完全串行。
Bucketing:把参数按固定大小分成若干桶。后面的层先算完梯度,攒满一个桶就立刻发起 AllReduce,同时 GPU 继续算前面的层——实现计算和通信重叠。
bucket_size 的 tradeoff:太大则桶少、重叠差(退化为朴素做法);太小则每次 AllReduce 数据量太小、NCCL 启动开销占比大。默认约 40M 参数是一个平衡点。
VPP 的 disable_bucketing 优化 VPP 反向调度
VPP 模式下每张卡有多个 model chunk。代码中 disable_bucketing=(model_chunk_idx > 0),即只有 model[0] 开启 bucketing。原因在于 VPP 的反向顺序:
| chunk | 反向顺序 | 梯度通信能和谁重叠 | bucketing |
|---|---|---|---|
| model[1] | 先算 | 和 model[0] 的计算重叠(跨 chunk) | 关闭,一大包更高效 |
| model[0] | 后算 | 没有后续计算了 | 开启,在自身反向内部制造重叠 |
setup_model_and_optimizer():组装一切
这个函数把模型构建、优化器创建和 checkpoint 加载串联在一起,返回训练所需的三件套:(model, optimizer, opt_param_scheduler)。
4.1 优化器选择:Adam vs Muon
# 从 args 构建 OptimizerConfig
config = OptimizerConfig(**{
f.name: getattr(args, f.name)
for f in dataclasses.fields(OptimizerConfig)
if hasattr(args, f.name)
})
# 根据 --optimizer 参数选择优化器
if 'muon' not in config.optimizer:
# 标准路径:Adam / AdamW(分布式优化器)
optimizer = get_megatron_optimizer(
config, model,
no_weight_decay_cond, # 哪些参数不要 weight decay
scale_lr_cond, # 哪些参数用不同学习率
...
)
else:
# Muon 优化器(实验性)
optimizer = get_megatron_muon_optimizer(
config, model, ...
)
get_megatron_optimizer() 返回的是 DistributedOptimizer,它将优化器状态(如 Adam 的 m 和 v)分片到各 DP rank 上,每个 rank 只存储和更新自己负责的那部分参数。这与在 集合通信操作 — 分布式优化器 中描述的策略一致。
4.2 学习率调度器
get_optimizer_param_scheduler() 支持两种训练计量方式:
- Iteration-based(
--train-iters):按训练步数调度 LR - Sample-based(
--train-samples):按处理的样本数调度 LR
两者最终都会被转换为 OptimizerParamScheduler 的统一接口,支持 warmup → decay → min_lr 的标准调度曲线。支持的衰减方式包括 linear、cosine 和 WSD (Warmup-Stable-Decay)。
4.3 Checkpoint 加载
if args.load is not None or args.pretrained_checkpoint is not None:
# 加载 checkpoint,恢复:
# - 模型权重
# - 优化器状态(Adam 的 m, v)
# - LR 调度器状态
# - 训练进度(iteration, consumed_samples)
args.iteration, args.num_floating_point_operations_so_far = load_checkpoint(
model,
optimizer,
opt_param_scheduler,
checkpointing_context=checkpointing_context,
)
else:
# 从零开始训练
args.iteration = 0
args.num_floating_point_operations_so_far = 0
load_checkpoint() 会自动处理分布式 checkpoint 的分片加载——每个 TP/PP rank 只加载自己那份参数。
4.4 MoE Upcycling(可选)
当设置 --moe-use-upcycling 时,Megatron 会执行一次 Dense → MoE 转换:
- 先构建一个临时的 dense 模型
- 从 checkpoint 加载 dense 权重
- 调用
upcycling_utils.load_and_upcycle_model()将 dense MLP 的权重复制到每个 MoE expert 中 - 保存转换后的 MoE checkpoint,然后退出
这是一种常见的 MoE 初始化策略:从预训练好的 dense 模型出发,将每个 expert 初始化为 MLP 的副本,然后继续训练让 router 学会分配 token。
4.5 Checkpoint 格式转换(可选)
当设置 --ckpt-convert-format 时,setup_model_and_optimizer() 会在加载 checkpoint 后立即以新格式重新保存,然后退出。这用于在不同 checkpoint 格式之间转换(如 torch → torch_dist)。
数据迭代器构建
模型和优化器就绪后,pretrain() 构建数据迭代器。这里有 VPP 和非 VPP 两条路径。
if args.virtual_pipeline_model_parallel_size is not None:
# VPP 模式:每个 vp_stage 有独立的 data iterator
train_data_iterator = []
valid_data_iterator = []
test_data_iterator = []
for vp_stage in range(len(model)):
vp_provider = functools.partial(
train_valid_test_dataset_provider,
vp_stage=vp_stage
)
iterators = build_train_valid_test_data_iterators(vp_provider)
train_data_iterator.append(iterators[0])
valid_data_iterator.append(iterators[1])
test_data_iterator.append(iterators[2])
else:
# 标准模式:单一 data iterator
train_data_iterator, valid_data_iterator, test_data_iterator = \
build_train_valid_test_data_iterators(
train_valid_test_dataset_provider
)
5.1 build_train_valid_test_data_iterators() 调用链
数据迭代器的构建是一个三层调用链:
总结:初始化完成后的全局状态
当 pretrain() 走完前三步、即将进入 train() 时,系统已经准备好了以下全局状态:
| 组件 | 内容 | 获取方式 |
|---|---|---|
| 命令行参数 | 所有训练配置(模型大小、并行策略、学习率等) | get_args() |
| 分词器 | SentencePiece / HuggingFace / GPT2 tokenizer | get_tokenizer() |
| 通信组 | TP / PP / DP / CP / EP 各维度的 ProcessGroup | mpu.get_*_group() |
| 模型 | List[DDP(Float16Module(GPTModel))],已在 GPU 上 |
局部变量 model |
| 优化器 | DistributedOptimizer(Adam 状态分片到各 DP rank) | 局部变量 optimizer |
| LR 调度器 | OptimizerParamScheduler(warmup → decay → min_lr) | 局部变量 opt_param_scheduler |
| 数据迭代器 | train / valid / test 各一个(VPP 下为列表) | 局部变量 |
| 训练进度 | args.iteration + args.consumed_train_samples |
get_args() |
| 日志 Writer | TensorBoard / WandB / OneLogger | get_tensorboard_writer() 等 |
| 容错 | Rerun 状态机 + FT integration | get_rerun_state_machine() |
pretrain() 调用 train() 进入训练主循环。train() 会在 while iteration < train_iters 循环中反复调用 train_step(),每步执行一次前向-反向-优化器更新。详见 训练循环篇。