
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这类带HeliumMVE指令的MCUU65则增加了对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 ArenaVela通过内存规划把SRAM占用压到了约310KBAI推理能在256KB SRAM的MCU上跑通。第四步指令编码。Vela把优化后的计算图和内存布局转换成NPU的命令流Command Stream这个command stream在MCU固件中运行时由驱动程序逐条提交给NPU执行。3.2 实际操作跑通一个Vision模型下面是一套我自己反复使用的标准流程环境基于Ubuntu 20.04Python 3.8已经装好tensorflow 2.10。第一步安装Velapip 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图像分类int8148KB约45ms约3.2msDS-CNN关键词识别int842KB约8ms约0.8msDeepLabV3 lite语义分割int8310KB约180ms约28msYOLOv8n目标检测int8392KB约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对这些网络的支持成熟度高转换链路顺畅。第二你对功耗要求极苛刻。典型MCUEthos-U55的方案整体功耗可以控制在几十毫瓦级别电池供电的设备能接受。换成GPU或应用处理器级别的NPU功耗分分钟上瓦级电池和散热完全撑不住。第三你对成本敏感要求物料清单尽量简洁。一颗带Ethos-U的MCU同时承担控制和推理省掉一颗独立NPU或DSP芯片PCB面积和BOM成本都能省不少。反过来如果你的网络结构非常复杂包含大量Transformer、Attention、动态shape或者长序列LSTMEthos-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规划、总线冲突、甚至编译器版本都强相关。我一般拿到一块新板子会先在同一个模型上跑一轮基准测试建立自己的性能基线后续做任何优化都先拿基线数据做对照避免“优化了个寂寞”。希望这篇解析对你有用。如果你的场景和上面提到的某个案例有重合或者遇到了我这里没列出来的问题欢迎在评论区一起交流。