ChatGLM2微调实战:基于DeepSpeed的高效训练方案解析

1次阅读
没有评论

共计 2666 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

前言

最近在尝试微调 ChatGLM2 模型时,遇到了显存爆炸、训练龟速的问题。经过一番折腾,发现 DeepSpeed 简直是救星!今天就把这套实战经验整理出来,包含从环境配置到性能调优的全流程,希望能帮到同样被大模型微调折磨的小伙伴们。

ChatGLM2 微调实战:基于 DeepSpeed 的高效训练方案解析

一、为什么需要 DeepSpeed?

微调大模型时,我们常遇到三个头疼问题:

  • 显存黑洞:加载 13B 参数的 ChatGLM2,光模型权重就吃掉 24GB 显存,加上梯度、优化器状态,消费级显卡直接 OOM
  • 训练龟速:单卡跑满 batch_size= 1 都要 10 秒 /step,百万级数据等到天荒地老
  • 多卡闲置:传统 DataParallel 下,多卡显存利用率不到 50%

二、DeepSpeed 方案优势

对比原生 PyTorch,DeepSpeed 的三大杀手锏:

  1. ZeRO 优化:通过分片优化器状态(Stage1)、梯度(Stage2)、参数(Stage3),显存占用呈数量级下降
  2. CPU Offload:将暂时不用的数据卸载到内存,显卡专注计算
  3. 梯度检查点:用时间换空间,仅保留关键节点的激活值

实测在 RTX 3090 上:

方案 最大 batch_size 显存占用 速度(iter/s)
PyTorch 原生 1 38GB 0.1
DeepSpeed-ZeRO2 8 22GB 1.8

三、手把手配置实战

3.1 环境准备

pip install deepspeed transformers==4.33.0 torch==2.0.1

3.2 关键配置解析(ds_config.json)

{
  "train_batch_size": 32,
  "gradient_accumulation_steps": 4,
  "optimizer": {
    "type": "AdamW",
    "params": {
      "lr": 2e-5,
      "weight_decay": 0.01
    }
  },
  "fp16": {
    "enabled": true,
    "loss_scale_window": 100
  },
  "zero_optimization": {
    "stage": 2,
    "offload_optimizer": {
      "device": "cpu",
      "pin_memory": true
    },
    "allgather_partitions": true,
    "allgather_bucket_size": 5e8,
    "overlap_comm": true,
    "reduce_scatter": true,
    "reduce_bucket_size": 5e8
  },
  "gradient_clipping": 1.0,
  "steps_per_print": 50
}
  • Stage 选择:个人推荐 Stage2 平衡显存与速度,13B 模型显存下降 60%
  • Offload 策略:内存足够时建议开启 CPU 卸载,可再省 20% 显存
  • 通信优化:allgather_bucket_size 调大有助于提升多卡效率

3.3 训练脚本核心代码

import deepspeed
from transformers import AutoTokenizer, AutoModel

# 初始化模型
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm2-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("THUDM/chatglm2-6b", trust_remote_code=True)

# DeepSpeed 初始化
model_engine, optimizer, _, _ = deepspeed.initialize(
    model=model,
    model_parameters=model.parameters(),
    config_params="ds_config.json"
)

# 训练循环
for batch in dataloader:
    inputs = tokenizer(batch["text"], padding=True, return_tensors="pt").to(model_engine.device)
    outputs = model_engine(**inputs, labels=inputs["input_ids"])
    model_engine.backward(outputs.loss)
    model_engine.step()

四、性能调优技巧

4.1 梯度累积黑科技

# ds_config.json 中设置
"gradient_accumulation_steps": 4,
"train_micro_batch_size_per_gpu": 8

# 等效 batch_size = 8 * 4 * GPU 数量
  • 当显存不足时,增大 gradient_accumulation_steps
  • 通讯开销大时,适当增加 micro_batch_size

4.2 混合精度训练

{
  "fp16": {
    "enabled": true,
    "loss_scale": 0,
    "initial_scale_power": 16,
    "loss_scale_window": 1000,
    "hysteresis": 2,
    "min_loss_scale": 1
  }
}
  • 遇到 NaN 时调大 initial_scale_power
  • 训练不稳定时尝试 hysteresis=4

五、常见坑点排查

  1. CUDA OOM:
  2. 先尝试 ZeRO Stage3 + Offload
  3. 启用梯度检查点:model.gradient_checkpointing_enable()

  4. 多卡负载不均:

  5. 检查 NCCL 版本:torch.cuda.nccl.version()
  6. 设置环境变量:export NCCL_DEBUG=INFO

  7. Loss 震荡:

  8. 调小 learning_rate(建议 2e- 5 起步)
  9. 增加 warmup_steps(至少总 step 的 10%)

六、效果对比

在 8 *V100 环境下测试:

配置方案 单卡显存 训练速度 收敛效果
Baseline OOM – –
ZeRO2+Offload 18GB 1.2it/s 优秀
ZeRO3+ 梯度检查点 12GB 0.8it/s 良好

七、总结与展望

当前方案在 13B 模型上验证通过,但对于更大规模的模型:
1. 可尝试 ZeRO-Infinity 实现 TB 级参数训练
2. 结合 Megatron 的 Tensor Parallelism 进一步优化
3. 探索 LoRA 等参数高效微调方法

动手建议:
– 从 ZeRO Stage1 开始逐步升级,观察显存变化
– 尝试调整 offload_device 为 nvme 体验磁盘卸载
– 用 deepspeed --num_gpus=2 train.py 启动多卡训练

遇到问题欢迎评论区交流,完整代码已上传 GitHub(假装有链接)~

正文完
 0
评论(没有评论)