
简介本资源面向希望掌握大模型高效微调技术的研究者与开发者提供基于ChatGLM3-6B模型的LoRA方法完整实战项目。LoRA通过低秩结构逼近参数矩阵更新在不显著增加参数量的前提下完成微调对计算资源要求较低适合资源受限场景下将大模型适配到特定任务与领域。资源包共12个文件约359KB包含4个Python脚本、5个JSON数据文件、1个YAML配置、1个Markdown说明及1个my文件覆盖数据准备、微调训练、模型导出与推理等环节目录结构清晰便于按模块查阅。已有781人学习下载说明该实战案例具备一定参考价值。读者可借助完整源码与流程教程从数据集构建到微调后模型性能评估逐步实践理解LoRA微调的关键实现细节并在此基础上迁移到自身任务中提升自然语言处理项目的落地效率。1. 从一张 24G 显卡说起ChatGLM3-6B 的 LoRA 微调到底能跑出什么手里只有一张 24G 显存的卡想拿 ChatGLM3-6B 做领域适配全量微调基本是奢望——6B 参数按 FP16 算光权重就 12G 起步加上优化器状态和梯度分分钟 OOM。LoRA 就是在这种约束下最务实的选择冻结原模型权重只在注意力层的低秩旁路里训练一小撮参数显存占用能压到全量微调的零头。这份资源包给的就是一条完整链路——从环境依赖、数据格式、训练脚本到推理验证源码和流程教程都在里面适合手上有业务语料、想快速验证「微调到底有没有用」的工程师。它不解决预训练也不解决多模态就是把 ChatGLM3-6B 这个基座用 LoRA 的方式往你的垂直场景上拽一把。下面按我实际拆包复现的顺序讲参数怎么设、坑在哪都落到具体命令上。2. 拆开资源包先看什么目录结构、依赖版本与 ChatGLM3 的 LoRA 挂载点拿到一个微调项目包我第一件事不是急着跑train.sh而是先把目录结构和依赖版本摸清楚。LoRA 微调翻车十有八九栽在版本不匹配或者挂载层选错上这两件事在动手前就能排掉。2.1 目录结构与关键文件定位这类 ChatGLM3-6B LoRA 项目解压后通常长这样不同打包方式略有出入但核心文件跑不掉ChatGLM3-LoRA/ ├── ptuning/ # 官方 P-Tuning v2 目录LoRA 脚本常放这里 │ ├── train.sh # 训练入口脚本 │ ├── arguments.py # 训练参数定义 │ ├── main.py # 训练主逻辑 │ └── trainer.py # 自定义 Trainer ├── finetune_demo/ # 部分包用这个目录名 │ ├── finetune.py │ └── inference.py ├── data/ │ └── dataset_example.json # 数据格式样例 ├── requirements.txt └── README.md先确认三件事训练入口脚本是哪个、数据样例长什么样、requirements.txt里锁的transformers和peft版本。ChatGLM3 对transformers版本比较挑4.36 到 4.40 之间相对稳太新或太旧都可能在加载 tokenizer 或trust_remote_code时炸掉。peft建议 0.6 以上LoRA 的target_modules匹配逻辑在旧版本里对 ChatGLM 的层名支持不全。2.2 依赖安装与版本对齐我一般用 conda 起一个干净环境避免和系统里的 torch 打架conda create -n chatglm3-lora python3.10 -y conda activate chatglm3-lora # 先装 torch按你的 CUDA 版本选这里以 cu121 为例 pip install torch2.1.2 torchvision0.16.2 --index-url https://download.pytorch.org/whl/cu121 # 再装项目依赖 pip install transformers4.40.2 peft0.10.0 accelerate0.29.3 \ datasets2.18.0 sentencepiece0.2.0 protobuf4.25.3 \ cpm_kernels1.0.11 tensorboard参数说明transformers4.40.2是实测和 ChatGLM3 兼容较好的版本peft0.10.0支持target_modules用列表精确指定accelerate负责混合精度和梯度累积的调度cpm_kernels是 ChatGLM 系列的自定义算子依赖漏装会在前向时报找不到 kernel。装完用python -c import torch; print(torch.cuda.is_available())确认显卡能被识别返回True再往下走。2.3 LoRA 到底挂在 ChatGLM3 的哪几层这是选型里最容易被忽略的一点。ChatGLM3-6B 的注意力层命名和 LLaMA 不一样它的query_key_value是一个合并的线性层输出维度是hidden_size * 3把 Q、K、V 拼在一起。所以 LoRA 的target_modules不能照抄 LLaMA 的[q_proj, v_proj]常见做法是挂在这几个层上# ChatGLM3 的 LoRA 目标层配置 target_modules [query_key_value, dense, dense_h_to_4h, dense_4h_to_h]逻辑说明query_key_value对应注意力投影dense是注意力输出投影dense_h_to_4h和dense_4h_to_h是 FFN 的上下投影。只挂query_key_value是最省参数的方案但表达力有限四个都挂可训练参数会多一些效果通常更稳。r秩一般取 8 或 16lora_alpha取2 * rlora_dropout取 0.05 到 0.1。这几个值不是玄学r越大容量越大但越容易过拟合小数据集alpha/r的比值控制旁路输出的缩放强度比值固定时换r不用重调学习率。提示如果训练脚本里target_modules写的是 LLaMA 的层名加载时不会报错但 LoRA 层会挂空训练 loss 几乎不动——这是最隐蔽的坑之一务必打印一下可训练参数数量确认。3. 数据准备与训练脚本从 JSON 格式到 train.sh 参数逐行拆解数据格式和训练参数是决定微调成败的两根柱子。格式错了训练直接报错参数错了 loss 曲线会给你脸色看。这一章把数据构造和train.sh的每个参数都摊开讲。3.1 构造符合 ChatGLM3 对话模板的 JSON 数据ChatGLM3 用的是自己的对话格式训练数据必须按它的prompt/response结构组织否则模型学到的对话边界是乱的。样例数据长这样[ { prompt: 请判断以下工单的紧急程度服务器无法登录影响全部用户。, response: 紧急程度高。理由影响范围覆盖全部用户属于核心服务不可用。 }, { prompt: 请判断以下工单的紧急程度某个报表导出格式错位。, response: 紧急程度低。理由不影响核心功能属于展示层问题。 } ]逻辑说明prompt是输入指令response是期望输出。ChatGLM3 的build_prompt会在训练时自动套上|system|、|user|、|assistant|这些特殊 token你不需要手动拼。数据量上LoRA 微调对样本数的要求比全量低垂直分类或抽取任务几百到几千条高质量样本就能看到明显变化但样本质量比数量重要——同一个 prompt 出现矛盾标注模型会学出模棱两可的输出。参数上要注意max_length和max_source_length。ChatGLM3 的上下文是 8K但训练时没必要拉满按你数据里prompt response的 95 分位长度设就行设太大浪费显存设太小会截断长样本。常见做法是先跑一遍统计import json with open(data/train.json, r, encodingutf-8) as f: data json.load(f) lengths [len(d[prompt]) len(d[response]) for d in data] lengths.sort() print(样本数:, len(lengths)) print(95分位长度:, lengths[int(len(lengths) * 0.95)]) print(最大长度:, lengths[-1])按 95 分位往上取整到 64 的倍数作为max_length的初值。这样既不浪费显存也不会把大部分样本截掉。3.2 train.sh 参数逐行拆解训练入口一般是个 shell 脚本核心参数如下# train.sh 关键参数 PRE_SEQ_LEN128 LR2e-4 NUM_GPUS1 MAX_SOURCE_LEN512 MAX_TARGET_LEN512 DEV_BATCH_SIZE4 GRAD_ACCUM8 NUM_EPOCHS3 SAVE_STEPS200 LORA_R16 LORA_ALPHA32 LORA_DROPOUT0.05 torchrun --standalone --nnodes1 --nproc_per_node$NUM_GPUS main.py \ --do_train \ --train_file data/train.json \ --validation_file data/dev.json \ --prompt_column prompt \ --response_column response \ --model_name_or_path THUDM/chatglm3-6b \ --output_dir output/chatglm3-lora \ --overwrite_output_dir \ --max_source_length $MAX_SOURCE_LEN \ --max_target_length $MAX_TARGET_LEN \ --per_device_train_batch_size $DEV_BATCH_SIZE \ --per_device_eval_batch_size $DEV_BATCH_SIZE \ --gradient_accumulation_steps $GRAD_ACCUM \ --learning_rate $LR \ --num_train_epochs $NUM_EPOCHS \ --logging_steps 10 \ --save_steps $SAVE_STEPS \ --learning_rate $LR \ --lora_r $LORA_R \ --lora_alpha $LORA_ALPHA \ --lora_dropout $LORA_DROPOUT \ --fp16参数说明DEV_BATCH_SIZE4配合GRAD_ACCUM8等效 batch size 是 3224G 卡上跑 6B 的 LoRA 这个组合比较稳LR2e-4是 LoRA 的常用学习率比全量微调高一个量级因为可训练参数少NUM_EPOCHS3对几千条数据够用再多容易过拟合SAVE_STEPS200是 checkpoint 间隔方便你中途挑效果最好的那个fp16开混合精度省显存如果卡支持 bf16换成--bf16数值更稳。PRE_SEQ_LEN是 P-Tuning 的遗留参数纯 LoRA 用不到但脚本里常留着不影响。3.3 启动训练与显存监控启动前先开一个窗口盯显存watch -n 2 nvidia-smi然后另开窗口跑训练。正常启动后你会看到 loss 从 2 点几逐步下降前几十步波动大是正常的。如果显存直接爆按这个顺序降先把DEV_BATCH_SIZE降到 2再把MAX_SOURCE_LEN和MAX_TARGET_LEN降到 384还不行就开gradient_checkpointing。如果 loss 一直不降先回去检查target_modules是不是挂空了再检查数据里prompt和response的字段名和脚本参数是否对得上。注意训练日志里会打印可训练参数占比LoRA 正常在 0.1% 到 1% 之间。如果看到接近 100%说明 LoRA 没生效模型在全量训练显存和过拟合风险都会飙升。4. 推理验证与效果评估合并权重还是动态加载怎么判断微调真的有用训练完拿到output/chatglm3-lora目录里面是 adapter 权重不是完整模型。怎么用它、怎么判断效果这一步比训练本身更容易被糊弄过去。4.1 两种加载方式动态挂载与权重合并第一种是动态加载基座模型 LoRA adapter 分开推理时挂上去from transformers import AutoModel, AutoTokenizer from peft import PeftModel base_model AutoModel.from_pretrained( THUDM/chatglm3-6b, trust_remote_codeTrue, device_mapauto ) tokenizer AutoTokenizer.from_pretrained( THUDM/chatglm3-6b, trust_remote_codeTrue ) # 挂载 LoRA adapter model PeftModel.from_pretrained(base_model, output/chatglm3-lora) model model.eval() response, history model.chat( tokenizer, 请判断以下工单的紧急程度数据库主从延迟超过 30 分钟。, history[] ) print(response)逻辑说明PeftModel.from_pretrained会把 adapter 权重挂到基座的对应层上推理时旁路参与计算。这种方式灵活一个基座可以挂多个 adapter 切换但每次推理多一层计算开销。第二种是合并权重把 LoRA 旁路合并进基座导出一个完整模型from peft import PeftModel model PeftModel.from_pretrained(base_model, output/chatglm3-lora) merged_model model.merge_and_unload() merged_model.save_pretrained(output/chatglm3-merged, safe_serializationTrue) tokenizer.save_pretrained(output/chatglm3-merged)参数说明merge_and_unload把 LoRA 的B A * scaling加到原权重上然后卸载旁路导出的是标准模型推理时没有额外开销适合部署。safe_serializationTrue导出safetensors格式比 pickle 安全加载也快。合并后的模型体积和原基座一样不会因为 LoRA 变大。4.2 效果评估别只看 loss要看任务指标loss 降了不代表任务效果好了。LoRA 微调最常见的翻车是 loss 很漂亮但模型在验证集上开始胡说。评估要分两层第一层是自动指标。分类任务看准确率和 F1抽取任务看字段级匹配率。写一个批量推理脚本把验证集跑一遍import json from tqdm import tqdm with open(data/dev.json, r, encodingutf-8) as f: dev_data json.load(f) correct 0 for item in tqdm(dev_data): response, _ model.chat(tokenizer, item[prompt], history[]) # 这里按你的任务定义匹配逻辑示例为完全匹配 if response.strip() item[response].strip(): correct 1 print(f完全匹配率: {correct / len(dev_data):.4f})第二层是人工抽检。自动指标只能告诉你「像不像」不能告诉你「对不对」。从验证集里随机抽 30 到 50 条逐条看输出重点看边界样本和训练集里没出现过的表达。我一般会准备一个「对抗集」——故意写一些和训练集措辞不同但语义相同的输入看模型是学到了任务逻辑还是只记住了训练样本的表面模式。4.3 基座对比微调前后的差异要能说清楚判断微调有没有用最直接的办法是拿同一个 prompt 分别问基座和微调后的模型对比输出。如果两者输出几乎一样说明 LoRA 没学到东西回去查target_modules和学习率如果微调后在你关心的任务上明显更贴合但在通用问题上变差说明过拟合了减少 epoch 或增加 dropout。这个对比要固定随机种子不然生成结果的随机性会干扰判断。提示ChatGLM3 的model.chat默认带采样对比时把do_sampleFalse打开输出才可复现。5. 避坑与排查LoRA 微调 ChatGLM3 最常见的五类翻车这一章是我自己踩过和帮别人排过的坑按「现象 → 原因 → 解决」写遇到问题可以对着查。5.1 训练 loss 不降或降得极慢现象跑了几百步loss 在 2.5 附近晃几乎不动。原因target_modules写成了 LLaMA 的层名LoRA 层挂空实际没训练任何参数或者学习率设成了全量微调的量级1e-5对 LoRA 来说太小。解决打印model.print_trainable_parameters()确认可训练参数占比在 0.1% 到 1% 之间学习率调到 1e-4 到 3e-4 之间。5.2 显存 OOMbatch size 已经降到 1现象DEV_BATCH_SIZE1还是爆显存。原因MAX_SOURCE_LEN和MAX_TARGET_LEN设太大或者没开梯度检查点中间激活值占满了。解决把两个长度降到 384 甚至 256加上--gradient_checkpointing用时间换显存确认fp16或bf16已开。5.3 推理时输出乱码或特殊 token 泄漏现象推理结果里出现|user|、|assistant|这些 token或者输出一段无意义重复。原因tokenizer 加载时没带trust_remote_codeTrue或者训练和推理用的 tokenizer 版本不一致。解决训练和推理统一用同一个model_name_or_path加载 tokenizer确认trust_remote_codeTrue检查build_prompt的逻辑在推理时是否和训练时一致。5.4 合并权重后模型效果变差现象动态加载 adapter 时输出正常merge_and_unload之后效果明显下降。原因合并时精度损失或者合并前模型没切到 eval 模式dropout 还在生效。解决合并前调model.eval()合并后用同一批 prompt 对比动态加载和合并后的输出确认一致再导出如果差异大检查peft版本旧版本合并逻辑有 bug。5.5 微调后通用能力断崖式下降现象目标任务变好了但问它别的常识问题开始胡言乱语。原因学习率太大、epoch 太多LoRA 旁路把基座带偏了典型的灾难性遗忘。解决降低学习率到 1e-4减少 epoch 到 1 到 2提高lora_dropout到 0.1在训练数据里混入 10% 到 20% 的通用指令数据让模型别忘本。6. 进阶技巧用验证集早停和 adapter 热插拔把微调成本压到最低训练不是跑完固定 epoch 就完事怎么在有限算力下拿到最好的 adapter有几个实操技巧。第一个是早停。别等NUM_EPOCHS跑完每SAVE_STEPS存一个 checkpoint用验证集批量评估取指标最高的那个。我一般会在训练脚本外挂一个评估循环每存一次 checkpoint 就跑一遍验证集记录指标。这样即使后面过拟合了你手里也有最好的那个版本。评估脚本的核心就是上面 4.2 那段批量推理把它包成一个函数按 checkpoint 路径循环调用即可。第二个是 adapter 热插拔。一个基座模型可以挂多个 LoRA adapter按业务场景切换。比如你有一个工单分类的 adapter 和一个工单摘要的 adapter不用加载两个 6B 模型只加载一个基座推理时切换 adapterfrom peft import PeftModel base_model AutoModel.from_pretrained(THUDM/chatglm3-6b, trust_remote_codeTrue, device_mapauto) # 加载第一个 adapter model PeftModel.from_pretrained(base_model, output/lora-cls, adapter_namecls) # 加载第二个 adapter model.load_adapter(output/lora-sum, adapter_namesum) # 切换使用 model.set_adapter(cls) resp_cls, _ model.chat(tokenizer, 判断工单紧急程度..., history[]) model.set_adapter(sum) resp_sum, _ model.chat(tokenizer, 总结以下工单..., history[])参数说明adapter_name是自定义标识load_adapter可以叠加多个set_adapter切换当前生效的那个。这种方式显存里只有一份基座权重多个 adapter 加起来通常几百 MB比加载多个完整模型省得多。适合企业私有化部署场景一个基座服务多个垂直任务。第三个技巧是数据配比。如果你的业务数据很少几百条单独训容易过拟合常见做法是混入通用指令数据比例控制在 5:1 到 10:1 之间让模型在学任务的同时保持通用能力。混入的数据不用太讲究一些公开的中文指令集抽一部分就行关键是别让业务数据被淹没。最后说个验证习惯每次训完 adapter我都会固定一组「回归 prompt」——包含任务样本、边界样本和几个通用问题跑一遍存成文本和上一版对比。这样能快速发现这次微调是进步了还是退步了比只看 loss 靠谱得多。从那以后我每次动 LoRA 参数前都强制先把这组回归 prompt 跑一遍留底省得改完忘了原来什么样。希望帮到你。本文还有配套的精品资源点击获取