LLM Systems 21: Parameter Efficient Fine-Tuning for LLM

0. Recap

  • Direct quantization for low-bit numbers
    • absmax: linearly scale according to max abs value
    • zero-point: finding zero-point and scale
  • Layer-wise quantization approaches
    • AdaQuant / KD: ZeroQuant / LLM.int8()
  • GPTQ
    • layer-wise quantization + compensation for errors + precompute
    • accurately compress some of the largest publicly-available models down to 3 and 4 bits, and bring end-to-end speedups

1. Overview of Parameter Efficient Fine-Tuning

Parameter Efficient Fine-tuning (PEFT)

PEFT 的核心思路是仅更新模型的一小部分参数或低秩矩阵,冻结大部分原始参数:

  • LoRA:权重梯度、优化器状态及 Adapter 权重的显存开销大幅降低,使得微调 LLaMA-8B 的总显存需求降至 ~33GB,可单张 A100 GPU 运行。
  • 进一步结合量化(QLoRA):将预训练模型权重压缩至 4-bit,使得微调 LLaMA-8B 的显存需求进一步降低至 ~9.2GB。

PEFT Approaches 分类


  1. 选择性方法(Selective Methods):
    • 原理:仅对模型参数的特定子集进行微调。
    • 代表技术:BitFit、LN Tuning、Attention Tuning、Diff-Pruning 等。
  2. 重参数化方法(Reparameterization Methods):
    • 原理:利用低秩(Low-rank)结构来表征参数的更新量。
    • 代表技术:LoRA、ReLoRA、fLoRA、QLoRA、KronA 等。
  3. 附加方法(Additive Methods):
    • 原理:冻结原模型,通过在模型中添加额外的可训练层或参数来进行微调。主要包含两大子类:
      • Adapters:在模型内部插入小型瓶颈层(如 Adapters、Parallel Adapters、Compacter 等)。
      • Soft Prompts:在输入层或中间注意力层追加可训练的虚拟 Token/Prompt 向量(如 Prompt Tuning、Prefix-Tuning、P-tuning 等)。

Reference:

  • Scaling Down to Scale Up: A Guide to Parameter-Efficient Fine-Tuning. Lialin et al. 2024.

2. LoRA: Low-rank Adaptation


  • 核心思想:冻结预训练好的原始权重矩阵 \(W_0\),仅通过训练一个低秩分解更新矩阵 \(\Delta W\) 来拟合微调的参数变化。
  • 形式化描述:

\[ W' = W_0 + \Delta W = W_0 + A \cdot B \]

其中 \(A \in \mathbb{R}^{d \times r}\),\(B \in \mathbb{R}^{r \times d}\),且秩满足 \(r \ll d\)。

  • 作用位置:通常主要应用于 Transformer 的 自注意力层(Self-Attention) 权重矩阵(如 \(W_q, W_v\) 等),不应用于 FFN Linear 层(认为 FFN Linear 权重存储了知识)。
  • 无推理延迟(Zero Additional Latency):微调结束进行推理时,只需提前将低秩矩阵融合进主模型 \(W' = W_0 + A \cdot B\),推理计算与标准大模型完全一致,不会增加任何额外计算耗时或延迟。
  • 高效任务/领域切换:在不同下游任务间切换时,无需重新加载大模型主干,只需通过矩阵加减法替换 LoRA 权重即可:\(W'' = W' - A \cdot B + A'' \cdot B''\) ,其中 \(A'', B''\) 为新任务的 LoRA 权重。
  • 前向传播(Forward):输出计算为原始路径与低秩路径之和,即 \(h = W_0 x + A \cdot B x\)。
  • 反向传播(Backward):由于原始权重 \(W_0\) 保持冻结,只需对小矩阵 \(A\) 和 \(B\) 计算梯度:

\[ \frac{\partial L}{\partial A} = \frac{\partial L}{\partial (W_0 x + A \cdot B x)} \cdot \frac{\partial (W_0 x + A \cdot B x)}{\partial A} = g_{out} \cdot (B x)^T, \quad \\ \\ \frac{\partial L}{\partial B} = \frac{\partial L}{\partial (W_0 x + A \cdot B x)} \cdot \frac{\partial (W_0 x + A \cdot B x)}{\partial B} = (g_{out}^{T} \cdot A)^{T} x^T = (A^{T} \cdot g_{out}) x^T \]

Memory Consumption of LoRA Training

以 LLaMA-8B 为例:

  • 可训练参数量:从 80亿(8B)减至约 400万(4M),权重仅占约 8MB。
  • 优化器状态显存(Adam):从 48GB 降至仅 ~24MB。
  • 总工作显存:从全参数微调的 ~80GB 降低至 ~33GB,可直接在单张 NVIDIA A100 GPU 上运行。

实验分析与核心结论

  • 覆盖任务与基线:在自然语言理解(NLU,如 RoBERTa/DeBERTa 运行 GLUE 榜单)、自然语言生成(NLG,如 GPT-2/GPT-3 175B)以及数学推理(GSM8K)等多个任务上,与全参数微调及 Adapter、Prefix-Tuning 等基线进行了对比。
  • 秩 \(r\) 的选择:实验表明,增加秩 \(r\) 并不一定会包含更多有意义的子空间。设置极小的秩(如 \(r = 8\) 或 \(r = 16\))就能达到甚至超越全参数微调的效果。
  • 大模型扩展性(Scalability):随着模型参数扩展至 GPT-3 175B 和 LLaMA-70B,LoRA 展现出极高的参数利用效率和出色的任务性能。

3. QLoRA: Quantization + Low-rank training

3.1 Overview

  • QLoRA 属于 Quantization-Aware Training (QAT) 类方法。
    • Quantization-Aware Training (QAT):integrates the weight conversion process during the training stage. often superior model performance.
  • QLoRA = Low-rank + Quantized training
  • 主要创新点:
    • 4-bit 正态分布参数存储(4-bit Normal Float for storage);
    • 双重量化(Double Quantization);
    • 分页优化器 (Paged Optimizers);
  • 使用 BF16 类型进行计算(计算到某个层时,先反量化为 BF16 类型再计算)。

QLoRA algorithm 整体流程

  • 两种数据类型:
    • NF4(4-bit NormalFloat)存储格式;
    • BF16 计算格式;
  • 算法流程:
    • 将模型权重双重量化为 NF4 格式。
    • 将权重双重反量化为 BF16 格式。
    • 用 BF16 格式进行 forward 和 backward 计算。
    • 仅对 LoRA 参数计算梯度(BF16 格式)。

3.2 Method

3.2.1 4-bit NormalFloat (NF4) 数据类型

  • 背景与问题:标准的 4-bit 线性量化(如 absmax)采用均匀划分区间的方式,在处理非均匀分布的数据时会引入较大的量化误差。
  • 正态分布设计:由于预训练模型的权重通常呈现均值为 0 的正态分布,NF4 专为这种分布建立了包含 16 个非线性采样点的分位数查找表(Lookup Table)。
  • 量化步骤:
    1. 将权重按 64 个数字划分为一个 Block(Block size = 64)。
    2. 找到该 Block 内的最大绝对值作为缩放因子(Scaling Factor),计算规范化数值 \(\hat{x} = \frac{x}{\text{scale}}\) 。
    3. 将规范化数值在 NF4 查找表中匹配最近邻点,输出对应的量化索引(范围为 [-7, 8])。
    4. 整个模型共享一个 NF4 Lookup Table,每个 block 有各自的 scale 和量化后数值表示。
  • 反量化:前向计算时,通过查找表将 4-bit 索引映射回浮点数,再乘以缩放因子恢复原值。

NF4 量化与反量化过程示意图



Lookup Table 构建


  • 直观理解:
    • 由于预训练大模型权重在规范化后呈标准正态分布 \(\mathcal{N}(0, 1)\),传统 INT4 的均匀划分(absmax)会导致高密度区域(0 附近)量化误差极高。NF4 的核心就是在累积概率分布(CDF)上做等概率分位数划分,使每个 4-bit 量化区间在信息论上具有相同的期望概率,从而最大程度降低量化信息损失。
  • 构建流程:
    1. 假设分布:假定规范化后的模型权重服从标准正态分布 \(\mathcal{N}(0, 1)\)。
    2. 概率分割与尾部裁剪:将累积概率分布划分,同时裁剪掉两端极小概率的极端尾部(避免离群点拉大区间)。
    3. 分位数映射 (Z-score):通过正态分布的反累积分布函数(Inverse CDF / Quantile Function),求出概率点对应的原始变量取值(Z-score)。
    4. 归一化缩放:将得到的 Z-score 统一除以最大绝对值,缩放到 \([-1.0, 1.0]\) 区间,得到全局固定的 16 个 NF4 标量查找表。

NF4 v.s. Absmax 与量化方法的先验/假设

量化算法设计的本质:量化器(Quantizer)的效率,取决于其先验假设(Prior)与真实数据分布(Data Distribution)的匹配程度。

从信息论与统计学的角度来看,对比 NF4 与 Absmax:

  1. 传统 Absmax / 线性量化(Uniform Quantization)
    • 先验假设:权重服从均匀分布 \(W \sim \text{Uniform}[-a, a]\)。
    • 设计策略:采用 等间距步长(Fixed Step Size) 划分区间。
    • 痛点:在大模型中,极少有权重会落在两端极端位置。按均匀分布划分,会导致大量采样点浪费在几乎没有权重的“空白区域”,而权重极其密集的 0 附近区域却因为分辨率不够而产生了巨大的量化噪声。
  2. QLoRA 的 NF4(NormalFloat 4)
    • 先验假设:权重服从正态分布 \(W \sim \mathcal{N}(0, \sigma^2)\)。
    • 设计策略:采用 等概率分位数(Equi-probable Quantiles) 划分区间。
    • 优势:把采样点大量“压”在概率密度极高的高频区域(0 附近),而在稀疏的尾部区域拉大间距。这本质上是在已知正态分布先验下,实现了信息熵最大化(Entropy Maximization)——让每一个 4-bit 编码所代表的概率质量相等。

从 NF4 的原理更进一步,任意分布指定目标量化 bits(假设希望量化为 \(b\) 个 bits),如何获取接近最优的量化结果?

  • 只要已知任意连续概率密度函数 \(f(x)\),通过将其累积分布函数(CDF)在 \([0, 1]\) 区间内做 \(2^b\) 等分,再通过反函数 \(F^{-1}(p)\) 求得映射标量,就能实现熵最大化(Maximum Entropy)的标量量化最优解。
  • 理论基础:
    • 信息熵最大化(Max-Entropy): 如果把 CDF 均匀切分为 \(2^b\) 份,就能确保每个量化区间被数据落入的概率严格等于 \(\frac{1}{2^b}\)。此时,量化后的离散随机变量达到了最大信息熵 \(H = b\text{ bits}\),没有浪费任何 1 bit 的编码容量。
    • NF4 的定位: NF4 恰恰就是这个通用理论在 “先验分布 \(f(x) = \mathcal{N}(0, 1)\)” 且 “目标位数 \(b=4\)” 时的特例实现。
  • 为什么这一量化方式没有被广泛使用?
    • 对模型权重(静态)可行,但是对于激活值(分布动态变化)做量化,动态分布拟合的计算开销很大,导致量化耗时增加,无法补偿带来的量化精度提升。
    • 计算时需要先查找 Lookup Table 反量化为 BF16 等格式计算,无法利用 GPU 硬件提供的原生 int4/int8 等计算加速。
    • 极端离群点(Outliers)与重尾分布的破坏(Salient Outliers Problem):纯粹按 CDF 概率均匀切分时,由于离群点的发生概率极低(如小于 \(0.01\%\)),它们会被强行压缩挤进最大或最小的那个量化 Bucket 里,导致离群点的绝对数值精度发生巨大失真。而在大模型中,这些极少数的离群点往往承载了模型关键的注意力与推理能力。
      • 后来的工作(如 AWQ、SmoothQuant、SpQR)退而求其次:不强行做纯 CDF 量化,而是专门把这些 Outlier 抽出来保留为 FP16,其余正常分布部分再做量化。

3.2.2 双重量化 (Double Quantization, DQ)

  • 动机:为了保证量化精度,NF4 采用了较小的 Block size(64),但这导致每个 Block 需存储一个 FP32 缩放因子,产生 \(32 / 64 = 0.5\text{ bits/param}\) 的额外显存开销。
  • 双重压缩机制:对第一级量化产生的缩放因子进行二次量化。它将每 256 个 FP32 缩放因子看作一组,采用 FP8 进行二次量化,仅保留一个 FP32 存储第二次量化的 Absmax 缩放因子。
  • 显存节省:成功将缩放因子的显存开销从 0.5 bits/param 大幅压缩至约 0.127 bits/param(0.125(\(\frac{8}{64}\)) + 0.002(\(\frac{32}{64 \times 256}\)) bits/param)。
  • 前向计算公式:

\[ Y^{\text{BF16}} = X^{\text{BF16}} \text{doubleDequant}(c_1^{\text{FP32}}, c_2^{\text{k-bit}}, W^{\text{NF4}}) + X^{\text{BF16}} L_1^{\text{BF16}} L_2^{\text{BF16}} \]

其中 \(\text{doubleDequant}(\cdot)\) 函数在运行时将 NF4 权重重新解压为 BF16 参与矩阵运算。

3.2.3 分页优化器 (Paged Optimizers)

  • 动机:在大模型训练过程中,激活值或梯度数据的突发峰值容易导致 GPU 显存溢出(OOM)。
  • 工作机制:借助 NVIDIA Unified Memory(统一内存)技术,在内存与显存之间实现自动的页对页(Page-to-page)传输。
    • 类似于操作系统中 CPU 内存与磁盘之间的虚拟内存换页。
  • 自动调度:将优化器状态分配在分页内存中;当 GPU 显存出现临界溢出时,系统会自动将优化器状态换出(Evict)至 CPU 内存,待进入优化器更新阶段时再重新拉回 GPU,有效规避了训练中断。

4. Code Walkthrough

4.1 LoRA Code Walkthrough

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class LoRALayer():
def __init__(
self,
r: int,
lora_alpha: int,
lora_dropout: float,
merge_weights: bool,
):
# LoRA rank
self.r = r
self.lora_alpha = lora_alpha
# Optional dropout
if lora_dropout > 0.:
self.lora_dropout = nn.Dropout(p=lora_dropout)
else:
self.lora_dropout = lambda x: x
# Mark the weight as unmerged
self.merged = False
self.merge_weights = merge_weights


class Linear(nn.Linear, LoRALayer):
# LoRA implemented in a dense layer
def __init__(
self,
in_features: int,
out_features: int,
r: int = 0,
lora_alpha: int = 1,
lora_dropout: float = 0.,
fan_in_fan_out: bool = False, # Set this to True if the layer to replace stores weight like (fan_in, fan_out)
merge_weights: bool = True,
**kwargs
):
nn.Linear.__init__(self, in_features, out_features, **kwargs)
LoRALayer.__init__(self, r=r, lora_alpha=lora_alpha, lora_dropout=lora_dropout,
merge_weights=merge_weights)

self.fan_in_fan_out = fan_in_fan_out
# Actual trainable parameters
if r > 0:
# LoRA 可学习参数
self.lora_A = nn.Parameter(self.weight.new_zeros((r, in_features)))
self.lora_B = nn.Parameter(self.weight.new_zeros((out_features, r)))
self.scaling = self.lora_alpha / self.r
# 冻结原始参数
# Freezing the pre-trained weight matrix
self.weight.requires_grad = False
self.reset_parameters()
if fan_in_fan_out:
self.weight.data = self.weight.data.transpose(0, 1)

def reset_parameters(self):
nn.Linear.reset_parameters(self)
if hasattr(self, 'lora_A'):
# initialize B the same way as the default for nn.Linear and A to zero
# this is different than what is described in the paper but should not affect performance
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
# B 要全 0 初始化,确保初始输出于原始模型一致
nn.init.zeros_(self.lora_B)

def train(self, mode: bool = True):
def T(w):
return w.transpose(0, 1) if self.fan_in_fan_out else w
nn.Linear.train(self, mode)
# 模式切换时的权重融合与逆融合逻辑
if mode:
if self.merge_weights and self.merged:
# Make sure that the weights are not merged
# 切回训练模式:从主模型权重中扣除 Adapter 权重
if self.r > 0:
self.weight.data -= T(self.lora_B @ self.lora_A) * self.scaling
self.merged = False
else:
if self.merge_weights and not self.merged:
# Merge the weights and mark it
# 进入推理模式:将 Adapter 权重加合并进主模型,消除推理延迟
if self.r > 0:
self.weight.data += T(self.lora_B @ self.lora_A) * self.scaling
self.merged = True

def forward(self, x: torch.Tensor):
def T(w):
return w.transpose(0, 1) if self.fan_in_fan_out else w
if self.r > 0 and not self.merged:
# 训练或未合并状态:计算“原始矩阵路径 + 低秩旁路”
result = F.linear(x, T(self.weight), bias=self.bias)
result += (self.lora_dropout(x) @ self.lora_A.transpose(0, 1) @ self.lora_B.transpose(0, 1)) * self.scaling
return result
else:
# 已合并状态:直接执行单次普通线性计算
return F.linear(x, T(self.weight), bias=self.bias)

4.2 QLoRA code

代码:

训练加载配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import torch
from transformers import AutoModelForCausalLM, BitsAndBytesConfig

# 1. 设置 QLoRA 4-bit / 双重量化配置
compute_dtype = torch.bfloat16

quantization_config = BitsAndBytesConfig(
load_in_4bit=True, # 启用 4-bit 量化加载
load_in_8bit=False,
llm_int8_threshold=6.0,
llm_int8_has_fp16_weight=False,
bnb_4bit_compute_dtype=compute_dtype, # 设置前向/反向计算的精度 (BF16)
bnb_4bit_use_double_quant=True, # 启用双重量化 (Double Quantization)
bnb_4bit_quant_type="nf4", # 指定使用 4-bit NormalFloat (NF4) 数据类型
)

# 2. 从 Hugging Face 加载大模型(如 Llama-2-70B)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-70b",
quantization_config=quantization_config,
device_map="auto"
)

5. Conclusion

  • LoRA
    • LoRA/CIAT enables cost-effective adaptation of large models by modifying fewer parameters (low-rank).
    • Scalability: Effective for giant models like GPT-3, making adaptation more accessible.
    • Low-Data Efficacy: Superior in low-data settings, reducing the need for large datasets.
  • QLoRA
    • QLoRA: double quantizing weights using NF4, computing in BF16
    • QLoRA matches original Low-rank Adapter performance.
    • 1st method that enables fine-tuning of 33B LLM on a single consumer GPU

LLM Systems 21: Parameter Efficient Fine-Tuning for LLM
https://arcsin2.cloud/posts/2026/11/3838233336/
作者
arcsin2
发布于
2026年11月14日
许可协议