news 2026/9/20 5:38:50

Qwen1.5-7B-Chat 高效微调实战:基于 transformers 与 peft 的 Lora 指令微调全流程指南(self-llm 项目)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qwen1.5-7B-Chat 高效微调实战:基于 transformers 与 peft 的 Lora 指令微调全流程指南(self-llm 项目)

Qwen1.5-7B-Chat 高效微调实战:基于 transformers 与 peft 的 Lora 指令微调全流程指南(self-llm 项目)

【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/datawhalechina/self-llm

本文是《开源大模型食用指南》(Datawhale/self-llm)中 Qwen1.5 系列教程的核心篇章,完整讲解如何在 Linux + PyTorch 环境下,基于 transformers、peft、datasets 等框架对 Qwen1.5-7B-Chat 进行 Lora 指令微调,并将其训练成具有甄嬛对话风格的人设化聊天模型。读完本文,你将掌握从环境搭建、指令数据集构建、数据格式化、LoraConfig 与 TrainingArguments 参数设计,到 Trainer 训练与 Lora 权重加载推理的端到端实战能力。

概览:Lora 微调的关键要素

本节教程要解决的工程问题十分明确:以最小的可训练参数量,让 Qwen1.5-7B-Chat 具备理解并遵循特定指令(人设对话)的能力。Lora(Low-Rank Adaptation)通过冻结基座模型、仅训练注入的低秩矩阵来实现高效微调。本文涉及的完整链路如下:

  1. 环境配置与依赖安装(transformers/peft/datasets/modelscope);
  2. 指令集构建:设计instruction/input/output三元组数据;
  3. 数据格式化:按 Qwen1.5 的 Chat Template 将文本编码为input_ids/attention_mask/labels
  4. 加载半精度模型并定义LoraConfig
  5. 配置TrainingArguments并使用Trainer训练;
  6. 加载训练好的 Lora 权重进行推理。

配套的可运行示例代码位于仓库 models/Qwen1.5/Qwen1.5-7B-Chat Lora.ipynb,建议读者跟随本文阅读时同步打开 notebook 逐 cell 执行验证。

环境配置

本文基础环境如下:

---------------- ubuntu 22.04 python 3.12 cuda 12.1 pytorch 2.3.0 ----------------

本文默认学习者已安装好以上 PyTorch(cuda) 环境,如未安装请自行安装。

接下来开始环境配置、模型下载和运行演示。首先对pip换源加速下载并安装依赖包:

pip install --upgrade pip # 更换 pypi 源加速库的安装 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple pip install modelscope==1.16.1 pip install transformers==4.43.2 pip install accelerate==0.32.1 pip install peft==0.11.1 pip install datasets==2.20.0

各依赖的职责如下:

  • modelscope:国内模型下载工具,用于拉取 Qwen1.5-7B-Chat 权重;
  • transformers:模型加载、Tokenizer 与训练器Trainer的底层框架;
  • acceleratedevice_map="auto"多卡/显存自动分配与Trainer训练的底层加速库;
  • peft:Lora 适配器(LoraConfigget_peft_modelPeftModel)所在库;
  • datasets:数据集加载与map批量预处理。

考虑到部分同学配置环境可能会遇到一些问题,本项目在 AutoDL 平台准备了 Qwen1.5 的环境镜像,该镜像适用于本仓库除 Qwen-GPTQ 和 vllm 外的所有部署环境,可直接创建 AutoDL 示例使用。

在本节教程里,我们将微调数据集放置在根目录 dataset/huanhuan.json。

模型下载

微调前需要先获取 Qwen1.5-7B-Chat 的原始权重。仓库中 01-Qwen1.5-7B-Chat FastApi 部署调用.md 给出了使用modelscopesnapshot_download函数下载模型的完整方式:第一个参数为模型名称,参数cache_dir为自定义的模型下载路径,参数revision为模型仓库分支版本,master代表主分支,也是一般模型上传的默认分支:

# model_download.py from modelscope import snapshot_download model_dir = snapshot_download('qwen/Qwen1.5-7B-Chat', cache_dir='/root/autodl-tmp', revision='master')

注意:记得修改cache_dir为你的模型下载路径。模型权重文件比较大,需要耐心等待直到下载完成。下载完成后,在后续代码中可将模型路径(如./qwen/Qwen1.5-7B-Chat/)替换为实际的本地目录。

指令集构建

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

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

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

即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对目标任务,针对性构建任务指令集。例如,在本节我们使用合作开源的 Chat-甄嬛(huanhuan-chat)项目作为示例,目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,因此构造的指令形如:

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

本项目实际使用的全部指令数据集位于仓库 dataset/huanhuan.json,共3729 条对话样本,字段结构即为上述三元组。数据预览(取自 notebook 中的ds[:3]输出):

{ "instruction": ["小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——", "这个温太医啊,也是古怪,谁不知太医不得皇命不能为皇族以外的人请脉诊病,他倒好,十天半月便往咱们府里跑。", "嬛妹妹,刚刚我去府上请脉,听甄伯母说你来这里进香了。"], "input": ["", "", ""], "output": ["嘘——都说许愿说破是不灵的。", "你们俩话太多了,我该和温太医要一剂药,好好治治你们。", "出来走走,也是散心。"] }

数据加载方式同样来自 notebook:

from datasets import Dataset import pandas as pd df = pd.read_json('./huanhuan.json') ds = Dataset.from_pandas(df)

数据格式化

Lora训练的数据是需要经过格式化、编码之后再输入给模型进行训练的。熟悉Pytorch模型训练流程的同学会知道,我们一般需要将输入文本编码为 input_ids,将输出文本编码为labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典:

def process_func(example): MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性 input_ids, attention_mask, labels = [], [], [] instruction = tokenizer(f"<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens response = tokenizer(f"{example['output']}", add_special_tokens=False) input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id] attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 因为eos token咱们也是要关注的所以 补充为1 labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id] if len(input_ids) > MAX_LENGTH: # 做一个截断 input_ids = input_ids[:MAX_LENGTH] attention_mask = attention_mask[:MAX_LENGTH] labels = labels[:MAX_LENGTH] return { "input_ids": input_ids, "attention_mask": attention_mask, "labels": labels }

这段代码是理解 Lora 微调数据流的关键,逐行解读如下:

  • MAX_LENGTH = 384:Qwen 分词器会将一个中文字切分为多个 token,需要放开最大长度保证数据完整性;超长样本直接截断到 384。
  • instruction编码:使用add_special_tokens=False手动拼接 Qwen1.5 的 Chat Template,避免 tokenizer 自动在开头追加 special token。
  • input_ids拼接指令部分 + 回答部分 + pad_token_id,其中末尾的pad_token_id起到句子结束标记(EOS)的作用。
  • attention_mask:末尾补充1,因为 EOS token 也需要被模型关注。
  • labels:指令部分的 label 全部置为-100(表示不参与 loss 计算),只有回答部分(response["input_ids"])参与损失,这正是"只学习回答、不学习提问"的监督微调核心。

随后通过datasetsmap方法批量处理全部样本(notebook 中该步骤实际处理了 3729 条样本):

tokenized_id = ds.map(process_func, remove_columns=ds.column_names)

处理完成后可用tokenizer.decode验证编码正确性。notebook 中解码第一条样本得到的完整训练文本为:

<|im_start|>system 现在你要扮演皇帝身边的女人--甄嬛<|im_end|> <|im_start|>user 小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——<|im_end|> <|im_start|>assistant 嘘——都说许愿说破是不灵的。<|endoftext|>

Qwen1.5采用的Prompt Template格式如下:

<|im_start|>system You are a helpful assistant.<|im_end|> <|im_start|>user 你是谁?<|im_end|> <|im_start|>assistant 我是一个有用的助手。<|im_end|>

从 notebook 加载的 tokenizer 信息可以看到,Qwen1.5-7B-Chat 使用的是Qwen2Tokenizervocab_size=151643model_max_length=32768use_fast=False),其特殊 token 为:<|endoftext|>(同时作为 eos_token 与 pad_token)、<|im_start|><|im_end|>。这解释了为什么在推理时可以使用tokenizer.apply_chat_template自动生成上述模板文本。

加载tokenizer和半精度模型

模型以半精度形式加载,如果你的显卡比较新的话,可以用torch.bfloat16形式加载。对于自定义的模型一定要指定trust_remote_code参数为True

tokenizer = AutoTokenizer.from_pretrained('./qwen/Qwen1.5-7B-Chat/', use_fast=False, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained('./qwen/Qwen1.5-7B-Chat/', device_map="auto",torch_dtype=torch.bfloat16)
  • use_fast=False:使用慢速(经典)tokenizer 实现,保证与仓库保存的 tokenizer 行为一致;
  • device_map="auto":由accelerate自动将模型各层分配到可用 GPU/CPU 上,显存不足时自动降级到 CPU;
  • torch_dtype=torch.bfloat16:以半精度 bfloat16 加载,显存占用约为 fp32 的一半,同时具备更大的指数范围,更适合 7B 级别模型在单卡上的微调。

notebook 中加载完成的模型结构(model.print输出)清晰展示了 Qwen1.5-7B-Chat 基于 Qwen2 架构的实现:Qwen2ForCausalLM包含 32 层Qwen2DecoderLayer,每层由self_attn(q/k/v_proj 均为 4096 维,o_proj 无 bias)与mlp(gate/up_proj 升维到 11008,down_proj 降回 4096,激活函数 SiLU)构成,lm_head映射到 151936 词表。这一结构也直接决定了下方LoraConfigtarget_modules的选择范围。

定义LoraConfig

LoraConfig这个类中可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看 peft 源码。

  • task_type:模型类型
  • target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
  • rlora的秩,具体可以看Lora原理
  • lora_alphaLora alaph,具体作用参见Lora原理

Lora的缩放是啥嘞?当然不是r(秩),这个缩放就是lora_alpha/r,在这个LoraConfig中缩放就是 4 倍。

config = LoraConfig( task_type=TaskType.CAUSAL_LM, target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], inference_mode=False, # 训练模式 r=8, # Lora 秩 lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理 lora_dropout=0.1# Dropout 比例 )

参数要点结合仓库 notebook 的实际运行结果展开:

  • target_modules:这里同时覆盖了注意力层的q/k/v/o_proj与 MLP 层的gate/up/down_proj全部 7 个线性层,与前面模型结构中观察到的Qwen2DecoderLayer内部层名一一对应。更激进的微调范围通常能带来更好的效果,但也会增加可训练参数量。
  • 缩放系数lora_alpha/r = 32/8 = 4:即注入的低秩分支输出会乘上 4 的缩放因子。该值越大,Lora 分支对最终输出的影响越强,实践中常与r按 2~4 倍比例搭配。
  • r=8的含义:每个被适配的线性层只训练一个秩为 8 的低秩矩阵对(A、B),而不是全量权重。

将配置应用到模型(notebook 中使用了get_peft_model),随后打印可训练参数统计:

from peft import LoraConfig, TaskType, get_peft_model model = get_peft_model(model, config) model.print_trainable_parameters()

notebook 的真实运行结果为:

trainable params: 19,988,480 || all params: 7,741,313,024 || trainable%: 0.2582052933143348

可以看到:在 77.4 亿参数的 Qwen1.5-7B-Chat 上,Lora 仅训练约2000 万参数,可训练比例仅0.26%——这正是 Lora"高效微调"的直接体现:显存与训练时间开销大幅下降,同时保留基座模型的通用能力。

自定义 TrainingArguments 参数

TrainingArguments这个类的源码也介绍了每个参数的具体作用,当然大家可以自行探索,这里就简单说几个常用的。

  • output_dir:模型的输出路径
  • 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(),这个原理大家可以自行探索,这里就不细说了。
args = TrainingArguments( output_dir="./output/Qwen1.5-7B-Chat", per_device_train_batch_size=4, gradient_accumulation_steps=4, logging_steps=10, num_train_epochs=3, save_steps=100, learning_rate=1e-4, save_on_each_node=True, gradient_checkpointing=True )

参数组合的实际含义与显存影响:

  • 有效 batch sizeper_device_train_batch_size(4) × gradient_accumulation_steps(4) = 16。如果显存较小,可以调低 batch_size 并相应增大梯度累加步数,保持等效 batch 不变。
  • gradient_checkpointing=True:以重计算换取显存,训练时需配套执行model.enable_input_require_grads()(notebook 中在加载模型后显式调用了该方法)。同时,开启后use_cache会被自动置为False(notebook 训练日志中有对应提示),即推理缓存与梯度检查点不兼容。
  • save_steps=100:每 100 步保存一次 checkpoint 到output_dir,便于中断恢复与选取最优 checkpoint。
  • learning_rate=1e-4:Lora 微调常用学习率区间为 1e-5 ~ 2e-4,1e-4是较为稳妥的默认选择;全参微调则通常需要更小的学习率。
  • save_on_each_node=True:多节点训练时每个节点均保存 checkpoint。

使用 Trainer 训练

trainer = Trainer( model=model, args=args, train_dataset=tokenized_id, data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True), ) trainer.train()

关键点说明:

  • train_dataset传入的是经过process_func编码后的tokenized_id(仅含input_ids/attention_mask/labels三列,原列已被remove_columns移除);
  • DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True)负责在 batch 内对不定长序列做 padding 对齐,并正确处理labels侧的 pad token(默认用-100填充,避免 padding 位置参与 loss 计算)。

notebook 中实际训练了 3 个 epoch,总计约 699 步,其中记录了真实的收敛过程:前 10 步训练损失约 4.15,至 130 步时已降至约 2.94,呈现平稳下降趋势,说明模型正在逐步习得"甄嬛风格回复"这一目标分布。不同显存条件下训练时长会有差异,可依据实际资源调整 batch、梯度累加与 epoch 数。

加载 lora 权重推理

训练好了之后可以使用如下方式加载lora权重进行推理:

from transformers import AutoModelForCausalLM, AutoTokenizer import torch from peft import PeftModel model_path = './qwen/Qwen1.5-7B-Chat/' lora_path = 'lora_path' # 加载tokenizer tokenizer = AutoTokenizer.from_pretrained(model_path) # 加载模型 model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto",torch_dtype=torch.bfloat16) # 加载lora权重 model = PeftModel.from_pretrained(model, model_id=lora_path, config=config) prompt = "你是谁?" messages = [ {"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"}, {"role": "user", "content": prompt} ] text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) model_inputs = tokenizer([text], return_tensors="pt").to('cuda') generated_ids = model.generate( model_inputs.input_ids, max_new_tokens=512 ) generated_ids = [ output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) ] response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0] print(response)

推理链路要点:

  • PeftModel.from_pretrained(model, model_id=lora_path):将训练好的 Lora 适配器权重挂载到原始基座模型上。lora_path应替换为你训练时的 checkpoint 目录(例如./output/Qwen1.5-7B-Chat/checkpoint-xxx/)。这一加载方式在本仓库的多个 Lora 微调教程中保持一致,例如 CharacterGLM Lora 微调 与 ChatGLM3 Lora 微调 均采用同样的PeftModel.from_pretrained加载模式。
  • tokenizer.apply_chat_template:与训练时的数据格式化逻辑对应,按 Qwen1.5 的 Chat Template 自动拼装system + user消息并追加assistant起始标记(add_generation_prompt=True),无需手工拼接模板字符串。
  • skip_special_tokens=True:解码时剔除<|im_start|><|im_end|><|endoftext|>等特殊 token,只保留干净的回答文本。

推理时 model 与 tokenizer 加载完成后即挂载 Lora 权重;若希望永久合并 Lora 与基座权重,可使用 peft 的save_pretrained/合并功能导出完整模型(本仓库 Atom-7B-Chat Lora 微调 中给出了model.save_pretrained(training_args.output_dir)的保存参考)。

延伸学习

  • 本文配套的完整逐 cell 可执行版本见 Qwen1.5-7B-Chat Lora.ipynb,其中包含每一步的真实输出(数据集预览、模型结构、可训练参数统计、训练损失曲线等),非常适合对照复现。
  • 想在微调过程中加入实验跟踪与指标可视化,可参考同目录的 08-Qwen1.5-7B-chat LoRA微调接入实验管理.md,在本文训练流程基础上无缝接入 SwanLab。
  • 训练完成后,可将微调模型接入 FastApi 服务、WebDemo 或 LangChain 知识库,相关教程见 Qwen1.5 系列文档 中列出的 01/02/03 号部署文档。
  • Lora 的底层原理(低秩分解、缩放系数、与全参微调的关系)可进一步阅读 peft 源码或查阅《深入浅出 Lora》等公开技术博客深入理解。

【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/datawhalechina/self-llm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

OpCore-Simplify实战:从硬件报告到一次生成 OpenCore EFI

OpCore-Simplify实战&#xff1a;从硬件报告到一次生成 OpenCore EFI 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 重启第三次&#xff0c;屏幕还卡…

作者头像 李华
网站建设 2026/9/20 5:37:43

LibreChat + MCP:构建可调试的Agent工程化落地平台

1. LibreChat 不是另一个 ChatGPT 前端&#xff0c;而是 Agent 架构的落地试验场LibreChat 这个名字刚出现时&#xff0c;我第一反应是&#xff1a;“又一个开源 ChatUI&#xff1f;”——毕竟市面上从 Chatbox、OpenWebUI 到 Ollama WebUI&#xff0c;UI 层轮子早被碾得稀碎。…

作者头像 李华
网站建设 2026/9/20 5:37:16

火电厂脱硝DCS调试实操指南:接地、冗余、I/O与PID全链路验证

简介&#xff1a;本资源是一份完整的脱硝DCS系统调试技术报告&#xff0c;面向电力行业自动化工程师、热控调试人员及火电厂运行维护技术人员&#xff0c;聚焦烟气脱硝工程中分散控制系统&#xff08;DCS&#xff09;的现场调试实践与验收标准。报告以忻州广宇煤电2135MW机组项…

作者头像 李华
网站建设 2026/9/20 5:35:24

CWI考试试题汇编:焊接检验标准应用能力的结构化训练指南

简介&#xff1a;本资源为CWI&#xff08;Certified Welding Inspector&#xff09;焊接检验师认证考试的权威试题汇编&#xff0c;面向软件开发中涉及工业工程、嵌入式系统集成、智能装备研发等领域的技术人员&#xff0c;以及需对接焊接质量标准的跨学科工程师。内容严格依据…

作者头像 李华
网站建设 2026/9/20 5:35:08

大模型时代AI产品经理必备能力与学习路径

1. 大模型时代的产品经理能力图谱在大模型技术爆发的当下&#xff0c;产品经理的角色定位正在发生深刻变革。传统互联网时代的需求翻译者角色已不足以应对AI产品的复杂性&#xff0c;我们需要既懂transformer架构又能设计商业闭环的复合型人才。去年负责某智能写作平台时&#…

作者头像 李华