目录

端侧模型剪枝实战:剪掉不重要的参数,让模型更小更快

压缩三件套,还差最后一块拼图

前两周我们聊了端侧模型的量化(INT8/INT4 把权重从 4 字节压到 0.5 字节)和知识蒸馏(小模型学大模型的输出)。今天补上三件套的最后一块:剪枝(Pruning),把权重矩阵里不重要的参数直接置零。

类比:一棵树长满枝杈,剪掉枯枝弱枝,主干不受影响。剪枝就是给神经网络修枝,把绝对值小的权重置 0,然后用稀疏表示存储(只存非零值加一张 0/1 掩码),模型体积变小,部分硬件上推理还更快。

三种剪枝粒度,先搞清楚再动手

粒度 做法 优点 注意
非结构化 单个权重按绝对值排序置 0 稀疏率任意,最灵活 通用框架不一定加速,依赖稀疏表示支持
n:m 半结构化 每 m 个权重剪掉 n 个(如 2:4、3:4) 硬件友好,GPU 有专用内核 稀疏率被格式锁死
结构化 整条通道/滤波器删除 直接减 FLOPs,任何硬件都受益 精度损失通常更大

注意:非结构化剪枝在通用框架里矩阵还是稠密计算、不会加速,但在 Core ML 上有效——iOS16/macOS13 起的 mlprogram 支持稀疏权重表示(位掩码加非零值),iOS18/macOS15 起还能和调色盘、量化叠加。

工具怎么选(截至 2026-08-31 验证)

工具 版本 状态 适用场景
torch.nn.utils.prune(PyTorch 内置) torch 2.13 活跃 学习原理、快速实验
torchao(pytorch/ao) 0.18.0 活跃 2:4 半结构化稀疏、LLM 剪枝(Wanda)
coremltools.optimize.torch 9.0 活跃 iOS 端,训练时剪枝加导出 Core ML
NNCF(OpenVINO 工具链) 3.3.0 活跃 走 OpenVINO/ONNX 的端侧或服务端部署
Microsoft NNI / Neural Magic SparseML - 已归档 别用了

SparseML 2025 年 6 月归档、NNI 2024 年归档,新项目不要再引入。

三行代码起步:PyTorch 内置剪枝

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import torch.nn.utils.prune as prune

# 把 conv1 权重里 30% 绝对值最小的参数置零
prune.l1_unstructured(model.conv1, name="weight", amount=0.3)

# 查看掩码(0/1 矩阵)
print(model.conv1.weight_mask)

# 固化掩码,真正把参数从模型里移除
prune.remove(model.conv1, "weight")

注意:l1_unstructured 之后模块里同时存在原始权重和掩码,剪完要 remove 才会真正瘦身。

进阶一:torchao 做 2:4 半结构化稀疏

1
2
3
4
5
6
7
8
9
from torchao.sparsity import apply_fake_sparsity, semi_sparse_weight, sparsify_

# 第一步:假剪枝,先模拟 2:4 稀疏,评估精度损失
apply_fake_sparsity(model)
# ... 在验证集上评估,损失可接受再继续 ...

# 第二步:真正把 Linear 权重转成 2:4 稀疏格式
sparsify_(model, semi_sparse_weight(),
          filter_fn=lambda m, fqn: isinstance(m, torch.nn.Linear))

2:4 稀疏(每 4 个权重只留 2 个非零)是 NVIDIA GPU 的硬件友好格式,torchao 官方实测在 SAM/ViT 上约 1.1x 推理加速;LLM 剪枝可以试自带的 WandaSparsifier。

进阶二:coremltools 训练时剪枝(iOS 首选)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from coremltools.optimize.torch.pruning import (
    MagnitudePruner, MagnitudePrunerConfig, ModuleMagnitudePrunerConfig,
)

config = MagnitudePrunerConfig(
    global_config=ModuleMagnitudePrunerConfig(
        target_sparsity=0.5,        # 剪掉 50%
        granularity="per_channel",  # 结构化:整条输出通道剪掉
    ),
)
pruner = MagnitudePruner(model, config)
pruner.prepare(inplace=True)

for epoch in range(num_epochs):
    # ...正常训练...
    pruner.step()                   # 每轮更新稀疏掩码

print(pruner.report())              # 查看各层实际稀疏度
pruner.finalize(inplace=True)       # 固化掩码,之后正常转 Core ML

MagnitudePruner 基于经典论文 To prune, or not to prune(arXiv:1710.01878),支持非结构化、块稀疏、n:m、per_channel/per_kernel 四种模式。掩码参与梯度回传,剪枝过程中模型会自适应,适合高稀疏度场景。

官方实测数据(截至 2026-08 验证)

Apple 在 ImageNet 上的官方基准(coremltools 文档):

  • ResNet50 非结构化 50% 稀疏:推理 1.77x 加速,精度 73.64(基线 76.14)
  • ResNet50 非结构化 75% 稀疏:推理 3.17x 加速,精度 73.40
  • MobileNetv2 非结构化 50%:1.37x 加速,精度几乎不掉(71.83 vs 71.86)

稀疏度不是越高越好,75% 以上精度开始明显下降,加速效果也依赖目标硬件对稀疏的支持。

推荐工作流

  1. 先用 data-free 剪枝快速试不同稀疏模式(coremltools 的 prune_weights,或 torchao 的 apply_fake_sparsity),看体积和延迟收益
  2. 精度可接受就直接上;否则用训练时剪枝(MagnitudePruner / NNCF)渐进提高稀疏度并微调
  3. LLM 大模型可试 Apple 的 LayerwiseCompressor(SparseGPT 实现,约 128 个样本即可一次性剪枝)
  4. 剪枝之后照常做量化,iOS18+ 上稀疏与量化可以叠加
  5. 导出后必须在真机实测耗时,别只看模型体积

三件套凑齐了:量化管体积、蒸馏管精度、剪枝管稀疏加速。端侧模型压缩,从这套组合拳开始。