基于上篇文章的理论知识,本文主要讲述了实战Transformers模型量化:介绍Facebook OPT模型的量化过程和相关技术。
Transformers 模型量化技术:GPTQ
Frantar等人发表了论文 GPTQ:Accurate Post-Training Quantization for Generative Pre-trained Transformers。作者详细介绍了一种训练后量化算法,适用于所有通用的预训练 Transformer模型,同时只有微小的性能下降。GPTQ算法需要通过对量化模型进行推理来校准模型的量化权重。其呈现的效果可以表达成Hugging Face + AutoGPTQ。
使用 GPTQ 量化模型
为了使用 auto-gptq 库量化一个模型,需要向量化器传递一个数据集。通常有两种方式构造数据集:
- 量化器支持的默认数据集(包括[‘wikitext2’,‘c4’,‘c4-new’,‘ptb’,‘ptb-new’])
- 一个字符串列表(这些字符串将被用作数据集)
先安装好项目所依赖的包,如requirements。
torch>=2.1.2==2.3.0.dev20240116+cu121
transformers==4.37.2
ffmpeg==1.4
ffmpeg-python==0.2.0
timm==0.9.12
datasets==2.16.1
evaluate==0.4.1
scikit-learn==1.3.2
pandas==2.1.1
peft==0.7.1
accelerate==0.26.1
autoawq==0.2.2
optimum==1.17.0
auto-gptq==0.6.0
bitsandbytes>0.39.0==0.41.3.post2
jiwer==3.0.3
soundfile>=0.12.1==0.12.1
librosa==0.10.1
gradio==4.13.0
trl
然后进行加载requirements.txt内所有依赖包。
pip install -r requirements.txt
运行出来的界面如下图所示:
使用 GPTQ 算法支持的默认数据集来量化
对此,现使用"wikitext2"数据集将模型量化为4位精度,且支持的精度有[2, 4, 6, 8]。
from transformers import AutoModelForCausalLM, AutoTokenizer, GPTQConfig
import torch
model_id = "facebook/opt-2.7b"
quantization_config = GPTQConfig(
bits=4, # 量化精度
group_size=128,
dataset="c4",
desc_act=False,
)
tokenizer =AutoTokenizer.from_pretrained(model_id)
quant_model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=quantization_config, device_map='auto')
关于device_map参数,大家可以浏览huggingface的官方原文解释。
地址:https://huggingface.co/docs/transformers/v4.36.1/en/quantization
上图为huggingface官方解释界面。
实测GPU显存占用:量化模型(峰值超过7GB)
由于电脑配置较低导致没有运行出来,笔者暂截取Github上的示例图。
检查量化模型正确性
通过检查线性层的属性来确保模型已正确量化,它们应该包含qweight和qzeros属性,这些属性应该是torch.int32数据类型。更简单地说,GPTQ简单的方法能够检查量化模型是否正确,是否有错误等,但该方法无法衡量模型的参数值被量化得非常好。即能够运行出来的模型到底有没有达到比较好的指标是无法被检查出来的。
quant_model.model.decoder.layers[0].self_attn.q_proj.__dict__
使用GPU加载模型并生成文本
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
text = "Merry Christmas! I'm glad to"
inputs = tokenizer(text, return_tensors="pt").to(0)
out = quant_model.generate(**inputs, max_new_tokens=64)
print(tokenizer.decode(out[0], skip_special_tokens=True))
使用自定义数据集量化模型
下面演示通过传递自定义数据集来量化一个模型。通过字符串列表来自定义一个数据集,建议样本数不少于128(样本数太少会影响模型性能)
from transformers import AutoModelForCausalLM, GPTQConfig, AutoTokenizer
model_name_or_path = "facebook/opt-2.7b"
custom_dataset = ["auto-gptq is an easy-to-use model quantization library with user-friendly apis, based on GPTQ algorithm."]
custom_quantization_config = GPTQConfig(
bits=4,
group_size=128,
desc_act=False,
dataset=custom_dataset
)
custom_quant_model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
quantization_config=custom_quantization_config,
torch_dtype=torch.float16,
device_map="auto")
相比使用默认数据集,未经精心准备的自定义数据集会明显降低模型性能。
text = "Merry Christmas! I'm glad to"
inputs = tokenizer(text, return_tensors="pt").to(0)
out = custom_quant_model.generate(**inputs, max_new_tokens=64)
print(tokenizer.decode(out[0], skip_special_tokens=True))
Transformers 模型量化技术:AWQ
在AWQ:Activation-aware Weight Quantization for LLM Compression and Acceleration论文中,作者介绍了一种激活感知权重量化算法,可以用于压缩任何基于 Transformer 的语言模型,同时只有微小的性能下降。AWQ可以看成transformers + quantization综合表达的效果。
量化前模型测试文本生成任务
from transformers import pipeline
model_path = "facebook/opt-125m"
# 使用 GPU 加载原始的 OPT-125m 模型
generator = pipeline('text-generation',
model=model_path,
device=0,
do_sample=True,
num_return_sequences=3)
实测GPU显存占用:加载OPT-125m模型后
示例对比
generator("The woman worked as a")
示例1 运行结果
generator("The man worked as a")
示例2 运行结果
使用 AutoAWQ 量化模型
下面我们以 facebook opt-125m 模型为例,使用 AutoAWQ 库实现的 AWQ 算法实现模型量化。
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
quant_path = "models/opt-125m-awq"
quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"}
# 加载模型
model = AutoAWQForCausalLM.from_pretrained(model_path, device_map="cuda")
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# 量化模型
model.quantize(tokenizer, quant_config=quant_config)
实测GPU显存使用:量化模型时峰值达到将近 4GB。
quant_config
Transformers兼容性配置
from transformers import AwqConfig, AutoConfig
# 修改配置文件以使其与transformers集成兼容
quantization_config = AwqConfig(
bits=quant_config["w_bit"],
group_size=quant_config["q_group_size"],
zero_point=quant_config["zero_point"],
version=quant_config["version"].lower(),
).to_dict()
# 预训练的transformers模型存储在model属性中,需要传递一个字典
model.model.config.quantization_config = quantization_config
# 保存模型权重
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path) # 保存分词器
Transformers 量化技术 BitsAndBytes
bitsandbytes是将模型量化为8位和4位的最简单选择。
- 8位量化将fp16中的异常值与int8中的非异常值相乘,将非异常值转换回fp16,然后将它们相加以返回fp16中的权重。这减少了异常值对模型性能产生的降级效果。
- 4位量化进一步压缩了模型,并且通常与QLoRA一起用于微调量化LLM(低精度语言模型)。
异常值: 是指大于某个阈值的隐藏状态值,这些值是以fp16进行计算的。虽然这些值通常服从正态分布([-3.5, 3.5]),但对于大型模型来说,该分布可能会有很大差异([-60, 6]或[6, 60])。8位量化适用于约为5左右的数值,但超过此范围后将导致显著性能损失。一个好的默认阈值是6,但对于不稳定的模型(小型模型或微调)可能需要更低的阈值。
在 Transformers 中使用参数量化
使用 Transformers 库的 model.from_pretrained()方法中的load_in_8bit或load_in_4bit参数,便可以对模型进行量化。只要模型支持使用Accelerate加载并包含torch.nn.Linear层,这几乎适用于任何模态的任何模型。
from transformers import AutoModelForCausalLM
model_id = "facebook/opt-2.7b"
model_4bit = AutoModelForCausalLM.from_pretrained(model_id,
device_map="auto",
load_in_4bit=True)
model_4bit
# 获取当前模型占用的 GPU显存(差值为预留给 PyTorch 的显存)
memory_footprint_bytes = model_4bit.get_memory_footprint()
memory_footprint_mib = memory_footprint_bytes / (1024 ** 2) # 转换为 MiB
print(f"{memory_footprint_mib:.2f}MiB")
运行结果为:1457.52MiB,
而在显存中占用的初始结果为1774MiB。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(model_id
)
text = "Merry Christmas! I'm glad to"
inputs = tokenizer(text, return_tensors="pt").to(0)
out = model_4bit.generate(**inputs, max_new_tokens=64)
print(tokenizer.decode(out[0], skip_special_tokens=True))
使用 NF4 精度加载模型
from transformers import BitsAndBytesConfig
nf4_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
)
model_nf4 = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=nf4_config)
# 获取当前模型占用的 GPU显存(差值为预留给 PyTorch 的显存)
memory_footprint_bytes = model_nf4.get_memory_footprint()
memory_footprint_mib = memory_footprint_bytes / (1024 ** 2) # 转换为 MiB
print(f"{memory_footprint_mib:.2f}MiB")
运行结果为:1457.52MiB
而在显存中占用的初始结果为3414MiB。
使用双量化加载模型
double_quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
)
model_double_quant = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=double_quant_config)
# 获取当前模型占用的 GPU显存(差值为预留给 PyTorch 的显存)
memory_footprint_bytes = model_double_quant.get_memory_footprint()
memory_footprint_mib = memory_footprint_bytes / (1024 ** 2) # 转换为 MiB
print(f"{memory_footprint_mib:.2f}MiB")
运行结果为:1457.52MiB,
而在显存中占用的初始结果为:4910MiB
使用 QLoRA 所有量化技术加载模型
import torch
qlora_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
model_qlora = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=qlora_config)
# 获取当前模型占用的 GPU显存(差值为预留给 PyTorch 的显存)
memory_footprint_bytes = model_qlora.get_memory_footprint()
memory_footprint_mib = memory_footprint_bytes / (1024 ** 2) # 转换为 MiB
print(f"{memory_footprint_mib:.2f}MiB")
运行结果为:1457.52MiB。