为什么需要参数高效微调?
全量微调一个 Llama-2-7B 模型大约需要 56GB 显存——通常需要多张 A100 才能跑。而 LoRA 只需要不到 1% 的可训练参数,显存需求降低到 16GB 以下,一张 RTX 3090 就足够。
LoRA:低秩适配
核心思想
LoRA(Low-Rank Adaptation)的核心假设是:模型适配过程中,权重更新矩阵 存在于一个低秩子空间中。
这意味着:
其中 ,,(通常是 8 或 16)。
在推理时, 可以直接合并为一个大矩阵,零额外推理延迟。
实现
import torch
import torch.nn as nn
class LoRALayer(nn.Module):
def __init__(self, in_features, out_features, rank=8, alpha=16):
super().__init__()
self.rank = rank
self.alpha = alpha
# 低秩矩阵:A用小幅值高斯初始化,B初始化为零
self.A = nn.Parameter(torch.randn(in_features, rank) * 0.02)
self.B = nn.Parameter(torch.zeros(rank, out_features))
# 缩放因子
self.scaling = alpha / rank
def forward(self, x):
return (x @ self.A @ self.B) * self.scaling
class LinearWithLoRA(nn.Module):
def __init__(self, linear, rank=8, alpha=16):
super().__init__()
self.linear = linear # 冻结的原始权重
self.lora = LoRALayer(linear.in_features, linear.out_features, rank, alpha)
def forward(self, x):
return self.linear(x) + self.lora(x)
def merge(self):
# 将LoRA权重合并到原权重中
self.linear.weight.data += (self.lora.A @ self.lora.B).T * self.lora.scaling
关键超参数
| 参数 | 建议值 | 说明 |
|---|---|---|
rank (r) | 8-16 | 越大容量越高,但不一定提升效果 |
alpha | 16-32 | 控制 LoRA 更新的缩放幅度 |
target_modules | q_proj, v_proj | 实验证明只适配 Query 和 Value 效果最好 |
QLoRA:量化 + 低秩适配
QLoRA 在 LoRA 基础上引入了三个关键创新:
4-bit NormalFloat(NF4)
NF4 是一种针对正态分布数据优化的 4-bit 量化方案。相比于传统的 INT4 量化,NF4 在处理 LLM 权重时具有更高的信息保真度。
双重量化
不仅量化模型权重,还对量化常数本身进行二次量化,进一步压缩 0.37 bits per parameter。
分页优化器
当遇到显存峰值时,自动将 optimizer state 分页到 CPU 内存:
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto"
)
实战:用 QLoRA 微调 Llama-2-7B
准备数据集
from datasets import load_dataset
dataset = load_dataset("json", data_files="my_safety_dataset.jsonl")
def format_instruction(example):
return {
"text": f"### Instruction:\n{example['instruction']}\n\n### Response:\n{example['response']}"
}
配置 PEFT
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=8,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # trainable params: 4,194,304 / 6,738,415,616 = 0.062%
训练
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./lora-llama-safety",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # effective batch size = 16
learning_rate=2e-4,
logging_steps=10,
save_strategy="epoch",
fp16=True,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
)
trainer.train()
效果对比
在安全审查任务上(检测代码中的潜在漏洞),微调前后的对比:
| 指标 | 原始 Llama-2-7B | QLoRA 微调后 |
|---|---|---|
| XSS 检测准确率 | 0.43 | 0.81 |
| SQL 注入检测准确率 | 0.51 | 0.84 |
| 误报率 | 0.31 | 0.12 |
仅增加 400 万可训练参数(0.06%),性能提升显著。这就是参数高效微调的魅力——用最小的资源成本获取最大的业务价值。
