训练 · 预训练之后
- 这里
- 秩 4 Wqkv × 12 个块
- 可训练
- 147,456 0.11%
- 模型
- GPT-2 small
全部步骤
冻结 W,学习 A · B
微调让权重矩阵改变某个 ΔW。LoRA 把 W 冻结,把 ΔW 学成两个细长矩阵的乘积 A · B,中间是 r 列。B 从零开始,所以训练从未改变的模型出发。
W′ = W + A · B · 秩 r训练的权重有多少
这里:在 GPT-2 的 12 个块中,每块的注意力输入矩阵上加秩 4 适配器,共 147,456 个可训练权重,占模型的 0.11%。只有它们需要梯度和优化器状态。
1.37 亿个权重中的 147,456 个一次真实运行
一次真实运行:用两句话教 GPT-2 small,东西都放在 “on the moon”。LoRA 和全量微调都把损失压到接近零;LoRA 需要更多步。在一句未见过的句子上,“The bird sang on the … moon” 从 0.75% 变成 100%:它学会了这个模式,而且学过头了。
损失 4.85 → 0.12(LoRA)· 0.12(全量)真实的变化是低秩的吗?
LoRA 押注于微调所需的变化是低秩的。对这个窄任务确实如此:秩 4 就容纳了全量微调对一个矩阵所做变化的 70%。Qwen3 广泛的后训练则不然:秩 16 只容纳了它对一个查询矩阵所做变化的 9.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