一、全参数微调的问题
以70B参数模型为例,全参数微调需要约140GB显存(FP16),单卡根本无法承载。全参数微调还需要更新所有梯度,计算和存储代价都极为高昂。
1.1 参数量与显存对照
# 各类模型的显存需求(全参数FP16微调)
# 公式:显存 ≈ 参数总量(GB) × 4 ~ 6(含梯度和优化器状态)
#
# 模型 参数 全量微调显存 LoRA显存(FP16) 节省比例
# ──────────────────────────────────────────────────────────
# LLaMA-7B 7B ~28GB ~2GB ~93%
# LLaMA-13B 13B ~52GB ~4GB ~92%
# LLaMA-70B 70B ~280GB ~16GB ~94%
# GPT-4(估计) ~1.8T ~7.2TB ~80GB ~99%
# 全参数微调的显存构成:
# ① 模型参数:7B × 2B(FP16) = 14GB
# ② 梯度:7B × 2B = 14GB
# ③ Adam优化器状态(两阶动量):
# 7B × 2B × 2(m,v) = 28GB
# ④ 激活值(反向传播时):batch_size × seq_len × layers × hidden
# 合计:14 + 14 + 28 + activations ≈ 56GB
二、LoRA的数学原理
2.1 低秩近似
# LoRA(Low-Rank Adaptation)的核心思想:
# 冻结预训练模型的权重W₀,只训练增量ΔW
# 前向传播:
# h = W₀ · x + ΔW · x
# 其中:ΔW = B · A,B∈R^(d×r),A∈R^(r×k)
# r << min(d, k),即低秩
# 初始化策略(关键!):
# A:随机高斯初始化,std=0.02
# B:全零初始化
# 这样训练开始时ΔW=0,模型输出与原始完全一致(安全基线)
# 在Transformer中的使用位置(按效果排序):
# ① Attention的Q和V投影(W_Q, W_V) ← 效果最好
# ② QKV三个投影都加LoRA
# ③ QKV + FFN全连接层
# ④ 全部权重加LoRA ← 效果最好,但显存增加
# rank(秩)的选择:
# r=4: 极简,显存占用最小,效果有限
# r=8: 常用值,效果与成本的平衡点
# r=64: 高秩,表达能力更强,显存略高
# r=128: 接近全参数微调效果(某些任务)
2.2 QLoRA:量化+LoRA
# QLoRA(Quantized LoRA)= NF4量化 + LoRA
# 将基座模型的权重从FP16量化为4-bit(NF4)
# 只在反向传播时反量化回FP16计算
# LoRA权重始终保持FP16
# NF4(Normal Float 4-bit)的核心设计:
# 量化分位点基于正态分布(非均匀量化)
# 因为Transformer权重近似正态分布,NF4比均匀量化更优
# bitsandbytes配置示例
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # Normal Float 4-bit
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True, # 双重量化:量化常数也量化
)
# 65B模型使用QLoRA后:
# 基座模型:65B × 4bit = 32GB → 量化后存储仅需~35GB
# LoRA权重:通常~0.1B参数,FP16 = 0.2GB
# 梯度:QLoRA不需要梯度(NF4权重不更新)
# 激活值:反量化计算,峰值~8GB
# 总计:~43GB,单卡A100(80GB)可以运行!
三、LoRA实战代码
# 使用peft库实现LoRA(Parameter-Efficient Fine-Tuning)
from peft import LoraConfig, get_peft_model, TaskType
lora_config = LoraConfig(
r=8, # rank,显存与效果的权衡
lora_alpha=16, # 缩放因子 = alpha/rank
target_modules=[ # 应用LoRA的层
"q_proj", "v_proj", # Attention投影
"k_proj", "o_proj", # 可选
],
lora_dropout=0.05, # Dropout防止过拟合
bias="none", # 不训练偏置项
task_type=TaskType.CAUSAL_LM
)
# 包装原始模型
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config, # QLoRA配置
device_map="auto"
)
model = get_peft_model(model, lora_config)
# 打印可训练参数比例
model.print_trainable_parameters()
# 输出:trainable params: 4,194,304 || all params: 6,738,415,872 || trainable%: 0.062%
# 训练配置
from transformers import TrainingArguments
training_args = TrainingArguments(
output_dir="./llama-lora-experiment",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # 等效batch_size=16
learning_rate=2e-4,
warmup_ratio=0.03,
lr_scheduler_type="cosine",
logging_steps=10,
save_strategy="epoch",
fp16=True,
optim="paged_adamw_32bit", # 分页AdamW,省显存
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
data_collator=data_collator,
)
trainer.train()
四、LoRA的高级变种
# ① AdaLoRA:自适应秩分配
# 根据参数重要性动态调整r
# 奇异值分解(SVD)衡量参数重要性
# 重要参数 → 高r,不重要 → 低r或剪枝
# ② QLoRA微调细节
# 分页optimizer:梯度累积超显存时自动卸载到CPU
# gradient checkpointing:反向传播时重算激活值,省显存
model.gradient_checkpointing_enable()
model.enable_input_require_grads()
# ③ DoRA(Weight-Decomposed LoRA)
# 将权重分解为:W = W₀ + ΔW = W₀ + m·B·A
# m为可学习的幅度向量,对每个参数独立缩放
# 在同等参数量下,效果优于标准LoRA
# ④ LoRA+ / VeRA
# LoRA+: 学习率分开(AdaFactor风格)
# VeRA: 共享随机种子,减少可训练参数