1. 图片由作者提供:流程展示了量化的需求。(笑脸和生气的脸的图片由Yan Krukau提供,https://www.pexels.com/)
在解释上面的图表之前,让我先来介绍你在本文中将学到的重点。
让我们一个个依次展开讲解。
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. 图像由作者:非对称线性量化
那么,我们如何从原始张量值推导出量化张量值呢? 这其实很简单。如果你还记得高中数学,你很容易就能理解下面的推导过程。让我们一步一步来(我建议你在推导方程时参考上面的图,以便更清楚地理解)。
我知道你们中很多人可能不想看下面的数学推导。但相信我,这一定会帮助你们把概念弄得更清楚,并在以后进行量化编码时节省大量时间。我在研究这个内容时也有同感。
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值。因此,我们无需编写额外的代码即可继续进行。
就这样! 我们已经到了这篇帖子的结尾。希望这篇帖子能帮助你建立对量化的基本直觉,并对数学推导部分有清晰的理解。
我的最终想法……
敬请期待,感谢您的阅读!
链接到 Google Colab 笔记本
参考文献