嵌入式转端侧AI:为什么我回头重学CNN,反而少走了三年弯路
发布时间:2026/10/26 12:35:48 锦皓数字建站

1. 为什么在端侧 AI 时代回头啃 CNN 不是走弯路很多人听说我在嵌入式岗位上干了几年之后反而掉头去把卷积神经网络从最底层重新学一遍第一反应都是你是不是搞反了方向。毕竟现在打开任何一个技术社区满屏都是大模型、Transformer、端侧部署量化推理这些词CNN 看起来像是上个时代的产物。但我自己踩过一圈坑之后越来越确信在嵌入式到端侧 AI 的这条路径上CNN 不是过时的东西而是绕不开的地基。先说清楚这个判断的背景。嵌入式开发和端侧 AI 部署本质上都受同一组约束支配算力有限、内存有限、功耗有限、实时性要求高。你在服务器上可以随手堆几百亿参数但在一个只有几百 KB RAM、主频几百 MHz 的 MCU 上或者在一个算力被严格限制的边缘设备上每一个乘加运算、每一次内存搬运都要精打细算。CNN 恰好是理解计算量从哪来、内存带宽怎么被吃掉、精度和速度怎么权衡这件事最直观的载体。它的结构足够简单简单到你可以在纸上把一层卷积的乘加次数算清楚又足够典型典型到几乎所有端侧推理框架的优化手段——算子融合、权重量化、内存复用、Winograd 变换——都是围绕它发展出来的。我决定重新从 CNN 开始还有一个很现实的原因端侧部署里 80% 的坑根子都在对模型计算过程的理解不够。你如果只是会调库、会跑现成的推理脚本一旦遇到精度掉点、推理变慢、内存溢出就完全不知道从哪下手。而当你亲手把一层卷积的前向传播、反向传播、量化误差都推导过一遍之后再看那些部署工具报出来的日志你会突然发现每一条都在说人话。这篇文章我想聊的不是CNN 是什么这种教科书内容而是站在一个从嵌入式转过来的人的角度把 CNN 和端侧 AI 之间那条真实的连接线讲清楚为什么嵌入式背景的人学 CNN 有独特优势、CNN 的核心计算到底怎么落到硬件上、量化为什么是端侧的生命线、以及从零搭一个能在端侧跑起来的小模型需要经历哪些环节。适合正在做嵌入式、想往端侧 AI 转或者已经在做端侧部署但总觉得根基不牢的朋友。2. 嵌入式背景学 CNN优势比你想的大得多2.1 你早就懂资源约束这门课做嵌入式的人有一个天然优势就是对资源约束有肌肉记忆。你写单片机代码的时候会下意识地关心这个变量占几个字节、这个循环会不会被编译器优化掉、这段代码放在 Flash 还是 RAM 里。这种思维习惯迁移到 CNN 上简直是降维打击。举个具体的例子。一个普通的卷积层输入特征图是 32×32×16卷积核 3×3输出通道 32。很多人第一反应是这不就是个矩阵乘法嘛但如果你有嵌入式背景你会立刻开始算这一层有多少次乘加输出特征图每个点要做 3×3×16 144 次乘加输出一共 32×32×32 32768 个点所以总共约 471 万次乘加。如果每个乘加按一次 MAC 算在只有几十 MHz 主频、没有硬件加速的 MCU 上这一层就要跑几十毫秒。这个数字一出来你就知道为什么端侧模型必须做通道剪枝、必须做量化了。这种先算账再动手的习惯是很多从纯算法转过来的人欠缺的。他们习惯性地认为算力是无限的直到部署的时候才发现模型根本跑不动。而你从一开始就会把算力预算当成设计约束这让你在设计模型结构时就有天然的克制。2.2 内存布局和访存模式嵌入式人一看就懂CNN 在硬件上跑得快不快很多时候不取决于计算量而取决于访存模式。这一点嵌入式开发者理解起来特别快因为你调过 DMA、写过缓存友好的代码、知道什么叫内存对齐。卷积的计算有个特点计算密度其实不高但访存很频繁。以 NHWC 和 NCHW 两种常见的内存布局为例同一个卷积在不同布局下的访存效率可能差好几倍。NCHW 布局下通道维是连续的做逐通道卷积时访存很友好但 NHWC 布局下空间维连续做 3×3 卷积时相邻像素的读取更连续。端侧推理框架之所以要针对不同硬件选择不同布局本质就是在优化访存局部性。我在实际调试一个端侧模型时遇到过这样的情况同一个模型只是把输入张量的内存布局从 NCHW 换成 NHWC在某个边缘设备上的推理时间就从 45ms 降到了 28ms。原因就是这个设备的缓存行是 64 字节NHWC 布局下连续读取的像素能更好地填满缓存行减少了缓存未命中。这种优化思路做过嵌入式内存优化的人几乎是本能反应。2.3 定点数思维让你对量化不再恐惧端侧 AI 绕不开量化而量化本质上就是把浮点运算换成定点运算。这件事对嵌入式开发者来说太熟悉了——你在 MCU 上做传感器数据处理的时候早就习惯了用 Q 格式定点数来避免浮点运算的开销。量化的核心是把 FP32 的权重和激活值映射到 INT8 的整数域。听起来复杂但如果你理解定点数的定标原理就会觉得这不过是换了个 scale 而已。一个 FP32 数值 x 量化成 INT8 的过程是先确定一个缩放因子 s然后 q round(x / s)反量化时 x q × s。这个 s 怎么选、怎么保证量化后精度损失最小就是量化算法的核心。而嵌入式开发者对精度和范围的权衡这件事早就在 ADC 采样、PWM 占空比这些场景里练过无数遍了。提示如果你之前没接触过定点数建议先花半天时间把 Q 格式比如 Q15、Q7的加减乘除和溢出处理搞清楚再回头看量化会发现概念是通的。3. 把 CNN 的计算拆到硬件层面才算真正理解它3.1 一层卷积到底在算什么很多人学 CNN 的时候脑子里是一张张漂亮的示意图但真到了部署阶段示意图帮不上忙。你需要把卷积拆成硬件能执行的原子操作。一个标准卷积层输入张量形状是 [H, W, C_in]卷积核是 [K, K, C_in, C_out]输出是 [H_out, W_out, C_out]。它的计算可以拆成四层循环遍历输出通道、遍历输出空间位置、遍历卷积核空间位置、遍历输入通道。最内层的操作就是一次乘加acc input[...] * weight[...]。这个四层循环的循环顺序直接决定了访存效率和能不能做并行。比如把输出通道放在最外层那么每个输出通道的计算可以独立进行适合多核并行把输入通道放在最内层那么内层循环可以连续读取输入和权重适合向量化指令。端侧推理框架里的im2col GEMM策略本质就是把卷积展开成矩阵乘法然后调用高度优化的矩阵乘法库这样就能复用成熟的 BLAS 优化。但 im2col 有个代价它会复制输入数据把内存占用放大 K×K 倍。在内存紧张的端侧设备上这个代价有时候不能接受。所以很多轻量级框架会选择直接卷积direct convolution虽然计算效率低一点但省内存。这就是一个典型的端侧权衡用计算换内存还是用内存换计算。3.2 感受野、步长、填充这些参数怎么影响端侧性能感受野、步长、填充这些概念在算法层面是理解模型表达能力的关键但在端侧部署层面它们直接决定了计算量和内存占用。步长stride是最直接的影响因素。步长为 1 的卷积输出空间尺寸基本不变步长为 2 的卷积输出尺寸减半计算量直接降到四分之一。所以在端侧模型里早期层经常用步长 2 来快速降分辨率减少后续层的计算量。但步长太大会丢失空间信息影响精度这个平衡点需要根据具体任务调。填充padding影响的是边界处理。SAME padding 会让输出尺寸和输入一致VALID padding 则会让输出缩小。在端侧padding 的处理方式会影响内存分配——SAME padding 需要在输入边界补零如果实现得不好会产生额外的内存拷贝。有些框架会通过隐式 padding来避免实际拷贝只在计算时判断边界这样能省下不少内存。感受野则是理解模型看到多大范围的关键。在端侧做目标检测或者语义分割时感受野不够大会导致模型无法理解全局上下文。但扩大感受野的手段——堆更多层、用空洞卷积、用大卷积核——都会增加计算量。所以端侧模型设计的一个核心矛盾就是在有限算力下怎么用最少的计算获得足够大的感受野。MobileNet 用深度可分离卷积、ShuffleNet 用通道混洗本质上都是在解这个矛盾。3.3 从乘加次数到实际延迟中间隔着什么理论上算出来的乘加次数MACs和实际推理延迟之间往往差着好几倍。这个差距来自哪里是端侧部署必须搞清楚的。第一是内存带宽。很多端侧设备的算力其实不弱但内存带宽是瓶颈。卷积层的计算密度每读一个字节能做多少次运算如果太低那实际性能就被内存带宽卡死而不是被算力卡死。这就是为什么深度可分离卷积虽然 MACs 大幅降低但在某些设备上加速比并没有理论值那么高——因为它的计算密度更低更容易受内存带宽限制。第二是算子启动开销。每个算子卷积、激活、池化在硬件上执行都有启动开销算子越多开销累积越明显。所以端侧推理框架会做算子融合把卷积 BN 激活融合成一个算子减少中间结果的写回和读取。这个优化在理论上不减少 MACs但实际延迟能降 20% 到 40%。第三是数据搬运。模型权重从 Flash 加载到 RAM、中间特征图在不同内存层级之间搬运这些都有开销。端侧设备的内存层级往往比服务器简单但 Flash 读取速度慢这个问题很突出。所以权重量化不仅是为了减少计算也是为了减少权重体积降低加载开销。影响因素对延迟的影响典型优化手段内存带宽计算密度低时成为主瓶颈提高计算密度、算子融合算子启动开销算子数量多时累积明显算子融合、图优化权重加载Flash 读取慢时突出权重量化、权重常驻 RAM缓存未命中访存模式差时严重调整内存布局、分块计算4. 量化端侧 AI 的生命线也是理解 CNN 的最佳切口4.1 为什么端侧几乎必然要量化在端侧设备上量化不是可选优化而是能不能跑起来的问题。原因有三层。最直接的是内存。一个 FP32 的权重占 4 字节量化成 INT8 只占 1 字节模型体积直接降到四分之一。对于一个几 MB 的模型这意味着能不能塞进设备的 Flash 里。很多低端 MCU 的 Flash 只有 512KB 到 1MB不量化根本放不下。其次是计算速度。INT8 乘加在大多数端侧硬件上比 FP32 快得多。有些 DSP 或者 NPU 有专门的 INT8 加速单元吞吐量是 FP32 的好几倍。即使没有专用硬件INT8 运算的功耗也更低这对电池供电的设备至关重要。第三是功耗。浮点运算的能耗远高于整数运算。有研究数据表明一次 FP32 乘加的能耗大约是一次 INT8 乘加的十几倍。在功耗预算严格的端侧场景量化带来的功耗下降是决定性的。4.2 训练后量化与量化感知训练怎么选量化有两条主要路线训练后量化PTQ和量化感知训练QAT。PTQ 是在模型训练完成之后用一批校准数据统计激活值的分布然后确定量化参数。它的优点是简单快捷不需要重新训练适合快速验证。缺点是精度损失可能比较大尤其是对那些激活值分布不均匀的模型。PTQ 又分动态量化和静态量化动态量化在推理时实时计算激活的量化参数灵活但开销大静态量化提前算好参数推理时直接用效率高但需要校准数据。QAT 是在训练过程中就模拟量化误差让模型学会适应量化。它的精度通常比 PTQ 好尤其是量化到 INT8 甚至更低比特时。代价是需要重新训练训练过程也更复杂要在前向传播里插入伪量化节点。我的经验是如果模型本身比较小、结构比较规整PTQ 通常够用如果模型对精度敏感或者要量化到 8 比特以下QAT 更稳。在端侧实际项目里我一般先用 PTQ 快速跑通流程看精度掉多少如果掉点在可接受范围内就直接用如果掉太多再上 QAT。4.3 量化误差从哪来怎么定位量化误差主要来自三个地方权重量化误差、激活量化误差、以及两者叠加后的累积误差。权重量化误差相对好控制因为权重是静态的可以提前分析分布选择合适的量化范围。激活量化误差更麻烦因为激活值是动态的不同输入的激活分布可能差别很大。如果校准数据的分布和实际推理数据不一致激活量化误差就会很大。定位量化误差我常用的方法是逐层对比把 FP32 模型和量化模型在同一个输入上的中间层输出都 dump 出来逐层算余弦相似度或者相对误差。哪一层的误差突然变大问题就出在哪一层。常见的原因是那一层的激活值有长尾分布或者有极端值把量化范围撑大了导致大部分值被压缩到很小的量化区间里。解决手段也有几种一是对那一层单独用更高的比特混合精度量化二是对激活值做截断把极端值裁掉牺牲一点精度换整体量化效果三是调整那一层的量化粒度从 per-tensor 改成 per-channel。注意per-channel 量化对权重的效果通常比 per-tensor 好很多因为不同输出通道的权重分布差异可能很大。但 per-channel 量化会增加推理时的开销因为每个通道要用不同的 scale。在端侧要权衡。5. 从零搭一个能上端侧的小 CNN完整实操链路5.1 先定约束再设计结构端侧模型设计的第一步不是画网络结构而是把约束列清楚。我通常会问自己几个问题目标设备的 RAM 有多大、Flash 有多大、有没有硬件加速单元、推理延迟要求是多少、精度底线是多少。这些约束定下来之后模型结构其实就八九不离十了。举个例子假设目标设备有 256KB RAM、1MB Flash、没有 NPU、要求单帧推理在 50ms 以内。那么模型参数量大概要控制在 100K 到 300K 之间中间特征图峰值内存不能超过 200KB。基于这些约束我会选择深度可分离卷积作为基本模块早期层用步长 2 快速降分辨率通道数从 16 开始逐步增加到 128 左右最后用全局平均池化代替全连接层来减少参数。这个设计过程不是拍脑袋而是每一步都有约束在背后撑着。通道数为什么从 16 开始因为第一层输入通道通常是 1 或 3输出通道 16 已经能提取足够的初级特征再大就浪费。为什么用全局平均池化因为全连接层的参数量通常是整个模型的大头去掉它能省下大量 Flash。5.2 训练阶段的几个关键设置端侧模型的训练和普通模型训练有些不一样的地方这些细节直接影响后面能不能顺利量化。首先是权重衰减。端侧模型通常比较小容易过拟合所以权重衰减要设得合理。但权重衰减太强会让权重分布过于集中反而不利于量化。我的经验是权重衰减设在 1e-4 到 1e-5 之间比较稳。其次是BN 层的处理。BN 层在训练时很有用但推理时会被融合进卷积。融合的前提是 BN 的参数已经稳定所以训练时要确保 BN 的移动平均更新充分。如果训练轮数太少BN 统计量不准融合后精度会掉。我一般会在训练后期冻结 BN 的统计量再跑几轮微调。第三是激活函数的选择。ReLU 是最省算力的但它的输出没有上界量化时不好确定范围。ReLU6 把输出限制在 0 到 6 之间量化友好得多这也是 MobileNet 系列用 ReLU6 的原因。如果对精度要求高可以用带参数的激活函数但要注意它们对量化的友好程度。5.3 量化实操从 FP32 到 INT8 的完整流程下面是我常用的量化流程以静态 PTQ 为例。第一步准备校准数据。校准数据不需要标注但分布要尽量接近实际推理数据。一般准备 100 到 500 个样本就够了。数据太少校准不准太多浪费时间。第二步插入观测节点。在模型里插入统计激活值分布的节点跑一遍校准数据收集每一层激活的最小值、最大值或者直方图。第三步计算量化参数。根据收集到的分布用 KL 散度或者最小化均方误差的方法确定每一层的 scale 和 zero_point。KL 散度方法会找一个截断点把长尾部分裁掉让量化范围更紧凑。第四步转换模型。把 FP32 的权重和激活都转成 INT8同时把卷积、BN、激活融合成量化算子。第五步验证精度。在测试集上跑量化模型对比 FP32 模型的精度。如果掉点超过阈值回到第三步调整量化策略。# 量化流程的伪代码示意 def quantize_model(fp32_model, calib_loader): # 1. 插入观测节点 model prepare_for_quantization(fp32_model) # 2. 跑校准数据收集分布 for batch in calib_loader: model(batch) # 3. 计算量化参数并转换 quantized_model convert_to_int8(model) # 4. 验证 acc_fp32 evaluate(fp32_model, test_loader) acc_int8 evaluate(quantized_model, test_loader) print(fFP32 acc: {acc_fp32}, INT8 acc: {acc_int8}) return quantized_model5.4 部署到端侧后的性能调优模型量化完、转换完只是走完了一半。真正部署到设备上之后还有一轮性能调优。首先要确认推理框架用的是不是针对目标硬件优化过的版本。同一个模型用通用框架和用厂商优化过的框架性能可能差好几倍。厂商的框架通常会针对自家硬件的缓存大小、DMA 特性、加速单元做专门优化。其次是内存规划。端侧推理框架一般会做内存复用把不同层的中间特征图分配到同一块内存上因为它们的生命周期不重叠。但内存复用算法有好有坏好的算法能把峰值内存压到理论下限附近。如果发现内存占用比预期高可以检查一下框架的内存规划策略。第三是线程和并行。如果设备是多核的要确认推理框架有没有用上多核。有些框架默认单线程需要手动开启多线程。但多线程也不是越多越好线程太多会有调度开销在核数少的设备上反而变慢。最后是输入预处理和后处理。这两部分经常被忽略但在端侧它们可能占总延迟的很大比例。比如图像 resize、归一化、NMS 这些操作如果实现得不好可能比模型推理本身还慢。我遇到过预处理占了总延迟 40% 的情况后来把 resize 换成硬件加速才解决。6. 那些只有踩过才知道的端侧 CNN 坑6.1 精度掉点不一定是量化的锅有一次我量化完一个模型精度掉了 5 个百分点第一反应是量化策略有问题调了半天量化参数都没用。后来逐层对比才发现问题出在 BN 融合上——训练时 BN 的移动平均没更新充分融合后统计量偏移导致精度掉。重新训练并冻结 BN 统计量之后量化精度只掉了 0.5 个点。这个坑让我养成了一个习惯精度掉点先查 BN 和算子融合再查量化本身。因为 BN 融合是确定性的数学变换如果实现正确不应该掉精度一旦掉了说明 BN 参数本身有问题。6.2 校准数据的分布比数量重要另一个常见的坑是校准数据选得不对。我见过有人用训练集的前 100 张图做校准结果这 100 张图恰好都是某个类别的激活分布严重偏斜量化后其他类别的精度惨不忍睹。校准数据的核心要求是分布覆盖不是数量。100 张覆盖各类别、各场景的图比 1000 张同质化的图效果好得多。如果实在不确定可以用聚类的方法从训练集里挑代表性样本。6.3 端侧设备的标称算力要打折扣设备规格书上的算力数字和实际能跑出来的性能往往差很远。标称 1 TOPS 的设备实际跑 CNN 可能只能发挥 20% 到 30%。原因很多内存带宽跟不上、算子支持不全、调度开销大。所以选型的时候不要只看标称算力要看实际跑目标模型的 benchmark。如果厂商提供了 benchmark 数据要看它跑的是什么模型、什么精度、什么 batch size。条件允许的话最好自己拿目标模型实测一下。6.4 别忽略数值稳定性端侧量化模型在极端输入下可能出现数值溢出。比如某个激活值在训练集里最大是 10量化范围定到 10但实际推理时来了一个激活值 15 的输入量化后就溢出了输出完全错误。解决办法是在确定量化范围时留一点余量或者用饱和处理。但余量留太多会浪费量化精度这个平衡要根据实际场景调。如果应用场景输入可控余量可以留小一点如果输入不可控余量要留足。7. 回头看这条路该怎么走从嵌入式转到端侧 AI再回头把 CNN 重新啃一遍我最大的体会是这两件事本来就是一件事。嵌入式的资源约束思维、定点数思维、访存优化思维和端侧 AI 的量化、算子融合、内存规划底层逻辑是通的。CNN 只是把这些思维应用到了一个具体的计算模式上。如果你也在走类似的路我的建议是不要急着去追最新的模型结构先把一个最简单的 CNN 从训练到量化到部署完整走一遍。走完之后你对端侧 AI 的理解会比看十篇综述都扎实。具体来说可以拿一个经典的图像分类任务自己搭一个几层的小 CNN训练到能用的精度然后量化、转换、部署到一个真实的端侧设备上测延迟、测内存、测精度。这个过程中遇到的每一个问题都是真正长本事的时刻。至于后续往哪个方向深入我的看法是先把 CNN 这条线走透再去看 Transformer 在端侧的部署。因为端侧 Transformer 的很多优化手段——量化、算子融合、内存复用——都是从 CNN 时代积累下来的。地基打牢了上面盖什么楼都稳。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。