GPU 架构学习
第七课:GPU 是算力受限,还是带宽受限
这一课建立 GPU 性能分析的核心框架:先判断程序受算力限制,还是受显存带宽限制。
目录
一、GPU 执行程序需要两件事二、什么是 FLOPS三、什么是显存带宽四、什么是 Arithmetic Intensity数组加法的计算强度FMA 操作的计算强度五、内存带宽能够支持多少算力六、什么是 Compute Bound七、什么是 Memory Bound八、Roofline Model九、转折点在哪里十、为什么矩阵乘法适合 GPU朴素矩阵乘法的问题使用 Tiling 后十一、计算强度不是程序语句数量十二、缓存如何影响 Roofline十三、低计算单元利用率不一定说明程序写得差十四、如何计算有效内存带宽十五、如何计算实际 FLOPS十六、为什么实际性能达不到理论 Roofline十七、一个 Kernel 可能同时经历不同阶段十八、怎样提高 Memory Bound 程序性能1. 合并访存2. 提高数据复用3. 融合 Kernel4. 使用更低位宽5. 改善数据布局十九、怎样提高 Compute Bound 程序性能1. 使用合适的执行单元2. 降低精度3. 增加指令级并行性4. 减少无效计算5. 保证足够并行度6. 减少执行管线竞争二十、Kernel Fusion 的权衡二十一、为什么神经网络中的算子表现不同矩阵乘法和大卷积ReLU、Clamp、Elementwise AddSoftmaxLayerNormAttention二十二、如何初步判断一个 Kernel 的瓶颈二十三、使用性能分析工具时看什么二十四、一个常见误区:带宽没有跑满就不是 Memory Bound二十五、另一个误区:计算强度越高一定越快二十六、从硬件架构角度理解 Roofline二十七、本课核心结论第八课:GPU 如何完成矩阵乘法导语:这一课建立 GPU 性能分析的核心框架:先判断程序受算力限制,还是受显存带宽限制。
GPU 标称算力可能达到几十甚至几百 TFLOPS,但实际程序经常只能达到峰值算力的一小部分。
这通常不是 GPU “没有发挥作用”,而是程序受到其他因素限制。最基本的性能分类是:
Compute Bound:计算受限
Memory Bound:内存带宽受限
判断属于哪一种,是 GPU 性能分析的第一步。
一、GPU 执行程序需要两件事
任何计算都可以粗略拆成:
搬运数据
+
执行运算
例如:
C[i] = A[i] + B[i];
每个元素需要:
- 读取
A[i]; - 读取
B[i]; - 执行一次加法;
- 写回
C[i]。
其中真正的算术操作只有一次,但需要传输三个浮点数。
如果使用 FP32,每个数占 4 字节,因此每个元素大约需要:
而计算量只有:
这说明它需要搬运很多数据,但计算很少。
二、什么是 FLOPS
FLOPS 是:
Floating Point Operations Per Second,每秒浮点运算次数。
例如:
一块 GPU 的 FP32 峰值算力为 60 TFLOPS,表示理想情况下每秒可以完成约:
次浮点运算。
需要注意,峰值算力通常是根据硬件资源计算出的理论值:
例如一个 FMA:
通常被计算为两个浮点操作:
- 一次乘法;
- 一次加法。
因此一个执行单元每周期执行一次 FMA,通常记为每周期 2 FLOPs。
三、什么是显存带宽
显存带宽表示 GPU 每秒理论上可以从显存读取或写入多少数据。
例如:
表示理论上每秒可以传输约 1 TB 数据。
它主要由以下因素决定:
- 显存类型;
- 显存频率;
- 显存总线宽度;
- 显存控制器数量;
- HBM 或 GDDR 的通道组织。
理论带宽可以粗略表示为:
GPU 同时可能拥有:
很高的计算吞吐率
+
很高的显存带宽
但两者的增长速度并不总是一致。
现代 GPU 的计算能力增长往往比显存带宽增长更快,所以许多简单程序更容易受到带宽限制。
四、什么是 Arithmetic Intensity
Arithmetic Intensity 可以翻译为:
算术强度或计算强度。
定义为:
单位通常为:
它回答的问题是:
每从内存搬运一个字节,程序能够完成多少次计算?
数组加法的计算强度
对于:
C[i] = A[i] + B[i];
如果忽略缓存,每个元素:
- 读取 A:4 Bytes;
- 读取 B:4 Bytes;
- 写入 C:4 Bytes;
- 计算一次加法:1 FLOP。
所以:
计算强度非常低。
这意味着程序每搬运大量数据,只做很少计算,通常属于显存带宽受限。
FMA 操作的计算强度
例如:
C[i] = A[i] * B[i] + C[i];
每个元素大约需要:
- 读取 A:4 Bytes;
- 读取 B:4 Bytes;
- 读取 C:4 Bytes;
- 写回 C:4 Bytes;
- 一次乘法和一次加法:2 FLOPs。
因此:
虽然使用了 FMA,计算强度仍然很低。
五、内存带宽能够支持多少算力
假设某块 GPU 的显存带宽是:
数组加法的计算强度约为:
那么显存最多支持的实际计算吞吐率约为:
得到:
即使 GPU 的理论计算能力是 60 TFLOPS,这个数组加法也可能只能达到几十 GFLOPS 量级。
原因不是 CUDA Core 不够强,而是数据供应速度限制了计算。
峰值算力:60 TFLOPS
带宽允许数组加法达到:约 0.083 TFLOPS
大量执行单元只能等待数据。
六、什么是 Compute Bound
Compute Bound 表示主要瓶颈是计算单元。
典型特征是:
- 每份数据要参与大量计算;
- 数据复用率高;
- 执行单元长时间忙碌;
- 显存带宽尚未达到极限;
- 增加计算能力可能提高性能。
例如大规模矩阵乘法:
每个输出元素:
需要执行大量乘加运算。
如果 A 和 B 中的数据被加载到 Shared Memory 或寄存器后反复复用,则每搬运一个字节,可以进行很多次计算。
这类程序计算强度较高,更可能成为 Compute Bound。
七、什么是 Memory Bound
Memory Bound 表示主要瓶颈是数据搬运。
典型特征是:
- 每个数据只使用一次或少数几次;
- 算术操作很简单;
- 显存带宽接近饱和;
- 计算单元利用率不高;
- 增加 CUDA Core 数量帮助很小。
典型例子包括:
数组复制
数组加法
简单逐像素运算
向量缩放
部分稀疏操作
简单激活函数
例如 ReLU:
y[i] = max(x[i], 0.0f);
每个元素只需要:
- 读取 x;
- 比较;
- 写回 y。
计算很少,数据搬运较多,因此通常也是带宽受限。
八、Roofline Model
Roofline Model 是分析计算性能的常用模型。
它使用两个硬件上限:
- 峰值计算性能;
- 峰值内存带宽。
程序理论性能受到下面公式限制:
其中:
- 是实际性能;
- 是峰值计算性能;
- 是显存带宽;
- 是计算强度。
直观表示为:

图示:八、Roofline Model
左边是斜线区域:
计算强度越高,内存能够支持的计算性能越高。
右边是水平区域:
即使继续增加计算强度,也不能超过硬件峰值算力。
九、转折点在哪里
Roofline 的转折点满足:
因此:
这个值称为 Ridge Point,可以理解为从带宽受限转向计算受限所需的最低计算强度。
假设 GPU:
显存带宽:
则:
因此:
- 如果程序计算强度明显小于 60 FLOPs/Byte,倾向于带宽受限;
- 如果明显大于 60 FLOPs/Byte,才可能达到计算受限区域。
数组加法只有约:
远低于 60,因此几乎不可能跑满计算峰值。
十、为什么矩阵乘法适合 GPU
假设计算两个 矩阵:
总计算量大约是:
因为每个输出元素需要 次乘法和 次加法。
三个矩阵的数据量大约与:
成正比。
因此理想情况下,计算强度大致随 增加:
矩阵越大,计算量增长速度比数据量更快。
这也是大规模矩阵乘法能够充分使用 GPU 算力的重要原因。
朴素矩阵乘法的问题
如果每做一次乘法都从 Global Memory 读取:
sum += A[row][k] * B[k][column];
那么 A 和 B 中相同的数据会被不同线程重复读取很多次。
这种实现的数据复用率较低。
使用 Tiling 后
可以把 A 和 B 的小块加载到 Shared Memory:

图示:使用 Tiling 后
假设一份数据从显存读取一次,却参与了 16、32 或更多次乘加,就显著提高了计算强度。
未优化:
读取数据一次 → 计算一次
分块优化:
读取数据一次 → 计算多次
这就是矩阵乘法优化的核心。
十一、计算强度不是程序语句数量
计算强度不只是看代码中有多少加法和乘法,还必须考虑实际的数据传输。
例如:
for (int k = 0; k < 100; ++k) {
sum += A[i];
}
如果 A[i] 第一次加载后一直保存在寄存器中,那么只需要从显存读取一次,却执行 100 次累加。
计算强度较高。
如果编译器或程序每次都从显存重新加载,则数据传输量会增加,计算强度下降。
所以计算强度取决于:
- 缓存命中;
- 寄存器复用;
- Shared Memory 复用;
- 数据布局;
- 实际访存事务;
- 编译器生成的代码。
十二、缓存如何影响 Roofline
最简单的 Roofline 使用显存带宽。
但 GPU 有多个存储层次:
Register
Shared Memory / L1
L2
Global Memory
因此可以建立分层 Roofline:
显存带宽上限
L2 带宽上限
L1/Shared Memory 带宽上限
计算峰值上限
如果数据已经命中 L2,就不需要重新访问显存。
此时性能可能受 L2 带宽限制,而不是显存带宽限制。
如果数据在 Shared Memory 中反复使用,可能受到 Shared Memory 带宽或 Bank Conflict 限制。
所以“Memory Bound”不一定只意味着外部显存带宽受限,也可能是:
- L1 带宽受限;
- L2 带宽受限;
- Shared Memory 带宽受限;
- Load/Store Unit 吞吐率受限;
- 地址计算受限。
十三、低计算单元利用率不一定说明程序写得差
假设一个数组复制 Kernel:
B[i] = A[i];
它几乎没有算术运算。
因此 CUDA Core 利用率很低是正常的。
正确的评价指标不是:
CUDA Core 有没有达到 100%?
而是:
显存带宽是否接近该程序可实现的上限?
对于数组复制,如果有效带宽已经很高,即使计算利用率很低,程序也可能已经接近最优。
不同类型程序应使用不同指标评价。
十四、如何计算有效内存带宽
假设复制 个 FP32 数据:
B[i] = A[i];
每个元素:
- 读取 4 Bytes;
- 写入 4 Bytes。
总传输量约为:
如果执行时间为 ,有效带宽为:
例如复制 1 GB 数据,读写总量约为 2 GB,执行时间为 2 ms:
这比只计算输入数组大小更准确,因为需要同时考虑读和写。
十五、如何计算实际 FLOPS
假设矩阵乘法:
总浮点操作数大约为:
如果执行时间为 ,实际性能为:
例如:
总操作数约为:
再除以执行时间,就能得到实际 FLOPS。
需要确保使用的操作数统计规则与峰值算力统计规则一致,例如 FMA 按 2 FLOPs 计算。
十六、为什么实际性能达不到理论 Roofline
Roofline 给出的仍然是上限,而不是保证值。
程序还可能受到以下限制:
分支发散
访存不合并
缓存未命中
Shared Memory Bank Conflict
寄存器不足
Occupancy 太低
指令依赖链过长
执行单元分布不匹配
同步等待
原子操作竞争
Kernel 启动开销
例如一个程序计算强度很高,但所有计算都是特殊函数,受 SFU 数量限制,它仍然无法达到 FP32 峰值算力。
又例如大量整数地址计算可能使 INT 管线成为瓶颈。
因此更准确的说法是:
Roofline 只是先抓住最重要的计算和内存两类上限。
十七、一个 Kernel 可能同时经历不同阶段
假设某个 Kernel:
- 从显存读取数据;
- 在寄存器中进行大量计算;
- 将结果写回显存。
加载阶段可能是 Memory Bound,计算阶段可能是 Compute Bound。
从整体测量时,最终表现取决于各阶段占比,以及它们能否通过流水线重叠。
所以一个 Kernel 并不一定能被绝对地贴上单一标签。
但通常可以判断它的主导瓶颈。
十八、怎样提高 Memory Bound 程序性能
对于内存受限程序,优化方向不是继续增加计算单元,而是减少数据搬运或提高有效带宽。
1. 合并访存
让同一个 Warp 中线程访问连续地址:
x = A[global_thread_id];
避免大跨度、随机访问。
2. 提高数据复用
将频繁使用的数据保存在:
- 寄存器;
- Shared Memory;
- Cache。
3. 融合 Kernel
假设原来有:
Kernel 1:读取 A,生成 B
Kernel 2:读取 B,生成 C
Kernel 3:读取 C,生成 D
每个 Kernel 都要把中间结果写入显存,再读回来。
融合后:
一个 Kernel:
读取 A
→ 在寄存器中生成 B
→ 继续生成 C
→ 生成 D
→ 最后只写一次
这叫 Kernel Fusion。
它可以显著减少 Global Memory 访问。
但融合也可能增加寄存器使用量,降低 Occupancy,因此需要权衡。
4. 使用更低位宽
例如把 FP32 改为 FP16:
FP32:4 Bytes
FP16:2 Bytes
INT8:1 Byte
相同带宽下可以传输更多元素。
但前提是精度允许,并且硬件支持高效处理该数据格式。
5. 改善数据布局
例如选择行优先、列优先、通道优先或其他布局,使线程访问更连续。
十九、怎样提高 Compute Bound 程序性能
如果程序计算受限,重点是提高执行单元利用率。
常见方向包括:
1. 使用合适的执行单元
矩阵计算尽可能使用 Tensor Core,而不是全部拆成普通 FP32 指令。
2. 降低精度
在允许的情况下使用:
TF32
FP16
BF16
FP8
INT8
FP4
低精度通常可以显著提高吞吐率。
3. 增加指令级并行性
减少长依赖链,使用多个独立累加器。
4. 减少无效计算
避免大量被谓词屏蔽的指令和严重分支发散。
5. 保证足够并行度
提供足够多 Block 和 Warp,使所有 SM 都有工作。
6. 减少执行管线竞争
避免所有指令集中使用某一类稀缺资源,例如 SFU 或原子单元。
二十、Kernel Fusion 的权衡
Kernel Fusion 常用于减少内存访问,但并不是总是有利。
融合前:
Kernel A:
寄存器使用少
并行度高
Kernel B:
寄存器使用少
并行度高
融合后:
Kernel AB:
需要同时保存更多中间变量
寄存器使用增加
Occupancy 下降
因此可能出现:
显存访问减少
但并行度也下降
最终性能要看哪种影响更大。
GPU 优化经常不是单一指标越高越好,而是在以下资源之间权衡:
- 寄存器;
- Shared Memory;
- Occupancy;
- 内存流量;
- 指令数量;
- 数据复用。
二十一、为什么神经网络中的算子表现不同
不同神经网络算子的计算强度差异很大。
矩阵乘法和大卷积
数据可以被大量复用,通常计算强度较高,容易使用 Tensor Core。
ReLU、Clamp、Elementwise Add
每个元素计算很少,通常更偏向显存带宽受限。
Softmax
包含:
- 归约;
- 指数计算;
- 数据读取和写回;
- 多阶段同步。
它可能同时受显存带宽、SFU、归约和同步限制。
LayerNorm
需要读取数据、计算均值和方差、再次处理数据,通常涉及多次数据访问和归约,常偏向带宽受限。
Attention
不同阶段表现不同:
QKᵀ 矩阵乘法:偏计算密集
Softmax:偏带宽和特殊函数
Softmax×V:矩阵乘法
FlashAttention 的核心思想之一,就是通过分块和融合减少中间矩阵写回显存,从而降低内存流量。
二十二、如何初步判断一个 Kernel 的瓶颈
可以按以下顺序分析。
第一步,统计计算量:
第二步,统计需要从显存传输的数据量:
第三步,计算算术强度:
第四步,计算硬件转折点:
第五步,进行比较:
AI 远小于转折点
→ 大概率带宽受限
AI 远大于转折点
→ 可能计算受限
之所以说“可能”,是因为还需要检查其他瓶颈。
二十三、使用性能分析工具时看什么
以 NVIDIA Nsight Compute 为例,分析时通常关注:
DRAM Throughput
L2 Throughput
SM Throughput
Tensor Core Utilization
Achieved Occupancy
Eligible Warps
Warp Stall Reasons
Branch Efficiency
Memory Coalescing
如果看到:
显存带宽接近峰值
SM 计算利用率不高
通常说明程序偏向带宽受限。
如果看到:
SM 或 Tensor Core 利用率很高
显存带宽没有饱和
通常说明程序偏向计算受限。
如果两者都不高,则可能存在:
- 并行度不足;
- 长延迟依赖;
- 分支发散;
- 非合并访存;
- 同步等待;
- 指令吞吐瓶颈。
二十四、一个常见误区:带宽没有跑满就不是 Memory Bound
不一定。
程序可能因为访存不合并,只实现了很低的有效带宽。
例如理论显存带宽是 1 TB/s,但随机访问导致每个内存事务只利用很少一部分数据。
性能仍然受内存访问限制,只是没有高效使用总线。
因此要区分:
带宽受限
和:
高效达到峰值带宽
前者表示程序被数据访问限制,后者表示内存系统已经被良好利用。
二十五、另一个误区:计算强度越高一定越快
计算强度高只说明程序每搬运一个字节执行的计算更多。
但如果计算本身非常复杂,执行时间仍然可能很长。
例如大量 exp、sin、除法或复杂整数操作,计算强度可能很高,但受特殊执行单元吞吐率限制。
计算强度主要用于判断资源瓶颈,而不是直接说明程序绝对速度。
二十六、从硬件架构角度理解 Roofline
GPU 芯片面积主要分配给:
计算单元
寄存器
缓存
片上互连
内存控制器
调度和控制逻辑
如果增加更多 CUDA Core,峰值计算能力提高。
但如果显存带宽没有同步提升,低计算强度程序不会明显加速。
相反,如果增加 HBM 通道和内存控制器,带宽提高,但高计算强度程序仍可能受计算单元限制。
因此 GPU 架构设计本质上需要平衡:
这也是为什么 GPU 架构不仅讨论 CUDA Core 数量,还要讨论:
- HBM;
- L2 Cache;
- Shared Memory;
- Tensor Core;
- NVLink;
- 数据压缩;
- 内存控制器。
二十七、本课核心结论
第一,GPU 性能首先受到计算能力和数据搬运能力共同限制。
第二,计算强度定义为:
第三,数组加法等逐元素操作计算强度低,通常属于带宽受限。
第四,矩阵乘法通过数据复用提高计算强度,更容易成为计算受限。
第五,Roofline 的核心公式是:
第六,转折点为:
第七,Memory Bound 程序应重点减少数据搬运、改善访存和提高数据复用。
第八,Compute Bound 程序应重点提高执行单元利用率、减少依赖和使用 Tensor Core。
第九,缓存、Shared Memory 和寄存器会改变实际数据传输量,因此会影响有效计算强度。
第十,实际性能还可能受分支、同步、原子操作、执行管线和并行度限制。
下一课将学习 GPU 中最核心的计算任务之一:
第八课:GPU 如何完成矩阵乘法
重点包括:
朴素矩阵乘法如何映射到线程
为什么每个线程计算一个输出元素
Tiling 如何减少显存访问
Shared Memory 如何保存矩阵块
寄存器如何保存累加结果
线程块与输出 Tile 如何对应
Tensor Core 和普通 CUDA Core 有什么区别