news 2026/9/19 17:17:25

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 重新加载技巧。

概述

LoRA(Low-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 transformers==4.37.2 pip install peft==0.4.0.dev0 pip install datasets==2.10.1 pip install accelerate==0.21.0

注:若使用仓库 models/ChatGLM/requirements.txt 中整理的全套依赖环境,其中对应版本为peft==0.4.0datasets==2.14.0accelerate==0.24.0等,实际运行时只要 peft / transformers / datasets / accelerate 版本兼容即可,无需与本文指定版本完全一致。

本节教程将微调数据集放置在仓库根目录的 dataset/huanhuan.jsonl(JSONL 格式,共 3729 条甄嬛台词对话)。脚本中则通过pd.read_json('../../dataset/huanhuan.json')读取同一份数据的 JSON 数组版本 dataset/huanhuan.json,两者内容一致、格式不同,均为甄嬛台词构造的指令数据。

指令集构建

LLM 的微调一般指指令微调(Instruction Tuning)过程。所谓指令微调,是指我们使用的微调数据形如:

{ "instrution":"回答以下用户问题,仅输出答案。", "input":"1+1等于几?", "output":"2" }

其中:

  • instruction:用户指令,告知模型其需要完成的任务;
  • input:用户输入,是完成用户指令所必须的输入内容;
  • output:模型应该给出的输出。

核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,应针对目标任务针对性构建任务指令集。例如,本项目以甄嬛对话风格建模为目标(参考 examples/Chat-嬛嬛/readme.md 中利用《甄嬛传》剧本台词打造个性化 AI 的思路),构造的指令形如:

{ "instruction": "", "input":"你是谁?", "output":"家父是大理寺少卿甄远道。" }

QA 与 Instruction 的区别与联系

QA 是一问一答的形式,通常是用户提问、模型给出回答;而 Instruction 源自 Prompt Engineering,将问题拆分为两个部分:Instruction 用于描述任务,Input 用于描述待处理的对象

例如,对于问题"请解释 VC 银翘片和双黄连口服液之间的区别":

  • 问答(QA)格式

    指令(Instruction): 输入(Input):VC银翘片和双黄连口服液之间的区别是什么?
  • 指令(Instruction)格式

    指令(Instruction):请解释下面两个药品之间的区别。 输入(Input):VC银翘片和双黄连口服液。

问答格式的训练数据通常用于训练模型回答基于知识的问题,而指令格式的训练数据更适用于训练模型执行具体任务。指令的形式可能使模型具有更好的泛化能力,因为它强调了任务的性质,而不仅仅是特定的输入。通常指令格式和问答格式可以相互转化。

数据格式化

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_tokens=True, truncation=True, max_length=MAX_LENGTH) response = tokenizer.encode(text=example["output"], add_special_tokens=False, truncation=True, max_length=MAX_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_idslabels两个键值对的字典,其中input_ids是输入文本的编码,labels是输出文本的编码。decode 之后应该是这样的形态:

[gMASK]sop <|system|> 现在你要扮演皇帝身边的女人--甄嬛 <|user|> 这个温太医啊,也是古怪,谁不知太医不得皇命不能为皇族以外的人请脉诊病,他倒好,十天半月便往咱们府里跑。<|assistant|> 你们俩话太多了,我该和温太医要一剂药,好好治治你们。

为什么会是这个形态?不同模型所对应的格式化输入都不一样,需要深度阅读模型的训练源码来确认——因为按照原本模型指令微调的形式进行 LoRA 微调效果最好,所以我们依然遵循 ChatGLM3 原始模型的输入格式。其格式化的核心要点是:

  • [gMASK]sop是 ChatGLM3 的特殊前缀 token(get_command("[gMASK]")对应 id 64790);
  • 使用<|system|><|user|><|assistant|>角色分隔符拼接 system / 用户 / 模型回复;
  • labels中指令部分的 token 用pad_token_id占位并在最后统一替换为-100,从而在损失计算时屏蔽指令与填充部分,只让模型学习output回复的生成。

Notebook models/ChatGLM/06-ChatGLM3-6B-Lora微调.ipynb 中还展示了两种等价的拆解思路以帮助理解:

  1. 调用 API 处理:使用tokenizer.build_chat_input(instruction, history=[], role="user")构造输入,tokenizer("\n" + output, add_special_tokens=False)编码回复;
  2. 手动拆解:用tokenizer.get_command("<|system|>")tokenizer.get_command("<|user|>")tokenizer.get_command("<|assistant|>")手动拼接角色标记与内容 token。

两种方式得到的 token 序列本质一致。ds.map(process_func, remove_columns=ds.column_names)会将原始 3729 条数据整体映射为仅含input_idslabels两个特征的Dataset,供后续 Trainer 使用。

加载 tokenizer 与半精度模型

模型以半精度形式加载,如果你的显卡比较新,也可以使用torch.bfloat16形式加载。对于 ChatGLM3 这类自定义实现的模型,必须指定trust_remote_code=True以信任并执行其远程代码(模型结构定义与 tokenizer 实现均来自模型仓库):

tokenizer = AutoTokenizer.from_pretrained('./model/chatglm3-6b', use_fast=False, trust_remote_code=True) # 模型以半精度形式加载,如果你的显卡比较新的话,可以用torch.bfloat16形式加载 model = AutoModelForCausalLM.from_pretrained('./model/chatglm3-6b', trust_remote_code=True, torch_dtype=torch.half, device_map="auto")

注意:ChatGLM3 的 tokenizer 属于慢速 tokenizer(use_fast=False),其padding_side='left'truncation_side='right'add_special_tokens=True时会自动带上[gMASK]sop等特殊前缀 token,这与上文格式化后看到的文本形态严格对应。

定义 LoraConfig

LoraConfig中可以设置很多参数,但主要参数并不多,逐个说明如下:

参数含义说明
task_type模型类型因果语言模型填TaskType.CAUSAL_LM
target_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_type=TaskType.CAUSAL_LM, target_modules=["query_key_value"], inference_mode=False, # 训练模式 r=8, # Lora 秩 lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理 lora_dropout=0.1 # Dropout 比例 )

针对 ChatGLM3,target_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 size
gradient_accumulation_steps梯度累加步数;显存较小可调小 batch_size、调大梯度累加
logging_steps每多少步输出一次 log
num_train_epochs训练轮数(epoch)
gradient_checkpointing梯度检查点,开启后模型必须执行model.enable_input_require_grads()
save_steps每多少步保存一次 checkpoint
learning_rate学习率

同时需要为 ChatGLM3 沿用其源仓库封装好的DataCollatorForSeq2Seq(支持以 -100 填充标签、对 batch 内样本做 padding 对齐):

# Data collator GLM源仓库从新封装了自己的data_collator,在这里进行沿用。 data_collator = DataCollatorForSeq2Seq( tokenizer, model=model, label_pad_token_id=-100, pad_to_multiple_of=None, padding=False ) args = TrainingArguments( output_dir="./output/ChatGLM", per_device_train_batch_size=4, gradient_accumulation_steps=2, logging_steps=10, num_train_epochs=3, gradient_checkpointing=True, save_steps=100, learning_rate=1e-4, )

脚本 models/ChatGLM/06-ChatGLM3-6B-Lora微调.py 中为适配单卡显存,采用了更保守的组合:per_device_train_batch_size=1gradient_accumulation_steps=8logging_steps=20num_train_epochs=1,实际训练时可结合显存大小灵活调整。

使用 Trainer 训练

把 model、训练参数、数据集全部传入Trainer,开始训练:

trainer = Trainer( model=model, args=args, train_dataset=tokenized_id, data_collator=data_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_tensors="pt").to(model.device) tokenizer.decode(model.generate(**ipt, max_length=128, do_sample=True)[0], skip_special_tokens=True)

注意推理时的 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_code=True, low_cpu_mem_usage=True) tokenizer = AutoTokenizer.from_pretrained("./model/chatglm3-6b", use_fast=False, trust_remote_code=True) 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_tensors="pt").to(model.device) tokenizer.decode(p_model.generate(**ipt, max_length=128, do_sample=True)[0], skip_special_tokens=True)

这种加载方式下,源模型权重与 LoRA 增量权重分开存放:PeftModel.from_pretrained(model, model_id="./output/ChatGLM/checkpoint-1000/")会将 checkpoint 目录下保存的adapter_model(LoRA 适配器)重新挂载到源模型上,随后即可正常推理。若需要部署为独立完整模型,还可以调用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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 17:13:29

Atlas 300V 24G昇腾推理卡实战:从环境配置到YOLOv8部署调优

最近被问到最多的问题&#xff0c;就是“Atlas 300V 24G是不是运算加速卡”和“这卡能不能部署YOLO”。问的人多了&#xff0c;我感觉很多人其实是在二手市场看到这张卡&#xff0c;发现显存有24G、价格又比同显存的GPU便宜一大截&#xff0c;于是动了“捡一张回来跑目标检测”…

作者头像 李华
网站建设 2026/9/19 17:13:00

HDFS到对象存储迁移:云原生时代存储底座重构之路

最近一年聊大数据架构&#xff0c;大家问得最多的一个问题就是&#xff1a;HDFS 到底还能不能留&#xff1f;乍一听有点反常识&#xff0c;毕竟过去十几年&#xff0c;大数据底座这个词几乎就是 HDFS 的代名词。但到了云原生阶段&#xff0c;事情确实起了变化。我手头好几个项目…

作者头像 李华
网站建设 2026/9/19 17:11:45

AzerothCore:WotLK 3.3.5a 私服实战上手指南

AzerothCore&#xff1a;WotLK 3.3.5a 私服实战上手指南 【免费下载链接】azerothcore-wotlk Complete Open Source and Modular solution for MMO 项目地址: https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk 想拥有自己的巫妖王年代艾泽拉斯&#xff1f;Aze…

作者头像 李华