量化:用更少的比特做更多的事
LLM的参数量从7B到671B不断增长,但GPU显存的增长速度远跟不上。量化是缓解这一矛盾最直接的技术——用更少的比特表示模型参数和激活值,在不修改模型架构的前提下减少显存占用和加速推理。
2026年,量化技术已经形成了从INT4到FP8的完整谱系,每种精度都有其适用场景和工程权衡。
量化的基本原理
对称量化
将浮点数映射到整数的固定范围。以INT8为例:
x_int8 = round(x_fp / scale)
scale = max(|x_fp|) / 127
反量化:x_fp ≈ x_int8 × scale
非对称量化
引入零点偏移,处理分布不对称的情况:
x_int8 = round((x_fp - zero_point) / scale)
scale = (max(x) - min(x)) / 255
zero_point = min(x)
量化粒度
- 逐张量(per-tensor):整个张量共用一个scale,最简单但精度损失大
- 逐通道(per-channel):每个输出通道一个scale,精度好但额外参数多
- 逐组(per-group):将通道分组,组内共用scale,是精度和效率的折中
def per_group_quantize(x, group_size=128):
"""分组量化"""
original_shape = x.shape
# 重排为 [out_features // group_size, group_size, in_features]
x_reshaped = x.view(-1, group_size, original_shape[-1])
# 每组计算独立的scale
scales = x_reshaped.abs().max(dim=1, keepdim=True).values / 127
# 量化
x_int8 = (x_reshaped / scales).round().clamp(-128, 127).to(torch.int8)
return x_int8, scales
def per_group_dequantize(x_int8, scales, original_shape):
"""分组反量化"""
x_fp = x_int8.float() * scales
return x_reshaped.view(original_shape)
INT8量化
适用场景
INT8是最成熟的量化方案,几乎所有推理框架都原生支持。适用于:
- 需要良好精度-速度平衡的场景
- CPU和GPU部署
- 对兼容性要求高的生产环境
精度表现
INT8量化在大部分LLM上的精度损失控制在1-3%以内。对于7B以上的模型,INT8量化几乎是"免费午餐"——精度损失可以忽略,但显存和速度都有显著改善。
W8A8(权重+激活均INT8)
将权重和激活都量化到INT8,可以获得最大的推理加速。但激活量化比权重量化更具挑战性——激活值包含异常值(outlier),这些异常值会压缩正常值的量化精度。
LLM.int8()
BitsAndBytes提出的方案解决了激活异常值问题。它将激活分解为"正常值"和"异常值"两部分:
- 正常值(>99.5%的元素)使用INT8量化
- 异常值(<0.5%的元素)保持FP16精度
def llm_int8_matmul(A, B, threshold=6.0):
"""LLM.int8() 矩阵乘法"""
# 识别异常值
outlier_mask = A.abs() > threshold
normal_mask = ~outlier_mask
# 正常部分:INT8量化计算
A_normal = A[normal_mask]
B_quantized, scales = quantize_int8(B)
result_normal = dequantize(A_normal_int8 @ B_quantized, scales)
# 异常部分:FP16精度计算
A_outlier = A[outlier_mask]
result_outlier = A_outlier @ B
# 合并结果
result = torch.zeros_like(A @ B)
result[normal_mask] = result_normal
result[outlier_mask] = result_outlier
return result
SmoothQuant
SmoothQuant通过数学等价变换将激活的异常值"迁移"到权重上,使得激活的分布更均匀,便于量化。其核心公式:
Y = (X · diag(s)^{-1}) · (diag(s) · W)
选择 s_j = max(|X_j|)^α / max(|W_j|)^{1-α},通过调整α控制异常值迁移的程度。
INT4量化
适用场景
INT4将模型大小压缩到1/4,是当前显存优化的主流方案。适用于:
- 显存极度受限的部署(如单卡部署70B模型)
- 对推理速度要求不极致的场景
- 对精度损失有一定容忍度的应用
精度挑战
INT4的精度损失比INT8显著——通常在3-8%之间。关键挑战在于4比特只能表示16个离散值,对于分布复杂的权重来说过于粗糙。
GPTQ
GPTQ是一种后训练量化方法,通过逐层最小化量化误差来恢复精度:
def gptq_quantize_layer(layer, calibration_data):
"""GPTQ单层量化"""
W = layer.weight.data # [out, in]
# 用校准数据计算Hessian矩阵
H = compute_hessian(layer, calibration_data) # [in, in]
H += torch.eye(H.shape[0]) * 0.01 # 正则化
# 逐列量化
for i in range(W.shape[1]):
# 量化第i列
w = W[:, i]
w_q = quantize(w)
# 计算量化误差
err = (w - w_q) / H[i, i]
# 将误差分摊到未量化的列
W[:, i+1:] -= err.unsqueeze(1) @ H[i, i+1:].unsqueeze(0)
W[:, i] = w_q
layer.weight.data = W
GPTQ的核心洞察是:量化一个权重后,其误差可以通过调整未量化的权重来补偿。这种"逐列量化+误差补偿"策略显著减少了累积量化误差。
AWQ
AWQ(Activation-aware Weight Quantization)发现:不是所有权重都同等重要——与大幅激活相对应的权重更为关键。AWQ通过激活分布感知的缩放来保护这些"重要权重":
def awq_quantize(W, X_calib, ratio=0.1):
"""AWQ量化"""
# 找出重要权重通道(激活值大的通道)
activation_magnitude = X_calib.abs().mean(dim=0)
important_channels = activation_magnitude.topk(
int(len(activation_magnitude) * ratio)
).indices
# 对重要通道施加缩放
scale = torch.ones(W.shape[1])
scale[important_channels] = activation_magnitude[important_channels] / \
activation_magnitude.mean()
W_scaled = W * scale
# 量化
W_quantized = quantize_int4(W_scaled)
# 反量化时除以scale
W_dequantized = dequantize(W_quantized) / scale
return W_dequantized
GGUF格式
llama.cpp使用的GGUF格式支持INT4量化,在CPU推理场景下非常流行。GGUF的INT4采用了一种特殊的"块量化"策略——每32个权重共享一个scale,实现了紧凑存储和高效反量化。
FP8量化
浮点格式的优势
与整数量化不同,FP8保持浮点数格式,有独立的指数和尾数位。这使得FP8天然支持更大的动态范围,不需要复杂的缩放策略。
FP8有两种格式:
- E4M3:4位指数,3位尾数。精度高,动态范围小。适合前向传播
- E5M2:5位指数,2位尾数。精度低,动态范围大。适合后向传播(梯度)
H100的原生支持
NVIDIA H100 GPU原生支持FP8张量核心计算,使得FP8量化不仅节省显存,还能真正加速计算。这是FP8相比INT8/INT4的重要优势——后者的反量化可能成为瓶颈。
量化感知训练(QAT)与FP8
FP8量化通常与量化感知训练配合使用:
class FP8QuantizationAwareTraining:
def __init__(self, model, calibration_data):
self.model = model
self.calibration_data = calibration_data
def quantize_weights(self):
"""将权重量化到FP8(STE直通估计器)"""
for param in self.model.parameters():
param_fp8 = param.data.to(torch.float8_e4m3fn)
# STE:前向用FP8,反向用FP16梯度
param.data = param_fp8.to(param.dtype)
FP8 vs INT8 对比
| 特性 | INT8 | FP8 |
|---|---|---|
| 精度 | 中 | 中高 |
| 动态范围 | 需scale调整 | 天然支持 |
| 硬件加速 | 通用 | H100+原生 |
| 实现复杂度 | 中 | 低 |
| 生态成熟度 | 高 | 中(2026年快速成长) |
混合精度策略
实践中,最优方案通常是对模型不同部分使用不同精度:
def apply_mixed_precision(model):
"""混合精度量化策略"""
for name, module in model.named_modules():
if 'embed' in name:
# 嵌入层:保持FP16(对量化敏感)
module.weight = module.weight.half()
elif 'lm_head' in name:
# 输出头:保持FP16
module.weight = module.weight.half()
elif 'attention' in name:
# 注意力层:INT8(精度要求中等)
quantize_module_int8(module)
elif 'ffn' in name or 'mlp' in name:
# FFN层:INT4(占参数主体,压缩收益最大)
quantize_module_int4(module)
else:
# 其他层:INT8
quantize_module_int8(module)
性能基准对比
以Llama-3-70B为例(单卡A100 80GB):
| 量化方案 | 显存占用 | 推理速度 | 困惑度变化 | 适用场景 |
|---|---|---|---|---|
| FP16(基线) | 140GB | 1.0x | 0% | 多卡部署 |
| INT8 | 70GB | 1.5x | +1.2% | 单卡A100 |
| INT4 (GPTQ) | 35GB | 2.0x | +4.5% | 单卡消费级GPU |
| INT4 (AWQ) | 35GB | 2.1x | +3.2% | 单卡消费级GPU |
| FP8 (H100) | 70GB | 2.5x | +0.8% | H100部署 |
结语
量化技术在2026年已经高度成熟。INT8是通用部署的默认选择,INT4适合显存受限场景,FP8在H100+硬件上提供了精度和速度的最佳平衡。混合精度策略进一步细化了不同层的量化方案。选择量化方案时,需要综合考虑硬件支持、精度要求和部署环境的约束。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。