DeepSpeed Zero-3 的参数分区与按需访问机制
【免费下载链接】DeepSpeedDeepSpeed is a deep learning optimization library that makes distributed training and inference easy, efficient, and effective.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSpeed
大模型训练时用普通数据并行,经常撞上torch.OutOfMemoryError: CUDA out of memory——每张卡都存一份完整参数,内存随卡数线性增长,加卡也解决不了单卡放不下。DeepSpeed 的 Zero-3 用参数分区解决它:把参数切成 N 份,每个数据并行进程只保留自己那份,算到某一层时再从其他卡临时凑齐完整副本,用完立刻释放。
💡如何最快跑通 Zero-3:克隆仓库git clone https://gitcode.com/GitHub_Trending/de/DeepSpeed并在 DeepSpeed 目录下执行pip install .。然后在训练脚本旁放一份最小配置:
{ "zero_optimization": { "stage": 3, "contiguous_gradients": true, "allgather_bucket_size": 500000000, "reduce_bucket_size": 500000000, "stage3_param_persistence_threshold": 100000, "stage3_gather_16bit_weights_on_model_save": true }, "bf16": {} }训练脚本里把优化器初始化换成deepspeed.initialize(model=model, model_parameters=model.parameters(), config="zero3.json"),即可开训。stage3_*参数不用全写,内置默认值在多数场景下可用,等碰到内存墙或吞吐瓶颈再回头调。
如何理解 ZeRO Stage 1/2/3 的分区对象与内存收益
ZeRO 三个阶段是渐进的:每一档都多分一类"每卡冗余"的数据。下图来自官方文档,以 7.5B 参数模型 + Adam 优化器 + 64 卡为例:
| 阶段 | 分区对象 | 上图示例中每卡内存 | 适用区间 |
|---|---|---|---|
| 基线数据并行 | 不分区,每卡全量 | 120GB | 小模型 |
| Stage 1 | 优化器状态(如 Adam 的动量、方差) | 31.4GB | 1B 级 |
| Stage 2 | 优化器状态 + 梯度 | 16.6GB | 1B~10B 级 |
| Stage 3 | 参数 + 梯度 + 优化器状态 | 1.9GB | 10B 级以上 |
例子里每卡内存从 120GB 降到 1.9GB,代价是通信量升到基线的约 1.5 倍。Stage 3 用异步预取把这部分通信藏进计算时间里——算当前层时,下一层参数已经在路上。
参数访问本身由一个状态机驱动,每个参数处于三种状态之一(定义见 partition_parameters.py 中的ZeroParamStatus):
NOT_AVAILABLE 本地只有分片,不能直接计算 │ allgather 发起 ▼ INFLIGHT 分片正在网络上传输 │ 传输完成 ▼ AVAILABLE GPU 上有完整副本,前向/反向可用 │ 使用完毕、引用计数归零 ▼ NOT_AVAILABLE (释放回分片形态,内存还给别的参数)围绕这个流转有两个优化点。其一是预取:fetch_sub_module(见 partitioned_param_coordinator.py)在当前层还没算完时就发起后续层参数的收集,参数到达时往往正好要用。其二是小参数驻留:元素数低于 1e5(默认值)的参数不参与分区,常驻每张卡,省掉高频小参数反复 gather 释放的往返开销。如果某个参数被定义它的模块之外的代码引用(典型如共享 embedding),需要显式调用register_external_parameter登记依赖,否则 DeepSpeed 会认为它没人用而提前释放。
stage3_max_live_parameters 等相关参数怎么调
这些配置都围绕一件事:每卡驻留多少参数。常用的几项:
| 参数 | 作用 | 建议值 | 调小的后果 | 调大的后果 |
|---|---|---|---|---|
stage3_max_live_parameters | 每卡释放前最多驻留的参数元素数 | 1e9 | 更省内存,gather/释放更频繁 | 更省通信、更吃内存,可能 OOM |
stage3_max_reuse_distance | 距离内会被复用的参数不释放 | 1e9 | 更省内存,密集复用的参数被反复释放 | 更吃内存 |
stage3_prefetch_bucket_size | 单次提前拉取的参数元素上限 | 5e7 | 预取不足,计算等通信 | 更吃内存,重叠更好 |
stage3_param_persistence_threshold | 元素数低于此值的参数不分区 | 1e5 | 小参数常驻变少,延迟受限的小消息变多 | 常驻小参数变多,占更多显存 |
allgather_bucket_size/reduce_bucket_size | all-gather / 归约的桶大小 | 5e8 | 通信更碎、延迟更高 | 通信缓冲更大 |
stage3_gather_16bit_weights_on_model_save | 保存 16bit 权重时是否先聚齐全量权重 | true | false 时 state_dict 不含完整权重 | — |
调参顺序建议:全部保持默认 → OOM 就先降stage3_max_live_parameters→ 不 OOM 但吞吐差就查stage3_prefetch_bucket_size和stage3_max_reuse_distance。开了 CPU/NVMe 卸载(offload_param/offload_optimizer,device设为cpu或nvme)后,前两项的下探空间更大。
Zero-3 常见报错的排查清单
- 使用参数时断言失败或挂起:参数在定义模块之外被访问(共享 embedding、别的模块引用 norm 权重),DeepSpeed 不知道要提前收集。用
register_external_parameter(module, param)登记外部依赖。 - 吞吐明显下降、通信占比高:预取桶或重用距离调得过小,gather-释放过于频繁。先恢复默认值对比,再逐步下调定位。
- 保存出的权重不完整:Zero-3 的权重分片散落在各卡上,
state_dict里拿不到完整张量。把stage3_gather_16bit_weights_on_model_save置为true,或在 rank 0 上先 gather 再保存。 - 显存 OOM:确认
offload_param/offload_optimizer是否配置、device是cpu还是nvme;同时检查stage3_param_persistence_threshold是否开得过大,导致大量小参数常驻显存。
Zero-3 本质是用"用到再临时凑齐、用完就释放"换取内存空间,上面所有配置项都在服务这件事:凑得更快、占得更少。从默认值出发,先观察显存占用与单步耗时,再逐项微调,方向就不容易跑偏。
【免费下载链接】DeepSpeedDeepSpeed is a deep learning optimization library that makes distributed training and inference easy, efficient, and effective.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSpeed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考