3. ZeRO Stage 3 参数切分
任务目标
本案例将配置DeepSpeed ZeRO Stage 3,在单机多GPU(或单GPU模拟)环境下训练模型,验证模型参数被切分到各GPU显存中,从而在有限显存下完成大模型训练。
环境依赖
- Python 3.8+,PyTorch 1.12+,DeepSpeed 0.9+,transformers 4.30+
- 安装:
pip install deepspeed transformers - 硬件:至少1块GPU(推荐4块,显存≥8GB)
核心实现
以下代码使用transformers加载(124M参数),通过DeepSpeed ZeRO Stage 3配置训练。Stage 3将模型参数、梯度和优化器状态全部切分到各GPU,每张卡仅持有1/世界大小参数分片。
import os
import torch
import deepspeed
from transformers import GPT2LMHeadModel, GPT2Tokenizer
# 1. 初始化DeepSpeed引擎(必须在模型加载前)
deepspeed.init_distributed()
# 2. 加载模型与分词器(模型先加载到CPU,由ZeRO-3切分)
model = GPT2LMHeadModel.from_pretrained("")
tokenizer = GPT2Tokenizer.from_pretrained("")
tokenizer.pad_ = tokenizer.eos_
# 3. DeepSpeed ZeRO Stage 3配置
ds_config = {
"train_batch_size": 8,
"gradient_accumulation_steps": 1,
"optimizer": {
"type": "AdamW",
"params": {"lr": 3e-5}
},
"zero_optimization": {
"stage": 3,
"offload_optimizer": {"device": "none"},
"offload_param": {"device": "none"},
"reduce_bucket_size": 5e8,
"stage3_prefetch_bucket_size": 5e8,
"stage3_param_persistence_threshold": 1e6
},
"fp16": {"enabled": True}
}
# 4. 用DeepSpeed包装模型
model_engine, optimizer, _, _ = deepspeed.initialize(
model=model,
model_parameters=model.parameters(),
config=ds_config
)
# 5. 构造训练数据(使用简单示例文本)
texts = [
"The quick brown fox jumps over the lazy dog.",
"DeepSpeed enables large model training with limited memory.",
"ZeRO stage 3 partitions parameters across GPUs.",
"Natural language processing benefits from large pretrained models."
]
inputs = tokenizer(texts, return_tensors="pt", padding=True, truncation=True, max_length=64)
input_ids = inputs["input_ids"].cuda()
attention_mask = inputs["attention_mask"].cuda()
labels = input_ids.clone()
# 6. 训练循环(3步验证参数分片)
model_engine.train()
for step in range(3):
outputs = model_engine(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
model_engine.backward(loss)
model_engine.step()
if model_engine.global_steps % 1 == 0:
print(f"Step {model_engine.global_steps}, Loss: {loss.item():.4f}")
# 7. 验证参数分片:检查每张卡上参数数量
param_count = sum(p.numel() for p in model_engine.module.parameters())
total_params = sum(p.numel() for p in GPT2LMHeadModel.from_pretrained("").parameters())
world_size = torch.distributed.get_world_size()
print(f"GPU {torch.distributed.get_rank()}: 本地参数数 {param_count}, 总参数数 {total_params}, 世界大小 {world_size}")
print(f"分片比例: {param_count / total_params:.4f} (预期约 1/{world_size})")
运行方式
# 单机4卡运行
deepspeed --num_gpus=4 train__zero3.py
# 单卡运行(模拟Stage 3,分片比例为1)
deepspeed --num_gpus=1 train__zero3.py
验证方法与判定标准
- 训练收敛:3步内loss应下降(如从约4.5降至约4.0以下),说明前向/反向传播正常。
- 参数分片验证:日志中“分片比例”应接近1/GPU数。例如4卡时约0.25,说明每卡仅持有25%参数,证明Stage 3生效。
- 显存对比:使用
nvidia-smi观察每卡显存占用,4卡训练时每卡显存应显著低于单卡加载完整模型所需(约1.2GB),通常每卡约400-600MB。
关键说明:ZeRO Stage 3的核心机制是在forward/backward过程中动态收集所需参数分片(通过all-gather),计算完成后立即释放,因此每卡仅永久持有1/世界大小的参数。上述代码第7步通过统计本地参数数量与总参数量的比值,直接验证了分片效果。
常见问题
- OOM错误:若显存不足,将
offload_param和offload_optimizer的device改为"cpu",将参数/优化器状态卸载到CPU内存。 - 单卡运行:Stage 3在单卡下分片比例为1,但代码仍可运行,用于功能验证。
- 版本兼容:transformers与DeepSpeed版本不匹配时,可固定使用
transformers==4.30.2和deepspeed==0.9.5。