QLoRA
1.摘要
作者提出了QLoRA,一种有效的微调方法,可以减少内存使用,足以在单个48 GB GPU上微调 65B 参数模型,同时保留完整的 16位 微调任务性能。
QLoRA 通过冻结的4位量化预训练语言模型将梯度反向传播到低秩适配器(LoRA)中。
QLORA引入了许多创新来节省内存而不牺牲性能:
(a)4位 NormalFloat(NF4),一种对于正态分布权重理论上最优的新数据类型
(B)双重量化,通过量化 量化常数 来减少平均内存占用,以及
(c)分页优化器来管理内存尖峰。
我们使用 QLoRA 对 1,000 多个模型进行微调,我们的研究结果表明,QLoRA 在一个小的高质量数据集上进行微调,即使使用比以前的 SoTA 更小的模型,也会产生最先进的结果。此外,我们发现,目前的聊天机器人基准是不值得信赖的准确评估聊天机器人的性能水平。
2.引言
QLoRA 使用高精度的计算把模型量化到 4bit ,然后增加一小部分可训练 LoRA 参数。
QLORA将65B参数模型的微调平均内存需求从超过 780GB 的 GPU 内存减少到不到 48GB ,而与 16位 完全微调的基准相比,运行时或预测性能没有降低(很震撼!)。这标志着LLM微调可访问性的重大转变:现在最大的公开可用模型可以在单个 GPU 上进行微调。
1.量化微调后的 Guanaco 13B 性能超越了 Bard;
2.33B/65B 的版本超越了 ChatGPT
3.同样是 13B ,Guranaco 比 Vicuna 小很多
Elo Rating (不仅考虑输赢,还会考虑你对手的水平):
ELO评级是一种测量棋手或其他竞技游戏玩家相对技能水平的系统。最初为国际象棋设计,
如今ELO评级广泛应用于各种竞技游戏和体育赛事。用于评估大模型:
将ELO评级应用于大型模型(如AI或机器学习模型)时,可以按照类似的原则进行:
- 设定基准:确定一个基线模型作为参照点,给予一个初始ELO分数。
- 比较性能:在特定任务或挑战中,将新模型与基线模型进行比较。
- 调整评分:根据新模型相对于基线模型的表现,上调或下调其ELO分数。
- 持续更新:随着模型的不断迭代和优化,持续更新其ELO评分,以反映其在特定任务上的相对能力。
除了展示QLORA恢复了16位性能,并训练了最先进的聊天机器人Guanaco,论文还分析了训练模型的趋势。
首先,结果发现数据质量比数据集大小更重要 ,例如,一个 9k 样本数据集(OASST1)在聊天机器人性能上超过了一个450k样本数据集(FLAN v2,抽样子集)即使两者都旨在支持指令遵循泛化。
其次,论文展示了在 Massive Multitask Language Understanding (MMLU)基准测试上的强劲表现并不意味着在 Vicuna 聊天机器人基准测试上同样表现强劲,反之亦然------换句话说,对于给定任务,数据集的适用性比大小更重要。
图1:不同的调优方法及其对内存的要求。QLORA比LoRA提高了将Transformer模型量化到4位精度,并使用分页优化器来处理内存峰值
3.背景
1.块状 k-bit 量化
**块状 k-bit 量化(**Block-wise k-bit Quantization):这是一种数据压缩技术,通过减少表示数据的比特数(如从32位浮点数到8位整数)来减少模型大小。
为了有效使用低比特数据类型的整个范围,通常会通过最大绝对值归一化输入数据。
然而,这种方法存在一个问题:如果输入数据中有极大或极小的异常值,一些量化区间将不会被充分利用。
为了解决这个问题,可以将输入数据划分为块,每个块独立进行量化。
当输入张量中存在较大幅度的值时,传统的量化方法可能会导致某些量化区间未被充分利用。为了解决这个问题,提出了一种将输入张量分块处理的方法,每个块都有自己的量化常数 c。具体如下:将输入张量 X ∈ R b × h X∈R^{b×h} X∈Rb×h 分成大小为 B B B 的 n n n 个连续块,然后将线性段切分为 n = ( b × h ) / B n=(b×h)/B n=(b×h)/B个块,独立地对这些块进行量化,即使用上面的 等式(1) 创建一个量化张量和 n n n 个量化常数 c i c_i ci 。这种方法的目的是通过独立地处理每个块,避免了异常值的影响,并确保了所有量化区间都能够被充分利用。
2.LoRA
讲过了
3.参数高效微调的内存需求
LoRA内存需求的讨论涉及到适配器数量和大小,因为LoRA的内存占用较小,因此可以使用更多的适配器来提高性能。
尽管LoRA被设计为参数高效微调(PEFT)方法,但LLM微调的大部分内存占用来自激活梯度,而不是学习到的LoRA参数。梯度检查点技术能够减少输入梯度的内存占用,但过度减少 LoRA 参数数量只会带来微小的内存优势。因此,可以使用更多的适配器,而不会显著增加整体训练内存占用量,这对于恢复完整的 16 位精度性能至关重要。
4.QLoRA 微调
技术概述 :通过提出的两种技术实现了高保真度的4位微调------4-bit NormalFloat(NF4)量化 和 双重量化 。此外,论文引入了分页优化器,以防止在梯度检查点期间内存峰值导致内存不足错误,这种错误传统上使得在单台机器上对大型模型进行微调变得困难。
存储与计算数据类型 :QLORA有一个低精度的存储数据类型,通常是4比特,和一个通常为BFloat16的计算数据类型。在实践中,这意味着每当使用QLORA的权重张量时,将张量反量化到BFloat16,然后执行16位的矩阵乘法。
1.4bit NormalFloat 量化
量化基础 - 正态浮点(NF)数据类型 : 正态浮点(NormalFloat, NF)数据类型是建立在分位数量化的基础上。分位数量化是一种信息论上的最优数据类型,它确保每个量化区间有相同数量的输入张量值。通过经验累积分布函数估算输入张量的分位数来实现。
分位数量化的局限性与近似算法: 分位数量化的主要局限在于估算过程的高成本。因此,采用了如 SRAM 分位数这样的 快速近似算法 来估算分位数。由于这些算法的近似性质,对于异常值(通常是最重要的值)会有较大的量化误差
利用固定分布避免高成本估算和近似误差: 当输入张量来自于一个固定分布(仅在量化常数上有所不同)时,可以避免昂贵的分位数估算和近似误差。在这种情况下,输入张量有相同的分位数,使得精确的分位数估算在计算上可行。
预训练神经网络权重的分布转换 : 预训练神经网络权重通常呈零中心正态分布 ,标准差为 σ σ σ。通过缩放 σ σ σ,可以将所有权重转换为单一固定分布,使分布完全符合数据类型的范围。对于该数据类型,设定了任意范围 [−1, 1]。因此,数据类型的分位数和神经网络权重都需要规范化到这个范围内。
针对正态分布的量化数据类型计算: 对于范围在 [−1, 1] 内的零均值正态分布,其标准差为任意 σ 的信息论上最优数据类型的计算方式如下:
- 估算理论上的 N ( 0 , 1 ) N(0,1) N(0,1) 分布的 2 k + 1 2^k+1 2k+1 个分位数,以获得 k k k 位的正态分布量化数据类型;
- 将此数据类型的值规范化到 [−1, 1] 范围内;
- 通过绝对最大值重缩放将输入权重张量规范化到 [−1,1] 范围内进行量化。
- 步骤 (3) 相当于重新缩放权重张量的标准差,以匹配 k k k 位数据类型的标准差。更具体地,数据类型的 2 k 2^k 2k 个值 q i q_i qi 的估算方式为: q i = 1 2 ( Q X ( i 2 k + 1 ) + Q X ( i + 1 2 k + 1 ) ) q_i=\frac12(Q_X(\frac i{2^k+1})+Q_X(\frac{i+1}{2^k+1})) qi=21(QX(2k+1i)+QX(2k+1i+1)) ,其中 Q X ( ⋅ ) Q_X(·) QX(⋅) 是标准正态分布 N ( 0 , 1 ) N(0,1) N(0,1) 的分位数函数。
对称量化的问题与非对称数据类型的创建 : 对于对称的 k k k 位量化,上述方法无法精确表示零,而零的精确表示对于量化填充和其他零值元素而无误差是重要的。为了确保一个离散的零点为 0,并使用所有的 2 k 2^k 2k 位表示 k k k 位数据类型,通过估算两个范围的分位数 q i : 2 ( k − 1 ) q_i:2^{(k−1)} qi:2(k−1) 用于负部分和 2 ( k − 1 ) + 1 2^{(k−1)}+1 2(k−1)+1 用于正部分,然后将这些 q i q_i qi 集合统一并移除两个集合中都出现的一个零。所得到的数据类型被称为 k k k 位正态浮点(NFk),因为该数据类型对于零中心的正态分布数据是信息论上最优的。这种数据类型的具体值可以在附录E中找到。
2.双重量化
双重量化(DQ)的引入 : 引入了双重量化(Double Quantization, DQ)的概念,这是一种对量化常数进行二次量化的过程,目的是为了进一步节省内存。尽管精确的4位量化需要小的块大小,但这也带来了相当大的内存开销。例如,使用32位的量化常数和64的块大小对于权重 W W W ,平均每个参数的量化常数增加了 32 / 64 = 0.5 32/64=0.5 32/64=0.5 位。
双重量化的具体过程 : 具体来说,双重量化将第一次量化的量化常数 c 2 F P 32 c_2^{FP32} c2FP32
作为第二次量化的输入。这一第二步产生了量化的量化常数 c 2 F P 8 c_2^{FP8} c2FP8 和第二层量化常数 c 1 F P 32 c_1^{FP32} c1FP32 。研究者使用 256 块大小的 8 位浮点数进行第二次量化,根据Dettmers和Zettlemoyer的研究,8位量化并没有观察到性能下降。由于 c 2 F P 32 c_2^{FP32} c2FP32 是正数,在量化之前从 c 2 c_2 c2 中减去均值以使值围绕零中心,并利用对称量化。
内存占用的减少 :平均来说,对于 64 的块大小,这种量化方法将每个参数的内存占用从 32 / 64 = 0.5 32/64=0.5 32/64=0.5 位降低到 8 / 64 + 32 / ( 64 ⋅ 256 ) = 0.127 8/64+32/(64·256)=0.127 8/64+32/(64⋅256)=0.127 位,每个参数减少了 0.373 位的内存占用。
3.优化器状态分配分页内存
使用NVIDIA统一内存特性: 论文中提到了使用NVIDIA的统一内存(Unified Memory)特性,这个特性可以在 GPU 偶尔内存不足的情况下,自动在 CPU 和 GPU 之间进行页到页的传输,以保证 GPU 处理过程中无误差。这个特性类似于 CPU RAM 和硬盘之间的常规内存分页。
优化器状态的内存分配: 作者使用这个特性来为优化器状态分配分页内存。当 GPU 内存不足时,这些状态会自动被逐出到 CPU RAM,然后在优化器更新步骤中需要内存时,再分页回 GPU 内存。
4.QLoRA
QLORA定义与线性层的量化实现: 在量化基础模型中,对于单个线性层和单个LoRA适配器,QLORA被定义为:
其中 doubleDequant(·) 被定义为:
双重解量化及其内存优化 : 使用 N F 4 NF4 NF4 作为 W W W 的数据类型和 FP8 作为 c 2 c_2 c2 的数据类型。为了获得更高的量化精度, W 使用 64 的块大小;而为了节省内存, c 2 c_2 c2 使用 256 的块大小。 doubleDequant 函数通过两次解量化过程,将存储数据类型转换为计算数据类型。
参数更新与梯度计算 : 对于参数更新,只需要计算适配器权重的误差梯度 ∂ E / ∂ L i ∂E/∂L_i ∂E/∂Li ,而不是4位权重的梯度 ∂ E / ∂ W ∂E/∂W ∂E/∂W 。然而,计算 ∂ E / ∂ L i ∂E/∂L_i ∂E/∂Li 需要通过方程式(5)进行,其中包括从存储数据类型 W N F 4 W_{NF4} WNF4 到计算数据类型 W B F 16 W_{BF16} WBF16 的反量化,以计算 ∂ X / ∂ W ∂X/∂W ∂X/∂W 的 BFloat16 精度导数。
QLORA的数据类型总结: QLORA使用两种数据类型:一种是存储数据类型(通常是 4 位的 NormalFloat),另一种是计算数据类型(16 位的 BrainFloat )。在正向和反向传播过程中,将存储数据类型反量化为计算数据类型,但只为 LoRA 参数(使用 16 位 BrainFloat )计算权重梯度。
5.QLoRA vs Standard Finetuning
论文结果一致表明,配备 NF4 数据类型的 4 位 QLORA 在学术基准测试中的表现可与 16 位完全微调和 16 位 LoRA 微调相媲美,这些基准测试都有成熟的评估设置。
论文还展示了 NF4 比 FP4 更有效,以及双重量化并不会降低性能。综合来看,这为 4 位 QLORA 调整可靠地达到与 16 位方法相匹配的结果提供了有力证据。
1. QLoRA 的超参数
作者对 LoRA 进行了下面的超参数搜索
LoRA dropout { 0.0,0.05,0.1},
LoRA r { 8,16,32,64,128,256},
LoRA layer {key+query,all attention layers,all FFN layers,all layers,attention + FFN output layers}。
我们保持 LoRA α 固定并搜索学习率,因为LoRA α始终与学习率成正比。
我们发现 LoRA dropout 0.05 对于小模型(7B,13B)有用 ,但对于较大的模型(33B,65B)无效。
我们发现,如果在所有层上使用 LoRA,则 LoRA r 与最终性能无关,如图所示
我们使用与 Wang 等人相同的 Super-Natural Instruction 数据集预处理。然而,我们将训练数据分割为训练和验证数据集,从而使我们能够执行更严格的超参数调整和早期停止。
我们使用本文中描述的相同超参数来训练 Super-Natural Instruction 数据上的各种 T5 模型大小。
对于小型、中型和大型T5型号 ,我们使用 LoRA r = 16,
对于T5 xl和xxl型号 ,我们使用LoRA r = 64。
我们还在所有实验中使用LoRA α = 64 ,并且没有LoRA dropout
6.训练最先进的聊天机器人实验设置详情
1.数据集
We include datasets obtained through crowd-sourcing (OASST1 [31],
HH-RLHF [4]), distillation from instruction-tuned models (Alpaca [55], self-instruct [59], unnatural instructions [26]) , corpora aggregations (FLAN v2 [12]), as well as hybrids (Chip2 [32], Longform [30]). These datasets cover different languages, data sizes, and licenses.
2.超参数
在 QLoRA 微调实验中,发现超参数在数据集上具有很大的鲁棒性。
我们使用MMLU 5-shot dev set进行验证和超参数调优。在我们所有的实验中,我们使用具有双重量化和 bf16 数据类型的 NF4。
设置 LoRA r=64, \alpha = 16 ,把 LoRA module 附加在基础模型的所有线性层。
Adam 的 beta2 = 0.999
最大梯度范数 = 0.3
LoRA 的 dropout =0.1(13B中),dropout=0.05(33B 和 65B 中)
根据之前关于指令微调的工作,在对其他线性和余弦调度器进行基准测试之后,我们使用 constant 学习率调度器。
我们使用 group-by-length 将相同批次中相似长度的示例分组(注意这将产生振荡损失曲线)
QLoRA微调的训练超参数,在不同的数据集和不同的模型大小
3.哪个更重要:指令微调数据集大小还是数据集质量?
数据集的适用性 比 数据集的大小 更重要。
为了了解数据集质量与数据集大小的影响,我们对至少有150,000个样本的大型数据集(Chip 2,FLAN v2,Unnatural Instructions)进行了实验,将其分为50,000,100,000和150,000大小的数据集,并检查了结果趋势,如表11所示。
我们发现,增加数据集大小 和 增加epoch数量 只能略微提高MMLU(0.0 - 0.5 MMLU),而数据集之间的差异高达40倍(1.5 - 8.0 MMLU)。
这清楚地表明,数据集质量 而不是数据集大小对平均MMLU准确性至关重要。我们对聊天机器人的性能获得了类似的发现,如中所讨论的。
7.超参数微调
1.超参数
-
量化相关参数
- 量化位宽(bits)
- 建议:
- 4-bit 量化
- 3-bit 可能导致显著性能下降
- 5-bit 以上可能导致失去量化优势
- 建议:
- 量化类型(quantization_type)
- NormalFloat4(默认):大多数场景适用
- Float4:对特殊值处理更好,但是开销大
- 建议从 NormalFLoat4 开始
- 量化位宽(bits)
-
分组大小(group_size)
- 决定多少参数共享一个缩放因子
- 建议
- 默认是 128
- 较小的值(64)可能提高精度但是增加内存
- 较大的值(256)节省内存但可能影响性能
-
双重量化(double_quantization)
- 是否对量化器本身进行二次量化
- 建议
- 默认开启
- 可以节省 2-3% 的内存
- 对性能影响极小
-
LoRA 相关参数
- rank:通常设置为 64-256
- alpha:与 rank 相同
- target_modules:比传统的 LoRA 可以设置更多的层
-
典型配置示例
pythonqlora_config = { # 量化参数 "bits": 4, "group_size": 128, "double_quant": True, "quant_type": "nf4", # LoRA参数 "lora_r": 64, "lora_alpha": 64, "target_modules": ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], # 分页参数 "window_size": 256, # 页面大小 "page_size": "2GB" # 单页大小 }
2.最佳实践建议
- 不同规模模型的参数配置
- 7B 模型:
- rank=64
- target_modules=所有线性层(也包括了q, k, v)
- group_size=128
- 13B-70B模型:
- rank: 32-64
- 重点关注attention相关层
- group_size: 128-256
- 70B以上模型:
- rank: 16-32
- 仅关注关键层
- group_size: 256
- 7B 模型:
- 内存优化策略
- 开启梯度检查点(gradient checkpointing)
- 是用分页系统(paged optimizer)
- 适当的调整 batch size 和 gradient accumulation
- 稳定性优化
- 使用 BF16 而不是 FP16
- 适当增加预热步数
- 考虑使用 gradient clipping
3.关键注意事项
- 显存管理:
- 合理设置 batch size
- 利用梯度累积
- 注意监控显存使用
- 训练稳定性
- 确保学习率较小(1e-4 ~ 5e-4)
- 使用足够的预热步骤
- 监控训练损失波动
- 常见问题解决
- 如果出现 OOM:
- 减小batch size
- 增加 group_size
- 减少 target modules
- 如果性能不够好
- 增加 rank
- 添加更多的 target modules
- 调整量化参数
- 如果出现 OOM:
- 与传统 LoRA 相比的优势
- 显著降低显存需求
- 支持更大的 rank 和 更多的 target modules
- 保持接近全参数微调的性能
- 特别建议
-
起步配置
pythonstarter_config = { "bits": 4, "group_size": 128, "double_quant": True, "lora_r": 64, "target_modules": ["q_proj", "v_proj"] }
-
进阶配置
- 逐步增加 target modules
- 根据显存情况调整 rank
- 实验不同的量化参数
-
4.Hugging Face 中
-
核心参数配置
pythonfrom peft import LoraConfig from transformers import BitsAndBytesConfig # 量化配置 quant_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True, ) # LoRA配置 qlora_config = LoraConfig( r=64, lora_alpha=128, target_modules=["q_proj", "v_proj"], lora_dropout=0.1, )
-
量化参数
python# 基础配置 basic_quant_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True, ) # 高性能配置 advanced_quant_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, )
-
LoRA 参数
- 小模型:r=64, lora_alpha=128
- 中模型:r=32, lora_alpha=64
- 大模型:r=16, lora_alpha=32
5.Hugging Face 共同最佳实践
python
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments
)
from peft import LoraConfig, get_peft_model
import torch
# 1. 更详细的量化配置
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4", # 使用NF4而不是FP4
bnb_4bit_compute_dtype=torch.float16,
# 以下是一些额外的优化配置
llm_int8_enable_fp32_cpu_offload=True, # 启用CPU卸载
llm_int8_skip_modules=None, # 跳过某些模块的量化
llm_int8_threshold=6.0, # 量化阈值
llm_int8_has_fp16_weight=False # 是否保持fp16权重
)
# 2. 更详细的LoRA配置
lora_config = LoraConfig(
r=64, # 论文中使用了较大的秩
lora_alpha=16, # alpha值调整
target_modules=["query_key_value"],
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM",
# 以下是一些额外的LoRA配置
inference_mode=False, # 训练模式
init_lora_weights=True, # 初始化LoRA权重
modules_to_save=None # 额外保存的模块
)
# 3. 加载模型
model = AutoModelForCausalLM.from_pretrained(
"your-model-name",
quantization_config=bnb_config,
device_map="auto",
torch_dtype=torch.float16,
# 以下配置有助于内存管理
max_memory={0: "24GB"}, # 为每个GPU分配内存
offload_folder="offload", # 设置权重卸载目录
offload_state_dict=True # 启用状态字典卸载
)
# 4. 应用LoRA
model = get_peft_model(model, lora_config)
# 5. 训练配置
training_args = TrainingArguments(
output_dir="./qlora_output",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
gradient_checkpointing=True, # 启用梯度检查点
max_grad_norm=0.3, # 梯度裁剪
num_train_epochs=3,
learning_rate=2e-4,
lr_scheduler_type="cosine", # 使用余弦学习率调度
warmup_ratio=0.03, # 预热比例
fp16=True, # 使用混合精度
logging_steps=10,
save_strategy="epoch",
# 以下是一些内存优化配置
gradient_checkpointing_kwargs={"use_reentrant": False},
fsdp_config={
"fsdp_offload_params": True,
"fsdp_state_dict_type": "FULL_STATE_DICT",
}
)