人工智能学习

想学习大型语言模型中的量化吗?

本文主要是介绍想学习大型语言模型中的量化吗?,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一个简单的指南,通过简单的数学推导和PyTorch编码教你理解量化。

1. 图片由作者提供:流程展示了量化的需求。(笑脸和生气的脸的图片由Yan Krukau提供,https://www.pexels.com/)

在解释上面的图表之前,让我先来介绍你在本文中将学到的重点。

  1. 首先,你将了解 量化是什么为什么
  2. 接下来,你将进一步学习 如何 通过一些简单的数学推导来进行 量化
  3. 最后,我们将一起在 PyTorch 中编写一些 代码 来执行 LLM 权重参数的量化和去量化。

让我们一个个依次展开讲解。

1. 什么是量化以及为什么需要它?

量化 是一种将较大尺寸的模型(大型语言模型或其他深度学习模型)压缩为较小尺寸的方法。在量化过程中,主要会对模型的权重参数和激活值进行量化。让我们通过简单的模型大小计算来验证这一说法。

2. 图像由作者绘制:左图:基础模型大小计算(单位GB),右图:量化模型大小计算(单位GB)

在上图中,基础模型Llama 3 8B的大小为32GB。经过INT8量化后,大小减少到8GB(减少了75%)。使用INT4量化后,大小进一步减少到4GB(减少了约90%)。 这是一个巨大的模型大小缩减。这确实令人惊叹,不是吗?这要归功于量化论文的作者们以及我对数学力量的深深感激。

现在你已经了解了量化是什么,让我们来谈谈为什么需要量化。 请看图1,作为一名有抱负的AI研究人员、开发人员或架构师,如果你希望在你的数据集上进行模型微调或推理,由于内存和处理器的限制,你可能无法在你的机器或移动设备上完成这些任务。可能,像我一样,你会有一个生气的表情,就像选项1b。这让我们来到了选项1a,你可以让云服务商为你提供所有你需要的资源,并轻松地使用任何你想要的模型。但这会花费你很多钱。如果你负担得起,那很好。但如果你的预算有限,好消息是你还有选项2可用。这就是你可以使用量化方法来减少模型的大小,并方便地在你的应用场景中使用它。如果你量化做得好,你会得到与原始模型几乎相同的准确率。

注意: 一旦你在本地机器上完成了微调或其他任务,并且你想将模型投入生产,我建议你将模型部署到云端,以向客户提供可靠、可扩展和安全的服务。

2. 量化是如何工作的?一个简单的数学推导。

从技术角度讲,量化将模型的权重值从高精度(如 FP32)映射到低精度(如 FP16|BF16|INT8)。虽然有许多可用的量化方法,但在本文中,我们将学习一种广泛使用的量化方法,称为线性量化方法。线性量化有两种模式:A. 非对称量化B. 对称量化。我们将依次学习这两种方法。

A. 非对称线性量化: 非对称量化方法将原始张量范围(Wmin, Wmax)中的值映射到量化张量范围(Qmin, Qmax)中的值。

3. 图像由作者:非对称线性量化

  • Wmin, Wmax: 原始张量(数据类型:FP32,32位浮点数)的最小和最大值。大多数现代LLM中权重张量的默认数据类型是FP32。
  • Qmin, Qmax: 量化张量(数据类型:INT8,8位整数)的最小和最大值。我们也可以选择其他数据类型,如INT4、INT8、FP16和BF16进行量化。在我们的示例中,我们将使用INT8。
  • 缩放值(S): 在量化过程中,缩放值将原始张量的值缩小以获得量化张量。在反量化过程中,它将量化张量的值放大以获得反量化值。缩放值的数据类型与原始张量相同,即FP32。
  • 零点(Z): 零点是量化张量范围内直接映射到原始张量范围内值0的非零值。零点的数据类型是INT8,因为它位于量化张量范围内。
  • 量化: 图表中的“A”部分显示了将 [Wmin, Wmax] 映射到 [Qmin, Qmax] 的量化过程。
  • 反量化: 图表中的“B”部分显示了将 [Qmin, Qmax] 映射到 [Wmin, Wmax] 的反量化过程。

那么,我们如何从原始张量值推导出量化张量值呢? 这其实很简单。如果你还记得高中数学,你很容易就能理解下面的推导过程。让我们一步一步来(我建议你在推导方程时参考上面的图,以便更清楚地理解)。

我知道你们中很多人可能不想看下面的数学推导。但相信我,这一定会帮助你们把概念弄得更清楚,并在以后进行量化编码时节省大量时间。我在研究这个内容时也有同感。

  • 潜在问题1:如果Z的值超出范围怎么办?解决方案: 使用简单的if-else逻辑将Z的值调整为Qmin,如果Z小于Qmin;将Z的值调整为Qmax,如果Z大于Qmax。这在下面图像4中的图A中有很好的描述。
  • 潜在问题2:如果Q的值超出范围怎么办?解决方案: 在PyTorch中,有一个名为clamp的函数,它可以将值调整到特定范围内(例如我们的例子中的范围是-128到127)。因此,clamp函数将Q值调整为Qmin,如果Q小于Qmin;将Q值调整为Qmax,如果Q大于Qmax。问题解决了,让我们继续。

4. 图像由作者提供:零点和量化张量超出范围

侧注: 对于INT8类型,量化张量的值范围是(-128, 127),即有符号整数类型。如果量化张量的值的数据类型是UINT8,即无符号整数类型,则范围将是(0, 255)。

B. 对称线性量化: 在对称方法中,原始张量范围中的0点映射到量化张量范围中的0点。因此,这被称为对称。由于0点在范围的两侧都映射到0,所以在对称量化中没有Z(零点)。整体映射发生在原始张量范围的(-Wmax, Wmax)到量化张量范围的(-Qmax, Qmax)之间。下图显示了量化和去量化的对称映射情况。

5. 图像由作者:对称线性量化

既然我们在非对称段定义了所有参数,这里也适用。让我们进入对称量化数学推导。

不对称量化和对称量化之间的区别:

现在你已经学习了什么是线性量化、为什么使用线性量化以及如何进行线性量化,这引领我们来到了本文的最后部分,编码部分

3. 使用 PyTorch 编写代码来执行 LLM 权重参数的量化和去量化。

如我之前所说,量化可以应用于模型的权重、参数和激活值。但是为了简单起见,我们仅在编码示例中量化权重参数。在开始编码之前,我们先快速看一下在 transformer 模型中,权重参数值在量化后的变化情况。这将有助于我们更好地理解。

6. 作者绘制图像: transformer架构中权重参数的量化

在我们将16个原始的FP32权重参数量化到INT8之后,内存占用从512位减少到了128位(减少了25%)。这证明了对于大型模型,这种减少将会更加显著。

下面你可以看到FP32、Signed INT8和Unsigned UINT8等数据类型在实际内存中的分布。我在实际计算中使用了2's补码。你可以自己练习计算并验证结果。

7. 作者绘制的图像:FP32、INT8、UINT8 数据类型分布和计算示例

现在,既然我们已经涵盖了你需要开始编码的所有内容。我建议你跟着一起操作,以便熟悉推导过程。

A. 不对称量化代码: 我们一步一步地来编写代码。

步骤 1: 我们将首先为原始权重张量(大小:4x4,数据类型:FP32)分配随机值。

    # !pip install torch; 如果你还没有安装 torch 库,请先运行此命令进行安装  
    # 导入 torch 库  
    import torch  

    original_weight = torch.randn((4,4))  
    print(original_weight)

原始权重张量(original_weight)以FP32格式

步骤2: 我们将定义两个函数,一个用于量化,另一个用于反量化。

    def 不对称量化(original_weight):  
      # 定义你想要量化的数据类型,在这个例子中是INT8。  
      量化数据类型 = torch.int8  

      # 从原始权重中获取Wmax和Wmin值,原始权重是FP32类型。  
      Wmax = original_weight.max().item()  
      Wmin = original_weight.min().item()  

      # 从量化数据类型中获取Qmax和Qmin值。   
      Qmax = torch.iinfo(量化数据类型).max  
      Qmin = torch.iinfo(量化数据类型).min  

      # 使用缩放公式计算缩放值。数据类型 - FP32。  
      # 如果你想知道公式是如何推导的,请参阅本文的数学部分。   
      S = (Wmax - Wmin)/(Qmax - Qmin)  

      # 使用零点公式计算零点值。数据类型 - INT8。  
      # 如果你想知道公式是如何推导的,请参阅本文的数学部分。  
      Z = Qmin - (Wmin/S)  
      # 检查Z值是否超出范围。  
      if Z < Qmin:  
        Z = Qmin  
      elif Z > Qmax:  
        Z = Qmax  
      else:  
        # 零点数据类型应该是INT8,与量化值相同。  
        Z = int(round(Z))  

      # 我们现在有了原始权重、缩放值和零点值,可以使用我们在数学部分推导出的公式计算量化权重。  
      量化权重 = (original_weight/S) + Z  

      # 我们还需要对其进行四舍五入,并使用torch clamp函数确保量化权重不会超出范围,保持在Qmin和Qmax之间。  
      量化权重 = torch.clamp(torch.round(量化权重), Qmin, Qmax)  

      # 最后将数据类型转换为INT8。  
      量化权重 = 量化权重.to(量化数据类型)  

      # 返回最终的量化权重。  
      return 量化权重, S, Z  

    def 不对称去量化(量化权重, 缩放值, 零点值):  
      # 使用本文数学部分推导出的去量化计算公式。  
      # 同时确保将量化权重转换为float,以避免两个INT8值(量化权重和零点值)之间的减法产生意外结果。   
      去量化权重 = 缩放值 * (量化权重.to(torch.float32) - 零点值)  

      return 去量化权重

步骤 3: 我们将通过调用 asymmetric_quantization 函数来计算量化权重、缩放因子和零点。您可以在下面的截图中看到输出结果,注意量化权重的数据类型为 int8,缩放因子为 FP32,零点为 INT8。

    量化权重, 缩放因子, 零点 = asymmetric_quantization(原始权重)  
    print(f"量化权重: {量化权重}")  
    print("\n")  
    print(f"缩放因子: {缩放因子}")  
    print("\n")  
    print(f"零点: {零点}")

量化权重,缩放因子和零点值

步骤4:现在我们已经得到了所有的量化权重、缩放因子和零点值。让我们通过调用 asymmetric_dequantization 函数来获取去量化的权重值。注意,去量化的权重值是 FP32 类型的。

    dequantized_weight = 不对称去量化(quantized_weight, scale, zero_point)  
    print(dequantized_weight)

去量化的权重值

步骤5: 通过计算它们之间的量化误差,让我们找出最终去量化的权重值与原始权重张量相比的准确性。

    量化误差 = (去量化的权重 - 原始权重).square().mean()  
    print(量化误差)

输出结果:量化误差小了很多。因此,我们可以认为非对称量化方法做得非常好。

B. 对称量化代码: 我们将使用之前在非对称方法中编写的相同代码。对称量化方法中唯一需要改变的是确保零输入值始终为0。因为在对称量化中,零输入值始终映射到原始权重张量中的0值。因此,我们无需编写额外的代码即可继续进行。

就这样! 我们已经到了这篇帖子的结尾。希望这篇帖子能帮助你建立对量化的基本直觉,并对数学推导部分有清晰的理解。

我的最终想法……

  • 在这篇帖子中,我们涵盖了所有必要的主题,让你能够参与任何与大语言模型(LLM)或深度学习量化相关的任务。
  • 虽然我们已经成功地对权重张量进行了量化,并且在大多数情况下也达到了很好的精度。但这在某些情况下可能还不够。如果你希望在更大的模型上进行更精确的量化,你需要执行通道量化(对权重矩阵的每一行或每一列进行量化)或组量化(将行或列分成更小的组并分别进行量化)。这些技术更复杂,我将在接下来的帖子中介绍它们。

敬请期待,感谢您的阅读!

链接到 Google Colab 笔记本

参考文献

  • 从huggingface博客:__https://huggingface.co/docs/optimum/en/concept_guides/quantization#pratical-steps-to-follow-to-quantize-a-model-to-int8
  • 从huggingface博客:__https://huggingface.co/blog/hf-bitsandbytes-integration
这篇关于想学习大型语言模型中的量化吗?的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!