1. Ethos-U到底是什么东西
这几年端侧AI的火爆程度不用我多说,从智能音箱到工业视觉检测,从可穿戴设备到智能家居网关,凡是带个MCU的设备,厂商都想往里面塞点神经网络模型。但问题跟着就来了:Cortex-M这类MCU的算力就那么多,靠CPU硬跑一个MobileNet V2,帧率能跑到个位数就不错,功耗还压不住。于是Arm在2019年前后推出了Ethos-U系列NPU,专门解决MCU级别设备跑神经网络的问题。
Ethos-U是Arm针对Cortex-M系列处理器配套设计的微架构NPU,目前主流的两条产品线是Ethos-U55和Ethos-U65。U55主打极致低功耗和小面积,适合Cortex-M55、Cortex-M85这类带Helium(MVE)指令的MCU;U65则增加了对Cortex-A内核的适配能力,可以在Linux侧或RTOS侧配合使用,覆盖更高性能需求场景。两者的设计哲学一脉相承:不追求绝对峰值算力,而是追求在极低功耗和极小面积内,把神经网络推理做到“够用且稳”。
这篇文章我结合自己实际调板子和跑模型的经验,从架构设计思路到Vela工具链的使用,再到性能优化时踩过的坑,系统梳理一遍Ethos-U的实战要点。不管你是刚接触嵌入式AI的新手,还是已经在用CMSIS-NN做推理的老手,想往NPU方案迁移,这篇都能给你一个相对完整的参考。
2. 微架构层面的几个关键设计决策
很多人有个误区,觉得NPU就是把一堆MAC(乘累加单元)堆在一起,堆得越多性能越强。Ethos-U的设计思路恰恰相反,它在意的核心指标是“能效比”和“存储带宽利用率”,而不是单纯的TOPS数字。
2.1 为什么不能只堆算力
先算一笔账。假设一个Cortex-M55跑在400MHz,主频算力大约就是4 GOPS左右。如果我要做一个手势识别模型,大概2M次乘加运算,CPU跑一次推理大约需要15到30毫秒,这在很多交互场景里已经能感觉到明显延迟。NPU把推理时间压到2到5毫秒,性能提升了接近10倍。
但关键在于,这个性能提升不能靠简单堆MAC实现。MCU的DDR或者Flash带宽是固定的,一个MAC每周期要读权重和输入数据,如果内存系统供不上数据,再多的MAC单元也只能空转。Ethos-U在设计时,花在存储系统上的心思远多于MAC阵列本身,这正是它和很多“暴力堆料”NPU的本质区别。
2.2 权重压缩引擎
Ethos-U55有一个叫Weight Compression Engine(权重压缩引擎)的模块,这是它在架构上最有价值的部分之一。
在嵌入式设备上,模型的权重往往比激活值大一个数量级。一个100KB的模型,权重可能占掉85KB。如果能把这85KB压缩到30KB,意味着什么?Flash读取时间减少、带宽占用降低、功耗下降。Ethos-U支持对权重进行类似“稀疏性编码+游程编码”的压缩,实测下来,8比特权重的压缩率通常能做到2到3倍。
我在实际工程里验证过:一个手势识别CNN模型,原始TFLite文件是180KB,经过Vela编译器转换NPU格式后,权重区只有74KB。这直接决定了我能把这套方案放进Flash只有256KB的MCU里,如果当时没有这个压缩能力,就得换更大Flash的芯片,成本完全不是一个量级。
2.3 内存布局的关键设计:Im2Col和权重驻留
Ethos-U在内存子系统上做了几个有讲究的决策。第一个是Im2Col操作硬件化。在使用CPU跑卷积时,im2col是一个很常见的预处理步骤,把三维的输入矩阵展开成二维,方便做矩阵乘法。这个操作本身要读一遍数据、写一遍数据,在MCU上会消耗大量时钟周期和内存带宽。Ethos-U把这件事用硬件电路直接做了,CPU和NPU都不用再关心这个步骤。
第二个决策是权重驻留(Weights Reside)策略。它在内部有一个专用的SRAM缓冲区,转换后的权重在推理过程中可以完整驻留在SRAM内,不需要频繁从Flash重读。这个设计的实际收益是:推理延迟可预测,不会因为Flash读取速度波动导致帧率抖动。对于做实时控制的场景,这一点比绝对的峰值算力更重要。
2.4 和Cortex-M的协作模式
Ethos-U不是独立运行的处理器,它更像Cortex-M的一个“加速外设”。CPU通过总线配置寄存器、下发任务描述符,NPU完成计算后通过中断通知CPU读取结果。
这种主从架构有一个显著优势:CPU在NPU计算期间可以去做别的事情。比如在语音唤醒场景中,NPU在跑关键词识别模型时,CPU可以去处理音频采集、缓冲管理,甚至部分武数据预处理。等NPU给出唤醒结果,CPU再介入后续逻辑。整体系统吞吐量和实时性显著优于“CPU串行跑整个流程”。
有一点需要特别注意:Ethos-U55的存储空间和Cortex-M是共享的,也就是说两者会竞争总线带宽。在系统设计时,要仔细规划DMA通道优先级和SRAM分区,避免NPU读取权重时和CPU的数据搬运产生冲突,这个细节我会在后面优化章节详细说。
3. Vela编译器:把TFLite变成NPU能听懂的话
有了硬件还不够,关键是软件工具链能不能把模型高效地映射到NPU上。Ethos-U的标准工具链是Vela编译器(项目地址在Arm的GitHub仓库下,叫ethos-u-vela)。它接收TFLite格式的模型文件,经过算子解析、布局转换、内存规划、指令生成等步骤,输出一个针对具体NPU配置优化过的二进制文件。
3.1 Vela的核心工作流程
Vela的完整处理链路大致分四步。
第一步,图解析。Vela读取TFLite文件,构建计算图IR(中间表示),同时检查每个算子是否受NPU支持。所有算子都跑NPU自然是理想状态,但实际中常常遇到某个算子不支持,Vela会把计算图拆成若干子图,NPU只处理子图范围内的算子,其余算子自动留在CPU端执行。这一步的结果会直接决定推理延迟的瓶颈在CPU还是在NPU。
第二步,算子布局转换。TFLite默认使用NHWC数据排布(N=批量、H=高度、W=宽度、C=通道)。但Ethos-U内部的计算核心更喜欢NCHW或特定Channel排列,以获得更好的数据读取连续性。Vela会插入必要的“transpose”操作,把数据布局变成NPU友好的形式。做性能调优时,理解这一环很重要:因为你看到的网络结构里,可能会有一些额外的“Transpose”算子出现在NPU子图中,这不是模型设计的问题,而是Vela为NPU优化做的布局转换。
第三步,内存规划。这是Vela最有价值的部分之一。它会分析整张计算图的张量生命周期,在片上SRAM里做类似“寄存器分配”的活:两个激活张量如果能错开生命周期,它们可以共用同一块缓冲区。这能显著降低SRAM需求。当年我调一个语义分割模型时,原始TFLite推理需要用880KB的Tensor Arena,Vela通过内存规划把SRAM占用压到了约310KB,AI推理能在256KB SRAM的MCU上跑通。
第四步,指令编码。Vela把优化后的计算图和内存布局转换成NPU的命令流(Command Stream),这个command stream在MCU固件中运行时,由驱动程序逐条提交给NPU执行。
3.2 实际操作:跑通一个Vision模型
下面是一套我自己反复使用的标准流程,环境基于Ubuntu 20.04,Python 3.8,已经装好tensorflow 2.10。
第一步,安装Vela:
pip install ethos-u-vela第二步,准备模型。拿一个常见的视觉模型举例,TFLite文件假设叫deeplab_v3_lite.tflite,量化类型是int8。
第三步,直接运行Vela转换命令:
vela deeplab_v3_lite.tflite \ --config my_config.ini \ --output-dir ./output配置文件里我通常这样设置:
; my_config.ini [System] arena_cache_size = 320000 [NPU] macs = 256 cores = 1 memory_mode = Shared_Sram system_config = Ethos_U55_High_End_Embedded memory_region = Sram这里的arena_cache_size是给NPU分配的最大SRAM缓存空间,单位是字节,需要根据具体MCU的SRAM大小来设置。macs参数对应NPU的MAC单元数量,U55有128/256两个版本。memory_mode表示存储模式,有Shared_Sram和Dedicated_Sram两种选项。system_config则需要根据实际芯片厂商提供的配置从支持的配置文件列表里选。
转换完成后,会在output目录里生成一个后缀为_vela.tflite的文件,这个就是最终烧录到MCU的格式。
3.3 固件侧集成方式
固件侧,Arm提供了配套的驱动程序源码,叫ethos-u-core-driver,可以从Arm的GitHub仓库获取。它提供了一组C API,核心流程是:
// 初始化NPU ethosu_init(); // 将Vela生成的模型二进制数据装载到内存 ethosu_load_model(model_data_ptr, model_size); // 配置推理输入输出缓冲区 ethosu_configure_tensors(input_ptr, output_ptr); // 触发推理 ethosu_run();实际工程中,我会在RTOS下把NPU推理做成一个独立任务,输入数据由DMA搬运完成,NPU执行完后触发中断,中断服务函数里发送信号量通知推理任务读取结果。这样整个流水线可以做到“采集-预处理-推理-后处理”四级流水,帧率比同步调用高出一大截。
4. 性能优化的方法论与实测要点
Vela转换完成、基础版本能跑起来,这只是万里长征第一步。实际性能能不能达到预期,还需要细致的调优。我把这一段时间踩坑总结下来的优化方法,按照“先看瓶颈、再调内存、后调算子”的顺序梳理一遍。
4.1 先搞清楚瓶颈在哪
拿到一个模型,第一步不是盲目调Vela参数,而是先看性能报告。Vela转换后会生成一个SUMMARY报告,里面有关键的性能评估数据:
Total SRAM used: 320000 bytes Total MAC count: 241,930,240 cycles Total command stream size: 46000 bytes这里面的MAC count是用来估算理论计算时间的,但不等于实际时间。实际推理时间由“计算时间”和“内存访问时间”中较大的那个决定。
我在调一个ResNet-18变体模型时遇到过这种情况:理论上MAC count只有2.1亿次,按256 MAC/周期算,大约82万周期,400MHz下应该是2ms级别的推理时间。但实测跑到8ms,慢了4倍。后来用Arm的Streamline工具抓NPU和总线活动曲线,发现总线被大量读取权重和写回中间结果占满,实际瓶颈在内存带宽而不是计算单元。
找到瓶颈后,对应的优化手段完全不同:如果是计算瓶颈,考虑剪枝和替换算子;如果是带宽瓶颈,考虑权重压缩、降低位宽、减小特征图尺寸。
4.2 内存优化:SRAM预算和复用
Ethos-U对SRAM的需求可以分成两部分:一是权重驻留空间,二是激活值缓冲空间。激活值缓冲主要取决于网络中间特征图的大小,这是最难优化的部分,因为你不能随便裁剪特征图尺寸,那会影响精度。
实际工程中,我的做法是这样的:
第一,让Vela自动做张量生命周期分析,尝试不同arena_cache_size配置,看SRAM占用和推理时间的变化关系。
第二,如果SRAM不够,考虑把部分大特征图的算子留在CPU执行。虽然CPU执行慢,但CPU可以用Flash带宽更低的方式来读取,不会和NPU抢SRAM。听起来有点反直觉,但实测中这个“混合部署”策略在某些场景下反而整体延迟更低。
第三,合理配置NPU的系统配置参数。Vela提供了一系列system_config预设,分别对应不同SRAM大小和带宽的配置。选错配置会导致Vela的内存规划过于保守或激进,影响性能。
4.3 算子层面的优化技巧
算子层面有几个高频可优化点:
一个是解决“Ops not supported”问题。网络里如果有NPU不支持的算子(比如某些高级激活函数、动态shape操作),整段子图只能落到CPU执行。CPU执行这些算子的速度如果远低于NPU,整个推理延迟就被拉长。我的建议是优先修改模型结构,用支持算子替代,比如把Softmax换成一个简化版本,在模型训练阶段就考虑部署时的算子约束。
另一个是Batch Normalization融合问题。Vela会把BN层在量化阶段融合进前面的卷积层,减少计算量。但前提是这个BN必须是“训练后可以合并”的形态。如果模型结构里BN层的位置比较特殊(比如BN后面又跟了一个Scale层),Vela可能无法自动融合,就需要在训练时调整网络结构。
还有一个容易被忽略的点:输入和输出的数据格式。模型的第一层如果是NCHW格式的输入,而你的摄像头数据是NHWC或Raw数据,Vela会插入额外的转换操作。最佳实践是把预处理尽量安排在NPU外部(比如用CPU的CMSIS-DSP做),输入数据直接以NPU友好的格式送进去。
4.4 实测数据参考
我在STM32N6开发板(Cortex-M55 400MHz + Ethos-U55 256MAC)上验证过几组典型的模型,结果可以作为参考:
| 模型 | 任务 | 量化 | Vela SRAM占用 | CPU推理(ms) | NPU推理(ms) |
|---|---|---|---|---|---|
| MobileNetV2 1.0 | 图像分类 | int8 | 148KB | 约45ms | 约3.2ms |
| DS-CNN | 关键词识别 | int8 | 42KB | 约8ms | 约0.8ms |
| DeepLabV3 lite | 语义分割 | int8 | 310KB | 约180ms | 约28ms |
| YOLOv8n | 目标检测 | int8 | 392KB | 约350ms | 约42ms |
以上数据是在我自己的测试环境和特定优化配置下得到的,不同板子、不同Vela版本会有差异,但趋势是一致的:NPU相对CPU有10到15倍的性能提升,而功耗只增加了一小部分。
5. 常见问题与排查技巧实录
5.1 模型转换报错:算子不支持怎么办
这是最高频的问题。Vela转换时报“ERROR: Unsupported operator”或者类似提示,后面会跟着算子名。我的排查流程是先确认这个算子是否真的关键。如果算子数量少,可以手工把子图拆出来放到CPU跑,用Google的“Delegate”机制。更常见的做法是修改模型结构,比如把“tf.split + concat”这种结构替换为单个卷积,或者用“激活函数 + 卷积”的等价组合替代不支持的结构。
5.2 推理结果和CPU端不一致
NPU推理结果和CPU端的参考结果有偏差,绝大多数情况下是量化精度问题,不是硬件Bug。排查方法是:
第一,确认模型是否完全量化成int8(包括输入和输出张量)。有时候某些层的输出仍然保持了浮点类型,Vela在转换时会强制量化,这可能会导致精度下降。
第二,检查输入数据的量化参数。TFLite模型在转换时,会记录每个张量的scale和zero_point。如果你在固件侧预处理数据时没有按正确的scale做缩放,输入到NPU的数据分布就错了,结果当然不对。我遇到过不止一次,最后发现是摄像头数据在RGB转BGR时把通道顺序搞反了,导致模型输出完全混乱。
第三,逐层对比中间张量值。Arm提供了调试工具可以导出NPU每层的输出,你可以和CPU参考值做逐层比较,定位是从哪一层开始出现明显偏差的。
5.3 推理时间不稳定,偶尔性能反弹
前面提到过,NPU和CPU竞争总线带宽会导致帧率抖动。如果发现推理时间忽快忽慢,大概率是总线冲突问题。
解决思路有三种:一是用RTOS的任务优先级调整,把NPU推理任务设为高优先级,其他内存搬运任务让位;二是调整DMA通道优先级,让音频或摄像头采集的DMA传输不干扰NPU的权重读取;三是如果芯片支持,可以把NPU的SRAM和CPU的SRAM在物理上隔开,用不同总线连接,避免共享仲裁。
5.4 Vela转换后模型体积变大
有些朋友发现Vela转换后的模型文件比原始TFLite还大,开始怀疑工具链有问题。其实这是正常现象。Vela输出的模型里包含的不是神经网络权重,而是NPU的命令流和控制数据,命令流为了保证NPU执行的流水线效率,可能包含一些填充和校验信息。
如果体积达标对你有硬性要求,可以尝试调整Vela的“--arena-cache-size”参数,或者在转换配置里开启“command stream compression”选项,能压缩掉一部分空间。
5.5 结合CMSIS-NN做协同优化
值得说的是,Ethos-U和CMSIS-NN并不是二选一的关系,它们可以协同使用。当模型中有一部分算子留在CPU执行时,这部分算子如果能用CMSIS-NN优化库实现,整个混合推理的性能还能再上一个台阶。
例如,我处理过一个语音识别流水线:音频预处理、特征提取用CPU的CMSIS-DSP加CMSIS-NN实现,神经网络的核心卷积部分交给NPU,最后后处理(如CTC解码)又回CPU。这样NPU跑重计算、CPU跑轻逻辑,两者各司其职,整体延迟比全CPU方案降低了80%以上。
6. 方案选型参考:什么时候选择Ethos-U
最后聊一个偏决策层面的问题:你的项目是不是真的需要NPU?
根据我的经验总结如下,如果你的产品属于以下场景,Ethos-U是一个很好的选择:
第一,你的模型以CNN和FC为主,特别是图像分类、目标检测、语音唤醒、关键词识别这类任务。Ethos-U对这些网络的支持成熟度高,转换链路顺畅。
第二,你对功耗要求极苛刻。典型MCU+Ethos-U55的方案,整体功耗可以控制在几十毫瓦级别,电池供电的设备能接受。换成GPU或应用处理器级别的NPU,功耗分分钟上瓦级,电池和散热完全撑不住。
第三,你对成本敏感,要求物料清单尽量简洁。一颗带Ethos-U的MCU同时承担控制和推理,省掉一颗独立NPU或DSP芯片,PCB面积和BOM成本都能省不少。
反过来,如果你的网络结构非常复杂,包含大量Transformer、Attention、动态shape或者长序列LSTM,Ethos-U可能不是一个好选择。这类模型更适合GPU或者专门的AI加速卡。对于这类模型,可以考虑Arm的Corstone-1000系列平台方案,将Ethos-U和Cortex-A结合,形成更高算力的异构计算节点。
还有一点,如果你的预算充足且在合适的主控平台上,Ethos-U65配合Cortex-A55/A53核可以覆盖很多“微边缘”场景,比如智能摄像头、小型网关等设备,算力和生态都比纯MCU方案更有优势。
7. 社区与生态
做嵌入式AI最怕的就是遇到问题没人讨论、资料少。Ethos-U这块的生态目前看是相对健康的,Arm官方维护了ethos-u的GitHub仓库,里面有核心驱动、Vela编译器源码,以及Ethos-U55和Ethos-U65的参考设计。相关的问题在Arm的开发者社区、Stack Overflow上也能搜到不少案例。
还有一款值得关注的开源项目是TensorFlow Lite for Microcontrollers,它已经集成了Ethos-U的Delegate支持,也就是说你可以在TFLite Micro的框架内直接调用Ethos-U,不需要自己写底层驱动和调度逻辑。对于团队里算法工程师多、嵌入式工程师少的情况,这条路径的开发和维护成本低很多。
我在实际项目中用的是自己的调度框架加Vela生成的模型文件,这样对推理流程的控制更精细,调试起来也更直接。如果你的团队规模较小、更追求快速出原型,建议优先用TFLite Micro + Delegate的现成方案。
8. 一点点个人体会
Arm Ethos-U这个系列,虽然看起来只是MCU领域的一个小NPU,但它的架构设计里其实藏着很多值得琢磨的思路。比如它没有跟风去堆稀疏计算,而是用“权重压缩+内存规划”来等效地提升有效带宽,这种在资源受限条件下寻找最优解的思路,做嵌入式芯片和系统的人应该都深有感触。
如果你正准备把AI推理往MCU端迁移,建议先用小模型把整套工具链跑通,不要一上来就上大型模型。Vela工具链的学习曲线不算陡,但“遇到算子不支持”、“内存规划不合理”这类问题在新手期几乎一定会碰到。先跑通一个100KB级别的模型,把整个流程理清楚了,再逐步上大模型,这个路径是最省时间的。
另外,性能数据不要只看理论值。芯片手册上的TOPS和访存带宽都只是上限,真正能跑多少,跟你的模型结构、SRAM规划、总线冲突、甚至编译器版本都强相关。我一般拿到一块新板子,会先在同一个模型上跑一轮基准测试,建立自己的性能基线,后续做任何优化,都先拿基线数据做对照,避免“优化了个寂寞”。
希望这篇解析对你有用。如果你的场景和上面提到的某个案例有重合,或者遇到了我这里没列出来的问题,欢迎在评论区一起交流。