CMSIS-NN 生产落地案例:电池供电的离线语音唤醒
生产落地案例:电池供电的离线语音唤醒
背景。一款电池供电的语音开关,主控是带 DSP 扩展的 Cortex-M4,额定续航以月计,唤醒词检测必须在本地完成,音频不上云。第一版把浮点模型直接编进固件,结果 RAM 吃紧,单帧推理把 CPU 占满,音频采样中断经常被挤掉,出现丢帧。
怎么做。把模型整体做 int8 量化,算子层换成 CMSIS-NN。前端音频特征(MFCC)用 CMSIS-DSP 计算;卷积、深度可分离卷积、全连接、Softmax 全部走 CMSIS-NN 的 int8 算子;特征图与各层中间激活共用一块 scratch 区,按各层需求的最大值分配;权重数组放在 flash 且保证对齐;乘子与移位数组由转换脚本直接生成 C 文件,不走手抄。
关键配置。编译时开 DSP 扩展能力宏,库的优化等级提到 -O2 或 -Os;给每个算子调用包一层周期计数,记录耗时,作为后续裁剪网络的依据。周期计数本身要做对:先开跟踪使能,再清计数器,最后启动。
/* DWT 周期计数:Cortex-M3 / M4 / M7 上可用 */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t t0 = DWT->CYCCNT;
/* 这里调用某个 CMSIS-NN 算子 */
uint32_t cycles = DWT->CYCCNT - t0;
效果。换成定点算子后,同样算力预算下能塞下更多层,推理期间的 CPU 占用明显下降,采样中断不再被长时间阻塞,主频可以降一档运行,续航因此改善。具体省了多少周期、省了多少 RAM,与网络结构、内核实现版本强相关,我们是在自己板子上用 DWT 实测才知道的,官方宣传的倍数只能当参考。
踩的坑与解决。
- 第一版把 im2col 用的 scratch buffer 按「输入通道数 × 卷积核面积」估算,漏算了元素位宽,跑大一点的层直接越界,现象是随机崩溃。解决:一律改用库提供的大小辅助函数,不自己算。
- 乘子与移位数组从转换器日志里手抄,漏了一位,输出整体偏移。解决:改成由转换脚本直接生成 C 数组文件。
- 库单独用 -O0 编译,性能只有 -O2 的零头,一开始误以为「CMSIS-NN 也就这样」。解决:把库的优化等级与工程对齐。
- 用 softfp 编库、工程其他部分是 hard,链接期就报 ABI 冲突。解决:全工程统一浮点 ABI。

