MobileNet与ShuffleNet快在哪:FLOPs背后的硬件工程真相
1. 先搞清楚一个反直觉的事实FLOPs 不等于速度业界聊到轻量化网络好多人的第一反应就是看 FLOPs浮点运算量。我见过不少同学兴奋地拿着某个新模型的 FLOPs 曲线跟我说“你看这个模型运算量只有 MobileNet 的三分之一肯定快 3 倍” 但实际跑到手机上一测被打脸的情况非常常见——理论 FLOPs 降了 70%延迟只降了 20%甚至有时候反而更慢。为什么会这样因为 FLOPs 衡量的只是“你做了多少次乘加运算”但它完全没告诉你这些运算在真实硬件上是怎么被执行的。CPU、GPU、NPU 都有自己的指令流水线、缓存层级、内存带宽和并行调度策略。同样的运算量排布方式不一样实际跑起来的速度天差地别。MobileNet 这类模型之所以“快”核心原因不是简单的“计算变少了”而是它的计算结构恰好碰上了硬件能高效执行的模式。理解这一点你就不会被各种论文里的 FLOPs 数字牵着鼻子走了。这篇文章就用最直白的方式把 MobileNet 和 ShuffleNet 变快的底层逻辑拆开来看再看看什么情况下这套“轻量化套路”会失灵。适合正在做移动端部署、边缘设备推理或者想搞清楚模型加速原理的开发者参考。看完之后你应该能自己判断一个轻量模型在你的目标硬件上到底会不会快。2. 为什么 MobileNet 快深度可分离卷积的“算账”逻辑2.1 标准卷积的计算量到底花在哪里在讲 MobileNet 之前先得把标准卷积的计算量算笔明白账。假设输入是一个 H×W×C_in 的特征图H 是高度W 是宽度C_in 是输入通道数你用 C_out 个尺寸为 K×K 的卷积核去扫这个特征图每个卷积核要对输入的所有 C_in 个通道做 K×K 的加权求和。那么一次标准卷积的总乘加次数大约是FLOPs_standard ≈ H × W × C_in × C_out × K × K这里的推导逻辑不复杂输出特征图每个位置H×W 个位置都要做 C_out 次卷积操作每次操作要对 C_in 个通道每个通道取 K×K 个像素做乘加。很多人会忽略这个公式背后的含义——标准卷积的计算量是输入通道数和输出通道数的“乘积”关系而不是“叠加”关系。这意味着通道数一涨计算量是平方级的膨胀。用生活化的类比来说标准卷积就像一个全科医生每一个输出结果都要参考所有输入维度的信息。假设你有 64 个输入通道、64 个输出通道每个输出像素都要把 64 个输入通道全部“会诊”一遍才算得出来。这种全连接的会诊模式信息丰富但消耗极大。2.2 深度可分离卷积把“会诊”拆成“专科门诊”MobileNet 的核心改动就是把标准卷积拆成两步深度卷积Depthwise Convolution和逐点卷积Pointwise Convolution。深度卷积的做法是每个输入通道单独配一个 K×K 的卷积核只在空间维度上做卷积通道之间完全不交互。这一步的计算量是FLOPs_depthwise ≈ H × W × C_in × K × K注意这里没有 C_out 这个乘项了。每个输入通道只产生一个输出通道所以通道数的膨胀项被彻底去掉了。逐点卷积的做法是用 1×1 的卷积核对深度卷积的输出做跨通道的信息融合。这一步的计算量是FLOPs_pointwise ≈ H × W × C_in × C_out因为卷积核尺寸是 1×1所以 K×K 这一项直接消失了。总计算量加起来FLOPs_depthwise_separable ≈ H × W × C_in × (K × K C_out)和标准卷积相比加速比大约是FLOPs_standard / FLOPs_depthwise_separable ≈ (C_out × K × K) / (K × K C_out)以 K3、C_out64 为例这个比值大约是 (64×9)/(964) ≈ 7.9 倍。也就是说理论上深度可分离卷积可以把计算量降到标准卷积的八分之一左右。这正是 MobileNet V1 论文里最核心的卖点。但这里我必须泼一盆冷水这个 7.9 倍只是理论纸面上的计算量缩减实际加速比能到 2 到 3 倍就算不错了。原因后面第五节会详细展开。2.3 MobileNet V2 的倒残差结构为什么是对的选择MobileNet V1 把卷积拆开之后遇到了一个很实际的问题深度卷积本身能提取的空间特征有限而且 ReLU 激活函数在低维空间里会大量丢失信息。MobileNet V2 提出了倒残差结构Inverted Residual思路是先把特征图通过 1×1 卷积“升维”在更高维的空间里做深度卷积再用 1×1 卷积“降维”回来并且去掉降维之后的 ReLU改用线性激活。这个过程怎么理解你可以把升维想象成先把信息“摊开”到更大的画布上再加工避免在低维空间里被 ReLU “揉掉”细节降维完后用线性激活是为了保证低维表示的信息完整度。从计算量角度来算倒残差结构确实比 V1 的平铺结构要“贵”一点因为它多了升维和降维两次逐点卷积但它换来的是每个算力单元都用在更有效的特征表达上。实测下来V2 在同等 FLOPs 下精度比 V1 更高所以在移动端反而更实用。对这个结构我个人的经验是不要死记“MobileNet 就是深度可分离卷积”这句话V2 的关键是它重新分配了计算预算——把更多的 FLOPs 花在通道融合上而不是空间卷积上这个分配思路对后续很多轻量网络都有启发。3. ShuffleNet 的聪明之处通道分组与“洗牌”策略3.1 分组卷积是什么它和深度卷积的关系ShuffleNet 走了一条和 MobileNet 不太一样的路。MobileNet 靠的是极端的通道独立每个通道一个卷积核ShuffleNet 则用分组卷积Group Convolution来做折中。分组卷积的意思很直接把输入通道分成 G 组每组内部做标准卷积组与组之间老死不相往来。计算量公式变成FLOPs_group ≈ H × W × (C_in / G) × (C_out / G) × K × K × G H × W × C_in × C_out × K × K / G当 G 等于 C_in 时分组卷积就退化成了深度卷积当 G 等于 1 时就是标准卷积。所以你可以把 MobileNet 看成是分组卷积的一个极端特例。分组卷积的问题也很显然如果分组后组间完全隔离那信息只能在组内流动特征表达能力会受限。这就好比你把一个团队分成好几个小部门每个部门只跟部门内的人协作长此以往整个公司的信息就断层了。3.2 通道混洗低成本的“信息交换”ShuffleNet 的解决办法是通道混洗Channel Shuffle在分组卷积之后把不同组的通道重新打乱排列再送进下一层分组卷积。这样每个组在下一次卷积时就能接收到来自之前不同组的特征信息。通道混洗本身不涉及任何乘加运算只是一个张量重排的操作所以它带来的额外耗时几乎可以忽略不计但信息流通的效果却相当好。ShuffleNet V1 就是靠“分组卷积 通道混洗”这个组合拳在同等 FLOPs 下达到了和 MobileNet 接近甚至更好的精度。3.3 分组数 G 该怎么选一个参数量与信息流通的权衡分组数 G 是 ShuffleNet 里最重要的超参数。G 越大计算量降得越狠分母上的 /G 变大但每组内的通道数越少特征表达能力越弱。实际工程里G 一般取 2、4、8 这几个值G8 的时候计算量最小但精度也掉得最明显。我自己的经验是如果你的输入分辨率比较小比如 224×224 以下通道数本来就不富裕G 取 2 或 4 性价比最高如果输入分辨率大、通道数充足G8 的收益才会真正体现出来。选分组数时不要光看 FLOPs要看“每组的通道数”是否低于 16——低于这个值卷积输出的特征图基本就没有冗余度可言了精度会断崖式下跌。4. 变快的第二层逻辑减少内存访问与硬件友好性4.1 通道数翻倍的代价从“算得快”到“读写得慢”前面算的都是 FLOPs但真实硬件上把数据从内存搬到计算单元、再把结果写回去这个过程的耗时占比往往比你想象的大得多。有一个经典公式可以估算一个卷积层的内存访问成本Memory Access CostMACMAC ≈ H × W × C_in H × W × C_out K × K × C_in × C_out公式里第一项是读入输入特征图的开销第二项是写出输出特征图的开销第三项是读卷积核权重或中间结果的开销。你会发现一个反直觉的现象当 FLOPs 被控制住时通道数越大MAC 反而越高。因为 FLOPs 正比于 C_in × C_out 的总量但 MAC 正比于 C_in C_out加上权重项。换句话说你把通道数翻倍来压缩空间尺寸算力上不亏但内存搬运的开销会显著上涨。这也是为什么很多轻量网络在低分辨率输入下性能提升不明显的原因——计算量本身已经很小了内存访问成了大头。MobileNet 的深度卷积尤其受这个问题的困扰。深度卷积的 FLOPs 只有 H×W×C_in×K×K但它的 MAC 几乎是同数量级的 H×W×C_in 翻倍。也就是说深度卷积本质上是一个“内存搬运密集型”操作而不是“计算密集型”操作。你在 GPU 上跑深度卷积算力利用率可能连 30% 都不到因为计算单元大部分时间在等数据从内存里搬过来。4.2 为什么 1×1 卷积在 GPU 上是“香饽饽”逐点卷积1×1 卷积之所以被 MobileNet 和 ShuffleNet 大量使用除了计算量低还有一个很实际的硬件原因1×1 卷积在实现上可以转化为矩阵乘法GEMM而 GEMM 是几乎所有深度学习框架和硬件厂商深度优化的核心算子。GPU 上的 Tensor Core、NPU 上的矩阵加速单元都是为 GEMM 量身定做的。深度卷积就惨了它没法直接塞进 GEMM 的框架里只能走 im2col 加 GEMM或者用专门的深度卷积算子效率完全看硬件厂商有没有用心优化。早期的移动端推理框架跑 MobileNet 时深度卷积实现在的优化水平参差不齐同一个模型在不同框架里速度差一倍都不稀奇。这里有一个实操层面的判断标准如果你的目标平台是 GPU 或 NPU尽量把计算量集中在 1×1 卷积上如果是 CPU深度卷积反而可能受益于缓存机制表现没有想象中那么差。这也是为什么同一个轻量模型在不同硬件上排名会倒挂——不是模型变了是硬件的“擅长项”变了。4.3 张量排布NCHW 与 NHWC 的细节影响另外一个容易被忽略的细节是特征图的内存排布方式。NCHW 格式通道维度在第二维对 GPU 上的 GEMM 更友好NHWC 格式通道维度在最后一维对 CPU 的向量化指令更友好。MobileNet 这类深度卷积因为每个通道独立操作用 NCHW 时访问的是一整块连续内存效率更高但逐点卷积在 NHWC 下可以把通道方向上的乘加操作向量化吞吐量更好。所以你在 PyTorch 里默认用的 NCHW拿到端侧推理框架里可能被转成 NHWC这一个转换就可能让不同层的耗时比例发生明显变化。我踩过的坑是同一个 MobileNet V2 模型在某个框架里用 NCHW 跑深度卷积那几层特别慢切到 NHWC 之后整体延迟降了将近 20%。如果你们有性能调优的需求建议把数据排布的选项也纳入测试矩阵不要只看算子本身。5. 什么时候会受限轻量化网络的 4 个“失效场景”5.1 通道数太小的时候卷积的并行度崩盘MobileNet 和 ShuffleNet 的设计思路都高度依赖“通道数够多”这个大前提。深度卷积是逐通道独立操作的如果输入通道只有 16 个那计算单元就只有 16 路并行度可用。GPU 动辄几千个计算核心16 路并行对 GPU 来说简直是“杀鸡用牛刀”大量计算单元直接空转。举个实际场景如果你把一个 MobileNet V2 的宽度乘子width multiplier从 1.0 降到 0.25理论上 FLOPs 降到原来的 1/16但延迟往往只降到原来的 1/4 甚至更低。为什么因为通道数降到几十个之后算力利用率也跟着崩塌了计算量减了但硬件的吞吐能力没有被喂饱。我在实际项目里测过MobileNet V2 的宽度乘子 0.25 跑在 GPU 上性能和 FlashNet 之类的大模型比加速比远低于 FLOPs 比的预期就是因为这个并行度瓶颈。5.2 逐算子延迟成为瓶颈小算子多不等于快MobileNet 的结构拆得非常碎——每一层都是一个小的深度卷积加一个小的逐点卷积一个完整的模型可能有几十上百个这样的组合。每一层之间的数据传递、算子启动、框架调度的开销都在累积。标准卷积模型里一层大卷积算子可能占整个网络 20% 以上的耗时轻量网络里没有任何一个算子能占到 10%但每个算子的固定开销——比如核函数启动时间、特征图重排、内存分配——反而成了不可忽略的成本。打个比方一趟高铁从北京到上海只要 5 个小时但如果你要中途换乘 10 次每换一次等 40 分钟那总时间可能比直达还长。这个效应在 CPU 上还不算致命但在 GPU 和小型 NPU 上特别明显。如果你的部署环境是 GPU跑轻量网络时建议先用分析工具比如 Nsight Systems 或 TensorFlow Profiler看一下每层之间的“空隙时间”占了多少我见过极端情况下算子调度空隙能占到总耗时的 40%。5.3 内存带宽受限深度卷积在高端 GPU 上反而吃亏前面提到深度卷积是内存搬运密集型的操作。当你的输入特征图分辨率很大比如大尺寸输入图片深度卷积需要把整张特征图完整地读进来再写出去这个过程消耗的是内存带宽。高端 GPU 的计算吞吐能力很强但内存带宽的增长远比不上计算吞吐的增长所以深度卷积在这种硬件上就变成了带宽瓶颈算力再强也帮不上忙。反过来标准卷积因为计算密度高每个像素数据被反复重用多次每个输出像素都要所有输入通道参与所以更容易把算力喂饱。这就是为什么在高端 GPU 上一个精心优化过的标准卷积模型可能比同样是精心优化的 MobileNet 还要快。NVIDIA 和不少厂商做过类似的对比实验结论都指向同一个方向轻量化网络的设计初衷是服务算力有限、带宽也有限的移动端和嵌入式设备放回桌面级 GPU 上它的优势会被硬件特性抹平甚至逆转。5.4 框架和硬件的算子支持度不一同样的模型天上地下最后一个“隐藏限制”来自软件生态。标准的 3×3 卷积在主流框架里都是经过反复优化的“明星算子”每出一个新硬件厂商都会第一时间优化通用卷积。但深度卷积、分组卷积、通道混洗这些操作优化优先级就低多了。我在不同的几个推理框架里测过同一个 ShuffleNet V2 模型最快的框架比最慢的快了将近一倍。深度学习框架之间的算子实现质量差距巨大有的框架把深度卷积实现得相当高效比如做了融合和内存复用有的则直接退化成通用卷积来跑那深度可分离卷积的算力优势就全没了。所以选择轻量化模型的时候一定要先确认你的目标硬件和推理框架对深度卷积、分组卷积的支持程度。建议的做法是把模型在自己实际的部署环境里先跑一个基准测试benchmark不要只对比模型介绍页面上的 FLOPs 和 Paper 里的速度表格。Paper 里的数字往往是在特定框架、特定硬件、特定输入尺寸下测出来的换个环境数字就失真了。6. 怎么判断一个轻量网络在你这儿会不会快实操清单6.1 先跑“可复现的基准测试”不要直接信论文跨平台对比模型速度最稳的做法是搭一个统一的评测脚本。我自己常用的流程是固定输入尺寸和你的真实业务一致不要直接用 224×224。用同一个推理框架、同一个后端跑对比模型至少跑 50 次取平均延迟。分别记录 warmup 之后和 cold start 的延迟cold start 里通常包含模型初始化和内存分配的开销。如果可以分别测一下 FLOPs 和实测延迟放在一起看比例——如果某个模型 FLOPs 很低但延迟很高大概率是算子调度和内存访问拖了后腿。6.2 用“占用率映射”快速判断是否是带宽瓶颈一个比较实用的技巧跑模型的时候用硬件性能监控工具比如 NVIDIA 的 ncu、Android 上的 SimplePerf看当前算力利用率Compute Utilization和内存带宽利用率Memory Utilization。如果 Compute Utilization 低于 50% 而 Memory Utilization 接近饱和说明这个模型在你的硬件上是内存带宽瓶颈。这种情况下你不需要换模型调整输入分辨率或者合并输入输出通道效果可能比换一个 FLOPs 更小的模型更明显。6.3 什么时候值得选 MobileNet / ShuffleNet什么时候不值得根据我多年的部署经验给出一个很主观但很有用的判断矩阵部署环境是否推荐 MobileNet/ShuffleNet理由低端手机 CPU无 GPU 加速强烈推荐内存带宽相对宽裕深度卷积的缓存友好性发挥明显中高端手机 NPU推荐但需实测NPU 对 1×1 卷积优化好深度卷积要看厂商支持程度边缘设备树莓派级别推荐算力有限轻量结构的优势能体现出来中高端 GPU训练或服务端推理不推荐并行度和算子调度瓶颈导致优势被抹平低配嵌入式设备几百 MHz 的 MCU谨慎选择模型本身内存占用小是优势但分组/混洗操作会增加复杂度和代码体积这个表的结论并不是说 MobileNet 在 GPU 上就不能用而是说在这种情况下它未必是“最优解”。如果你手头的 GPU 算力相对内存带宽非常充足标准卷积的稠密计算反而更划算。7. 回到核心把 FLOPs 当成参考而非信仰MobileNet 和 ShuffleNet 变快的本质是通过改变卷积的计算结构降低了每个输出结果需要参与的乘加运算数量同时提高了内存访问的局部性。但这个“快”是有前提条件的——它要求硬件和框架对深度卷积、分组卷积这类算子有足够的优化支持要求输入通道数不至于太小导致并行度崩盘也要求内存带宽不是唯一的瓶颈。我在实际项目中养成了一个习惯每接触一个轻量化模型先不看它的 FLOPs 数字而是在目标硬件上把延迟、内存占用、功耗三个指标都测一遍再反推这个模型在结构上到底是“算力友好型”还是“内存友好型”。这么做过几个项目之后你会慢慢形成一种直觉看到某个模型的通道数和分组数就能大概判断出它在不同硬件上的表现走向。如果你正在做移动端模型选型建议从 MobileNet V2、MobileNet V3、ShuffleNet V2 三个型号入手在真实部署环境里各跑一轮基准测试把数据留档。后面再换硬件或者换业务场景这些基准数据就是你判断模型迁移成本最宝贵的参考。最后再多说一句模型加速这件事永远不要只盯着理论指标实测数据才是真正的裁判。