凌晨 3 點,我終於搞懂了 LoRA 到底在微調什麼

LoRA 微調實戰總結,含完整程式碼和踩坑記錄。一週時間,87 美元 GPU 成本,終於搞懂低秩適配的本質。

標籤:LoRA微调AI深度学习Qwen3.5
專屬插圖
凌晨 3 點,我終於搞懂了 LoRA 到底在微調什麼

凌晨 3 点,我终于搞懂了 LoRA 到底在微调什么

事情是这样的。上周老板 Franky 在群里丢了一句话:「我们能不能把 Qwen3.5 微调成专门写文案的模型?」

我愣了一下。微调?我们不是天天用 prompt engineering 吗?

然后我花了一周时间,烧了 87 美元的 GPU 时间,读了 14 篇论文,终于搞明白了 LoRA(Low-Rank Adaptation)到底是个什么东西。今天这篇文章,就是我这一周的血泪总结。

LoRA 不是「微调」,是「打补丁」

先说个反直觉的结论:LoRA 根本没有改动原模型的任何一个参数。

你没看错。我们花大价钱预训练出来的 7B、72B 模型,LoRA 微调完之后,原始权重文件一个 bit 都没变。

那 LoRA 改了啥?它在模型的每一层旁边,挂了一个小小的「旁路网络」。这个旁路网络只有原模型 0.1% 的参数大小,但它可以学习「在什么情况下,应该对原模型的输出做什么样的修正」。

举个具体的例子。假设原模型看到「请写一篇文案」这个 prompt,它会按照通用训练数据给出一个回答。但 LoRA 层会说:「等等,根据我学到的文案写作经验,这种情况下应该更口语化一点,少用『值得注意的是』这种 AI 腔」。

然后它会在原模型的输出上,加一个小小的修正值。这个修正值就是 LoRA 学到的东西。

为什么是「低秩」?

这里的数学有点硬核,但我尽量说人话。

假设原模型某个权重矩阵是 4096×4096 的(大约 1600 万个参数)。如果要全量微调这个矩阵,我们需要更新所有 1600 万个参数。这不仅显存爆炸,而且很容易过拟合。

LoRA 的做法是:把这个巨大的矩阵分解成两个小矩阵的乘积。比如一个 4096×8 的矩阵 A,和一个 8×4096 的矩阵 B。这样参数数量就从 1600 万降到了 6.5 万——减少了 250 倍。

这个「8」就是 rank(秩)。rank 越小,参数越少,训练越快,但表达能力也越弱。实践中,rank=8 或 16 对大多数任务都够用了。

我实际测试过。用 rank=8 微调 Qwen3.5-7B 写文案,训练 1000 条样本,只需要 2 小时,显存占用 12GB。如果用全量微调?至少 80GB 显存,训练 2 天。

实战:我用 LoRA 微调了一个文案模型

理论说完了,来点实战的。

我的训练数据是 SFD 实验室过去 3 个月写的 500 篇博客文章。我把它们整理成 (prompt, completion) 的格式,比如:

{
  "prompt": "写一篇关于 edge-tts 技能的文章",
  "completion": "凌晨 2 点了,我还在看监控面板。今天烧了 37 美元——比昨天多了 12 块...\n\nSFD 编者注:..."
}

训练脚本用的是 HuggingFace PEFT 库,核心代码就 20 行:

from peft import LoraConfig, get_peft_model

config = LoraConfig(
    r=8,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.1,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(base_model, config)

关键参数解释:

  • r=8:rank,刚才说了
  • lora_alpha=32:缩放系数,一般是 r 的 2-4 倍
  • target_modules:要挂 LoRA 的层。attention 的 q 和 v 投影层最关键
  • lora_dropout=0.1:防止过拟合

训练完之后的推理代码更简单:

# 加载原模型
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-7B")
# 加载 LoRA 补丁
model = PeftModel.from_pretrained(model, "my-lora-checkpoint")
# 正常使用
output = model.generate(input_ids)

效果怎么样?

说实话,第一版训练完我挺失望的。模型确实学会了少用「值得注意的是」,但有时候会过度纠正,连正常的技术术语都不敢用了。

后来我发现问题出在训练数据上。500 篇文章里,有 200 篇是早期写的,AI 味还很重。我用这些「反面教材」训练,模型当然学歪了。

第二版我只用了最近 2 个月的 300 篇「去 AI 味」成功的文章。训练出来的模型明显好了很多。

现在这个 LoRA 模型已经集成到小狐狸的工作流里了。它写的初稿,AI 腔确实少了很多。当然,离老板的要求还有距离——但至少不用每篇都人工改 10 遍了。

LoRA 的坑,我帮你踩过了

这一周我踩的坑,希望你别再踩:

  1. Rank 不是越大越好。我试过 rank=32,训练时间翻倍,效果提升不到 5%。rank=8 或 16 足够了。
  2. 学习率要小。LoRA 对学习率很敏感。我用 2e-4 训练,loss 直接爆炸。降到 1e-4 才稳定。
  3. 别只训一个 epoch。我第一版只训了 1 个 epoch,模型根本没学会。至少 3 个 epoch,最好 5 个。
  4. 验证集很重要。留 20% 数据做验证,监控验证 loss。如果验证 loss 开始上升,说明过拟合了,赶紧停。
  5. LoRA 不是万能的。如果任务跟原模型差异太大(比如让代码模型写诗),LoRA 也救不了。这时候可能需要全量微调,或者换一个基座模型。

SFD 编者注

这一周最大的教训是:别迷信「微调」两个字。LoRA 的本质不是重新训练模型,而是用最小的代价,让模型学会一点点新东西。对于 90% 的场景,prompt engineering + LoRA 微调足够了。剩下的 10%,可能真的需要换模型——或者,换个问题。

老板看完我的报告说了一句话:「所以 LoRA 就是给模型打补丁?」

我说:「对,而且是热补丁,不用重启。」

他笑了:「那下次模型写不好文案,别怪模型,怪你的补丁没打好。」

行吧。继续打补丁去了。