剪枝可以制造零权重,聚类可以减少权重取值,蒸馏可以把大模型知识迁移到小模型,算子替换可以改善部署兼容性。它们对训练模型有明确作用,但MCU是否获益取决于存储格式、稀疏内核和运行时支持。
一个压缩文件更小,解压后进入固件的权重可能恢复原尺寸;非结构化稀疏度很高,普通密集内核仍会执行全部乘加。评价必须落到目标部署链。
|-------------------------------------------------|
| 压缩率是中间指标。MCU项目最终只认可Flash、RAM、推理时间、能量和模型效果的实际变化。 |
|-----------------------------------------------------------------------------------------------------------------------------------------------|
| 完成本篇后,你应该能够 1. 判断剪枝、聚类、蒸馏和算子替换分别优化什么。 2. 识别压缩文件变小与MCU Flash、RAM、延迟变小的差异。 3. 用统一账单比较优化前后的真实收益。 压缩方法只有在目标运行时和硬件能够利用其结构时才产生部署收益。先明确瓶颈,再选择方法。 |
一、先把核心关系连起来
非结构化剪枝需要稀疏执行支持
把单个权重置零能够提高稀疏度,但密集卷积内核仍会读取零并执行乘加。只有模型格式、运行时和内核共同支持稀疏表示时,计算收益才可能出现。
结构化剪枝直接删除通道、卷积核或层,更容易减少张量形状和MACC,但需要重新训练并检查算子形状是否仍适合优化内核。

图1:不同优化方法需要不同的部署支持才能转化为MCU收益
聚类常先改善压缩文件
权重聚类让大量参数共享少数中心值,有利于通用压缩算法缩小文件。若部署时仍展开为普通int8数组,固件中的权重大小和密集计算可能不变。
外部存储或通信受限时,文件压缩本身有价值;实时执行是否受益需要分开评价。
二、蒸馏直接训练更小的学生模型
蒸馏使用教师模型的软输出或中间表示指导学生模型。学生模型结构本身更小,因此不依赖稀疏内核也能减少参数和计算。
教师模型不能替代真实标签和独立测试。学生可能继承教师在未知工况上的偏差,最终仍需使用业务指标验收。
表1:压缩方法的验收对象
| 方法 | 先看中间指标 | 最终必须实测 |
|---|---|---|
| 剪枝 | 稀疏度、通道数 | 固件、MACC与时间 |
| 聚类 | 中心数、压缩后文件 | 解压后权重与启动成本 |
| 蒸馏 | 学生模型指标 | 资源、独立测试与现场误差 |
| 算子替换 | 兼容性与静态分析 | Arena和优化内核时间 |
三、算子替换常带来最直接的部署收益
把不支持的层改成标准卷积、深度可分离卷积或全局平均池化,可以消除转换失败并使用CMSIS-NN等优化内核。
替换后应重新训练并检查激活内存。有些结构参数更少,却产生更大的中间张量,优化不能只看模型文件。
Python:用同一字段比较优化前后,不只记录文件大小
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| FIELDS = "test_f1", "model_bytes", "firmware_flash", "arena_peak", "macc", "p95_ms", "energy_uj" def compare(before, after): return { key: afterkey - beforekey for key in FIELDS } # before和after必须来自相同数据、构建配置和目标板。 |
剪枝和聚类常先改善存储编码,不自动减少计算
非结构化剪枝把单个权重变为0。若TFLM内核仍执行稠密卷积,这些0仍参与乘加,延迟几乎不变;稀疏格式还需索引。只有运行时支持相应稀疏结构时才可能兑现。
权重聚类让许多权重共享少量中心值,压缩文件可用索引加码本表示。转换成普通INT8 FlatBuffer后若重新展开,最终模型和内核仍按稠密权重工作。要检查部署产物,而非训练框架压缩包。
结构化剪枝删除整通道或整卷积核,更容易降低后续层输入通道、MACC和激活,但需要模型重构与微调,并检查维度对优化内核是否友好。
优化方法与可能兑现的资源
| 方法 | 直接目标 | MCU兑现条件 |
|---|---|---|
| 非结构化剪枝 | 零权重 | 稀疏格式与内核支持 |
| 聚类 | 权重熵和压缩 | 部署格式保留编码 |
| 蒸馏 | 训练更小学生 | 学生结构本身可部署 |
| 算子替换 | 降低不友好算子 | 内核支持且效果可接受 |
蒸馏与算子替换更容易形成可见结构收益
蒸馏用教师模型的软输出帮助小学生训练。部署时只保留学生,因此参数、激活和算子由学生结构决定。教师可以很大,但训练数据与温度参数仍需验证。
算子替换直接面向部署:用全局平均池化代替Flatten+大Dense,用stride卷积提前缩短时间轴,用可支持激活代替自定义函数。这些变化能从shape和算子列表中看到。
每次优化只改一个主要变量,并记录FP32效果、INT8效果、文件、固件Flash、Arena、预处理与Invoke时间。某方法若只让zip文件变小,应如实标为传输收益。
|------------------------------------------------------------------|
| 示例:长度50×通道32的Flatten接64个Dense需要102,464参数; 全局平均池化后32→64只需2,112参数。 |
四、落地检查
- 为每种优化保留未优化模型作为同条件基线。
- 确认模型格式与运行时是否真正支持稀疏或聚类执行。
- 重新检查算子兼容、激活内存和量化路径。
- 在相同构建配置下测量Flash、Arena、时间和能量。
- 独立测试集中复核关键类别与未知输入表现。
动手:判断四种优化是否击中真实瓶颈
脚本读取多个假设实验记录,按瓶颈类型计算相对收益并拒绝没有目标板证据的"优化"。数值用于学习评价口径。
实验环境与输入
- Python 3标准库。
- 保存为 `optimization_audit.py` 并运行。
- 实际使用时从构建报告和测量结果填表。
按顺序完成实验
- 运行并比较每种方法。
- 观察clustered只改善压缩包而不改善部署指标。
- 把项目瓶颈从latency改为flash。
- 为记录加入效果下降门槛。
可直接运行:优化收益审计
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| baseline = {"accuracy":0.94, "flash":210, "arena":120, "latency":18.0} trials = {"name":"unstructured_prune", "accuracy":0.935, "flash":208, "arena":120, "latency":17.9}, {"name":"clustered", "accuracy":0.939, "flash":210, "arena":120, "latency":18.1}, {"name":"distilled_student", "accuracy":0.928, "flash":132, "arena":76, "latency":10.2}, {"name":"gap_replace_dense", "accuracy":0.932, "flash":148, "arena":84, "latency":11.4}, bottleneck = "latency" min_accuracy = 0.925 for t in trials: gain = (baselinebottleneck-tbottleneck)/baselinebottleneck valid = t"accuracy" >= min_accuracy and gain > 0 print(t"name", "accuracy", t"accuracy", f"{bottleneck}_gain={gain:.1%}", "KEEP" if valid else "REJECT") |
先读懂代码中的关键路径
- baseline与trials使用相同字段,避免不同方法只报告有利指标。
- bottleneck选择决定当前主要收益。
- min_accuracy是效果硬底线,资源改善不能覆盖。
- KEEP只表示值得继续目标板验证,不自动代表可发布。
你应该观察到什么
- 学生模型与算子替换产生多个部署指标变化。
- 非结构化剪枝在稠密内核假设下延迟收益很小。
- 是否保留取决于当前瓶颈与效果底线。
成功标准
- 优化前先写明瓶颈。
- 最终数字来自部署FlatBuffer、固件和目标板。
- 每次实验保留独立效果与资源证据。
失败时从哪里查起
优化没有兑现
| 现象 | 原因 | 检查 |
|---|---|---|
| 压缩包小、固件不变 | 编码在转换时展开 | 检查最终FlatBuffer/map |
| MACC少、延迟不降 | 内核或内存访问限制 | 逐算子目标板计时 |
| Arena不降 | 峰值来自另一层 | 查看张量生命周期 |
可以接受优化无收益的结论;它能阻止团队继续投入与目标平台不匹配的方法。
把实验迁移到真实MCU项目
剪枝或聚类后检查最终 `.tflite`、固件map和算子内核。若只有压缩传输包变小,应将收益归入下载带宽,而非推理Flash。
蒸馏学生和算子替换必须重新转换、重新量化、重新测Arena与时间。优化改变结构后,旧黄金中间张量不再适用,但输入与最终业务指标仍应回归。
稀疏与聚类还会影响升级和传输:差分包、压缩包和Flash中展开模型是三个不同对象。报告分别记录网络下载字节、存储字节和运行时字节,才能避免把通信收益误写成推理收益。
结构化剪枝删除通道后,后续层权重和批归一化参数都要同步重建。仅把通道权重置零仍保留原shape,稠密内核不会减少输出激活。检查模型摘要能确认结构是否真正缩小。
优化实验还要控制训练预算。学生模型多训练数倍轮次后略优于基线,收益可能来自训练时间而非蒸馏。记录轮次、增强、学习率和随机种子,让比较能够复现。
算子替换后的精度下降应通过错误类型判断是否可接受。关键故障召回下降不能由平均准确率补偿,资源收益也不能突破业务硬门槛。
最后把被拒绝的方法也写入决策记录,注明目标平台、运行时版本和失败原因。未来内核增加稀疏支持时,可以有依据地重新评估。
把结果再向前推进一步
- 找出当前项目唯一最紧的资源。
- 检查目标运行时是否支持计划使用的稀疏格式。
- 设计只替换Flatten+Dense的对照实验。
**收束:**剪枝、聚类、蒸馏和算子替换只有通过目标部署链,才会转化为MCU收益。用最终固件、Arena、时间、能量和效果做统一验收。
参考资料: