PyTorch中custom_from_mask实现细粒度剪枝:直接传入同形布尔掩码,调用后须立即prune.remove()固化权重,否则保存出错;掩码推荐用torch.where生成以防梯度中断,且需与权重同device、同dtype。

PyTorch中用torch.nn.utils.prune.custom_from_mask实现细粒度剪枝
自定义剪枝不等于从头写剪枝逻辑——PyTorch的custom_from_mask是最快落地的方式,它允许你直接传入布尔掩码(mask),跳过所有启发式计算。适合已有压缩目标(比如“只保留每层权重绝对值前30%”或“按通道重要性分数裁剪”)的场景。
常见错误是把掩码形状搞错:mask必须和待剪枝参数tensor完全同形;若对Linear.weight做结构化剪枝(如剪整行/整列),掩码就得按对应维度全置0,不能只设单个元素为0。
- 先调用
prune.custom_from_mask(module, name='weight', mask=your_mask),别漏掉name参数,默认是'weight'但显式写出更安全 - 剪完立刻调用
prune.remove(module, 'weight'),否则module.weight仍是PruningContainer对象,保存模型会出错 - 掩码生成建议用
torch.where而非布尔索引赋值,避免梯度中断:例如mask = torch.where(torch.abs(weight) > threshold, 1.0, 0.0)
TensorFlow/Keras里绕过tfmot.sparsity默认策略,手动注入稀疏掩码
Keras的tfmot.sparsity默认只支持全局或层内统一比率剪枝,没法按通道敏感度、梯度幅值或NAS搜索结果来定制。真要自定义,得放弃prune_low_magnitude,改用tf.keras.layers.Layer子类重写call,在内部用tf.multiply硬乘掩码。
容易踩的坑是掩码没注册为不可训练变量——如果掩码随训练更新(比如做可学习掩码),必须用self.add_weight(..., trainable=False)声明;否则model.save()会报ValueError: Model cannot be saved because the input shapes have not been set。
- 掩码张量需与权重同device、同dtype(推荐
tf.float32),否则tf.multiply可能静默转成全0 - 推理时务必调用
model.set_weights()重载含掩码的权重,不能只靠load_weights()——后者不触发掩码绑定逻辑 - 导出TFLite时,
tf.lite.TFLiteConverter.from_keras_model会自动丢弃掩码,若需保留稀疏结构,得用converter.experimental_enable_resource_variables = True
剪枝后模型体积没变小?检查state_dict里是否残留未压缩的原始权重
剪枝操作本身不删参数,只是把部分值设为0。模型文件体积不变,是因为torch.save()或model.save_weights()仍序列化了全部浮点数,包括大量0值。真正减体积要靠后续步骤。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
PyTorch用户常忽略prune.remove()之后的torch.quantization.convert()或torch.nn.utils.spectral_norm无关——重点是导出为ONNX再用onnx-simplifier,或用torch.jit.trace后调用torch._C._jit_pass_remove_dead_code清理零权重节点。
- 验证是否生效:加载剪枝后模型,运行
sum(p.numel() for p in model.parameters())对比剪枝前后,数值应一致;但sum((p != 0).sum().item() for p in model.parameters())应显著下降 - ONNX导出时加
do_constant_folding=True,能合并被掩码归零的计算分支 - 若用
torch.save({'state_dict': model.state_dict()}, ...),记得先prune.remove(),否则state_dict里还藏着_weight_orig和_weight_mask两个冗余项
为什么剪枝后精度暴跌?关键在rewind和retrain的时机
一次性剪到90%稀疏度再微调,基本必崩。真正有效的流程是:小步剪枝(每次5–10%)→ 短期微调(1–3 epoch)→ 重复。中间必须做rewind:不是重置权重,而是把优化器状态(如Adam的exp_avg)恢复到剪枝前一步,否则梯度方向突变。
另一个隐藏雷区是BatchNorm层的running_mean/running_var——剪枝改变通道分布后,这些统计量就失效了,必须在每次微调前用新数据重新校准,哪怕只跑一个batch。
- PyTorch可用
torch.optim.Optimizer.load_state_dict()回滚优化器状态,前提是之前存了optimizer.state_dict() - 不要在
prune.global_unstructured后直接model.eval()测精度,此时BN统计量还是旧的,得先跑model.train()+torch.no_grad()前向一次校准 - 微调学习率建议设为原学习率的1/5,太大容易跳出当前稀疏结构的最优解
剪枝不是开关式操作,掩码生成、权重固化、统计量校准、导出优化这四步缺一不可。最容易被跳过的其实是BN校准——很多人看到精度跌就去调学习率,其实只是running_var里堆着剪枝前的方差。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










