神经网络推理 算子拆解:卷积、GEMM、Attention 谁吃掉了算力
更新时间:2026-09-25。本文是「集成电路大类 · 芯片设计」**入门层(beginner)的一篇新正文。上一篇 AI 加速器入门说神经网络的绝大部分运算是海量乘加(MAC),这一篇再往前走一步:打开网络结构,看看这些乘加到底分布在哪些算子(operator)**里。结论可能出乎意料——一个有上百层的网络,真正决定性能的算子只有三五类。
本文要回答的问题
- 一个几十上百层的网络,本质上由哪几类算子组成?
- 卷积、全连接、池化、激活、归一化,各自吃掉多少 FLOPs?
- Transformer 里的 Attention 在计算结构上和卷积有什么不同?
- 什么是算术强度?为什么卷积/GEMM 是 compute-bound,而激活/归一化是 memory-bound?
一、层的名字很多,算子的种类很少
看一个现代网络的结构描述,层的名字五花八门:Conv2d、BatchNorm、ReLU、MaxPool、Linear、Softmax、LayerNorm、GELU、MultiheadAttention……名字可以列半页纸。但如果按「计算结构」归类,它们只对应少数几类算子:
- 卷积(Convolution, Conv):滑动窗口内做乘加,视觉网络的主体;
- 全连接 / 矩阵乘(Fully Connected / GEMM):输入向量与权重矩阵相乘;
- 注意力(Attention):Query/Key/Value 三组矩阵乘加一次 softmax 加权,Transformer 的核心;
- 池化(Pooling):窗口内取最大或平均,如 MaxPool、AvgPool;
- 逐元素运算:ReLU、GELU、加法(残差连接)、缩放偏置等,一个元素对应一次简单运算;
- 归一化(Normalization):BatchNorm、LayerNorm,本质是减均值、除标准差的逐元素公式。
推理框架(TensorRT、ONNX Runtime、各家 NPU 的编译器)拿到模型后的第一件事,就是把几百个节点计算图归并成这几类标准算子,然后分别调用针对底层硬件手工优化过的 kernel。理解了算子,就理解了 90% 的推理性能讨论。
这里先澄清一个用词:**推理(inference)指用训练好的网络对新数据做一次前向计算,比如拍一张照片让网络判断是不是猫;与之相对的训练(training)**还要反向传播梯度、更新权重,计算量约为推理的 2~3 倍。本文所有 FLOPs 数字都针对推理单条样本。另外,算子和「层」不是一一对应:一个 Conv-BN-ReLU 结构在网络描述里是三层,落到硬件上往往只执行一个融合 kernel;反过来一个注意力层内部包含多个不同算子。所以看性能要数算子,而不是数层。
二、一张图看懂 FLOPs 落在谁身上
衡量一类算子「吃掉多少算力」,通用指标是 FLOPs(Floating Point Operations,浮点运算次数)。下图以经典视觉网络 VGG-16(输入 224×224 图像)为例,每格代表约 10% 的总 FLOPs。
VGG-16 一次推理约 15 GFLOPs,其中卷积约占 91%。这个比例不是个例:早期和经典 CNN 的共同特征就是卷积一家独大。
这里有一个常见的认知反转:VGG-16 的参数量分布和 FLOPs 分布几乎是倒过来的——卷积层占约九成的计算却只占约一成的参数,三个大 FC 层参数超过 1.2 亿(占全网约 86%),计算只占 9%。原因是 FC 层每个权重只对一个输入元素使用一次、没有复用,参数多但复用少。后来 ResNet 用全局平均池化加一个小 FC 取代巨大的 FC 层,参数大幅下降而精度不降,成为视觉网络的标准做法。记住这个区分:参数量决定模型占多少存储/显存,FLOPs 与算术强度共同决定推理耗时,两者不是同一张分布图。换几个有代表性的网络看:
| 网络 / 任务 | Conv | GEMM(含 FC/Linear) | Attention | 池化 + 激活 + 归一化 |
|---|---|---|---|---|
| VGG-16(图像分类) | ~91% | ~9%(FC 层) | 无 | <1% |
| ResNet-50(图像分类) | ~99% | ~1% | 无 | <1%(常被融合掉) |
| MobileNetV2(端侧视觉) | ~80%(含 Depthwise) | ~20%(1×1 Conv 本质是 GEMM) | 无 | <1% |
| BERT-Base(NLP,短序列) | 无 | ~70%(QKV/FFN 投影) | ~25% | ~5% |
| GPT 类(长序列生成) | 无 | ~50–65% | ~30–45%(序列越长越高) | ~5% |
读这张表有三个要点:第一,视觉网络优化卷积就优化了几乎全部;第二,1×1 卷积(MobileNet、ResNet 瓶颈结构里大量存在)在计算上就是 GEMM,所以现代网络里 GEMM 的实际比重比表面看上去高;第三,Transformer 的 Attention 占比随序列长度上升,因为它的计算量随序列长度平方增长(见第四节)。
还要注意 FLOPs 和「墙上时间」不是一回事。同样 1 GFLOPs 的卷积和 1 GFLOPs 的 ReLU,在同一颗芯片上耗时可能差几十倍:前者能把 MAC 阵列喂到接近峰值,后者的时间全耗在读写内存上。FLOPs 回答的是「有多少活」,算术强度(第五节)回答的是「这些活能不能高效干完」。另外精度也会改变账目的绝对值:INT8 推理一次乘加算 1 OP,FP32 同样算 1 OP,但 INT8 的数据搬运量只有四分之一、峰值 TOPS 通常高 2~4 倍,所以同一网络从 FP32 换到 INT8,FLOPs 数字不变,实际延迟却常常减半还多。
三、卷积与 GEMM:为什么它们是「计算密集」
卷积层做的事可以一句话说完:输出特征图上的每个点,等于输入特征图上一个小窗口(如 3×3)与卷积核逐元素相乘再求和。一个 3×3、输入输出各 64 通道的卷积层,单个输出点就要做 3×3×64 = 576 次乘加,而一张特征图有几万到几十万个输出点。
它的 FLOPs 估算公式是:
FLOPs ≈ 2 × H_out × W_out × C_in × C_out × K_h × K_w
(系数 2 是因为每次乘加算 1 次乘法 + 1 次加法。)
拿 ResNet-50 里一个有代表性的 3×3 卷积层(输入 56×56、256 通道进、256 通道出,步长 1)代入:输出尺寸 56×56,参数总量约 256×256×3×3 ≈ 59 万个,而 FLOPs 约 2 × 56 × 56 × 256 × 256 × 9 ≈ 37 亿次。注意这里的对比:权重只有约 60 万个(FP16 下约 1.2 MB),却撑起了 37 亿次运算,平均每个权重被复用了约 3000 次。这就是卷积计算密集的来源。
关键在于数据复用:同一个卷积核权重在整张特征图上滑动,被复用成千上万次;同一个输入窗口又被相邻输出点重叠使用。一个权重从内存读进来,可以参与几百上千次乘法。复用方式可以细分为权重复用、输入复用、部分和累加复用三种,不同网络形状适合不同的复用策略。工程上常把卷积通过 im2col 变换成 GEMM(General Matrix Multiply,通用矩阵乘):把每个滑动窗口摊平成矩阵的一列,卷积就变成两个大矩阵相乘,从而复用几十年积累下来的矩阵乘极致优化(分块、打包、SIMD);全连接层更是天生就是矩阵乘。这也是卷积和 GEMM 能在 MAC 脉动阵列上高效跑起来的根本原因——具体的数据流设计(权重驻留、输出驻留等)见 数据复用与 Weight Stationary。
四、Attention:GEMM 之外多了一个「平方项」
Transformer 的一个注意力头计算可以写成:
Attention(Q, K, V) = softmax(Q × Kᵀ / √d) × V
拆开看,Q/K/V 的生成投影、Q×Kᵀ、与 V 相乘、以及 FFN 前馈层,主体仍然是 GEMM。真正「新」的东西有两处:
- Q×Kᵀ 的矩阵规模是 S×S(S 是序列长度),计算量随序列长度平方增长。序列从 1K 涨到 8K,注意力部分的 FLOPs 涨约 64 倍,而线性投影只涨 8 倍。长上下文场景下 Attention 由此成为主导。
- softmax 是逐行归约 + 逐元素指数运算:要先求一行最大值、求指数、求和、再相除。它不产生多少 FLOPs,但夹在矩阵乘之间,破坏了「一路 GEMM 到底」的规则性,需要专门的 kernel 设计,详见 Transformer Attention Kernel。
softmax 的麻烦不在运算种类,而在归约(reduction)这种跨元素操作:求一行最大值时,每个部分和都依赖前一个元素,天然的并行链比矩阵乘短得多;而且同一行数据要被读两到三遍。FlashAttention 等现代 kernel 的核心思路就是不把 S×S 的注意力分数矩阵写回 HBM,而是分块留在片上 SRAM 里,边算 softmax 边乘 V——计算量几乎不增加,却省掉了整张中间矩阵的读写。这再次印证:Transformer 的性能优化,一半在 GEMM,一半在怎么让中间结果不落地。
所以更准确的说法是:Transformer = 大量 GEMM + 一个随序列变长而凸显的 S×S 注意力项 + 少量不规则的 softmax/LayerNorm。
给一组具体数字建立直觉:BERT-Base(12 层、隐藏维 768、序列长 512)整网单条约 22 GFLOPs,其中 QKV 投影、输出投影和 FFN 两层线性变换这些 GEMM 占约七成,注意力分数计算约占两成多。自回归生成还有一个容易被忽略的特征:每生成一个 token 都要重跑一遍所有层,而 KV 可以缓存(KV Cache)——有了缓存后,每步新 token 只对历史 K/V 做一次 GEMM,避免重新计算,代价是缓存容量随上下文线性增长,长对话时显存压力很大。这也是为什么大模型推理芯片除了拼算力,还要拼 HBM 容量和带宽。
五、池化、激活、归一化:FLOPs 很少,但并不「免费」
再看另一类算子。ReLU 对每个元素只做一次比较(负数清零);MaxPool 在 2×2 窗口里取最大值,平均每输出元素约 3 次比较;BatchNorm 推理时公式是 y = x·γ/√(σ²+ε) + (β − μ·γ/√(σ²+ε)),括号里是常量,每个元素只剩一次乘法加一次加法;LayerNorm 略重,要现场算均值方差,但也只是几遍逐元素扫描。
它们的共同特征是:每个元素只摊到一两次运算。在 FLOPs 统计表里它们合计不到 5%,但这绝不等于它们不花时间。原因在下图:
这里的核心概念是算术强度(Arithmetic Intensity):每从内存搬 1 字节数据,能摊到多少次浮点运算,单位 FLOPs/Byte。
- 逐元素激活:每个元素要读一次、写一次(FP16 共约 4 字节),只做 1 次运算,算术强度约 0.25 FLOPs/Byte。
- 池化:输入有窗口重叠复用,但输出也要写出,强度约 1–2 FLOPs/Byte。
- 归一化:LayerNorm 需要对同一行数据读两遍(算统计量、再归一化),还包含一次平方根和除法,强度仍在 1–2 的量级。
- 普通卷积/GEMM:权重和输入被高度复用,强度可达每字节几百甚至上千 FLOPs。
GELU(Transformer FFN 中常用的激活)比 ReLU 更重:精确实现含高斯误差函数或一次 tanh,每个元素要做十几次超越运算,但它依然是 memory-bound——因为运算再多也只摊到一个元素上,而这个元素的一读一写省不掉。实际部署常用查表、多项式近似或 INT8 分段线性近似,把它变得和 ReLU 一样廉价。这类近似以及整体量化之所以可行,是因为网络对小幅数值扰动通常不敏感,损失的零点几个百分点精度换来数倍延迟收益,在端侧和大模型推理上都很划算。
算术强度低意味着什么?现代 AI 芯片上,搬一个数的代价远高于算一次乘法(上一篇讲的「内存墙」)。于是自然分成两类瓶颈:
| 算子 | 典型 FLOPs 占比 | 访存特征 | 算术强度 | 瓶颈类型 |
|---|---|---|---|---|
| 普通卷积(3×3 等) | 视觉网络 80–99% | 权重复用次数高 | 高(数十~数百) | compute-bound |
| GEMM / FC / Linear | Transformer 50–70% | 大矩阵规则复用 | 高(随矩阵规模增长) | compute-bound |
| Attention(QKᵀ、AV) | Transformer 25–45% | S×S 中间矩阵大 | 中高,长序列时偏访存 | compute-bound 居多 |
| Depthwise Conv | MobileNet 中显著 | 每个通道独立、几乎无通道复用 | 低(约 1–2) | memory-bound |
| 池化 | <2% | 读写整张特征图 | 低 | memory-bound |
| ReLU/GELU/残差加 | ❤️% | 每元素一读一写 | 约 0.25–1 | memory-bound |
| LayerNorm/BatchNorm | ❤️% | 多次扫描整行 | 约 1–2 | memory-bound |
工程上对付 memory-bound 算子的主要手段不是给它分配更多 MAC,而是算子融合:把 Conv → BatchNorm → ReLU 合成一个 kernel,数据在卷积输出后直接在片上 SRAM 或寄存器里做完归一化和激活,只把最终结果写回内存一次——三轮访存合并成一轮。推理时 BatchNorm 还可以直接折叠进卷积权重,运行时零开销。这也是为什么 FLOPs 表上归一化占比 <1%:它在真正执行前往往已经「消失」了。
顺带一提 MobileNet 里的 Depthwise Conv:每个卷积核只在一个通道内滑动,没有跨通道复用,算术强度掉到约 1–2 FLOPs/Byte,虽然 FLOPs 总量小,实际耗时占比却常常和 FLOPs 占比不成比例,是端侧网络出了名的「低 FLOPs 但跑不快」的算子。MobileNetV2 中 Depthwise 与逐点 1×1 卷积的 FLOPs 比例约为 1:6 到 1:9,但在不少实现里两者耗时接近——这就是算术强度差异的直接体现。判断性能要看算术强度,而不是只看 FLOPs 大小。
把这两类瓶颈映射到芯片设计上,结论就很清晰:compute-bound 算子考验的是 MAC 阵列的规模、主频和数据供给方式,芯片标称的峰值 TOPS 只有在这类算子上才可能跑满;memory-bound 算子考验的是内存带宽、片上 SRAM 容量和编译器的融合调度能力。一颗带宽不足的 NPU 跑 ResNet 可能跑分很好看(卷积多、吃算力),跑 MobileNet 或大模型生成却严重掉队。roofline(屋顶线)模型把这两件事画在一张图上:以算术强度为横轴、可达性能为纵轴,斜线段是带宽天花板、水平段是算力天花板,每个算子落在折线上的位置就决定了它的瓶颈归属。
六、跟着一个残差块算一笔账
把前面的概念串到 ResNet 的一个瓶颈块(Bottleneck)上走一遍。这个块只有三层卷积:1×1 降维(256→64)、3×3 特征提取(64→64)、1×1 升维(64→256),外加 ReLU 和一条逐元素相加的残差短路。输入 56×56×256,逐层算账:
- 两个 1×1 卷积各约 1 亿和 4 亿 FLOPs,本质是矩阵乘;3×3 卷积约 2.4 亿 FLOPs。整个块约 7 亿 FLOPs,100% 来自这三层卷积。
- 三处 ReLU 处理的元素数分别约 20 万、20 万、80 万,每次只做一次比较,合计约 120 万次简单运算——和 7 亿比,连千分之二都不到。
- 残差加法读两张 80 万元素的特征图、写一张,运算量约 80 万次,但要读写约 4.8 MB 数据(FP16)。融合得当,它可以并入最后一个 1×1 卷积的输出阶段,不单独产生访存。
- BatchNorm 在推理部署时已折叠进卷积权重和偏置,运行时根本不存在。
这就是真实网络的典型模样:几十个这样的块串起来,宏观上是几百层,微观上每块的计算账目都是同一笔——GEMM/卷积拿走几乎全部,其他算子要么被融合、要么在等内存。 ResNet-50 的 50 层说法里真正独立的权重层有 53 个,而推理时执行的 kernel 数量经过融合后通常只剩每块两三个。
batch size(一次推理同时处理的样本数)也会改变算子的面貌。batch=1 时,GEMM 的一个矩阵维度很小,复用不充分,算术强度下降,这也是手机端单张拍照识别时芯片利用率不高的原因;云端 batch 拉大后,同一批样本共享权重,权重读一次服务多个输入,GEMM 形状变得「又大又方」,才能逼近峰值 TOPS。Transformer 生成场景 batch 则常被「请求长短不一」拖累,需要连续批处理(continuous batching)这类调度手段把矩阵重新填满。
七、这一篇在知识体系里的位置
- 想补「为什么 MAC 阵列和内存墙决定了上面这些瓶颈」 → AI 加速器入门:NPU/GPU 为什么比 CPU 快;
- 想深入卷积/GEMM 怎么靠数据流复用数据 → 数据复用与 Weight Stationary;
- 想看 Attention 的 softmax、归约在 kernel 层怎么实现 → Transformer Attention Kernel。
总结
一句话总结:神经网络虽然有几十上百层、层名五花八门,但按计算结构只归为卷积、GEMM(全连接/Linear,1×1 卷积本质也是它)、Attention、池化、逐元素激活与归一化这少数几类算子;经典视觉网络里卷积一家独大(VGG-16 约 91%、ResNet-50 约 99%),因为卷积核在特征图上高度复用,算术强度可达每字节数百 FLOPs,属于典型的 compute-bound,工程上还常通过 im2col 转成 GEMM;Transformer 里绝大多数计算仍是 GEMM(QKV 投影与 FFN,占 50–70%),真正新增的是随序列长度平方增长的 S×S 注意力矩阵(长序列下占比升至 30–45%)和夹在中间、规则性差的 softmax;而 ReLU/GELU、池化、归一化每元素只做一两次运算、FLOPs 占比不到 5%,但每个数据都要一读一写、算术强度只有 0.25–2 FLOPs/Byte,是 memory-bound——搬数据的代价超过计算,Depthwise Conv 也因缺少跨通道复用落入此类;因此推理优化的主线是让卷积/GEMM 在脉动阵列上充分复用算力(compute-bound 看峰值 TOPS),并通过 Conv-BN-ReLU 算子融合、BN 折叠等手段把多轮访存合并成一轮(memory-bound 看带宽),评估一个网络或一颗芯片时,FLOPs 占比和算术强度要一起看,才能判断谁真正吃掉了时间。