LoRA 机制学习笔记:为什么它能把函数和技能变成大模型的可插拔补丁

从矩阵乘法、Transformer 线性层、低秩增量和 adapter 加载机制出发,解释 LoRA 如何与大模型内核协作,以及为什么 Program-as-Weights 和 Parametric Skills 都选择把能力内化成 LoRA。

前两篇分别写了 Program-as-WeightsParametric Skills

读到这里会自然出现一个更底层的问题:

既然它们都把函数 / 技能内化成 LoRA,
那 LoRA 到底是什么?
它又是怎么和大模型内部协作的?

这篇专门回答这个问题。

先给结论:

LoRA 不是一个外挂 prompt,也不是另一个小模型。它是在 Transformer 内部的若干线性层旁边,加上一条低秩增量支路。大模型 forward 时,原始权重和 LoRA 增量一起参与计算,所以模型行为会被这个 adapter 持续调制。

如果用软件类比:

大模型原始权重 = 操作系统 / 通用运行时
LoRA adapter = 可插拔插件 / 技能补丁
一次 forward = 原始能力 + 插件增量共同执行

PAW 把“函数”编译成这种插件;Parametric Skills 把“技能”编译成这种插件。

1. 先从一个线性层说起

大模型内部有大量矩阵乘法。最简单的线性层可以写成:

y = xW

这里:

x = 当前 token 的 hidden state
W = 模型训练好的权重矩阵
y = 输出 hidden state

假设 hidden size 是 4096,那么一个方阵线性层可能是:

W: 4096 × 4096

这一个矩阵就有:

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

Transformer 里这种矩阵很多。attention 里有 Q/K/V/O projection,MLP 里有 up/gate/down projection。模型的“知识”和“行为倾向”,很大一部分都编码在这些矩阵里。

如果要让模型更擅长某个任务,最直接的方法是 full fine-tuning:

把 W 本身改掉

但这很重:

需要训练所有参数
需要保存一份完整新模型
容易破坏原模型能力
部署时切换成本高

LoRA 的想法是:不要直接改 W,只给 W 加一个小增量。

2. LoRA 的核心公式

LoRA 把原来的线性层:

y = xW

改成:

y = xW + xΔW

其中:

W  = 原始模型权重,冻结不动
ΔW = 任务/技能带来的权重增量

关键是,LoRA 不直接保存完整的 ΔW,而是把它拆成两个小矩阵:

ΔW = A B

所以实际计算是:

y = xW + xAB

如果 W4096 × 4096,完整 ΔW 也会是 4096 × 4096,还是 1677 万参数。

但如果 rank 取 16:

A: 4096 × 16
B: 16 × 4096

参数量变成:

4096 × 16 + 16 × 4096 = 131,072

约等于原矩阵的 1/128。

这就是 Low-Rank Adaptation 的意思:用一个低秩矩阵近似表达“该如何调整原模型”。

3. 为什么低秩增量够用?

直觉上,很多任务不需要重塑整个模型,只需要把模型行为往某个方向推一点。

比如:

更倾向输出 JSON
更关注 error log 里的根因
更稳定地遵守某种代码迁移步骤
更偏向把“紧急邮件”识别出来
更懂某个公司项目里的命名习惯

这些变化不是“从零学习语言”,而是在已有能力上加一层偏置。原始大模型已经会读文本、写代码、分类、推理。LoRA 只需要告诉它:

在这个任务里,哪些注意力模式更重要
哪些中间表示应该被放大
哪些输出分布应该被压低或抬高

这类行为调整往往可以用低维方向表达。

可以类比成调音台:

原模型 = 一首已经混好的歌
LoRA = 对若干频段做小幅调节

不是重新录一首歌,而是在原有基础上调出某种风格。

4. LoRA 插在 Transformer 的哪里?

一个 decoder-only Transformer block 大致长这样:

input hidden state
→ self-attention
   → q_proj
   → k_proj
   → v_proj
   → o_proj
→ MLP
   → gate_proj
   → up_proj
   → down_proj
→ output hidden state

这些 *_proj 基本都是线性层,也就是矩阵乘法。

LoRA 通常会插在这些 projection 上:

q = xWq + xAqBq
k = xWk + xAkBk
v = xWv + xAvBv
o = xWo + xAoBo

up   = xWup   + xAupBup
gate = xWgate + xAgateBgate
down = xWdown + xAdownBdown

不同系统会选择不同插入点。有的只插 Q/V,有的插 attention 全部 projection,有的连 MLP 也插。

这意味着 LoRA 会影响两类东西:

4.1 影响 attention:模型看哪里

插在 Q/K/V/O projection 上,会影响 attention 的行为。

attention 的核心是:

query 和 key 算相似度
根据相似度加权 value
再通过 output projection 回到 hidden state

LoRA 如果改了 Q/K/V/O 的变换,就会改变模型:

更关注哪些 token
哪些 token 之间更容易建立联系
哪些上下文信息会被带到下一层

这对 skill 很关键。比如一个 debugging skill 可能会让模型更关注:

错误日志里的 stack trace
测试失败的 assertion
最近修改过的函数
配置文件里的版本号

4.2 影响 MLP:模型如何加工中间特征

MLP 层更像每个 token 上的特征变换器。它把 hidden state 投到更高维空间,经过非线性,再压回来。

插在 MLP 上,会影响模型:

如何抽取局部模式
如何强化某类概念
如何压制某些错误倾向
如何把中间表示映射到输出倾向

对于格式修复、分类、代码迁移、风格控制等任务,MLP LoRA 往往也很有用。

5. LoRA 和 prompt 的本质差异

Prompt 是运行时输入。

你把说明书放在上下文里,
模型要读它、理解它、记住它、执行它。

LoRA 是模型计算图的一部分。

你把说明书对应的行为增量装进模型层里,
模型每一层 forward 都受到它影响。

可以对比一下:

维度Prompt / SKILL.mdLoRA adapter
存在位置上下文窗口模型层内部
工作方式模型阅读文本后遵守每层矩阵乘法加入增量
成本每次调用都占 token加载后不占上下文
可读性人类可直接审查需要测试和工具解释
适合场景一次性、低频、需人工可控高频、可复用、行为稳定化
失败方式读到了但没照做adapter 偏置错误或过强

这也是为什么 Parametric Skills 说文本 skill 的问题不是“没有知识”,而是“模型在长上下文里未必能稳定利用知识”。

LoRA 不让模型临时读说明书,而是把说明书的一部分变成行为倾向。

6. 加载 LoRA 时模型内部发生了什么?

从工程实现看,加载 LoRA 大概有两种方式。

6.1 运行时旁路计算

不改原始权重 W,每次 forward 额外算:

base = xW
lora = xAB * scale
y = base + lora

这种方式 adapter 可以随时切换:

load_adapter("json-repair")
unload_adapter()
load_adapter("flaky-test-debugging")

适合多租户、多任务、动态 skill。

6.2 合并进原权重

也可以预先计算:

W' = W + AB * scale

然后 forward 时直接用:

y = xW'

这叫 merge adapter。好处是推理时更简单,坏处是切换不如旁路灵活,而且要小心多个 adapter 的合并和回滚。

PAW 和 Parametric Skills 更强调 adapter 的可加载、可组合、可版本化,所以概念上更接近第一种动态加载方式。

7. 为什么 PAW 可以把“函数”变成 LoRA?

PAW 的函数不是传统 Python 函数,而是 fuzzy function:

判断日志是否该报警
判断邮件是否紧急
修复半坏 JSON
给搜索结果打相关性标签

这类函数的难点不是控制流,而是语义判断。大模型本来就会做这些判断,只是每次直接 prompt 大模型太贵。

PAW 做的是:

自然语言 spec
→ compiler
→ pseudo-program + LoRA
→ frozen interpreter 执行

这里的 LoRA 承载的是:

这个函数该关注哪些输入特征
输出格式是什么
边界样例怎么处理
哪些情况应该判 YES / NO
哪些情况应该拒绝或保守处理

如果不用 LoRA,只靠 prompt,解释器每次都要重新读 spec。用了 LoRA,函数行为的一部分直接进入模型层的增量计算。

所以 PAW 里的 LoRA 更像:

per-function behavior patch

它不是存储所有输入输出表,而是改变模型处理这类输入时的内部表示和输出倾向。

8. 为什么 Parametric Skills 可以把“技能”变成 LoRA?

Skill 比函数更复杂。它包含:

触发条件
做事步骤
常见错误
验证方法
工具使用习惯
多轮任务里的注意事项

文本 skill 的问题是,模型可能在上下文里看到了,但执行时忘了。

Parametric Skills 的做法是:

SKILL.md / agent 轨迹经验
→ hypernetwork
→ LoRA
→ base model 加载 skill adapter

这里的 LoRA 承载的是:

遇到这类任务时应该先查什么
哪些证据比其他信息更重要
不要犯哪些常见错误
什么时候该运行测试
失败后该如何回退和重新定位

它更像:

per-skill behavior patch

和 PAW 的区别是粒度:

PAW LoRA:让模型像一个具体函数
Parametric Skills LoRA:让模型像掌握了一套做事方法

9. LoRA 为什么能组合?

如果两个 adapter 都是对原模型权重的增量:

W + ΔW1
W + ΔW2

那么多 adapter 合并的直觉就是:

W + αΔW1 + βΔW2

这就是为什么大家会研究 adapter merge、adapter routing、rank-concat 等方法。

但组合不是免费午餐。两个 skill 可能语义冲突:

skill A:尽量最小修改
skill B:优先大规模重构

skill A:输出严格 JSON
skill B:输出详细解释

参数空间里相加,不等于语义上无冲突。

所以真正成熟的系统需要三层机制:

选择:这次任务该加载哪些 adapter
组合:多个 adapter 如何合并或路由
验证:加载后结果是否真的更好

这也是 Parametric Skills 里多 skill merging 和 self-evolving 很重要的原因。

10. LoRA 和大模型“内核”的协作方式

如果把大模型看成一个固定内核,LoRA 的协作方式可以分成四步:

10.1 内核提供通用能力

原始大模型已经学会:

语言理解
代码模式
常识推理
格式生成
基本工具使用

LoRA 不重新发明这些能力。

10.2 Adapter 提供任务方向

LoRA 提供的是“这次应该往哪里偏”的信号:

更像分类器
更像 JSON 修复器
更像 debugging specialist
更像 API migration assistant

10.3 Forward 时二者逐层相加

每一层里:

hidden state
→ 原始权重变换
→ LoRA 增量变换
→ 相加
→ 传给下一层

所以 adapter 不是只影响最后输出,而是在多层 hidden state 演化过程中持续参与。

10.4 输出仍然由同一个模型生成

最后 logits 还是由同一个模型头产生。LoRA 只是改变了前面 hidden state 的轨迹。

这点很重要:LoRA 不是外部规则引擎,它不绕过模型。它是模型内部计算的一部分。

11. 为什么这比“长期 prompt”更像软件工件?

一个 prompt 很难像二进制一样管理。

你当然可以版本化 prompt 文本,但它运行时仍然依赖:

上下文顺序
模型是否注意到
历史消息干扰
token budget
远程模型版本

LoRA adapter 则更像一个部署工件:

文件大小明确
版本明确
hash 明确
依赖的 base model 明确
可以加载 / 卸载 / 合并 / 回滚

这就是 PAW 和 Parametric Skills 想抓住的工程价值。

未来一个 Agent 系统可能不只有 prompt library,而是有 adapter registry:

json-repair@1.2.0.lora
log-triage@0.4.1.lora
flaky-test-debugging@2.0.0.lora
api-migration-react18@1.1.0.lora
company-style-guide@3.5.2.lora

每个 adapter 都有:

source spec / skill text
compiler version
base model version
eval report
known failure cases
rollback policy

这就从 prompt 管理,走向了神经插件管理。

12. LoRA 的风险:内化不等于可控

LoRA 很适合内化能力,但它也带来新问题。

12.1 不透明

Prompt 可以直接读。LoRA 不能直接读。

你可以知道它来自哪份 spec 或 skill,但很难回答:

这个 adapter 为什么在这个输入上失败?
是哪一层的哪个增量导致的?
两个 adapter 冲突在哪里?

所以必须保留源文本、训练记录和评估报告。

12.2 可能过拟合

如果 adapter 来自少量样本,它可能学到表面模式。

比如一个日志报警 adapter 可能过度依赖某几个关键词,而不是真正理解严重性。

12.3 可能破坏原能力

LoRA 虽然小,但如果 scale 太大、插入层太多、训练数据偏,仍然可能让模型在其他能力上退化。

所以 adapter 要做回归测试:

目标任务是否提升
非目标任务是否明显下降
输出格式是否稳定
安全边界是否仍然存在

12.4 多 adapter 组合会产生干扰

多个 LoRA 同时加载时,不只是“能力相加”。它们的增量可能把 hidden state 推向不同方向。

这需要 routing、gating、merge calibration 和任务级选择。

12.5 依赖 base model

LoRA 是相对于某个 base model 训练或生成的。

qwen3-0.6b adapter
不一定能直接挂到另一个结构不同的模型上

这和传统软件插件不同。它高度绑定模型结构、层名、hidden size、projection shape。

13. 一个更准确的心智模型

不要把 LoRA 想成:

一个知识文件
一个提示词压缩包
一个独立小模型

更准确的心智模型是:

LoRA 是一组分布在模型多层线性变换旁边的低秩行为增量。

它的效果不是“记住一段文字”,而是改变模型在某类输入上的计算路径:

关注哪里
强化什么特征
压制什么倾向
更容易输出什么结构
更容易遵守什么过程

这也是为什么它能承载函数、技能、领域、风格。

14. 三篇文章连起来看

现在这三篇可以组成一条完整链路。

第一篇 PAW 回答:

能不能把一个 fuzzy function 编译成权重?

答案是:可以,用 compiler 生成 per-function LoRA,让小解释器执行。

第二篇 Parametric Skills 回答:

能不能把 Agent skill 编译成权重?

答案是:可以,用 hypernetwork 生成 per-skill LoRA,让模型更稳定地利用技能。

这篇回答:

为什么 LoRA 能承担“函数”和“技能”?

答案是:因为 LoRA 不是外部文本,而是参与模型内部矩阵乘法的低秩增量。它不替换大模型,只调制大模型。

三句话总结:

PAW:函数规格 → LoRA → 可复用 fuzzy function
Parametric Skills:技能文档 → LoRA → 可加载 agent skill
LoRA 机制:原始权重 + 低秩增量 → 行为被调制的大模型

15. 最后一句话

LoRA 之所以适合承载 PAW 和 Parametric Skills,不是因为它“神奇地存储了知识”,而是因为它以很低成本进入了大模型的内部计算路径。

Prompt 是让模型读一份说明书;LoRA 是给模型装一个行为补丁。

这就是“能力从上下文窗口迁移到参数模块”的底层机制。

参考