Lazy loaded image
记录- QLoRA微调
字数 1957阅读时长 5 分钟
2025-8-30
2025-9-6
AI智能摘要
GPT
这里是萌新AI,这篇文章介绍了博主对 QLoRA 微调方法的理解和代码实践。QLoRA 结合了 LoRA 微调和量化技术,核心包括 NF4 量化、双重量化和页优化。NF4 量化是一种基于正态分布的最优 4-bit 量化方法,通过计算分位数生成 16 个离散值。文章还简要提及了代码准备和执行流程,旨在记录学习过程以供回顾。
URL
type
status
date
slug
summary
tags
category
icon
password
😀
最近,博主使用QLoRA微调模型,所以想通过本篇博客记录博主对QLoRA的理解,以及代码实践。方便后期的回顾。

📝 QLoRA

QLora集成了Lora微调和量化两种操作。QLoRA核心包含4-bit NormalFloat量化、双重量化和页优化三种核心操作,LoRA微调的思想在该篇博客有相关记录,请点击该链接

NF4量化

QLoRA作者构建了新的数据类型NF4。NF4与FP4计算方式不同。NF4是零中心正态分布权重设计的信息论最优4-bit量化。简单来讲就是NF4在N(0,1)标准正态分布中取2^4+1个分位点,接着计算分位点对应的分位数,然后将分位数归一化到[-1,1]的范围内。
分位点:0/17,1/17,2/17,3/17,…,16/17,17/17
分位数:公式如下:
根据论文结果16个离散值,博主猜测(没有计算):
归一化:-1,…,0,…,1
最终的16个离散值:
那么,已知16个离散值如何进行量化呢?例子如下:
假设:模型参数W=[0.7, 0.8, 0.2, 0.4, 0.5, -0.9],则W绝对值最大值是0.9,那么NF4的量化因子c = 1/0.9 = 1.111。W的每个值乘以缩放因子W’=[ 0.7777, 0.8888, 0.2222, 0.4444, 0.5555, -0.9999]。将W‘中的每个参数与16个离散值比较,接近哪一个值便是最终的量化目标值。以0.7777为例进行比较:0.7777-0.7229568362236023=0.0547,1-0.7777=0.2223,0.2223>0.0547,0.7777接近0.7229568362236023,所以最终量化值为0.7229568362236023。

双重量化

双重量化是针对NF4量化的缩放因子进行量化。为了更好的量化模型参数,NF4采用的是分块量化。也就是将模型每层的参数分成多块,每块存放一个缩放因子。在大模型中,层数很多,每层的参数量也很多。同时,正常量化的缩放因子会保存为fp32的数据类型。这也表明了缩放因子也占用了很大的内存。
notion image
问题:这里伙伴们可能会想,那就不分块呗,使用一个缩放因子不久行了吗?
解答:这肯定是不行的。每一层的参数量都上万,将上万的参数从高比特数据类型直接映射到底比特的数据类型,仅仅使用一个缩放因子,会造成数据信息严重丢失。伙伴们也可以根据卷机中卷机核进行类比分析。eg:针对一个224*224的图像,如果直接采用224*224的卷机核提取特征,这会造成大量的图像信息丢失。如果采用3*3的卷机核逐层局部提取特征,那么最后丢失的图像信息远远小于224*224卷积核。

页优化

页优化主要是针对CUDA out of memory进行优化的。伙伴们在训练模型时,前面几个epoch正常训练,然后突然报cuda内存爆炸问题。当页优化遇到该问题时,会将部分内存参数offload到CPU。这样可以释放部分GPU的显存。

🤗 代码实践

博主能力有限尝试复现QLoRA代码,但是没有成功😭。博主直接Debug了QLoRA论文作者提供的代码。
注意:Github上QLoRA代码需要的python是3.10系列版本。

代码准备

首先,我们从GitHub将QLoRA代码克隆到本地。
接着,安装QLoRA的依赖包。
再接着,将量化微调的模型下载到本地。博主从魔塔社区下载的shakechen/Llama-2-7b-hf模型。
再接着,下载数据集。QLoRA代码从HuggingFace下载训练数据集,所以我们需要先将数据下载到本地。博主使用的是tatsu-lab/alpaca数据集。从HuggingFace下载数据集需要配置国内镜像,配置教程可以点击该链接
下载好后,将数据路径复制到qlora.py代码中。如下:
notion image
最后,尝试运行。出现如下信息代表代码运行成功。
notion image

QLoRA执行流程

简单记录一下QLoRA执行流程,辅助后续理解。
首先,加载目标模型,将目标模型参数转换为NF4数据类型并冻结目标模型参数,这一部涉及到了NF4量化双重量化
接着,在模型层的目标层中添加LoRA分支。
注意:QLoRA保存了两种数据类型:目标模型的NF数据类型和LoRA执行计算的FP16数据类型。从这里伙伴们可以发现,模型的前向计算和反向传播,依然使用的是FP16数据类型,但也仅仅是在LoRA层上。NF4数据 + FP16数据 = FP16数据。当模型参数合并时,目标模型的权重需要反量化到FP16数据类型
最后,模型微调训练。

浅尝代码

论文作者将QLoRA中的NF4量化集成到了bitsandbytes库中。同时,Huggingface的transformers库集成了bitsandbytes库
首先,我们DeBug QLoRA的源码分析了解每一个核心模块工作流程。博主通过DeBug发现QLoRa在量化目标模型时,先将目标层替换成支持NF4的线性层。如下图:
notion image
其中,A表示进行线性层替换的代码文件名,B表示代码文件路径,C表示代码文件中的具体代码段。从C中可以发现,在这里并没有量化权重值,仅仅进行了线性层转换。同时,从C中也可以发现,QLoRA仅仅是将目标模型参数值量化成NF4数据类型,但是,执行前向和反向计算仍然使用的是BF16,FP16或者FP32(可以通过quantization_config.bnb_4bit_compute_dtype设置)。
接着,我们一起进入NF4线性层中一探究竟。
notion image
notion image
再接着,量化模型参数和二重量化。
其实,模型量化是通过上图Params4bit类的cuda函数进行的,也就是下面这句代码。
notion image
其中,量化是通过lib.cquantize_blockwise_bf16_nf4()函数实现。输入参数含义已经在图中标记。
notion image
其中,quantize_blockwise将NF4量化的缩放因子进行量化。这里也就是二重量化的主要操作函数。
注意:博主发现返回out的dtype是torch.uint8。这让我感到很疑惑。有知道的大佬,可以在下方评论区告诉我?
再接着,将量化后的参数值赋值到替换层。
notion image
再接着,在目标层增加LoRA分支。
notion image
最后,模型训练微调。

📎 参考文章

 
💡
以上便是本篇博客的内容,欢迎您在底部评论区留言,一起交流~
 
上一篇
记录- LoRA微调
下一篇
记录-模型训练如何使用混合精度

评论
Loading...