上一篇 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 真正在做什么,可以把 A 和 B 拆开。
设:
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 为例:
| rank | LoRA 参数量 | 相对完整矩阵 |
|---|---|---|
| 1 | 8,192 | 0.049% |
| 8 | 65,536 | 0.39% |
| 16 | 131,072 | 0.78% |
| 64 | 524,288 | 3.13% |
| 4096 | 33,554,432 | 200% |
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 低秩适配的数学作用。