3、GPU核心概念:CUDA核心、SM(流多处理器)、Warp(线程束)的定义与关系
好,咱们进入第三个核心话题。前面聊了线程层次和内存模型,现在该聊聊GPU真正的“肌肉”了——CUDA核心、SM和Warp。
这三个概念,我敢说90%的初学者都搞混过。我自己刚入行时也犯过糊涂,以为CUDA核心就是CPU核心的缩小版。嗯,后来被现实狠狠教育了一顿。
3.1 先说Warp(线程束)——GPU调度的基本单位
Warp这个词,直译是“一束线”。在GPU里,它指的是32个线程组成的一个小组。这32个线程,执行完全相同的指令,只是处理的数据不同。
你想想看,这像什么?像军训时全班同学一起做“向左转”。指令是一样的,但每个人转的方向(数据)可能不同。这就是SIMT(单指令多线程)的精髓。
核心要点:Warp是GPU调度和执行的基本单位。不是单个线程,而是32个线程一组的Warp。
为什么是32?不是16或64?我记得NVIDIA官方文档里提过,这是经过大量权衡后的选择。32这个数字,在硬件实现上能很好地平衡调度开销和并行度。我在做某个图像处理项目时,曾经尝试过手动调整Warp大小(当然,这是不可能的,硬件固定了),但通过改变线程块大小来间接影响Warp利用率,效果确实不一样。
3.2 再说CUDA核心——别被名字骗了
CUDA核心,这个名字其实挺误导人的。我第一次听到时,以为它和CPU核心一样,是个完整的计算单元。其实不是。
一个CUDA核心,说白了就是一个浮点运算单元(FPU),只能做简单的算术运算。它没有CPU核心那样的复杂控制逻辑、分支预测、乱序执行等能力。它就是个“傻大个”,只会算数。
| 特性 | CPU核心 | CUDA核心 |
|---|---|---|
| 控制逻辑 | 复杂(分支预测、乱序等) | 极简 |
| 缓存 | 大(L1/L2/L3) | 小(主要依赖SM内共享内存) |
| 并行度 | 1-2个线程/核心 | 32个线程/Warp |
| 主要用途 | 通用计算、复杂逻辑 | 大规模并行算术 |
一个SM里通常有几十到几百个CUDA核心。比如我常用的RTX 3090,每个SM有128个CUDA核心。但这些核心不是独立工作的,它们被组织成多个Warp调度器来管理。
避坑指南:我曾经看到有人写代码时,试图让每个CUDA核心执行不同的指令。这是不可能的。同一Warp内的所有线程,在同一时刻执行的是同一条指令。如果你想做分支,那就会导致Warp分化(divergence),性能会大打折扣。
3.3 最后说SM(流多处理器)——真正的“大脑”
SM,全称Streaming Multiprocessor,流多处理器。它才是GPU里真正独立完整的计算单元。
一个SM包含:
- 多个CUDA核心(比如128个)
- 共享内存(Shared Memory)
- 寄存器文件(Register File)
- Warp调度器(Warp Scheduler)
- 指令缓存(Instruction Cache)
- 张量核心(Tensor Core,如果有的话)
你可以把SM想象成一个“小型的多核处理器”。它自己就能独立地调度和执行Warp。一个GPU芯片上,通常有几十到上百个SM。
我个人习惯把SM比作一个工厂车间,CUDA核心是车间里的工人,Warp是工人组成的小组。车间主任(Warp调度器)负责给各个小组分配任务。
3.4 三者的关系——一张图说清楚
咱们用个具体的例子来串一下。假设你写了一个简单的向量加法核函数:
__global__ void vecAdd(float* A, float* B, float* C, int N) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < N) {
C[i] = A[i] + B[i];
}
}
当你启动这个核函数时,比如启动1024个线程:
- 线程组织:这1024个线程被分成多个线程块(比如每个块256线程,共4个块)。每个线程块会被分配到某个SM上。
- Warp划分:在每个SM上,线程块里的线程被进一步分成Warp(每32个线程一组)。比如256线程的块,分成8个Warp。
- Warp调度:SM里的Warp调度器,每次选一个Warp,让这个Warp里的32个线程在CUDA核心上执行指令。
- 执行:这32个线程同时执行相同的指令(比如加法),但操作不同的数据(A[i]、B[i]不同)。
关键关系:
- 线程 → 组成 → Warp(32线程)
- Warp → 在 → SM上调度执行
- SM → 包含 → 多个CUDA核心
- CUDA核心 → 执行 → Warp内线程的算术运算
3.5 一个常见的误解
很多人以为,一个CUDA核心对应一个线程。这是错的。
实际上,一个SM里的CUDA核心数量,远少于它能同时管理的线程数。比如一个SM有128个CUDA核心,但它可能同时管理着2048个线程(64个Warp)。
为什么会这样?因为GPU用大量线程来隐藏延迟。当一个Warp在等待内存访问结果时,调度器立刻切换到另一个Warp执行。只要Warp数量足够多,计算单元(CUDA核心)就能一直忙碌。
我记得有一次做性能调优,发现某个核函数的占用率(occupancy)只有30%。我一开始以为是CUDA核心不够用,后来才发现是每个线程使用的寄存器太多,导致SM能同时容纳的Warp数量减少。调整寄存器使用后,占用率提升到80%,性能翻了一倍。
注意:不要盲目追求高占用率。有时候低占用率但每个Warp有更多寄存器,反而能提升单个Warp的性能。这需要根据具体算法来权衡。我见过有人为了追求100%占用率,把寄存器用得死死的,结果每个线程都慢得像蜗牛。
3.6 总结一下
这三个概念,你只要记住一句话:
Warp是调度的单位,CUDA核心是执行的单位,SM是管理的单位。
下次你写CUDA代码时,可以想想:我的线程块大小设置合理吗?会不会导致Warp分化?每个SM能同时容纳多少个Warp?这些思考,才是真正理解GPU性能的关键。
嗯,这一章就到这里。下一章咱们聊聊Warp分化(divergence)这个坑,我保证你会觉得“原来如此”。