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(全参数微调通常需要更低学习率)
# 其余数据加载、训练参数、评估逻辑完全一致

运行方式

  1. 将上述代码保存为 lora_finetune.py,在终端执行:python lora_finetune.py
  2. 全参数微调脚本保存为 full_finetune.py,同样直接运行。
  3. 两个脚本独立运行,互不依赖。

验证方法与判定标准

实验完成后,通过以下标准判断 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_,属正常处理。