📍 你在这里 训练进阶 · 第 2 篇 / 共 9 篇
← 上一篇 PyTorch 基础环境安装 · 下一篇 → LLM 微调数据集格式:Alpaca 与 ShareGPT
基础知识
参考:https://kq4b3vgg5b.feishu.cn/wiki/LxI9wmuFmiaLCkkoiCIcKvOan7Q(https://kq4b3vgg5b.feishu.cn/wiki/LxI9wmuFmiaLCkkoiCIcKvOan7Q)
LoRA的原理:
- 在标准微调中,我们会修改模型的所有权重,而在 LoRA 中,只有某些低秩矩阵(适配器)被训练和调整。这意味着原始模型的参数保持不变,只是通过少量的新参数来调整模型的输出。
- 低秩矩阵的引入可以在显存和计算能力有限的情况下,依然有效地对大型预训练模型进行微调,从而让 LoRA 成为显存较小的设备上的理想选择。
为啥需要“低秩矩阵”?
你可以把「大模型微调」想象成“改装汽车”——
- 标准微调是“全车改装”:把发动机、方向盘、座椅全拆了换零件(改模型所有权重),效果好,但费钱、费时间(占显存、算得慢)。
- LoRA 是“局部轻改”:只换方向盘套、加个脚垫(只改少量新参数),既不破坏原车核心性能(原模型权重不动),又能让车更贴合你的习惯(适应新任务)。
而“低秩矩阵”,就是 LoRA 用来实现“局部轻改”的「小工具包」。
第一步:先懂“秩”——不是数学,是“复杂度”
“秩”(Rank)不用记公式,你可以理解成「描述一个东西“复杂程度”的指标」:
- 比如一张“像素混乱的风景照”,里面有山、水、云、树,每个元素都独立,没法用少数元素代替——这就是“高秩”(复杂)。
- 再比如一张“纯红色的纯色图”,整个画面只有1种颜色,随便找一个红色像素,就能代表整张图——这就是“低秩”(简单)。
矩阵的“秩”也是一个道理:秩越小,矩阵越“简单”,需要的“存储/计算资源”越少。
第二步:“低秩矩阵”在 LoRA 里是怎么干活的?
还是用“改装汽车”类比,这次再细化:
假设你有一辆“大模型牌汽车”,它的核心部件是“发动机”(对应模型里的“权重矩阵”,比如一个 1024×1024 的大矩阵,相当于发动机有 100 多万个零件)。
现在你想“微调”这辆车,让它适合“山路行驶”(对应新任务,比如“生成国风图片”“翻译小语种”):
- 「标准微调」:直接把发动机拆开,100 多万个零件逐个调整——费钱(占显存)、费时间(算得慢),还可能拆坏原有的“高速行驶能力”(原模型的通用知识)。
- 「LoRA 微调」:不拆发动机!而是给发动机加两个“小配件”(对应两个“低秩矩阵”):
- 第一个配件是“小转换器”(矩阵 W_A):比如只有“1024×16”个零件(16 就是“低秩”,远小于 1024),负责把“原发动机的动力”转成一种“中间信号”;
- 第二个配件是“小调节器”(矩阵 W_B):比如只有“16×1024”个零件,负责把“中间信号”调成“山路需要的动力”。
这两个小配件组合起来(相当于 W_A × W_B),就能起到“微调发动机动力”的效果——但你只需要改这两个小配件(总共 1024×16 + 16×1024 ≈ 3 万个零件),比改 100 多万个零件简单太多!
第三步:核心结论——“低秩矩阵”的本质是“偷懒但高效”
LoRA 用“低秩矩阵”,本质是做了一件事:
不直接改大模型的“核心大零件”(高秩矩阵),而是用两个“小零件”(低秩矩阵)的组合,实现差不多的微调效果。
就像你想让房间变温馨:不用重新刷墙、换家具(高成本),只需要加个小台灯、铺块地毯(低成本)——效果到位,还不折腾。
而“低秩”的“低”,就是说这两个“小零件”特别简单(秩越小,零件越少),所以:
- 显存够:不用装一大堆新零件,手机、笔记本也能跑;
- 速度快:调小零件比调大零件快得多;
- 不翻车:原模型的“核心能力”(比如通用理解、生成流畅度)不会被破坏。
矩阵的 “秩” 是衡量矩阵 “信息量” 或 “复杂度” 的指标,代表矩阵中线性无关的行(或列)的最大数量。 这儿的线性无关啥意思?
“线性无关”是理解矩阵秩的核心概念,简单说就是:一组向量(行或列)中,没有任何一个向量能被其他向量“组合”出来。
用两个生活例子和一个数学例子,你就能彻底明白:
用“配料”理解线性无关
假设我们有3种“基础配料”:
- 向量A:1份牛奶
- 向量B:1份咖啡
- 向量C:1份牛奶 + 1份咖啡
这3个向量中:
- A和B是“线性无关”的:你无法用牛奶“配出”咖啡,也无法用咖啡“配出”牛奶(两者彼此独立,谁也离不开谁)。
- C和A、B是“线性相关”的:C = A + B(C能被A和B组合出来,本身没有新信息)。
所以这组向量的“最大线性无关数量”是2——这就是它们的“秩”。
用“方向”理解线性无关
把向量想象成“坐标系里的箭头”:
- 向量A:沿x轴的箭头(比如(1,0))
- 向量B:沿y轴的箭头(比如(0,1))
- 向量C:沿x轴+ y轴的箭头(比如(2,3))
这3个向量中:
- A和B是“线性无关”的:x轴和y轴方向完全独立,不能说“x轴方向是y轴方向的多少倍”(两者无法互相表示)。
- C和A、B是“线性相关”的:C = 2×A + 3×B(C可以用A和B的“倍数相加”表示,方向不独立)。
所以这组向量的秩也是2。
数学定义(结合矩阵)
对于矩阵中的行向量(或列向量):
如果存在一组不全为0的数字 <font style="color:rgb(28, 31, 35);">k₁, k₂, ..., kₙ</font>,使得
<font style="color:rgb(28, 31, 35);">k₁×向量₁ + k₂×向量₂ + ... + kₙ×向量ₙ = 0</font>(零向量)
就说这组向量是“线性相关”的(有向量能被其他向量组合出来)。
反之,如果只有当所有k都为0时,上面的等式才成立,就说这组向量是“线性无关”的(彼此独立,谁也不能被谁组合)。
假设有一个 3×3 矩阵,它的 3 个行向量分别是:
- 向量₁ = [1, 2, 3]
- 向量₂ = [4, 5, 6]
- 向量₃ = [6, 9, 12]
我们来验证是否存在 “不全为 0 的 k₁, k₂, k₃”,使得:
<font style="color:rgba(0, 0, 0, 0.85) !important;">k₁×向量₁ + k₂×向量₂ + k₃×向量₃ = 零向量 [0, 0, 0]</font>
通过观察可以发现:
向量₃ = 2× 向量₁ + 1× 向量₂(代入数值验证:2×[1,2,3] + 1×[4,5,6] = [2+4, 4+5, 6+6] = [6,9,12] = 向量₃)
变形后得到:
<font style="color:rgba(0, 0, 0, 0.85) !important;">2×向量₁ + 1×向量₂ - 1×向量₃ = [0, 0, 0]</font>
这里的 k₁=2,k₂=1,k₃=-1(不全为 0),满足等式。因此这 3 个向量是 “线性相关” 的。
回到矩阵的秩
矩阵的秩,就是矩阵中“线性无关的行(或列)的最大数量”。
- 比如一个3×3的矩阵,如果它的3行都是线性无关的,秩就是3(满秩);
- 如果其中1行能被另外2行组合出来,秩就是2(低秩);
- 如果所有行都能被1行组合出来(比如全是同一行的倍数),秩就是1(更低秩)。
秩越小,说明矩阵里的“独立信息”越少,越容易被压缩(这也是LoRA用低秩矩阵省资源的核心原因)。
低秩矩阵
要从数学上理解 LoRA 里的“低秩矩阵”,我们可以拆解成 “矩阵的秩是什么” 和 “低秩矩阵为什么能‘替代’高秩矩阵” 两步,用最基础的线性代数概念讲透,不绕复杂公式。
第一步:先搞懂“矩阵的秩”——它是矩阵的“信息密度”
我们先抛开 LoRA,单纯看“矩阵的秩(Rank)”这个数学概念。它本质描述的是:矩阵能表示的“独立信息”有多少。
用一个具体例子帮你建立直觉:假设我们有一个 3×3 的矩阵 A(可以理解为 LoRA 里要微调的“高秩权重矩阵”):
$ A = \begin{pmatrix}
1 & 2 & 3 \
2 & 4 & 6 \
3 & 6 & 9
\end{pmatrix} $
观察这个矩阵会发现:
- 第 2 行 = 第 1 行 × 2
- 第 3 行 = 第 1 行 × 3
也就是说,这 3 行里只有 1 行是“独立”的——另外两行的信息完全可以由第 1 行推导出来,没有新东西。
在数学上,这个矩阵 A 的“秩”就等于 1(记为 rank(A)=1),是一个低秩矩阵。
再换一个 3×3 矩阵 B(秩=2):
$ B = \begin{pmatrix}
1 & 0 & 2 \
0 & 1 & 3 \
2 & 1 & 7
\end{pmatrix} $
观察发现:
- 第 3 行 = 第 1 行 × 2 + 第 2 行 × 1
- 第 1 行和第 2 行无法互相推导(一个是 [1,0,2],一个是 [0,1,3],没有倍数关系)
所以 B 里有 2 行独立信息,秩=2(依然是低秩,因为 2 < 3)。
核心结论1:矩阵的秩 ≠ 矩阵的尺寸
- 一个 1000×1000 的矩阵,秩可能只有 10(意味着它的 1000 行里,只有 10 行是独立的,剩下 990 行都是“重复信息”);
- 一个矩阵的“秩”最大不会超过它的行数或列数(比如 3×3 矩阵的最大秩是 3,此时它是“满秩矩阵”,所有行/列都独立,信息密度最高)。
第二步:LoRA 的核心数学逻辑——用“低秩矩阵分解”替代“高秩矩阵微调”
LoRA 要解决的问题是:大型预训练模型的权重矩阵(比如 1024×1024,满秩)太大,直接微调所有参数会占很多显存/计算量。
它的数学 trick 就是:用两个“低秩小矩阵的乘积”,去近似表示“高秩大矩阵的微调量”。
1. 先明确:LoRA 不碰原始权重,只加“微调增量”
假设预训练模型里有一个权重矩阵 ( W )(比如尺寸是 ( d_{in} \times d_{out} ),比如 1024×1024,满秩,秩≈1024),模型在推理时会用 ( W ) 对输入 ( x )(比如维度为 ( d_{in} ) 的向量)做线性变换:
$ y = Wx $
LoRA 不修改 ( W )(原始权重冻结),而是在旁边加一个“微调增量矩阵” ( \Delta W ),让新的输出变成:
$ y = (W + \Delta W)x $
关键来了:( \Delta W ) 不是直接训练一个 1024×1024 的高秩矩阵,而是用两个低秩小矩阵的乘积来表示。
2. 低秩分解:把 ( \Delta W ) 拆成 ( A \times B )
LoRA 会把 ( \Delta W ) 分解成两个小矩阵的乘积:
$ \Delta W = A \times B $
这里的尺寸和秩是关键,我们用具体数字举例(比如 ( d_{in}=d_{out}=1024 ),LoRA 的秩 ( r=16 )):
- 矩阵 ( A ):尺寸是 ( 1024 \times 16 ),秩最大为 16(因为列数是 16,独立列最多 16 个);
- 矩阵 ( B ):尺寸是 ( 16 \times 1024 ),秩最大也为 16(因为行数是 16,独立行最多 16 个);
- 乘积 ( A \times B ):尺寸是 ( 1024 \times 1024 )(和原始 ( W ) 一样大),但它的秩 最大只能是 16(数学定理:两个矩阵乘积的秩 ≤ 其中任意一个矩阵的秩)。
也就是说:我们用两个“低秩小矩阵”(A 和 B),乘出了一个“低秩大矩阵”(ΔW),这个 ΔW 的尺寸和原始 W 一样,但它的“信息密度”(秩)只有 16,远低于 W 的满秩(1024)。
3. 为什么这招管用?—— 模型微调只需要“低秩信息”
从数学本质看,预训练模型微调的核心,是在原始权重 ( W ) 的基础上,加入一个“针对性的小调整”(ΔW),而这个调整不需要“满秩的复杂信息”——它只需要捕捉“任务相关的少量独立特征”(比如从“通用文本”到“医学文本”的微调,只需要加“医学术语映射”的信息,不需要重构整个语言模型)。
而低秩矩阵 ΔW(由 A×B 得到)正好能满足这个需求:它的秩 r 很小(比如 8、16、32),意味着它只包含 r 个“独立的调整方向”,刚好能捕捉任务所需的核心信息,又不会引入多余的复杂度。
第三步:数学上的“性价比”——为什么低秩矩阵能省显存/计算量?
我们用参数数量对比,就能直观看到优势:
假设原始权重矩阵 ( W ) 是 ( d_{in} \times d_{out} = 1024 \times 1024 ):
- 直接微调 ( W ) 的参数数量:1024×1024 = 1,048,576 个;
- LoRA 分解后(r=16):A 的参数是 1024×16=16,384,B 的参数是 16×1024=16,384,总参数是 16,384+16,384=32,768 个。
两者对比:LoRA 的参数数量只有直接微调的 32,768 / 1,048,576 ≈ 3.125%,显存和计算量直接降为原来的几十分之一——这就是低秩矩阵在数学上的“性价比”。
矩阵计算(线性代数基础)
先明确矩阵的基本信息
假设:
- 矩阵A:形状是
1024×64(1024行,64列) - 矩阵B:形状是
64×1024(64行,1024列) - 目标:计算它们的乘积 ΔW = A × B,结果ΔW的形状是
1024×1024(1024行,1024列)
矩阵乘法的核心规则(关键!)
两个矩阵相乘(比如A×B),结果矩阵ΔW中第i行第j列的元素,等于:
A的第i行所有元素,与B的第j列所有元素,逐个相乘后再求和。
用公式表示(不用记,看例子):
ΔW[i][j] = (A[i][0] × B[0][j]) + (A[i][1] × B[1][j]) + ... + (A[i][63] × B[63][j])
这里的「63」是因为A的列数和B的行数都是64(从0开始计数到63)。
用简化的小例子演示(把1024换成2,64换成3,方便计算)
为了让你直观理解,我们用「小矩阵」模拟:
- A:形状
2×3(2行3列)
A = [
[a11, a12, a13], # 第1行
[a21, a22, a23] # 第2行
]
- B:形状
3×2(3行2列)
B = [
[b11, b12], # 第1行
[b21, b22], # 第2行
[b31, b32] # 第3行
]
- 计算 ΔW = A×B,结果形状是
2×2(2行2列)
步骤1:计算ΔW的第1行第1列(ΔW[0][0])
对应规则:A的第1行 × B的第1列,相乘后求和
ΔW[0][0] = (a11 × b11) + (a12 × b21) + (a13 × b31)
步骤2:计算ΔW的第1行第2列(ΔW[0][1])
对应规则:A的第1行 × B的第2列,相乘后求和
ΔW[0][1] = (a11 × b12) + (a12 × b22) + (a13 × b32)
步骤3:计算ΔW的第2行第1列(ΔW[1][0])
对应规则:A的第2行 × B的第1列,相乘后求和
ΔW[1][0] = (a21 × b11) + (a22 × b21) + (a23 × b31)
步骤4:计算ΔW的第2行第2列(ΔW[1][1])
对应规则:A的第2行 × B的第2列,相乘后求和
ΔW[1][1] = (a21 × b12) + (a22 × b22) + (a23 × b32)
最终结果
ΔW = [
[ΔW[0][0], ΔW[0][1]], # 第1行
[ΔW[1][0], ΔW[1][1]] # 第2行
]
回到原来的问题:A(1024×64) × B(64×1024) 如何得到ΔW(1024×1024)?
原理和上面的小例子完全一样,只是规模更大:
- ΔW的行数 = A的行数(1024行)
- ΔW的列数 = B的列数(1024列)
- 每个元素的计算:
比如ΔW的第5行第8列(ΔW[4][7],注意从0计数):
= A的第5行第1个元素 × B的第1列第8个元素
(总共64项相加,因为A有64列,B有64行)
- A的第5行第2个元素 × B的第2列第8个元素
- ...
- A的第5行第64个元素 × B的第64列第8个元素
先明确矩阵乘法的“唯一前提条件”
假设要计算矩阵 X × Y(X乘Y),唯一要求是:
X的列数 = Y的行数
只要满足这个,不管X的行数是多少、Y的列数是多少,都能相乘。最终结果矩阵的形状是固定的:
- 结果矩阵的行数 = X的行数
- 结果矩阵的列数 = Y的列数
用表格直观验证(结合你的例子和其他场景)
我们用不同维度的矩阵举例,看是否符合这个规则:
| 前矩阵X(形状) | 后矩阵Y(形状) | 关键条件:X的列数=Y的行数? | 能否相乘? | 结果矩阵形状(X行数×Y列数) |
|---|---|---|---|---|
| A (1024×64) | B (64×1024) | 64 = 64 ✅ | 能 | 1024×1024(你的例子) |
| X (2×3) | Y (3×5) | 3 = 3 ✅ | 能 | 2×5 |
| X (5×4) | Y (3×2) | 4 ≠ 3 ❌ | 不能 | 无 |
| X (7×1) | Y (1×9) | 1 = 1 ✅ | 能 | 7×9 |
反例
假设我们有两个矩阵:
- 矩阵 A:2行3列(行数=2,列数=3)
A = [
[1, 2, 3], # 第1行(3个元素)
[4, 5, 6] # 第2行(3个元素)
]
- 矩阵 B:4行2列(行数=4,列数=2)
B = [
[7, 8], # 第1列(4个元素)
[9, 10], # 第2列(4个元素)
[11, 12],
[13, 14]
]
矩阵乘法要求 “前矩阵的列数 = 后矩阵的行数”:
- A 的列数 = 3,B 的行数 = 4 → 3 ≠ 4,不满足条件,所以 A×B 无法计算。
回忆矩阵乘法的核心规则:计算结果矩阵的“第i行第j列”,需要用 A的第i行(所有元素) 和 B的第j列(所有元素) 逐个相乘后求和。
但这里会出现“元素数量不匹配”的问题:
- A的任意一行有 3个元素(比如第1行 [1,2,3]);
- B的任意一列有 4个元素(比如第1列 [7,9,11,13])。
要“逐个相乘”,必须两边元素数量相同(比如3个对3个、4个对4个)——现在3个和4个无法一一对应,根本没法完成“相乘再求和”的操作。
比如想算“结果[0][0]”(A第1行 × B第1列):
需要 (1×7) + (2×9) + (3×11) + (?×13) → 最后一个13找不到A里对应的元素,算式根本列不完整,自然算不出结果。
LoRA、QLoRA
高效微调与LoRA、QLoRA
尽管全量微调可以对模型的能力进行深度改造,但要带入模型全部参数进行训练,需要消耗大量的算力,且有一定的技术门槛。相比之下,在绝大多数场景中,如果我们只想提升模型某个具体领域的能力,那高效微调会更加合适。尽管在2020年前后,深度学习领域诞生了很多高效微调的方法,但现在适用于大模型的最主流的高效微调方法只有一种——LoRA。
LoRA(Low-Rank Adaptation)微调是一种参数高效的微调方法,旨在通过引入低秩矩阵来减少微调时需要调整的参数数量,从而显著降低显存和计算资源的消耗。具体来说,LoRA 微调并不直接调整原始模型的所有参数,而是通过在某些层中插入低秩的适配器(Adapter)层来进行训练。
LoRA的原理:
- 在标准微调中,我们会修改模型的所有权重,而在 LoRA 中,只有某些低秩矩阵(适配器)被训练和调整。这意味着原始模型的参数保持不变,只是通过少量的新参数来调整模型的输出。
- 低秩矩阵的引入可以在显存和计算能力有限的情况下,依然有效地对大型预训练模型进行微调,从而让 LoRA 成为显存较小的设备上的理想选择。
LoRA的优势:
- 显存优化: 只需要调整少量的参数(适配器),显著减少了显存需求,适合显存有限的GPU。
- 计算效率: 微调过程中的计算负担也更轻,因为减少了需要调整的参数量。
- 灵活性: 可以与现有的预训练模型轻松结合使用,适用于多种任务,如文本生成、分类、问答等。
而QLoRA(Quantized Low-Rank Adaptation) 则是 LoRA 的一个扩展版本,它结合了 LoRA 的低秩适配器和量化技术。QLoRA 进一步优化了计算效率和存储需求,特别是在极端显存受限的环境下。与 LoRA 不同的是,QLoRA 会将插入的低秩适配器层的部分权重进行量化(通常是量化为 INT4 或 INT8),在保持性能的同时显著降低模型的存储和计算需求。
- 核心思想: 在 LoRA 的基础上加入量化技术,减少权重表示的位数,从而降低显存和计算需求。QLoRA 结合了低秩适配器和量化的优点,能够在显存有限的设备上进行更高效的微调。
- 量化: 通过将模型权重量化为低精度(如 INT4),减少内存占用,并提高推理和训练速度。
- 优势:
- 在显存非常有限的情况下仍能进行微调。
- 可以处理更大规模的模型。
- 适合用于边缘设备和需要低延迟推理的场景。
LoRA 与 QLoRA 二者对比
| 特性 | LoRA | QLoRA |
|---|---|---|
| 核心技术 | 低秩适配器(Low-Rank Adapters) | 低秩适配器 + 量化技术(Low-Rank Adapters + Quantization) |
| 适用场景 | 显存受限,但设备性能较好 | 极限显存受限或需要快速推理的设备 |
| 计算效率 | 提高计算效率,减少调整的参数数量 | 进一步提升效率,减少内存使用并加快推理速度 |
| 量化技术 | 无量化 | 将权重量化为低精度(如 INT4 或 INT8) |
| 内存消耗 | 较低,但不如 QLoRA 低 | 显著降低内存消耗,适合更小的设备 |
| 训练复杂度 | 较简单,适用于大多数微调场景 | 需要更多的量化和适配工作,但适合超大模型和设备受限场景 |
QLoRA的量化过程
(以核心的4-bit量化为例)是通过将模型权重从高精度(如16-bit)压缩到4-bit,同时同时尽量保留模型性能的技术。其实现逻辑可以拆解为「量化准备→量化映射→动态反量化」三个核心步骤,以下是具体实现方式:
一、量化前的准备:权重分块(Block-wise Quantization)
量化不是对整个权重矩阵一次性处理,而是先将大矩阵拆分成小 block(块)(通常每个block包含64个元素)。
- 目的:让每个block内的权重值分布更集中,减少量化误差(比如同一block内的权重可能都在[-5,5]范围内,而不同block的范围可能差异很大)。
- 例:一个1024×1024的权重矩阵,会被拆分成 (1024/64)×(1024/64) = 16×16 个block,每个block是64×64的小矩阵。
二、核心量化步骤:4-bit映射(以NF4格式为例)
对每个block,通过「范围计算→量化映射→存储编码」将16-bit权重转为4-bit编码,关键是找到合适的映射规则,让4-bit编码尽可能保留原始权重的信息。
1. 计算block的权重范围(缩放因子和零点)
对每个block内的权重,先统计两个关键值:
- 最大值(max) 和 最小值(min):确定该block内权重的数值范围 [min, max]。
- 缩放因子(scale):将原始权重范围映射到4-bit能表示的范围(4-bit有16个可能值),公式为:
scale = (max - min) / (16 - 1)(16-1是因为4-bit无符号整数范围是0~15,共15个间隔)。 - 零点(zero_point):用于对齐量化后的数值(类似偏移量),确保0值在量化后能准确表示,公式为:
zero_point = -min / scale(取整数,确保量化后0值对应原始权重的0)。
2. 用NF4格式映射到4-bit编码
QLoRA采用NF4(NormalFloat4) 量化格式(专为神经网络权重设计),而非普通的4-bit整数量化:
- 普通4-bit整数:用0~15表示16个均匀分布的离散值(适合均匀分布的数据)。
- NF4:16个离散值的分布是正态分布(符合神经网络权重“大部分值接近0,少数极端值”的分布规律),能更精准地捕捉权重特征。
映射过程:
对block内的每个16-bit权重值 w,计算其对应的4-bit编码 q:
q = round( (w - min) / scale )
(将 w 从 [min, max] 范围映射到0~15的NF4编码,取最近的整数)。
3. 存储量化结果
量化后,每个block只需要存储:
- 4-bit编码(每个权重占4bit,相比16bit压缩4倍);
- 该block的缩放因子(scale,通常用16-bit存储)和零点(zero_point,整数)。
整体内存占用:原始16-bit权重需要 N×16bit,量化后需要 N×4bit + (scale+zero_point)×block数量,通常能压缩4倍以上。
三、推理/训练时:动态反量化(恢复高精度)
量化后的权重无法直接参与计算(4-bit精度太低),因此在模型前向传播或反向传播时,需要动态反量化,临时恢复到16-bit精度:
对每个4-bit编码 q,通过以下公式还原为接近原始值的16-bit权重 w_hat:
w_hat = q × scale + min
- 过程:在计算时,先从内存加载4-bit编码、scale和zero_point,实时还原为16-bit权重参与矩阵乘法;
- 优势:反量化是“临时计算”,不需要额外存储16-bit权重,兼顾了内存节省和计算精度。
四、一步优化:双重量化(Double Quantization)
为了进一步压缩scale和zero_point的存储,QLoRA还会对这些“量化参数”再做一次量化:
- 第一次量化:将原始权重转为4-bit(如上述步骤);
- 第二次量化:将所有block的scale和zero_point(原本16-bit)再量化为8-bit,进一步减少存储开销(约节省50%的量化参数内存)。
总结:量化实现的核心逻辑
QLoRA的量化本质是“用统计规律(分块、正态分布映射)实现高精度到低精度的损失可控压缩”,关键技术点包括:
- 分块处理:让每个block的量化更精准;
- NF4格式:匹配权重分布,减少量化误差;
- 动态反量化:在计算时临时恢复精度,不影响模型运算;
- 双重量化:进一步压缩量化参数的存储。
通过这些手段,QLoRA能在消费级GPU上实现70B参数模型的微调,同时性能接近全精度微调。
QLoRA 即量化低秩适配具体细节如下(还是看不太懂,等有时间重学线性代数和深度学习):
- 量化方案与方法选择
- 量化方案:QLoRA 通常会把预训练模型的权重量化为 4-bit 精度,它默认使用 NF4(NormalFloat4)量化,也可以采用 INT4量化,即将权重参数量化为 4-bit 整数。
- 量化方法:主要运用训练后量化(Post-Training Quantization,PTQ)方式,这意味着是在模型训练完成后进行量化的,无需额外的训练数据。
- 分块量化操作
- QLoRA 会实施分块量化策略,具体是将每 64 个值归为一个独立的块,每个块单独设置一个量化 scale,然后进行 NF4 4-bit 量化。这种分块处理方式能够较好地应对数据中的异常值,防止因为个别异常数据的存在致使整体量化精度出现下滑。
- NF4 格式量化映射过程
- 计算 block 关键统计量:对于每个分块内的权重数值,首先要去统计最大值与最小值,以此明确该 block 内权重的数值范围。然后基于这个范围计算缩放因子(scale),其计算方式是用最大值和最小值的差值,除以 4-bit 能够表示的非零数值数量(因为 4-bit 无符号整数范围是 0 至 15 ,所以间隔数量为 15)。此外,还需要计算零点(zero_point),目的是让量化后的数值能更好地对齐,一般是用负的最小值除以缩放因子,并取整数值,从而确保原始权重的 0 值在量化后也可以精准呈现。
- NF4 映射编码:NF4 是专门为正态分布权重设计的数据类型,由于大模型预训练阶段大量参数呈现均值为 0 的正态分布特性,NF4 量化技术便利用这一特征,将参数精准缩放到特定范围,采用用两个特定范围分别代表正负区间并对两端截断的方式,保留 0 的特殊性质,让 4 位存储空间可更准确地去表示正态分布的权重 。对 block 中的每个原始权重值,将其减去最小值后再除以缩放因子,就可以把它从原始数值范围映射到 0 至 15 的 NF4 编码空间,随后将得到的结果就近取整,得到对应的 4-bit 编码值 。
- 双重量化优化
- 在完成对权重的 4-bit 量化后,QLoRA 还会针对量化过程中的产生的量化常数,也就是每个分块的 scale 值进行二次量化,一般是将其量化为 8-bit,每 256 个分块的 scale 值会进行这样一次 8bit 量化,尽管反量化时需要先后对 scale 值和 tensor 值分别反量化,带来一些计算开销,但却能进一步减少内存占用。
- 动态反量化机制
- 当模型处于推理或者训练环节时,由于 4-bit 精度过低,无法直接用于计算,此时就需要动态反量化操作。具体是依靠之前存储的缩放因子和零点数据,将 4-bit 编码重新映射回较高精度数值,通常是恢复到 16-bit,让其能够满足前向传播以及反向传播的计算需求。
- 结合低秩适配训练
- QLoRA 会保留 LoRA 的低秩更新机制,具体做法是在量化后的权重矩阵上额外添加低秩更新矩阵,即量化后的权重矩阵 W_{quantized}<\inline_LaTeX_Formula> 更新表达式为 W_{quantized}<\inline_LaTeX_Formula> 加上 A\cdot B<\inline_LaTeX_Formula>,其中 A<\inline_LaTeX_Formula> 和 B<\inline_LaTeX_Formula> 是两个低秩矩阵。在模型微调期间,原本量化好的权重会保持冻结状态,只对 A<\inline_LaTeX_Formula> 和 B<\inline_LaTeX_Formula> 两个矩阵的参数做训练更新,进而降低计算成本,同时实现参数高效微调。
- 分页优化
- QLoRA 还会借助 NVIDIA 的统一内存技术,采用分页优化器,将优化器状态,例如 Adam 优化器的动量状态等数据分页到 CPU 内存,以此降低 GPU 内存的使用量,防止在处理长序列长度的小批量数据时出现内存不足的情况,让微调过程更加稳定。
微调(Fine-tuning)、强化学习训练(Reinforcement Learning)、模型蒸馏(Model Distillation)
| 特征 | 微调(Fine-tuning) | 强化学习训练(Reinforcement Learning) | 模型蒸馏(Model Distillation) |
|---|---|---|---|
| 目标 | 优化已预训练模型在特定任务上的表现 | 学习最优行为策略,最大化长期奖励 | 将复杂模型的知识转移到更小的模型上,减少计算开销 |
| 数据依赖 | 依赖于标注数据,通常需要针对具体任务的少量数据进行训练 | 依赖于与环境的交互,智能体从奖励信号中学习 | 依赖于教师模型,学生模型通过模仿教师模型的行为来学习 |
| 训练方式 | 通过微调已预训练的模型参数来适应新的任务 | 通过试错学习,智能体与环境交互,优化决策过程 | 教师模型生成软标签或行为,学生模型通过模仿学习教师模型的行为 |
| 应用场景 | 特定任务(如文本分类、情感分析、翻译等) | 游戏AI、机器人控制、自动驾驶、策略优化 | 模型压缩、边缘计算、低资源设备上部署 |
| 计算复杂度 | 相对较低,计算量通常较小,但依赖于任务规模 | 较高,需要大量的环境交互和计算资源 | 较低,通过蒸馏学生模型来减少计算资源和存储需求 |
| 反馈机制 | 基于标注数据的监督学习,通常通过计算损失函数进行优化 | 基于环境反馈的强化学习,通过奖励信号进行优化决策 | 通过教师模型的行为或预测结果来为学生模型提供指导 |
Colossal-AI 与 LLaMA-Factory、Unsloth、ms-SWIFT 定位不同,它更偏向于分布式训练基础设施,而非专注于参数高效微调的工具,以下是具体对比:
| 维度 | Colossal-AI | LLaMA-Factory | Unsloth | ms-SWIFT |
|---|---|---|---|---|
| 核心定位 | 分布式训练框架,支持高效并行计算 | 全栈式微调平台,覆盖训练/部署全流程 | 极致训练加速器,专注单卡高效微调 | ModelScope 生态下的参数高效微调工具 |
| 核心优势 | 1. 强大的并行技术(数据/模型/流水线并行等) 2. 支持超大规模模型(千亿级参数)训练 3. 自动优化显存和计算效率 | 1. 支持多模型/多任务/多量化方式 2. 提供图形化界面,易用性强 3. 社区活跃,迭代快 | 1. 训练速度提升显著(30倍加速) 2. 显存占用极低(降低90%) 3. 消费级GPU友好 | 1. 深度集成ModelScope生态 2. 支持多种参数高效微调算法 3. 适配特定领域模型 |
| 适用场景 | 1. 超大规模模型全参训练(如千亿级LLM) 2. 分布式集群环境下的高效训练 3. 对并行计算有高需求的场景 | 1. 中小规模模型微调(从7B到70B) 2. 多任务适配(分类/生成/对话等) 3. 需要灵活配置微调策略的场景 | 1. 单卡/少量GPU资源下的快速微调 2. 追求极致速度和显存效率的场景 3. 快速验证模型微调效果 | 1. 依赖ModelScope生态的任务 2. 特定领域模型(如中文模型)微调 3. 轻量级参数高效微调 |
| 硬件门槛 | 较高,依赖多GPU集群(如8卡A100),适合企业级环境 | 中等,支持消费级GPU(如RTX 3090),量化后可单卡训70B | 低,消费级GPU(如RTX 3090)可训70B模型 | 中等,依赖ModelScope的硬件适配,支持主流GPU |
| 参数高效微调支持 | 支持LoRA/QLoRA等,但非核心功能,需手动集成 | 原生支持QLoRA/AQLM等多种量化微调技术 | 原生深度优化QLoRA,效率业内领先 | 原生支持SWIFT(类似LoRA的参数高效技术) |
| 易用性 | 较低,需熟悉分布式训练概念,代码侵入性较强 | 高,提供Gradio UI和简洁API,新手友好 | 高,API极简,几行代码即可启动微调 | 中等,需熟悉ModelScope框架,文档较完善 |
总结:
- Colossal-AI 适合大规模全参训练,尤其在企业级集群环境下发挥并行计算优势,是“基础设施级”工具;
- LLaMA-Factory 和 Unsloth 更适合中小规模参数高效微调,前者胜在灵活性和多场景适配,后者胜在极致速度和显存优化;
- ms-SWIFT 则是ModelScope生态内的专用工具,适合依赖该生态的开发者。
选择时需根据场景判断:若需训超大规模模型或分布式部署,选Colossal-AI;若追求单卡高效微调,选Unsloth;若需多场景灵活配置,选LLaMA-Factory;若深度使用ModelScope,选ms-SWIFT。
一起交流
分享你的思考,让讨论更进一步。