QLoRA实战 | 使用单卡高效微调bloom-7b1,效果惊艳

2023-12-23 12:10

本文主要是介绍QLoRA实战 | 使用单卡高效微调bloom-7b1,效果惊艳,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

来自:YeungNLP

进NLP群—>加入NLP交流群

在文章Firefly(流萤): 中文对话式大语言模型、中文对话式大语言模型Firefly-2b6开源,使用210万训练数据中,我们介绍了关于Firefly(流萤)模型的工作。对大模型进行全量参数微调需要大量GPU资源,所以我们通过对Bloom进行词表裁剪,在4*32G的显卡上,勉强训练起了2.6B的firefly模型。

在本文中,我们将介绍QLoRA,由华盛顿大学提出的一种高效微调大模型的方法,可在单张A100上对LLaMA-65B进行微调。在论文中,作者的实验表明使用QLoRA微调的LLaMA-65B,可达到ChatGPT性能水平的99.3%(由GPT-4进行评价),并且QLoRA的性能可以逼近全量参数微调。作者做了丰富的实验证明这一结论。

7acc059bebd245707d76b1abb1684b29.png

在本文中我们将对QLoRA的基本原理进行介绍,并且在Firefly项目中进行实践。我们在bloom-7b1的基础上,使用QLoRA进行中文指令微调,获得firefly-7b1-qlora-v0.1模型,具有不错的效果,生成效果见第三章。QLoRA确实是一种高效训练、效果优秀、值得尝试和深入研究的方法

论文地址:

https://arxiv.org/pdf/2305.14314.pdf

项目代码:

https://github.com/yangjianxin1/Firefly

模型权重:

https://huggingface.co/YeungNLP/firefly-7b1-qlora-v0.1

01

QLoRA简介

本章节主要对LoRA与QLoRA进行介绍,如读者已了解本章节的内容,可直接跳过,阅读项目实践部分。

LoRA简介

在介绍QLoRA之前,简单回顾一下LoRA。LoRA的本质是在原模型的基础上插入若干新的参数,称之为adapter。在训练时,冻结原始模型的参数,只更新adapter的参数。对于不同的基座模型,adapter的参数量一般为几百万~几千万。

fbd4feb73f101bbe6493cc779e6e0ac7.png

LoRA的优势在于能够使用较少的GPU资源,在下游任务中对大模型进行微调。在开源社区中,开发者们使用LoRA对Stable Diffusion进行微调,取得了非常不错的效果。随着ChatGPT的火爆,也涌现出了许多使用LoRA对LLM进行指令微调的工作。

此前,我们也实践过使用LoRA对LLM进行指令微调,虽然未进行定量分析,但主观感受LoRA比全量微调还是有一定的差距。实践下来,我们发现LoRA微调中存在以下三个痛点:

  1. 参数空间小:LoRA中参与训练的参数量较少,解空间较小,效果相比全量微调有一定的差距。

  2. 微调大模型成本高:对于上百亿参数量的模型,LoRA微调的成本还是很高。

  3. 精度损失:针对第二点,可以采用int8或int4量化,进一步对模型基座的参数进行压缩。但是又会引发精度损失的问题,降低模型性能。

QLoRA简介

接下来便引入今天的主角QLoRA。整篇论文读下来,我们认为QLoRA中比较重要的几个做法如下:

  1. 4-bit NormalFloat:提出一种理论最优的4-bit的量化数据类型,优于当前普遍使用的FP4与Int4。

  2. Double Quantization:相比于当前的模型量化方法,更加节省显存空间。每个参数平均节省0.37bit,对于65B的LLaMA模型,大约能节省3GB显存空间。

  3. Paged Optimizers:使用NVIDIA统一内存来避免在处理小批量的长序列时出现的梯度检查点内存峰值。

  4. 增加Adapter:4-bit的NormalFloat与Double Quantization,节省了很多空间,但带来了性能损失,作者通过插入更多adapter来弥补这种性能损失。在LoRA中,一般会选择在query和value的全连接层处插入adapter。而QLoRA则在所有全连接层处都插入了adapter,增加了训练参数,弥补精度带来的性能损失。

通过上述优化,只需要41G显存即可微调LLaMA-65B模型。甚至可以直接使用一张1080Ti来微调LLaMA-13B,手中的旧卡又可以继续发挥余热了。

9a4f94327e8d788dc2c0118f7659850a.png

作者使用GPT4对各个模型进行评价,结果显示,使用QLoRA在OASST1数据集上微调得到的Guanaco-65B模型达到了ChatGPT的99.3%的性能。

3e07db220849361012cbc4198835f9c2.png

作者进一步采用了Elo等级分制度对各个模型进行评价,裁判为人类或者GPT-4。结果显示Guanaco-65B和Guanaco-33B均优于ChatGPT-3.5。

b7fc768a98b0eebccb844bbf12393444.png

实验分析

QLoRA方法是否有用,其与全量参数微调的差距有多大?作者使用LLaMA-7B和Alpaca数据集进行了实验。下图结果表明,通过插入更多的adapter,能够弥补QLoRA量化带来的性能损失,复现全量参数微调的效果。

1afffc021f4d458c0c4392b867b6aa8c.jpeg

除此之外,作者还将QLoRA应用于RoBERTA和T5,评测其在GLUE和Super-NaturalInstructions数据集上的表现。从下表中可以看到,QLoRA+NF4+DQ基本上复现了BF16全量微调的实验指标。

下表中LoRA+BF16基本上也复现了BF16全量微调的实验指标,如果作者能加上LoRA+FP4或者LoRA+int4的实验结果,则可以更清晰地展现LoRA与QLoRA的性能差异。

56c27342c3285e597e1f9fb46af56fe3.png

在指令微调阶段,数据质量和数据数量,哪一个更重要?作者使用三种不同的训练集,每个数据集分别使用5万、10万、15万的数据量进行训练。对于下表,纵向来看,随着数据量的增加,指标并没有明显的提升,说明数据量不是关键因素。横向来看,对于不同的数据集,指标差距甚大,说明数据质量更关键。

a98d7c6dbd67fd9d8255ff18c6668816.png

值得一提的是,在论文中,作者仅使用了9千多条OASST1的数据训练得到Guanaco-65B,这进一步验证了,数据质量远比数量重要,模型的知识来源于预训练阶段。

模型的知识来源于预训练阶段,指令微调目的是和人类指令进行对齐。在指令微调阶段,数据的质量与丰富度,远比数量更重要。这是最近一段时间,开源社区以及各个论文强调的一个结论,在我们的实践中也深有体会。

02

项目实践

在本项目中,我们使用bloom-7b1作为基座模型。数据集为moss-003-sft-no-tools,这是由MOSS项目开源的中文指令微调数据集,我们随机抽取了29万条作为训练数据,训练得到firefly-7b1-qlora-v0.1。

训练时,我们将多轮对话拼接成如下格式,然后进行tokenize。

<s>input1</s>target1</s>input2</s>target2</s>...

我们在一张32G显卡上使用QLoRA进行训练,在所有全连接层处都插入adapter,最终参与训练的参数量超过1亿,相当于一个bert-base的参数量。训练时只计算target部分的损失函数。

训练超参数如下所示:

max length
1024
lr_scheduler_typecosine
batch size
16
lr
2e-4
warmup step
3000
optimizer
paged_adamw_32bit
training step
18万

模型的训练损失的变化趋势如下图所示:

bc23851de9dcbb324e190d00b09bc680.png

firefly-7b1-qlora-v0.1的使用方式如下:

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, LlamaTokenizer, BitsAndBytesConfig
import torchmodel_name = 'bigscience/bloom-7b1'
adapter_name = 'YeungNLP/firefly-7b1-qlora-v0.1'
device = 'cuda'
input_pattern = '<s>{}</s>'model = AutoModelForCausalLM.from_pretrained(model_name,low_cpu_mem_usage=True,torch_dtype=torch.float16,device_map='auto'
)
model = PeftModel.from_pretrained(model, adapter_name)
model.eval()
model = model.to(device)
tokenizer = AutoTokenizer.from_pretrained(model_name)text = input('User:')
while True:text = input_pattern.format(text)input_ids = tokenizer(text, return_tensors="pt").input_idsinput_ids = input_ids.to(device)outputs = model.generate(input_ids=input_ids, max_new_tokens=250, do_sample=True, top_p=0.75, temperature=0.35,repetition_penalty=1.2, eos_token_id=tokenizer.eos_token_id)rets = tokenizer.batch_decode(outputs)output = rets[0].strip().replace(text, "").replace('</s>', "")print("Firefly:{}".format(output))text = input('User:')

03

生成效果

下面的样例均为firefly-7b1-qlora-v0.1模型所生成,未经修改,仅供参考。

多轮对话

对话示例1:

591099f4dff001487ffbf14cac7ae58e.png

对话示例2:

4361d37c3d2d10e91994e3f16447db88.png

邮件生成

62ffca1a1d6c6c9b7357abba23c4ac33.png

e3dfc75616d88904260d9cea10e37d00.png

1727e23780e43a2a4125318822784b2d.png

商品文案生成

d1ec8b440dd893531efffcf496d8682e.png

35ac4929b19cdeaedd766b1b26c9312b.png

医疗问答

66bcace93cf32146baf0cfd1ce460339.png

73369dfc73b88c19d940d9f9517ba59e.png

创意性写作

56530d70fa26dd224630298bc0978b64.png

7e933a6194d2c99489e5fb22098052cf.png

6d0a512186fc8de6395a951c08a5ad51.png

44741f873c23e4d8fb31847d74385269.png

777fe7b825aaa68a6619b78244169a7a.png

其他例子

b9cf1166272baff8c2618e438ffa3c39.png

792bd0c3815199ec65a15e403646aa15.png

122be0671549faa3022ba64406b61e4f.png

b1e050da9ea563a3d7d61a2946d07672.png

04

结语

在本文中,我们介绍了QLoRA的基本原理,以及论文中一些比较重要的实验结论。并且使用QLoRA对bloom-7b1模型进行中文指令微调,获得了非常不错的效果。

从firefly-7b1-qlora-v0.1的生成效果来看,虽然没有做定量的评测(对LLM做评测确实比较困难),但就生成效果来看,丝毫不逊色于全量微调的firefly-2b6-v2。

一些碎碎念:

  1. 论文中表明QLoRA能够媲美全量参数微调的效果,虽然可能需要更丰富、多角度的实验进行验证,但如果【增大基座模型的参数量+QLoRA】能够优于【全量微调较小的模型】,也是非常有意义的。

  2. 对基座模型进行量化压缩,通过增加adapter来弥补量化导致性能损失,是一个非常不错的idea,论文中的实验也证实了这一点。并且从我们的实践效果看来,确实惊艳,效果远胜LoRA。

  3. 最后,如果你手边的训练资源不足,QLoRA非常值得一试。

您的点赞、在看、关注是我坚持的最大动力!


进NLP群—>加入NLP交流群

这篇关于QLoRA实战 | 使用单卡高效微调bloom-7b1,效果惊艳的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/527989

相关文章

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Qt中QUndoView控件的具体使用

《Qt中QUndoView控件的具体使用》QUndoView是Qt框架中用于可视化显示QUndoStack内容的控件,本文主要介绍了Qt中QUndoView控件的具体使用,具有一定的参考价值,感兴趣的... 目录引言一、QUndoView 的用途二、工作原理三、 如何与 QUnDOStack 配合使用四、自

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4:

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t