CNN与ViT模型复杂度计算:FLOPs与参数量原理、公式推导与工程实践
1. 项目概述为什么我们需要亲手计算模型复杂度在模型优化和部署的日常工作中我们经常会遇到这样的场景产品经理拿着一个在云端跑得飞快的模型要求你把它塞进一个算力有限的边缘设备里或者你在对比两篇论文提出的新结构时光看准确率似乎不相上下但直觉告诉你其中一个“更轻巧”。这时候仅仅看Top-1准确率是远远不够的我们必须深入到模型的“体格检查”层面——也就是计算它的计算量FLOPs和参数量Parameters。这个项目标题“【指标原理】CNN和ViT的FLOPs、参数量计算”直指深度学习模型评估中最核心、最实用的两个效率指标。FLOPsFloating Point Operations浮点运算次数衡量的是模型执行一次前向推理所需的计算负担直接关联到推理速度和功耗参数量则反映了模型的大小关系到内存占用、存储成本以及过拟合的风险。亲手推导和计算这些指标绝不是纸上谈兵而是工程师进行模型选型、结构优化、部署评估的必备技能。尤其是当传统的卷积神经网络CNN与基于自注意力的视觉TransformerViT同台竞技时理解它们复杂度来源的根本差异能帮助我们在设计或选择模型时做出更明智的决策。本文将彻底拆解CNN和ViT中FLOPs与参数量的计算方法。我不会只给你几个最终的公式而是会从最基本的运算单元开始一步步推导让你明白每一个数字是怎么来的。我们会涵盖从标准的Conv层、全连接层到ViT中的多头自注意力MSA和前馈网络FFN模块。更重要的是我会分享在实际代码实现和论文复现中如何验证你的计算是否正确以及那些容易踩坑的细节。无论你是正在学习模型压缩的学生还是需要将模型部署到资源受限环境的工程师这篇文章都将提供一份可直接参考的“计算手册”。2. 核心概念辨析与计算基础在深入计算之前我们必须厘清几个关键概念避免后续产生混淆。很多讨论中这些术语被混用但它们在物理意义上截然不同。2.1 FLOPs vs FLOPS vs MACs别再傻傻分不清这是最容易混淆的一组概念甚至在一些论文中也会出现误用。FLOPs (Floating Point Operations)注意末尾的 ‘s’ 是小写。它指的是浮点运算次数是一个衡量计算量的绝对值。例如一次矩阵乘法包含的加法和乘法次数。我们通常说一个模型有 “3.0 GFLOPs”意思是它需要30亿次浮点运算。FLOPS (Floating Point Operations Per Second)注意末尾的 ‘S’ 是大写。它指的是每秒浮点运算能力是一个衡量硬件计算速度的速率单位。例如一块GPU的算力是 “10 TFLOPS”表示它每秒能进行10万亿次浮点运算。MACs (Multiply–Accumulate Operations)乘加运算。一次MAC通常指一次乘法加上一次加法a a b * c。在深度学习中卷积和全连接层的核心运算可以分解为大量的MACs。1 MAC 2 FLOPs一次乘法和一次加法。很多轻量化论文和部署框架如TensorRT喜欢用MACs来衡量计算量。重要提示在阅读不同资料时务必注意作者使用的是FLOPs还是MACs。本文后续的计算将统一使用FLOPs作为输出并在过程中明确区分乘法和加法以便你可以根据需要自行转换为MACs。2.2 参数量Parameters的本质参数量就是模型中所有需要被训练即可以通过梯度下降法更新的变量的总数。对于卷积层就是卷积核的权重和偏置对于全连接层就是权重矩阵和偏置向量对于归一化层如BatchNorm其可学习的缩放因子gamma和平移因子beta也是参数。参数量直接影响模型文件大小假设参数以32位浮点数float32存储则模型大小MB约为参数量 * 4 / (1024 * 1024)。训练时的内存占用除了参数本身优化器如Adam通常需要为每个参数维护额外的状态如动量这会占用数倍于参数量的内存。模型容量与过拟合风险参数量大的模型理论上拥有更强的拟合能力但也更容易在数据不足时过拟合。2.3 计算的基本假设与约定为了简化计算并聚焦于核心原理我们做出以下通用假设数据格式输入特征图、权重、输出特征图均使用浮点数float32进行计算。忽略激活函数ReLU、GELU等激活函数的计算量相对较小在估算总FLOPs时通常被忽略。但对于像Swish这样包含复杂运算的激活函数在精确计算时需要考虑。批量大小Batch Size为1我们计算的是单张图片进行一次前向传播所需的FLOPs。因为FLOPs与批量大小通常呈线性关系计算单张图片的复杂度更具通用性。输入输出维度我们用常见的符号表示。对于卷积层输入特征图形状为(C_in, H_in, W_in) 输出为(C_out, H_out, W_out) 卷积核大小为(K, K)。对于全连接层输入向量长度为M 输出为N。3. CNN核心模块的计算方法详解卷积神经网络是计算机视觉的基石其计算量主要集中于卷积层。理解它的计算过程是分析更复杂模型的基础。3.1 标准卷积层Conv2d的计算拆解一个标准的2D卷积操作可以看作是一个“滑动窗口”乘加过程。我们分参数和FLOPs两部分来计算。3.1.1 参数量计算对于一个卷积层其参数来源于卷积核权重每个输出通道都有一个独立的C_in * K * K的卷积核。共有C_out个输出通道。权重参数 C_out * C_in * K * K偏置项Bias通常每个输出通道有一个偏置值。这是可选的但默认情况下PyTorch/TensorFlow的Conv2d层会包含。偏置参数 C_out(如果biasTrue)因此总的参数量为Params_conv C_out * C_in * K * K C_out当biasTrue时示例输入通道C_in3(RGB图像)输出通道C_out64 卷积核K3 带偏置。 参数量 64 * 3 * 3 * 3 64 1728 64 1792。3.1.2 FLOPs计算FLOPs计算的是运算次数。一次卷积运算在一个位置为一个输出通道计算一个点包含乘法次数卷积核覆盖的输入区域有C_in * K * K个值每个值与卷积核中对应的权重相乘。所以乘法次数 C_in * K * K。加法次数完成C_in * K * K次乘法后需要将这些乘积相加得到该位置该输出通道的一个值。这需要C_in * K * K - 1次加法。再加上一个偏置项的加法如果存在总加法次数约为C_in * K * K为简化常将乘法和对应的加法视为一次MAC即2 FLOPs。那么为一个输出通道的一个输出点计算需要C_in * K * K次乘法和C_in * K * K次加法总计2 * C_in * K * KFLOPs。现在我们需要计算所有输出位置和所有输出通道输出特征图的空间大小H_out * W_out。H_out和W_out取决于输入尺寸、填充Padding、步长Stride和膨胀Dilation。公式为H_out floor((H_in 2*P - D*(K-1) -1) / S 1) 对于W同理。输出通道数C_out。因此一个标准卷积层的总FLOPs为FLOPs_conv H_out * W_out * C_out * (2 * C_in * K * K)如果考虑偏置加法还需要加上H_out * W_out * C_out次加法。但通常这部分占比极小可以忽略或合并到上述计算中即把2 * C_in * K * K近似看作包含了乘加和偏置加。简化记忆公式FLOPs_conv ≈ 2 * K * K * C_in * C_out * H_out * W_out示例延续假设输入图片尺寸224x224(H_inW_in224) 使用padding1, stride1保持尺寸不变则H_outW_out224。 FLOPs ≈2 * 3 * 3 * 3 * 64 * 224 * 224 2 * 9 * 3 * 64 * 50176 2 * 9 * 3 * 64 * 50176先计算3 * 64 192,192 * 9 1728,1728 * 50176 ≈ 86.7M 再乘以2得到约173.4 MFLOPs1.734亿次浮点运算。3.2 深度可分离卷积Depthwise Separable Conv的复杂度分析深度可分离卷积是MobileNet、EfficientNet等轻量化网络的核心它将标准卷积分解为两步极大地减少了计算量和参数量。3.2.1 深度卷积Depthwise Conv操作每个输入通道独立与一个K x K的卷积核进行卷积产生一个输出通道。输入输出通道数相等 (C_in C_out C)。参数量C * K * K每个通道一个卷积核无偏置简化计算。FLOPsH_out * W_out * C * (2 * K * K)。因为每个通道独立计算没有跨通道的累加。3.2.2 逐点卷积Pointwise Conv, 即1x1 Conv操作一个1x1的标准卷积用于融合深度卷积输出的各个通道的信息并改变通道数。参数量C_out * C_in * 1 * 1 C_out * C假设C_in C。FLOPsH_out * W_out * C_out * (2 * C)。3.2.3 总量对比将深度卷积和逐点卷积的参数量与FLOPs相加再与相同输入输出配置的标准卷积对比卷积类型参数量FLOPs (近似)标准卷积K*K*C*C_out2*K*K*C*C_out*H*W深度可分离卷积K*K*C C*C_out2*H*W*C*(K*K C_out)比例分析 参数量比约为(K*K*C C*C_out) / (K*K*C*C_out) 1/C_out 1/(K*K)当C_out较大如256且K3时比例约为1/256 1/9 ≈ 0.004 0.111 0.115。即参数量减少为原来的约11.5%。FLOPs比约为(K*K C_out) / (K*K*C_out) 1/C_out 1/(K*K) 与参数量比例公式相同。因此计算量也减少为原来的约11.5%。实操心得深度可分离卷积虽然高效但因其减少了通道间的密集交互有时会带来精度损失。在实际应用中MobileNetV2引入了倒残差结构和线性瓶颈EfficientNet则通过复合缩放来平衡深度、宽度和分辨率以弥补这一不足。在计算复杂度时务必按顺序先算深度卷积再算逐点卷积并注意特征图尺寸在两步中是否一致通常深度卷积会设置stride而逐点卷积的stride1。3.3 全连接层Linear/Dense与池化层3.3.1 全连接层全连接层可以看作是一种特殊的卷积层卷积核大小等于输入特征图大小。参数量M * N N(权重 偏置)。M是输入神经元数N是输出神经元数。FLOPs一次矩阵向量乘法包含M * N次乘法和M * N次加法累加加上N次偏置加法。总计约2 * M * NFLOPs。注意在CNN中全连接层往往参数量巨大例如VGG最后一个FC层有4096 * 4096 ≈ 16.7M参数是模型压缩如剪枝、量化的重点目标。现代网络如ResNet, ViT普遍使用全局平均池化GAP替代FC层极大减少了参数。3.3.2 池化层MaxPool, AvgPool池化层没有可训练的参数。FLOPs池化操作主要是比较MaxPool或求平均AvgPool。对于一个K x K的池化窗口MaxPool需要K*K - 1次比较运算比较通常不计入FLOPs但消耗时间AvgPool需要K*K次加法和1次除法。在粗略估算模型总FLOPs时池化层的计算量通常被忽略因为它远小于卷积层。4. ViT核心模块的计算方法详解视觉TransformerViT的结构与CNN截然不同其核心是自注意力机制。理解其复杂度计算关键在于理解矩阵乘法的规模。4.1 图像分块与线性投影Patch EmbeddingViT首先将图像分割成固定大小的块如16x16然后将每个块展平为一个向量。输入图像(H, W, C) 块大小P。块数N (H * W) / (P * P)。这也是Transformer的序列长度Sequence Length。线性投影每个展平后的块向量长度为P*P*C通过一个线性层全连接层映射到模型隐藏维度D。这个线性层就是Patch Embedding。计算参数量 (P*P*C) * D D(权重 偏置)。FLOPs N * 2 * (P*P*C) * D。因为要对N个块分别做线性投影。示例HW224, C3, P16, D768。N (224*224)/(16*16) 50176 / 256 196个块。 参数量 (256*3) * 768 768 768 * 768 768 590, 592。 FLOPs ≈196 * 2 * 768 * 768 196 * 2 * 589, 824 ≈ 231 MFLOPs。可以看到仅Patch Embedding一步计算量就已经不小。4.2 多头自注意力Multi-Head Self-Attention, MSA模块这是Transformer计算量和内存消耗的核心。我们逐步拆解。4.2.1 生成Q, K, V矩阵对于输入序列X(形状N x D) 通过三个不同的线性层生成查询Query、键Key、值Value矩阵。每个线性层参数量D * D D。三个线性层总参数量3 * (D * D D)。计算FLOPs生成Q/K/V各需要一次(N, D)矩阵与(D, D)权重的乘法。每次乘法的FLOPs约为2 * N * D * D。三项总计约6 * N * D * DFLOPs。4.2.2 计算注意力分数与加权和QK^T 计算(N, D) (D, N) - (N, N)。FLOPs约为2 * N * N * D。缩放与Softmax除以sqrt(D)是N*N次除法。Softmax计算涉及指数、求和、除法复杂度为O(N*N) 具体约为3 * N * N次运算。这部分在估算时常被计入但相对于矩阵乘法较小。注意力权重与V相乘(N, N) (N, D) - (N, D)。FLOPs约为2 * N * N * D。因此注意力核心部分的FLOPs主要来自两次N x N矩阵与向量的运算约为4 * N * N * D。4.2.3 多头与输出投影多头假设有h个头每个头的维度是D_h D / h。上述QKV生成和注意力计算是在每个头上独立进行的总计算量不变因为h * (N * D_h) N * D。但QK^T的计算变为(N, D_h) (D_h, N) - (N, N) 每个头的FLOPs为2 * N * N * D_h h个头总计2 * N * N * D 与单头一致。注意力加权和部分同理。输出投影多个头的输出拼接后N x D经过一个线性层映射回D维。参数量D * D D。FLOPs2 * N * D * D。4.2.4 MSA模块总复杂度参数量4 * D * D 4 * D近似忽略偏置细节。FLOPs主要项为6 * N * D * D(生成QKV输出投影) 4 * N * N * D(注意力计算)。关键洞察MSA的FLOPs由两项主导一项与序列长度N和隐藏维D的平方成正比 (N*D^2) 另一项与序列长度的平方和隐藏维成正比 (N^2*D)。当图像分辨率很高N很大时N^2*D项会成为计算瓶颈这也是ViT在处理高分辨率图像时计算开销巨大的原因。4.3 前馈网络FFN/MLP模块FFN通常是一个两层全连接层中间有一个激活函数如GELU。结构Linear(D - 4D) - Activation - Linear(4D - D)参数量第一层D * 4D 4D 第二层4D * D D 总计约8 * D * D。FLOPs第一层2 * N * D * 4D 8 * N * D * D 第二层2 * N * 4D * D 8 * N * D * D 总计约16 * N * D * D。4.4 ViT整体复杂度估算与CNN对比以一个经典的ViT-Base模型为例D768, h12, layers12 输入图像224x224 块大小16x16 则N196。Patch Embedding FLOPs: ~231 MFLOPs (如前所述)。一个Transformer Block:MSA FLOPs:6*N*D^2 4*N^2*D 6*196*589,824 4*38,416*768 ≈ 693M 118M ≈ 811 MFLOPs。FFN FLOPs:16*N*D^2 16*196*589,824 ≈ 1,850 MFLOPs。一个Block总计约2.66 GFLOPs。12个Blocks12 * 2.66 ≈ 31.9 GFLOPs。分类头通常是一个LayerNorm线性层计算量相对很小可忽略。ViT-Base总FLOPs约0.23 31.9 ≈ 32.1 GFLOPs。作为对比一个经典的ResNet-50模型的FLOPs大约为4.1 GFLOPs。ViT-Base的计算量是ResNet-50的近8倍这直观地说明了为什么早期的ViT需要在大数据集如JFT-300M上预训练才能发挥威力以及为什么后续研究如Swin Transformer, PVT致力于通过分层设计、局部注意力等方法来降低N^2项的计算复杂度。5. 实操验证使用工具计算与手动验证理论计算是基础但实际模型中可能包含跳跃连接、层归一化、Dropout等操作。我们需要借助工具进行整体评估并学会验证工具的准确性。5.1 使用流行工具库进行计算在Python中有几个优秀的库可以自动计算模型的FLOPs和参数量。5.1.1torchinfo(推荐)torchinfo是torchsummary的升级版提供更清晰、更详细的输出。import torch import torchvision.models as models from torchinfo import summary model models.resnet50() batch_size 1 summary(model, input_size(batch_size, 3, 224, 224))输出会包含每层的输出形状、参数量以及总的参数量和“Mult-Adds”即MACs。注意将MACs乘以2得到FLOPs。5.1.2thop(PyTorch-OpCounter)thop库可以直接计算FLOPs和参数量。import torch import torchvision.models as models from thop import profile, clever_format model models.resnet50() input torch.randn(1, 3, 224, 224) flops, params profile(model, inputs(input,)) flops, params clever_format([flops, params], %.3f) print(fFLOPs: {flops}, Params: {params})5.1.3fvcore(Facebook Research)fvcore是Meta AI提供的核心库功能强大。from fvcore.nn import FlopCountAnalysis, parameter_count model models.resnet50() input torch.randn(1, 3, 224, 224) flops FlopCountAnalysis(model, input) params parameter_count(model) print(fTotal FLOPs: {flops.total()}) print(fTotal parameters: {params[]}) # 注意参数访问方式5.2 手动验证与工具结果交叉检查完全依赖工具可能会掩盖细节。对于关键模块或自定义层手动验证是必要的。验证步骤隔离模块从模型中提取出你要验证的单个层如一个Conv2d层。获取真实输入输出维度用随机输入前向传播一次打印或记录该层的输入、输出张量形状。手动计算根据本章前面推导的公式使用记录的维度手动计算FLOPs和参数量。与工具对比使用thop.profile或fvcore对这个孤立层进行分析对比结果。示例验证一个Conv2d层import torch import torch.nn as nn from thop import profile # 1. 定义层 conv nn.Conv2d(in_channels64, out_channels128, kernel_size3, stride1, padding1, biasTrue) # 2. 创建输入 x torch.randn(1, 64, 56, 56) # (batch, C_in, H, W) # 3. 手动计算 C_in, C_out, K 64, 128, 3 H_out W_out 56 # 因为padding1, stride1 params_manual C_out * C_in * K * K C_out # 权重 偏置 flops_manual 2 * K * K * C_in * C_out * H_out * W_out # 简化公式 print(fManual - Params: {params_manual:,}, FLOPs: {flops_manual:,}) # 4. 工具计算 flops_tool, params_tool profile(conv, inputs(x,)) print(fTool - Params: {params_tool:,}, FLOPs: {flops_tool:,}) # 5. 比较 assert params_manual params_tool, fParams mismatch! {params_manual} vs {params_tool} # FLOPs可能因工具实现细节有微小差异但应在同一数量级 ratio flops_tool / flops_manual print(fFLOPs ratio (tool/manual): {ratio:.4f})注意事项不同工具对某些操作如BatchNorm、特定激活函数的FLOPs统计方式可能不同。有些工具会忽略这些操作有些会进行估算。对于精确对比最好统一使用同一种工具并了解其统计范围。手动计算是理解根本和验证工具可信度的最佳途径。5.3 计算中的常见陷阱与边界情况分组卷积Grouped Convolution如ResNeXt、ShuffleNet中使用。当groups 1时参数量和FLOPs都会减少为标准卷积的1 / groups。计算公式需将C_in和C_out替换为每组的通道数C_in/groups和C_out/groups 再乘以组数groups。最终FLOPs公式变为2 * K * K * (C_in/groups) * C_out * H_out * W_out。膨胀卷积Dilated Convolution膨胀率d不影响参数量卷积核大小不变但会影响感受野和输出特征图尺寸H_out, W_out的计算从而间接影响FLOPs。计算H_out/W_out时需使用公式H_out floor((H_in 2*padding - d*(K-1) -1) / stride 1)。转置卷积Transposed Conv常用于上采样。其FLOPs计算逻辑与标准卷积类似但输出尺寸更大。最可靠的方法是直接使用工具计算或仔细查阅框架文档理解其具体实现。BatchNorm层包含可学习的参数gamma, beta参数量为2 * C。在推理时BN层通常被折叠fold进前面的卷积层因此不产生额外的乘加运算。但在训练时它需要计算均值和方差会产生额外的计算量。大多数FLOPs计算工具默认统计的是推理时的计算量因此不计算BN的FLOPs。跳跃连接Skip Connection加法操作本身计算量很小通常忽略不计。但它要求相加的两个张量维度必须一致这有时会导致额外的卷积层如1x1卷积来调整维度这些调整层的计算量必须计入。6. 模型复杂度分析的实际应用与决策掌握了计算方法最终目的是为了指导实践。以下是几个关键的应用场景。6.1 模型选择与权衡精度 vs. 效率当面临多个精度相近的模型时复杂度分析是决策的关键。云端部署可能更关注精度对FLOPs和参数量有一定容忍度但更低的复杂度意味着更低的推理成本和延迟。移动端/边缘设备部署对FLOPs影响速度和功耗和参数量影响内存占用极其敏感。通常需要在精度上做出较大妥协选择MobileNet、ShuffleNet、EfficientNet-Lite等为移动端设计的架构。ViT vs CNN如前所述标准ViT的FLOPs远高于同性能的CNN如DeiT对标ResNet。但在数据充足、追求更高精度上限的场景下ViT可能是更好的选择。对于资源受限场景可以考虑混合架构如MobileViT或高效的ViT变体如Swin-T、PVTv2。决策框架明确部署目标的硬性约束如模型必须 5MB 推理时间 30ms。在满足约束的模型池中选择精度最高的那个。如果没有现成模型满足约束则需要对现有模型进行压缩剪枝、量化、知识蒸馏或神经架构搜索NAS。6.2 指导模型优化与压缩知道模型的“胖”在哪里才能有效地“减肥”。参数量过大往往源于全连接层或最后几个卷积层的大卷积核。优化方向用全局平均池化GAP替代FC层使用深度可分离卷积对大型卷积层进行剪枝。FLOPs过高通常源于早期的高分辨率特征图上的卷积操作或ViT中注意力机制的N^2复杂度。优化方向使用更高效的骨干网络如EfficientNet在ViT中使用局部注意力、池化注意力或线性注意力来降低序列长度N的影响降低输入图像分辨率需权衡精度。内存访问成本MAC除了FLOPs内存带宽也是瓶颈。例如组卷积Group Conv虽然减少了FLOPs但可能增加MAC。ShuffleNet通过通道重排Channel Shuffle来缓解这个问题。在分析时可以借助更专业的分析工具如NVIDIA的DLProf、PyTorch的Profiler来评估内存读写开销。6.3 论文复现与结果对比中的注意事项在阅读论文或进行复现时对复杂度的报告要保持警惕。统一标准确保你计算的FLOPs/Params与论文中报告的是同一种定义例如是否包含偏置是否包含BN层的参数是MACs还是FLOPs。最好能复现论文中的计算环境。输入尺寸FLOPs与输入图像尺寸强相关对比时务必确认论文使用的输入分辨率例如是224x224还是384x384。有时论文会报告“GFLOPs 224x224”来明确。只计算主干网络有些工作为了公平比较只计算主干网络Backbone的复杂度而不包含任务特定的头部如检测头、分割头。在对比时要清楚比较的范围。工具差异如前所述不同工具计算结果可能有细微差别。在复现时注明所使用的工具和版本是一个好习惯。6.4 一个完整的分析案例ResNet-50块与ViT块的对比让我们直观对比一个ResNet-50的Bottleneck块和一个ViT Transformer块。ResNet-50 Bottleneck (stride1)结构1x1 Conv (降维) - 3x3 Conv - 1x1 Conv (升维) 跳跃连接。假设输入输出为(56, 56, 256) 中间通道为64。计算简化Conv1 (1x1, 256-64): FLOPs ~2*1*1*256*64*56*56 ≈ 102.8MConv2 (3x3, 64-64): FLOPs ~2*3*3*64*64*56*56 ≈ 231.8MConv3 (1x1, 64-256): FLOPs ~2*1*1*64*256*56*56 ≈ 102.8M总计~437.4 MFLOPs。特点计算集中在3x3卷积上FLOPs与空间尺寸H*W和通道数C^2相关。ViT Transformer Block (D768, N196)结构MSA - FFN。如前计算一个块约2.66 GFLOPs。特点计算由N*D^2(线性投影) 和N^2*D(注意力) 两项主导。当N较大时注意力项占主导。对比结论在图像尺寸为224x224时一个ViT块的计算量约是一个ResNet Bottleneck块的6倍。这解释了为什么ViT需要更多的数据来训练以及为什么降低序列长度N如使用更大的patch size或分层下采样是优化ViT效率的关键。掌握CNN和ViT的FLOPs与参数量计算就像拥有了一个模型的“X光机”。它让你能超越表面的精度指标深入到模型设计的效率本质。从手动推导公式到利用工具验证再到将这些分析应用于实际的模型选型、优化和论文工作中这套方法论是每一位从事模型研发和部署的工程师的必修课。记住没有最好的模型只有最适合特定场景和约束的模型。而复杂度分析就是你做出这个关键判断的最有力依据之一。在实际工作中我习惯在模型设计初期就建立一个简单的复杂度估算表格这能帮助我快速排除那些在目标设备上根本不可行的结构把精力集中在最有希望的方向上。