FLops计算最全详解-理论分析

2025-11-08 00:16:44

@

目录1. 前言2. model.train()和model.eval()两种模式的原理与用法2.1 model.train()2.2 model.eval()2.3 计算模型的FLOPs应处于model.eval()模式3. 计算Pytorch中常见层的FLOPs3.1 Conv2d层(卷积层)3.2 Linear层(线性层或全连接层)3.3 ReLU层(激活函数)3.4 ReLU6(激活函数)3.5 BatchNorm2d层(归一化层)3.6 MaxPool2d层(最大池化层)3.7 AdaptiveAvgPool2d(全局平均池化层)3.8 GELU(激活函数)3.9 SiLU层(激活函数)3.10 Hardswish层(激活函数)3.11 Hardsigmoid层(激活函数)3.12 Sigmoid层(激活函数)3.13 StochasticDepth层3.14 Identity层(激活函数)3.15 SelectAdaptivePool2d(选择性池化)3.16 transformer层(多头注意力机制)3.16.1 前置知识3.16.2 transform主要计算过程3.17 LayerNorm层(归一化)3.17.1 LayerNorm和BatchNorm的区别3.17.2 LayerNorm层的计算量

1. 前言

全文15870 字数,码字不易,你的点赞、收藏、评论、打赏是我创作的最大动力!

转载请注明出处,违法必究!!!

本文主要介绍深度学习模型的FLOPs计算和Pytorch中常见层的FLOPs计算。

在此之前,我们先来理清一些容易混淆的概念。

FLOPS(Floating Point Operations per Second)

FLOPS 表示单位时间内的浮点运算次数,是衡量计算机系统或计算设备处理能力的一种指标。而浮点运算次数指的是乘法、除法、加法、减法等的次数。

FLOPs(Floating Point Operations)

FLOPs 指的是模型中的浮点运算操作总数,也常称为计算量。主要用于衡量模型的计算复杂度。通常是衡量模型在一次前向传播过程中需要执行多少次浮点运算的总量。

MACs, MAdds(Multiply–Accumulate Operations) :

MACs, MAdds表示乘加累积操作数,常常与FLOPs概念混淆,实际上1MACs包含1个乘法操作与1个加法操作,通常MACs与FLOPs存在一个2倍的关系。即FLOPs=2 * MACs。

在已发表论文中的实际情况,却是FLOPs、MACs(MAdds)这两个指标混着用(如Mobilenetv2和Shufflenetv2),没有明确的界限和规定。但可以确定的是,论文中的FLOPs或则MACs(MAdds)指的都是MACs(实际上它们的计算并不相同),即一个乘法操作与一个加法操作才算做一个浮点运算数(或者只考虑乘法)。(因为按照MACs的计算方法才可以和原论文中的FLOPs数值对上)。必须要注意的是,以上对于有关权重矩阵的运算是成立的,但有些激活函数等操作可能加法次数和乘法次数并不相等,这时把加法次数和乘法次数的总和当作总的FLOPs。总的来说,有关权重矩阵的层,此时:

FLOPs=2 * MACs,其他没有权重矩阵的层就按照FLOPs的定义来计算FLOPs。因此,下文中,若没有特别说明,FLOPs指的就是MACs。

对于指数函数exp(x),其浮点运算次数(FLOPs)取决于具体实现和平台。在通用的硬件上,指数函数的计算通常比较昂贵,涉及到约10至30次的FLOPs计算,因其不单单包含了多项式近似或其他提高效率的方法,也可能包含一个或多个除法和乘法操作。然而,不同编译器和库实现 exp 函数的效率可以有很大的差异。因此为了简便和统一计算,有关指数函数exp(x)如Gelu、SiLU等函数的FLOPs都忽略不计。(来源ChatGpt以及现有计算FLOPs的包均没有计算有关指数函数exp(x)的FLOPs)。或则把指数函数exp(x)看作是1次的FLOPs也是可行的,因为python中,该运算集成度很高。

2. model.train()和model.eval()两种模式的原理与用法

2.1 model.train()

在使用pytorch构建神经网络的时候,训练过程中会在程序上方添加一句model.train(),作用是启用Batch Normalization和 dropout。如果模型中有BN层(Batch Normalization)和Dropout ,需要在训练时添加model.train()。model.train()是保证 BN 层能够用到每一批数据的均值和方差。对于 Dropout,model.train() 是随机取一部分网络连接来训练更新参数。

2.2 model.eval()

model.eval()的作用是不启用Batch Normalization和Dropout。如果模型中有BN层(Batch Normalization)和Dropout,在测试时添加model.eval()。model.eval()是保证BN层能够用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变。对于Dropout,model.eval()是利用到了所有网络连接,即不进行随机舍弃神经元。

2.3 计算模型的FLOPs应处于model.eval()模式

在评估模式下,所有的Dropout会被禁用、Batch Normalization用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变。以确保它们不会改变其行为,导致对模型结构的误解,从而只有固定的网络架构参与 FLOPs 的计算,提供了一致的评估结果。总的来说,处于model.eval()下计算FLOPs是,Dropout等层忽略不计,Batch Normalization仍然需要计算。

参考链接:脚本之家、CSDN博客

3. 计算Pytorch中常见层的FLOPs

3.1 Conv2d层(卷积层)

卷积部分主要涉及的就是权重矩阵运算,核心算式为:y = wx + b,其中w是权重,b是偏执

先说结论,设卷积核大小为KK,组数为g,输入的形状为(B, Cin, Hin, Win),经过卷积之后的输出形状为(B, Cout, Hout, Wout),则FLOPs = (K^2 * Cin/g) * Cout * Hout * Wout。同时B(Batch_size)通常设为1。

标准卷积:

如上图所示,输入形状为(1, 5, 5),卷积核大小为 3x3,那么一次3x3的卷积(求右图矩阵一个元素的值)所需运算量:(3x3)个乘法+(3x3-1)个加法 = 17。要得到右图convolved feature (3x3的大小)所需的运算量:17x9 = 153。推广到一般的情况,当卷积核大小为KK,输入的形状为(B, Cin, Hin, Win),经过卷积之后的输出形状为(B, Cout, Hout, Wout),可得该卷积部分共需要:

K^2 * Cin * Cout * Hout * Wout次乘法 (1)

(K^2 * Cin - 1)* Cout * Hout * Wout次加法(2)

当考虑偏执项b所产生的计算量时,由于 b 只存在加法运算,输出特征中的每一个值做一次偏置项加法。因此,该卷积层在计算偏置项时总共包含的计算量为:

Cout * Hout * Wout 次加法 (3)

将该卷积层的 wx 和 b 两部分的计算次数累计起来,也就是结合(1)(2)(3)式可得:

K^2 * Cin * Cout * Hout * Wout次乘法 (4)

K^2 * Cin * Cout * Hout * Wout次加法 (5)

因此,标准卷积层的FLOPs = K^2 * Cin * Cout * Hout * Wout,其中组数g=1。

深度可分离卷积:

如上图所示,深度可分离卷积先深度卷积,再逐点卷积。在深度卷积中,每个输入通道通过各自独立的过滤器(卷积核)进行卷积操作。如果输入特征图的尺寸是 (Cin, Hin, Win) ,对于深度卷积,我们可以设定Cout个过滤器(Cin=Cout),每个过滤器的尺寸可以是K * K ,过滤器的深度为1,并且每个过滤器只在一个通道上操作。因此该部分的计算量FLOPs = (K^2 * Cin/g) * Cout * Hout * Wout。具体的推导推导过程不做赘述,详情可参考Mobilenetv1论文和博客链接。

综上所述,卷及部分的计算量FLOPs = (K^2 * Cin/g) * Cout * Hout * Wout。

3.2 Linear层(线性层或全连接层)

设输入X的尺寸为(B, Cin),权重矩阵W为(Cout, Cin),因此输出Y = X * WT(转置),性状为(B, Cout)。所以:

FLOPs = B * Cin * Cout,B=1时,FLOPs = Cin * Cout(推理过程和卷积部分类似)

3.3 ReLU层(激活函数)

该层没有真正意义上的加乘数,通常我们把一次比较记为一次浮点运算数。

ReLU(x) = Max(0, x)

设输入为(B, Cin, Hin, Win),每个元素都会和0进行一次比较,最后取两者的最大值作为最终输出,因此该层的计算量为:

FLOPs = B * Cin * Hin * Win

3.4 ReLU6(激活函数)

和ReLU函数类似,公式为:

ReLU(x) = Max(6, Max(0, x))

设输入为(B, Cin, Hin, Win),每个元素都会和0进行一次比较,再和6进行一次比较,最后取最大值作为最终输出,因此该层的计算量为:

FLOPs = 2 * B * Cin * Hin * Win

3.5 BatchNorm2d层(归一化层)

公式为:

γ和β初始化时默认为1和0。(详情参考Pytorch官网)

输入必须是4维的张量X,形状为(B, Cin, Hin, Win),而对四维张量进行归一化的步骤是: 对不同样本的同一个通道的所有像素进行求均值μ. 最后得到的均值是一个(1,a,1,1) 的张量, a为通道数量. 即对于每一个通道,都能计算出一个均值. 然后再求方差

, 最后进行变换得到输出。

现在我们求解每部分的计算量。

均值u:Hin * Win -1 次加法;1次乘法

(x - u)^2:Hin * Win 次加法;Hin * Win 次乘法

:Hin * Win -1 次加法;2次乘法。

:Hin * Win 次加法,Hin * Win次乘法

综上,该层共有4 * Hin * Win -2次加法;2 * Hin * Win + 2次乘法

因此,以乘法为例,忽略掉常数项,可得该层的FLOPs = 2 * B * Cin * Hin * Win

3.6 MaxPool2d层(最大池化层)

Max Pooling是一种常用的降维和特征下采样技术,它可以显著减少参数的数量和计算量,同时能够捕捉到重要的特征。MaxPool2d 层通常会使用一个取值窗口(通常是一个正方形或矩形),在输入的特征图上滑动窗口,并且对于每个窗口内的最大值作为输出。对于Max Pooling来说,由于它是一种选择性操作,实际上每经过一个窗口进行一次比较,把最大值提取出来。它的FLOPs计算方法为:

FLOPs = B * Cin * Hin/K * Win/K * K^2 = B * Cin * Hin * Win 。K不是卷积核大小,而是窗口的大小(例如,K = 2 表示2x2的窗口)。

3.7 AdaptiveAvgPool2d(全局平均池化层)

AdaptiveAvgPool2d(output_size=(1, 1))操作的会将输入张量转换为一个具有一个像素的输出张量,而忽略输入的具体尺寸。这个单像素输出代表了输入张量每个通道的平均值。由于输出尺寸为(1, 1),无论输入张量的尺寸如何,输出将只有一个像素,并包含输入张量所有通道的单一值。在这种情况下:

对于每个通道Cin,我们都将遍历输入张量的整个通道来计算平均值。

然后,除了计算平均值的操作以外,不再需要任何其他算术操作。

这意味着对于每个通道,我们需要对Hin * Win个像素值进行求和操作,共有Cin个通道,故该层的FLOPs为:

FLOPs = B * Cin * Hin * Win

3.8 GELU(激活函数)

(Pytorch链接)

GELU(Gaussian Error Linear Unit)是一种近年来流行的激活函数,利用高斯分布的特性来调整激活的程度。GELU 的公式定义如下:

FLOPs计算步骤:

近似公式:GELU的FLOPs估算只针对它的近似公式,因为近似公式通常在实践中更为高效。

变量运算:计算 GELU 涉及到的乘法、加法和指数函数。

指数函数:在近似公式中,由于有 tanh 函数内的 x + 0.044715x^3,可以分解为三次乘法,加上指数的及 tanh 函数的运算。

现在我们来计算GELU的FLOPs:

FLOPs = B * Cin * Hin * Win * (a +b + c)

其中:

a: 是tanh中所进行的操作数

b:是来自 tanh 的操作数

c:是和GELU的线性组合的所需的乘法。

对于近似公式,我们可以估算如下:

a = 5, b = 7 c = 3

综上,粗略计算该层的FLOPs = 15 * B * Cin * Hin * Win

以上计算是针对卷积网络的,且没有经过FLOPs包的验证。而对于transformer网络中GELU的FLOPs计算,应该采取如下的方式:

FLOPs = s * h + h

这里的s是tokens数量,h是当前每个s的维度。后面的+s我的理解是在结构中用到了残差连接(这点可能不对)。但按照该公式计算出来的FLOPs和用FLOPs包计算该层的FLOPs是一样的,因此该层FLOPs公式可以放心使用,只是后面的+h操作的真正含义有待斟酌。

(例如用包ptflops计算vit_b_16模型和deit_tiny_patch16_224模型中的GELU函数的MACs就是该公式计算的结果)

3.9 SiLU层(激活函数)

(Pytorch链接)

SiLU(Sigmoid线性单元),也称为 swish 激活函数,是在神经网络中使用的非单调激活函数。其数学表达式如下:

对于x的每个元素,1个指数运算、1个加法运算、1个除法运算和1个乘法运算是必要的。当输入为(B, Cin, Hin, Win)时,,SiLU的FLOPs可以估算为:

FLOPs = 4 * B * Cin * Hin * Win

(来源:ChatGpt)

3.10 Hardswish层(激活函数)

(Pytorch链接)

由Hardswish的定义可知,FLOPs有:

3次比较:对每个元素进行的比较运算,这需要3次(每个阈值-3和3一次,以及一个非线性比较一次)

1次加法:x 与 3 的加法

1次除法: 6 作为分母的除法

1次乘法:与本身的乘法

因此,一次的Hardswish函数涉及6次的计算量,当输入为(B, Cin, Hin, Win)时,FLOPs为:

FLOPs = 6 * B * Cin * Hin * Win

3.11 Hardsigmoid层(激活函数)

(Pytorch链接)

有Hardsigmoid的定义知,FLOPs有:

3次比较:对每个元素进行的比较运算,这需要3次(每个阈值-3和3一次,以及一个非线性比较一次)

1次加法:x与1/2的加法

1次除法:x与6的除法

因此,一次的Hardsigmoid函数涉及4次的计算量,当输入为(B, Cin, Hin, Win)时,FLOPs为:

FLOPs = 4 * B * Cin * Hin * Win

3.12 Sigmoid层(激活函数)

Pytorch链接

对于x的每个元素,1个指数运算、1个加法运算、1个除法运算是必要的。当输入为(B, Cin, Hin, Win)时,,SiLU的FLOPs可以估算为:

FLOPs = 3 * B * Cin * Hin * Win

(来源:ChatGpt)

3.13 StochasticDepth层

参考资料:论文、博客

随机深度stochastic depth,在训练阶段随机删除某些层使得网络的总层数变少,既缓解了梯度消失和特征重用减少的问题,又缩短了训练时间。此外和Dropout类似,stochastic depth还起到了正则化的作用。由于处于model.eval()评估模式下,禁用了该层,所以忽略掉该层的FLOPs。(该层在EfficientNetV1、EfficientNetV2论文中出现过)

3.14 Identity层(激活函数)

Identity 激活函数本质上是一个恒等变换,即通过该层的输入数据直接变成了输出数据,中间没有任何数学变换操作。数学表达式可以简写为:

Identity(x) = x

因此,该层的FLOPs是0。

3.15 SelectAdaptivePool2d(选择性池化)

注意:Pytorch中没有该层

(global_pool) SelectAdaptivePool2d(pool_type=avg, flatten=Flatten(start_dim=1, end_dim=-1))

如上式所示,池化类型为平均池化,平均池化的计算通常在数学上非常简单,其计算量基本与 H_out * W_out * C 成正比,其中 H_out 和 W_out 是池化层输出的高和宽。但是,在平均池化的上下文中,H_out 和 W_out 都是固定值(例如,你可能选择 1x1 以收紧整个特征图),这样可以大大简化计算。对应的 FLOPs:FLOPs = B * C * H_out * W_out,一般情况下,输出分辨率设定成 1 以使输出的每个通道包含整幅图片的信息(即全局池化)。即:

FLOPs = B * C * 1 * 1 = B * C。

Flatten 展平操作是一个在深度学习模型中用来将一个多维张量降维到二维张量的步骤。其实质是将除第一个维度(通常为批量大小 B )以外的所有维度压缩成一个维度,展平操作 (Flatten) 不需要任何计算,因为它仅仅是重新组织分配和存储方式。

来源:ChatGpt

在论文MixConv、FBNet均有出现

3.16 transformer层(多头注意力机制)

3.16.1 前置知识

在介绍之前,我们先来了解一下矩阵乘法,设矩阵A = [m, n],B = [n, p],则矩阵C= A * B = [m, p],该矩阵乘法共涉及到的乘加次数为:m * n * p,这里也许会有人认为该矩阵乘法涉及到的乘法次数是:m * n * p,加法次数是:m * (n - 1) * p,但在实际的网络结构中,需要考虑偏执(bias)的存在(核心算式为:y = wx + b,w是权重矩阵,b是偏执项),因此加法次数是:

m * (n - 1 + 1) * p = m * n * p。所以矩阵乘法的FLOPs = 2 * m * n * p,MACs = m * n * p。

3.16.2 transform主要计算过程

参数说明:

transformer 模型的层数为:L

隐藏层维度为: h

注意力头数为: n_h

词表大小为: v(vit中就是最后的分类结果1000)

批次大小为: b

序列长度为: s(tokens数量)

计算公式:

其中,x = (s, h),WQ=WK=WV = (h, h),Wo = (h, h),最后+x表示残差连接。另外,FLOPs 计算的主要是矩阵乘法,而加法、乘系数、dropout 计算量较小通常忽略不计。因此,FLOPs计算如下:

1. Attention层

2. MLP层

该层比较简单,两个Linear层即可,因此,FLOPs计算如下:

3. 分类层

综上所述,transformer层的总FLOPs = (24 * s * h^2 + 4 * s^2 * h) * L + 2 * s * h * v

总MACs = (12 * s * h^2 + 2 * s^2 * h) * L + s * h * v

注意:

完整的transformer包括Encoder和Decoder,这里只计算了Encoder部分,也就是Vision transformer模型的计算量。

这里没有提及批次大小(b),是因为我们在算计算量的时候,常把b = 1。

有人可能会有疑问,这里只计算了单头注意力机制的计算量,那多头注意力机制的计算量该如何计算呢?其实,单头注意力机制和多头注意力机制的计算量是完全一样的,我们来简单的推导一下:

多头注意力机制如下图所示:

计算公式如下图:

已知共有n_h(上图中的h,表示总头数)个头,输入的总维度是dmodel(也就是前文参数说明提到的h),则每个头的维度

dk、dv = dmodel / n_h;WQ/WK/WV = [dmodel , dk] 、[dmodel , dk]、 [dmodel , dv];WO = [n_h * dv, dmodel]

则有以下运算:

Atten部分:

Q = x * WQ = [s, dmodel] * [dmodel, dk] = [s, dk];K, V计算与Q一样,可得:K = [s, dk],V = [s, dv]

其中,FLOPs = 2 * 3 * s * dmodel * dk,MACs = 3 * s * dmodel * dk

又因为总共有n_h个头,则:

总FLOPs = 2 * 3 * s * dmodel * dk * n_h = 2 * 3 * s * dmodel^2;总MACs = 3 * s * dmodel^2

score = Q * KT(转置) = [s, dk] * [dk, s] = [s, s]

其中,FLOPs = 2 * s * dk * s,MACs = s * dk * s

又因为总共有n_h个头,则:

总FLOPs = 2 * s * dk * s * n_h = 2 * s^2 * dmodel;总MACs = s^2 * dmodel

x = score * V = [s, s] * [s, dv] = [s, dv]

其中,FLOPs = 2 * s * s * dv,MACs = s * s * dv

又因为总共有n_h个头,则:

总FLOPs = 2 * s * s * dv * n_h = 2 * s^2 * dmodel;总MACs = s^2 * dmodel

x = Concat(head1,....,headh) = [s, n_h * dv],拼接操作无计算量

x * WO = [s, n_h * dv] * [n_h * dv, dmodel] = [s, dmodel]

其中,FLOPs = 2 * s * n_h * dv * dmodel = 2 * s * dmodel^2; MACs = s * dmodel^2

将上述各个部分的总FLOPs和总MACs相加可得Atten部分:

总FLOPs = 8 * s * dmodel^2 + 4 * s^2 * dmodel

总MACs = 4 * s * dmodel^2 + 2 * s^2 * dmodel

惊喜的发现这个计算结果和前面我们计算单头注意力机制的结果是完全一样的。

由此可得出结论,单头注意力机制和多头注意力机制的计算量是完全一样的,在之后的计算中,无论是单头注意力机制,还是多头注意力机制,我们就直接按照单头注意力机制的方法来计算FLOPs和MACs。

MLP层和分类层的推理过程和Atten层是一样的,这里不再赘述。

最后我们再次重申transformer层的计算公式:

总FLOPs = (24 * s * h^2 + 4 * s^2 * h) * L + 2 * s * h * v

总MACs = (12 * s * h^2 + 2 * s^2 * h) * L + s * h * v

当然,这只是我们的理论推导,接下来我们以vit_b_16这个模型为例,验证我们的公式是否正确。

由vision transformer论文可知:

输入为(1, 3, 224, 224),L = 12,h = 768, v = 1000

由pytorch官网可知:

Pit论文可知:

vit_b_16模型的计算量是17.6B。

因此,由我们的公式可得:

MACs = (12 * 196 * 768^2 + 2 * 196^2 * 768) * 12 + 196 * 768 *1000 = 17.51B,结果对的上,偏差在接受范围内。

偏差的原因:

LayerNorm层的MACs没有计算

GELU层的MACs没有计算

Conv2d层的MACs没有计算

参考资料:

博客:知乎手推计算量FLOPS、transform中的FLOPs、vision transformer论文详解

3.17 LayerNorm层(归一化)

Pytorch链接

3.17.1 LayerNorm和BatchNorm的区别

它们的计算公式都是一样的,都是让该层参数稳定下来,避免梯度消失或者梯度爆炸,但计算方法含义却大不相同。

归一化的范围:

BatchNorm:标准化操作在整个批次(Batch)的数据上进行,同时应用到所有的特征通道。BatchNorm 计算每个特征通道的均值和方差,并使用这些统计量来进行归一化。

LayerNorm:标准化操作在同一特征通道的不同样本上进行,即对单个样本内的全部激活进行标准化。LayerNorm 在整个层的所有激活上计算均值和方差,然后进行归一化。

参数计算:

BatchNorm:在训练过程中学习每个特征通道的两个参数,γ(用于缩放)和β(用于偏移),这两个参数是可学习的并且对应该特征通道。

LayerNorm:通常使用一对固定参数,γ和β,用于所有特征通道的缩放和平移。

对批次大小的依赖性:

BatchNorm:对批次大小非常敏感。在小批次情况(如在实时训练或推断时),BatchNorm 可能表现不佳,因为均值和方差可能不是很准确地反映整个数据集的统计特性。

LayerNorm:不依赖于批次大小,对每个样本使用其自己的统计量进行标准化,因此它在任何批次大小下都能表现一致。

并行化的能力:

BatchNorm:不适合高度并行化,因为需要跨所有样本和特征通道收集统计数据。

LayerNorm:可以很容易地并行化,因为每个样本都是独立处理的。

适用领域:

BatchNorm:CV领域

LayerNorm:NLP和transformer领域

以上的解释不是很直观,我们通俗易懂的解释一下:

参考链接:博客

BatchNorm的理解:

来一张经典的图

上图画的是一个batch_size为N的图像特征张量。

BatchNorm把一个batch中同一通道的所有特征(如上图红色区域)视为一个分布(有几个通道就有几个分布),并将其标准化。这意味着:

不同图片的的同一通道的相对关系是保留的,即不同图片的同一通达的特征是可以比较的

同一图片的不同通道的特征则是失去了可比性

LayerNorm的理解:

来一张经典的图

上图画的是一个N个句子的语义特征张量。

如上图LayerNorm把一个样本的所有词义向量(如上图红色部分)视为一个分布(有几个句子就有几个分布),并将其标准化。这意味着:

同一句子中词义向量(上图中的V1, V2, …,VL)的相对大小是保留的,或者也可以说LayerNorm不改变词义向量的方向,只改变它的模。

不同句子的词义向量则是失去了可比性。

3.17.2 LayerNorm层的计算量

LayerNorm层的计算与前面BatchNorm层的计算类似,这里就不在推导,我们可以得出结论:

FLOPs = s * h + h

这里的s是tokens数量,h是每个s的维度。后面的+s我的理解是在结构中用到了残差连接(这点可能不对)。但按照该公式计算出来的FLOPs和用FLOPs包计算该层的FLOPs是一样的,因此该层FLOPs公式可以放心使用,只是后面的+s的真正含义有待斟酌。

(例如:用包ptflops计算vit_b_16模型和deit_tiny_patch16_224模型中的LayerNorm层的MACs就是该公式计算的结果)

码字不易,你的点赞、收藏、评论、打赏是我创作的最大动力!

转载请注明出处,违法必究!!!