4. LoRA微调LLaMA-7B
任务目标
本案例使用 LoRA(Low-Rank Adaptation)方法对 LLaMA-7B 进行参数高效微调,在保持模型质量的同时大幅减少可训练参数量。通过对比 LoRA 与全参数微调在相同下游任务上的表现,验证 LoRA 在资源受限场景下的实用价值。
环境与依赖
需要一台配备至少 24GB 显存的 GPU(如 RTX 3090/4090 或 A10),并安装以下依赖:
pip install transformers==4.36.0 peft==0.7.1 datasets accelerate bitsandbytes
数据集准备
使用 imdb 影评情感分类数据集(二分类),取 2000 条训练样本和 500 条测试样本,保证实验可快速复现。数据预处理将文本截断至 256 。
完整实现代码
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer
from peft import LoraConfig, get_peft_model, TaskType
from datasets import load_dataset
import numpy as np
from sklearn.metrics import accuracy_score
# ========== 1. 加载数据 ==========
dataset = load_dataset("imdb", split="train[:2000]")
eval_dataset = load_dataset("imdb", split="test[:500]")
tokenizer = AutoTokenizer.from_pretrained("huggyllama/llama-7b")
tokenizer.pad_ = tokenizer.eos_
tokenizer.padding_side = "right"
def preprocess(examples):
return tokenizer(examples["text"], truncation=True, max_length=256, padding="max_length")
train_data = dataset.map(preprocess, batched=True, remove_columns=["text"])
eval_data = eval_dataset.map(preprocess, batched=True, remove_columns=["text"])
train_data = train_data.rename_column("label", "labels")
eval_data = eval_data.rename_column("label", "labels")
train_data.set_format("torch", columns=["input_ids", "attention_mask", "labels"])
eval_data.set_format("torch", columns=["input_ids", "attention_mask", "labels"])
# ========== 2. 加载模型并应用 LoRA ==========
model = AutoModelForSequenceClassification.from_pretrained(
"huggyllama/llama-7b",
num_labels=2,
torch_dtype=torch.float16,
device_map="auto"
)
# LoRA 配置:仅微调 attention 层的 q_proj, v_proj
lora_config = LoraConfig(
task_type=TaskType.SEQ_CLS,
r=8,
lora_alpha=16,
lora_dropout=0.1,
target_modules=["q_proj", "v_proj"],
bias="none"
)
peft_model = get_peft_model(model, lora_config)
peft_model.print_trainable_parameters()
# 预期输出: trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.0622
# ========== 3. 训练参数 ==========
training_args = TrainingArguments(
output_dir="./lora_llama7b_imdb",
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
gradient_accumulation_steps=8,
num_train_epochs=1,
evaluation_strategy="steps",
eval_steps=100,
save_steps=500,
logging_steps=50,
learning_rate=2e-4,
fp16=True,
remove_unused_columns=False,
report_to="none"
)
def compute_metrics(eval_pred):
predictions, labels = eval_pred
preds = np.argmax(predictions, axis=-1)
return {"accuracy": accuracy_score(labels, preds)}
trainer = Trainer(
model=peft_model,
args=training_args,
train_dataset=train_data,
eval_dataset=eval_data,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
# ========== 4. 训练与评估 ==========
trainer.train()
eval_result = trainer.evaluate()
print(f"LoRA 微调测试集准确率: {eval_result['eval_accuracy']:.4f}")
# ========== 5. 保存模型 ==========
peft_model.save_pretrained("./lora_llama7b_imdb_final")
tokenizer.save_pretrained("./lora_llama7b_imdb_final")
全参数微调对比实现
为对比 LoRA 效果,使用相同数据和训练配置进行全参数微调。仅需将上述代码中 LoRA 相关部分替换为直接训练原始模型:
# 注释掉 LoRA 配置与 get_peft_model 调用,直接使用原始 model
# 同时将学习率调整为 1e-5(全参数微调通常需要更低学习率)
# 其余数据加载、训练参数、评估逻辑完全一致
运行方式
- 将上述代码保存为
lora_finetune.py,在终端执行:python lora_finetune.py - 全参数微调脚本保存为
full_finetune.py,同样直接运行。 - 两个脚本独立运行,互不依赖。
验证方法与判定标准
实验完成后,通过以下标准判断 LoRA 微调是否成功:
- 训练参数量对比:LoRA 脚本应打印 trainable params 约为 419 万,仅占全部参数的 0.06%;全参数微调则为 100% 参数量参与训练。
- 显存占用对比:在相同 batch size 下,LoRA 微调显存占用通常比全参数微调低 30%-50%(可通过
nvidia-smi观察)。 - 准确率对比:LoRA 微调在测试集上的准确率应达到全参数微调准确率的 95% 以上(例如全参数微调准确率约 0.92,LoRA 应不低于 0.87)。
- 训练时间对比:LoRA 微调完成 1 个 epoch 的时间应显著短于全参数微调(通常快 2-3 倍)。
若 LoRA 准确率明显低于全参数微调(差距超过 5 个百分点),可尝试增大 r 至 16 或添加 k_proj 到 target_modules 中重新实验。
常见问题
- 显存不足:若 24GB 显存无法运行,可减少
per_device_train_batch_size至 2,并相应增大gradient_accumulation_steps。 - 加载模型慢:首次运行需下载约 13GB 模型权重,请确保网络稳定或提前手动下载至本地缓存。
- tokenizer 警告:LLaMA 原始 tokenizer 无 pad_,代码中已设置为 eos_,属正常处理。