大模型微调实战:从 LoRA 到 QLoRA

Maurice | 灵阙学院 2026-02-27

微调 vs RAG:何时选择微调

维度 RAG 微调 两者结合
知识更新频率 高(实时替换文档) 低(需重新训练)
风格/格式控制 最强
推理成本 较高(长 context) 较低(知识内化)
幻觉控制 强(有据可查) 中(需要高质量数据) 最强
适用场景 知识问答、文档检索 风格迁移、领域术语、格式输出 企业级知识助手

决策规则:如果问题是"模型不知道某些事实" ---> 用 RAG;如果问题是"模型知道但表达方式/格式不对" ---> 用微调。

微调方法全景对比

┌─────────────────────────────────────────────────────────┐
│              Parameter-Efficient Fine-Tuning             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Full Fine-tuning          全参数更新,效果最好但成本极高  │
│       │                                                 │
│       ├── LoRA             低秩分解,仅训练小矩阵          │
│       │    └── QLoRA       量化基座 + LoRA,内存降 75%     │
│       │                                                 │
│       ├── Prefix Tuning    学习虚拟前缀 token             │
│       ├── P-Tuning v2      深层 prompt 注入               │
│       └── Adapter          在层间插入小型网络              │
│                                                         │
└─────────────────────────────────────────────────────────┘
方法 可训练参数 显存需求(7B) 训练速度 效果 推荐度
Full Fine-tuning 100% ~120GB (FP16) 最佳 资源充足时
LoRA 0.1-1% ~28GB (FP16) 接近全参 默认首选
QLoRA 0.1-1% ~10GB (NF4) 接近 LoRA 显存受限时
Prefix Tuning <0.1% ~18GB 最快 中等 简单任务
Adapter 1-5% ~35GB 良好 多任务切换

LoRA 核心原理

LoRA 的核心洞察:微调过程中权重更新矩阵 Delta-W 是低秩的。与其更新完整的 d x d 矩阵,不如分解为两个小矩阵 A (d x r) 和 B (r x d),其中 r 远小于 d。

原始权重:  W (d x d)         -- 冻结,不更新
LoRA 分支: Delta-W = B x A   -- B: (d x r), A: (r x d)
最终输出:  h = W(x) + B(A(x)) * (alpha / r)
  • r (rank):通常 8-64,值越大表达能力越强但参数越多
  • alpha:缩放因子,通常设为 2r(即 alpha/r = 2)
  • target_modules:通常选 q_proj, v_proj;更激进可加 k_proj, o_proj, gate_proj, up_proj, down_proj

QLoRA:量化基座 + LoRA

QLoRA 在 LoRA 基础上引入三项关键技术:

  1. 4-bit NormalFloat (NF4):信息论最优的 4-bit 量化格式
  2. Double Quantization:对量化常数再做一次量化,每参数额外节省 0.37 bit
  3. Paged Optimizers:用 CPU 内存分页处理 GPU 显存峰值

效果:7B 模型从需要 28GB 显存降低到 10GB,单张 RTX 3090/4090 即可训练。

数据集准备

Instruction 格式(推荐 Alpaca 格式)

[
  {
    "instruction": "判断以下发票是否合规",
    "input": "发票号码:01234567,开票日期:2025-13-01,金额:5000元",
    "output": "不合规。原因:开票日期 2025-13-01 中月份 13 无效,应为 01-12 之间。建议退回重开。"
  },
  {
    "instruction": "解释增值税留抵退税政策的适用条件",
    "input": "",
    "output": "根据财政部税务总局公告2022年第14号,增值税留抵退税需满足以下条件:1) 纳税信用等级为A级或B级..."
  }
]

数据质量要点

要素 要求 常见错误
数量 500-10000 条高质量 > 100000 条低质量 堆量不管质
多样性 覆盖目标场景的各种变体 只有一种模式
一致性 输出格式、长度、风格统一 混杂不同标注者的风格
难度梯度 从简单到复杂均有覆盖 全是简单/全是困难
去重 语义去重,非仅文本去重 近似重复拉高指标但不泛化

实战训练代码

环境准备

pip install transformers peft trl bitsandbytes datasets accelerate

QLoRA 训练完整示例

import torch
from datasets import load_dataset
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    TrainingArguments,
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer

# 1. 量化配置
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

# 2. 加载基座模型
model_id = "Qwen/Qwen2.5-7B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
)
model = prepare_model_for_kbit_training(model)

tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token

# 3. LoRA 配置
lora_config = LoraConfig(
    r=32,                          # rank
    lora_alpha=64,                 # scaling factor
    lora_dropout=0.05,
    target_modules=[
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    task_type="CAUSAL_LM",
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# Output: trainable params: 83,886,080 || all params: 7,699,959,808 || 1.09%

# 4. 数据集
dataset = load_dataset("json", data_files="train_data.json", split="train")

def format_instruction(sample):
    if sample["input"]:
        text = f"### Instruction:\n{sample['instruction']}\n\n### Input:\n{sample['input']}\n\n### Response:\n{sample['output']}"
    else:
        text = f"### Instruction:\n{sample['instruction']}\n\n### Response:\n{sample['output']}"
    return {"text": text}

dataset = dataset.map(format_instruction)

# 5. 训练参数
training_args = TrainingArguments(
    output_dir="./qwen2.5-7b-tax-lora",
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,   # 等效 batch_size=16
    learning_rate=2e-4,
    warmup_ratio=0.1,
    lr_scheduler_type="cosine",
    bf16=True,
    logging_steps=10,
    save_strategy="epoch",
    optim="paged_adamw_8bit",
    max_grad_norm=0.3,
)

# 6. 开始训练
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    max_seq_length=2048,
)

trainer.train()
trainer.save_model("./qwen2.5-7b-tax-lora/final")

超参数调优指南

参数 推荐范围 影响 调优建议
rank (r) 8-64 越大表达力越强 先 r=16 跑 baseline
alpha 2r 缩放因子 alpha = 2*rank 为经验默认值
learning_rate 1e-4 ~ 5e-4 过大震荡,过小收敛慢 QLoRA 推荐 2e-4
epochs 2-5 过多过拟合 观察 eval loss
batch_size 4-16 (等效) 太小不稳定 gradient_accumulation 凑到 16
warmup_ratio 0.05-0.1 训练初期稳定性 0.1 适合小数据集

成本估算

模型规模 方法 最低显卡 训练 1000 条/3 epoch 耗时 云成本估算
7B QLoRA 1x RTX 4090 (24GB) ~30 min ~$1
7B LoRA 1x A100 40GB ~20 min ~$3
13B QLoRA 1x A100 40GB ~60 min ~$6
70B QLoRA 2x A100 80GB ~8 hours ~$80
70B Full FT 8x A100 80GB ~24 hours ~$800

评估与合并

# 合并 LoRA 权重到基座
from peft import PeftModel

base_model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16)
merged_model = PeftModel.from_pretrained(base_model, "./qwen2.5-7b-tax-lora/final")
merged_model = merged_model.merge_and_unload()
merged_model.save_pretrained("./qwen2.5-7b-tax-merged")

# 推理测试
from transformers import pipeline
pipe = pipeline("text-generation", model="./qwen2.5-7b-tax-merged", tokenizer=tokenizer)
result = pipe("### Instruction:\n判断以下交易是否需要缴纳印花税\n\n### Input:\n甲方与乙方签订软件开发合同,金额50万元\n\n### Response:\n", max_new_tokens=256)
print(result[0]["generated_text"])

常见陷阱

  1. 过拟合:小数据集训练 epoch 过多,eval loss 上升但 train loss 下降 ---> 减少 epoch 或增加 dropout
  2. 灾难性遗忘:微调后模型丧失通用能力 ---> 混入 5-10% 通用指令数据
  3. 格式不一致:训练数据格式与推理时 prompt 格式不匹配 ---> 推理时严格使用训练时的模板
  4. 量化误差累积:NF4 在极端值上有精度损失 ---> 关键场景对比 FP16 LoRA 效果

Maurice | maurice_wen@proton.me