主题
🛠️ PEFT 微调实战
从零到一跑通第一个 LoRA 微调——代码直接抄走能用
环境准备
bash
pip install transformers peft accelerate datasets bitsandbytes核心库:
| 库 | 作用 |
|---|---|
| transformers | 加载模型和 tokenizer |
| peft | LoRA 实现 |
| accelerate | 分布式训练 |
| datasets | 数据加载 |
| bitsandbytes | 4-bit 量化(QLoRA 用) |
完整代码:QLoRA 微调 Qwen2.5-7B
以下代码在 16GB 显存显卡上可运行,微调 7B 模型。
python
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
TrainingArguments,
Trainer,
DataCollatorForSeq2Seq,
)
from peft import LoraConfig, get_peft_model, TaskType
from datasets import Dataset
import json
# ========== 1. 加载模型(4-bit 量化) ==========
model_name = "Qwen/Qwen2.5-7B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto",
load_in_4bit=True, # QLoRA 关键
bnb_4bit_compute_dtype=torch.float16,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# ========== 2. 配置 LoRA ==========
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # rank,越大效果越好但越慢
lora_alpha=16, # 缩放系数,一般设 r 的 2 倍
lora_dropout=0.1, # dropout 防过拟合
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # Qwen 的注意力层
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 输出:trainable params: 8,388,608 || all params: 7,623,000,000 || trainable%: 0.11%
# ========== 3. 加载数据 ==========
def load_dataset(path):
with open(path) as f:
data = json.load(f)
texts = []
for item in data:
# 拼接成 ChatML 格式
text = f"<|im_start|>system\n你是合同审查助手<|im_end|>\n"
text += f"<|im_start|>user\n{item['instruction']}\n{item['input']}<|im_end|>\n"
text += f"<|im_start|>assistant\n{item['output']}<|im_end|>"
texts.append(text)
return Dataset.from_dict({"text": texts})
dataset = load_dataset("contract_data.json")
# ========== 4. Tokenize ==========
def tokenize(examples):
result = tokenizer(
examples["text"],
truncation=True,
max_length=2048,
padding=False,
)
result["labels"] = result["input_ids"].copy()
return result
dataset = dataset.map(tokenize, batched=True)
# ========== 5. 训练配置 ==========
training_args = TrainingArguments(
output_dir="./qwen-lora-contract",
per_device_train_batch_size=2, # 显存不够就改成 1
gradient_accumulation_steps=4, # 4 步累加 = 等效 batch_size=8
num_train_epochs=3,
learning_rate=2e-4,
fp16=True,
logging_steps=10,
save_steps=100,
save_total_limit=2,
warmup_ratio=0.05,
lr_scheduler_type="cosine",
)
# ========== 6. 开始训练 ==========
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train()
# ========== 7. 保存 LoRA 权重 ==========
model.save_pretrained("./qwen-lora-contract-final")
tokenizer.save_pretrained("./qwen-lora-contract-final")显存不够怎么办
| 问题 | 解法 |
|---|---|
| OOM at batch_size=2 | 改成 1 |
| OOM at batch_size=1 | 开 gradient_checkpointing |
| 还是 OOM | 换 QLoRA(4-bit),或减小 max_length |
| 还是 OOM | 换更小的模型(1.5B 或 3B) |
python
# 开梯度检查点(省显存,稍慢)
model.gradient_checkpointing_enable()
# 减小序列长度
max_length=1024 # 从 2048 降到 1024加载微调后的模型
python
from peft import PeftModel
# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
torch_dtype=torch.float16,
device_map="auto",
)
# 加载 LoRA 权重
model = PeftModel.from_pretrained(base_model, "./qwen-lora-contract-final")
# 推理
inputs = tokenizer("审查这份合同:...", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=512)
print(tokenizer.decode(outputs[0]))合并 LoRA 到基座模型
想把 LoRA 权重合并到原模型(方便 Ollama/vLLM 部署):
python
merged_model = model.merge_and_unload()
merged_model.save_pretrained("./qwen-contract-merged")
tokenizer.save_pretrained("./qwen-contract-merged")训练技巧
- 学习率别太大:LoRA 推荐 1e-4 到 5e-4,全参数 1e-5 到 5e-5
- epoch 别太多:3-5 个 epoch 够了,多了过拟合
- 监控 loss:训练 loss 降、验证 loss 也降 → 正常;验证 loss 涨了 → 过拟合,停
- 小步快跑:先用 200 条数据跑通流程,再放全量数据
🎯 本章要点
- QLoRA = 4-bit 量化 + LoRA,16GB 显存就能微调 7B 模型
- 完整流程:加载→配 LoRA→加载数据→tokenize→训练→保存→加载推理
- OOM 四步解法:减 batch→开 checkpoint→降 max_length→换小模型
- LoRA 权重只有几 MB,合并后才能给 Ollama/vLLM 部署
加载练习题中...