3、剪枝粒度与策略:细粒度剪枝、向量剪枝、通道剪枝、层剪枝的对比
聊到剪枝,很多人第一反应就是「把不重要的权重砍掉」。但具体怎么砍、砍到什么程度,这里面的门道可深了。我刚开始做模型压缩那会儿,也踩过不少坑——比如一股脑儿用细粒度剪枝,结果模型是变小了,但推理速度一点没快,你说气不气人?
说白了,剪枝的粒度决定了你的收益天花板。咱们今天就把四种主流粒度掰开揉碎了讲清楚。
细粒度剪枝:最精细,但最不实用
细粒度剪枝,也叫非结构化剪枝。它直接对单个权重下手——觉得哪个参数不重要,就把它置零。
举个例子,一个 4×4 的卷积核,剪完后可能变成这样:
原始权重:
[[0.8, 0.1, 0.6, 0.3],
[0.2, 0.9, 0.4, 0.7],
[0.5, 0.3, 0.8, 0.2],
[0.1, 0.6, 0.4, 0.9]]
剪枝后(50%稀疏度):
[[0.8, 0.0, 0.6, 0.0],
[0.0, 0.9, 0.0, 0.7],
[0.5, 0.0, 0.8, 0.0],
[0.0, 0.6, 0.0, 0.9]]
你看,权重变成了稀疏矩阵。理论上压缩率很高,但实际部署时问题就来了——硬件对非结构化稀疏的支持很差。我在RK3588上试过,稀疏度80%的模型,推理速度反而比原始模型慢了30%。为什么?因为CPU和GPU的缓存机制被破坏了,零值和非零值交错存储,根本没法批量跳过。
所以我的建议是:除非你的目标硬件明确支持稀疏计算(比如某些专用NPU),否则别碰细粒度剪枝。它更适合学术研究,不适合工程落地。
向量剪枝:折中的选择
向量剪枝,也叫结构化剪枝的初级版。它不再砍单个权重,而是以「向量」为单位——比如卷积核的一行或一列。
你想想看,一个 3×3 的卷积核,如果整行都不重要,直接干掉这一行,剩下的结构还是规整的矩阵。这对硬件就友好多了。
原始卷积核(3×3):
[[w11, w12, w13],
[w21, w22, w23],
[w31, w32, w33]]
向量剪枝(剪掉第2行):
[[w11, w12, w13],
[w31, w32, w33]]
嗯,这里要注意:向量剪枝后,矩阵的维度变了。比如原本 3×3 变成 2×3,后续的矩阵乘法需要重新适配。我在项目中遇到过这种情况——剪完枝后,下一层的输入通道数对不上,还得手动调整网络结构。
向量剪枝的压缩率介于细粒度和通道剪枝之间。我个人习惯用它来做中间层的微调——比如某个层剪掉20%的向量,精度损失控制在1%以内,推理速度能提升15%左右。性价比还不错。
通道剪枝:工程界的首选
通道剪枝,说白了就是直接砍掉整个通道(也就是整个特征图)。这是目前工业界最常用的方法,没有之一。
为什么?因为通道剪枝后的模型结构是完全规整的——你砍掉一个通道,对应的卷积核也砍掉一层,整个网络还是标准的矩阵运算。硬件跑起来毫无压力。
| 剪枝粒度 | 结构规整性 | 硬件友好度 | 压缩率 | 精度损失 |
|---|---|---|---|---|
| 细粒度 | 差 | 极差 | 高 | 低 |
| 向量剪枝 | 中 | 中 | 中 | 中 |
| 通道剪枝 | 好 | 好 | 中高 | 中 |
| 层剪枝 | 最好 | 最好 | 低 | 高 |
通道剪枝的核心在于如何评估通道的重要性。常用的方法有:
- 基于权重大小:L1/L2范数小的通道直接砍掉。简单粗暴,但效果还行。
- 基于激活值:统计每个通道的输出激活值,零值多的通道说明贡献小。
- 基于BN层参数:利用Batch Normalization的缩放因子γ,γ小的通道可以剪掉。这是目前最主流的方法。
层剪枝:最暴力,但最有效
层剪枝,就是直接删掉整个网络层。比如ResNet有50层,你觉得后面20层没啥用,直接砍掉。
听起来很粗暴对吧?但有时候它确实管用。我记得有一次做语音识别模型的部署,原始模型有12层Transformer,我试着剪掉最后2层,精度只掉了0.3%,但推理速度提升了20%。
层剪枝的难点在于:你很难判断哪些层是冗余的。我常用的方法是:
- 逐层计算「贡献度」——把某一层的输出替换为恒等映射,看精度变化。
- 如果精度下降小于0.5%,说明这层可有可无。
- 从后往前剪,因为深层网络的特征更抽象,冗余度更高。
四种策略怎么选?
说实话,没有银弹。我一般按这个思路来选:
- 目标硬件是GPU/CPU:首选通道剪枝,次选向量剪枝。别碰细粒度。
- 目标硬件是专用NPU:先查文档,看它支持哪种稀疏格式。有些NPU支持2:4结构化稀疏,这时候细粒度剪枝反而香。
- 模型特别深(100层+):先做层剪枝,砍掉明显冗余的层,再做通道剪枝微调。
- 精度要求极高:用细粒度剪枝做微调,但只做最后阶段的压缩,别一开始就用。
最后说一句:剪枝不是一锤子买卖。我习惯的做法是「迭代式剪枝」——每次剪掉10%的参数,然后微调恢复精度,再剪10%,直到精度开始明显下降。这样比一次性剪到位稳定得多。