在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化

这篇具有很好参考价值的文章主要介绍了在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

LLM的问题就是权重参数太大,无法在我们本地消费级GPU上进行调试,所以我们将介绍3种在训练过程中减少内存消耗,节省大量时间的方法:梯度检查点,LoRA和量化。

梯度检查点

梯度检查点是一种在神经网络训练过程中使动态计算只存储最小层数的技术。

为了理解这个过程,我们需要了解反向传播是如何执行的,以及在整个过程中层是如何存储在GPU内存中的。

1、前向和后向传播的基本原理

前向传播和后向传播是深度神经网络训练的两个阶段。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

在前向传递过程中,输入被矢量化(将图像转换为像素,将文本转换为嵌入),并且通过一系列线性乘法和激活函数(如sigmoid或ReLU等非线性函数)在整个神经网络中处理每个元素。

神经网络的输出,被称为头部,被设计用来产生期望的输出,例如分类或下一个单词预测。然后将矢量化的预测结果与预期结果进行比较,并使用特定的损失函数(如交叉熵)计算损失。

基于损失值,以最小化损失为目标更新每层的权值和偏差。这个更新过程从神经网络的末端开始并向起点传播。

上面就是一个简单的过程,下面才是我们主要关注的:计算是如何存储在内存中的。

2、减少存储数量

一种简单的方法是只保留反向传播所需的基本层,并在它们的使用完成后从内存中释放它们。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

从上图可以看出,同时存储在内存中的层的最大数量并不是最优的。所以我们需要找到一种方法,在保持反向传播工作的同时,在内存中存储更少的元素。

3、减少计算时间

减少内存占用的一种方法是在神经网络开头的反向传播过程中重新计算每一层。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

但是在这种情况下,计算时间会明显增加,使得训练在大模型的情况下不可行。

4、优化计算和内存梯度检查点

该技术通过保存“检查点”以计算反向传播期间“丢失”的层。该算法不是从头开始计算层,如前面的示例所示,而是从最近的检查点开始计算。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

平衡内存存储和计算时间的最佳策略是设置O(sqrt(n))个检查点,层数为n。这样,一次反向传播计算的额外计算次数将对应于一次额外的前反向传播。

这种技术可以在较小的gpu上训练较大的模型,但代价是需要额外的计算时间(约20%)。

5、如何实现梯度检查点

transformer库已经提供了梯度检查点技术。

 from transformers import AutoModelForCausalLM, TraininArguments
 
 model = AutoModelForCausalLM.from_pretrained(
     model_id,
     use_cache=False, # False if gradient_checkpointing=True
     **default_args
 )
 model.gradient_checkpointing_enable()

LoRA

LoRA是微软团队开发的一种技术,用于加速大型语言模型的微调。他们在GPT-3 175B上实施了这种方法,并大大减少了训练参数的数量。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

他们的方法冻结预训练模型的所有参数,并将新的可训练参数嵌入到transformer架构中的特定模块中,如注意力模块(查询、键、值,但也适用于其他模块)。

为了实现这些适配器,他们利用线性层,如下面的等式所示,其中x (dimension: d)和h (dim: k)作为乘法前后的层,Wo作为预训练的权重,B和A作为新的权重矩阵。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

矩阵B和A的维数分别为(d × r)和(r × k),且r << min(d, k)。

也就是说在不使训练过程复杂化的情况下,将新的密集层添加到现有的层上。在微调过程中,权重矩阵BA初始化为0,并遵循α/r的线性尺度,α为常数。当使用Adam算法优化权重时,α与学习率大致相同。

对不同的LoRA配置进行了测试,论文得出的结果是,将r=8(或更高)应用于各种模块的性能最好。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

一旦对LoRA模型进行了微调,就可以将权重合并在一起以获得单个模型,或者只单独保存适配器,并将预训练模型与现有模型分开加载。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

Hugging Face开发的PEFT库,可以利用LoRA技术。

 from peft import LoraConfig, TaskType
 
 lora_config = LoraConfig(
         r=16,
         lora_alpha=16,
         target_modules=["query_key_value"]
         lora_dropout=0.1,
         bias="none",
         task_type=TaskType.CAUSAL_LM,
     )

还可以针对transformer架构中的所有密集层:

 # From https://github.com/artidoro/qlora/blob/main/qlora.py
 def find_all_linear_names(args, model):
     cls = torch.nn.Linear
     lora_module_names = set()
     for name, module in model.named_modules():
         if isinstance(module, cls):
             names = name.split('.')
             lora_module_names.add(names[0] if len(names) == 1 else names[-1])

然后就是将“初始化”适配器添加到预训练模型中。

 from transformers import AutoModelForCausalLM
 from peft import get_peft_model
 
 model = AutoModelForCausalLM.from_pretrained(model_id)
 lora_model = get_peft_model(model, peft_config)
 lora_model.print_trainable_parameters()

训练完成后,可以单独保存适配器,也可以将它们合并到模型中。

 # Save only adapaters
 lora_model.save_pretrained(...)
 
 # Save merged model
 merged_model = lora_model.merge_and_unload()  
 merged_model.save_pretrained(...)

量化

谈到LoRA,我就还需要说一下量化。这两种技术在论文QLORA得到了高效的融合,并且已经通过bitsandbytes、peft和accelerayte整合到了Hugging Face 的transformer中。

1、什么是量化?

量化是一种技术,可以降低元素的精度,但不会失去元素的整体意义。例如在图片的情况下,量化包括减少像素的数量,同时保持图像的一个体面的分辨率。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

上图肉眼基本看不出区别,但是存储空间却少了很多。在解释量化之前,需要了解计算机如何表示数字的

2、浮点数基本原理

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

计算机是二进制的,这意味着它们只通过0和1交换信息。为了表示数字,科学家设计了一种称为浮点格式的特殊系统,它允许计算机理解大范围的数值。最常见的表示形式是单精度浮点格式,由32位组成(1位= 0或1)。

除此以外还存在各种格式,例如半精度(16位)或双精度(64位)。简而言之,使用的比特数越多,可以容纳的数字范围就越广。

像GPT-3.5或Bloom-175B这样的模型非常大。在FP32格式中,这将表示:

175*10⁹. 4字节= 700Gb,半精度为350Gb,基本不可能加载到GPU内存中,那么我们如何缩小这些模型呢?

3、从FP32到Int8

Int8表示[- 127,127]之间的任何数字。

我们想将一个浮点数向量简化为Int8格式:

 v = [-1.2, 4.5, 5.4, -0.1]

我们需要做的是定义v的最大值(这里是5.4),并将所有数字缩放到Int8[- 127,127]的范围内。所以需要计算系数

 α = 127 / max(v) = 127 / 5.4 ~ 23.5

然后把v中的所有数乘以α,然后四舍五入,得到:

 α.v = [-28, 106, 127, -2]

如果想去量化这个向量,只需要做相反的操作,就能够得到初始向量!

 v = [-1.2, 4.5, 5.4, -0.1]

可以看到量化和反量化不会丢失任何信息。但实际上在四舍五入每个值时确实会失去精度。然而,在这个特定的例子中差异并不大,因为我们决定只用一个小数来表示数字,另外就是对于大模型来说,参数相互很大,之间也有关系,所以四舍五入的精度丢失不会对模型的结果产生很大的影响(是不产生很大影响,不是没影响),为了节省内存丢失一些小小的精度还是可以接受的。

那么,如果有异常值存在会发生什么?假设我们现在有这个向量:

 v’ = [-1.2, 70, 5.4, -0.1]

目前的最高数字是70,这可以被视为一个异常值。如果我们重现完全相同的过程,我们在量化之后得到:

 de-quantized v’ = [-1.1, 70, 5.5, 0.0]

精度的损失开始出现了,让如果我们将同样的损失应用于由70亿个参数组成的LLM:缺乏精度将在整个神经网络中积累,导致有意义的信息完全丢失,并导致纯噪声。而且我们现在使用的是8位格式,如果是4位甚至3位,结果会更糟,对吧。

但是大佬们找到了一种将量化应用于LLM的方法!

4、LLM.int8()使大规模量化成为可能

论文LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale 介绍了一种绕过此异常值问题的方法。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

量化参数的完整性会导致性能下降,而在矩阵乘法过程中使用量化,结合混合精度分解和向量量化。在矩阵乘法过程中,从权重矩阵中提取包含异常值(高于阈值)的向量,从而产生两次乘法。小数字矩阵(根据论文代表 99.9% 的值)被量化,而大数字则保留在 FP16 中。

按照混合精度分解原理,对小数乘法输出进行反量化,并添加到其他输出。

在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化,深度学习,LLM,自然语言处理,模型微调

也就是说量化技术仅在推理(矩阵乘法)期间使用,这意味着实际上没有8位数字组成的更小的模型!由于这种技术实现,我们甚至得到了一个更大的模型!(根据该论文,对于13B以下的模型,误差为0.1%)但是在BLOOM-175B上的实验表明,在没有任何性能下降的情况下,内存占用减少了1.96倍!这种技术可以访问以前无法装入GPU内存的大型模型

5、可以微调这个量化模型吗?

不行,因为这种技术只适用于推理,不适合训练。

如果我们可以使用量化减少GPU内存占用,并使用LoRA技术训练新的适配器,会怎么样?

还记得我们以前介绍的QLoRA吗,它就干的是这个事,他们成功地将预训练模型量化为4位!它们通过一些新技术来成功地量化模型,比如双量化和4位NormalFloat。

6、如何在代码中使用量化?

首先需要安装bitsandbytes和accelerate 库

 pip install -q bitsandbytes
 pip install -q accelerate
 pip install -q peft==0.4.1

然后,在调用from_pretrained方法时,可以通过传递参数load_in_4bit=True或load_in_8bit= true来加载4位或8位量化的模型。

 from transformers import AutoModelForCausalLM
 model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m", 
                load_in_4bit=True, 
                device_map="auto"
       )

也可以使用BitsAndBytesConfig类来进行高级的设置

 from transformers import BitsAndBytesConfig
 
 nf4_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_use_double_quant=True,
    bnb_4bit_compute_dtype=torch.bfloat16
 )
 
 model_nf4 = AutoModelForCausalLM.from_pretrained(
                           model_id, 
                           device_map="auto"
                           quantization_config=nf4_config
                           )

这样模型差不多可以进行推断了。但是我们还需要设置一下的参数:

冻结量化参数以防止训练,

在所有归一化层和 LM 头中使用FP32(未量化),以确保模型的稳定性

如果使用梯度检查点,需要配置model.enable_input_require_grad()

 for name, param in model.named_parameters():
     # freeze base model's layers
     param.requires_grad = False
 
 # cast all non int8 or int4 parameters to fp32
 for param in model.parameters():
   if (param.dtype == torch.float16) or (param.dtype == torch.bfloat16):
       param.data = param.data.to(torch.float32)
 
 if use_gradient_checkpointing:
     # For backward compatibility
     model.enable_input_require_grads()

在最新的peft==0.4.1库中,使用prepare_model_for_kbit_training()方法可以处理这个准备工作。

这样我们就有了一个量子的模型!

一段代码总结

我们已经介绍了梯度检查点、LoRA和量化,让我们编写代码来对LLM进行微调。

先安装必要的库:

 pip install -q -U bitsandbytes
 pip install -q -U git+https://github.com/huggingface/transformers.git
 pip install -q -U git+https://github.com/huggingface/peft.git
 pip install -q -U git+https://github.com/huggingface/accelerate.git

然后就是代码:

 from transformers import (
         AutoModelForCausalLM,
         BitsAndBytesConfig
 )
 from peft import (
         get_peft_model,
         LoraConfig,
         TaskType,
         prepare_model_for_kbit_training
 )
 # Import the model
 gradient_checkpointing = True
 model = AutoModelForCausalLM.from_pretrained(
         args.model_id,
         use_cache=False if gradient_checkpointing else True,  # this is needed for gradient checkpointing
         device_map="auto",
         load_in_4bit=True
     )
 
 # Prepare the model (freeze, cast FP32, enable_require_grads, activate gradient checkpointing)
 model = prepare_model_for_kbit_training(
                     model, 
                     use_gradient_checkpointing=gradient_checkpointing
     )
 # Prepare Peft model by adding Lora
 peft_config = LoraConfig(
         r=64,
         lora_alpha=16,
         target_modules=modules,
         lora_dropout=0.1,
         bias="none",
         task_type=TaskType.CAUSAL_LM,
     )
 
 model = get_peft_model(model, peft_config)

这样模型就可以在本地的GPU上进行微调了。通过创建SFTTrainer (Trainer的一个子类,可以处理我们到目前为止讨论的所有内容)使这个过程变得更加容易。

 from trl import SFTTrainer
 
 model = AutoModelForCausalLM.from_pretrained(
     "EleutherAI/gpt-neo-125m",
     load_in_4bit=True,
     device_map="auto",
 )
 
 trainer = SFTTrainer(
     model,
     train_dataset=dataset,
     dataset_text_field="text",
     torch_dtype=torch.bfloat16,
     peft_config=peft_config,
 )
 
 trainer.train()

总结

在本文中,介绍了大型语言模型微调过程中出现的一个挑战:如何在单个GPU上进行微调。我们介绍了3种技术来减少内存占用:梯度检查点、LoRA和量化。我们看到了如何通过利用PEFT、BitsAndBytes和Transformers将这些技术应用到我们的代码中。

本文的目标是提供一个深入而简单的视图,利用的现有技术,以便在你的项目中微调自己的llm。

https://avoid.overfit.cn/post/7d68614b936a431a8973ff825091a795

作者:Jeremy Arancio文章来源地址https://www.toymoban.com/news/detail-626074.html

到了这里,关于在消费级GPU调试LLM的三种方法:梯度检查点,LoRA和量化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 动态VALN的三种划分方法

    VLAN的概念 VLAN(Virtual Local Area Network)的中文名为\\\"虚拟局域网\\\"。VLAN是一种将局域网设备从逻辑上划分成一个个网段,从而实现虚拟工作组的新兴数据交换技术。这一新兴技术主要应用于交换机和路由器中,但主流应用还是在交换机之中。但又不是所有交换机都具有此功能,

    2024年02月07日
    浏览(71)
  • 加密文档的三种基本方法

    一、Windows系统自带的加密工具:       1、找到一个word文档,对其进行加密:        2、在选择的word文档上右击,并选择最下方的属性,进入属性界面       3、在属性界面点击高级,进入高级属性界面,找到“机密内容以便于保护数据”并选择它,最后在高级属性和wor

    2024年02月05日
    浏览(31)
  • 提取人脸特征的三种方法

    安装dlib方法: https://blog.csdn.net/hhhhhhhhhhwwwwwwwwww/article/details/121470556 思路: 1、使用dlib.get_frontal_face_detector()方法检测人脸的位置。 2、使用 dlib.shape_predictor()方法得到人脸的关键点。 3、使用dlib.face_recognition_model_v1()方法提取特征。 新建face_embedding1.py,插入代码: predictor_path是

    2024年02月07日
    浏览(32)
  • Java延时的三种方法

    一、Robot,Thread和Timer 打印: 二、补充: 关于方法二的 this.cancel() ; 解释: 取消此计时器任务。如果任务已计划一次执行,但尚未运行,或尚未计划,则它将永远不会运行。如果任务已计划重复执行,则它将永远不会再次运行。(如果此调用发生时任务正在运行,则任务将运

    2024年02月16日
    浏览(36)
  • 设置环境变量的三种方法

    用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是“永久的”。 修改文件后要想马上生效还要运行# source /etc/profile不然只能在下次重进此用户时生效。 一般只有root用户才有编辑权限; 用VI在用户目录下的.bash_profile文件中增加变量,改变量仅会

    2024年02月15日
    浏览(27)
  • 快速排序的三种实现方法

    快速排序的单趟排序 快速排序的单趟排序:是以一个数作为基准值,实现将数组中比基准数小的数放在基准值的左侧,比基准值大的数放在基准值的右侧。 方法一:霍尔法 霍尔法的由来:霍尔是一个人的名字,他是最初发现快速排序的人,所以,它使用的单趟排序算法被称为

    2024年01月25日
    浏览(31)
  • python中的三种注释方法

    在编写程序中,使用注释不会影响程序代码的执行,但可以使得代码通俗易懂,便于维护, 在python,一共有三种注释方法 法一 单行注释,使用#注释,一般放于句首,或者放在代码语句之后,要被注释的代码之前 例如: 法二 对于多行注释,使用单行注释效率不高,所以用三

    2024年02月02日
    浏览(40)
  • MySQL插入数据的三种方法

    insert into:正常的插入数据,插入数据的时候会检查主键或者唯一索引,如果出现重复就会报错。 replace into:替换数据。插入时,如果表中已经存在相同的primary key或者unique索引,则用新数据替换;如果没有相同的primary key或者unique索引,则直接插入。 insert ignore into:插入时

    2023年04月08日
    浏览(25)
  • GIT合并分支的三种方法

    1、目标:将dev分支合并到master分支 1.1、首先切换到master分支上 1.2、如果是多人开发的话 需要把远程master上的代码pull下来 1.3、然后我们把dev分支的代码合并到master上 1.4、然后查看状态及执行提交命令 比如 feature 分支上的commit 82ecb31非常重要,它含有一个bug的修改,或其他人

    2024年02月12日
    浏览(38)
  • Java创建List 的三种方法

    1.通过 new ArrayList()  2.  通过Arrays.asList() 这种方法构造出的List是固定长度的,如果调用add方法增加新的元素,会报异常,List是由Array转换而来,而Array是不能动态增加长度的,适合于构造静态不变List. 3.通过hutool工具类collectionUtil创建   list可以动态添加元素,比较友好,适合

    2024年02月11日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包