在ch10监督微调章节中提到了LoRALinear的实现。 ``` import torch import torch.nn as nn class LoRALinear(nn.Module): def __init__(self, original: nn.Linear, r: int = 8, alpha: int = 16): super().__init__() self.original = original self.original.weight.requires_grad_(False) # 冻结 W out_dim, in_dim = original.weight.shape self.A = nn.Parameter(torch.randn(r, in_dim) * 0.01) # 这里的0.01的作用未提及。 self.B = nn.Parameter(torch.zeros(out_dim, r)) self.scale = alpha / r def forward(self, x): base_out = self.original(x) # Wx lora_out = (x @ self.A.T) @ self.B.T # xA^T B^T = (BA)x 的等价写法 return base_out + self.scale * lora_out ``` 然后文章下面花了一点篇幅讲解为啥`self.B`需要使用全0初始化,是因为`BAx=0` → 训练起点等价原模型,避免一开始就破坏 pretrain。A 必须非零(Kaiming 类小方差初始化),否则 B 的梯度`∂L/∂B = (∂L/∂y)·(Ax)^T`含`Ax=0`因子 → B 永远学不动。两个都为 0 时双向卡死:`B=0` 让 A 的梯度 `B^T·(∂L/∂y)·x^T` 也为 0。所以 LoRA 选 "`B=0 + A 随机`" 这一组合:起点等价 base,且 B 第一步就有非零梯度。 但是没有讲解为啥`self.A`需要乘一个0.01,本质上是因为`torch.randn(r, in_dim)`是初始化一个`[r, in_dim]`的张量,生成来自标准正态分布(均值为 0,方差为 1 的高斯分布),乘上0.01之后,就变成了均值依然为0,但是方差变成`0.01^2=1e-4`的高斯分布的张量了,这样子同样能够起到降低初始训练对于原模型的影响,让模型起到一个平滑扰动的起始微调,和B的全0初始化的作用类似。 建议补充在原始文章中。
Activity
yangfch3 commentedon Sep 14, 2026
已补,谢谢 @jcchan23 健文大神~