模型剪枝,说白了就是给神经网络“瘦身”。
你想想看,一个训练好的大模型,里面其实有很多参数是冗余的。就像一棵树,有些枝丫长得太密,剪掉反而能让主干长得更好。我在做高通SNPE部署的时候,就遇到过模型太大、跑不动的情况。剪枝,就是解决这个问题的利器。
14.1 为什么要做剪枝?
模型太大,带来的问题很直接:
- 推理慢:计算量太大,NPU算不过来
- 功耗高:算得多,电就耗得快
- 内存占:参数多,DDR带宽吃紧
剪枝的目标很明确:在尽量不损失精度的前提下,把模型做小、做快。
核心思路:去掉那些“不重要”的权重,保留“重要”的权重。
14.2 结构化剪枝 vs 非结构化剪枝
这两种剪枝方式,我习惯把它们比作“拆墙”和“挖洞”。
14.2.1 非结构化剪枝
非结构化剪枝,就是把单个权重置为零。哪个权重不重要,我就把它干掉。
举个例子,一个卷积核是3x3的矩阵:
原始权重: [[0.9, 0.1, 0.8], [0.2, 0.7, 0.3], [0.6, 0.4, 0.5]] 剪枝后(阈值0.5): [[0.9, 0.0, 0.8], [0.0, 0.7, 0.0], [0.6, 0.0, 0.5]]你看,小于0.5的权重都被置零了。这种剪枝方式精度损失小,但有个大问题——稀疏矩阵不规整。
我曾经踩过的坑:非结构化剪枝后的模型,在普通硬件上根本加速不了。因为权重分布是随机的,没法用SIMD指令高效计算。说白了,你剪了白剪。
14.2.2 结构化剪枝
结构化剪枝就聪明多了。它以“通道”或“卷积核”为单位进行剪枝。
比如一个卷积层有64个输出通道,我剪掉其中16个不重要的通道。剩下的48个通道,结构还是规整的。
原始卷积核:64个输出通道 × 3×3 剪枝后:48个输出通道 × 3×3这样做的好处很明显:
- 硬件友好:矩阵运算还是规整的,NPU可以直接加速
- 显存节省:通道数减少,特征图也变小了
- 推理加速:计算量直接减少25%
我的建议:在NPU上部署,优先考虑结构化剪枝。虽然精度损失比非结构化大一点,但加速效果是实打实的。
14.3 NPU对稀疏性的支持
高通NPU对稀疏性,其实是有一定支持的。但别高兴太早,它不是万能的。
14.3.1 高通NPU的稀疏加速原理
高通NPU支持权重稀疏,但不支持激活值稀疏。什么意思呢?
- 权重稀疏:模型参数中很多是0,NPU可以跳过这些0的计算
- 激活值稀疏:中间特征图中有很多0,NPU目前不支持跳过
我记得在骁龙865平台上测试过,当权重稀疏度达到50%时,推理速度能提升约1.3倍。但稀疏度低于30%时,基本没什么加速效果。
14.3.2 稀疏格式与存储
高通NPU内部使用CSR(Compressed Sparse Row)格式来存储稀疏权重。这种格式只存储非零元素的值和位置,能有效减少内存占用。
| 稀疏格式 | 存储方式 | NPU支持 |
|---|---|---|
| CSR | 行压缩,存非零值和列索引 | ✅ 支持 |
| CSC | 列压缩,存非零值和行索引 | ❌ 不支持 |
| COO | 坐标格式,存非零值和行列坐标 | ❌ 不支持 |
注意:高通SNPE工具链在量化时,会自动将权重转换为CSR格式。你不需要手动处理。
14.4 剪枝与量化的配合
剪枝和量化,是两兄弟。我一般建议先剪枝,后量化。
为什么?
- 剪枝后的模型参数分布更集中,量化误差更小
- 量化后的模型再做剪枝,效果会变差(因为量化引入了误差)
具体流程是这样的:
- 训练原始模型:得到一个高精度的浮点模型
- 结构化剪枝:剪掉不重要的通道
- 微调:让模型恢复精度
- 量化:转换为INT8
- 部署到NPU:享受加速效果
一个小技巧:剪枝率不要超过50%。我试过剪掉70%的通道,精度直接掉了5个点,微调都救不回来。
14.5 实战:在高通SNPE中实现剪枝
高通SNPE本身不提供剪枝工具。你需要用PyTorch或TensorFlow先剪好,再导出为SNPE支持的格式。
这里给一个PyTorch结构化剪枝的示例:
import torch import torch.nn.utils.prune as prune # 定义一个卷积层 conv = torch.nn.Conv2d(3, 64, kernel_size=3) # 结构化剪枝:按通道剪掉30% prune.ln_structured(conv, name='weight', amount=0.3, n=2, dim=0) # 移除剪枝掩码,得到永久剪枝后的权重 prune.remove(conv, 'weight') # 导出为ONNX dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export(conv, dummy_input, "pruned_model.onnx")然后,用SNPE的snpe-onnx-to-dlc工具转换为DLC格式,再量化部署。
注意:剪枝后的模型,通道数必须是8的倍数。高通NPU的硬件设计,对通道数有对齐要求。我当初没注意这个,结果模型跑起来报错,查了半天才发现是通道数不对齐。
14.6 稀疏性的未来
说实话,高通NPU对稀疏性的支持,目前还比较初级。但趋势是好的。
我记得在骁龙8 Gen 2的文档里,已经提到了对2:4结构化稀疏的支持。这种稀疏模式,每4个权重中只有2个非零,硬件可以高效加速。
如果你现在做项目,我的建议是:
- 短期:用结构化剪枝,配合INT8量化
- 长期:关注高通新平台的稀疏加速特性
一句话总结:剪枝是模型瘦身的好方法,但要在NPU上真正加速,必须用结构化剪枝。非结构化剪枝,除非NPU硬件支持,否则别碰。
好了,关于模型剪枝和稀疏化,就聊到这里。记住,剪枝不是万能的,但不剪枝是万万不能的。在NPU上部署,一定要把剪枝和量化结合起来用,才能达到最好的效果。