ChatGLM3-6B LoRA 微调实战:基于 PEFT 构建甄嬛风格个性化对话模型

发布时间:2026/9/19 17:17:37
ChatGLM3-6B LoRA 微调实战:基于 PEFT 构建甄嬛风格个性化对话模型 ChatGLM3-6B LoRA 微调实战基于 PEFT 构建甄嬛风格个性化对话模型【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/datawhalechina/self-llm本文是《开源大模型食用指南》self-llm 项目中 ChatGLM3 系列的第六篇实战教程完整演示如何基于 transformers、peft 等框架对智谱 ChatGLM3-6B 对话模型进行 LoRA 高效微调以甄嬛台词数据集为训练语料构建一个能够模拟甄嬛对话风格的个性化大语言模型。读完本文你将掌握 ChatGLM3 特化的指令数据格式化方法、LoraConfig 与 TrainingArguments 的关键参数配置、Trainer 训练全流程以及微调后模型的推理与 PeftModel 重新加载技巧。概述LoRALow-Rank Adaptation是一种高效微调方法其核心思想是在冻结预训练模型原始参数的前提下通过在权重矩阵旁注入低秩分解矩阵A、B来实现参数的高效更新训练完成后只需保存占比极小的增量参数即可实现对模型的领域化改造显著降低微调的显存与存储开销。本节所讲述的代码脚本位于 models/ChatGLM/06-ChatGLM3-6B-Lora微调.py可直接运行该脚本来执行微调过程。需要特别注意的是本文代码未使用分布式框架微调 ChatGLM3-6B-Chat 模型至少需要21G 及以上显存运行前必须修改脚本文件中的模型路径默认/root/autodl-tmp/ZhipuAI/chatglm3-6b和数据集路径默认读取仓库根目录dataset/huanhuan.json同目录下还提供了逐 Cell 讲解的 models/ChatGLM/06-ChatGLM3-6B-Lora微调.ipynb 教学 Notebook便于逐步学习调试。环境配置在完成基本环境配置含 CUDA、PyTorch与 ChatGLM3-6B 本地模型部署的前提下还需要安装以下第三方库pip install transformers4.37.2 pip install peft0.4.0.dev0 pip install datasets2.10.1 pip install accelerate0.21.0注若使用仓库 models/ChatGLM/requirements.txt 中整理的全套依赖环境其中对应版本为peft0.4.0、datasets2.14.0、accelerate0.24.0等实际运行时只要 peft / transformers / datasets / accelerate 版本兼容即可无需与本文指定版本完全一致。本节教程将微调数据集放置在仓库根目录的 dataset/huanhuan.jsonlJSONL 格式共 3729 条甄嬛台词对话。脚本中则通过pd.read_json(../../dataset/huanhuan.json)读取同一份数据的 JSON 数组版本 dataset/huanhuan.json两者内容一致、格式不同均为甄嬛台词构造的指令数据。指令集构建LLM 的微调一般指指令微调Instruction Tuning过程。所谓指令微调是指我们使用的微调数据形如{ instrution:回答以下用户问题仅输出答案。, input:11等于几?, output:2 }其中instruction用户指令告知模型其需要完成的任务input用户输入是完成用户指令所必须的输入内容output模型应该给出的输出。核心训练目标是让模型具有理解并遵循用户指令的能力。因此在指令集构建时应针对目标任务针对性构建任务指令集。例如本项目以甄嬛对话风格建模为目标参考 examples/Chat-嬛嬛/readme.md 中利用《甄嬛传》剧本台词打造个性化 AI 的思路构造的指令形如{ instruction: , input:你是谁, output:家父是大理寺少卿甄远道。 }QA 与 Instruction 的区别与联系QA 是一问一答的形式通常是用户提问、模型给出回答而 Instruction 源自 Prompt Engineering将问题拆分为两个部分Instruction 用于描述任务Input 用于描述待处理的对象。例如对于问题请解释 VC 银翘片和双黄连口服液之间的区别问答QA格式指令Instruction 输入InputVC银翘片和双黄连口服液之间的区别是什么指令Instruction格式指令Instruction请解释下面两个药品之间的区别。 输入InputVC银翘片和双黄连口服液。问答格式的训练数据通常用于训练模型回答基于知识的问题而指令格式的训练数据更适用于训练模型执行具体任务。指令的形式可能使模型具有更好的泛化能力因为它强调了任务的性质而不仅仅是特定的输入。通常指令格式和问答格式可以相互转化。数据格式化LoRA 训练的数据需要经过格式化、编码之后再输入给模型。熟悉 PyTorch 训练流程的同学知道我们一般需要将输入文本编码为input_ids将输出文本编码为labels编码之后的结果都是多维向量。首先定义预处理函数对每一个样本编码其输入、输出文本并返回编码后的字典def process_func(example): MAX_LENGTH 512 input_ids, labels [], [] instruction tokenizer.encode(text\n.join([|system|, 现在你要扮演皇帝身边的女人--甄嬛, |user|, example[instruction] example[input] |assistant|]).strip() \n, add_special_tokensTrue, truncationTrue, max_lengthMAX_LENGTH) response tokenizer.encode(textexample[output], add_special_tokensFalse, truncationTrue, max_lengthMAX_LENGTH) input_ids instruction response [tokenizer.eos_token_id] labels [tokenizer.pad_token_id] * len(instruction) response [tokenizer.eos_token_id] pad_len MAX_LENGTH - len(input_ids) input_ids [tokenizer.pad_token_id] * pad_len labels [tokenizer.pad_token_id] * pad_len labels [(l if l ! tokenizer.pad_token_id else -100) for l in labels] return { input_ids: input_ids, labels: labels }经过格式化后送入模型的每一条数据都是一个包含input_ids、labels两个键值对的字典其中input_ids是输入文本的编码labels是输出文本的编码。decode 之后应该是这样的形态[gMASK]sop |system| 现在你要扮演皇帝身边的女人--甄嬛 |user| 这个温太医啊也是古怪谁不知太医不得皇命不能为皇族以外的人请脉诊病他倒好十天半月便往咱们府里跑。|assistant| 你们俩话太多了我该和温太医要一剂药好好治治你们。为什么会是这个形态不同模型所对应的格式化输入都不一样需要深度阅读模型的训练源码来确认——因为按照原本模型指令微调的形式进行 LoRA 微调效果最好所以我们依然遵循 ChatGLM3 原始模型的输入格式。其格式化的核心要点是[gMASK]、sop是 ChatGLM3 的特殊前缀 tokenget_command([gMASK])对应 id 64790使用|system|、|user|、|assistant|角色分隔符拼接 system / 用户 / 模型回复labels中指令部分的 token 用pad_token_id占位并在最后统一替换为-100从而在损失计算时屏蔽指令与填充部分只让模型学习output回复的生成。Notebook models/ChatGLM/06-ChatGLM3-6B-Lora微调.ipynb 中还展示了两种等价的拆解思路以帮助理解调用 API 处理使用tokenizer.build_chat_input(instruction, history[], roleuser)构造输入tokenizer(\n output, add_special_tokensFalse)编码回复手动拆解用tokenizer.get_command(|system|)、tokenizer.get_command(|user|)、tokenizer.get_command(|assistant|)手动拼接角色标记与内容 token。两种方式得到的 token 序列本质一致。ds.map(process_func, remove_columnsds.column_names)会将原始 3729 条数据整体映射为仅含input_ids、labels两个特征的Dataset供后续 Trainer 使用。加载 tokenizer 与半精度模型模型以半精度形式加载如果你的显卡比较新也可以使用torch.bfloat16形式加载。对于 ChatGLM3 这类自定义实现的模型必须指定trust_remote_codeTrue以信任并执行其远程代码模型结构定义与 tokenizer 实现均来自模型仓库tokenizer AutoTokenizer.from_pretrained(./model/chatglm3-6b, use_fastFalse, trust_remote_codeTrue) # 模型以半精度形式加载如果你的显卡比较新的话可以用torch.bfloat16形式加载 model AutoModelForCausalLM.from_pretrained(./model/chatglm3-6b, trust_remote_codeTrue, torch_dtypetorch.half, device_mapauto)注意ChatGLM3 的 tokenizer 属于慢速 tokenizeruse_fastFalse其padding_sideleft、truncation_siderightadd_special_tokensTrue时会自动带上[gMASK]、sop等特殊前缀 token这与上文格式化后看到的文本形态严格对应。定义 LoraConfigLoraConfig中可以设置很多参数但主要参数并不多逐个说明如下参数含义说明task_type模型类型因果语言模型填TaskType.CAUSAL_LMtarget_modules需要训练的模型层名字主要就是 attention 部分的层不同模型层的名字不同可传数组、字符串或正则表达式rLoRA 的秩低秩矩阵的秩越小参数量越少、表达能力越受限lora_alphaLoRA alpha与缩放比例相关具体作用参见 LoRA 原理lora_dropoutDropout 比例对注入的低秩分支施加的随机失活比例用于缓解过拟合modules_to_save额外完整训练模块除拆成 LoRA 的模块外其他需要完整训练不拆分的模块LoRA 的缩放是什么当然不是r秩这个缩放是lora_alpha / r。在本节的配置中缩放为32 / 8 4倍。这个缩放的本质并没有改变 LoRA 的参数量大小本质在于将里面的参数数值做广播乘法进行线性的缩放。config LoraConfig( task_typeTaskType.CAUSAL_LM, target_modules[query_key_value], inference_modeFalse, # 训练模式 r8, # Lora 秩 lora_alpha32, # Lora alaph具体作用参见 Lora 原理 lora_dropout0.1 # Dropout 比例 )针对 ChatGLM3target_modules指向其注意力模块中的query_key_value层ChatGLM 系列将 Q、K、V 投影合并为一个线性层。Notebook 中提示target_modules也可以传入正则表达式例如仅针对第 1 层 attention 的.*\\.1.*query_key_value。通过get_peft_model(model, config)完成 LoRA 注入后Notebook 中的实测输出为trainable params: 1,949,696 || all params: 6,245,533,696 || trainable%: 0.031217444255383614也就是说6.2B 参数的 ChatGLM3-6B 在 LoRA 微调中仅需训练约 195 万参数约占 0.031%这正是 LoRA 能够大幅降低显存与算力门槛的直接证据。自定义 TrainingArguments 参数TrainingArguments的源码对每个参数都有说明这里说明几个常用的参数含义output_dir模型的输出路径checkpoint 保存目录per_device_train_batch_size单卡 batch sizegradient_accumulation_steps梯度累加步数显存较小可调小 batch_size、调大梯度累加logging_steps每多少步输出一次 lognum_train_epochs训练轮数epochgradient_checkpointing梯度检查点开启后模型必须执行model.enable_input_require_grads()save_steps每多少步保存一次 checkpointlearning_rate学习率同时需要为 ChatGLM3 沿用其源仓库封装好的DataCollatorForSeq2Seq支持以 -100 填充标签、对 batch 内样本做 padding 对齐# Data collator GLM源仓库从新封装了自己的data_collator,在这里进行沿用。 data_collator DataCollatorForSeq2Seq( tokenizer, modelmodel, label_pad_token_id-100, pad_to_multiple_ofNone, paddingFalse ) args TrainingArguments( output_dir./output/ChatGLM, per_device_train_batch_size4, gradient_accumulation_steps2, logging_steps10, num_train_epochs3, gradient_checkpointingTrue, save_steps100, learning_rate1e-4, )脚本 models/ChatGLM/06-ChatGLM3-6B-Lora微调.py 中为适配单卡显存采用了更保守的组合per_device_train_batch_size1、gradient_accumulation_steps8、logging_steps20、num_train_epochs1实际训练时可结合显存大小灵活调整。使用 Trainer 训练把 model、训练参数、数据集全部传入Trainer开始训练trainer Trainer( modelmodel, argsargs, train_datasettokenized_id, data_collatordata_collator, ) trainer.train()训练过程中模型权重本身被冻结仅更新注入的 LoRA 低秩矩阵query_key_value层内的 A、B 矩阵checkpoint 默认保存在output_dir下每隔save_steps步生成一个 checkpoint 子目录如checkpoint-1000。模型推理训练完成后可以用经典方式直接基于当前已注入 LoRA 的模型进行推理model.eval() model model.cuda() ipt tokenizer(|system|\n现在你要扮演皇帝身边的女人--甄嬛\n|user|\n {}\n{}.format(你是谁, ).strip() |assistant|\n, return_tensorspt).to(model.device) tokenizer.decode(model.generate(**ipt, max_length128, do_sampleTrue)[0], skip_special_tokensTrue)注意推理时的 prompt 格式必须与训练时的数据格式化保持一致|system|系统角色提示 →|user|用户提问 →|assistant|触发模型回复否则模型可能无法正确遵循指令。Notebook 中的实测输出为[gMASK]sop |system|\n现在你要扮演皇帝身边的女人--甄嬛\n|user|\n 你是谁|assistant|\n 我是甄嬛家父是大理寺少卿甄远道。可以看到微调后的模型已经学会了以甄嬛的口吻进行回答成功达到了个性化对话建模的目标。重新加载微调模型通过 PEFT 微调的模型都可以使用下面的方法进行重新加载并推理加载源 model 与 tokenizer使用PeftModel将源 model 与 PEFT 微调后的 LoRA 参数合并。from peft import PeftModel model AutoModelForCausalLM.from_pretrained(./model/chatglm3-6b, trust_remote_codeTrue, low_cpu_mem_usageTrue) tokenizer AutoTokenizer.from_pretrained(./model/chatglm3-6b, use_fastFalse, trust_remote_codeTrue) p_model PeftModel.from_pretrained(model, model_id./output/ChatGLM/checkpoint-1000/) # 将训练所得的LoRA权重加载起来 ipt tokenizer(|system|\n现在你要扮演皇帝身边的女人--甄嬛\n|user|\n {}\n{}.format(你是谁, ).strip() |assistant|\n, return_tensorspt).to(model.device) tokenizer.decode(p_model.generate(**ipt, max_length128, do_sampleTrue)[0], skip_special_tokensTrue)这种加载方式下源模型权重与 LoRA 增量权重分开存放PeftModel.from_pretrained(model, model_id./output/ChatGLM/checkpoint-1000/)会将 checkpoint 目录下保存的adapter_modelLoRA 适配器重新挂载到源模型上随后即可正常推理。若需要部署为独立完整模型还可以调用p_model.merge_and_unload()将 LoRA 权重合并回主模型再保存此方式在推理部署时可避免额外的适配器加载开销。小结至此我们完整走通了指令数据构造 → ChatGLM3 特化格式化 → LoRA 参数注入 → Trainer 微调 → 推理验证 → PeftModel 重新加载的 ChatGLM3-6B 高效微调全流程。整个过程仅需训练约 0.031% 的参数约 195 万却能让 6B 级大模型获得甄嬛式对话风格充分体现了 LoRA 在低成本、个性化大模型定制上的实战价值。读者只需替换模型路径、数据集路径与角色设定文本现在你要扮演皇帝身边的女人--甄嬛即可将该流程复用到任意角色扮演、风格迁移或领域指令微调场景中。【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/datawhalechina/self-llm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考