📍 你在这里 训练进阶 · 第 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 微调」:不拆发动机!而是给发动机加两个“小配件”(对应两个“低秩矩阵”):
    1. 第一个配件是“小转换器”(矩阵 W_A):比如只有“1024×16”个零件(16 就是“低秩”,远小于 1024),负责把“原发动机的动力”转成一种“中间信号”;
    2. 第二个配件是“小调节器”(矩阵 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)?

原理和上面的小例子完全一样,只是规模更大:

  1. ΔW的行数 = A的行数(1024行)
  2. ΔW的列数 = B的列数(1024列)
  3. 每个元素的计算:
    比如Δ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的优势:

  1. 显存优化: 只需要调整少量的参数(适配器),显著减少了显存需求,适合显存有限的GPU。
  2. 计算效率: 微调过程中的计算负担也更轻,因为减少了需要调整的参数量。
  3. 灵活性: 可以与现有的预训练模型轻松结合使用,适用于多种任务,如文本生成、分类、问答等。

而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的量化本质是“用统计规律(分块、正态分布映射)实现高精度到低精度的损失可控压缩”,关键技术点包括:

  1. 分块处理:让每个block的量化更精准;
  2. NF4格式:匹配权重分布,减少量化误差;
  3. 动态反量化:在计算时临时恢复精度,不影响模型运算;
  4. 双重量化:进一步压缩量化参数的存储。

通过这些手段,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。

— 感谢阅读 —

一起交流

分享你的思考,让讨论更进一步。