模型压缩(剪枝 / 蒸馏 / 量化)
先把结论放前面
模型压缩是解决"大模型在有限资源下部署"的标准方法。三个方向解决的问题不同:
- 剪枝:减少模型参数量(哪些权重不重要,可以去掉)
- 知识蒸馏:用小模型学习大模型的行为(小模型从大模型学到"软标签")
- 量化:降低权重和计算的精度(FP32 → INT8 → INT4)
三者可以叠加:蒸馏后的模型再做量化,通常能再压缩 2-4 倍。
剪枝(Pruning)
核心思想
神经网络的权重不是同等重要的。很多权重接近零,或者对最终输出的影响极小。剪枝把这些"不重要"的权重去掉,减少参数量和计算量。
结构化剪枝 vs 非结构化剪枝
非结构化剪枝:随机把单个权重设为零。
优点:压缩率高,可以剪掉任意比例的参数
缺点:稀疏矩阵需要专门的稀疏矩阵计算库才能加速(通常硬件不支持)
结构化剪枝:按结构单位(神经元、通道、注意力头)批量剪枝。
常见单位:
- Channel Pruning(去掉整个卷积通道)
- Head Pruning(去掉 Transformer 的某个注意力头)
- Layer Pruning(去掉整个 Transformer 层)
- Neuron Pruning(去掉 FC 层的神经元)
优点:硬件友好(不需要稀疏矩阵库)
缺点:压缩率有限(每次只能剪掉一个 Channel/Head)
Magnitude Pruning(幅度剪枝)
最简单的方法:把绝对值最小的权重剪掉。
# 保留 top-k% 最大的权重,其余设为零
threshold = np.percentile(np.abs(weights), prune_percentile)
mask = np.abs(weights) > threshold
pruned_weights = weights * mask
Lottery Ticket Hypothesis(彩票假说)
一个很有趣的理论:任何一个随机初始化的稠密网络,都包含一个子网络,当它被单独训练时能达到和原网络一样的精度。
这个子网络就叫"中奖彩票"(Lottery Ticket)。
找到它的方法:训练一个大网络 → 剪枝 → 把剪枝后的结构单独重新训练(用原始随机初始化)→ 如果精度恢复,就找到中奖彩票了。
Practical Pruning 流程(工程实践)
# 1. 训练一个大网络(Teacher)
model = train_large_model()
# 2. 评估每个权重的重要性
# 常用方法:L1 norm / Taylor expansion / gradient-based
importance = torch.sum(torch.abs(model.weight), dim=0) # channel importance
# 3. 按重要性排序,剪掉最低的 p%
prune_threshold = torch.kthvalue(importance, k=int(len(importance) * 0.3)).values
mask = importance > prune_threshold
# 4. 微调恢复精度(通常 1-3 个 epoch)
model = fine_tune_pruned_model(mask, train_loader)
剪枝的比例(Pruning Rate)
通常需要做实验确定。经验值:
- 保留参数 50-70%:精度几乎不下降
- 保留参数 30-50%:可能需要更多微调
- 保留参数 < 20%:精度下降明显,需要更复杂的微调策略
知识蒸馏(Knowledge Distillation)
核心思想
大模型(Teacher)学到的不仅是"正确答案",还有"正确答案的概率分布"——比如"这张图 70% 是猫,20% 是狗",这个"软标签"比硬标签包含更多信息。
知识蒸馏让小模型(Student)同时学习:
- 硬标签(真实标签,hard target)
- 软标签(Teacher 的输出概率,soft target)
蒸馏温度(Temperature)
软标签除以一个温度参数 T 再做 softmax,增大类间差异:
# 普通 softmax
probs = softmax(logits / 1.0)
# 高温 softmax(T > 1,概率分布更平滑)
probs_soft = softmax(logits / T)
温度 T 的作用:T 越高,概率分布越平滑,小模型越容易学到类间关系。常用 T = 2~5。
蒸馏 Loss
loss = alpha * CE_loss(student_preds, hard_labels) \
+ (1 - alpha) * KL_loss(softmax(student_logits/T), softmax(teacher_logits/T))
- 第一项:学硬标签(普通交叉熵)
- 第二项:学软标签(KL 散度)
自蒸馏(Self-Distillation)
不需要单独训练 Teacher。常见做法:
- 用当前模型的 EMA(指数移动平均)版本作为 Teacher
- 或用深层的 Layer 作为 Teacher 教浅层(Depth Distillation)
中间层蒸馏
有些方法不仅让学生学习 Teacher 的最终输出,还让学生学习 Teacher 的中间层表示(如 Hidden Activation、BERT 的 [CLS] token)。
量化(Quantization)
核心思想
神经网络权重和激活值通常用 FP32(32位浮点)存储。量化把它们映射到低位宽(INT8、INT4、FP16、BF16),减少存储和计算。
量化精度级别
| 格式 | 位宽 | 动态范围 | 精度 | 硬件支持 |
|---|---|---|---|---|
| FP32 | 32bit | 3.4e38 | 最高 | 通用 |
| FP16 | 16bit | 65504 | 高 | GPU 原生 |
| BF16 | 16bit | 3.4e38 | 高 | 新 GPU |
| INT8 | 8bit | ±127/±255 | 中 | CPU/GPU/NPU |
| INT4 | 4bit | ±7/±15 | 低 | 专用硬件 |
| INT2 | 2bit | ±1/±3 | 极低 | 专用硬件 |
BF16 vs FP16:BF16 的指数范围和 FP32 相同(避免溢出),但尾数精度较低。LLM 训练推荐 BF16,推理可用 INT8。
训练后量化(PTQ,Post-Training Quantization)
模型训练好后直接量化,不需要重新训练。最简单,精度损失通常在 1-3%。
# PyTorch 训练后量化
import torch.quantization
model = load_model("model.pth")
model.eval()
model_int8 = torch.quantization.quantize_dynamic(
model, {torch.nn.Linear}, dtype=torch.qint8
)
常见方法:
- Dynamic Quantization:权重实时量化, activations 动态量化(最快,精度损失中)
- Static Quantization:需要校准数据,确定 activations 的范围(精度更好,需要校准数据)
量化感知训练(QAT,Quantization-Aware Training)
在训练时模拟量化效果,让模型适应低位宽表示。
训练时:
Forward: weights → quantized(weights) → activation
Backward: gradients computed through "fake quantization"
推理时:
用真实量化
QAT 精度比 PTQ 高(通常 1-2% 提升),但需要重新训练或微调,成本高。
GPTQ / AWQ(LLM 专用量化)
GPTQ:单样本量化,对每个权重块做一次奇异值分解,精度接近 FP16 但只需 4-8bit。适合 100B+ 的大模型。
AWQ(Activation-Aware Weight Quantization):不只看权重分布,还看激活值分布,对高激活的权重做更低量化(保护关键权重)。
# AutoAWQ 示例
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4}
model = AutoAWQForCausalLM.from_pretrained("model_name")
model.quantize(tokenizer, quant_config=quant_config)
llama.cpp 量化
llama.cpp 是最流行的 LLM 本地推理框架,支持 GGUF 格式(GGML 的继任者)。
GGUF 格式特点:
- 把 LLM 的权重和 Tokenizer 打包成一个文件
- 支持多种量化级别(Q4_K_M、Q5_K_S、Q8_0 等)
- 可以在 CPU 和 GPU 上运行
- 模型文件可以直接分发,不需要额外的推理框架
常用量化级别对比:
| 量化级别 | 内存压缩 | 精度保留 | 速度 |
|---|---|---|---|
| Q8_0 | ~4bit/param | 极高(基本无损) | 最慢 |
| Q5_K_M | ~3.5bit/param | 高 | 中 |
| Q4_K_M | ~3bit/param | 中高(主流推荐) | 快 |
| Q3_K_M | ~2.5bit/param | 中(效果明显下降) | 很快 |
| Q2_K | ~2bit/param | 低 | 最快 |
三者的对比与叠加
| 维度 | 剪枝 | 知识蒸馏 | 量化 |
|---|---|---|---|
| 压缩对象 | 参数数量 | 模型容量 | 权重精度 |
| 精度损失 | 中等 | 较低 | 中等(取决于方法) |
| 计算量 | 减少 | 不变 | 显著减少 |
| 硬件友好度 | 结构化剪枝友好 | 友好 | 最友好(INT8 有硬件支持) |
| 实现难度 | 中等(需要微调) | 中等 | 低(PTQ)或高(QAT) |
| 适用场景 | CV 模型、CNN | Teacher-Student 蒸馏 | LLM、边缘部署 |
三者叠加:
大模型 (FP32)
→ 剪枝(保留 30-50% 参数)
→ 知识蒸馏(用原模型教小模型)
→ INT8 量化(降低精度)
→ 最终模型:原大小 5-10%
如果放到面试里怎么讲
"模型压缩有哪些方法?"
我会从三个方向说:剪枝去掉不重要的参数或结构单元,知识蒸馏用大模型教小模型学习软标签,量化降低权重精度。其中量化是最通用的,剪枝和蒸馏可以叠加。
"大模型怎么在有限显存里跑起来?"
首先看量化:INT8 量化通常能把模型体积压缩到 1/4,精度损失在 1-2% 可接受。如果还不够,用 GPTQ 或 AWQ 做 4bit 量化。另一个方向是用 llama.cpp 跑 GGUF 格式,在 CPU 上也能运行。
最后记几个点
- 剪枝分结构化(硬件友好)和非结构化(压缩率高),结构化剪枝按 Channel/Head/Layer 单位剪
- 知识蒸馏的核心是让 Student 学习 Teacher 的软标签,用温度 T 控制软化程度
- 量化三层次:FP32 → FP16/BF16 → INT8 → INT4,越低压缩越大,精度损失也越大
- BF16 是 LLM 训练的推荐精度(避免溢出),INT8 是推理的主流精度
- GPTQ/AWQ 是 LLM 专用的 4bit 量化方法,比普通 INT8 效果更好
- llama.cpp + GGUF 格式是目前最流行的本地 LLM 推理方案
- 三者可以叠加:剪枝 → 蒸馏 → 量化,通常能压缩到原模型的 5-10%
跨库关联
本文件的量化/蒸馏部分是通用视角,LLM 专项内容在 大模型知识库 中: