
DeepSpeed
用 DeepSpeed 引擎改造 PyTorch 模型并启动多机训练
从安装 DeepSpeed、用 deepspeed.initialize 包装 torch.nn.Module,到写出 ds_config.json、保存检查点,再用 deepspeed 启动器在单机或多机上拉起训练任务,包括 hostfile 资源划分与免 SSH 的 no_ssh 模式。
DeepSpeed 是面向大规模模型训练的分布式训练框架,核心是一个可以包裹任意 torch.nn.Module 的训练引擎。它把分布式数据并行、混合精度、梯度累积、优化器与学习率调度器管理、检查点保存这些原本需要手写的样板代码收进引擎内部,对外只暴露少量 API。本文要解决的问题是:把一个已经能跑的 PyTorch 训练脚本,改造成由 DeepSpeed 引擎驱动,并用 deepspeed 启动器在单机或多机上拉起任务。适合已经写过 PyTorch 训练循环、需要把训练扩展到多卡多机的读者。如果你只是想先了解这类训练框架的定位,可以在 DeepSpeed 工具页看概览,再回到本文动手改造。
准备工作
安装
安装本身只有一条命令:
pip install deepspeed
DeepSpeed 与 HuggingFace Transformers、PyTorch Lightning 都有直接集成。使用 Transformers 时,可以通过一个 --deepspeed 参数加上配置文件来加速模型训练;使用 PyTorch Lightning 时,则通过 Lightning Trainer 接入 DeepSpeed。这两条路径不需要自己写 deepspeed.initialize,本文主要讲原生 PyTorch 模型的改造方式。
硬件与加速器
DeepSpeed 默认使用 NCCL 后端,这是经过充分测试的组合。除 NVIDIA GPU 外,它还支持 AMD 的 ROCm 环境(有对应的 ROCm 镜像可用)、Intel Xeon CPU、Intel Data Center Max 系列 XPU、Intel Gaudi HPU、华为昇腾 NPU 等加速器,具体安装与配置方式需要参考对应加速器的设置指南。不同加速器支持的算子与特性范围并不一致,选型前应以官方当前文档为准。
代码与配置前置条件
- 一个可运行的 PyTorch 训练脚本,模型是
torch.nn.Module的子类。 - 一个 DeepSpeed 配置 JSON 文件,通常命名为
ds_config.json。 - 多机训练时,需要一份 hostfile,列出主机名(或 SSH 别名)与每台机器的 GPU 槽位数。
- 多机训练默认要求节点之间可以免密 SSH 登录;如果做不到,可以使用后文的
--no_ssh模式。
操作步骤
第一步:用 deepspeed.initialize 初始化引擎
DeepSpeed 的训练由引擎完成。引擎包裹模型,并提供训练与检查点相关的最小 API 集合。初始化写法如下:
model_engine, optimizer, _, _ = deepspeed.initialize(
args=cmd_args,
model=model,
model_parameters=params
)
deepspeed.initialize 会在内部完成分布式数据并行或混合精度训练所需的全部准备工作。除了包裹模型,它还会根据传入的参数和 DeepSpeed 配置文件,构造并管理训练优化器、数据加载器和学习率调度器。需要注意,DeepSpeed 会在每个训练步自动执行学习率调度。
第二步:处理已有的分布式初始化代码
如果你的脚本里已经有分布式环境初始化,需要把这一行:
torch.distributed.init_process_group(...)
替换为:
deepspeed.init_distributed()
默认使用 NCCL 后端,也可以覆盖默认值。如果分布式环境在 deepspeed.initialize() 之前并不需要,那么不必调用这个函数,DeepSpeed 会在初始化过程中自动完成分布式环境初始化。无论哪种情况,原来代码里的 torch.distributed.init_process_group 都必须移除。
第三步:改写训练循环
引擎初始化完成后,训练循环只需要三个 API:前向传播(引擎本身可调用)、反向传播(backward)、权重更新(step)。
for step, batch in enumerate(data_loader):
# 前向传播
loss = model_engine(batch)
# 反向传播
model_engine.backward(loss)
# 权重更新
model_engine.step()
在这三个调用背后,DeepSpeed 自动完成了分布式数据并行训练所需的操作,并以混合精度、配合预定义的学习率调度器运行:
- 梯度平均:在分布式数据并行训练中,
backward会在按train_batch_size完成训练后,确保梯度在各数据并行进程之间取平均。 - 损失缩放:在 FP16 或混合精度训练中,引擎自动处理 loss 的缩放,避免梯度精度损失。
- 学习率调度:当使用 DeepSpeed 的学习率调度器(在
ds_config.json中指定)时,每次执行model_engine.step(),DeepSpeed 都会调用调度器的step()方法。
如果不使用 DeepSpeed 的学习率调度器,分两种情况处理:
- 调度需要在每个训练步执行:把调度器传给
deepspeed.initialize,由 DeepSpeed 负责更新以及保存/恢复。 - 调度按其他间隔执行(例如按训练轮次):不要在初始化时把调度器传给 DeepSpeed,必须自己显式管理。
第四步:编写 DeepSpeed 配置文件
各项特性通过一个 JSON 配置文件开启、关闭或配置,路径由 args.deepspeed_config 指定。一个最小示例:
{
"train_batch_size": 8,
"gradient_accumulation_steps": 1,
"optimizer": {
"type": "Adam",
"params": {
"lr": 0.00015
}
},
"fp16": {
"enabled": true
},
"zero_optimization": true
}
这里 train_batch_size 是全局训练批大小,gradient_accumulation_steps 控制梯度累积步数,optimizer 段声明优化器类型与参数,fp16.enabled 打开混合精度,zero_optimization 打开 ZeRO 优化。完整特性集合以官方 API 文档为准。
第五步:用 deepspeed 启动器拉起训练
安装 DeepSpeed 会同时安装 deepspeed 入口点,用它来启动分布式训练。假设已经完成模型改造,client_entry.py 是入口脚本,client args 是 argparse 命令行参数,ds_config.json 是配置文件,单机启动命令形如:
deepspeed client_entry.py <client args> \
--deepspeed --deepspeed_config ds_config.json
第六步:配置多机资源(hostfile)
多机资源通过 hostfile 配置,格式与 OpenMPI、Horovod 兼容。hostfile 每行是一台机器的主机名(或 SSH 别名)加上槽位数,槽位数表示该机器可用的 GPU 数量:
worker-1 slots=4
worker-2 slots=4
这表示 worker-1 与 worker-2 各有 4 块 GPU 参与训练。hostfile 通过 --hostfile 命令行选项指定。如果没有指定 hostfile,DeepSpeed 会查找 /job/hostfile;如果既没有指定也没有找到,DeepSpeed 会查询本机 GPU 数量,以确定本地可用槽位数。
下面这条命令会在 myhostfile 中列出的所有节点和 GPU 上启动 PyTorch 训练任务:
deepspeed --hostfile=myhostfile <client_entry.py> <client args> \
--deepspeed --deepspeed_config ds_config.json
第七步:限制参与训练的节点与 GPU
可以只使用可用资源的一个子集,通过 --num_nodes 与 --num_gpus 两个参数控制。例如只使用两台节点:
deepspeed --num_nodes=2 \
<client_entry.py> <client args> \
--deepspeed --deepspeed_config ds_config.json
也可以用 --include 与 --exclude 精确地包含或排除特定资源。例如使用除 worker-2 的 GPU 0 以及 worker-3 的 GPU 0、1 之外的全部资源:
deepspeed --exclude="worker-2:0@worker-3:0,1" \
<client_entry.py> <client args> \
--deepspeed --deepspeed_config ds_config.json
反过来,只使用 worker-2 上的 GPU 0 和 1:
deepspeed --include="worker-2:0,1" \
<client_entry.py> <client args> \
--deepspeed --deepspeed_config ds_config.json
第八步:在无免密 SSH 的环境下启动
在 Kubernetes 这类容器编排灵活、搭建 leader-worker 免密 SSH 反而增加复杂度的云环境中,可以使用 no-SSH 模式。该模式下需要在所有节点上分别执行 DeepSpeed 命令,命令结构如下:
deepspeed --hostfile=myhostfile --no_ssh --node_rank=<n> \
--master_addr=<addr> --master_port=<port> \
<client_entry.py> <client args> \
--deepspeed --deepspeed_config ds_config.json
各参数含义:
--hostfile=myhostfile:指定包含节点与 GPU 信息的 hostfile。--no_ssh:启用 no-SSH 模式。--node_rank=<n>:节点序号,必须是 0 到 n-1 之间唯一的整数。--master_addr=<addr>:leader 节点(rank 0)的地址。--master_port=<port>:leader 节点的端口。
这种模式下,hostfile 中的主机名不需要能通过免密 SSH 访问,但 hostfile 仍然必需,因为启动器要靠它收集节点数量、每节点 GPU 数量等环境信息。每个节点必须用唯一的 node_rank 启动,并且所有节点都要拿到 leader 节点的地址与端口。该模式让启动器的行为接近 torchrun。
第九步:传播自定义环境变量
多机训练时,DeepSpeed 默认会传播所有已设置的 NCCL 与 PYTHON 相关环境变量。如果需要额外传播其他变量,可以在执行目录下创建一个名为 .deepspeed_env 的点文件,内容为按行分隔的 VAR=VAL 条目,启动器会读取它。
一个完整示例
下面把前面的步骤串成一个可运行的最小流程。假设入口脚本为 train.py,模型与数据加载器已在脚本内定义。
1. 配置文件 ds_config.json
{
"train_batch_size": 8,
"gradient_accumulation_steps": 1,
"optimizer": {
"type": "Adam",
"params": {
"lr": 0.00015
}
},
"fp16": {
"enabled": true
},
"zero_optimization": true
}
2. 入口脚本 train.py 的关键片段
import argparse
import torch
import deepspeed
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", type=int, default=-1)
parser.add_argument("--deepspeed_config", type=str, required=True)
parser.add_argument("--save_dir", type=str, default="./ckpt")
parser.add_argument("--save_interval", type=int, default=1000)
args = parser.parse_args()
model = MyModel()
params = [p for p in model.parameters() if p.requires_grad]
model_engine, optimizer, _, _ = deepspeed.initialize(
args=args,
model=model,
model_parameters=params
)
client_sd = {}
for step, batch in enumerate(data_loader):
loss = model_engine(batch)
model_engine.backward(loss)
model_engine.step()
if step % args.save_interval == 0:
client_sd["step"] = step
ckpt_id = loss.item()
model_engine.save_checkpoint(args.save_dir, ckpt_id, client_sd=client_sd)
3. 启动命令
单机多卡,直接使用本机全部 GPU:
deepspeed train.py --deepspeed_config ds_config.json
多机,使用 hostfile 中列出的全部资源:
deepspeed --hostfile=myhostfile train.py \
--deepspeed_config ds_config.json
4. 从检查点恢复训练
检查点的保存与加载通过 save_checkpoint 与 load_checkpoint 完成,二者都用两个参数唯一标识一个检查点:ckpt_dir 是保存目录,ckpt_id 是目录内唯一标识该检查点的标识符。下面的片段用 loss 值作为检查点标识符,并把训练步数存进客户端状态字典:
# 加载检查点
_, client_sd = model_engine.load_checkpoint(args.load_dir, args.ckpt_id)
step = client_sd["step"]
# 把数据加载器推进到检查点对应的步
dataloader_to_step(data_loader, step + 1)
for step, batch in enumerate(data_loader):
loss = model_engine(batch)
model_engine.backward(loss)
model_engine.step()
if step % args.save_interval == 0:
client_sd["step"] = step
ckpt_id = loss.item()
model_engine.save_checkpoint(args.save_dir, ckpt_id, client_sd=client_sd)
DeepSpeed 会自动保存和恢复模型、优化器与学习率调度器的状态,把这些细节对用户隐藏。如果训练过程还有模型特有的额外数据需要保存,就通过 save_checkpoint 的 client_sd 参数传入,加载时再从 load_checkpoint 的返回值中取回。上面的例子就是把 step 存进了 client_sd。
注意事项
- 检查点必须所有进程都调用:
save_checkpoint不能只由 rank 0 进程调用。每个进程都需要保存自己的 master weights 以及调度器、优化器状态。如果只让 rank 0 调用,该方法会一直挂起等待与其他进程同步。 - 不要保留旧的分布式初始化:脚本里原有的
torch.distributed.init_process_group必须删除,否则会与 DeepSpeed 的初始化冲突。 - 学习率调度器的归属要明确:按步执行的调度器交给 DeepSpeed 管理;按轮次等其他间隔执行的调度器不要传给 DeepSpeed,自己显式调用。
- hostfile 的查找顺序:未指定
--hostfile时先找/job/hostfile,再退回到查询本机 GPU 数量。多机场景下如果忘了指定 hostfile,任务可能只在本地启动。 - no-SSH 模式仍需 hostfile:主机名不必可免密 SSH 访问,但启动器依赖 hostfile 获取节点数与每节点 GPU 数。
- node_rank 必须唯一:no-SSH 模式下每个节点的
--node_rank必须是 0 到 n-1 之间不重复的整数,且所有节点都要配置相同的 leader 地址与端口。 - 加速器支持范围:不同硬件后端(ROCm、Intel XPU、Gaudi HPU、昇腾 NPU 等)的可用特性与安装方式不同,具体支持情况、镜像标签与版本对应关系请以官网当前信息为准。
- 配置项与默认值:本文示例中的批大小、学习率、梯度累积步数只是演示值,实际取值需按模型与硬件调整;完整配置项与默认值以官方 API 文档当前版本为准。