文章摘要
系统掌握模型压缩的三大核心方法——量化、剪枝和知识蒸馏,理解原理、对比方案、掌握实战技巧,让大模型在资源受限环境中高效运行
1模型压缩为什么是 AI 工程化的必修课
AI 模型的参数量和计算量在过去几年呈指数级增长。从 GPT-3 的 1750 亿参数到当前最大规模模型的万亿级别参数,模型能力的提升伴随着部署成本的急剧上升。这种趋势带来了三个核心矛盾。
第一个矛盾是模型体积与存储空间的矛盾。一个 FP32 精度的大语言模型动辄占用数十 GB 甚至上百 GB 的显存。在实际部署场景中——无论是边缘设备、移动端还是云端推理服务——存储成本都是硬性约束。特别是当需要部署多个模型副本以服务高并发请求时,存储开销成倍增长。
第二个矛盾是计算复杂度与延迟的矛盾。Transformer 架构的自注意力机制具有 O(n²) 的计算复杂度,序列越长计算量越大。在实时应用场景中——比如语音助手、自动驾驶的感知模块——推理延迟必须控制在毫秒级别,而未经优化的大模型往往需要数百毫秒甚至秒级的响应时间。
第三个矛盾是精度追求与能耗的矛盾。FP32 全精度模型虽然精度最高,但能耗也最大。数据中心级别的部署需要考虑 PUE(电源使用效率)指标,而单个芯片的功耗上限也在不断提高——当前 GPU 的 TDP 已经普遍超过 500W。
模型压缩的目标就是在尽可能保留精度的前提下,同时解决这三个矛盾。
| 矛盾维度 | 问题表现 | 压缩技术解法 | 收益 |
|---|---|---|---|
存储空间 | FP32 模型占用数十 GB | 量化减少位宽 | 体积缩小 4-8 倍 |
推理延迟 | 大模型响应慢 | 剪枝减少计算量 | 延迟降低 2-5 倍 |
能耗成本 | 高 TDP 导致能耗大 | 蒸馏+量化联合优化 | 能耗降低 3-10 倍 |
💡 一句话理解
模型压缩不是在真空中做选择。在开始压缩之前,必须先明确你的部署目标:目标硬件是什么?延迟预算是多少?精度损失能接受多少?这三个问题的答案决定了你应该选择哪种压缩策略。
2量化(Quantization):从 FP32 到低位宽的核心原理
量化是模型压缩中最直接、最广泛应用的技术。其核心思想很简单:用更少的比特数表示模型的权重和激活值,从而减少存储需求和计算量。但实现这一目标的方式远非简单的"舍入"那么简单。
量化的本质是一个映射问题。假设原始权重范围是 [-2.0, 2.0],FP32 用 32 位表示。如果量化为 INT8(8 位有符号整数,范围 -128 到 127),需要将每个浮点值 x 映射到整数 q = round(x / scale),其中 scale = (max_val - min_val) / (2^n - 1)。这个 scale 参数决定了量化的精度——scale 越小,量化粒度越细,精度损失越小。
量化按时机分为两大类:训练后量化(PTQ)和量化感知训练(QAT)。PTQ 在模型训练完成后直接进行量化,不需要重新训练,速度快但精度损失较大。QAT 在训练过程中模拟量化误差,让模型学会在低位宽下保持精度,效果更好但训练成本更高。
按粒度则分为逐层量化(每个层使用统一的 scale)、逐通道量化(每个输出通道独立 scale)和逐组量化(每组权重共享 scale)。逐通道量化在当前模型压缩实践中是性价比最高的选择——它比逐层量化精度更高,比逐组量化实现更简单。
import torch
import torch.nn as nn
class PTQQuantizer:
"""训练后量化(PTQ):对称量化示例"""
def __init__(self, bits: int = 8):
self.bits = bits
self.q_max = 2 ** (bits - 1) - 1 # INT8: 127
self.q_min = -(2 ** (bits - 1)) # INT8: -128
def quantize(self, tensor: torch.Tensor) -> torch.Tensor:
# 计算对称缩放因子:取绝对值最大值
scale = tensor.abs().max() / self.q_max
# 映射到整数量化范围
q_tensor = torch.clamp(
torch.round(tensor / scale),
self.q_min, self.q_max
).to(torch.int8)
return q_tensor, scale
def dequantize(self, q_tensor: torch.Tensor, scale: float) -> torch.Tensor:
# 反量化回浮点(用于推理时实际计算)
return q_tensor.to(torch.float32) * scale
# 使用示例
model = torch.load("model_fp32.pt")
quantizer = PTQQuantizer(bits=8)
for name, param in model.named_parameters():
q_param, scale = quantizer.quantize(param.data)
param.data = quantizer.dequantize(q_param, scale)
print(f"{name}: scale={scale:.6f}")3量化精度阶梯:INT8 到 INT4 到 FP4 的精度-体积权衡
量化并非越低位宽越好,不同的位宽选择对应着不同的精度损失曲线和应用场景。理解这条曲线是做出正确工程决策的关键。
INT8 量化是当前最成熟的量化方案。几乎所有主流推理框架都原生支持 INT8。对于大多数模型,INT8 量化带来的精度损失在 0.1-0.5% 范围内,同时将模型体积缩小 4 倍(32 bit → 8 bit)。这是绝大多数部署场景的首选方案——在精度几乎无损的前提下获得了显著的性能提升。
INT4 量化将位宽进一步压缩到 4 位,模型体积缩小 8 倍。但代价是精度损失显著增大——通常在 1-5% 之间,且对特定任务(如数学推理、代码生成)的损失更大。INT4 量化需要更精细的实现——分组量化和混合精度是 INT4 实用化的关键技术。分组量化将权重分成小块,每块独立计算 scale,减少极端值的负面影响。混合精度则保留关键层(通常是最后一层或注意力层)为 INT8,其余层使用 INT4。
FP4(4 位浮点)是较新的量化格式。与 INT4 相比,FP4 保留了浮点数的动态范围特性——它可以表示很小的值(0.001 级别)和很大的值(10 级别),而 INT4 的范围是固定的。FP4 在精度上优于 INT4,但硬件支持仍在发展中。
对于大语言模型,还有一个关键技术是 KV Cache 量化。KV Cache 占用量与序列长度成正比,长文本场景下可能比模型权重本身还大。对 KV Cache 进行 INT8 甚至 INT4 量化,可以在长文本场景中大幅减少显存占用。
| 位宽 | 体积压缩 | 典型精度损失 | 硬件支持 | 推荐场景 |
|---|---|---|---|---|
FP32 | 1x(基准) | 0% | 全部 | 训练、高精度推理 |
FP16/BF16 | 2x | 0-0.1% | GPU 全部支持 | 常规推理首选 |
INT8 | 4x | 0.1-0.5% | 广泛支持 | 生产部署首选 |
INT4 | 8x | 1-5% | 部分支持 | 端侧/边缘部署 |
FP4 | 8x | 0.5-2% | 发展中 | 前沿探索 |
INT2 | 16x | 5-15% | 极少支持 | 不推荐用于生产 |
4剪枝(Pruning):删除冗余参数的艺术与科学
如果说量化是用更少的比特表示相同的参数,那么剪枝就是直接删除不重要的参数。神经网络的一个关键特性是过度参数化——训练后的模型中,大量参数对最终输出的贡献微乎其微。剪枝的目标就是识别并移除这些冗余参数。
剪枝按结构分为两大类。非结构化剪枝(Unstructured Pruning)直接删除单个权重值为零的元素——它将权重矩阵中的某些元素置零,形成稀疏矩阵。这种方法能实现很高的压缩率(90% 以上的参数可以剪掉),但稀疏矩阵在通用硬件上无法直接加速——需要专门的稀疏计算库(如 Intel MKL-Sparse)才能利用稀疏性。
结构化剪枝(Structured Pruning)则删除完整的结构单元——整个滤波器、整个通道、整个注意力头或整个层。这种方法的好处是剪枝后的模型可以直接在通用硬件上加速——因为它仍然是稠密矩阵,只是维度变小了。代价是结构化剪枝的压缩率上限较低——通常能压缩 30-60%,而非结构化剪枝可以达到 90% 以上。
剪枝的粒度选择也是一个关键决策。Fine-grained 剪枝(元素级别)灵活但难加速;Coarse-grained 剪枝(通道/头级别)加速效果好但可能丢失更多精度。对于 Transformer 架构,注意力头剪枝和 FFN 层通道剪枝是当前最实用的结构化剪枝方案。
import torch
import torch.nn.utils.prune as prune
def iterative_prune(model, amount=0.3, steps=5):
"""迭代剪枝:逐步增加剪枝比例,减少精度损失"""
for step in range(1, steps + 1):
current_amount = amount * step / steps
pruned_count = 0
total_count = 0
for name, module in model.named_modules():
if isinstance(module, (torch.nn.Linear, torch.nn.Conv2d)):
# 按权重幅度剪枝——绝对值最小的最先被剪
prune.l1_unstructured(
module, name='weight',
amount=current_amount
)
# 将掩码永久化,释放显存
prune.remove(module, 'weight')
pruned_count += (module.weight == 0).sum().item()
total_count += module.weight.numel()
sparsity = 100 * pruned_count / total_count if total_count > 0 else 0
print(f"Step {step}/{steps}: 稀疏度 {sparsity:.1f}%")
# 在每步之间进行短暂微调
# fine_tune_one_epoch(model, train_loader)
return model
# 使用示例:目标 50% 稀疏度,分 5 步迭代
model = torch.load("trained_model.pt")
model = iterative_prune(model, amount=0.5, steps=5)💡 一句话理解
迭代剪枝比一次性剪枝的精度损失小得多。每次剪枝后微调几轮,让模型适应新的稀疏结构,再进行下一轮剪枝——这是工业实践中最常用的剪枝策略。
5注意力头剪枝:Transformer 特有的剪枝策略
Transformer 架构的多头注意力机制天然支持头级别的剪枝。每个注意力头学习不同的模式——有的关注局部上下文,有的关注长距离依赖,有的关注特定词性。通过识别和删除贡献较小的头,可以在几乎不损失精度的前提下减少计算量。
头重要性评估方法有多种。最简单的是基于权重幅度的评估——计算每个头输出投影层的权重范数,范数越小的头被认为越不重要。更精确的方法是使用基于梯度的重要性评分——在验证集上计算每个头输出对损失函数的梯度,梯度越大说明该头对模型输出越重要。
还有一种更高级的方法是基于注意力模式相似度的评估。如果两个头产生的注意力分布高度相似(比如余弦相似度 > 0.9),说明它们在学类似的东西,可以合并或删除其中一个。这种方法特别适合已经过度参数化的大模型。
在实际操作中,通常建议采用分层剪枝策略——先剪浅层的头(对最终精度影响较小),再根据精度下降情况决定是否继续剪深层。一般来说,Transformer 模型可以安全剪掉 20-40% 的注意力头而不引起显著的精度下降。
6知识蒸馏(Knowledge Distillation):让小学生学会大学生的思维
知识蒸馏的核心隐喻非常直观:让一个小模型模仿一个大模型的行为。但这里的模仿不仅仅是模仿输出结果,更重要的是模仿大模型的内部推理过程。
传统的监督学习只关注模型的输出——给定输入 x,模型预测 y,与真实标签比较计算损失。但大模型在预测时不仅仅输出一个类别,它还输出了整个概率分布——比如一张猫的图片,大模型可能输出 [猫: 0.85, 狗: 0.10, 鸟: 0.03, ...]。这个分布包含了丰富的暗知识(Dark Knowledge)——它告诉我们猫和狗在某些特征上相似(都是四足动物),但和鸟差异较大。
蒸馏的关键就是让小模型也学到这些暗知识。蒸馏通过一个温度参数 T 来控制概率分布的平滑程度。高温下,概率分布变得更均匀,暗知识更明显;低温下,分布更尖锐,接近原始的 one-hot 标签。
蒸馏损失函数由两部分组成:一部分是学生模型输出与教师模型输出(经过温度软化)之间的 KL 散度,另一部分是学生模型输出与真实标签之间的交叉熵。
知识蒸馏有三种主要策略:响应蒸馏(输出层的概率分布对齐)、特征蒸馏(中间层的特征图对齐)和关系蒸馏(样本间关系的对齐)。响应蒸馏最简单但效果有限;特征蒸馏能传递更多知识但需要对齐中间层维度;关系蒸馏最能捕捉教师模型的推理逻辑但实现最复杂。
import torch
import torch.nn.functional as F
def distillation_loss(
student_logits: torch.Tensor,
teacher_logits: torch.Tensor,
true_labels: torch.Tensor,
temperature: float = 4.0,
alpha: float = 0.5
) -> torch.Tensor:
"""知识蒸馏损失 = α * 蒸馏损失 + (1-α) * 硬标签损失"""
# 软化后的教师/学生概率分布
soft_teacher = F.softmax(teacher_logits / temperature, dim=1)
soft_student = F.log_softmax(student_logits / temperature, dim=1)
# KL 散度:学生模仿教师的软分布
distill_loss = F.kl_div(
soft_student, soft_teacher, reduction='batchmean'
) * (temperature ** 2)
# 标准交叉熵:学生对齐真实标签
hard_loss = F.cross_entropy(student_logits, true_labels)
# 加权组合
total_loss = alpha * distill_loss + (1 - alpha) * hard_loss
return total_loss
# 训练循环示例
for inputs, labels in train_loader:
teacher.eval() # 教师模型冻结
student.train()
with torch.no_grad():
teacher_logits = teacher(inputs)
student_logits = student(inputs)
loss = distillation_loss(
student_logits, teacher_logits, labels,
temperature=4.0, alpha=0.5
)
loss.backward()
optimizer.step()7特征蒸馏与关系蒸馏:超越输出层的知识传递
响应蒸馏只关注最终输出,但教师模型的真正价值在于其多层级的表征能力。特征蒸馏的目标是让学生的中间层也能学习到教师中间层的特征表达。
特征蒸馏的实现面临一个核心挑战:教师和学生模型的中间层维度通常不同。教师模型可能有 1024 维的特征空间,学生模型只有 256 维。解决这个问题的方法有两种:投影适配和注意力适配。投影适配通过一个可学习的线性层将学生特征投影到教师特征空间(或反之),然后计算 MSE 损失。注意力适配则用注意力机制代替直接投影,让学生学会关注教师特征中的重要部分。
关系蒸馏更进一步——它不关心单个样本的特征,而是关心样本之间的关系。比如教师模型认为「猫的图片 A 和狗的图片 B 的相似度是 0.3,和鸟的图片 C 的相似度是 0.1」,学生模型也应该学到这种关系结构。关系蒸馏通过构建样本间的相似度矩阵(Gram 矩阵或亲和矩阵),让学生模型的相似度矩阵逼近教师模型的。
在实际应用中,最常用的是响应蒸馏加上特征蒸馏的组合。纯关系蒸馏的训练成本较高,通常只在精度要求极端的场景中使用。
💡 一句话理解
特征蒸馏的温度参数通常比响应蒸馏更低(2-3 而不是 4-8),因为中间层特征的分布比输出层更集中。如果蒸馏 loss 不稳定,尝试降低温度或增加 alpha 权重。
8三种压缩方法的系统性对比
量化、剪枝和蒸馏各有优势和局限。理解它们的差异是做出正确技术选型的前提。
量化的优势是实现简单、通用性强——几乎所有推理框架都支持。它最适合的场景是:模型已经训练好,需要快速减少部署成本。但量化的局限是它只是改变了数值表示方式,并没有减少实际计算量(除非硬件有低位宽加速支持)。
剪枝的优势是真正减少了计算量——被剪掉的参数不再参与计算。它最适合的场景是:需要加速推理、目标硬件支持稀疏计算或采用结构化剪枝。但剪枝的局限是需要微调来恢复精度,且非结构化剪枝在通用硬件上无法直接加速。
蒸馏的优势是不仅能压缩模型,有时甚至能让小模型在特定任务上超过大模型(因为小模型更专注、不过度参数化)。它最适合的场景是:有一个训练好的大模型,需要为特定场景训练一个专用小模型。但蒸馏的局限是需要额外的训练过程,且需要教师模型持续参与。
三种方法不是互斥的,而是互补的。工业实践中最常见的策略是:先用剪枝减少参数数量,再用量化降低位宽,最后用蒸馏微调恢复精度。这种组合策略通常能实现 10-20 倍的压缩比,精度损失控制在 1% 以内。
| 维度 | 量化 | 剪枝 | 知识蒸馏 |
|---|---|---|---|
压缩原理 | 减少位宽 | 删除参数 | 模仿教师模型 |
是否需要训练 | PTQ: 否 / QAT: 是 | 需要微调 | 需要完整训练 |
典型压缩比 | 4x(INT8)/ 8x(INT4) | 2-10x | 2-20x(取决于学生大小) |
精度损失 | 0.1-0.5%(INT8) | 1-3%(微调后) | 0.5-2% |
实现难度 | 低 | 中 | 中高 |
硬件加速支持 | 广泛 | 稀疏硬件有限 | 不需要特殊支持 |
最佳组合顺序 | 第二步 | 第一步 | 最后一步 |
9LLM 时代的模型压缩新范式
大语言模型的出现给模型压缩带来了全新的挑战和机遇。与传统 CNN/RNN 模型不同,LLM 有其独特的压缩需求和技术方案。
LLM 压缩的第一个特殊性是 KV Cache 的优化。自回归生成过程中,每生成一个 token 都需要缓存之前所有 token 的 Key-Value 对。对于 8K 上下文长度的模型,KV Cache 的显存占用可能与模型权重本身相当。KV Cache 量化是 LLM 特有的压缩技术——对缓存中的 K 和 V 矩阵进行低位宽量化,通常 INT8 量化就能减少 50% 的 KV Cache 显存占用。
第二个特殊性是权重的非均匀敏感性。LLM 中不同层、不同权重对量化的敏感度差异巨大。研究表明,LLM 中的异常值(outliers)——数量极少但数值极大的权重——是量化精度的主要瓶颈。AWQ(Activation-aware Weight Quantization)和 SmoothQuant 等技术通过识别和保护这些异常值,实现了 INT4 量化下的极低精度损失。
第三个特殊性是稀疏化的新维度。对于 LLM,除了传统的权重剪枝,还有 MoE(混合专家)稀疏化——在推理时只激活部分专家层,其余层不参与计算。这种结构化稀疏天然适合 LLM 的架构特性。此外,推测解码(Speculative Decoding)也是一种特殊的压缩策略——用一个小模型快速生成候选 token,大模型只负责验证,从而大幅提升生成速度。
第四个方面是后训练量化(PTQ)在 LLM 中的特殊实现。由于 LLM 的训练成本极高,QAT 通常不现实。LLM 压缩主要依赖改进的 PTQ 方法——使用少量校准数据(几百到几千个样本)来统计权重和激活的分布,然后计算最优的量化参数。GPTQ 及其后续变种(如 EXL2、Marlin)是当前最流行的 LLM 量化方案。
10实战:构建完整的模型压缩流水线
在实际工程中,模型压缩不是单个操作,而是一个多阶段的流水线。以下是一个典型的端到端压缩流程。
第一阶段是基线评估——在压缩之前,必须在目标数据集上评估原始模型的精度、延迟和显存占用。这是所有后续压缩步骤的参照点。没有基线,就无法判断压缩是否成功。
第二阶段是剪枝探索——使用迭代幅度剪枝,从 20% 开始,每次增加 10%,在每一步进行 1-2 个 epoch 的微调,记录精度变化。找到精度开始显著下降的临界点——这决定了最大可接受的稀疏度。
第三阶段是量化选型——基于目标硬件选择量化格式。如果目标 GPU 支持 Tensor Core INT8,优先使用 INT8 PTQ;如果需要部署到边缘设备(如 Jetson、手机端),考虑 INT4 加混合精度方案。使用校准数据集统计激活分布,计算量化参数。
第四阶段是蒸馏微调——如果前三步的精度损失超过容忍范围,引入蒸馏环节。使用原始模型作为教师,用压缩后的模型作为学生,在领域特定的数据集上进行蒸馏微调。这一步通常能恢复 0.5-2% 的精度。
第五阶段是验证与部署——在完整测试集上验证压缩后模型的精度,在目标硬件上测量实际延迟和显存占用,确认满足部署指标后导出最终模型。
import torch
from dataclasses import dataclass
@dataclass
class CompressionConfig:
"""压缩流水线配置"""
target_sparsity: float = 0.5 # 目标稀疏度 50%
prune_steps: int = 5 # 剪枝迭代步数
target_bits: int = 8 # 目标量化位宽
distill_temperature: float = 4.0 # 蒸馏温度
distill_alpha: float = 0.5 # 蒸馏权重
tolerance: float = 0.02 # 可接受精度损失 2%
class CompressionPipeline:
"""端到端模型压缩流水线"""
def __init__(self, config: CompressionConfig):
self.config = config
self.baseline_accuracy = None
def evaluate(self, model, test_loader):
"""评估模型精度"""
model.eval()
correct, total = 0, 0
with torch.no_grad():
for inputs, labels in test_loader:
outputs = model(inputs)
correct += (outputs.argmax(1) == labels).sum().item()
total += labels.size(0)
return correct / total
def run(self, model, teacher_model, train_loader, test_loader):
"""执行完整压缩流程"""
# Step 0: 基线评估
print("=== Step 0: 基线评估 ===")
self.baseline_accuracy = self.evaluate(model, test_loader)
print(f"基线精度: {self.baseline_accuracy:.4f}")
# Step 1: 迭代剪枝
print("=== Step 1: 迭代剪枝 ===")
model = self._prune(model, train_loader)
# Step 2: 量化
print("=== Step 2: 量化 ===")
model = self._quantize(model)
# Step 3: 蒸馏微调
print("=== Step 3: 蒸馏微调 ===")
model = self._distill(model, teacher_model, train_loader)
# Step 4: 最终评估
final_accuracy = self.evaluate(model, test_loader)
drop = self.baseline_accuracy - final_accuracy
print(f"最终精度: {final_accuracy:.4f} (下降 {drop:.4f})")
assert drop <= self.config.tolerance, "精度损失超出容忍范围!"
return model
def _prune(self, model, loader): ...
def _quantize(self, model): ...
def _distill(self, model, teacher, loader): ...11部署框架中的压缩集成
模型压缩的最后一公里是在目标推理框架中正确加载和使用压缩后的模型。不同的推理框架对压缩格式有不同的支持程度。
ONNX Runtime 是最通用的跨平台推理框架,支持 INT8 量化模型(通过 QDQ 节点表示量化操作)。导出 ONNX 量化模型时,需要使用专门的量化工具(如 onnxruntime.quantization)将浮点模型转换为带 QDQ 节点的量化模型。ONNX Runtime 的 CPU 后端对 INT8 有良好的支持,GPU 后端则需要 CUDA 11+。
TensorRT 是 NVIDIA GPU 上最快的推理引擎,原生支持 INT8 和 FP16。TensorRT 的独特优势在于它的自动校准器(Calibrator)——只需要提供一个代表性的数据集,它就能自动计算最优的量化参数。TensorRT 还支持层融合(Layer Fusion)和内核自动调优(Auto-Tuning),能进一步加速推理。
vLLM 是当前最流行的 LLM 推理框架,原生支持 GPTQ、AWQ、EXL2 等多种量化格式。对于 LLM 部署,AWQ 格式通常能提供最佳的精度-速度平衡。vLLM 还支持 PagedAttention 技术来优化 KV Cache 的显存管理,与 KV Cache 量化配合使用可以进一步降低显存需求。
对于边缘部署,CoreML(Apple)和 NNAPI(Android)是移动端的主要推理框架。CoreML 支持 FP16 和 INT8 量化,且能自动利用 Apple 芯片的 Neural Engine。将模型转换为 CoreML 格式时,建议使用 CoreML Tools 的量化工具链,而不是手动量化,因为它会自动选择最优的量化策略。
122026 年模型压缩技术趋势展望
模型压缩领域在 2026 年出现了几个值得关注的新趋势。
第一个趋势是 LLM 专用压缩方案的成熟。与通用模型压缩不同,LLM 压缩需要考虑自回归生成的特殊性。除了前面提到的 AWQ、SmoothQuant 和 GPTQ 系列,新的压缩技术如 QuaRot(在旋转位置上做量化,保持旋转不变性)和 QuIP(量化前对权重做随机正交变换,使分布更均匀)代表了量化算法的最新方向。
第二个趋势是自动化压缩(AutoCompression)的兴起。手动调优压缩参数(稀疏度、位宽、温度等)既耗时又容易错过最优解。自动化压缩框架——如 NNCF、PaddleSlim 和 torch_pruning 的高级功能——能自动搜索最优的压缩配置。这些框架通常使用基于验证集精度的搜索策略,在用户指定的约束条件(如目标延迟、目标体积)下找到最优的压缩方案。
第三个趋势是压缩与架构设计的协同优化。与其在训练后压缩模型,不如在架构设计阶段就考虑压缩性。比如使用结构化的可剪枝架构——在训练时就引入稀疏正则化,让模型自然地形成更容易压缩的结构。或者使用量化友好的激活函数——避免使用容易出界的激活函数,选择对量化鲁棒性更好的替代方案。
第四个趋势是多模态模型压缩的兴起。随着视觉-语言模型(VLM)的普及,如何压缩这些包含视觉编码器和语言解码器的复杂模型成为新的研究热点。目前的方案通常是对视觉编码器和语言解码器分别压缩,然后进行联合微调。
第五个趋势是压缩评估标准的完善。过去压缩效果主要用精度(Accuracy)和压缩比(Compression Ratio)两个指标衡量。现在越来越多的工作引入了多维评估体系——包括延迟(Latency)、吞吐量(Throughput)、显存占用(Memory)、能耗(Energy)和精度(Accuracy)五个维度,为不同场景下的压缩方案选择提供更全面的参考。
13常见问题与避坑指南
在模型压缩的工程实践中,有一些反复出现的坑值得提前了解。
量化精度突然崩溃——最常见的情况是量化后精度从 90% 直接掉到 10%。这通常不是量化算法的问题,而是校准数据集选择不当。如果校准集太小或分布不均衡,计算出的 scale 参数就不具有代表性。建议使用至少 100-500 个样本的校准集,覆盖模型会遇到的各种输入类型。
剪枝后微调不收敛——剪枝相当于对模型施加了一个大的扰动,微调时如果使用与训练时相同的学习率,模型可能无法适应。建议使用比原始训练小 5-10 倍的学习率进行微调,并适当增加 warmup 步数。
蒸馏时学生模型精度不如直接训练——这可能是因为教师模型太强而学生模型太弱,蒸馏目标对学生来说过于复杂。建议先尝试缩小教师和学生的差距——选择一个不太大的教师模型,或者选择一个不太小的学生模型。另一种策略是渐进式蒸馏——先用一个中等大小的中间模型蒸馏,再用小模型蒸馏中间模型。
ONNX 导出后量化信息丢失——某些框架在导出 ONNX 时会丢失量化参数(scale 和 zero-point)。解决方法是使用框架专用的导出工具(如 PyTorch 的 torch.onnx.export 配合 dynamic_axes),并在导出后使用 ONNX 量化工具重新量化。
移动端部署时 INT8 模型反而更慢——这是因为某些移动端推理引擎对 INT8 的支持不完善,可能使用了软件模拟而不是硬件加速。建议在部署前在目标设备上做基准测试,确认 INT8 确实能带来加速。如果不行,回退到 FP16 可能是更好的选择。
14扩展阅读与进一步学习
经典论文:
- "Deep Compression" (Han et al., 2015) — 模型压缩的开创性工作
- "Distilling the Knowledge in a Neural Network" (Hinton et al., 2015) — 知识蒸馏的开山之作
- "To Quantize or Not to Quantize" — 量化精度的理论分析
- "AWQ: Activation-aware Weight Quantization" (2023) — LLM 量化的里程碑工作
- "SmoothQuant" (2022) — 解决 LLM 量化中激活值异常的问题
实用工具:
- NNCF(Neural Network Compression Framework)— Intel 的自动压缩框架
- PaddleSlim — 百度飞桨的压缩工具集
- AutoGPTQ — LLM 的 GPTQ 量化实现
- LLM.int8() — bitsandbytes 库的 LLM 量化方案
- torch_pruning — PyTorch 结构化剪枝工具
扩展方向: