一、全参数微调的问题

以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: 共享随机种子,减少可训练参数