AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


QLora:如何在一块GPU上finetune Mistral 7B
发布日期:2024-04-16 08:00:23 浏览次数: 2182


微调是指我们对现有开源模型使用特定领域数据对其进行调整,目的加强模型在特定领域的能力,微调使得每个拥有大量行业数据的人都能拥有一个专业的LLM为自己服务。

尽管微调是一个简单(且强大)的过程,但将它应用于LLM并不简单,关键的问题是LLM的计算成本(非常)昂贵例如,70B 参数模型的全量参数微调需要超过 1TB 的内存 ,而一张A100 GPU(售价2w美元)的内存为80GB,因此为了finetune,你首先需要几十万美元去购置显卡!

幸好,目前已经有在消费级别显卡上微调这些LLM的方法,其中最流行的一种,是QLora。本文将介绍QLora,并且运用QLora微调目前最好的开源模型:Mistral-7B,生成一个模仿视频博主自动回复Youtube评论的聊天机器人!


什么是量化?

QLoRA 的一个关键部分是量化。虽然这听起来像是一个可怕而复杂的词,但它是一个简单的想法。“量化“,其实就是把某个范围的数字分块的过程。

例如,0 到 100 之间有无限多的实数,例如 1、12、27、55.3、83.7823 等等。我们可以根据整数将它们分成多个数据块来量化这个范围,从而将数字序列 (1, 12, 27, 55.3, 83.7823) 变成 (1, 12, 27, 55, 83),或者我们可以使用十的因数分块,从而数字被映射为 (0, 0, 20, 50, 80)。该过程的可视化如下所示。

为什么我们需要量化

量化使我们能够用更少的信息来表示一组给定的数字。首先,简要地讨论一下计算机是如何工作的。计算机使用二进制数字对信息进行编码。例如,如果我想让计算机记住数字 83.7823,则需要将这个数字转换为 1 和 0 的字符串。一种方法是通过单精度浮点格式(即FP32),它将数字表示为 32 位序列,83.7823可以表示为01000010101001111001000010001010。

使用FP32表示数字,精度很高;但对于LLM,如果我们用FP32来表示每个模型参数,则每个参数将占用 4 个字节的内存(1 个字节 = 8 位)。因此,10B参数模型将消耗40GB内存。而如果我们想要进行全参数微调,则需要接近 200GB 的内存!这给LLM的微调带来了困难。我们希望成功训练的模型有高精度参数,但微调过程耗费尽量少的内存,平衡这两者是 QLoRA 的一个关键贡献。

QLoRA

QLoRA(量化低阶适应)结合了 4 个要素,可以在不牺牲模型性能的情况下充分利用机器的有限内存。我将简要总结每个要点。更多详细信息请参阅 QLoRA 论文。

要素 1:4 位 NormalFloat

QLoRA 使用了被称为4 位 NormalFloat的特殊数据类型,这种数据类型仅用 4 位对数字进行编码。虽然这意味着我们只有 2⁴ (= 16) 个块来表示模型参数,但 4 位 NormalFloat 使用特殊技巧来从有限的信息容量中获取更多信息

量化一组数字的简单方法就是我们之前看到的,即我们将数字分成等距的块。然而,更有效的方法是使用相同信息量的块。这两种方法之间的差异如下图所示。

更具体地说,4 位 NormalFloat 对正态分布数据采用信息理论上最优的量化策略。由于模型参数往往聚集在 0 附近,因此这是量化LLM 参数的有效策略。

要素2:双量化

双量化,顾名思义,就是对已经量化的模型参数再次执行量化。考虑以下量化过程。给定 FP32 张量,量化它的简单方法是使用下面的数学公式。


这里我们将 FP32格式的数字转换为 [-127, 127] 范围内的 Int8(8 位整数)表示。这个过程需要调整张量XFP32X^{FP32}XFP32的值,将它四舍五入到最接近的整数。同时,我们也可以化简量化公式,通过引入量化常数cFP32c^{FP32}cFP32简化方程,将计算过程简化为张量XFP32X^{FP32}XFP32与量化参数cFP32c^{FP32}cFP32相乘。

虽然这种幼稚的量化方法并不是在实践中实现的(记住我们在 4 位 NormalFloat 中看到的技巧),但它确实说明了量化需要一些计算开销才能将结果常量存储在内存中。

我们可以通过只执行一次这个过程来最小化这种开销。换句话说,为所有模型参数计算一个量化常数。然而,这并不理想,因为它对极值(非常)敏感。换句话说,由于c^FP32中的absmax()函数,一个相对较大的参数值会扭曲所有其他参数值。

或者,我们可以将模型参数划分为更小的块以进行量化。这减少了大值扭曲其他值的可能性,但会带来更大的内存占用。

为了减轻这种内存成本,我们可以(再次)采用量化,但现在采用这种逐块方法生成的常量。对于 64 的块大小,FP32 量化常数增加 0.5 位/参数。通过进一步量化这些常量(例如 8 位),我们可以将此占用空间减少到 0.127 位/参数 [4]。

标准量化与块量化的视觉比较

要素3:分页优化器

使用 Nvidia 提供的特性来避免训练期间出现的内存不足错误。当 GPU存储容量达到极限时,它会将内存从 GPU 传输到 CPU。由于LLM训练期间可能会出现间歇性内存峰值,可能会终止训练进程,因此这个特性很重要。

要素4:LoRA

LoRA(低秩适应)是一种参数高效微调(PEFT)方法。关键思想是 LoRA 不重新训练所有模型参数,而是在保持原始参数固定的情况下添加相对少量的可训练参数。它可以将可训练的数量减少100~1000倍,但不会显著牺牲模型性能。

将所有要素整合在一起:QLora

现在我们已经了解了 QLoRA 的所有要素,让我们看看如何将它们组合在一起。

首先,考虑一个10B模型标准的微调过程,其中包括重新训练每个模型参数。假如使用 FP16 作为模型参数和梯度(4 个字节/每个参数,40GB),使用 FP32 作为优化器状态,例如动量、方差、参数(12 个字节/参数,120GB)。因此,10B 参数模型将需要大约 160GB 内存来进行微调

使用 LoRA,我们可以通过减少可训练参数的数量来立即降低计算成本,Lora冻结了LLM的原始参数并添加一组适配器(Adapter)来作为可训练参数。模型参数和梯度的计算成本与以前相同( 4 字节/每个参数,40GB)。节省来自优化器状态。如果我们的可训练参数减少 100 倍(这些参数全来自于adapter),那么对于0.1B的adapter模型,它的参数更新需要4 个字节/每个参数,优化器状态更新需要12个字节/每个参数(总计1.6GB)。因此,10B 参数模型将需要大约 41.6GB 内存来进行微调。虽然可以节省大量成本,但依然不能在消费级别硬件上运行。

QLoRA通过使用要素1和要素2 ,量化原始模型参数来更进一步。这将训练成本从 4 字节/参数减少到大约 1 字节/参数。然后,通过以同样的方式再次使用 LoRA,这将增加0.16 字节/参数的消耗。因此,10B 模型只需 11.6GB 内存即可进行微调!它可以轻松地在消费类硬件上运行,例如 Google Colab 上的免费 T4 GPU。

下图显示了这 3 种方法的直观比较 。

示例代码:微调 Mistral-7b-Instruct 来回复 YouTube 评论

现在,使用QLora来微调Misttral-7B!该代码支持在谷歌的Colab上运行。

导入模块

从Hugging Face 的Transforms、Peft和datasets库导入模块。

 transformers  , , pipeline
 peft  prepare_model_for_kbit_training
 peft  , get_peft_model
 datasets  load_dataset
 transformers

安装以下依赖项:








!pip install auto-gptq
!pip install optimization
!pip install bitsandbytes

负载基础模型和分词器

接下来,从 Hugging Face 加载量化模型,使用The Block发布的Mistral-7B-Instruct model(链接:https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GPTQ),这是一个经过4-bit量化的Mistral-7b模型。

除了指定要下载的模型存储库之外,还设置以下参数:device_maptrust_remote_coderevisiondevice_map让该方法自动找出如何最好地分配计算资源以在机器上加载模型,trust_remote_code=False会阻止自定义模型文件在您的计算机上运行。最后,revision指定我们要使用存储库中的模型版本。

model_name 
 model  AutoModelForCausalLM.from_pretrained
    model_name,
    device_map ,
    trust_remote_code ,
    revision

加载后,我们看到 7B 参数模型仅占用4.16GB 内存,可以轻松装入 Colab 上免费提供的 CPU 或 GPU 内存。

接下来,加载模型的tokenizer:

 = AutoTokenizer.from_pretrained(model_name, use_fast= )

使用基本模型

接下来,测试这个模型:输入模型的文本是“Great content, thank you!”,一个模仿youtube的评论。

model.() 


comment = 
prompt=f


inputs = (prompt, return_tensors=)


outputs = model.(input_ids=inputs[].(), 
                            max_new_tokens=)

(tokenizer.(outputs)[])

模型的响应如下所示。虽然它有一个好的开头,但这种回复有些啰嗦,听起来不像一个真人博主的回复!

I
topics you
help.

 the meantime, I
content I
you
up-- information.

Thanks  reading,  I look forward  helping you  any questions you 
may have!


准备训练模型

让我们看看如何通过微调来提高模型的性能。首先,启用梯度检查点,梯度检查点是一种节省内存的技术,可以清除特定的激活并在向后传递期间重新计算它们:

model.train()


model.gradient_checkpointing_enable()


model =prepare_model_for_kbit_training(model)

接下来,通过配置config设置 LoRA 训练。打印可训练参数的数量,我们观察到减少了 100 倍以上。

 config = LoraConfig(
    r=8 ,
    lora_alpha=32 ,
    target_modules=[ ],
    lora_dropout=0.05 ,
    bias= ,
    task_type=
 )


model = get_peft_model(model , config)


model.print_trainable_parameters()

准备训练数据集

现在,我们可以导入训练数据。此处使用的数据集可在 HuggingFace Dataset Hub获取。我使用我YouTube 频道[1]的评论和回复生成了此数据集。生成该数据集的代码见链接(https://github.com/ShawhinT/YouTube-Blog/blob/main/LLMs/qlora/create-dataset.ipynb

 = load_dataset( )

接下来,准备训练数据集。这需要数据具有适当的长度,并且被处理为tokenization:

 ():

    text = examples[]


    tokenizer.truncation_side = 
    tokenized_inputs = tokenizer(
        text,
        return_tensors=,
        truncation=,
        max_length=
    )

 tokenized_inputs


tokenized_data = data.(tokenize_function, batched=)


tokenizer.pad_token = tokenizer.eos_token


data_collator = Transformers.DataCollatorForLanguageModeling(tokenizer,
                                                              mlm= )

微调模型

在下面的代码块中,定义了模型训练的超参数。

lr = 2e-4
batch_size = 4
num_epochs = 10


training_args = transformers.TrainingArguments(
    output_dir= ,
    learning_rate=lr,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    num_train_epochs=num_epochs,
    weight_decay=0.01,
    logging_strategy=,
    evaluation_strategy=,
    save_strategy=,
    load_best_model_at_end=True,
    gradient_accumulation_steps=4,
    warmup_steps=2,
    fp16=True,
    optim=,
)

虽然这里列出了几个,但我想在 QLoRA 上下文中强调的两个是fp16和optim。fp16=True使训练器在训练过程中使用 FP16 值,与标准 FP32 相比,这会显着节省内存。optim=”paged_adamw_8bit”启用前面讨论的要素3(即分页优化器)。

设置完所有超参数后,我们可以使用下面的代码运行训练过程。

trainer  Transformers.Trainer
    modelmodel,
    train_datasettokenized_data ,
    eval_datasettokenized_data ,
    argstraining_args,
    data_collatordata_collator



model.config.use_cache 
trainer.train


model.config.use_cache

由于我们只有 50 个训练样本,因此该训练过程大约需要 10 分钟。

加载微调模型

最终模型可在HF hub(https://huggingface.co/shawhin/shawgpt-ft)上获得,如果想跳过训练过程直接加载,可以使用下面的代码:

peft importPeftModel , PeftConfig
from Transformersimport AutoModelForCausalLM

model_name =
 model = AutoModelForCausalLM.from_pretrained(model_name,
                                             device_map= ,
                                             trust_remote_code=False ,
                                             revision= )

config = PeftConfig.from_pretrained( )
model = PeftModel.from_pretrained(model, )


tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)


使用微调模型

可以对微调模型进行推理,这是微调模型对之前相同的评论“Great content, thank you!”的回复:

Glad you enjoyed it!  know  you have any questions.

与微调前相比,这样的回复更像我自己,同时也更简短而恰当。

总结

QLoRA 是一种微调技术,使得训练自己的大语言模型变得更加容易。在这里,我概述了该方法的工作原理,并分享了一个使用 QLoRA 创建 YouTube 评论回复器的具体示例。虽然经过微调的模型在模仿我的响应风格方面做得很好,但它在理解专业数据科学知识方面存在一些局限性,这可以通过RAG来改善。




53AI,企业落地应用大模型首选服务商

产品:大模型应用平台+智能体定制开发+落地咨询服务

承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询