主要参考官网文档,对于具体内容还需参考官方文档
随着深度学习模型规模的爆炸式增长(从 BERT 的几亿参数到 GPT-3 的千亿参数,再到现在的万亿参数模型),传统的单 GPU 训练方式变得力不从心,即使是多 GPU 训练也面临巨大挑战:
Memory Wall
):
Compute Efficiency
):
Communication Overhead
):
Engineering Complexity
):
DeepSpeed 应运而生,旨在解决上述挑战,使大型模型训练触手可及:
理解 DeepSpeed,首先要掌握其背后的几个关键技术。
ZeRO 是 DeepSpeed 最核心的内存优化技术,通过消除冗余内存来减少显存占用。它将模型状态(优化器状态、梯度、参数)在不同的 GPU 之间进行分片(partitioning
)。
ZeRO-Offload (CPU/NVMe 卸载):
ZeRO-Stage 1 (Optimizer States Partitioning):
原理: 将优化器状态(如 Adam 的动量和方差)在所有 GPU 上均匀分片。每个 GPU 只存储其分片的部分。
显存节省: 节省大约 4 倍于 FP16 参数的显存(因为 Adam 优化器状态通常是 FP32,占用 8 倍参数显存,分片后变为 8/N 倍,其中 N 是 GPU 数量)。
通信开销: 在梯度更新前需要 AllGather 完整的优化器状态。
示意图:
说明:P代表参数,G代表梯度,OS代表优化器状态。ZeRO-Stage 1 将 OS 在各 GPU 间分片。
ZeRO-Stage 2 (Optimizer States + Gradients Partitioning):
原理: 在 Stage 1 的基础上,进一步将梯度也在所有 GPU 上均匀分片。每个 GPU 只存储和处理其分片的部分梯度。
显存节省: 在 Stage 1 的基础上,再节省大约 3 倍于 FP16 参数的显存(梯度通常是 FP16,占用 2 倍参数显存,分片后变为 2/N 倍)。总共节省约 7 倍显存。
通信开销: 在反向传播过程中需要进行 AllReduce 来聚合完整的梯度,但在更新参数前,每个 GPU 只处理自己分片的那部分梯度。
示意图:
说明:ZeRO-Stage 2 将 G 和 OS 在各 GPU 间分片。
ZeRO-Stage 3 (Optimizer States + Gradients + Parameters Partitioning):
原理: 在 Stage 2 的基础上,进一步将模型参数也在所有 GPU 上均匀分片。每个 GPU 只存储其分片的部分参数。在需要完整参数进行前向/反向传播时,通过 AllGather 操作从其他 GPU 收集。
显存节省: 理论上可以节省高达 N 倍显存(N 为 GPU 数量),因为每个 GPU 只存储 1/N 的模型参数、梯度和优化器状态。
通信开销: 前向传播和反向传播都需要进行 AllGather 操作来获取完整的参数/梯度,通信量最大,但能支持最大模型。
示意图:
说明:ZeRO-Stage 3 将 P, G, OS 全部在各 GPU 间分片。
ZeRO-Infinity:
原理:
在训练过程中同时使用 FP32(单精度浮点数)和 FP16(半精度浮点数)。
DeepSpeed 实现: 自动管理 FP16 和 FP32 的转换、梯度缩放 (GradScaler) 以防止梯度下溢,以及参数副本的维护。
当模型过大,即使 ZeRO-Stage 3 也无法完全容纳在一个 GPU 上时,就需要模型并行。模型并行将模型的不同部分放在不同的 GPU 上。
DeepSpeed 提供了自己的优化器实现,这些优化器通常是高度优化的,并且能够与 ZeRO 技术无缝集成。例如,它有 DeepSpeedCPUAdam
(将 Adam 的计算卸载到 CPU) 和 FusedAdam
(GPU 上更快的 Adam 实现)。
使用 Conda 或 Virtualenv 创建隔离的 Python 环境。
conda create -n deepspeed_env python=3.10
conda activate deepspeed_env
查询 PyTorch 官方网站以获取兼容性信息
# 确保安装了正确版本的 PyTorch (例如,CUDA 12.1)
# 详情参考 PyTorch 官网,这里以 CUDA 12.1 为例
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# 安装 DeepSpeed
pip install deepspeed
# 如果需要从源码安装以获取最新功能或进行开发:
# git clone https://github.com/microsoft/DeepSpeed.git
# cd DeepSpeed
# pip install . --global-config-path
验证安装:
python -c "import deepspeed; print(deepspeed.__version__)"
DeepSpeed 的核心是其配置文件 (Configuration File)。将大部分优化选项写入一个 JSON 文件中,然后在训练脚本中通过 deepspeed.init_distributed()
和 deepspeed.initialize()
来加载和应用。
创建一个名为 ds_config.json
的 DeepSpeed 配置文件:
// ds_config.json
{
"train_batch_size": 16, // 训练批次大小 (全局批次大小 = train_batch_size)
"train_micro_batch_size_per_gpu": 2, // 每个 GPU 上的微批次大小 (Gradient Accumulation Steps = train_batch_size / (num_gpus * train_micro_batch_size_per_gpu))
"gradient_accumulation_steps": 8, // 梯度累积步数 (如果未设置,DeepSpeed 会根据前两个自动计算)
"optimizer": { // 优化器配置
"type": "AdamW", // 优化器类型,例如 AdamW
"params": {
"lr": 1e-5, // 学习率
"betas": [0.9, 0.999], // AdamW 参数
"eps": 1e-8,
"weight_decay": 0.01 // 权重衰减
}
},
"scheduler": { // 学习率调度器配置
"type": "WarmupLR", // 调度器类型,例如 WarmupLR
"params": {
"warmup_min_lr": 0,
"warmup_max_lr": 1e-5,
"warmup_num_steps": 1000
}
},
"fp16": { // 混合精度训练配置
"enabled": true, // 启用混合精度
"loss_scale": 0, // 0表示DeepSpeed自动管理,否则为固定值
"initial_scale_power": 12, // 初始梯度缩放因子 (2^12 = 4096)
"loss_scale_window": 1000,
"hysteresis": 2,
"min_loss_scale": 1
},
"zero_optimization": { // ZeRO 优化器配置
"stage": 2, // ZeRO 阶段 (0, 1, 2, 3)
"offload_optimizer": { // 优化器状态卸载到 CPU
"device": "cpu", // 卸载到 CPU
"pin_memory": true // 锁定 CPU 内存,提高传输速度
},
"offload_param": { // 参数卸载到 CPU (仅 Stage 3 配合使用)
"device": "cpu",
"pin_memory": true
},
"overlap_comm": true, // 开启通信与计算重叠
"contiguous_gradients": true, // 尝试使梯度内存连续
"sub_group_size": 1e9, // 较大的值表示禁用子组优化
"stage3_prefetch_bucket_size": 0, // stage3预取大小,0为禁用
"stage3_param_persistence_threshold": 1e5, // 仅对Stage3,参数持久化阈值
"stage3_max_live_parameters": 1e9, // Stage3 最大驻留参数
"stage3_max_act_size": 1e9 // Stage3 最大激活值大小
},
"gradient_clipping": 1.0, // 梯度裁剪,防止梯度爆炸
"prescale_gradients": false, // 启用 prescale_gradients 可能会提高 Stage3 的性能
"wall_clock_breakdown": false, // 是否详细记录训练时间分解
"sparse_attention": false, // 稀疏注意力 (针对稀疏模型)
"flops_profiler": { // FLOPS 分析器
"enabled": false,
"profile_step": 1,
"module_depth": -1,
"top_modules": 1,
"detailed": true,
"output_file": null
},
"activation_checkpointing": { // 激活值检查点 (类似于梯度检查点,节省显存)
"enabled": false,
"cpu_checkpointing": false, // 激活值检查点是否卸载到 CPU
"partition_activations": false // 激活值是否分片 (只适用于 ZeRO Stage 3)
},
"pipeline": { // 管道并行配置 (如果启用)
"seed_layers": false,
"micro_batch_size": 0
},
"steps_per_print": 10, // 每隔多少步打印一次日志
"seed": 1234, // 随机种子
"tensorboard": { // TensorBoard 日志
"enabled": true,
"output_path": "tensorboard_logs/",
"job_name": "my_deepspeed_experiment"
}
}
train_batch_size
vs train_micro_batch_size_per_gpu
vs gradient_accumulation_steps
:
train_batch_size
是全局等效的训练批次大小,它定义了模型在一个优化步骤中处理的数据量。train_micro_batch_size_per_gpu
是每个 GPU 上的微批次大小。gradient_accumulation_steps
是梯度累积的步数。train_batch_size = num_gpus * train_micro_batch_size_per_gpu * gradient_accumulation_steps
。如果你设置了前两个,DeepSpeed 会自动计算 gradient_accumulation_steps
。这是控制实际更新模型参数频率的关键。使用 deepspeed
命令行工具来启动训练脚本:
deepspeed --num_gpus=4 your_training_script.py --deepspeed_config ds_config.json
--num_gpus
: 指定使用的 GPU 数量。your_training_script.py
: 你的 PyTorch 训练脚本。--deepspeed_config
: 指定 DeepSpeed 配置文件的路径。只需要少量修改即可将 PyTorch 脚本转换为 DeepSpeed 脚本。
# your_training_script.py
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
import deepspeed
import argparse
# 1. 定义模型
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.linear1 = nn.Linear(10, 20)
self.relu = nn.ReLU()
self.linear2 = nn.Linear(20, 1)
def forward(self, x):
return self.linear2(self.relu(self.linear1(x)))
def get_args():
parser = argparse.ArgumentParser(description="DeepSpeed Training Example")
parser.add_argument('--deepspeed_config', default='ds_config.json', type=str,
help='DeepSpeed config file path')
parser.add_argument('--local_rank', type=int, default=-1, help='local rank passed from DeepSpeed')
args = parser.parse_args()
return args
def main():
args = get_args()
# 2. DeepSpeed 初始化
# model, optimizer, _, lr_scheduler = deepspeed.initialize(
# model=model,
# model_parameters=model.parameters(),
# config_params=ds_config_dict # 可以直接传递字典
# )
# 或者从配置文件加载
model = SimpleModel()
model_engine, optimizer, _, lr_scheduler = deepspeed.initialize(
args=args,
model=model,
model_parameters=model.parameters()
)
# 3. 准备数据 (示例数据)
# 确保数据在正确的设备上 (DeepSpeed 会自动处理数据到对应GPU)
# 对于DeepSpeed,通常数据加载无需手动.to(device)
dummy_data = torch.randn(100, 10).cuda()
dummy_labels = torch.randn(100, 1).cuda()
dataset = TensorDataset(dummy_data, dummy_labels)
# DeepSpeed 会处理分布式采样器
train_dataloader = DataLoader(dataset, batch_size=model_engine.train_micro_batch_size_per_gpu)
# 4. 定义损失函数
criterion = nn.MSELoss()
# 5. 训练循环
print(f"Rank {model_engine.local_rank}: Starting training...")
for epoch in range(10):
for i, (inputs, labels) in enumerate(train_dataloader):
# inputs, labels = inputs.cuda(), labels.cuda() # DeepSpeed会自动移动数据
outputs = model_engine(inputs)
loss = criterion(outputs, labels)
# 反向传播和优化
model_engine.backward(loss)
model_engine.step()
# 打印日志 (可选,DeepSpeed 会有自己的打印逻辑)
if (i + 1) % model_engine.steps_per_print() == 0:
print(f"Rank {model_engine.local_rank}, Epoch [{epoch+1}/10], Step [{i+1}/{len(train_dataloader)}], Loss: {loss.item():.4f}")
if model_engine.local_rank == 0: # 只有主进程保存
print(f"Epoch {epoch+1} finished.")
# 保存检查点
# model_engine.save_checkpoint(f"checkpoints/epoch_{epoch+1}")
# 保存最终模型 (所有进程都会保存自己的分片)
if model_engine.local_rank == 0:
model_engine.save_checkpoint(save_dir="final_model_checkpoint", client_state={"epoch": epoch})
print("Training finished and model saved.")
if __name__ == "__main__":
# DeepSpeed 使用 torch.distributed.launch 类似的机制
# 需要在命令行通过 deepspeed 命令启动,而不是直接 python your_script.py
main()
训练多个模型
model_engines = [engine for engine, _, _, _ in [deepspeed.initialize(m, ...,) for m in models]]
for batch in data_loader:
losses = [engine(batch[0], batch[1]) for engine in model_engines]
loss = sum(l / (i + 1) for i, l in enumerate(losses))
loss.backward()
for engine in model_engines:
engine._backward_epilogue()
for engine in model_engines:
engine.step()
for engine in model_engines:
engine.optimizer.zero_grad()
除了使用多个 DeepSpeedEngine 之外,上述用法与典型用法在两个关键方面有所不同:
deepspeed.init_inference()
返回一个 推理引擎,其类型为InferenceEngine
deepspeed.init_inference()
传入config
参数,对于config
支持以下几种
class deepspeed.inference.config.InferenceCheckpointConfig
deepspeed.inference.config.DeepSpeedInferenceConfig
deepspeed.inference.config.DeepSpeedTPConfig
deepspeed.inference.config.DeepSpeedMoEConfig
deepspeed.inference.config.QuantizationConfig
对每个
config
具体参数参考
for step, batch in enumerate(data_loader):
#forward() method
loss = engine(batch)
ds_config.json
中配置 zero_optimization.offload_optimizer
和 zero_optimization.offload_param
。nvme_path
配置)比 CPU 卸载能支持更大的模型,但速度更慢。zero_optimization.stage
参数。activation_checkpointing
等技术配合以进一步节省显存。overlap_comm
(通信与计算重叠) 和 contiguous_gradients
(梯度内存连续性) 对性能至关重要,建议在 Stage 2/3 中启用。在 ds_config.json
中设置 fp16.enabled: true
。DeepSpeed 会自动管理:
GradScaler
,DeepSpeed 会根据配置文件中的 loss_scale
、initial_scale_power
等参数自动处理。DeepSpeed 主要关注数据并行和内存优化(通过 ZeRO)。虽然它本身不直接提供像 Megatron-LM 那样的复杂张量并行模块,但可以通过与这些库的集成来支持模型并行。DeepSpeed 提供了原生管道并行。
将模型垂直切分,不同层在不同 GPU 上。
DeepSpeed 中的配置: 在 ds_config.json
中配置 pipeline.stages
(如果使用)。
代码示例(概念性):
# DeepSpeed 管道并行示例(概念性,实际实现更复杂)
# 通常需要定义好各个子模块,并指定哪些模块属于哪个pipeline stage
# DeepSpeed会帮你处理forward/backward pass在不同GPU上的调度
import deepspeed
from deepspeed.pipe import PipelineModule, LayerSpec
class MyPipelineModel(PipelineModule):
def __init__(self, **kwargs):
# 定义模型层,这里用LayerSpec来表示每个阶段包含哪些层
# 实际模型会更复杂,例如 Transformer Blocks
super().__init__(layers=[
LayerSpec(nn.Linear, 10, 20),
LayerSpec(nn.ReLU),
LayerSpec(nn.Linear, 20, 30),
LayerSpec(nn.ReLU),
LayerSpec(nn.Linear, 30, 1)
], **kwargs)
# 实际使用时,需要在配置文件中开启 pipeline 模式,并设置 micro_batch_size
# "pipeline": {
# "stages": N_STAGES, // N_STAGES 个阶段,对应 N_STAGES 个 GPU
# "micro_batch_size": YOUR_MICRO_BATCH_SIZE
# }
# 然后在 initialize 时传入 PipeLineModule 实例
# model_engine, optimizer, _, _ = deepspeed.initialize(
# args=args,
# model=MyPipelineModel(loss_fn=nn.MSELoss()), # 需要指定损失函数
# model_parameters=[p for p in model.parameters() if p.requires_grad]
# )
DeepSpeed 封装了许多标准优化器,并提供了一些定制优化器,例如:
zero_optimization.offload_optimizer.device
设置为 “cpu” 时,DeepSpeed 会自动选择使用它。DeepSpeed 支持多种学习率调度器(如 WarmupLR, ReduceLROnPlateau 等),可以在配置文件中直接配置。
训练大型模型通常需要很长时间,因此保存和加载检查点以防止训练中断以及进行实验是非常重要的。
DeepSpeed 的检查点: DeepSpeed 能够以分布式方式保存和加载模型参数、优化器状态和学习率调度器状态。对于 ZeRO-Stage 3,它会智能地处理分片后的参数。
保存:
model_engine.save_checkpoint(save_dir, client_state=None)
save_dir
: 保存检查点的目录。client_state
: 可以保存任何自定义状态信息(如当前 epoch 数)。加载:
load_path, client_state = model_engine.load_checkpoint(load_dir, tag=None, load_module_only=False)
load_dir
: 检查点所在的目录。tag
: 如果有多个检查点,可以指定标签。load_module_only
: 如果只加载模型参数而不加载优化器和调度器状态,设为 True。train_micro_batch_size_per_gpu
。gradient_accumulation_steps
。zero_optimization.stage
提高到 2 或 3。zero_optimization.offload_optimizer
和/或 offload_param
到 CPU/NVMe。activation_checkpointing
。torch.distributed.init_process_group
等初始化正确。zero_optimization.offload_optimizer
配置,权衡卸载量。overlap_comm
和 contiguous_gradients
。train_micro_batch_size_per_gpu
。wall_clock_breakdown
和 flops_profiler
进行性能分析。gradient_accumulation_steps
来增加等效批次大小,同时保持较小的微批次大小以节省显存。overlap_comm: true
可以有效隐藏通信延迟。activation_checkpointing
是一个非常有效的显存优化手段,它会重新计算激活值而不是存储它们。flops_profiler
来分析模型的计算瓶颈。num_workers
适当并行加载。DeepSpeed 提供了丰富的日志输出。结合 TensorBoard 可以可视化训练过程中的各种指标(损失、学习率、内存使用等)。
DeepSpeed 与 Hugging Face 的 transformers
库高度集成。Hugging Face 提供了可以直接使用 DeepSpeed 的 Trainer
类。
使用方式:
# 使用 transformers 命令行工具
transformers-cli run --model_name_or_path <model_path> \
--deepspeed <deepspeed_config_file> \
--num_gpus <num_gpus> \
--do_train \
# ... 其他训练参数
或者在 Python 脚本中:
from transformers import Trainer, TrainingArguments, AutoModelForSequenceClassification, AutoTokenizer
# ... 定义模型、tokenizer、数据集
training_args = TrainingArguments(
output_dir="./output",
deepspeed="ds_config.json", # 指定 DeepSpeed 配置文件
# ... 其他参数
)
trainer = Trainer(
model=model,
args=training_args,
# ... 其他 Trainer 参数
)
trainer.train()