深度学习模型越做越大,但部署环境却常常受限:手机端只有几GB内存,边缘设备的算力远不如服务器,即使是线上服务,推理延迟也直接关系到用户体验和机器成本。模型压缩与加速技术就是为了解决“模型大、跑不动”这个矛盾而生的。目前在工业界落地最成熟的三条路线是剪枝、知识蒸馏和量化,本文结合实际工程经验,把三者的原理、实现代码和选型思路讲清楚。

一、模型剪枝:把冗余的参数砍掉
剪枝的基本思想很朴素:神经网络中存在大量对输出贡献很小的参数,把它们置零或者直接移除,模型体积和计算量都会下降。按照剪枝的粒度,可以分为非结构化剪枝和结构化剪枝两大类,这也是新手最容易混淆的地方。
非结构化剪枝直接对单个权重进行裁剪,通常先计算每个权重的绝对值,把低于某个阈值的权重置为0。它的压缩率可以做到很高,但有个致命问题:稀疏矩阵在GPU上并不能自动加速,除非硬件和推理框架专门支持稀疏计算,否则非结构化剪枝后的模型实际推理速度几乎没有提升,只是节省了存储(配合稀疏存储格式)。而结构化剪枝是按通道、按卷积核甚至按整层网络为单位进行裁剪,剪完之后模型结构本身变小了,任何推理框架都能直接受益,这才是工程上真正实用的方案。
下面是一个基于PyTorch的简单结构化剪枝示例,按通道L1范数裁剪卷积层:
import torch import torch.nn.utils.prune as prune model = MyCNN() # 对第一个卷积层按输出通道做非结构化剪枝,压缩30%权重 prune.l1_unstructured(model.conv1, name='weight', amount=0.3) # 按L1范数对卷积层做结构化剪枝,移除40%的输出通道 prune.ln_structured(model.conv1, name='weight', amount=0.4, n=2, dim=0) # 剪枝后需要移除mask,把剪枝永久化 prune.remove(model.conv1, 'weight')
剪枝的实际效果有一个普遍规律:压缩率和精度是此消彼长的。经验上,不经过微调直接剪枝,压缩到50%左右精度损失通常可以接受;如果压缩率超过70%,必须配合蒸馏或继续训练来恢复精度。工程上常见的做法是“剪枝-微调”循环:每次剪掉一小部分,微调几个epoch,逐步逼近目标压缩率,这比一次性大比例剪枝稳定得多。
二、知识蒸馏:让小模型学会大模型的“暗知识”
知识蒸馏的核心思想是让一个轻量的学生模型去模仿一个强大的教师模型的输出行为。与传统只使用硬标签训练不同,蒸馏引入了软标签的概念。教师模型在softmax时使用较高的温度系数,输出的是各类别之间的相似度分布,这被称为“暗知识”。比如一张猫的图片,教师模型可能输出“猫0.7、狗0.2、狐狸0.1”,这个分布信息比单纯的“这是猫”丰富得多,学生模型学到的判别边界也更平滑。
蒸馏的损失函数一般由两部分组成:一部分是学生输出与教师软标签的KL散度,另一部分是学生输出与真实硬标签的交叉熵。两者的权重通过一个系数来平衡。来看一个典型的蒸馏训练代码:
import torch
import torch.nn as nn
import torch.nn.functional as F
def distillation_loss(student_logits, teacher_logits, labels,
temperature=4.0, alpha=0.7):
# 软标签损失:学生与教师在高温softmax下的KL散度
soft_loss = F.kl_div(
F.log_softmax(student_logits / temperature, dim=1),
F.softmax(teacher_logits / temperature, dim=1),
reduction='batchmean'
) * (temperature ** 2)
# 硬标签损失:常规交叉熵
hard_loss = F.cross_entropy(student_logits, labels)
return alpha * soft_loss + (1 - alpha) * hard_loss
# 训练时教师模型设为eval模式,只做前向
teacher.eval()
with torch.no_grad():
teacher_logits = teacher(images)
student_logits = student(images)
loss = distillation_loss(student_logits, teacher_logits, labels)
loss.backward()
温度系数T的取值有讲究。T越大,软标签分布越平滑,暗知识越明显,但过大会导致分布过于均匀、失去判别信息,一般取3到10之间尝试。蒸馏的收益与学生模型的容量相关:学生模型越小,从教师那里获得的提升越明显;当学生模型本身容量足够时,蒸馏的边际收益会迅速下降。除了输出层蒸馏,还有基于中间层特征的特征蒸馏方法,适合层数较深的学生网络,可以让中间表征对齐,训练更稳定。
三、模型量化:用更少的比特表示权重
量化是把模型中的浮点数(通常是FP32)转换为低精度表示(INT8甚至INT4)的技术。FP32每个参数占4字节,量化到INT8后只占1字节,模型体积直接缩小到四分之一,同时整数运算在CPU和NPU上比浮点运算快得多,内存带宽压力也大幅降低。量化分为训练后量化(PTQ)和量化感知训练(QAT)两种路线。
训练后量化不需要重新训练,只需要几百个校准样本统计各层的数值分布,确定缩放因子后直接转换,成本极低,适合快速上线。但缺点是对某些结构敏感的模型(比如有大量深度可分离卷积的MobileNet)精度损失可能偏大。量化感知训练则是在训练过程中插入伪量化节点,让模型在前向传播时就模拟量化带来的精度损失,反向传播时正常更新权重,模型会自动学习到对量化更鲁棒的参数分布。QAT精度恢复效果好,但需要完整的训练数据和训练流程。
下面是两种量化在PyTorch中的实现:
import torch
import torch.quantization as quant
# 方式一:训练后动态量化,适合RNN、Transformer类模型
quantized_model = quant.quantize_dynamic(
model, {torch.nn.Linear}, dtype=torch.qint8
)
# 方式二:量化感知训练
model_fp32 = MyModel()
model_fp32.qconfig = quant.get_default_qat_qconfig('fbgemm')
quant.prepare_qat(model_fp32, inplace=True)
# 在模拟量化状态下继续训练若干epoch
for epoch in range(3):
train_one_epoch(model_fp32, ...)
# 训练完成后转换为真正的量化模型
quantized_model = quant.convert(model_fp32.eval(), inplace=False)
量化时有一个常见坑需要提醒:权重量化相对安全,但激活值的动态范围波动大,容易溢出或截断。解决办法包括使用滑动平均的校准方法(如MinMax、Percentile)、对个别敏感层保持FP16不量化(混合精度部署),或者采用更先进的LLM量化方案如GPTQ、AWQ,它们会综合考虑各层误差传播,对大语言模型的INT4量化效果显著优于朴素方法。
四、工程选型建议与组合拳
三种技术并不是互斥的,实际项目中往往组合使用。一个经过验证的有效流程是:先用知识蒸馏把大模型的能力迁移到一个结构紧凑的学生模型上,再对学生模型做结构化剪枝去掉冗余通道,最后做INT8量化并部署到TensorRT、ONNX Runtime或NCNN等推理引擎上。这个“蒸馏-剪枝-量化”的流水线在图像分类任务上通常能做到精度损失1到2个百分点,同时推理速度提升3到5倍。
单独来看三者的适用场景:如果训练数据齐全、算力充足,优先考虑蒸馏,因为可以自由设计学生结构;如果只能拿到现成模型、没有训练数据,量化是最现实的选择,PTQ几乎零成本;如果模型中存在明显的冗余通道(可以通过BN层的gamma系数观察,接近零的通道说明冗余),剪枝能带来实打实的结构瘦身。另外别忘了,压缩之前先做算子融合和图优化,把一些能合并的计算在框架层面处理掉,往往不用动模型就有可观的加速。
最后强调一点:任何压缩手段都必须以完善的评估为准。建议准备一个专门的精度基准测试集,每做一步压缩就跑一次完整评估,记录精度、延迟、模型体积三项指标的变化,一旦精度跌破业务可接受线就停下来分析原因,而不是盲目堆砌压缩手段。压缩的最终目的是在业务约束下找到速度、体积和精度的最佳平衡点,而不是片面追求某个极限指标。