🤗量化入门,💗🧑‍🍳烹制

社区文章 发布于2023年8月25日

Image by jenestephaniuk on Unsplash

量化是一组技术,用于降低深度学习模型的精度,使模型更小,训练更快。如果你不理解这句话,别担心,在这篇博客文章的最后你会明白。在这篇博客文章中,我们将介绍...

  1. 什么是精度,为什么我们需要量化,以及一个简单的量化示例;
  2. GPTQ 量化;
  3. 4/8 位(bitsandbytes)量化。

冲呀!

精度

精度可以定义为表示一个数字所使用的有效数字或位数。它是衡量一个数字可以被**精确**表示的程度,或者在表示中可以保留多少细节(在我们的例子中,是二进制表示)。

在这篇博客文章中,我不会详细介绍用于精度的二进制表示如何工作,但你可以查看这篇直观的博客文章来了解它的工作原理。

我们根据数字本身的不同,以不同的数据类型表示数字。每种数据类型都有其可以表示的数字范围。常见的数据类型包括:

数据类型 范围 精度
FP32(单精度) 约 ±1.4013 x 10^-45 到 ±3.4028 x 10^38 7 位小数
FP16(半精度) 约 ±5.96 x 10^-8 到 ±6.55 x 10^4 3-4 位小数
FP8(自定义 8 位浮点) 动态 动态
Int8(8 位整数) -128 到 127(有符号)或 0 到 255(无符号) 无小数

一个数字需要表示得越精确,它所占用的内存就越多。在深度学习中,我们使用浮点表示(FP32/16/8...)来表示权重。虽然 FP32 表示能提供更高的精度和准确性,但模型大小会变得更大,并且训练或推理(取决于量化类型)期间的计算会变慢。因此,需要降低精度,这也被称为量化。它是一种有损压缩形式,我们压缩的信息越多,性能损失就越大。

一种非常简单的量化技术是将较大范围的量化类型缩放/投影到较小的尺度,例如(FP32 到 int8)。这看起来如下图所示 👇

image/png

对于给定数据类型范围 [-α, α],我们可以使用以下公式投影给定值 ss

s=(2b1)1/α=127/αs = (2^{b-1})-1/α = 127/α

然而,这种方法有不同的缺点,例如在训练和推理过程中引入开销,降低性能,并且对数据中的分布变化敏感。尽管它非常简单,但在不同的混合精度训练方法中被大量使用。

主要有两种量化方法 👇

  • 训练后量化:这些方法侧重于在模型训练后降低精度。由于它更容易理解,我们将在本博客文章中主要介绍这种方法,尽管它的性能不如量化感知训练。
  • 量化感知训练:这种方法允许量化模型,然后对模型进行微调,以减少量化引起的性能下降,或者量化可以在训练期间进行。

最先进的方法正试图克服上述问题。我们现在将讨论它们以及如何通过 🤗 生态系统中的工具(transformers、TGI、optimum 和 PEFT)使用它们。

GPTQ 量化

GPTQ 是一种训练后量化方法,通过校准数据集使模型更小。GPTQ 的核心思想非常简单:它通过找到权重的压缩版本来量化每个权重,以使均方误差最小,如下图所示 👇

给定一个层 ll 及其权重矩阵 WlW_{l} 和层输入 XlX_{l},找到量化权重 W^l\hat{W}_{l}

(W^l=argminWl^WlXW^lX22)({\hat{W}_{l}}^{*} = argmin_{\hat{W_{l}}} ||W_{l}X-\hat{W}_{l}X||^{2}_{2})

在 GPTQ 中,我们一次性进行后量化,这既能节省内存又能加快推理速度(与我们稍后将介绍的 4/8 位量化不同)。AutoGPTQ 是一个支持 GPTQ 量化的库。它已集成到 🤗 生态系统中的各种库中,用于量化模型、使用/服务已量化模型或进一步微调模型。让我们看看如何做到这些。

首先,安装 AutoGPTQ

pip install auto-gptq # for cuda versions other than 11.7, refer to installation guide in above link
pip install transformers optimum peft
  • 你可以在 Hugging Face Hub 上运行给定的 GPTQ 模型,如下所示(在此处查找它们:这里)👇
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("TheBloke/Llama-2-7b-Chat-GPTQ", torch_dtype=torch.float16, device_map="auto")

你可以像下面这样量化任何 transformer 模型 👇

from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig

model_id = "facebook/opt-125m"
tokenizer = AutoTokenizer.from_pretrained(model_id)
quantization_config = GPTQConfig(bits=4, dataset = "c4", tokenizer=tokenizer)

model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto", quantization_config=quantization_config)
  • 你可以使用 PEFT 在这个gist中进一步微调给定的 GPTQ 模型。
  • 你可以使用 text-generation-inference 量化和提供已经用 GPTQ 方法量化的模型。它在底层不使用 AutoGPTQ 库。按照此处的说明安装 TGI 后,你可以像下面这样运行你在 hub 上找到的 GPTQ 模型 👇
docker run --gpus all --shm-size 1g -p 8080:80 -v $volume:/data ghcr.io/huggingface/text-generation-inference:latest --model-id $model --quantize gptq

要仅使用校准数据集运行量化,只需运行

text-generation-server quantize tiiuae/falcon-40b /data/falcon-40b-gptq

您可以通过运行 text-generation-server quantize --help 了解更多量化选项。

使用 bitsandbytes 进行 4/8 位量化

bitsandbytes 是一个用于对模型进行 8 位和 4 位量化的库。它可以在训练期间用于混合精度训练,也可以在推理前使用,以使模型更小。

8 位量化使数十亿参数规模的模型能够适应更小的硬件,而不会降低性能。8 位量化工作原理如下 👇

  1. 从输入隐藏状态中按列提取较大的值(离群值)。
  2. 对 FP16 中的离群值和 int8 中的非离群值进行矩阵乘法。
  3. 将非离群值结果放大以将值拉回到 FP16,并将其添加到 FP16 中的离群值结果中。

所以本质上,我们执行矩阵乘法以节省精度,然后将非离群值结果拉回 FP16,而不会在非离群值的初始值和缩放回的值之间产生很大的差异。您可以在下面看到一个示例 👇

您可以像下面这样安装 bitsandbytes 👇

pip install bitsandbytes

要在 transformers 中以 8 位加载模型,只需运行 👇

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

model_8bit = AutoModelForCausalLM.from_pretrained(name, device_map="auto", load_in_8bit=True)
tokenizer = AutoTokenizer.from_pretrained(name)
encoded_input = tokenizer(text, return_tensors='pt')
output_sequences = model.generate(input_ids=encoded_input['input_ids'].cuda())
print(tokenizer.decode(output_sequences[0], skip_special_tokens=True))

bitsandbytes 8 位量化的一个缺点是推理速度比 GPTQ 慢。

4 位浮点(FP4)和 4 位 NormalFloat(NF4)是两种与 QLoRA 技术(一种参数高效微调技术)一起使用的数据类型。这些数据类型也可以用于在不使用 QLoRA 的情况下减小预训练模型的大小。TGI 基本上在推理前使用这些数据类型进行训练后量化。

与 8 位加载类似,您可以在 4 位中加载 transformers 模型,如下所示 👇

from transformers import AutoModelForCausalLM

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

在 TGI 中,您可以通过将 --quantize 与以下值一起传递来使用 bitsandbytes 量化技术,以实现不同的量化方法

  • 用于 8 位量化的 bitsandbytes
  • 用于 4 位 NormalFloat 的 bitsandbytes-nf4
  • 用于 4 位的 bitsandbytes-fp4,如下所示 👇
docker run --gpus all --shm-size 1g -p 8080:80 -v $volume:/data ghcr.io/huggingface/text-generation-inference:latest --model-id $model --quantize bitsandbytes

有用资源

您可以通过以下链接了解更多上述概念。

社区

注册登录 以评论