大语言模型推理速度优化之模型量化实践
背景
模型压缩在尽可能保证模型性能的前提下,可以减少模型的资源占用,提高推理速度,对于实时在线的场景作用非常大。
基础名词解释
Prefill阶段:预填充阶段,该阶段会对输入的prompt中的所有token进行并行计算,同时缓存已经生成的输入token的KV键值对;
Decode阶段:解码阶段 ,会复用prefill阶段的KV缓存,逐个生成输出Token(每个输出Token都依赖前置的所有Token),由于有依赖关系,因此该阶段不可并行;
模型量化:模型量化是一种用于减少神经网络模型大小和计算量的技术,将模型参数(如权重或激活参数)从高精度数据类型(比如Float32)转换为低精度数据类型(如:int8或者fp4)。模型量化通过以更少的位数表示数据,可以减少模型尺寸,进而减少在推理时的内存消耗,并且在一些低精度运算较快的处理器上可以增加推理速度,同时尽可能的不降低模型性能。
Weight && Activations:Weight参数表示权重参数,Activations表示激活参数,我们有时会见到W8A16、W6A16这类数据,W8A16就表示权重8bit+激活16bit,W6A16就表示权重6bit+激活16bit。
理论知识
常见的数据类型
数据类型 | 位宽 | 符号位 | 指数位 | 尾数位 | 十进制有效数字 | 动态范围 | 相对内存 |
---|---|---|---|---|---|---|---|
FP64 | 64 | 1 | 11 | 52 | 15-17位 | 10^308 | 8x |
FP32 | 32 | 1 | 8 | 23 | 6-7位 | 10^38 | 4x |
BF16 | 16 | 1 | 8 | 7 | 2-3位 | 10^38 | 2x |
FP16 | 16 | 1 | 5 | 10 | 3-4位 | 10^5 | 2x |
FP8(E4M3) | 8 | 1 | 4 | 3 | ~1位 | 10^2 | 1x |
FP8(E5M2) | 8 | 1 | 5 | 2 | <1位 | 10^4 | 1x |
INT8 | 8 | 2-3位 | 10^2 | 1x | |||
INT4 | 4 | 1位 | 10^1 | 0.5x |
模型量化对象
Weight:权重是模型在训练过程中学到的固定数值,它是最常见的量化对象。量化权重可达到减少模型内存占用空间。权重在训练完成后固定,数值范围与输入无关,可离线完成量化,通常相对容易量化;
Activation:激活值是模型在处理输入序列时,其前向传播过程中计算出的动态中间结果,这部分通常是内存占用的大头,因此量化激活不仅可以大大减少内存占用。更重要的是,结合权重量化可以充分利用整数计算获得推理模型性能的提升。但是激活输出随输入变化而变化,需要统计数据动态范围,通常更难量化;
KV Cache:这是Transformer模型在自回归生成文本时使用的一种优化技术。在自回归生成中,模型每次预测下一个词元时,都需要处理从开头到当前词元的整个序列。为了减少重复计算,我们可以把前面已经计算好的词元缓存起来,后续词元可以直接使用这部分缓存。当文本较长时,KV缓存会占据较大的内存空间。因此,量化KV缓存对于提高模型长序列生成的吞吐量至关重要;
Gradient:相对上面的量化对象,略微小众一些,主要用于训练场景。在训练深度学习模型时,提督通常是浮点数,量化提督可以在分布式计算中减少通信开销,同时,也可以减少反向传播时的开销。
模型量化分类
量化感知训练(QAT):在模型训练过程中加入伪量化算子,通过训练时统计输入输出的数据范围可以提升量化后模型的精度,适用于对模型精度要求较高的场景;其量化目标无缝地集成到模型的训练过程中。这种方法使LLM在训练过程中适应低精度表示,增强其处理由量化引起的精度损失的能力。这种适应旨在量化过程之后保持更高性能。
量化感知微调(QAF):在微调过程中对LLM进行量化。主要目标是确保经过微调的LLM在量化为较低位宽后仍保持性能。通过将量化感知整合到微调中,以在模型压缩和保持性能之间取得平衡。
训练后量化(PTQ):在LLM训练完成后对其参数进行量化,只需要少量校准数据,适用于追求高易用性和缺乏训练资源的场景。主要目标是减少LLM的存储和计算复杂性,而无需对LLM架构进行修改或重新训练。PTQ的主要优势在于其简单性和高效性。但PTQ可能会在量化过程中引入一定程度的精度损失。
特性 | 训练后量化(PTQ) | 量化感知微调(QAF) | 量化感知训练(QAT) |
---|---|---|---|
介入阶段 | 训练完成后 | 训练中后期微调 | 从零开始训练 |
精度损失 | 中(3-5%) | 低(<1%) | 理论最低 |
时间成本 | 分钟级 | GPU小时级 | GPU周级 |
硬件要求 | 无 | 需支持低精度训练 | 需定制硬件 |
开源支持 | AutoGPTQ, GGUF | NNCF, Brevitas | FP6-LLM代码库 |
产业应用 | ★★★★★ | ★★★★☆ | ★☆☆☆☆ |
常见的量化方法
GPTQ(高精度训练后权重量化)
思想:仅量化权重,其中模型权重被量化为int4数值类型,而激活值则保留在FP16。GPTQ将权重分组为多个子矩阵,然后对每个子矩阵内的所有参数逐个量化,每个参数量化后,需要适当调整这个子矩阵内其他未量化的参数,来弥补量化造成的精度损失。GPTQ量化需要准备校准数据集。
优点:
- 量化速度较快
- 模型性能损耗较小(4位量化的情况下,仍能接近于全精度模型的性能)
- 内存占用大幅降低(量化后的175B模型可在单个A100-80GPU运行,未量化需5块GPU)
- 推理速度提升(在生成任务中,3位量化的模型在A100 GPU上实现了约3.25倍提速)
缺点:
- 依赖校准数据(数据质量差则效果差)
- 可能存在过拟合校准集,导致模型在校准集之外的场景下性能下降
- 只支持新显卡(Nvidia 30系以上)
AWQ(激活感知的权重量化)
思想:仅保护激活幅度最大的1%权重,此部分权重精度不变,将其余部分权重精度降低至4-bit。
优点:
- 泛化性强(不同场景表现稳定)
- 可以自动识别关键参数(无需人工指定)
缺点:
- 不支持复杂模型结构(比如MoE专家网络)
- 需简单调参
BitsandBytes(动态反量化)
思想:权重以int4/int8存储,计算时动态反量化为FP16。
优点:
- 无需校准或导出,可直接使用
- 支持4/8-bit混合精度
缺点:
- 存在性能损失,4-bit量化精度下降较明显
- 推理过程中的反量化会增加10%的延迟
QLoRA(量化低秩微调)
思想:基础权重使用4-bit存储,低秩适配器使用BF16精度进行训练
优点:
- 可极致降低模型微调使用的显存,比如微调65B模型仅需48G显存;
缺点:
- 低秩适配器需要手动调参
- 在某些任务上,QLoRA微调的模型精度可能比不上全量微调模型
模型评估
当模型量化压缩完成后,我们需评估新模型的性能。此时,我们可以借助https://github.com/EleutherAI/lm-evaluation-harness,一个开源的大语言模型评测框架。该框架集成了60多个LLM标准学术基准,包括数百个子任务以及变体。
安装
git clone https://github.com/EleutherAI/lm-evaluation-harness
cd lm-evaluation-harness
pip install -e .
使用命令行评测
# 评测Huggingface模型
lm_eval --model hf \--model_args pretrained=deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B,trust_remote_code=true \--tasks cmmlu,coqa \--device cuda:0 \--batch_size auto:1 \--output_path ./eval_out/DeepSeek-R1-Distill-Qwen-1.5B \--use_cache ./eval_cache# 评测本地模型
lm_eval --model vllm \--model_args pretrained=./models/DeepSeek-R1-Distill-Qwen-1.5B,trust_remote_code=true \--tasks cmmlu,coqa \--device cuda:0 \--batch_size auto:1 \--output_path ./eval_out/DeepSeek-R1-Distill-Qwen-1.5B \--use_cache ./eval_cache
动手实践
使用GPTQ量化DeepSeek-R1-Distill-Qwen-1.5B模型
硬件配置
- GPU-12G
- 运行内存-32G
- CPU 6核12线程
工具介绍
https://github.com/vllm-project/llm-compressor/tree/main
使用教程:https://github.com/vllm-project/llm-compressor/blob/main/examples/quantization_w4a16/README.md
原始模型导入
from transformers import AutoTokenizer, AutoModelForCausalLMMODEL_ID = "meta-llama/Meta-Llama-3-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, device_map="auto", torch_dtype="auto",
)
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
校准数据准备
from datasets import load_dataset# 数据越大,校准效果越好,但是量化过程的耗时也会增加
NUM_CALIBRATION_SAMPLES=16
MAX_SEQUENCE_LENGTH=2048# Load dataset.
ds = load_dataset("HuggingFaceH4/ultrachat_200k", split=f"train_sft[:{NUM_CALIBRATION_SAMPLES}]")
ds = ds.shuffle(seed=42)# Preprocess the data into the format the model is trained with.
def preprocess(example):return {"text": tokenizer.apply_chat_template(example["messages"], tokenize=False,)}
ds = ds.map(preprocess)# Tokenize the data (be careful with bos tokens - we need add_special_tokens=False since the chat_template already added it).
def tokenize(sample):return tokenizer(sample["text"], padding=False, max_length=MAX_SEQUENCE_LENGTH, truncation=True, add_special_tokens=False)
ds = ds.map(tokenize, remove_columns=ds.column_names)
量化压缩代码
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier# Configure the quantization algorithm to run.
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])# Apply quantization.
oneshot(model=model, dataset=ds,recipe=recipe,max_seq_length=MAX_SEQUENCE_LENGTH,num_calibration_samples=NUM_CALIBRATION_SAMPLES,
)# Save to disk compressed.
SAVE_DIR = MODEL_ID.rstrip("/").split("/")[-1] + "-W4A16"
model.save_pretrained(SAVE_DIR, save_compressed=True)
tokenizer.save_pretrained(SAVE_DIR)
量化压缩耗时约10分钟,部分日志输出如下
(25/29): Propagating: 100%|█████████████████████████████████████████████████████████████████████████████████████████████| 256/256 [00:02<00:00, 127.97it/s]
(26/29): Calibrating: 100%|██████████████████████████████████████████████████████████████████████████████████████████████| 256/256 [00:09<00:00, 27.58it/s]
2025-06-15T22:03:28.826392+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.25.self_attn.q_proj using 256 samples
2025-06-15T22:03:29.408727+0800 | compress | METRIC - time 0.58s
2025-06-15T22:03:29.409013+0800 | compress | METRIC - error 16848.74
2025-06-15T22:03:29.409136+0800 | compress | METRIC - GPU 0 | usage: 48.28% | total memory: 12 GB
2025-06-15T22:03:29.409202+0800 | compress | METRIC - Compressed module size: 4.77696 MB
2025-06-15T22:03:29.409326+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.25.self_attn.k_proj using 256 samples
2025-06-15T22:03:29.932167+0800 | compress | METRIC - time 0.52s
2025-06-15T22:03:29.932492+0800 | compress | METRIC - error 1962.31
2025-06-15T22:03:29.932624+0800 | compress | METRIC - GPU 0 | usage: 48.28% | total memory: 12 GB
2025-06-15T22:03:29.932690+0800 | compress | METRIC - Compressed module size: 0.79616 MB
2025-06-15T22:03:29.932814+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.25.self_attn.v_proj using 256 samples
2025-06-15T22:03:30.451121+0800 | compress | METRIC - time 0.52s
2025-06-15T22:03:30.451454+0800 | compress | METRIC - error 9955.87
2025-06-15T22:03:30.451579+0800 | compress | METRIC - GPU 0 | usage: 48.28% | total memory: 12 GB
2025-06-15T22:03:30.451649+0800 | compress | METRIC - Compressed module size: 0.79616 MB
2025-06-15T22:03:30.451778+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.25.self_attn.o_proj using 256 samples
2025-06-15T22:03:30.976098+0800 | compress | METRIC - time 0.52s
2025-06-15T22:03:30.976397+0800 | compress | METRIC - error 12021.09
2025-06-15T22:03:30.976531+0800 | compress | METRIC - GPU 0 | usage: 48.28% | total memory: 12 GB
2025-06-15T22:03:30.976597+0800 | compress | METRIC - Compressed module size: 4.773888 MB
2025-06-15T22:03:30.976721+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.25.mlp.gate_proj using 256 samples
2025-06-15T22:03:31.568360+0800 | compress | METRIC - time 0.59s
2025-06-15T22:03:31.568674+0800 | compress | METRIC - error 107954.09
2025-06-15T22:03:31.568797+0800 | compress | METRIC - GPU 0 | usage: 48.28% | total memory: 12 GB
2025-06-15T22:03:31.568864+0800 | compress | METRIC - Compressed module size: 27.84768 MB
2025-06-15T22:03:31.568991+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.25.mlp.up_proj using 256 samples
2025-06-15T22:03:32.157024+0800 | compress | METRIC - time 0.59s
2025-06-15T22:03:32.157350+0800 | compress | METRIC - error 118589.96
2025-06-15T22:03:32.157465+0800 | compress | METRIC - GPU 0 | usage: 48.28% | total memory: 12 GB
2025-06-15T22:03:32.157542+0800 | compress | METRIC - Compressed module size: 27.84768 MB
2025-06-15T22:03:32.157675+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.25.mlp.down_proj using 256 samples
2025-06-15T22:03:35.601911+0800 | compress | METRIC - time 3.44s
2025-06-15T22:03:35.603181+0800 | compress | METRIC - error 108456.48
2025-06-15T22:03:35.603345+0800 | compress | METRIC - GPU 0 | usage: 48.28% | total memory: 12 GB
2025-06-15T22:03:35.603418+0800 | compress | METRIC - Compressed module size: 27.84768 MB
(26/29): Propagating: 100%|█████████████████████████████████████████████████████████████████████████████████████████████| 256/256 [00:02<00:00, 126.78it/s]
(27/29): Calibrating: 100%|██████████████████████████████████████████████████████████████████████████████████████████████| 256/256 [00:09<00:00, 27.55it/s]
2025-06-15T22:03:46.915018+0800 | on_sequential_batch_end | INFO - Quantizing model.layers.26.self_attn.q_proj using 256 samples
模型压缩前的文件大小约为3.5G,压缩后的模型文件大小为1.5G
模型评估代码
from vllm import LLM
model = LLM("./Meta-Llama-3-8B-Instruct-W4A16-G128")
lm_eval --model vllm \--model_args pretrained="./Meta-Llama-3-8B-Instruct-W4A16-G128",add_bos_token=true \--tasks gsm8k \--num_fewshot 5 \--limit 250 \--batch_size 'auto'
量化前、后的模型评估结果如下
模型体积 | CoQA | gsm8K | |
---|---|---|---|
量化前 | 3.5G | 0.4232 | 0.684 |
量化后 | 1.5G | 0.4232 | 0.684 |
参考资料:
- https://github.com/EleutherAI/lm-evaluation-harness
- https://temp53ai.uweb.net.cn/news/finetuning/2025040154176.html
- https://zhuanlan.zhihu.com/p/11886909512
- https://zhuanlan.zhihu.com/p/695144724
- https://zhuanlan.zhihu.com/p/704228271
- https://mp.weixin.qq.com/s?__biz=Mzk0NzcwNTQyOA==&mid=2247483763&idx=1&sn=287fea25d46ba0e602b50313caf15acc&chksm=c3738248f4040b5ed0f770123e6a269a9121807d084da41774fbfb7efa4df9a39557274e416a&scene=21#wechat_redirect
- https://zhuanlan.zhihu.com/p/716688741
- https://zhuanlan.zhihu.com/p/627436535
- https://zhuanlan.zhihu.com/p/646210009
- https://zhuanlan.zhihu.com/p/667109491
- https://zhuanlan.zhihu.com/p/9270075965
- https://zhuanlan.zhihu.com/p/1899107168172630461
- https://qwen.readthedocs.io/zh-cn/latest/deployment/vllm.html
- https://github.com/vllm-project/llm-compressor/blob/main/examples/quantization_w4a16/README.md