训练 · 预训练之后

LoRA

冻结权重旁边的低秩适配器

这里
秩 4 Wqkv × 12 个块
可训练
147,456 0.11%
模型
GPT-2 small

全部步骤

  1. 冻结 W,学习 A · B

    微调让权重矩阵改变某个 ΔW。LoRA 把 W 冻结,把 ΔW 学成两个细长矩阵的乘积 A · B,中间是 r 列。B 从零开始,所以训练从未改变的模型出发。

    W′ = W + A · B · 秩 r

  2. 训练的权重有多少

    这里:在 GPT-2 的 12 个块中,每块的注意力输入矩阵上加秩 4 适配器,共 147,456 个可训练权重,占模型的 0.11%。只有它们需要梯度和优化器状态。

    1.37 亿个权重中的 147,456 个

  3. 一次真实运行

    一次真实运行:用两句话教 GPT-2 small,东西都放在 “on the moon”。LoRA 和全量微调都把损失压到接近零;LoRA 需要更多步。在一句未见过的句子上,“The bird sang on the … moon” 从 0.75% 变成 100%:它学会了这个模式,而且学过头了。

    损失 4.85 → 0.12(LoRA)· 0.12(全量)

  4. 真实的变化是低秩的吗?

    LoRA 押注于微调所需的变化是低秩的。对这个窄任务确实如此:秩 4 就容纳了全量微调对一个矩阵所做变化的 70%。Qwen3 广泛的后训练则不然:秩 16 只容纳了它对一个查询矩阵所做变化的 9.5%。

    窄任务:低秩 · 广泛训练:不是

  5. 合并,或切换

    训练结束后,可以把 A · B 一次性加进 W,模型运行速度和以前完全一样。也可以让适配器保持独立:一个冻结的基座模型可以服务许多小适配器,每个客户或任务一个,按请求切换。

    W′ = W + A·B,或一个 W 上挂多个 A·B

代码

class LoRALinear(nn.Module):
    def __init__(self, base, r=4):
        self.base = base.requires_grad_(False)                          # W stays frozen
        self.A = nn.Parameter(torch.randn(base.in_features, r) * 0.02)
        self.B = nn.Parameter(torch.zeros(r, base.out_features))       # ΔW = A·B starts at 0
    def forward(self, x):
        return self.base(x) + (x @ self.A) @ self.B
# after training: merge once, no extra cost
base.weight.data += (A @ B).T

延伸阅读