大语言模型的模型量化(INT8/INT4)技术

目录

  1. 一、LLM.in8 的量化方案
    1. 1.1 模型量化的动机和原理
    2. 1.2 LLM.int8 量化的精度和性能
    3. 1.3 LLM.int8 量化的实践
  2. 二、SmoothQuant 量化方案
    1. 2.1 SmoothQuant 的基本原理
    2. 2.2 SmoothQuant 的实践
  3. 三、GPTQ 量化训练方案
    1. 3.1 GPTQ 的基本原理
    2. 3.2 GPTQ 的实践
  4. 参考资料

一、LLM.in8 的量化方案

1.1 模型量化的动机和原理

成本和准确度。

常见精度介绍:大模型涉及到的精度是啥?FP32、TF32、FP16、BF16、FP8、FP4、NF4、INT8区别

在训练时,为保证精度,主权重始终为 FP32。而在推理时,FP16 权重通常能提供与 FP32 相似的精度,这意味着在推理时使用 FP16 权重,仅需一半 GPU 显存就能获得相同的结果。那么是否还能进一步减少显存消耗呢?答案就是使用量化技术,最常见的就是 INT8 量化。

简单来说, INT8 量化即将浮点数 x f x_f xf 通过缩放因子 s c a l e scale scale 映射到范围在[-128, 127] 内的 8bit 表示 x q x_q xq ,即

x q = Clip ⁡ ( Round ⁡ ( x f ∗  scale  ) ) x_q=\operatorname{Clip}\left(\operatorname{Round}\left(x_f * \text { scale }\right)\right) xq=Clip(Round(xf scale ))

其中 Round 表示四舍五入都整数,Clip 表示将离群值(Outlier) 截断到 [-128, 127] 范围内。对于 scale 值,通常按如下方式计算得到:

a m a x = max ⁡ ( a b s ( x f ) )  scale  = 127 / a m a x \begin{aligned} &amax =\max \left(a b s\left(x_f\right)\right)\\ &\text { scale }=127/amax \end{aligned} amax=max(abs(xf)) scale =127/amax

反量化的过程为:

x f ′ = x q / s c a l e x_f^{'} = x_q/scale xf=xq/scale

下面是通过该方式实现的量化-反量化的例子:

当进行矩阵乘法时,可以通过组合各种技巧,例如逐行或逐向量量化,来获取更精确的结果。举个例子,对矩阵乘法,我们不会直接使用常规量化方式,即用整个张量的最大绝对值对张量进行归一化,而会转而使用向量量化方法,找到 A 的每一行和 B 的每一列的最大绝对值,然后逐行或逐列归一化 A 和 B 。最后将 A 与 B 相乘得到 C。最后,我们再计算与 A 和 B 的最大绝对值向量的外积,并将此与 C 求哈达玛积来反量化回 FP16。

1.2 LLM.int8 量化的精度和性能

上文说明了如何对单个向量进行量化,原理比较简单,但是可能也会出现一些问题,比如下面这个例子:

A=[-0.10, -0.23, 0.08, -0.38, -0.28, -0.29, -2.11, 0.34, -0.53, -67.0]

注意到向量A中有离群值(Emergent Features) − 67.0 -67.0 67.0 ,如果去掉该值对向量A做量化和反量化,处理后的结果是:

[-0.10, -0.23, 0.08, -0.38, -0.28, -0.28, -2.11, 0.33, -0.53]

出现的误差只有-0.29 -> -0.28。但是如果我们在保留 − 67.0 -67.0 67.0 的情况下对该向量做量化和反量化,处理后的结果是:

[ -0.00, -0.00, 0.00, -0.53, -0.53, -0.53, -2.11, 0.53, -0.53, -67.00]

可见大部分信息在处理后都丢失了。

幸运的是,Emergent Features的分布是有规律的。对于一个参数量为6.7亿的transformer模型来说,每个句子的表示中会有150000个Emergent Features,但这些Emergent Features只分布在6个维度中

基于此,可以采用混合精度分解的量化方法:将包含了Emergent Features的几个维度从矩阵中分离出来,对其做高精度的矩阵乘法;其余部分进行量化。如下图所示:

精度与性能

如下图所示的对比实验,可以看到,在模型参数量达到6.7亿时,使用vector-wise方法进行量化会使模型性能有非常大的下降,而使用LLM.int8()方法进行量化则不会造成模型性能的下降。

对 OPT-175B 模型,使用 lm-eval-harness 在 8 位和原始模型上运行了几个常见的基准测试,结果如下:

测试基准 - - - - -
测试基准名 指标 指标值 int8 指标值 fp16 标准差 fp16 指标差值
hellaswag acc_norm 0.7849 0.7849 0.0041 0
hellaswag acc 0.5921 0.5931 0.0049 0.001
piqa acc 0.7965 0.7959 0.0094 0.0006
piqa acc_norm 0.8101 0.8107 0.0091 0.0006
lambada ppl 3.0142 3.0152 0.0552 0.001
lambada acc 0.7464 0.7466 0.0061 0.0002
winogrande acc 0.7174 0.7245 0.0125 0.0071

LLM.int8() 方法的主要目的是在不降低性能的情况下降低大模型的应用门槛,使用了 LLM.int8() 的 BLOOM-176B 比 FP16 版本慢了大约 15% 到 23%,结果如下所示:

精度 参数量 硬件 延迟 (ms/token,BS=1) 延迟 (ms/token,BS=8) 延迟 (ms/token,BS=32)
bf16 176B 8xA100 80GB 239 32 9.9
int8 176B 4xA100 80GB 282 37.5 10.2
bf16 176B 14xA100 40GB 285 36.5 10.4
int8 176B 5xA100 40GB 367 46.4 oom
fp16 11B 2xT4 15GB 11.7 1.7 0.5
int8 11B 1xT4 15GB 43.5 5.3 1.3
fp32 3B 2xT4 15GB 45 7.2 3.1
int8 3B 1xT4 15GB 312 39.1 10.2

1.3 LLM.int8 量化的实践

bitsandbytes 是基于 CUDA 的主要用于支持 LLM.int8() 的库。它是torch.nn.modules的子类,你可以仿照下述代码轻松地将其应用到自己的模型中。

  1. 首先导入模块,初始化fp16 模型并保存
import torch
import torch.nn as nn
from bitsandbytes.nn import Linear8bitLt

fp16_model = nn.Sequential(
    nn.Linear(64, 64),
    nn.Linear(64, 64)
).to(torch.float16).to(0)
torch.save(fp16_model.state_dict(), "model.pt")
  1. 初始化 int8 模型并加载保存的weight,此处标志变量has_fp16_weights非常重要。默认情况下,它设置为True,用于在训练时使能 Int8/FP16 混合精度。
int8_model = nn.Sequential(
    Linear8bitLt(64, 64, has_fp16_weights=False),
    Linear8bitLt(64, 64, has_fp16_weights=False)
)

int8_model.load_state_dict(torch.load("model.pt"))

此时还未进行量化操作,可以查看weight值

int8_model[0].weight
Parameter containing:
Parameter(Int8Params([[ 0.1032,  0.0544,  0.1021,  ..., -0.0465,  0.1050,  0.0687],
            [ 0.0083,  0.0352,  0.0540,  ..., -0.0931, -0.0224,  0.0541],
            [ 0.0476,  0.0220, -0.0803,  ...,  0.1031,  0.1134,  0.0905],
            ...,
            [-0.0523, -0.0858,  0.0330,  ...,  0.1122, -0.1082,  0.1210],
            [ 0.0045, -0.1019,  0.0072,  ..., -0.1069, -0.0417,  0.0365],
            [-0.1134,  0.0032, -0.0742,  ..., -0.1142, -0.0374,  0.0915]]))

之后将模型加载到GPU上,此时发生量化操作:

int8_model = int8_model.to(0) # Quantization happens here

此时可以查看weight值,可以看到值已经传到GPU上并转为 INT8 类型。

int8_model[0].weight
Parameter containing:
Parameter(Int8Params([[ 105,   55,  104,  ...,  -47,  107,   70],
            [   9,   36,   56,  ...,  -96
### 大模型量化中的 Q8 Int8 #### 定义与特性 在大模型量化过程中,Q8 Int8 是两种不同的数值表示方法。Int8 表示的是整数类型的 8 位精度,其范围是从 -128 到 127;而 Q8 更多是指一种特定的量化方案,在某些情况下可能也采用 8 位整数形式,但是带有额外的比例因子偏移量来更好地映射浮点数到整数空间[^1]。 对于超大规模语言模型而言,通过引入这些低精度的数据类型可以显著减少计算资源消耗并提高推理速度。例如,在处理矩阵乘法操作时,利用专门优化过的库能够实现高效的 8-bit 运算。 #### 应用场景差异 当涉及到硬件支持方面: - **GPU vs CPU**: FP32 或者 FP16 精度级别的模型通常依赖于高性能图形处理器 (GPU),因为这类设备擅长执行高并发性的单指令流多数据流(SIMD)运算。然而,一旦采用了较低精度如 INT8 或更甚者 INT4,则可以在中央处理器(CPU)上部署相应的应用程序[^2]。 这意味着如果目标平台缺乏足够的 GPU 资源或者成本考虑倾向于使用通用服务器架构的话,那么选择适合CPU运行环境下的INT系列量化版本将会是一个明智之举. 另外值得注意的一点是在实际应用当中: - **性能权衡** :虽然降低了权重存储需求以及加速了前向传播过程,但同时也可能导致一定程度上的表达能力损失.因此具体选用哪种程度的量化方式还需依据业务场景的具体要求来做决定. ```bash # 示例命令用于展示如何将原始模型转换成不同量化格式 ./quantize ./models/model-f16.gguf ./models/model-Q4_K_M.gguf Q4_K_M ``` 此脚本展示了从半精度浮点(FP16)至自定义四比特量化(Q4_K_M)的过程,类似的工具也可以用来创建适用于其他量化标准(比如Q8/Int8)模型文件[^3].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值