【Datawhale学习笔记】模型量化实战
模型量化实战
什么是量化
量化,听起来是一个复杂的数学概念,但实际非常简单,就是用较少的信息来表示数据,在尽量不损失模型性能的前提下,降低资源开销。深度学习模型(无论是 CV 还是 NLP 领域)普遍表现出显著的参数冗余性。早在 1989 年,Yann LeCun 等人就在论文《Optimal Brain Damage》 中指出神经网络中存在大量参数可以被删除而不影响准确率;而后续著名的“彩票假设”(The Lottery Ticket Hypothesis) 更是进一步证明,密集网络中包含一个极小的子网络(“中奖彩票”),它的性能可与原始网络媲美。量化技术正是利用这一特性,通过降低非关键参数的数值精度(例如从 FP16 降至 INT4),在大幅减少显存占用和计算量的同时,尽可能保持模型的原始性能。
精度与显存的关系
模型权重通常以浮点数形式存储,不同的精度决定了每个参数占用的字节数:
- FP32(Full Precision):单精度浮点数,占用 4 Bytes。这是深度学习训练的默认精度,但在推理时通常不需要这么高。
- FP16 / BF16(Half Precision):半精度浮点数,占用 2 Bytes。
- FP16:传统的半精度,数值范围较小,容易溢出。
- BF16(BFloat16):Google 提出的格式,牺牲了小数位精度以换取与 FP32 相同的数值范围(指数位),训练更稳定,是目前大模型训练的主流选择。
- INT8:8 位整数,占用 1 Byte。
- INT4:4 位整数,占用 0.5 Byte(即 4 bit)。
显存估算公式
在计算机存储单位中,1 GB = 1024 MB,1 MB = 1024 KB。但在估算模型参数量(如 7B = 7 Billion)和显存(GB)时,为了方便,通常近似认为 。如果追求精确计算,记得除以 。模型所需显存大小的通用估算公式如下:
以我们之前学习过的 Qwen2.5 为例,这里选择 Qwen2.5-7B(约 70 亿参数,即 ):
(1)FP16 / BF16 精度(2 Bytes/参数):
(2)INT8 量化(1 Byte/参数):
(3)INT4 量化(0.5 Byte/参数):
Transformers 中的主流集成方案
面向生成式模型的高效量化
GPTQ (Generative Pre-trained Transformer Quantization) 3是一种面向大规模生成式 Transformer 的训练后量化(Post-Training Quantization, PTQ)技术。它是经典的 OBQ (Optimal Brain Quantization) 算法在超大模型上的高效进化版,基于近似二阶信息实现了一次性权重量化(one-shot weight quantization)。GPTQ 解决了以往简单的“四舍五入”(Round-to-Nearest, RTN)量化在模型参数超过百亿级时会导致严重精度崩塌的问题,成功将 1750 亿参数的超大模型压缩至 3-bit 或 4-bit,且几乎不损失精度。
激活感知权重量化
AWQ (Activation-aware Weight Quantization) 4 提出了一种更符合直觉且高效的量化思路,特别适合端侧部署。与 GPTQ 依赖复杂的二阶信息进行误差补偿不同,AWQ 另辟蹊径,发现权重的“重要性”并不取决于权重本身的大小,而取决于它所处理的激活值的大小。实验表明,仅保留 1% 的“显著权重”(即对应激活值较大的通道)为 FP16 精度,就能极大恢复模型性能。有趣的是,如果按权重本身的 L2 范数来选这 1%,效果和随机选差不多;但如果按激活值幅值来选,效果立竿见影。
BitsAndBytes (BNB)
在前面的实战中,我们其实已经尝试使用了 BNB,通过配置 BitsAndBytesConfig 轻松实现了 4-bit 量化加载。如果说 GPTQ 和 AWQ 是侧重于“精打细算”的量化算法,那么 BNB 则是承载了 LLM.int8() 5和 QLoRA 等前沿研究的工程基石。它不仅是一个底层的 CUDA 库,更包含了一整套处理大模型量化难题的解决方案。BNB 的主要贡献是解决了大模型量化中一个棘手的**“离群值”问题。研究发现,当模型参数规模超过 67 亿(6.7B)时,Transformer 层中会系统性地涌现出少量数值巨大的离群特征(Emergent Outliers)。虽然这些特征只占所有参数的约 0.1%,但它们对模型性能非常重要。传统的 8-bit 量化会将这些巨大的数值强制截断或粗糙量化,导致模型精度瞬间崩塌(如困惑度暴增)。如图 13-4,在 6.7B 参数规模处,普通 8-bit 量化(橙线)的准确率急剧下降,而 LLM.int8()(蓝线)则保持了与 16-bit 基线一致的性能。
Qwen2.5 模型推理量化实战
llmcompressor简介
LLM Compressor 6 是一个易于使用的库,目标是优化大语言模型以便使用 vLLM 进行部署。它能够实现高达 5 倍的推理速度提升,并显著降低成本。作为一个综合性的工具包,
llmcompressor核心功能:
- 算法支持丰富:支持包括 GPTQ、AWQ、SmoothQuant、SparseGPT 等在内的多种权重量化、激活量化和剪枝算法。
- 无缝集成:与 Hugging Face 的 Transformers、Models 和 Datasets 深度集成,使用体验流畅。
- vLLM 友好:支持基于 safetensors 的压缩模型存储格式,可直接被 vLLM 加载。
- 高效处理:借助 accelerate 库,支持对超大模型进行高性能压缩。
LLM Compressor 工作流程图

工具安装
设置环境变量
export HF_ENDPOINT=https://hf-mirror.com
创建conda环境
conda create -n llmcompressor_lab python=3.10 -y
conda activate llmcompressor_lab
环境安装
pip install llmcompressor
GPTQ 量化实战
初始化环境
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from llmcompressor.modifiers.quantization import GPTQModifier
from llmcompressor import oneshot
# 基础配置
base_model_id = "/tmp/pretrainmodel/Qwen2.5-1.5B-Instruct"
device = "cuda" if torch.cuda.is_available() else "cpu"
# 定义量化策略
# 量化后模型输出目录
gptq_out_dir = "./models/qwen2.5-1.5b-instruct-gptq-llmc"
# 定义 GPTQ 量化策略
gptq_recipe = [
GPTQModifier(
scheme="W4A16", # 权重 4bit,激活保持 16bit
targets="Linear", # 只量化线性层
ignore=["lm_head"], # 保持输出头的高精度,避免性能损失
),
]
# 执行 One-Shot 量化
oneshot(
model=base_model_id,
dataset="open_platypus", # 使用公开数据集进行校准
recipe=gptq_recipe, # 传入定义好的量化策略
output_dir=gptq_out_dir,
max_seq_length=2048,
num_calibration_samples=128, # 128个样本通常足够计算准确的统计信息
)
# 加载与效果验证
# 加载 GPTQ 量化后的检查点做推理
gptq_tokenizer = AutoTokenizer.from_pretrained(gptq_out_dir, trust_remote_code=True)
if gptq_tokenizer.pad_token_id is None:
gptq_tokenizer.pad_token = gptq_tokenizer.eos_token
gptq_model = AutoModelForCausalLM.from_pretrained(
gptq_out_dir,
device_map="auto",
torch_dtype=torch.float16,
trust_remote_code=True,
)
gptq_model.eval()
# 打印 tokenizer 的特殊 token 信息,确保 pad_token 设置正确
gptq_tokenizer.pad_token, gptq_tokenizer.eos_token, gptq_tokenizer.pad_token_id, gptq_tokenizer.eos_token_id
# 检查第 0 层的 q_proj,确认量化是否生效
layer = gptq_model.model.layers[0].self_attn.q_proj
print(f"GPTQ Layer Type: {type(layer)}")
# 简单的推理测试
gptq_tokenizer = AutoTokenizer.from_pretrained(gptq_out_dir)
@torch.no_grad()
def gptq_chat(question: str) -> str:
msgs = [
{"role": "system", "content": "你是一名 AI 助手,回答准确、简洁。"},
{"role": "user", "content": question},
]
input_ids = gptq_tokenizer.apply_chat_template(
msgs,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt",
).to(gptq_model.device)
gen_ids = gptq_model.generate(
input_ids=input_ids,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.1,
eos_token_id=gptq_tokenizer.eos_token_id,
pad_token_id=gptq_tokenizer.pad_token_id,
)
out_ids = gen_ids[0, input_ids.shape[-1]:]
return gptq_tokenizer.decode(out_ids, skip_special_tokens=True).strip()
gptq_chat("用两三句话解释一下什么是量子计算?")
AWQ 量化实战
# 初始化环境
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from llmcompressor.modifiers.quantization import GPTQModifier
from llmcompressor import oneshot
# 基础配置
base_model_id = "/tmp/pretrainmodel/Qwen2.5-1.5B-Instruct"
device = "cuda" if torch.cuda.is_available() else "cpu"
# 定义 AWQ 策略
from llmcompressor.modifiers.awq import AWQModifier
awq_out_dir = "models/qwen2.5-1.5b-instruct-awq-llmc"
awq_recipe = [
AWQModifier(
scheme="W4A16",
targets="Linear",
ignore=["lm_head"],
),
]
# 执行 One-Shot 量化
oneshot(
model=base_model_id,
dataset="open_platypus",
recipe=awq_recipe,
output_dir=awq_out_dir,
max_seq_length=2048,
num_calibration_samples=128,
)
# 加载 GPTQ 量化后的检查点做推理
gptq_tokenizer = AutoTokenizer.from_pretrained(awq_out_dir, trust_remote_code=True)
if gptq_tokenizer.pad_token_id is None:
gptq_tokenizer.pad_token = gptq_tokenizer.eos_token
gptq_model = AutoModelForCausalLM.from_pretrained(
awq_out_dir,
device_map="auto",
torch_dtype=torch.float16,
trust_remote_code=True,
)
gptq_model.eval()
# 打印 tokenizer 的特殊 token 信息,确保 pad_token 设置正确
gptq_tokenizer.pad_token, gptq_tokenizer.eos_token, gptq_tokenizer.pad_token_id, gptq_tokenizer.eos_token_id
# 检查第 0 层的 q_proj,确认量化是否生效
layer = gptq_model.model.layers[0].self_attn.q_proj
print(f"GPTQ Layer Type: {type(layer)}")
gptq_tokenizer = AutoTokenizer.from_pretrained(awq_out_dir)
@torch.no_grad()
def gptq_chat(question: str) -> str:
msgs = [
{"role": "system", "content": "你是一名 AI 助手,回答准确、简洁。"},
{"role": "user", "content": question},
]
input_ids = gptq_tokenizer.apply_chat_template(
msgs,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt",
).to(gptq_model.device)
gen_ids = gptq_model.generate(
input_ids=input_ids,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.1,
eos_token_id=gptq_tokenizer.eos_token_id,
pad_token_id=gptq_tokenizer.pad_token_id,
)
out_ids = gen_ids[0, input_ids.shape[-1]:]
return gptq_tokenizer.decode(out_ids, skip_special_tokens=True).strip()
gptq_chat("用两三句话解释一下什么是量子计算?")
DeepSpeed 分布式训练框架
DeepSpeed 概述
我们在前面已经通过 QLoRA、PEFT、RLHF 等技术,体验了在单机单卡甚至消费级显卡上完成大模型微调的可能性。但如果目标从“微调一个 7B 模型”升级为从零预训练、全量微调甚至训练万亿参数模型,仅靠量化与 LoRA 显然还不够,此时就需要引入微软开源的 PyTorch 分布式训练与推理优化库 DeepSpeed。它的目标是让开发者在相同硬件条件下用更少的显存训练更大的模型、训练更快、扩展更稳,并提供从训练到推理的一整套工程化组件。它与 Hugging Face 的关系可以简单理解为 Hugging Face 更偏向“模型与数据”的生态(transformers、datasets 等),而 DeepSpeed 更偏向“系统与算力”的生态(显存优化、并行策略、通信调度等)。
主要子模块
- DeepSpeed Training:本节的主角,包含 ZeRO、Offload、Infinity、3D 并行等训练技术;
- DeepSpeed Inference / Compression:针对推理和压缩(量化、剪枝、KV Cache 优化等)的加速库;
- DeepSpeed-MoE:面向 MoE 大模型的高效路由与通信实现;
- DeepSpeed-Chat / RLHF:支持大规模 RLHF、DeepSpeed-Chat 等对话模型训练;
- DeepSpeed for Science:将这些系统能力扩展到科学计算场景。
- 点赞
- 收藏
- 关注作者
评论(0)