BlogLoRA
A pedagogical primer · 2026 · 05

从 SFT 到 LoRA

预训练让大模型变成"博学的实习生"——它懂很多,却不知道在你这儿该怎么干活。 监督微调(SFT)教它怎么干, LoRA 则让你用一张消费级显卡就能完成这件事。

预设读者:基础 ML、基础高数、基础线代。完全没碰过微调也行——我们从动机讲起。

10 节 · 5 个手写交互演示 · 全部数据点附原始来源 · 最后更新 2026-05-19

10000×10\,000\times可训练参数减少
3×3\timesGPU 显存节省
35 MB取代 350 GB 的 checkpoint
00 ms推理延迟(可合并)

为什么要再微调

把训练一个 LLM 类比成培养一个员工,大致长这样:

  1. 预训练 (Pretraining) — 像让 TA 读完整个互联网:百科、小说、代码、论文。得到一个什么都懂一点的通才。
  2. 监督微调 (SFT) — 像入职培训:用"问题 → 标准答案"的对子告诉 TA「我们公司客服就是这么说话的」。
  3. 偏好对齐 (RLHF / DPO) — 像绩效反馈:在多个答案里告诉 TA「这条比那条好」,磨细风格与安全性。
  • Pretrain
    1012\sim 10^{12} tokens
  • SFT
    104 ⁣ ⁣106\sim 10^{4}\!-\!10^{6}
  • RLHF / DPO
    104 ⁣ ⁣105\sim 10^{4}\!-\!10^{5} 偏好

本文关心第 2 步。LoRA 不是另一种新的微调流派——它是让"第 2 步"变得便宜的实现技巧

PART ISFT 的本质

§1.1SFT 到底是什么

SFT 用的损失函数和预训练一模一样——都是 next-token 交叉熵:

LSFT  =  tresponselogPθ ⁣(yt|y<t,  x)\mathcal{L}_{\text{SFT}} \;=\; - \sum_{t \,\in\, \text{response}} \log P_\theta\!\left(y_t \,\middle|\, y_{\lt t},\; x\right)

关键差别只有两点:

  • 数据少而精:从万亿 token 的网络爬虫,缩到几千到几十万对人工写好的 (指令, 答案)
  • 只对答案算 loss:把 prompt 部分的 label 设成 100-100(PyTorch 的 ignore_index),让模型学怎么答,而不是学怎么问
Demo 1 · Loss-mask 可视化
参与 loss:0 tokens
被 mask:0 tokens

鼠标悬停每个 token 看注释。灰色 + 删除线= 被 mask(不计 loss); 绿色 = 参与 cross-entropy。

python
labels = input_ids.clone()
# Mask the prompt portion so CE loss skips it
labels[: prompt_len] = -100

常见 SFT 数据格式 · Alpaca 风

json
{
  "instruction": "用一句话解释什么是低秩矩阵。",
  "input": "",
  "output": "低秩矩阵的列空间维度远小于其形状所允许的最大维度。"
}

Stanford Alpaca 用 52K 条这样的样本就把 LLaMA 7B 调出了类 ChatGPT 风格。 [source]

§1.2把全部参数都改一遍?显存先崩

NN 参数的模型做全参数 SFT,每个可训练参数大约需要:

  • 22 字节存权重(fp16 / bf16)
  • 22 字节存梯度
  • 88 字节存 Adam 的两个动量 m,vm, v(fp32)

合计 1616 字节 / 参数。再加上前向激活、KV cache、梯度累积,实际显存往往是参数量的 2020

结果:7B7\mathrm{B} 模型全参 SFT 已经吃掉一张 A100 80G,175B175\mathrm{B} 直接劝退个人玩家

Demo 2 · 显存估算器
权重
梯度
优化器
激活/其他

预估峰值显存: GB

滑动模型规模与优化器选项,看四类显存如何堆起来。
PART IILoRA 的核心

§2.1关键直觉 · 微调改的东西本来就"很瘦"

线性代数里,一个 d×kd \times k 矩阵的秩 (rank) 等于它的列向量张成空间的维度。秩越低,这个矩阵"压缩"得越狠——把整个空间压成一个低维子空间。

论文实验显示,在 175B175\mathrm{B} 的 GPT-3 上,把 ΔW\Delta W 约束到 1122 都几乎不掉点。 [Hu et al. 2021]

Demo 3 · 低秩重构
Demo 3 · 低秩重构Demo 3 · 低秩重构
原始 WW秩-rr 近似压缩率 重构误差
滑动秩 rr,左边是原始的 64×6464 \times 64 "权重图",右边是只保留前 rr 个奇异值后的重构。

§2.2LoRA 的数学 · 把 ΔW\Delta W 拆成两小块

LoRA 把前向公式改写成:

h  =  W0x  +  ΔWx  =  W0x  +  αrBAxh \;=\; W_0\, x \;+\; \Delta W\, x \;=\; W_0\, x \;+\; \frac{\alpha}{r}\, B\,A\, x
W0Rd×kW_0 \in \mathbb{R}^{d\times k} 预训练权重,训练时冻结ARr×kA \in \mathbb{R}^{r\times k} 高斯初始化BRd×rB \in \mathbb{R}^{d\times r} 零初始化 \Rightarrow 起步时 BA=0BA = 0,不扰动原模型rmin(d,k)r \ll \min(d,\, k) 通常 4644 \sim 64α/r\alpha / r 缩放因子,作用类似学习率

它在做什么

原本你要训练一个 d×kd \times k 的"修改量" ΔW\Delta W,这是 dkdk 个参数。LoRA 说:

参数量从 dkdk 变成 r(d+k)r(d+k)。当 d=k=4096,  r=8d = k = 4096,\; r = 8 时:

  • 原本:4096×4096=167772164096 \times 4096 \,=\, 16\,777\,216
  • LoRA:8×(4096+4096)=655368 \times (4096 + 4096) \,=\, 65\,536
  • 缩减 256×256\times(仅一层;模型有上百层,整体缩减更夸张)
Demo 4 · 参数节省计算器
全参 ΔW\Delta W
LoRA BABA

节省倍数:

滑动 d,k,rd, k, r,实时看 LoRA 占了全参的多大比例。

形状一眼看懂

W0W_0d×kd \times k
++
BBd×rd \times r
×\times
AAr×kr \times k

BB 又高又瘦,AA 又扁又长——它们的乘积是一个秩最多为 rr 的"瘦扁"矩阵 ΔW\Delta W

§2.3训练时冻结,推理时合并

LoRA 与 Adapter 类方法最大的区别在于推理时可以合并权重,不引入任何额外延迟。

① 训练阶段

W0W_0
冻结
BB
AA
  • 梯度只流过 A,BA, B
  • 优化器只为 A,BA, B 维护动量
  • 显存峰值 35×\approx 3 \sim 5\times 小于全参 SFT

② 部署 · 保留 adapter

W0W_0
++
αrBA\frac{\alpha}{r}BA
adapter
  • 不同任务 → 不同 LoRA 文件(10 ⁣ ⁣100\sim 10\!-\!100 MB)
  • 一个 base + N 个 adapter
  • 切任务零成本(换文件即可)

③ 部署 · 合并权重

W=W0+αrBAW' = W_0 + \frac{\alpha}{r}BA
  • BABA 加回 W0W_0,得到普通的 Transformer
  • 推理延迟和原模型完全一样
  • 代价:失去多任务切换的灵活性
PART III上手实战

§3.1动手 · 20 行 PEFT + TRL

来源:HF PEFT 文档 · TRL SFTTrainer · QLoRA

python
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTTrainer, SFTConfig

dataset = load_dataset("tatsu-lab/alpaca", split="train")

peft_config = LoraConfig(
    r=8,                                  # 秩,常用 8/16/32
    lora_alpha=16,                        # alpha = 2r 经验法则
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=["q_proj", "v_proj"],  # 或 "all-linear"
)

trainer = SFTTrainer(
    model="meta-llama/Llama-3.2-1B",
    train_dataset=dataset,
    args=SFTConfig(
        output_dir="lora-alpaca",
        num_train_epochs=1,
        per_device_train_batch_size=2,
        learning_rate=2e-4,               # LoRA 用 ~10x 于全参微调
    ),
    peft_config=peft_config,
)
trainer.train()
python
import torch
import torch.nn as nn

class LoRALinear(nn.Module):
    def __init__(self, base: nn.Linear, r=8, alpha=16):
        super().__init__()
        self.base = base
        for p in self.base.parameters():
            p.requires_grad = False            # freeze W0
        d, k = base.out_features, base.in_features
        self.A = nn.Parameter(torch.randn(r, k) * 0.01)  # Gaussian init
        self.B = nn.Parameter(torch.zeros(d, r))         # zero init -> BA = 0
        self.scale = alpha / r

    def forward(self, x):
        return self.base(x) + self.scale * (x @ self.A.T @ self.B.T)
python
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import prepare_model_for_kbit_training, LoraConfig, get_peft_model
import torch

bnb = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",                # NF4 quantization
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,           # extra savings
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.2-8B", quantization_config=bnb)
model = prepare_model_for_kbit_training(model)
model = get_peft_model(model, LoraConfig(
    r=16, lora_alpha=32,
    target_modules="all-linear",
    task_type="CAUSAL_LM"))

# 8B model + QLoRA fits on a single 16 GB consumer card.

§3.2超参数 · 七寸在哪

r

最重要的旋钮。8/16/328 / 16 / 32 是 90% 任务的最佳点。

rr 不是越大越好——研究发现 r=256r = 256 偶尔最优,但 r=8r = 8 已经能拿到 95%\ge 95\% 的性能。

Raschka 实验

α alpha

默认规则:α=2r\alpha = 2r

α/r\alpha/r 控制 LoRA 对原模型的"影响强度"。把 α\alpha 翻倍 \approx 把学习率翻倍。 想要不依赖 rr 的稳定尺度,可用 RSLoRA 的 α/r\alpha/\sqrt{r}

target 目标层

原论文只动 Wq,WvW_q, W_v。 实践派推荐 all-linearq,k,v,oq, k, v, o 加 MLP 全套。

覆盖全部线性层效果最佳,代价是 LoRA 参数 ×45\times 4 \sim 5

lr 学习率

LoRA 通常 1e43e41\mathrm{e}{-4} \sim 3\mathrm{e}{-4}(比全参 SFT 高 1010 倍)。

因为只有 BABA 在更新,原模型不会被冲坏,可以"放心上大 lr"。RL 阶段(DPO/GRPO)需降到 5e65\mathrm{e}{-6}

dropout

典型 0.00.10.0 \sim 0.1,常见 0.050.05

小数据集多加一点防过拟合,大数据集设 00 也行。

🍳 推荐入门配方

yaml
r:            16
lora_alpha:   32
lora_dropout: 0.05
target:       all-linear
lr:           2e-4
epochs:       1-3
batch:        越大越好(用 grad-accum)
PART IV生态

§4.1LoRA 大家族

从 2021 的 LoRA 出发,一个迅速膨胀的工具谱系。每张卡一句话讲清卖点,详细看论文。

QLoRA

2023 · Dettmers

把 base 模型量化到 44-bit NF4,再在上面跑 LoRA。6565B 模型塞进单张 4848GB 显卡。

论文 →

DoRA

2024 · Liu (ICML Oral)

把权重拆成 方向 ++ 幅度,只对方向做 LoRA。准确率显著高于 LoRA,零推理开销。

论文 →

LoRA+

2024 · Hayou

BBAA162416 \sim 24的学习率,理论推导支持,约 2×2\times 训练速度。

论文 →

VeRA

2024 · Kopiczko

A,BA, B 全部 冻结成随机矩阵,只训两个小缩放向量。参数量再减一个数量级。

论文 →

AdaLoRA

2023 · Zhang

用 SVD 形式参数化,按重要性自适应分配 rr 给不同层,预算相同时质量更高。

论文 →

RSLoRA

2023 · Kalajdzievski

把缩放因子从 α/r\alpha/r 改成 α/r\alpha/\sqrt{r},使大 rr 时训练更稳定。

PEFT 实现 →

§4.2一句话总结

🧠 该带走的 5 个事实

  1. SFT 与预训练共享同一个损失,但只在 response token 上算。
  2. 全参数 SFT 显存 \approx 参数量 ×1620\times\, 16 \sim 20 字节,根本吃不消。
  3. LoRA 假设 ΔW\Delta W 是低秩的,所以分解为 BABA,只训 r(d+k)r(d+k) 个数。
  4. BB 零初始化保证训练起点等价于原模型,安全无扰动。
  5. 推理时可以合并权重,零延迟代价

📚 想再深入

输入关键词,全站正文即刻可搜(中文分词友好)。
    ↑↓ 选择 · Enter 打开 · Esc 关闭Pagefind