大模型微调实战:LoRA 微调与显存优化

通用大模型什么都懂一点,但不懂你公司的业务。微调就是用自己的数据教它专业技能,LoRA 是性价比最高的微调方法:只训练一小部分参数,效果接近全量微调,显存和时间都省一个数量级。这篇把 LoRA 微调讲透:方法对比、原理、数据准备、训练实战、显存优化、监控、评估、部署、常见问题。

一、微调方法对比:先选对方法

方法训练参数7B 显存效果适用
全量微调100%60GB+最好数据多、预算足、效果优先
LoRA不到 1%24GB接近全量通用,推荐默认
QLoRA不到 1%12GB接近 LoRA显存紧张,消费级显卡
Adapter约 1%24GB略低于 LoRA多任务切换
Prefix Tuning不到 0.1%16GB一般简单任务、快速验证

原则:数据少于 1 万条用 LoRA,数据多、预算足才考虑全量。QLoRA 是 LoRA 的省钱版:底座先 4-bit 量化再训 LoRA,24GB 显存能训 13B,效果损失很小。

二、LoRA 原理:为什么只训一点就够

LoRA 的核心思想是:微调时权重的变化量是低秩的。也就是说,不用改整个权重矩阵,只需要在旁边加两个小矩阵(A 和 B),它们的乘积就是权重变化量。训练时冻结原模型,只训 A 和 B;推理时把 A×B 加回原权重,速度不受影响。

  • r(rank,秩):小矩阵的维度,16 是常用值。任务复杂可以加到 32,简单任务 8 就够。r 越大参数越多,效果越好也越容易过拟合。
  • alpha:缩放系数,通常设成 r 的 2 倍(r=16 时 alpha=32)。实际学习率效果是 alpha/r 倍,别只调学习率不看 alpha。
  • target_modules:LoRA 加在哪些层上。通常加在注意力层的 q_proj 和 v_proj,有时加上 k_proj、o_proj 和 MLP 层。加得越多效果越好,参数也越多。
  • 合并:训完把 LoRA 权重合并回底座,得到一个完整的模型文件,部署时和普通模型一样用,不用额外加载 LoRA。

三、数据准备:质量比数量重要

[
  {
    "instruction": "把这段运维报错翻译成处理步骤",
    "input": "FATAL ERROR: Function otb_init() cannot be called multiple times.",
    "output": "该初始化函数在同一进程内被调用了两次。请检查测试平台,确认 init 只在仿真开始时调用一次,排查重复调用点。"
  },
  {
    "instruction": "解释这个命令的作用",
    "input": "nvidia-smi --query-gpu=memory.used --format=csv",
    "output": "查询 GPU 的显存使用量,输出为 CSV 格式,适合脚本解析和监控采集。"
  }
]

指令数据三件套:instruction(任务说明)、input(输入)、output(期望输出)。数据量不用大,几百到几千条高质量样本就能见效;数据质量差,越多越糟。

  • 先人工写标杆:先人工写 100 条标杆样本,定下格式和质量标准,再用模型辅助扩写,最后人工抽查。标杆质量决定整批数据的上限。
  • 清洗:去掉重复、格式错乱、答案错误的样本。数据里混进错误答案,模型会把错误学进去。
  • 多样性:覆盖主要场景,别全是同一种问题。场景覆盖不全,模型在没见过的场景上表现会断崖式下跌。
  • 数据版本:数据和模型版本对应,留好记录。业务变了数据要更新,模型要定期重训,没有版本记录就不知道哪版数据训出了哪版模型。

四、训练实战(LLaMA-Factory)

# 装 LLaMA-Factory
git clone https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e ".[torch,metrics]"

# 准备数据:放进 data 目录,格式是 Alpaca 式 JSON
# 在 dataset_info.json 里注册数据集
# {
#   "my_ops_data": {
#     "file_name": "my_ops_data.json",
#     "formatting": "alpaca"
#   }
# }

# 启动训练(单卡 4090,7B 模型,LoRA)
CUDA_VISIBLE_DEVICES=0 python src/train.py \
  --stage sft \
  --model_name_or_path Qwen/Qwen2.5-7B-Instruct \
  --dataset my_ops_data \
  --finetuning_type lora \
  --lora_rank 16 \
  --lora_alpha 32 \
  --lora_target q_proj,v_proj \
  --output_dir output/lora-qwen2.5-7b \
  --per_device_train_batch_size 4 \
  --gradient_accumulation_steps 4 \
  --num_train_epochs 3 \
  --learning_rate 1e-4 \
  --lr_scheduler_type cosine \
  --warmup_ratio 0.03 \
  --bf16 True \
  --logging_steps 10 \
  --save_steps 100
超参常用值说明
learning_rate1e-4 到 2e-4LoRA 比全量微调大一个数量级,只训一小部分参数
num_train_epochs3数据少就多跑两轮,配合验证集看,别硬跑
lora_rank16任务复杂加到 32,简单任务 8 就够
per_device_train_batch_size4显存不够就降,用梯度累积补
gradient_accumulation_steps4等效 batch = 4 × 4 = 16
warmup_ratio0.03前 3% 的步数线性升温,防一开始 loss 炸掉
lr_scheduler_typecosine余弦衰减,收尾平滑

五、显存优化:组合拳

# 显存不够时的组合拳
# 1. 梯度检查点:拿时间换显存,重算代替存储
#    --gradient_checkpointing True
#    显存省 30-50%,训练慢 20-30%
# 2. 8-bit 优化器:AdamW 换成 adamw_bnb_8bit
#    优化器状态显存省 75%,效果几乎不变
# 3. 梯度累积:batch_size 降到 2,accumulation 提到 8
#    等效大 batch,显存占用小
# 4. 量化底座:4-bit 量化加载底座再训 LoRA(QLoRA)
#    24GB 显存能训 13B,消费级显卡也能玩
# 5. Flash Attention:注意力计算优化
#    显存和速度都受益,长上下文必开

# 显存估算(7B LoRA,BF16)
# 权重 14GB + LoRA 参数 0.1GB + 梯度 0.1GB
# + 优化器状态(8-bit)约 2GB + 激活(看序列长度和 batch)
# 总计约 20GB 左右,24GB 的 4090 刚好能跑

# 看训练时的显存占用
nvidia-smi --query-gpu=memory.used,memory.total --format=csv -l 2

六、训练监控:loss 曲线会说话

  • loss 正常下降:前几十步快速下降,之后缓慢下降,最后趋平。loss 不降先看学习率是不是太小,loss 炸掉先看学习率是不是太大。
  • 过拟合判断:训练 loss 一直降,验证 loss 开始升,就是过拟合了。数据少、epoch 多容易过拟合,解决办法是加数据、降 epoch、加 dropout。
  • checkpoint:每 100 步存一个 checkpoint,训坏了能回滚。别只存最后一个,中间某个 checkpoint 可能效果最好。
  • 监控工具:接 W&B 或者 TensorBoard,loss 曲线、学习率、显存占用都能看,出了问题第一时间发现。

七、评估:能不能上线,评估说了算

  • 评估集设计:准备 100 到 200 条测试问题,覆盖主要场景,人工写标准答案。评估集和训练集不能重叠,重叠了等于开卷考试。
  • 对比基线:微调前后都跑一遍评估集,看准确率和格式遵守率提升了多少。只看微调后的分数没有意义,要看提升。
  • 人工评测:自动指标只能看格式和关键词,语义对不对要人工看。抽 50 条人工打分,通过率 90% 以上再上线。
  • A/B 测试:上线后先切 10% 流量到新模型,对比用户反馈,没问题再全量。

八、合并与部署

# 合并 LoRA 权重回底座(LLaMA-Factory)
python src/export_model.py \
  --model_name_or_path Qwen/Qwen2.5-7B-Instruct \
  --adapter_name_or_path output/lora-qwen2.5-7b \
  --template qwen \
  --finetuning_type lora \
  --export_dir output/merged-qwen2.5-7b \
  --export_size 5 \
  --export_device cpu

# 部署:用 vLLM 跑合并后的模型
python -m vllm.entrypoints.openai.api_server \
  --model output/merged-qwen2.5-7b \
  --gpu-memory-utilization 0.9 \
  --port 8000

# 多 LoRA 切换:不合并也行,vLLM 支持动态加载多个 LoRA
# --enable-lora --max-lora-rank 16 --lora-modules ops=output/lora-qwen2.5-7b
# 一个底座挂多个 LoRA,按请求切换,省显存

九、常见问题

  • loss 不降:先看学习率,再看数据格式是不是对的,最后看模型是不是加载错了。数据格式错是最常见的,Alpaca 格式的字段名错一个字,训练就白跑。
  • 灾难性遗忘:新技能学会了,旧本事忘了。解决办法是训练数据里混一部分通用数据,或者降低学习率、减少 epoch。
  • 格式漂移:微调后模型输出格式乱了,不按要求的格式回。原因是训练数据格式不统一,先统一格式再训。
  • 过拟合:训练集上表现很好,测试集上很差。加数据、降 epoch、加 dropout、早停,四招一起上。

微调就三句话:数据质量决定上限,LoRA 决定成本,评估决定能不能上线。先小规模验证,再放大投入,别一上来就全量训大模型。

发表回复

后才能评论