做嵌入式这行十来年从裸机寄存器一路写到 Linux 驱动再到这两年折腾端侧 AI 部署我越来越觉得一件事很多人把顺序搞反了。大家一上来就追 Transformer、追大模型端侧落地结果连一个最基础的卷积层在 NPU 上为什么跑不满都说不清楚。我自己也走过这段弯路所以现在决定退回来老老实实从 CNN 重新开始梳理一遍。这不是怀旧而是因为 CNN 是理解端侧 AI 推理全链路最扎实的入口——它把算子、内存、量化、硬件加速这几件事全都串在一起了。这篇内容适合两类人一类是嵌入式工程师想往端侧 AI 方向转但被各种框架和术语绕晕了另一类是做算法的模型能训出来但一到板子上部署就抓瞎。我会从为什么选 CNN 作为切入点讲起把卷积的计算本质、端侧部署的真实约束、量化到底动了什么、NPU 和 CPU 的分工逻辑以及一个能跑通的实操路径全部拆开讲清楚。核心关键词就几个嵌入式、端侧 AI、CNN、NPU、模型量化。看完你至少能明白端侧 AI 不是把模型塞进板子那么简单它是一整套围绕算力和内存做的工程取舍。1. 为什么端侧 AI 的入口是 CNN 而不是大模型1.1 端侧的真实约束决定了技术选型先摆一个事实端侧设备和服务器最大的区别不是算力绝对值而是内存带宽和功耗预算。我在一块常见的嵌入式 AI 开发板上做过测试CPU 主频 1.5GHz 左右NPU 算力标称 1TOPS 级别内存 2GB 到 4GB。这个配置跑一个几百 MB 的大模型光是权重加载就能把内存吃满更别说推理时的中间激活值了。CNN 就不一样。一个典型的轻量级 CNN比如 MobileNet 系列或者自己设计的几层卷积网络参数量可以压到几 MB 甚至几百 KB。这意味着它能完整放进片上缓存或者紧耦合内存里推理时不需要频繁访问外部 DDR。这个差别在实际跑起来的时候非常明显同样一块板子跑 CNN 能做到几十毫秒一帧跑大模型可能几秒才出一个 token。所以从工程角度CNN 是端侧 AI 最现实的起点。它让你能把注意力放在推理链路本身上而不是被模型体积和内存管理拖垮。1.2 CNN 把端侧 AI 的核心问题全暴露了我为什么说 CNN 是最好的学习载体因为它一次性把端侧部署的所有关键问题都摆在你面前算子映射卷积、池化、激活这些算子怎么从框架图映射到硬件指令内存布局NCHW 还是 NHWC通道对齐到多少直接影响 NPU 的利用率量化误差float32 转 int8 之后精度掉多少哪一层最敏感硬件分工哪些层给 NPU哪些层回退到 CPU中间数据怎么搬这些问题在大模型上同样存在但大模型太复杂你很难定位到底是哪一环出了问题。CNN 结构清晰层数少你能一层一层地验证。我自己的做法是先用一个三到五层的简单 CNN 把整条链路跑通然后再逐步换成更复杂的结构。1.3 从 CNN 建立的能力可以平移有人会问现在都 2025 年了学 CNN 是不是过时了。我的看法恰恰相反。你在 CNN 上学到的量化方法、算子融合思路、内存复用策略换成 Transformer 一样用。区别只是注意力机制多了一些矩阵乘和 softmax但底层的 int8 量化、权重重排、NPU 指令调度这些逻辑是通的。我见过太多人直接上手部署大模型结果卡在量化精度上连怎么逐层分析误差都不会。如果先用 CNN 练过手知道怎么用校准集统计激活值分布知道 per-channel 和 per-tensor 量化的区别换到大模型上就是换个模型文件的事。2. 卷积到底在算什么把数学还原成硬件动作2.1 卷积的计算本质是乘加累加很多人对卷积的理解停留在滑动窗口做点积这个层面但要做端侧部署你得把它还原成硬件真正执行的动作。一个卷积层输入特征图是 H×W×Cin卷积核是 K×K×Cin×Cout输出是 H×W×Cout。对输出上的每一个点硬件要做的是输出点 Σ (输入窗口的每个元素 × 对应卷积核权重) 偏置这个求和次数是 K×K×Cin。举个例子3×3 卷积输入通道 32那每个输出点要做 288 次乘加。如果输出是 56×56×64那这一层就是 56×56×64×288 次乘加大约 5700 万次。这个数字直接决定了这一层在 NPU 上要跑多久。我习惯在部署前先算一遍每层的 MAC乘加次数这样心里有数知道哪一层是瓶颈。很多时候你以为网络很深所以慢其实慢的是某几个通道数特别大的层。2.2 数据复用是卷积优化的核心卷积有个很好的性质权重复用和输入复用。同一个卷积核会在整张特征图上滑动所以权重可以被反复使用同一个输入点也会被多个输出点用到。硬件加速器就是围绕这个复用做设计的。在 CPU 上我们靠缓存来利用复用在 NPU 上通常有专门的权重缓存和输入缓存。这就引出一个实操要点数据布局要和硬件的复用模式匹配。比如很多 NPU 要求输入是 NHWC 布局因为这样通道方向连续方便做向量化加载。如果你给的是 NCHW硬件可能要先做一次转置这个转置本身就要花时间。我在实际项目里踩过这个坑模型转换时没注意布局结果 NPU 利用率只有 30% 多后来改成 NHWC 并做好通道对齐直接拉到 70% 以上。这个差别不是算法问题纯粹是数据摆放的问题。2.3 感受野和通道数决定了计算量分布设计或分析一个 CNN 时我关注两个量感受野和通道数。感受野决定了这一层能看到多大范围的输入通道数决定了这一层的计算量。通常网络越往后空间尺寸越小通道数越大。这意味着后面的层虽然特征图小了但计算量未必小。举个实际例子一个典型的分类网络前面几层可能是 224×224×32后面变成 7×7×512。虽然后面空间尺寸只有前面的几百分之一但通道数涨了 16 倍算下来计算量可能还是后面几层占大头。所以在端侧做优化时不能只盯着前面的高分辨率层后面的高通道层同样要关注。理解了这一点你在做模型裁剪或者通道剪枝时就知道该往哪下手。我一般的策略是先保证前面几层的高分辨率特征提取然后在后面几层做通道压缩这样对精度的影响相对可控。3. 端侧部署的真实约束内存、带宽、功耗三座山3.1 内存墙比算力墙更致命在服务器上大家习惯说算力不够在端侧我更常遇到的是内存不够和带宽不够。一块嵌入式板子DDR 带宽可能只有几 GB/s而一次卷积推理要搬运的数据量可能就有几十 MB。如果每一层都要把中间结果写回 DDR 再读出来带宽立刻成为瓶颈。我做过一个对比测试同一个模型一种实现是每层算完写回内存另一种是把连续几层融合在一起中间结果留在片上。后者的推理速度是前者的两倍多。这就是为什么算子融合在端侧这么重要。常见的融合有 ConvBNReLU把三个算子合成一个中间不需要落内存。提示做端侧部署时先看你的推理框架支持哪些融合模式能融合的尽量融合这是性价比最高的优化。3.2 功耗预算决定了你能跑多久端侧设备很多是电池供电或者有严格的热设计功耗限制。NPU 满载跑和间歇跑功耗差别很大。我在做产品化的时候会特别关注能效比也就是每瓦能跑多少次推理而不是单纯看峰值算力。一个实用技巧是动态调频。不是所有帧都需要 NPU 满速跑如果场景允许可以降低频率换取更低的功耗。另外把不重要的层放到 CPU 上跑让 NPU 专注在卷积这种它擅长的密集计算上整体能效往往更好。3.3 实时性要求倒推模型设计端侧 AI 很多是实时场景比如摄像头每帧 33 毫秒的预算。这个预算倒推回来就决定了你的模型能有多大、能有多深。我一般的做法是先确定帧率和延迟上限算出每帧可用的计算预算根据预算反推模型复杂度和量化策略这个顺序很重要。很多人先设计模型再想办法部署结果发现根本跑不到实时。正确的做法是部署约束先行模型设计在约束内做。4. 模型量化int8 到底动了什么手脚4.1 量化的本质是仿射映射量化说穿了就是一个仿射变换。把浮点数范围 [min, max] 映射到整数范围比如 int8 的 [-128, 127]。公式很简单q round(x / scale) zero_point x ≈ (q - zero_point) * scale其中 scale 是缩放因子zero_point 是零点偏移。听起来简单但魔鬼在细节里。scale 怎么选是按整个张量选一个per-tensor还是按每个通道选一个per-channel结果差别很大。我的经验是权重量化用 per-channel激活量化用 per-tensor。因为权重的分布在不同通道间差异大per-channel 能显著降低误差而激活值在推理时是动态的per-tensor 更容易实现硬件支持也更好。4.2 校准集决定了量化精度量化不是拍脑袋定 scale而是要用一批真实数据跑一遍统计每一层激活值的分布这个过程叫校准。校准集的选择非常关键。我一般会从训练集或者真实场景数据里挑几百张有代表性的样本覆盖各种光照、角度、场景。踩过的坑有一次我用了一批偏暗的图片做校准结果模型在正常光照下精度掉得厉害。后来换成覆盖多种光照的校准集精度就回来了。所以校准集一定要贴近实际部署场景不能随便拿训练集的前几百张凑数。4.3 量化误差的逐层定位方法量化之后精度掉了怎么定位是哪一层的问题我的方法是逐层对比。把浮点模型和量化模型在同一个输入上跑逐层比较输出的余弦相似度或者均方误差。哪一层误差突然变大哪一层就是敏感层。对于敏感层有几个处理办法这一层保持浮点不量化混合精度调整这一层的量化范围用更精细的 scale如果这一层是激活敏感的考虑用 KL 散度校准而不是 min-max我遇到过一个网络最后那个全连接层对量化特别敏感单独把它保持浮点之后整体精度就恢复了。这种混合精度的做法在端侧很常见代价是这一层要回退到 CPU 跑。5. NPU 与 CPU 的分工谁该干什么活5.1 NPU 擅长的是规则密集计算NPU 的设计目标很明确高效执行卷积、矩阵乘这类规则、密集的计算。它的数据流是定制的适合大批量的乘加。但 NPU 不擅长的是控制流、不规则计算、动态形状。比如条件分支、循环次数不固定的操作NPU 处理起来很别扭。所以一个实用的分工原则是卷积、全连接、池化给 NPU预处理、后处理、非极大值抑制、动态逻辑给 CPU。这样各司其职整体效率最高。5.2 数据搬运是分工的隐藏成本NPU 和 CPU 之间搬数据是有成本的。如果频繁地在两者之间来回倒腾搬运开销可能超过计算本身。我在设计推理流水线时会尽量让数据单向流动CPU 做完预处理一次性交给 NPUNPU 把整个网络跑完再交给 CPU 做后处理。中间尽量不要打断。如果确实需要混合执行比如某些层 NPU 不支持那就尽量把这些层集中在一起减少切换次数。切换一次的开销可能比多跑几层还大。5.3 算子支持列表要先查清楚不同 NPU 支持的算子集不一样。有的支持 depthwise 卷积有的不支持有的支持特定尺寸的卷积核有的只支持 3×3 和 1×1。在选型或者设计模型之前一定要先拿到目标硬件的算子支持列表。我一般的流程是先看硬件支持哪些算子再设计或选择模型最后做转换和验证。如果反过来先定模型再看硬件很可能发现某个关键算子不支持只能回退到 CPU性能直接崩掉。6. 一条能跑通的端侧 CNN 实操路径6.1 从训练到部署的完整链路我把整条链路拆成几个阶段每个阶段都有明确的输入输出阶段输入输出关键动作模型训练数据集浮点模型用 PyTorch 或类似框架训练模型导出浮点模型ONNX 等中间格式固定输入形状简化图结构量化校准中间格式 校准集量化模型统计激活分布插入量化节点模型转换量化模型硬件可执行格式用厂商工具链转换板端推理可执行格式推理结果集成到应用验证精度和性能这个链路里模型导出和量化校准是最容易出问题的两步。导出时如果图结构没简化干净转换工具可能报一堆不支持的算子量化校准时如果数据不对精度直接崩。6.2 一个最小可跑通的 CNN 示例我建议从最简单的开始。下面是一个三层的 CNN输入 32×32 灰度图做十分类。这个规模足够小能在任何板子上跑起来又足够完整能覆盖卷积、池化、全连接、量化这些关键环节。import torch import torch.nn as nn class TinyCNN(nn.Module): def __init__(self, num_classes10): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 16, 3, padding1), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, 3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(32 * 8 * 8, 64), nn.ReLU(), nn.Linear(64, num_classes), ) def forward(self, x): x self.features(x) x self.classifier(x) return x这个网络参数量很小量化后大概几十 KB。先把它在板子上跑通测出单帧延迟再逐步换成更大的网络。这个过程能帮你把工具链、驱动、内存分配这些基础设施问题全部暴露出来。6.3 部署时最容易忽略的三个细节第一个是输入预处理的一致性。训练时图像怎么归一化的部署时必须一模一样。我见过因为归一化参数不一致导致精度掉一半的情况。均值、方差、通道顺序这些都要对齐。第二个是输出后处理的匹配。模型输出的 logits 还是 softmax 后的概率类别顺序是什么这些要和训练时一致。有时候转换工具会改变输出顺序一定要验证。第三个是内存对齐。很多 NPU 要求输入输出缓冲区按特定字节对齐比如 16 字节或 64 字节。如果没对齐可能直接报错或者性能下降。分配内存时用对齐的分配函数别用普通的 malloc。7. 踩过的坑和实测经验7.1 量化后精度崩了的排查顺序精度崩了别慌按这个顺序排查先确认浮点模型在板端跑的结果对不对。如果浮点就不对那是部署链路的问题不是量化的问题。检查校准集。是不是数据分布和实际场景差太远。逐层对比。找出误差最大的层。对敏感层做混合精度。保持浮点或者用更精细的量化。检查量化配置。per-tensor 还是 per-channel对称还是非对称。我遇到的大部分精度问题最后都落在校准集和敏感层这两点上。7.2 NPU 利用率上不去的常见原因NPU 利用率低通常不是 NPU 本身的问题而是喂给它的数据不对。常见原因有数据布局不对硬件要做额外转置通道数没有对齐到硬件要求的倍数算子被拆分得太碎每次计算量太小调度开销占比高频繁的 CPU-NPU 切换我一般的优化顺序是先调布局和通道对齐这两个改动成本低、收益大然后再考虑算子融合和减少切换。7.3 关于工具链选择的个人建议工具链这块我的建议是跟着硬件走。你用什么芯片就用它官方推荐的工具链。第三方工具链虽然通用性好但在特定硬件上的优化往往不如官方深入。另外工具链的版本要和驱动版本匹配。我踩过版本不匹配导致推理结果错误的坑排查了很久才发现是版本问题。所以环境搭建时把工具链、驱动、固件的版本都记录下来固定住。8. 从 CNN 出发下一步往哪走把 CNN 这条链路走通之后你会发现很多能力是可以直接迁移的。量化方法、内存优化、算子映射这些换成更复杂的网络结构一样适用。我的建议是先在 CNN 上把精度和性能的平衡这件事练熟知道怎么在有限的算力下把模型调到能用的状态。等你对量化误差、内存布局、硬件分工这些有了手感再去碰 Transformer 或者更大的模型就不会那么慌了。因为那时候你面对的只是新的算子而不是全新的问题。最后分享一个我自己的习惯每做一个新模型我都会先画一张表列出每一层的输入输出尺寸、计算量、量化敏感度、目标执行单元。这张表看起来麻烦但它能让你在出问题的时候快速定位。端侧 AI 这行很多时候拼的不是谁懂得多而是谁排查问题快。这张表就是我的排查地图。