在消费级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模板网!

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

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

相关文章

  • 提取人脸特征的三种方法

    安装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日
    浏览(46)
  • 脱离文档流的三种方法

            什么是脱离文档流呢?可以这样理解,本来这个标签是属于文档流管理的,那么它应该按照文档流的正常布局方式从左至右从上之下,并且符合标签本身的含义。         脱离文档流是指,这个标签脱离了文档流的管理。不受文档流的布局约束了,并且更重要

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

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

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

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

    2024年02月16日
    浏览(45)
  • python中的三种注释方法

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

    2024年02月02日
    浏览(52)
  • 动态VALN的三种划分方法

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

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

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

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

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

    2024年02月11日
    浏览(43)
  • 隐藏IP地址的三种方法

    随着互联网的不断发展,用户们在日常使用通信设备请求网站时的风险也在不断增大。因为IP 地址对 Internet 上的每个人都是可见的。根据 IP 地址,其他互联网用户可以跟踪用户的定位、用户使用哪个提供商连接到互联网等等。因此许多用户都在寻求保护IP地址的方法,接下来

    2024年02月12日
    浏览(48)
  • mysql常用的三种备份方法

    mysql按照备份恢复方式分为逻辑备份和物理备份 逻辑备份是备份sql语句,在恢复的时候执行备份的sql语句实现数据库数据的重现 物理备份就是备份数据文件了,比较形象点就是cp下数据文件,但真正备份的时候自然不是的cp这么简单 这2种备份各有优劣,一般来说,物理备份恢

    2024年02月12日
    浏览(59)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包