4、模型剪枝与稀疏化:结构化剪枝与非结构化剪枝、权重剪枝与神经元剪枝、稀疏矩阵的存储与计算、NVIDIA的稀疏化技术(2:4稀疏)
模型剪枝,说白了就是给神经网络“瘦身”。
你想想看,一个训练好的大模型,里面其实有很多参数是冗余的。就像一张高清照片,去掉一些不重要的像素点,人眼根本看不出来。剪枝的目的就在于此——去掉那些不重要的连接或神经元,让模型更轻、更快,同时尽量不掉精度。
我在实际部署中遇到过不少情况,模型精度明明很高,但一上嵌入式设备就卡成PPT。后来发现,剪枝一下,推理速度能翻倍。嗯,这活儿值得好好聊聊。
4.1 结构化剪枝 vs 非结构化剪枝
这是剪枝领域最基础的一个分法。我个人习惯这么理解:
- 非结构化剪枝:把单个权重置为零。粒度很细,想剪哪个权重就剪哪个。但问题是,剪完后的权重矩阵变得稀疏且不规则,普通硬件很难加速。
- 结构化剪枝:按“块”来剪。比如剪掉整个卷积核、整个通道,甚至整个层。剪完后模型结构还是规整的,硬件友好,加速效果立竿见影。
核心区别一句话:非结构化剪枝是“点”级别的修剪,结构化剪枝是“面”级别的修剪。
我在项目中遇到过这样一个坑:非结构化剪枝后,模型大小确实变小了,但推理速度几乎没变。为什么?因为底层库(比如cuDNN)对稀疏矩阵的支持并不好,计算时还是按稠密矩阵来算的。白忙活一场。
所以我的建议是:如果你追求实际加速,优先考虑结构化剪枝。非结构化剪枝更适合学术研究,或者配合专门的稀疏计算硬件(比如NVIDIA的Ampere架构)。
4.2 权重剪枝 vs 神经元剪枝
这两个概念容易混淆,我简单拆解一下。
- 权重剪枝:剪掉的是单个权重参数。比如一个全连接层有1000个权重,我们剪掉其中300个(置为零)。这是最细粒度的剪枝。
- 神经元剪枝:剪掉的是整个神经元(或整个通道)。比如一个卷积层有64个输出通道,我们直接砍掉16个。这属于结构化剪枝的一种。
你可能会问:那到底该用哪种?
我的经验是:权重剪枝适合压缩模型体积,因为零权重多了,可以用稀疏格式存储。而神经元剪枝适合加速推理,因为通道数少了,计算量直接下降。
小技巧:在实际工程中,我经常把两者结合使用。先用神经元剪枝把通道数砍到合理范围,再用权重剪枝做微调。这样既保证了加速效果,又不会损失太多精度。
4.3 稀疏矩阵的存储与计算
剪枝之后,模型里会出现大量零值。如果还用稠密矩阵存,那简直是浪费。所以我们需要稀疏矩阵的存储格式。
常见的稀疏格式有几种:
| 格式 | 说明 | 适用场景 |
|---|---|---|
| CSR(Compressed Sparse Row) | 按行压缩,存储非零值、列索引、行偏移 | 行稀疏、列稠密的矩阵 |
| CSC(Compressed Sparse Column) | 按列压缩,类似CSR的转置 | 列稀疏、行稠密的矩阵 |
| COO(Coordinate) | 直接存储非零值的坐标和数值 | 简单直观,适合构建稀疏矩阵 |
| BSR(Block Sparse Row) | 按块压缩,适合结构化稀疏 | NVIDIA的2:4稀疏就用这种思路 |
我记得有一次,我用CSR格式存了一个剪枝后的模型,模型文件从50MB降到了12MB。但推理时发现,CPU上计算稀疏矩阵乘法反而比稠密还慢。后来查资料才知道,稀疏计算需要专门的硬件或库支持,否则得不偿失。
避坑指南:我曾经在ARM CPU上尝试用稀疏矩阵加速,结果性能反而下降了20%。原因是ARM的NEON指令集对稀疏计算支持有限。所以,用稀疏格式前,先确认你的硬件平台是否支持。
4.4 NVIDIA的稀疏化技术(2:4稀疏)
说到稀疏化,不得不提NVIDIA的2:4稀疏。这是Ampere架构引入的一项硬核技术。
什么叫2:4稀疏?简单说就是:每4个权重中,强制让2个为零。而且这2个零的位置是固定的——必须是每4个一组中的前2个或后2个。
为什么这么设计?因为NVIDIA的硬件专门为这种模式做了优化。在Tensor Core中,如果遇到2:4稀疏的矩阵,计算速度可以翻倍,同时精度几乎不掉。
我在部署一个BERT模型时试过这个技术。原模型有1.1亿参数,剪枝后只剩一半的非零值。用NVIDIA的A100跑推理,速度提升了1.8倍,而精度只掉了0.3%。效果相当惊艳。
使用2:4稀疏的步骤大致如下:
# 伪代码示例
import torch
from torch.sparse import to_sparse_semi_structured
# 假设我们有一个训练好的权重矩阵 W
# 首先进行剪枝,保留每4个中最大的2个
mask = torch.zeros_like(W)
for i in range(0, W.shape[1], 4):
_, idx = torch.topk(W[:, i:i+4], 2, dim=1)
mask[:, i + idx] = 1
W_pruned = W * mask
# 转换为半结构化稀疏格式
W_sparse = to_sparse_semi_structured(W_pruned)
# 推理时,Tensor Core会自动加速
output = torch.nn.functional.linear(input, W_sparse)
个人经验:2:4稀疏对精度的影响通常很小,但也不是万能的。如果你的模型本身就很紧凑(比如MobileNet),剪枝后精度可能会掉得比较明显。建议先在小数据集上验证一下。
另外,NVIDIA的2:4稀疏目前只支持FP16和BF16精度。如果你用的是INT8量化,那就没法享受这个加速了。嗯,这里要注意一下。
小结
模型剪枝和稀疏化,说到底就是“用更少的参数,做同样的事”。
- 结构化剪枝适合实际部署,非结构化剪枝适合学术探索。
- 权重剪枝压缩体积,神经元剪枝加速推理。
- 稀疏矩阵的存储格式要选对,否则可能适得其反。
- NVIDIA的2:4稀疏是当前最成熟的硬件加速方案,值得优先考虑。
最后说一句:剪枝不是万能的。它和量化、蒸馏等技术配合使用,效果才最好。我一般会在剪枝后做一次微调,让模型重新适应稀疏结构。这一步很关键,别偷懒。