self-llm 实战:基于 transformers 与 peft 对 Qwen2.5-Coder-7B-Instruct 进行 LoRA 微调
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm
本教程是《开源大模型食用指南》(self-llm)中 Qwen2.5-Coder 系列的微调篇,面向已掌握基础环境配置与模型部署的读者,完整演示如何基于transformers、peft框架对 Qwen2.5-Coder-7B-Instruct 模型进行高效 LoRA 微调。读完本篇,你将掌握从模型下载、指令数据集构建、数据格式化、LoRA 配置、Trainer 训练到加载 LoRA 权重推理的全流程,并能够将其迁移到任何遵循 ChatML 模板的开源模型上。
一、LoRA 微调与 Qwen2.5-Coder 概述
LoRA(Low-Rank Adaptation,低秩适配)是一种高效微调方法,核心思想是冻结预训练模型的全部参数,仅在模型中注入少量可训练的低秩分解矩阵来学习任务差异。与全参数微调相比,LoRA 大幅降低了显存占用与训练成本,且微调产物是一个体积很小的 adapter 权重文件,便于保存、分发和切换。深入了解其原理可参见博客 知乎|深入浅出LoRA。
Qwen2.5-Coder-7B-Instruct 是通义千问系列面向代码场景的 7B 级指令模型,同样具备优秀的通用对话能力。从本仓库 Qwen2.5-Coder-7B-instruct lora微调.ipynb 中记录的模型结构可以看出,它由 28 层 Qwen2DecoderLayer 组成,每层包含自注意力(q_proj/k_proj/v_proj/o_proj)与 MLP(gate_proj/up_proj/down_proj),模型隐藏维度 3584,词表大小 152064,这正是后续LoraConfig.target_modules参数取值的直接依据。
本文配套 Notebook:Qwen2.5-Coder-7B-Instruct Lora 微调.ipynb,建议边读边在 Notebook 中执行。
二、模型下载
使用modelscope中的snapshot_download函数下载模型,第一个参数为模型名称,参数cache_dir为模型的下载路径。
在/root/autodl-tmp路径下新建model_download.py文件并输入以下内容,粘贴代码后及时保存文件,然后运行python /root/autodl-tmp/model_download.py执行下载:
import torch from modelscope import snapshot_download import os model_dir = snapshot_download('Qwen/Qwen2.5-Coder-7B-Instruct', cache_dir='/root/autodl-tmp', revision='master')模型大小约 14.18GB,下载耗时视网络状况而定(仓库实测约 5~15 分钟)。下载完成后,模型会被保存在cache_dir指定的目录下。在 05-Qwen2.5-Coder-7B-Instruct Lora 微调 SwanLab 可视化记录版.md 中,加载路径写作/root/autodl-tmp/Qwen/Qwen2___5-Coder-7B-Instruct/,这是 ModelScope 对模型名中.的转义目录命名(.对应___),若你的本地路径不同,请以实际下载目录为准。
三、环境配置
在完成基本环境配置和本地模型部署的情况下,还需要安装以下第三方库:
python -m pip install --upgrade pip pip install modelscope==1.20.0 pip install transformers==4.46.2 pip install sentencepiece==0.2.0 pip install accelerate==1.1.1 pip install datasets==3.1.0 pip install peft==0.13.2各库职责如下:
| 库 | 版本 | 用途 |
|---|---|---|
modelscope | 1.20.0 | 模型下载与本地加载 |
transformers | 4.46.2 | 模型、分词器、Trainer 训练框架 |
sentencepiece | 0.2.0 | 分词器底层依赖(Qwen 系列 BPE 分词需要) |
accelerate | 1.1.1 | 分布式与 device_map 自动加载支持 |
datasets | 3.1.0 | 数据集加载与预处理 |
peft | 0.13.2 | LoRA 配置、注入与权重管理 |
考虑到部分同学配置环境可能会遇到一些问题,可在 AutoDL 平台使用 Qwen2.5 环境镜像(codewithgpu 搜索
datawhalechina/self-llm/qwen2.5-coder)直接创建实例,避免重复踩坑。
环境就绪后,本节微调数据集放置在仓库根目录 dataset/huanhuan.json(甄嬛风格对话数据集,共 3729 条样本)。
四、指令集构建
LLM 的微调一般指指令微调(Instruction Tuning)过程。所谓指令微调,是指使用的微调数据形如:
{ "instruction":"回答以下用户问题,仅输出答案。", "input":"1+1等于几?", "output":"2" }其中,instruction是用户指令,告知模型其需要完成的任务;input是用户输入,是完成用户指令所必需的输入内容;output是模型应该给出的输出。核心训练目标是让模型具有理解并遵循用户指令的能力。
因此,在指令集构建时,应针对目标任务构建针对性的任务指令集。例如本节使用开源的 Chat-甄嬛 项目作为示例,目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,构造的指令形如:
{ "instruction": "你是谁?", "input":"", "output":"家父是大理寺少卿甄远道。" }打开 dataset/huanhuan.json 可以看到,实际语料正是以“台词-应答”形式组织的对话对,例如“娘娘。”→“你放心,本宫到任何时候都不会自轻自贱委屈了这孩子。”,模型将在微调中学习这种语言风格与角色设定。
五、数据格式化
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 数据处理的本质:
- Prompt Template 拼接:Qwen2.5-Coder 采用 ChatML 风格的 Prompt Template,格式如下。
instruction部分(system + user 消息)会被完整拼进模板,同时add_special_tokens=False防止开头自动加上 BOS 等特殊 token:
<|im_start|>system You are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|> <|im_start|>user {user_prompt}<|im_end|> <|im_start|>assistant {assistant_response}<|im_end|>labels 掩码:
labels中instruction部分全部置为-100(PyTorch 交叉熵损失会忽略该值),只有response部分的 token 参与损失计算。这样模型只学习“如何回答”,而不学习“如何复述指令”。从 Qwen2.5-Coder-7B-instruct lora微调.ipynb 中的验证代码可以看到,过滤掉-100后解码labels恰好还原为模型应答文本加<|endoftext|>结束符。填充与截断:输入末尾补
pad_token_id(Qwen2.5 的 pad token 是<|endoftext|>,id 为 151643,可在 Notebook 的 tokenizer 打印信息中确认),并将超长序列截断到MAX_LENGTH=384。中文一个字可能被切分为多个 token,所以最大长度需要适当放宽以保证数据完整性。
六、加载 tokenizer 与半精度模型
模型以半精度(bfloat16)形式加载,如果你的显卡比较新,可以使用torch.bfloat16。对于自定义模型一定要指定trust_remote_code=True:
tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/qwen/Qwen2-7B-Instruct/', use_fast=False, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/qwen/Qwen2-7B-Instruct/', device_map="auto",torch_dtype=torch.bfloat16)两个参数的注意事项:
use_fast=False:使用慢速(Python)tokenizer,避免与模型快速分词器在特殊 token 处理上的潜在不一致;device_map="auto":由 accelerate 自动将模型各层分配到可用 GPU/CPU,可配合显存较小的环境使用。
七、定义 LoraConfig
LoraConfig中可以设置很多参数,主要的参数如下:
task_type:模型类型,因果语言模型填TaskType.CAUSAL_LM;target_modules:需要训练(注入 LoRA)的模型层名字,主要就是 attention 部分的层,不同模型层名不同,可以传数组、字符串或正则表达式。Qwen2.5-Coder 的 28 层 decoder 中,自注意力层的q_proj/k_proj/v_proj/o_proj与 MLP 层的gate_proj/up_proj/down_proj都是候选目标(依据见 Qwen2.5-Coder-7B-instruct lora微调.ipynb 中打印的模型结构);r:LoRA 的秩(rank),决定低秩矩阵的维度;lora_alpha:LoRA 缩放系数,详见 LoRA 原理。
LoRA 的缩放因子不是r(秩),而是lora_alpha / r,本配置中即为 32 / 8 = 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 比例 )注入方式为get_peft_model(model, config)。在 Qwen2.5-Coder-7B-instruct lora微调.ipynb 中,执行model.print_trainable_parameters()后输出:
trainable params: 20,185,088 || all params: 7,635,801,600 || trainable%: 0.2643即 7.6B 参数的模型中仅约 0.26%(约 2000 万)参数参与训练,这正是 LoRA 高效性的直观体现。
超参数对比:SwanLab 可视化版 05-Qwen2.5-Coder-7B-Instruct Lora 微调 SwanLab 可视化记录版.md 中采用
r=64, lora_alpha=16, lora_dropout=0.1,此时缩放因子为 16/64=0.25,并通过get_peft_model注入后直接训练。实际调参时可依据任务难度在 r=8~64 之间权衡:秩越大适配能力越强,但可训练参数量与显存占用也随之上升。
八、自定义 TrainingArguments 参数
TrainingArguments的源码介绍了每个参数的具体作用,这里介绍几个常用的:
output_dir:模型的输出路径;per_device_train_batch_size:单卡 batch_size;gradient_accumulation_steps:梯度累加步数。显存较小时可以把 batch_size 调小、梯度累加调大,等效放大全局 batch;logging_steps:每多少步输出一次 log;num_train_epochs:训练轮数;gradient_checkpointing:梯度检查点。一旦开启,模型必须执行model.enable_input_require_grads(),原理可自行探索。
args = TrainingArguments( output_dir="./output/Qwen2_instruct_lora", 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 )这里per_device_train_batch_size=4与gradient_accumulation_steps=4组合,等效全局 batch size 为 16。gradient_checkpointing=True时需要注意:Notebook 训练日志中会出现use_cache=True is incompatible with gradient checkpointing. Setting use_cache=False...的提示,这是框架自动关闭 KV cache 缓存以节省显存,属于正常现象。
九、使用 Trainer 训练
trainer = Trainer( model=model, args=args, train_dataset=tokenized_id, data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True), ) trainer.train()其中tokenized_id是调用ds.map(process_func, remove_columns=ds.column_names)后的编码数据集,DataCollatorForSeq2Seq负责将 batch 内序列 padding 到相同长度(padding=True),并自动处理labels的填充对齐。
从 Qwen2.5-Coder-7B-instruct lora微调.ipynb 的完整训练记录可以看到,3 个 epoch 共 699 步,最终train_loss ≈ 2.7545,训练时长约 23 分钟,loss 从初期的 3.97 逐步下降到 2.1 左右并趋于收敛,验证了该参数组合在甄嬛数据集上的有效性。
十、加载 LoRA 权重推理
训练完成后,输出目录会按save_steps保存多个 checkpoint(例如output/Qwen2_instruct_lora/checkpoint-690/)。推理时使用PeftModel加载 LoRA 权重:
from transformers import AutoModelForCausalLM, AutoTokenizer import torch from peft import PeftModel model_path = 'Qwen/Qwen2.5-Coder-7B-Instruct' 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)几点推理细节说明:
- 模板一致性:训练时手工拼接的是 ChatML 模板,推理时用
tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)生成同样格式的输入,保证训练/推理模板对齐; - 生成后处理:
generated_ids通过切片去掉输入部分,只保留新增生成的 token,再batch_decode得到应答文本; - 加载方式:
PeftModel.from_pretrained(model, model_id=lora_path)会把 LoRA adapter 叠加到已加载的基座模型上。在 Qwen2.5-Coder-7B-instruct lora微调.ipynb 中,加载 checkpoint-690 后以max_length=2500, do_sample=True, top_k=1采样,模型对“你是谁?”的回答是“我是甄嬛,家父是大理寺少卿甄远道。”,说明微调已成功将角色设定注入模型。
十一、进阶:结合 SwanLab 可视化微调与结果监控
仓库同目录提供了 05-Qwen2.5-Coder-7B-Instruct Lora 微调 SwanLab 可视化记录版.md 及其配套 Notebook,展示了将上述全流程与 SwanLab 结合的进阶玩法,可作为本教程的直接延伸:
- 数据集替换:使用中文法律问答数据集 DISC-Law-SFT 的 Pair-QA 子集,先通过脚本将 jsonl 格式重写为统一的
{instruction, input, output}结构; - 训练配置:取前 5000 条训练、5 条评测,
r=64, lora_alpha=16, lora_dropout=0.1,训练 1 个 epoch; - SwanLab 集成:SwanLab 与 Transformers 已做好集成,只需在
Trainer的callbacks参数中传入SwanLabCallback实例,即可自动记录超参数与训练指标;首次使用需在 swanlab.cn 注册账号并粘贴 API Key; - 自定义回调:继承
SwanLabCallback重写on_train_begin与on_epoch_end,在训练开始前与每个 epoch 结束时对测试集进行主观评测,并把问答结果以swanlab.Text形式记录到面板。
如上图所示,训练启动后 SwanLab 会提示登录并给出云端项目链接;训练结束后打开面板即可看到 loss、learning_rate、grad_norm、epoch 等指标曲线(见本文首图),便于对比实验、定位过拟合与欠拟合问题。
十二、常见问题与调参建议
- 显存不足:优先降低
per_device_train_batch_size(如 4→2→1),同时提高gradient_accumulation_steps维持等效 batch;开启gradient_checkpointing并调用model.enable_input_require_grads(); - loss 不下降:检查
labels掩码是否正确(-100只应覆盖 instruction 部分)、Prompt Template 与apply_chat_template是否一致、学习率是否过大/过小(常用区间 1e-5~1e-4); - 推理时角色不生效:确认推理 system prompt 与训练时保持一致(如“现在你要扮演皇帝身边的女人--甄嬛”),且基座模型路径与训练时一致;
- trainable% 异常:检查
target_modules是否匹配模型实际层名,Qwen2.5-Coder 的 7 个目标模块名可对照 Notebook 中打印的Qwen2ForCausalLM结构确认; - 数据量:huanhuan.json 约 3729 条样本,配合 r=8 训练 3 个 epoch 即可看到明显效果;若任务更复杂,可增大
r或扩充数据。
本文从模型下载、数据构建、训练到推理的完整链路均基于仓库 models/Qwen2.5-Coder 目录下的文档与 Notebook 整理而成。后续如需将微调后的模型投入服务,可继续参考同目录下的 04-Qwen2.5-Coder-7B-Instruct vLLM 部署调用.md 等部署篇章。
【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调(全参数/Lora)、部署国内外开源大模型(LLM)/多模态大模型(MLLM)教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考