嵌入式AI算子为何卡顿?内存怎么排?
模型能在开发板上跑起来,不等于能在控制周期里稳定跑完。嵌入式AI最先暴露的常不是算力峰值不够,而是算子拆分和内存搬运把推理时间切成了不可预测的碎片。
算子卡顿常从一次看似无害的回退开始。模型里只要有一个激活函数、归一化或后处理算子不被 NPU 支持,运行时就可能把整段子图切到 CPU,再把张量从加速器内存搬回通用内存。搬运本身要经过总线仲裁,格式还可能从 NHWC 转成 NCHW,下一段再转回去。这样单个算子的计算量并不大,却在每帧里插入了多次同步点,流水线被迫等待最慢的那次转换。
更麻烦的是,小张量回退会破坏批量优化假设。很多推理框架在大卷积上能稳定调用内核,但在 reshape、slice、concat 这类边界算子上只能靠通用实现兜底;如果这些算子夹在两段加速子图之间,就会让 NPU 频繁停机。嵌入式AI项目里排查这类问题,不能只看模型总 MAC 数,而要导出逐算子时间线,确认每个节点到底跑在哪个执行域,是否触发了隐式拷贝和格式重排。
内存排布则决定这些等待会不会继续放大。激活特征图、输入帧、输出缓存和后处理临时数组如果都从同一片堆里动态申请,运行时很容易在峰值帧遇到连续空间不足,随后退回更慢的外部 DDR 或重新整理内存池。即便容量足够,SRAM 与 DDR 的访问带宽也不同;把高复用的中间层留在片上,把只读权重按分块顺序预取,通常比盲目扩大外部内存更有效。
更稳妥的做法,是在部署前按最坏分辨率和最大并发数生成静态内存计划。每层激活只在最后一次使用前保持占用,生命周期不重叠的缓冲可以复用;输入预处理和 NPU 推理之间用 DMA 双缓冲衔接,避免 CPU 同时承担拷贝和格式转换。若必须使用 Cache,还要把回写和失效动作固定在缓冲所有权交接处,不能让调试日志或后处理线程随手触碰正在被 DMA 使用的区域。
还有一个容易被忽略的边界,是后处理与预处理的共享缓存。检测框解码、归一化反算和阈值筛选若在 CPU 上串行执行,会把前端已经隐藏的拷贝重新暴露出来。对小模型而言,这段尾部处理有时占比高于主干网络,因此应把解码、量化反算和输出排序纳入同一条性能账,而不是只优化卷积核。
验证时应同时记录算子时间、总线占用和内存水位。只看平均 FPS 会掩盖长尾帧,因为偶发慢帧往往正好来自一次缓存维护、一段 CPU 后处理或一次外部内存突发冲突。把这些事件与控制周期对齐,才能判断优化目标是换模型结构、改算子实现,还是重排缓冲生命周期。若同一帧在不同负载下时间差很大,还应同步检查电源降频和总线限速,避免把系统级拥塞误认为单个算子慢。





