资讯详情

资讯详情

嵌入式工程师视角:端侧AI部署为何从CNN重新入手

1. 为什么我又回头捡起了CNN做了七八年嵌入式从裸机寄存器一路写到Linux驱动再到这两年往端侧AI方向靠我发现自己绕了一个大圈最后还是回到了卷积神经网络这个起点。不是因为CNN有多新恰恰相反它老得都快被Transformer挤到墙角了。但如果你真的在资源受限的板子上跑过模型就会明白一个道理端侧AI不是把云端那套东西原封不动搬下来而是要在算力、内存、功耗三条线之间找平衡而CNN恰好是那个平衡点最稳的方案。这篇文章我想聊的不是“CNN是什么”这种教科书内容而是从一个嵌入式工程师的视角讲清楚为什么端侧AI部署要从CNN重新入手、CNN在NPU上到底怎么跑、模型量化踩过哪些坑、以及一个完整的嵌入式端侧AI项目从选型到落地的全流程。适合那些有嵌入式基础、想往AI方向转的朋友也适合已经在做端侧部署但总觉得模型跑不顺的同行。我会尽量把每个决策背后的“为什么”讲透而不是只丢一堆结论。先说一个我自己的判断端侧AI的核心矛盾从来不是模型精度不够而是算力供给和模型需求之间的错配。云端可以堆GPU端侧不行。一块常见的边缘计算芯片NPU算力可能只有几个TOPS内存可能就几百MB功耗还得控制在几瓦以内。在这种约束下CNN的局部连接、权值共享、平移不变性这些特性天然就比全连接网络和注意力机制更省资源。这不是情怀是算术。2. 端侧AI的真实约束先算清楚账再谈模型2.1 算力、内存、功耗的三重天花板很多从算法转过来的朋友习惯先看模型精度再看推理速度。但在嵌入式端侧顺序得反过来。你得先知道板子能给多少算力再决定模型能做多大。我拿一块典型的中端边缘AI芯片举例NPU标称算力4TOPSINT8内存512MB LPDDR4典型功耗3W。看起来还行对吧但你要注意几个隐藏成本。第一TOPS是峰值算力实际利用率能到60%就算不错了因为数据搬运、算子调度、内存带宽都会吃掉大量时间。第二512MB内存不是全给模型的系统要占、中间激活要占、双缓冲要占真正留给模型权重的可能就200MB出头。第三3W是典型功耗NPU满载的时候可能冲到5W以上散热跟不上就降频。所以我在做任何端侧AI项目之前都会先做一张资源预算表资源项总量系统占用可用余量模型预算NPU算力4 TOPS驱动/调度约10%3.6 TOPS按60%利用率算约2.2 TOPS内存512 MB系统中间件约180 MB332 MB权重激活约250 MB功耗5 W峰值系统约1.5 W3.5 WNPU约2 W存储8 GB eMMC系统约2 GB6 GB模型文件数据这张表看起来简单但它决定了你后面所有技术选型的边界。比如你一看模型预算只有250MB内存那ResNet-50这种25MB权重的模型加上激活其实还好但你要是想上ViT-Base光权重就300多MB直接出局。2.2 为什么CNN比Transformer更适合端侧起步我并不是说Transformer不能在端侧跑而是说对于刚入门的团队CNN的确定性更强。原因有三条。第一算子成熟度。CNN的核心算子是卷积、池化、BN、ReLU这些在几乎所有NPU上都有硬件加速支持工具链也成熟。Transformer的注意力机制涉及大量的矩阵乘、Softmax、LayerNorm不同NPU的支持程度参差不齐有的甚至要回退到CPU跑速度直接崩。第二内存访问模式。CNN的卷积是局部窗口滑动数据复用率高缓存友好。Transformer的全局注意力需要频繁的大范围内存访问带宽压力大。在端侧这种内存带宽本来就紧张的环境里CNN的优势非常明显。第三量化友好度。CNN对INT8量化的容忍度普遍比Transformer高。我实测过一个MobileNetV2量化到INT8精度掉不到1个点但一个小的ViT量化后掉3到5个点是常事。原因在于注意力机制里的Softmax和LayerNorm对数值范围很敏感量化误差会被放大。注意这不是说端侧不能做Transformer而是说如果你的团队第一次做端侧AI部署从CNN入手能让你把工具链、量化流程、NPU调度这些基础设施跑通再往上叠复杂结构才稳。2.3 嵌入式工程师做AI的独特优势我见过不少纯算法背景的朋友做端侧部署模型训得很好但一到板子上就各种问题。反而是嵌入式出身的工程师在这件事上有天然优势。你懂内存布局就知道为什么NCHW和NHWC的转换会带来额外开销你懂DMA就知道怎么用零拷贝减少数据搬运你懂中断和实时性就知道怎么安排推理任务和采集任务的优先级。这些在纯算法视角里是“工程细节”但在端侧就是决定成败的关键。我自己在做第一个端侧AI项目的时候最大的收获不是学会了PyTorch而是意识到模型部署的本质是一个嵌入式系统设计问题只不过其中一个模块换成了神经网络。3. CNN核心结构在端侧视角下的再理解3.1 卷积层为什么它天生省算力教科书讲卷积重点在特征提取。但从端侧视角看卷积最值钱的特性是权值共享和局部连接。假设一个全连接层输入1024维输出1024维参数量是1024×1024≈100万。同样感受野的卷积层用3×3卷积核、64通道参数量只有3×3×64×64≈3.7万。差了将近30倍。这不是小数目在内存只有几百MB的板子上参数量直接决定你能不能跑起来。再说计算量。卷积的计算量是输出特征图尺寸×卷积核尺寸×输入通道×输出通道。虽然看起来也不少但因为有大量的数据复用实际在NPU上的执行效率很高。我实测过一个3×3卷积在NPU上的利用率能到70%以上而全连接层往往只有30%到40%因为全连接没有数据复用纯粹是矩阵乘受内存带宽限制严重。3.2 池化与步长卷积降维的两种思路端侧模型设计里降维是个关键操作。特征图越小后续计算量和内存占用就越低。传统做法是池化现代做法更多用步长卷积。池化层的优点是零参数、计算简单在NPU上几乎不占时间。但它的问题是信息丢失比较粗暴而且有些NPU对池化的支持不如卷积好。步长卷积则是把降维和特征提取合在一起用stride2的卷积代替池化卷积的组合精度通常更好但计算量会大一些。我的经验是在算力紧张的板子上优先用池化在算力有余量、追求精度的场景用步长卷积。这个取舍没有绝对答案要看你板子的实际瓶颈在哪。3.3 BN层与ReLU融合是必须的BN层在训练时很重要但在推理时它就是一个线性变换完全可以融合进前面的卷积层。具体来说卷积的输出是yWxbBN是yγ(y-μ)/σβ把BN的参数代进去可以得到一个新的权重W和偏置b直接用一个卷积就搞定。这个融合在端侧是必须做的不是可选项。原因很简单单独跑BN层意味着多一次内存读写多一次算子调度在NPU上可能就是几毫秒的差距。我见过一个模型融合BN前后推理时间差了15%。ReLU就更不用说了几乎零成本而且能增加稀疏性有些NPU还能利用稀疏性加速。3.4 全连接层端侧模型尽量少用全连接层在分类头里很常见但它在端侧是个“内存杀手”。一个512维到1000维的全连接层参数量就是51万占2MBFP32或0.5MBINT8。看起来不多但如果你有多个全连接层累积起来就很可观。更麻烦的是全连接层的计算是稠密的没有数据复用NPU利用率低。所以我在端侧模型里能用全局平均池化代替全连接就用全局平均池化实在要用也尽量把维度压小。4. 从训练到部署一个CNN端侧落地的完整流程4.1 模型选型不是越新越好端侧模型选型我的原则是先看工具链支持再看精度最后看新颖度。工具链支持是第一位。你选一个NPU厂商工具链不支持的模型结构后面量化、编译、部署全是坑。我一般会先翻NPU的算子支持列表确认卷积、深度可分离卷积、池化、ReLU、BN这些基础算子都支持再去选模型。目前端侧比较稳的CNN系列有MobileNetV2/V3、ShuffleNetV2、EfficientNet-Lite。这几个都是为移动端设计的参数量小、计算量低、量化友好。我个人的首选是MobileNetV2因为它的结构简单、工具链支持最广、量化后精度损失最小。模型参数量计算量量化友好度工具链支持MobileNetV23.5M300M高极广MobileNetV3-Small2.5M60M高广ShuffleNetV22.3M146M中高广EfficientNet-Lite04.7M407M中中ResNet-1811.7M1.8G中广4.2 训练阶段的端侧意识很多人在训练阶段完全不考虑部署等到部署时才发现各种问题。我的做法是训练时就把端侧约束带进去。具体来说第一输入分辨率不要定太高。224×224是常见选择但如果你的场景允许160×160甚至128×128能省大量算力。第二不要用奇怪的算子。比如自定义的激活函数、非标准的池化方式这些在NPU上很可能不支持。第三训练时就用QAT量化感知训练而不是训练完再PTQ训练后量化。QAT虽然麻烦一点但精度损失小很多。QAT的核心是在训练时模拟量化误差让模型权重适应低精度表示。PyTorch里有现成的工具基本就是加几行代码的事。我实测过一个模型PTQ掉3个点QAT只掉0.5个点。4.3 量化INT8不是简单截断量化是端侧部署最关键的环节也是最容易出问题的环节。很多人以为量化就是把FP32直接转成INT8其实远不止。量化的本质是用一个仿射变换把浮点范围映射到整数范围。公式是q round(x/scale zero_point)。scale是缩放因子zero_point是零点偏移。这两个参数决定了量化的精度。问题在于不同层的数值范围差异很大。第一层卷积的输入是图像像素范围0到255中间层的激活值可能范围很小最后一层的输出又可能很大。如果所有层用同一个scale精度肯定崩。所以实际用的是逐层量化每层有自己的scale和zero_point。更细的还有逐通道量化就是每个卷积核通道有自己的scale。这个精度更高但有些NPU不支持需要确认。实操心得量化校准集的选取非常关键。不要随便拿几张图就去校准要用能覆盖实际场景分布的数据。我校准过一个工业质检模型用训练集校准和用现场采集数据校准精度差了2个多点。4.4 编译与部署工具链的坑最多量化完的模型要经过NPU厂商的编译器转成板子能跑的格式。这一步是坑最多的地方。常见问题包括算子不支持导致回退CPU、内存分配失败、输入输出格式不匹配、多线程调度冲突。我的建议是先用一个极简模型跑通全流程比如就一个卷积加一个全连接确认工具链没问题再上真实模型。编译时要注意几个参数输入shape要固定不要用动态shapebatch size一般设1端侧很少做批处理内存分配模式要选对有的工具链支持静态分配有的要动态。部署到板子上之后第一件事是验证数值正确性。拿同样的输入对比PC端和板子端的输出误差在合理范围内才算通过。我见过有人模型跑起来了但结果完全不对最后发现是输入归一化参数没对上。5. NPU实战那些文档里不会写的事5.1 NPU不是万能加速器很多新手以为有了NPU就万事大吉实际上NPU只加速特定算子。卷积、矩阵乘这些它能跑得飞快但像reshape、transpose、concat这些内存操作NPU往往帮不上忙甚至可能因为格式转换更慢。我遇到过一个案例模型里有个频繁的reshape操作在PC上无所谓在板子上因为NPU不支持回退到CPU跑结果整个推理时间翻倍。后来把reshape改成等价的其他算子组合问题才解决。所以看模型的时候不能只看计算量还要看算子类型分布。内存操作占比高的模型在端侧往往表现不佳。5.2 内存带宽才是真瓶颈NPU算力标称再高如果内存带宽跟不上也是白搭。卷积层的计算强度计算量/内存访问量相对较高但深度可分离卷积的计算强度就低很多容易受带宽限制。我实测过一个深度可分离卷积为主的模型NPU利用率只有40%左右瓶颈就在内存带宽。解决办法有两个一是用更大的batch但端侧通常不行二是优化数据布局减少不必要的内存搬运。5.3 多核调度与任务划分有些NPU是多核的比如双核或四核。怎么把模型划分到多个核上是个技术活。简单按层切分往往效果不好因为核间同步有开销。更好的做法是按计算图切分把可以并行的分支分到不同核上。这个通常由工具链自动完成但你可以通过调整模型结构来帮助它。比如减少跨分支的依赖让并行度更高。6. 常见问题与排查速查6.1 精度掉了怎么办精度下降是端侧部署最常见的问题。排查顺序我一般是这样现象可能原因排查方法解决方向精度掉很多量化误差大对比逐层输出改QAT或逐通道量化精度掉一点校准集不匹配换校准数据用现场数据校准结果完全不对预处理不一致检查归一化参数对齐PC和板子预处理部分类别差数据分布偏移分析混淆矩阵补充难例训练6.2 速度不达预期怎么办速度问题通常有三个来源算子回退、内存瓶颈、调度开销。先看profiling确认时间花在哪。如果某个算子耗时异常大概率是回退CPU了。如果整体均匀地慢可能是内存带宽问题。如果启动慢但推理快可能是初始化开销。6.3 内存不够怎么办内存不够的解决办法按优先级排第一减小输入分辨率第二用更小的模型第三减少中间激活的内存占用比如用inplace操作第四量化到INT8甚至INT4。避坑技巧有些工具链在编译时会预留很大的内存池实际用不到那么多。可以手动调小内存池配置能省不少。7. 我踩过的几个真实坑第一个坑是输入格式。我一开始没注意板子端图像采集是BGR模型训练是RGB结果精度掉了一大截。后来加了通道转换才恢复。这个坑很隐蔽因为模型能跑只是结果不对。第二个坑是量化校准。我用训练集做校准精度还行但一到现场就崩。后来发现现场光照条件和训练集差异很大重新采集校准数据后才稳定。第三个坑是NPU频率。板子默认NPU频率不是最高需要手动调。调完之后推理速度提升了30%。这个在文档里往往写得很隐蔽要翻很久才能找到。第四个坑是多线程冲突。我的采集线程和推理线程共享内存没加锁导致偶发数据错乱。这种问题很难复现但一旦出现就是致命的。8. 后续可以怎么扩展CNN跑通之后往上可以走几条路。一是换更复杂的backbone比如引入注意力模块但要先确认NPU支持。二是做多模型串联比如检测加分类。三是做模型蒸馏用大模型教小模型在端侧拿到更好的精度。我个人的建议是先把一个CNN模型在板子上跑稳、跑快、跑准把工具链和流程摸透再去碰更复杂的东西。端侧AI这件事基础打牢比追新更重要。最后分享一个小技巧每次改完模型或量化参数一定要做回归测试拿一组固定输入对比输出。我见过太多因为一个小改动导致精度悄悄下降的案例没有回归测试根本发现不了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →