CUDA系统拆解-04-warp、SIMT 与 SM:真实执行不是“线程各跑各的”

本文是「CUDA系统拆解」系列第 04 篇。
系列导读:CUDA系统拆解-00-导读:从编程模型到 AI 推理系统的学习路线
上一篇:CUDA系统拆解-03-线程组织模型:grid、block、thread 到底在表达什么
下一篇:CUDA系统拆解-05-内存层级与访存本质:性能瓶颈为什么常卡在数据

1. 这篇解决什么问题

  • 为什么写 CUDA 时看到的是 thread,但真正影响执行效率的常常是 warp
  • warpSIMTSM 这些概念分别在说什么。
  • threadwarpblockSM 到底是什么关系。
  • 为什么分支发散会拖慢执行。
  • GPU 为什么能靠很多并发 warp 去隐藏延迟。

2. 先记住的核心结论

  • thread 是编程模型里的基本单位,warp 是执行和性能分析里的关键粒度。
  • 在 CUDA 语境里,一个 warp 通常是 32 个线程一起被推进执行。
  • CUDA 更准确的描述是 SIMT,不是简单的 SIMD
  • block 是协作单位,SM 是执行 blockwarp 的核心硬件单元。
  • 同一个 warp 内线程行为越一致,通常执行效率越高;控制流一旦分裂,就可能发生 warp divergence
  • GPU 隐藏延迟的主要方式不是把单线程做得很强,而是让一个 SM 上挂很多 warp,谁在等内存就先切去跑别的 warp。

3. 正文讲解

3.1 从线程组织走到真实执行

上一部分你已经知道:

  • thread / block / grid 是怎么组织任务的
  • 一个线程如何通过索引找到自己的数据
  • block 为什么是协作边界

但这还只是编程视角。
到了执行视角,最关键的变化是:

你写的是 thread,GPU 高效推进时主要看的却是 warp。

所以后面很多性能现象,最后都要从 warp 角度解释:

  • 为什么分支会慢
  • 为什么访存模式重要
  • 为什么 block size 常取 32 的倍数
  • 为什么活跃 warp 不够时,延迟就会暴露出来

3.2 warp 到底是什么

可以先把 warp 理解成:

一组一起被调度、一起向前推进的线程。

你写 kernel 时,代码看起来像每个线程都在独立执行;但在底层,GPU 并不会把每个线程都当成一个完全独立的小任务去调度。更常见的做法是:

  • 先把 block 内线程分组
  • 每组形成一个 warp
  • 再以 warp 为关键粒度发射和推进指令

这就是为什么后面分析性能时,常见问题都不是“某个线程发生了什么”,而是:

  • 一个 warp 内线程是不是走了相同路径
  • 一个 warp 内线程访存是不是规则
  • 一个 SM 上是不是有足够多 warp 可切换

所以 warp 的意义不只是“32 个线程一组”这个定义,而是:

它是把编程层面的很多线程,映射成硬件可高吞吐执行的基本观察窗口。

3.3 什么是 SIMT,为什么它不像简单的 SIMD

这是最容易混淆的一组概念。

SIMD

  • 更像“一条向量指令处理多个数据 lane”
  • 常见于 CPU 的向量指令,比如 AVX、SSE
  • 程序员看到的是更显式的向量化模型

SIMT

  • 你写的是很多线程各自的标量代码
  • 每个线程逻辑上有自己的寄存器、线程 ID、控制流
  • 但硬件会尽量让同一个 warp 内线程按相似的指令流一起推进

所以二者的共同点是都在追求多数据并行。
关键区别是:

  • SIMD 更强调“显式向量指令”
  • SIMT 更强调“线程级编程抽象 + 底层成组推进执行”

这也是 CUDA 好学但又不能只停留在语法层的原因:

表面上你写的是多个 thread,底层效率却依赖这些 thread 在 warp 内是否足够整齐。

3.4 threadwarpblockSM 的关系

这四个概念最好一次压清楚:

thread

  • 最小编程单位
  • 负责一份具体计算

warp

  • 关键执行粒度
  • block 内线程会被拆成多个 warp

block

  • 线程协作单位
  • block 内可以同步,可以共享 shared memory
  • 一个 block 会被放到某个 SM 上执行

SM

  • GPU 上负责执行 block 和 warp 的核心硬件单元
  • 强调高吞吐和快速切换,不强调单线程极致低延迟

可以把关系记成:

1
2
3
4
5
grid
-> many blocks
-> each block runs on one SM
-> each block is split into multiple warps
-> each warp contains many threads

这里最关键的两句话是:

  • thread 是你写代码时直接面对的单位
  • warpSM 才是解释执行效率时更关键的单位

3.5 为什么 warp divergence 会带来性能损失

理想情况下,同一个 warp 内线程会沿着相同控制流一起前进。
一旦同一个 warp 内线程走了不同分支,就会出现 warp divergence

例如一种很典型的坏模式是:

  • 同一个 warp 里,部分线程走 if
  • 另一部分线程走 else

这时硬件通常不能让同一个 warp 在同一时刻同时执行两条完全不同的路径,只能变成:

  1. 先推进其中一条路径,让另一部分线程暂时不活跃
  2. 再推进另一条路径,让前一部分线程暂时不活跃
  3. 最后再汇合

这意味着什么?

  • 原本可以一起推进的线程组,被部分串行化了
  • warp 的有效并行度下降
  • 吞吐下降

所以 warp divergence 的本质不是“分支一定错”,而是:

同一个 warp 内控制流不一致,会让原本适合成组推进的执行变得破碎。

3.6 哪些分支更容易出问题,哪些没那么可怕

更危险的分支通常有两类:

  • 按线程细粒度交错分裂的分支,比如奇偶线程走不同路径
  • 随机数据驱动、同一个 warp 内很容易混杂的分支

相对没那么可怕的情况:

  • 同一个 warp 内大部分线程都走同一路径
  • 只在边界附近少量线程发生分裂

例如最常见的边界判断:

  • if (idx < N)

它当然也可能让最后一个 warp 有部分线程空转,但影响通常只集中在尾部少量 warp 上。相比正确性,这个代价完全合理。

所以正确理解不是“CUDA 里不要写 if”,而是:

尽量避免把同一个 warp 切得很碎,必须分支时要知道代价在哪里。

3.7 GPU 为什么能隐藏延迟

CPU 的典型思路是:

  • 尽量把单线程做快
  • 靠缓存、乱序执行、分支预测去减少等待

GPU 的典型思路不是这样。GPU 更常见的做法是:

  • 一个 SM 上同时挂很多 warp
  • 某个 warp 因为 global memory 访问而等待时
  • 不傻等,先切去执行别的 ready warp

这就是 GPU 的 latency hiding

它的核心不是“让等待消失”,而是:

当一部分 warp 在等时,尽量保证还有别的 warp 可以继续让 SM 忙起来。

这也是为什么活跃 warp 数量很重要。
如果一个 SM 上可切换的 warp 太少,就会出现:

  • 这个 warp 在等
  • 那个 warp 也在等
  • 没有足够的 ready warp 可以补位

最后结果就是 SM 更容易空转,吞吐下降。

3.8 为什么这些概念会直接影响后续优化

到了这里,你应该能理解几件事:

  • 为什么 block size 常取 32 的倍数,因为 block 最终会被拆成 warp
  • 为什么访存优化常按 warp 看,因为同一个 warp 的地址模式会影响 memory transaction
  • 为什么 occupancy 有意义,因为更多活跃 warp 往往更有利于隐藏延迟
  • 为什么很多 GPU 不喜欢复杂控制流,因为 warp 内控制流一碎,执行效率就会掉

所以这一篇不是在学“几个硬件名词”,而是在建立后面所有性能分析的第一层直觉。

4. 和 AI 推理的关系

这些概念和 AI 推理的关系非常直接。

GEMM、attention、LayerNorm、softmax 这类 kernel 之所以适合 GPU,本质上都是因为它们更容易形成:

  • 大量规则并行
  • warp 内较整齐的行为
  • 可重组的访存模式
  • 足够多的并发工作去喂满 SM

反过来,如果一个推理 kernel:

  • 分支很多
  • 数据访问很乱
  • 每个 warp 干的事差异很大

那即使线程数很多,也未必高效。

所以你后面看 FlashAttention、paged attention、decode 优化时,真正要问的是:

  • warp 内线程在做什么
  • block 如何协作
  • SM 上有没有足够多 warp 隐藏等待

5. 常见误区

  • thread 就是执行效率分析里最关键的单位。不是,很多性能问题要落到 warp 上看。
  • SIMT 就等于 SIMD。不是,它们都追求并行,但编程抽象和执行方式不同。
  • warp divergence 说明代码写错了。不是,问题不在“有分支”,而在同一个 warp 被切得太碎。
  • GPU 隐藏延迟靠的是单线程很强。不是,主要靠很多并发 warp 和快速切换。
  • 线程够多就一定快。不是,还要看 warp 一致性、访存模式、资源占用和 SM 上的活跃 warp 数量。
  • SM 就是 CPU core。不能这么机械类比,SM 更像高吞吐执行单元,不是豪华单核。

6. 复习自测

  • 为什么说 thread 是编程单位,warp 是关键执行粒度?
  • warp 的定义是什么,它为什么重要?
  • SIMTSIMD 的共同点与区别是什么?
  • threadwarpblockSM 分别处在什么层次,它们之间怎么对应?
  • 为什么同一个 warp 内线程走不同分支会导致性能下降?
  • 为什么边界判断通常不是最可怕的分支?
  • GPU 是怎样依靠很多并发 warp 去隐藏延迟的?
  • 为什么后面的 coalescing、occupancy、GEMM、attention 都要回到 warp / SM 视角来理解?

系列导航