LoRA rank=16 数学直觉:不是更新 16 个维度,而是用 16 个方向生成完整增量

从矩阵秩、外积、低秩分解和瓶颈层出发,用通俗数学解释 LoRA 里的 rank=16 到底是什么意思,为什么它能影响所有输出维度,以及为什么常在 8/16/32/64 之间取值。

上一篇 LoRA 机制学习笔记 讲了 LoRA 怎么插进大模型的 Transformer 层。

这篇专门回答一个更细的问题:

LoRA 里 rank=16 到底是什么意思?
是不是只更新大张量里的 16 个维度?
为什么偏偏取 16?

先给结论:

rank=16 不是说只改 16 个坐标,也不是说模型只剩 16 维。它说的是:这次对某个大权重矩阵的更新,只允许由 16 个独立的低秩方向组合出来。最后生成的更新量仍然可以影响所有输出维度。

最重要的公式是:

xAB = Σ (x · a_i) b_i

这句话可以翻译成:

用 16 个输入方向做检测,
再用 16 个输出方向做响应,
最后组合成一个完整维度的更新。

读懂这句,LoRA 的 rank 基本就通了。

1. 先把场景固定住

假设大模型某一层里有一个线性变换:

y = xW

其中:

x: 1 × 4096
W: 4096 × 4096
y: 1 × 4096

这表示当前 token 的 hidden state 是一个 4096 维向量,经过 W 这个大矩阵,输出仍然是 4096 维。

如果 full fine-tuning,要直接更新整个 W

W' = W + ΔW

其中:

ΔW: 4096 × 4096

完整的 ΔW 有:

4096 × 4096 = 16,777,216 个参数

这太大了。

LoRA 的做法是,不直接学完整 ΔW,而是让:

ΔW = AB

如果 rank 取 16:

A: 4096 × 16
B: 16 × 4096

参数量变成:

4096 × 16 + 16 × 4096 = 131,072

从 1677 万降到 13 万左右。

但注意,AB 的结果仍然是:

AB: 4096 × 4096

所以它不是只更新 16 个位置,也不是只输出 16 维。它仍然生成一个完整形状的矩阵更新,只是这个更新矩阵被限制成低秩。

2. rank 是什么:一个矩阵里有多少个独立方向

数学上,矩阵的 rank 可以粗略理解成:

这个矩阵真正包含多少个独立方向

举一个简单例子。

如果一个二维变换可以把平面里的点任意变到另一个二维平面,它 rank 可能是 2。

如果它无论怎么变,最后都把所有点压到一条直线上,它 rank 就是 1。

如果它把所有点都压成 0,它 rank 就是 0。

所以 rank 不是矩阵有多少行、多少列,而是这个矩阵实际能展开多少个独立变化方向。

对于 LoRA:

ΔW = AB

有一个基本结论:

rank(AB) <= min(rank(A), rank(B))

而:

A: 4096 × 16
B: 16 × 4096

中间只有 16 维,所以:

rank(ΔW) = rank(AB) <= 16

这就是 rank=16 的数学含义:

这个 4096 × 4096 的更新矩阵,最多只有 16 个独立变化方向。

它不是小矩阵,而是一个大矩阵受到低秩约束。

3. 不是更新 16 个维度,而是通过 16 个方向更新所有维度

你的问题非常关键:

是不是更新大张量的 16 个维度?

答案是:不是。

如果只更新 16 个维度,那会像这样:

4096 个输出坐标里,只动第 1、7、23、... 这 16 个
其他 4080 个完全不动

但 LoRA 不是这样。

LoRA 的输出增量是:

δy = xAB

其中:

δy: 1 × 4096

这个 δy 是完整 4096 维向量,每个输出维度都可能变化。

真正被限制的是:这个 4096 维变化不能任意乱变,而必须来自 16 个基础响应方向的组合。

可以类比成调光台:

4096 个灯泡 = 输出维度
16 个旋钮 = rank=16 的中间通道

每个旋钮不是控制一个灯泡,而是控制一种“灯光模式”。一种灯光模式可以同时影响很多灯泡。

所以:

不是:16 个旋钮控制 16 个灯泡
而是:16 个旋钮控制 16 套灯光模式,每套模式都能影响 4096 个灯泡

最后 16 套模式叠加,4096 个灯泡都可能发生变化。

4. 最关键公式:xAB = Σ (x·a_i) b_i

为了看清楚 rank=16 真正在做什么,可以把 AB 拆开。

设:

A = [a_1, a_2, ..., a_16]

也就是 A 有 16 个列向量,每个 a_i 都是 4096 维。

再把 B 看成 16 个行向量:

B =
  b_1
  b_2
  ...
  b_16

每个 b_i 都是 4096 维输出方向。

那么:

xAB = (xA)B

第一步:

xA = [x·a_1, x·a_2, ..., x·a_16]

这一步把 4096 维输入压成 16 个数字。

每个数字 x·a_i 可以理解成:

当前 hidden state 和第 i 个检测方向有多匹配

第二步:

(xA)B = (x·a_1)b_1 + (x·a_2)b_2 + ... + (x·a_16)b_16

也就是:

xAB = Σ (x · a_i) b_i

这就是 LoRA rank 的核心。

每个 a_i 是一个输入检测方向:

这个 hidden state 里有没有某种特征?

每个 b_i 是一个输出响应方向:

如果检测到这种特征,应该如何改变输出 hidden state?

rank=16 表示有 16 套这样的“检测方向 + 响应方向”。

不是 16 个维度,而是 16 个低秩调节模式。

5. 外积视角:一个大矩阵由 16 张 rank-1 图案叠出来

还有一个等价视角。

因为:

ΔW = AB

可以展开成:

ΔW = a_1 b_1 + a_2 b_2 + ... + a_16 b_16

这里每一项 a_i b_i 是一个外积,形状是:

4096 × 4096

但每个外积本身 rank 只有 1。

所以 rank=16 的 LoRA 更新可以理解成:

用 16 张 rank-1 的大图案,叠出一张 4096 × 4096 的更新图案。

这和图片压缩很像。

一张复杂图片如果允许每个像素自由变化,自由度很高。但很多图片可以由少量基础图案叠出来:

图案 1:整体亮度
图案 2:横向边缘
图案 3:纵向边缘
图案 4:某种纹理
...

rank 越大,可以叠的基础图案越多,表达能力越强,但参数也越多。

LoRA 就是在说:

这次微调不需要一张完全自由的 4096 × 4096 更新图,
用 16 张基础更新图叠一下,可能已经够用了。

6. 为什么不是 rank=1?

rank=1 表示:

ΔW = a_1 b_1

也就是只有一套:

输入检测方向 a_1
输出响应方向 b_1

这太弱了。

它只能表达一种调节模式:

如果输入像 a_1,就往 b_1 方向推

但真实任务通常不止一种情况。

比如一个“日志报警”函数可能至少需要识别:

服务宕机
安全告警
支付失败
数据库连接耗尽
用户影响面很大
只是普通 debug 日志

一个 rank=1 的更新只有一个主方向,很难同时覆盖这些不同模式。

rank=16 给了 16 套检测-响应模式,表达能力明显更强。

7. 为什么不是 rank=4096?

rank=4096 接近完整矩阵更新。

如果:

A: 4096 × 4096
B: 4096 × 4096

参数量会变成:

4096×4096 + 4096×4096

这比直接学一个完整 ΔW 还大。

这就失去了 LoRA 的意义:

不轻量
不省显存
不容易部署多个 adapter
更容易过拟合

LoRA 的核心优势就是:用少量参数表达大部分有用更新。

所以 rank 的选择本质是一个 trade-off:

rank 太小:表达能力不够
rank 太大:参数、显存、过拟合风险上升

常见选择会在:

4 / 8 / 16 / 32 / 64

之间调。

8. 为什么常见 rank=16?

rank=16 不是数学定律,也不是某种神秘最优值。

它是一个经验上常见的折中点。

站在数学和工程之间看,它刚好平衡了三件事。

8.1 表达能力够用

rank=16 意味着每个被插 LoRA 的矩阵有 16 套独立调节模式。

对于很多任务,模型已经有很强的基础能力,adapter 只是调整方向:

输出格式
任务偏好
注意力重点
领域术语
分类边界
操作流程

这类调整往往不需要 4096 个独立方向。

8.2 参数量小很多

4096 × 4096 为例:

rankLoRA 参数量相对完整矩阵
18,1920.049%
865,5360.39%
16131,0720.78%
64524,2883.13%
409633,554,432200%

rank=16 只用不到 1% 的参数,就能给这个矩阵提供一个可训练更新。

8.3 多层叠加后总能力不止 16

还要注意:rank=16 通常不是整个模型只有 16 个方向。

LoRA 可能插在很多地方:

第 1 层 q_proj:rank=16
第 1 层 v_proj:rank=16
第 1 层 o_proj:rank=16
第 1 层 up_proj:rank=16
...
第 32 层 q_proj:rank=16
第 32 层 v_proj:rank=16
...

每个矩阵都有自己的 16 个低秩方向。

所以整个模型的调节能力不是“全局 16 个方向”,而是:

很多层 × 很多 projection × 每个 projection 16 个方向

这就是为什么单个矩阵 rank 看起来不大,整体 adapter 仍然可以表达相当复杂的行为调整。

9. 低秩假设:微调更新往往有低内在维度

LoRA 背后的一个重要假设是:

微调时真正需要改变的方向,远少于完整权重矩阵的自由度。

这可以叫低内在维度假设。

大模型原来已经学到了大量通用能力:

语言
代码
常识
推理
格式
世界知识

微调一个具体任务时,通常不是从零学习这些能力,而是:

把已有能力重新组合一下
把某些行为倾向放大
把某些错误倾向压低
把输出格式约束住

这种更新经常集中在少数方向上。

可以类比成你已经会开车,现在要适应一辆新车:

不需要重新学“交通规则 + 手眼协调 + 方向盘原理”
只需要调整刹车力度、方向盘手感、油门响应等少数习惯

LoRA 就是把这种“少数习惯调整”用低秩矩阵表达出来。

10. rank=16 对一次输入实际做了什么?

回到最关键的 forward 过程。

完整计算是:

y = xW + xAB

其中 LoRA 部分:

xAB

可以拆成两步。

第一步,压缩:

z = xA

形状:

x: 1 × 4096
A: 4096 × 16
z: 1 × 16

这里 z 是 16 个调节系数:

z_1 = x·a_1
z_2 = x·a_2
...
z_16 = x·a_16

第二步,展开:

δy = zB

形状:

z: 1 × 16
B: 16 × 4096
δy: 1 × 4096

所以一次输入经过 LoRA 时,是这样:

4096 维 hidden state
→ 提取 16 个调节信号
→ 用这 16 个信号混合 16 个输出方向
→ 得到完整 4096 维增量
→ 加回原模型输出

这就是 rank=16 最直观的执行过程。

11. 为什么它能影响所有输出维度?

因为 B 的每一行都是 4096 维。

当:

δy = z_1 b_1 + z_2 b_2 + ... + z_16 b_16

每个 b_i 都可以在 4096 个输出坐标上有非零值。

所以 δy 的每个维度都可能被影响。

约束不是:

只有 16 个输出坐标能变

而是:

4096 个输出坐标的变化,必须是 16 个输出方向的线性组合

这像调颜色。

屏幕上可能有几百万个像素,但如果你只有 3 个颜色通道 RGB,仍然能影响所有像素的颜色,只是所有颜色都要由 R/G/B 三个基础通道组合出来。

rank=16 也是类似:所有输出维度都能变,但变化方式来自 16 个基础通道。

12. rank 和“记住多少知识”不是一回事

一个常见误解是:

rank=16 是不是只能记 16 条规则?

不是。

rank 约束的是一个线性增量矩阵的独立方向数,不是知识条数。

知识在模型里不是按“一条规则一个维度”存储的。模型的行为来自很多层、很多 head、很多 MLP 神经元的组合。

一个 rank=16 adapter 可能影响:

attention 看哪里
MLP 强化什么特征
输出层更偏向哪些 token
多层 hidden state 如何逐步演化

这些变化组合起来,可以表现出比“16 条规则”复杂得多的行为。

但 rank 仍然限制了它的表达能力。它不是无限容量。任务越复杂,通常越需要更大的 rank、更多插入层,或者更多训练数据。

13. rank、alpha 和 scale 的关系

LoRA 里除了 rank,经常还会看到一个参数:

alpha

实际增量常写成:

y = xW + (alpha / r) xAB

其中:

r = rank
alpha / r = scale

rank 控制的是:

有多少个低秩方向

alpha 控制的是:

这些方向整体影响力有多大

可以类比:

rank = 有多少个旋钮
alpha = 这些旋钮最大能拧多响

rank 大不一定影响强,alpha 大也不一定表达丰富。两者控制的是不同东西。

14. 如何选择 rank?

没有一个永远正确的 rank。

可以按任务复杂度和资源预算判断。

14.1 rank 小:适合简单、窄任务

比如:

固定格式输出
轻量分类
少量风格调整
简单领域词汇适配

可以从:

r = 4 或 8

开始。

14.2 rank 中等:适合多数任务

比如:

指令微调
工具调用习惯
代码风格
日志 triage
结构化抽取

常见选择:

r = 16 或 32

14.3 rank 大:适合复杂能力迁移

比如:

跨领域知识
复杂代码任务
多步骤 Agent 行为
多个技能合并

可能需要:

r = 64 / 128

甚至更多。

但 rank 越大,要越注意:

显存
训练稳定性
过拟合
adapter 文件大小
多 adapter 合并冲突

15. 一个很小的二维类比

为了更直观,假设输出只有 3 维,而不是 4096 维。

完整更新可以任意产生:

δy = [任意 a, 任意 b, 任意 c]

如果 rank=1,它只能沿一个方向变化:

δy = z × [1, 2, -1]

无论 z 是多少,变化都在同一条线上。

如果 rank=2,它可以由两个方向组合:

δy = z1 × [1, 2, -1] + z2 × [0, 1, 3]

这样变化范围就变成一个平面。

如果 rank=3,在 3 维空间里理论上可以覆盖整个空间。

回到 4096 维:

rank=16

表示输出增量落在一个最多 16 维的子空间里,但这个子空间嵌在 4096 维空间中,里面的每个方向都可能影响全部 4096 个坐标。

16. 从 PAW / Parametric Skills 回看 rank

现在再看 PAW 和 Parametric Skills,就更容易理解。

PAW 的 per-function LoRA 不是把一个函数写成 if/else,而是给解释器加一些低秩行为方向:

哪些输入特征该触发 YES
哪些输出格式该被强化
哪些边界样例该保守处理

Parametric Skills 的 per-skill LoRA 也不是把整份 SKILL.md 存成文本,而是给模型加一些低秩行为方向:

遇到错误先查哪里
什么时候运行测试
如何避免常见反模式
失败后如何回退

rank=16 的意思就是:每个被注入的矩阵,最多用 16 套检测-响应模式来表达这种能力补丁。

如果 skill 很复杂,16 可能不够。如果只是格式或轻量分类,16 可能已经绰绰有余。

17. 最后一句话

LoRA 的 rank 不是“更新几个维度”,而是“允许多少个独立更新方向”。

rank=16 的数学图像是:

从 4096 维 hidden state 里提取 16 个调节信号,
再用这 16 个信号混合 16 个完整输出方向,
生成一个 4096 维的行为增量。

它小,是因为只学 16 套基础模式;它有用,是因为这 16 套模式可以分布到所有输出维度,并在模型很多层里反复参与计算。

这就是 LoRA 低秩适配的数学作用。

参考