初级
LoRA - 大型语言模型的低秩适配
LoRA - 大型语言模型的低秩适配
在本博客中,我们将学习 LoRA——大型语言模型的低秩适配。
今天,我们将涵盖以下主题:
- 宏观视角
- 为什么全量微调成本高昂
- LoRA 的核心思想
- LoRA 的逐步工作原理
- 一个小型数值示例
- LoRA 在 Transformer 中的应用位置
- 将 LoRA 合并回模型
- 实际应用场景
- 快速总结
我是 Amit Shekhar,Outcome School 的创始人。我曾教导和指导过许多开发者,他们的努力帮助他们获得了高薪技术工作,帮助许多科技公司解决了独特的问题,并创建了许多被顶级公司使用的开源库。我热衷于通过开源、博客和视频分享知识。
我在 Outcome School 教授人工智能与机器学习。
让我们开始吧。
宏观视角#
在深入细节之前,我们先了解宏观视角。
LoRA 是一种在不更新所有权重的情况下微调大型模型的方法。我们不改变原始权重矩阵,而是将其冻结,并在旁边学习一对额外的微小矩阵。这些微小矩阵捕捉了我们想要的“调整”。
简单来说:
LoRA = 冻结的原始权重 + 在侧面学习的小型低秩更新。
这使得微调更加便宜、快速且存储更轻量。同时,我们仍然能获得强大的任务特定性能。
为什么全量微调成本高昂#
在深入 LoRA 之前,我们必须先理解为什么全量微调很困难。
你可以在这个视频中了解关于微调的一切。
一个现代大型语言模型可能有数十亿个参数。例如,一个 70 亿参数的模型内部有 70 亿个数字。当我们进行全量微调时,我们会更新每一个数字。
这会导致以下问题:
- GPU 内存爆炸。我们需要在内存中保留权重、梯度和优化器状态。对于 Adam 优化器,这可能是模型本身大小的 3 到 4 倍。
- 训练速度慢。更新数十亿个参数需要大量计算。
- 存储负担重。每个微调后的模型副本与原始模型大小相同。如果我们有 10 个不同的任务,就需要 10 个完整的数 GB 模型副本。
- 共享困难。每个任务一个数 GB 的检查点不容易分发给用户。
我们需要一种更聪明的方法。我们需要一种在不触及大部分权重的情况下将模型适配到新任务的方法。
所以,LoRA 登场了。
LoRA 的核心思想#
LoRA 的全称是低秩适配(Low-Rank Adaptation)。
让我们分解这个名称:
LoRA = 低秩 + 适配。
- 适配意味着我们将模型适配到新任务或新风格。
- 低秩意味着我们对权重所做的更改以非常紧凑的形式表示,使用两个小矩阵。
这个想法基于微软原始 LoRA 论文中的一个有力观察:
当我们微调大型模型时,实际应用于权重的更新是非常低秩的。我们不需要一个完整的巨大更新矩阵——一个更小的矩阵就足够了。
简单来说,教模型新任务所需的“调整”比模型本身小得多。因此,我们不需要一个巨大的矩阵来表示它。
为什么这有效?预训练模型已经知道很多。它已经学习了语法、事实、推理和一般模式。为了一项新任务微调它并不是重写。它只是在几个重要方向上的轻微推动。这正是“低秩”的含义。
让我们用一个现实世界的类比。把预训练模型想象成一本巨大的教科书。全量微调就像为了一门新主题重写教科书的每一页。LoRA 就像保持教科书不变,并在上面添加一小套便签纸,携带新知识。教科书保持不变,而小便签完成了实际的调整。
现在,让我们看看这在数学上是如何工作的。
模型内部的权重矩阵 W 的形状是 d x d。在全量微调中,我们学习一个相同形状的新矩阵 W_new:
W_new = W + ΔW这里,ΔW 是我们想要应用的更改。W 和 ΔW 都非常大。
LoRA 用两个小矩阵的乘积替换了这个巨大的 ΔW:
ΔW = BA其中:
- A 的形状是 r x d
- B 的形状是 d x r
- r 是秩,一个小的数字,如 4、8、16、32 或 64
- d 是原始维度,通常是 4096 或更大
现在,BA 的形状仍然是 d x d,但我们从不存储完整的 d x d 更新。我们只存储 A 和 B,它们与 W 相比非常小。
注意:为简单起见,我们假设 W 是一个方阵 d x d。在真实的大型语言模型中,注意力投影矩阵是方阵,但前馈层不是。它们通常是 d x 4d 或 4d x d。LoRA 适用于任何矩形矩阵——如果 W 的形状是 d x k,那么 B 变成 d x r,A 变成 r x k。思想完全相同。
这是一个简单的 ASCII 图,比较了大小。
ΔW (完整更新) B x A
(d x d 矩阵) (d x r) (r x d)
+-------------------+ +--+ +-------------------+
| | | | +-------------------+
| | | |
| | | |
| | = | | x
| | | |
| | | |
| | | |
| | | |
+-------------------+ +--+这里,大正方形是我们必须存储的完整更新。细列 B 和短行 A 一起承载了我们实际训练的所有信息。视觉上的大小差异正是 LoRA 如此节省内存的原因。
输入向量 x 的前向传播变为:
h = Wx + (BA)x这里,W 被冻结。只有 A 和 B 被训练。
LoRA 逐步工作原理#
让我们一步步了解 LoRA。
第一步: 获取预训练模型,冻结其所有原始权重。原始模型内部的任何参数在训练过程中都不会被更新。
第二步: 对于每个我们想要适配的权重矩阵,在其旁边添加两个小矩阵 A 和 B。
- A 使用小的随机值(高斯分布)初始化。
- B 使用全零初始化。
这种零初始化非常重要。在训练开始时,BA = 0,因此模型的行为与原始预训练模型完全相同。训练过程会逐渐将 BA 从零推开。
第三步: 在训练过程中,只有 A 和 B 接收梯度更新。原始的 W 保持不变。
第四步: 在前向传播过程中,输出计算如下:
h = Wx + (alpha / r) * (BA)x其中,alpha 是缩放因子。alpha / r 的比值控制着 LoRA 更新对输出的影响程度。常见的选择是 alpha = 2 * r。
第五步: 训练完成后,我们得到一组新的小权重 A 和 B,它们捕获了任务特定的知识。原始模型仍然保持不变。
这就是整个核心思想。
回到我们的教科书类比:原始教科书就是冻结的 W。便利贴就是 A 和 B。便利贴一开始是空白的,因为 B 是零。随着训练的进行,便利贴上逐渐填满修正内容,将教科书的行为向新任务方向调整。
注意: 秩 r 的选择是 LoRA 最重要的超参数。较小的 r 意味着更少的可训练参数和更快的训练速度,但适配器学习复杂变化的能力较弱。较大的 r 提供更强的学习能力,但会损失一些效率优势。在实践中,对于大多数任务,秩在 8 到 64 之间效果良好。
想要通过实际项目动手学习微调、PEFT 和 LoRA,请查看 Outcome School 的 AI 与机器学习课程。
一个小型数值示例#
让我们用具体数字来理解。
假设我们有一个形状为 4096 x 4096 的权重矩阵 W。W 中的参数量为:
4096 x 4096 = 16,777,216仅这一个矩阵就有约 1680 万个参数。全量微调将更新所有这些参数。
现在,让我们应用秩 r = 8 的 LoRA。
- A 的形状为 8 x 4096 = 32,768 个参数
- B 的形状为 4096 x 8 = 32,768 个参数
- LoRA 总参数量 = 65,536
让我们比较一下:
- 全量微调:每个矩阵 16,777,216 个参数
- 秩为 8 的 LoRA:每个矩阵 65,536 个参数
对于同一个矩阵,参数量减少了约 256 倍。而且,这仅仅是一个矩阵——这种节省会扩展到模型的每一层。
对于一个完整的 7B 参数模型,全量微调需要更新 70 亿个参数。秩为 8 的 LoRA 通常只更新几百万个参数。可训练参数量减少了约 1000 倍或更多,具体取决于秩和 LoRA 的应用位置。
LoRA 在 Transformer 中的应用位置#
LoRA 可以应用于模型中的任何线性层。但在实践中,LoRA 最常应用于注意力投影矩阵。
我们有一篇关于 Transformer 架构 的详细博客,解释了注意力层在 Transformer 内部的工作原理。
在每个注意力块内部,有四个投影矩阵:
- W_Q - 查询投影
- W_K - 键投影
- W_V - 值投影
- W_O - 输出投影
如果想深入了解 Q、K、V 的计算和使用方式,可以阅读 注意力机制背后的数学:Q、K、V。
原始的 LoRA 论文发现,仅适配 W_Q 和 W_V 通常就足以在最小化额外参数的情况下获得强大的任务性能。在实践中,我们也可以将 LoRA 应用于所有四个投影,甚至前馈层,以获得更好的质量,但代价是增加可训练参数。
下面是一个简单的 ASCII 图,展示了单个权重矩阵的结构。
输入 x
|
+-------+-------+
| |
v v
+---------+ +-------+
| W | | A | (可训练,r x d)
| (冻结) | +-------+
+---------+ |
| v
| +---------+
| | B | (可训练,d x r)
| +---------+
| |
v v
+-------+-------+
|
v
h = Wx + (BA)x这里,W 保持冻结,只有 A 和 B 被训练。两条路径相加形成最终输出。
如果想深入了解 Transformer 架构、注意力和 Q/K/V 投影,我们有一个完整的课程——请查看 Outcome School 的 AI 与机器学习课程。
将 LoRA 合并回模型#
这是 LoRA 最优雅的特性之一。
训练完成后,我们可以将 BA 直接合并到 W 中:
W_merged = W + (alpha / r) * (BA)现在,模型使用单个权重矩阵 W_merged,与原始模型完全相同。推理时没有额外的矩阵和额外的计算。
这意味着合并后的 LoRA 在推理时不会增加任何延迟。我们获得了微调的好处,而没有任何运行时开销。
在我们的教科书类比中,合并就像将便利贴永久写入教科书本身。便利贴消失了,但教科书现在承载了新的知识。
如果希望在运行时在不同任务之间切换,我们也可以将 A 和 B 与 W 分开保存。这是适配器交换的基础。
注意: 合并是单向操作。一旦将 A 和 B 合并到 W 中,就不能轻易地将该适配器换成另一个。因此,如果希望从同一个基础模型服务多个任务,应该保持适配器未合并,并按需加载。
实际应用场景#
LoRA 在现代 LLM 工作流中无处不在。让我们看看 LoRA 在实际中的应用。
- 在单 GPU 上微调开源 LLM。 像 LLaMA、Mistral 和 Qwen 这样的模型通常使用 LoRA 在单个消费级或工作站 GPU 上进行微调。没有 LoRA,这将需要一组昂贵的 GPU 集群。
- 任务特定适配器。 团队为每个任务训练小型 LoRA 适配器——一个用于摘要生成,一个用于代码生成,一个用于客户支持——并且只加载所需的适配器。基础模型保持不变。
- 风格和领域适配。 LoRA 用于教基础模型特定的写作风格、领域(如医疗或法律)或特定角色。
- 图像生成模型。 LoRA 广泛用于像 Stable Diffusion 这样的图像模型,以添加新角色、风格或概念,而无需重新训练整个模型。社区共享了数千个小型 LoRA 文件。
- QLoRA 的基础。 LoRA 是 QLoRA 的构建块,后者将 LoRA 与 4 位量化相结合,以便在单个消费级 GPU 上微调非常大的模型。
注意: 当向用户分发 LoRA 适配器时,我们只分发小的 A 和 B 矩阵。这些通常只有几兆字节。相比之下,全量微调后的模型有数 GB。这就是为什么 LoRA 适配器如此易于共享、交换和版本管理的原因。
快速总结#
让我们回顾一下我们解码的内容:
- LoRA = 低秩自适应(Low-Rank Adaptation)。我们冻结原始权重,并学习一个小的低秩更新作为补充。
- 更新公式为 ΔW = BA,其中 A 和 B 是由小秩 r 控制的微小矩阵。
- 初始时,A 随机初始化,B 为零。这使得模型开始时与原始预训练模型完全一致。
- 仅训练 A 和 B。原始权重 W 保持冻结,从而节省大量内存和计算资源。
- 根据秩和 LoRA 的应用位置,可训练参数减少 100 倍到 1000 倍甚至更多。
- 训练后,LoRA 可以合并到原始权重中,因此在推理时不会产生额外延迟。
- 适配器交换技术允许我们通过加载不同的 A 和 B 矩阵,从单个基础模型为多个任务提供服务。
- LoRA 被广泛应用于大语言模型、图像模型、领域自适应,并作为 QLoRA 的基础。
这就是 LoRA 如何让大语言模型的微调变得廉价、快速且易于实现。
本次分享到此结束。
感谢
Amit Shekhar
创始人 @ Outcome School