模型剪枝与蒸馏:让大模型在边缘端跑起来

各位同学,今天我们来聊聊模型压缩里最核心的两个技术——剪枝和蒸馏。说实话,这两个东西我刚开始接触的时候也觉得挺玄乎的,但干久了你会发现,它们就是让大模型在边缘设备上跑起来的「两把刷子」。

我先说说我的感受。很多同学觉得模型压缩就是「把模型变小」,其实没那么简单。你想想看,一个100MB的模型,你硬生生砍到10MB,精度掉得一塌糊涂,那还不如不用。所以,剪枝和蒸馏的核心目标只有一个:在尽量不损失精度的情况下,把模型做小、做快

结构化剪枝与非结构化剪枝

剪枝,说白了就是「砍掉不重要的连接」。但怎么砍?这里分两派。

非结构化剪枝:细粒度,但硬件不友好

非结构化剪枝,就是逐个神经元或者权重去判断要不要砍掉。比如一个卷积核里有3x3=9个权重,我可能只保留其中4个,其余5个置为0。

我在项目中遇到过这种情况:用非结构化剪枝把模型压缩了5倍,精度只掉了0.5%,心里美滋滋。结果一部署到NPU上,发现推理速度根本没变快!为什么?因为硬件没法跳过那些零值,它还是老老实实做全矩阵乘法。

核心要点:非结构化剪枝适合在GPU上做稀疏计算,但在边缘端的CPU、NPU上,收益往往不如预期。

# 非结构化剪枝示例(PyTorch风格)
import torch.nn.utils.prune as prune

# 对卷积层做非结构化剪枝,砍掉30%的权重
prune.l1_unstructured(conv_layer, name='weight', amount=0.3)
# 剪枝后权重变成稀疏矩阵,很多值为0
print(conv_layer.weight)  # 你会看到大量0值

结构化剪枝:粗粒度,硬件友好

结构化剪枝就不一样了。它直接砍掉整个通道、整个卷积核,或者整个层。比如一个卷积层有64个输出通道,我直接砍掉16个,剩下48个。这样一来,模型结构变了,但计算图还是规整的,硬件跑起来很舒服。

我个人习惯在边缘端优先用结构化剪枝。虽然精度损失可能比非结构化大一点,但部署后的加速效果是实打实的。我曾经在一个语音唤醒模型上做结构化剪枝,砍掉40%的通道,推理速度提升了1.8倍,精度只掉了1%。

我的建议:如果目标设备是GPU,可以试试非结构化剪枝+稀疏加速库。如果是CPU/NPU,老老实实用结构化剪枝。

# 结构化剪枝示例:按通道重要性排序
def structured_prune(model, prune_ratio=0.3):
    # 计算每个通道的L1范数作为重要性指标
    importance = torch.norm(conv_layer.weight, p=1, dim=(1,2,3))
    # 砍掉重要性最低的30%通道
    threshold = torch.quantile(importance, prune_ratio)
    mask = importance > threshold
    # 保留重要通道
    conv_layer.weight.data = conv_layer.weight.data[mask]
    conv_layer.bias.data = conv_layer.bias.data[mask]

知识蒸馏的原理

知识蒸馏,这个概念我第一次听的时候觉得特别形象——就像酿酒一样,把大模型(老师)的「精华」蒸馏到小模型(学生)里。

为什么需要蒸馏?你想想看,一个100层的ResNet学到的特征表示,肯定比一个10层的MobileNet丰富得多。但你不能直接把ResNet的权重复制给MobileNet,因为结构都不一样。蒸馏就是让MobileNet去模仿ResNet的「行为」。

软标签与温度系数

蒸馏的核心是「软标签」。普通训练用的是硬标签(比如猫=1,狗=0),但老师模型输出的概率分布里包含更多信息——比如它觉得这张图有70%像猫,20%像狗,10%像兔子。这种「模糊」的信息,恰恰是学生模型需要学习的。

温度系数T就是用来控制这个「模糊程度」的。T越大,概率分布越平滑,信息越丰富。T=1就是原始输出,T=10就几乎变成均匀分布了。

避坑指南:我曾经把温度设到20,结果学生模型学了一堆噪声,精度反而下降了。一般来说,T在3-8之间效果比较好,具体得调。

# 知识蒸馏的损失函数
def distillation_loss(student_logits, teacher_logits, labels, T=4, alpha=0.7):
    # 软标签损失:学生和老师的KL散度
    soft_loss = KL_div(
        F.softmax(student_logits / T, dim=1),
        F.softmax(teacher_logits / T, dim=1)
    ) * (T * T)  # 温度补偿
    
    # 硬标签损失:常规交叉熵
    hard_loss = CrossEntropy(student_logits, labels)
    
    # 加权融合
    return alpha * soft_loss + (1 - alpha) * hard_loss

Teacher-Student模型训练

Teacher-Student训练,说白了就是「师徒制」。老师模型先训练好,然后学生模型跟着学。但这里有个关键点:老师不能太强,也不能太弱

我记得有一次,我用一个ResNet-152当老师,教一个MobileNet-V2当学生。结果学生怎么学都学不好,因为老师太强了,学生根本理解不了那些高级特征。后来换了个ResNet-50当老师,效果反而好了很多。

训练流程

  1. 预训练老师模型:用完整数据集训练一个大模型,精度越高越好。
  2. 冻结老师模型:老师模型不再更新,只用来生成软标签。
  3. 训练学生模型:同时使用软标签和硬标签,让学生模型学习。
  4. 微调:去掉老师模型,只用硬标签对学生模型做少量微调。

注意:学生模型的结构不一定要和老师一样。实际上,我经常用不同架构——比如老师是CNN,学生是Transformer,只要输出维度一致就行。

剪枝与蒸馏的联合优化策略

剪枝和蒸馏单独用效果都不错,但联合起来用,往往能产生「1+1>2」的效果。我常用的策略有三种:

策略一:先剪枝,后蒸馏

先把大模型剪枝到目标大小,然后用原始大模型当老师,蒸馏剪枝后的模型。这样做的好处是:剪枝后的模型结构已经定了,蒸馏可以弥补剪枝带来的精度损失。

策略二:边剪枝,边蒸馏

在剪枝过程中,每一步剪枝后都做一次蒸馏。这样模型可以逐步适应新的结构。我个人的经验是,这种方法训练时间会长一些,但最终精度往往更高。

策略三:蒸馏辅助剪枝

用老师模型的输出来指导剪枝决策。比如,不是简单地按权重大小剪枝,而是看哪个通道对老师模型的输出影响最小,就剪哪个。这种方法我在一个目标检测模型上试过,比传统剪枝方法精度高了2%。

策略 适用场景 精度表现 训练时间
先剪后蒸 快速部署,时间紧迫 良好
边剪边蒸 追求极致精度 优秀
蒸馏辅助剪枝 模型结构复杂 优秀 中等

我的经验:在实际项目中,我通常先用策略一快速出一个基线版本,如果精度不达标,再切换到策略二或三。别一上来就搞最复杂的,先跑通再说。

知识体系总览

下面这张图是我自己整理的剪枝与蒸馏的知识体系,你可以对照着看,心里有个谱。

模型剪枝与蒸馏知识体系 模型剪枝 非结构化剪枝 结构化剪枝 细粒度,稀疏矩阵 粗粒度,通道/核 知识蒸馏 Teacher-Student 软标签+温度 老师教学生 T=3~8效果佳 联合优化策略 先剪后蒸 边剪边蒸 蒸馏辅助剪枝 快速部署,基线版本 精度最优,训练时间长 复杂结构,精度提升 核心目标:在尽量不损失精度的情况下,把模型做小、做快

好了,这一章的内容就到这里。剪枝和蒸馏是边缘AI部署的必修课,你可以在实际项目中多试试不同的组合,找到最适合自己场景的方案。

专注资料整理