ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ChatGLM 大模型微调实战:LoRA 与 DeepSpeed 多 GPU 分布式训练指南

ChatGLM 大模型微调实战:LoRA 与 DeepSpeed 多 GPU 分布式训练指南 简介这份资源面向希望掌握大模型微调与分布式训练的开发者提供基于LoRA、DeepSpeed与多GPU环境对ChatGLM进行微调的完整项目源码适合具备一定深度学习基础、想切入大模型实战的中高级学习者。压缩包共376个文件约170.03MB以193个Python脚本为核心辅以json、yaml、sh等配置与启动文件以及pickle、pkl、pt等权重与数据缓存另有jpg、png、pdf、md等说明与文档资料目录结构便于按模块查阅。项目围绕LoRA低秩分解降低多GPU通信开销、DeepSpeed的ZeRO优化器与混合精度训练展开源码覆盖模型初始化、预训练权重加载、损失函数定义、优化器与学习率调度、数据准备处理及评估验证等环节可帮助读者理解ChatGLM对话生成微调的完整链路。目前已有795人学习适合作为大模型微调与分布式训练的实操参考。1. ChatGLM 微调这件事卡住人的从来不是模型本身ChatGLM 系列放到今天已经不算新面孔但它在中文对话场景里的性价比依然能打。真正让一线工程师头疼的是「大模型 finetune」这条链路里那些琐碎又致命的环节LoRA 的秩设多少、DeepSpeed 的 ZeRO 阶段怎么选、多 GPU 之间梯度怎么同步、显存什么时候会突然炸掉。你搜「大模型微调实战」出来的教程要么只贴一段Trainer调用要么把 DeepSpeed 配置当成黑匣子直接甩给你跑不通就卡在那里。这篇要讲的是用 LoRA 做参数高效微调用 DeepSpeed 做多 GPU 分布式训练把 ChatGLM 在一个自定义指令数据集上跑通。适合手里有 2 到 8 张卡、想把通用对话模型往垂直领域掰一掰的从业者。读完你应该能自己写出配置文件、启动训练、看懂 loss 曲线、在翻车时知道先查哪里。不涉及推理部署和量化那是另一条线的事。2. LoRA 与 DeepSpeed 的分工谁省显存谁管并行2.1 LoRA 到底改了什么为什么它能把显存压下来全量微调 ChatGLM-6B光是优化器状态加梯度就要吃掉数倍于模型本身的显存。LoRA 的思路是不动原始权重在注意力层的线性变换旁边挂一对低秩矩阵 A 和 B前向时把BA加到原输出上。原始权重冻结只有 A 和 B 参与梯度更新。这意味着三件事第一可训练参数量从几十亿降到几百万到几千万量级第二优化器状态只针对这部分参数显存占用断崖式下降第三训练完可以把BA合并回原权重推理时不增加任何延迟。关键参数是秩r和缩放系数lora_alpha。r决定低秩矩阵的秩常见取值 4、8、16、32。lora_alpha一般设成r的两倍或固定 16/32。还有一个target_modules决定往哪些层挂 LoRA。ChatGLM 里通常是query_key_value这个融合了 QKV 的线性层有些实现也会加上dense和dense_h_to_4h。from peft import LoraConfig, TaskType, get_peft_model lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r8, # 秩越大容量越强但显存和过拟合风险上升 lora_alpha32, # 缩放系数通常为 r 的 2~4 倍 lora_dropout0.1, # 防过拟合小数据集建议 0.05~0.1 target_modules[query_key_value], # ChatGLM 的 QKV 融合层 biasnone, # 不训练 bias省显存 ) model get_peft_model(base_model, lora_config) model.print_trainable_parameters()print_trainable_parameters()会打印可训练参数占比正常应该在 0.1% 到 1% 之间。如果这个数字大得离谱八成是target_modules写错了把整个模型都挂上了。2.2 DeepSpeed ZeRO 三个阶段选错了就是白跑DeepSpeed 解决的是多卡场景下显存和通信的平衡问题。ZeRO 把训练状态切分到各张卡上阶段越高切得越狠但通信量也越大。ZeRO 阶段切分对象显存节省通信开销适用场景Stage 1优化器状态约 4 倍低显存略紧卡间带宽一般Stage 2优化器状态 梯度约 8 倍中最常用6B~13B 模型首选Stage 3优化器状态 梯度 参数线性于卡数高大模型或单卡显存极小对 ChatGLM-6B 配 LoRAStage 2 基本够用。Stage 3 虽然省显存但参数切分后每次前向都要 all-gather卡间通信不好的机器上反而拖慢训练。我一般先用 Stage 2 跑OOM 了再升 Stage 3。配置文件里还有几个必须盯的参数train_batch_size是全局批次train_micro_batch_size_per_gpu是单卡批次两者比值就是梯度累积步数。gradient_clipping设 1.0 防止梯度爆炸。fp16或bf16根据显卡架构选A100/H100 用 bf16V100/2080Ti 用 fp16。{ train_batch_size: 64, train_micro_batch_size_per_gpu: 4, gradient_accumulation_steps: 4, gradient_clipping: 1.0, bf16: { enabled: true }, zero_optimization: { stage: 2, offload_optimizer: { device: none }, allgather_partitions: true, overlap_comm: true, contiguous_gradients: true }, steps_per_print: 50 }overlap_comm让通信和计算重叠contiguous_gradients减少显存碎片这两个开着基本没坏处。offload_optimizer如果设成cpu可以进一步省显存但速度会明显下降除非真的卡不够否则不建议。2.3 多 GPU 启动命令与数据并行策略DeepSpeed 用torchrun或deepspeed启动。假设你有 4 张卡脚本叫train.py配置叫ds_config.jsontorchrun --nproc_per_node4 --master_port29500 train.py \ --model_name_or_path /path/to/chatglm-6b \ --train_file data/train.json \ --val_file data/val.json \ --output_dir output/chatglm-lora \ --num_train_epochs 3 \ --learning_rate 2e-4 \ --per_device_train_batch_size 4 \ --gradient_accumulation_steps 4 \ --deepspeed ds_config.jsonnproc_per_node必须等于你实际使用的 GPU 数。master_port随便选一个没被占用的端口多机训练时所有节点要一致。数据并行下每张卡拿到不同的数据分片梯度通过 all-reduce 同步DeepSpeed 会自动处理。学习率对 LoRA 来说通常比全量微调大1e-4 到 5e-4 都有人用。我一般从 2e-4 起步配 cosine 调度和 warmup。如果 loss 前期震荡厉害先降学习率而不是改 batch size。3. 从数据到权重ChatGLM 微调的可复现流程3.1 指令数据的格式与预处理ChatGLM 的微调数据一般组织成多轮对话格式。每条样本是一个列表里面交替出现role和content。常见做法是构造成promptresponse的配对或者直接拼成 ChatGLM 自己的对话模板。import json def build_sample(instruction, input_text, output_text): # 按 ChatGLM 对话格式拼接注意保留分隔符 prompt f问{instruction}\n{input_text}\n答 return {prompt: prompt, response: output_text} raw [ {instruction: 判断情感, input: 这家店服务太差了, output: 负面}, {instruction: 提取实体, input: 张三在北京工作, output: 人名张三地点北京}, ] with open(data/train.json, w, encodingutf-8) as f: for item in raw: f.write(json.dumps(build_sample(**item), ensure_asciiFalse) \n)数据量方面垂直领域任务 500 到 5000 条高质量样本就能看到明显效果。关键是质量而不是数量标注不一致的数据会让模型学出矛盾行为。验证集留 5% 到 10%用来观察是否过拟合。预处理时要把prompt部分做 tokenize 并设置labels为 -100只对response部分计算 loss。这一步很多教程会漏掉导致模型把问题也当成要学的内容推理时容易复读用户输入。3.2 训练脚本的核心结构与关键参数训练脚本用 HuggingFaceTrainer加 DeepSpeed 集成是最省事的路径。核心是继承DataCollator处理变长序列以及正确设置TrainingArguments。from transformers import Trainer, TrainingArguments, DataCollatorForSeq2Seq training_args TrainingArguments( output_diroutput/chatglm-lora, per_device_train_batch_size4, gradient_accumulation_steps4, learning_rate2e-4, num_train_epochs3, lr_scheduler_typecosine, warmup_ratio0.03, logging_steps20, save_strategyepoch, evaluation_strategyepoch, fp16False, bf16True, deepspeedds_config.json, report_tonone, ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, eval_datasetval_dataset, data_collatorDataCollatorForSeq2Seq(tokenizer, paddingTrue), ) trainer.train()warmup_ratio设 0.03 到 0.1让学习率从 0 线性升上去避免一开始就大步更新破坏预训练权重。save_strategy和evaluation_strategy设成epoch适合小数据集数据量大时改成steps并指定save_steps。bf16和fp16不能同时开。判断标准很简单显卡是 Ampere 架构及以上A100、A10、3090、4090用 bf16否则用 fp16。fp16 需要配fp16_opt_level和 loss scalingDeepSpeed 会自动处理但偶尔会遇到 NaN这时候换 bf16 或者加gradient_clipping能救回来。3.3 训练过程中的监控指标与中断恢复启动后先看显存占用。nvidia-smi里每张卡的显存应该接近但不爆满。如果第一张卡明显比其他的高说明模型加载没做均衡检查device_map是否设成了auto。loss 曲线看两个东西训练 loss 是否稳定下降验证 loss 是否在某个点后开始上升。后者是过拟合信号提前停或者减 epoch。正常情况训练 loss 从 2.0 左右降到 0.5 以下验证 loss 跟降但略高。DeepSpeed 支持断点续训。在TrainingArguments里设resume_from_checkpointTrue重新启动时会自动找最新的 checkpoint。checkpoint 目录里存了模型权重、优化器状态和调度器状态所以文件会比较大定期清理旧的能省不少磁盘。# 查看 checkpoint 占用 du -sh output/chatglm-lora/checkpoint-* # 只保留最近两个 ls -dt output/chatglm-lora/checkpoint-* | tail -n 3 | xargs rm -rf训练完成后 LoRA 权重是单独保存的需要和基础模型合并才能用于推理。PEFT 提供了merge_and_unload()合并后就是一个完整的 ChatGLM 模型。4. 多卡训练避坑那些让训练白跑的细节4.1 现象启动就 OOM但单卡明明能跑原因通常有三个。一是per_device_train_batch_size设太大多卡下每张卡都要放一份模型和激活值不会因为卡多就自动变小。二是 DeepSpeed 配置里train_batch_size和实际不匹配导致梯度累积计算错误。三是模型加载时没有用device_map所有卡都试图加载完整模型。解决先把per_device_train_batch_size降到 1确认能启动后再往上加。检查train_batch_size per_device_train_batch_size × GPU数 × gradient_accumulation_steps这个等式是否成立。加载模型时用AutoModel.from_pretrained(..., device_mapauto)或者交给 DeepSpeed 的deepspeed.initialize处理。4.2 现象loss 一直是 nan 或者不下降NaN 多半是 fp16 溢出。V100 上跑 fp16 遇到大梯度容易出问题换成 bf16 或者把gradient_clipping从 1.0 降到 0.5。如果 loss 不降先查学习率是不是太小LoRA 场景下 1e-5 基本学不动至少 1e-4 起步。再查数据里labels是不是全被设成了 -100那样等于没监督信号。还有一个隐蔽原因ChatGLM 的 tokenizer 在拼接 prompt 和 response 时没有加正确的分隔符导致模型分不清哪里是输入哪里是输出。打印几条 tokenize 后的结果人工看一眼比调参管用。4.3 现象多卡速度比单卡还慢卡间通信是瓶颈。如果机器上 GPU 之间走的是 PCIe 而不是 NVLinkZeRO Stage 3 的 all-gather 会非常慢。降到 Stage 2或者关掉overlap_comm试试。另外nproc_per_node设成了超过实际卡数进程会排队等 GPU看起来像卡死。用nvidia-smi topo -m看卡间互联类型。如果是PHB或SYS说明走 PCIe通信带宽有限这时候优先用 Stage 1 或 2别上 Stage 3。NCCL_DEBUGINFO环境变量能看到通信初始化过程排查卡间连接问题很有用。4.4 现象验证 loss 比训练 loss 低很多这听起来是好事但往往是数据泄露。检查训练集和验证集有没有重叠样本或者验证集的 prompt 在训练集里出现过。另一种可能是验证集太小几个样本的 loss 波动大看起来低而已。验证集至少 100 条以上才有参考意义。还有一种情况是evaluation_strategy设成了steps但eval_steps没设导致验证频率异常。统一设成epoch最省心小数据集一个 epoch 验证一次足够。4.5 现象合并后的模型推理效果和训练时不一样LoRA 权重合并时如果lora_alpha和r的比例没对上缩放系数会出错。PEFT 的merge_and_unload()会自动处理但如果你手动合并权重记得缩放因子是lora_alpha / r。另外推理时的max_length、temperature、top_p和训练时的生成配置不一致也会导致输出风格突变。合并后先用训练集里的几条样本测一下输出应该和训练时的验证输出接近。如果差很远检查 tokenizer 是不是同一个ChatGLM 不同版本的 tokenizer 有差异。5. 把 LoRA 权重用起来合并、导出与效果验证训练跑完只是第一步真正要验证的是微调后的模型在目标任务上有没有提升。我一般会做三件事合并权重、跑一批测试样本、和原始模型做对比。合并权重的代码很短但顺序不能错from peft import PeftModel from transformers import AutoModel, AutoTokenizer base AutoModel.from_pretrained(/path/to/chatglm-6b, trust_remote_codeTrue) tokenizer AutoTokenizer.from_pretrained(/path/to/chatglm-6b, trust_remote_codeTrue) # 加载 LoRA 权重并合并 model PeftModel.from_pretrained(base, output/chatglm-lora/checkpoint-xxx) merged model.merge_and_unload() merged.save_pretrained(output/chatglm-merged, safe_serializationTrue) tokenizer.save_pretrained(output/chatglm-merged)safe_serializationTrue保存成 safetensors 格式加载更快也更安全。合并后的模型大小和原始 ChatGLM 一致LoRA 的增量已经融进去了。效果验证别只看 loss。准备 20 到 50 条目标任务样本分别用原始模型和微调后模型生成人工对比。重点看三个维度格式遵循比如要求输出 JSON 是否真的输出 JSON、领域术语准确性、以及有没有灾难性遗忘通用对话能力是否明显下降。如果发现通用能力掉得厉害说明 LoRA 秩太大或者训练数据太单一。降r到 4或者在训练数据里混入 10% 到 20% 的通用对话样本。这个比例我试过既能保持领域效果又不会把通用能力冲垮。还有一个实用技巧用r8训一版再用r16训一版对比验证集 loss 和人工评测。多数垂直任务上r8和r16差距很小但r8的显存和过拟合风险更低。别一上来就冲r64那是给超大数据集准备的。最后说个我踩过的坑合并后的模型用chat()方法推理时history参数如果传了训练时没见过的格式输出会乱。统一用空列表[]初始化多轮对话时按标准格式追加。这个细节不值得单独开一章但每次都能坑到人。希望帮到你。本文还有配套的精品资源点击获取
返回列表