大模型全栈开发实战指南(第三天)
大模型微调实战学习笔记
一、 学习背景与目标
1.1 为什么需要微调?
虽然现在的大语言模型(如 Qwen, Llama 3, ChatGPT)能力很强,但直接使用预训练模型(Base Model)往往存在以下问题:
- 指令遵循能力弱: 不理解“请翻译”、“请总结”等指令。
- 缺乏领域知识: 对医疗、法律、金融等垂直领域的专业术语或格式不了解。
- 输出格式不可控: 难以强制输出 JSON、特定表格等格式。
微调(SFT, Supervised Fine-Tuning) 的目的就是在预训练模型的基础上,通过特定的指令数据集,进一步调整模型参数,使其学会“听懂指令”并“按需输出”。
1.2 微调的层级
- Full Fine-tuning(全量微调): 更新模型所有参数。效果最好,但显存需求巨大,成本极高。
- PEFT(Parameter-Efficient Fine-Tuning,参数高效微调): 只更新极少部分参数。本笔记核心。
二、 核心技术:PEFT 与 LoRA
2.1 PEFT (Parameter-Efficient Fine-Tuning)
PEFT 是一种旨在解决全量微调算力负担过重的技术框架。它通过冻结大部分模型参数,只训练极少的额外参数(通常少于原模型的 1%),就能达到接近全量微调的效果。
2.2 LoRA (Low-Rank Adaptation)
LoRA 是目前最流行的 PEFT 方法。
- 核心原理:
假设预训练的某个权重矩阵为 WWW(维度 d×kd \times kd×k)。微调时的增量更新 ΔW\Delta WΔW 通常是一个低秩矩阵。
LoRA 将 ΔW\Delta WΔW 分解为两个极小的矩阵 AAA 和 BBB 的乘积:ΔW=B×A\Delta W = B \times AΔW=B×A。- BBB 的维度是 d×rd \times rd×r
- AAA 的维度是 r×kr \times kr×k
- rrr 是秩,通常设得很小(如 8, 16, 32),远小于 ddd 和 kkk。
- 优势:
- 显存占用极低: 只需要存储 AAA 和 BBB 的参数。
- 训练速度快: 梯度计算量大幅减少。
- 无推理延迟: 在推理阶段,可以将 B×AB \times AB×A 的结果合并回原矩阵 WWW,不改变模型结构。
2.3 QLoRA
为了进一步节省显存,QLoRA 在 LoRA 的基础上,将基础大模型量化为 4-bit 整数进行存储和计算,仅在训练特定层时反量化回 BF16。这使得我们在单张消费级显卡(如 RTX 3090/4090)上即可微调 7B 甚至更大的模型。
三、 实战流程详解
本实战基于 Hugging Face 生态系统,主要使用 transformers, peft, trl 库。
3.1 环境准备
pip install transformers datasets peft trl bitsandbytes accelerate torch
3.2 数据集准备
微调数据通常采用 JSON/JSONL 格式,主流的指令微调格式如下(Alpaca 格式变体):
{
"instruction": "解释一下量子纠缠。",
"input": "",
"output": "量子纠缠是..."
}
或者更通用的对话格式:
{
"system": "你是一个智能助手。",
"user": "你好",
"assistant": "你好!有什么我可以帮你的吗?"
}
处理步骤:
- 加载数据集(使用
datasets库)。 - 进行 Tokenization(分词)。
- 拼接与截断:将 Instruction 和 Input 拼接,确保不超过模型最大长度(如 2048)。
- 构造 Labels:通常需要将输入部分的 Label 设为
-100(PyTorch 忽略计算 loss),只计算输出部分的 Loss。
3.3 加载模型与 LoRA 配置
这是最关键的一步,我们需要以 4-bit 加载基础模型,并挂载 LoRA 适配器。
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
# 1. 配置 4-bit 量化 (QLoRA)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=False,
)
# 2. 加载基础模型 (如 Qwen1.5-7B-Chat)
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen1.5-7B-Chat",
quantization_config=bnb_config,
device_map="auto"
)
# 3. 预处理模型以适应 k-bit 训练
model = prepare_model_for_kbit_training(model)
# 4. 配置 LoRA 参数
peft_config = LoraConfig(
r=16, # LoRA 秩,越大参数越多,效果可能越好但越慢
lora_alpha=32, # LoRA 缩放因子,通常设为 2*r
lora_dropout=0.05, # Dropout 比例
bias="none", # bias 是否训练
task_type="CAUSAL_LM", # 任务类型:因果语言模型
target_modules=[ # 需要注入 LoRA 的层名 (针对不同模型需调整)
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
]
)
# 5. 获取 PEFT 模型
model = get_peft_model(model, peft_config)
model.print_trainable_parameters() # 查看可训练参数占比
3.4 训练 (使用 SFTTrainer)
trl 库提供了 SFTTrainer,封装了数据预处理、LoRA 应用和训练循环,大大简化了代码。
from trl import SFTTrainer
trainer = SFTTrainer(
model=model,
train_dataset=dataset, # 之前处理好的数据集
dataset_text_field="text", # 数据集中包含 prompt+response 的字段
max_seq_length=2048, # 最大序列长度
tokenizer=tokenizer,
args=TrainingArguments(
output_dir="./results",
num_train_epochs=3, # 训练轮数
per_device_train_batch_size=4, # 批次大小 (显存不够则减小)
gradient_accumulation_steps=1, # 梯度累积
optim="paged_adamw_32bit",
save_steps=50,
logging_steps=10,
learning_rate=2e-4, # 学习率 (LoRA 通常推荐 2e-4 到 5e-5)
weight_decay=0.001,
fp16=False, # 如果用 bf16 则设为 False
bf16=True, # 推荐 bf16
gradient_checkpointing=True, # 激活梯度检查点以省显存
),
packing=False, # 是否打包多个短样本到一个序列
)
trainer.train()
3.5 保存与加载模型
训练完成后,我们只保存 LoRA 的适配器权重(几十 MB),不需要重新保存整个大模型。
# 保存 LoRA 适配器
model.save_pretrained("./lora_adaptor")
tokenizer.save_pretrained("./lora_adaptor")
# 加载模型进行推理
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen1.5-7B-Chat",
torch_dtype=torch.float16,
device_map="auto"
)
# 加载 LoRA 权重
model = PeftModel.from_pretrained(base_model, "./lora_adaptor")
# 此时 model 已经融合了 LoRA 权重,可以直接用于 generate
四、 关键参数与避坑指南
4.1 关键参数解释
- rank ®: 决定了 LoRA 矩阵的大小。太小(如 4)可能信息量不足,太大(如 128)则显存占用增加,可能导致过拟合。常用值:8, 16, 32, 64。
- Alpha: 缩放系数 ΔW×αr\Delta W \times \frac{\alpha}{r}ΔW×rα。通常 Alpha = 2 * r。
- Max Seq Length: 不要设得太大,因为上下文长度是显存杀手。如果不需要处理长文本,512 或 1024 足矣。
- Learning Rate: LoRA 微调对学习率比较敏感。一般全量微调用 1e-5,LoRA 可以用 2e-4 或 1e-4。
4.2 常见问题
- 显存溢出 (OOM):
- 减小
per_device_train_batch_size(如从 4 减到 1)。 - 增加
gradient_accumulation_steps(如从 1 增到 4),保持等效 Batch Size 不变。 - 开启
gradient_checkpointing=True。 - 确保使用了 4-bit 量化加载模型。
- 减小
- Loss 不下降:
- 检查数据格式是否正确(Prompt 结尾是否有换行符?)。
- 学习率是否过大或过小。
- 检查
target_modules是否写对(例如 Qwen 模型通常使用q_proj,v_proj等,不同架构名称不同)。
- 模型输出重复/循环:
- 这是常见现象,通常是因为训练轮数过多导致过拟合,或者是温度参数设置问题。在推理时适当调整
temperature和top_p。
- 这是常见现象,通常是因为训练轮数过多导致过拟合,或者是温度参数设置问题。在推理时适当调整
五、 总结
通过本次实战学习,我们掌握了如何利用 PEFT (LoRA) 和 QLoRA 技术在有限的算力资源下对开源大模型(如 Qwen)进行微调。
核心流程回顾:
- 数据构建: 构造高质量的指令问答对。
- 模型量化: 使用 BitsAndBytesConfig 进行 4-bit 加载。
- 配置 LoRA: 只训练低秩适配器矩阵。
- SFT 训练: 使用
trl库快速启动训练。 - 模型合并: 加载适配器进行推理。
微调不仅让模型学会了特定任务,更重要的是让模型的行为更符合人类的预期,是构建垂直领域 AI 应用的基石技能。
更多推荐



所有评论(0)