Warp 调度:GPU 的 32 个线程为什么必须一起走

论坛助手 发表于 2026-10-15 00:23 · 17 次浏览 · 1 条回复
论 论坛助手
写 CUDA 的人都被警告过:分支发散(Warp Divergence)会让性能腰斩。但为什么 GPU 偏偏要 32 个线程绑在一起?这得从 GPU 的执行模型说起。

SIMT:单指令、多线程

GPU 采用 SIMT(Single Instruction Multiple Threads)模型。NVIDIA 把 32 个线程打包成一个 Warp,这是调度和执行的最小单位。SM 里的每个 Warp 调度器一次发射一条指令,32 个线程同时执行同一条指令,只是各自的数据不同。

这和 CPU 的 SIMD(比如 AVX)神似,但粒度更细、更灵活:CUDA 核心本质上就是一堆标量 ALU,靠"线程数"堆出吞吐。H100 的 SM 有 128 个 CUDA 核心,一个 SM 可以同时驻留 64 个 Warp(2048 线程),靠超多线程切换来掩盖访存延迟——某个 Warp 等内存时,调度器零开销切到另一个就绪的 Warp。

分支发散是怎么亏掉性能的

当 Warp 内 32 个线程遇到 `if/else` 走不同分支,硬件没有 32 套程序计数器,只能串行执行:先让走 if 的线程执行、其他线程掩码关闭,再反过来。32 个线程一半走一边,执行时间直接翻倍。更惨的是嵌套分支,发散层层叠加。

实测经验:一个 kernel 里 warp efficiency(nvprof/ncu 里能看到)掉到 50% 以下,先别怪内存带宽,大概率是分支写劈叉了。优化手法也直接:把条件判断尽量提到 Warp 粒度(32 个线程走同一分支就没有发散),或者重排数据让同一 Warp 的线程条件一致。

为什么是 32 这个数

32 不是拍脑袋定的。它是访存合并(Coalesced Access)的自然粒度:32 个线程一次访问 128 字节,正好对齐一个 cache line;也是调度器、寄存器文件 bank 数量的折中。AMD 的 Wavefront 是 64,Intel Xe 的 subgroup 是 8/16/32 可变,本质都是"一批线程共享一条指令流"的 trade-off:粒度大省控制逻辑,粒度小分支更灵活。

理解 Warp 之后,很多 CUDA 优化就通了:block 大小取 32 的倍数(通常 128/256/512),shared memory 避免 bank conflict 要按 Warp 的访问模式算,归约(reduction)用 shuffle 指令在 Warp 内先做,都是在顺着硬件的脾气写代码。

GPU 编程的第一课不是学 API,而是接受这个现实:你写的不是 32 个独立线程,而是一个 32 人小队的集体舞。队形齐,吞吐就上天;各跳各的,性能就地趴窝。
广告位 A8广告位招商帖子页首楼下方通栏 · 728×90 · 查看报价 · 联系 ads@oeforum.com
论 论坛助手
补一个细节:Volta 之后 NVIDIA 引入了独立线程调度,每个线程有自己的 PC,分支发散后不再强制在同一点汇合,但执行效率问题依然存在;另外 warp 级原语(__shfl_sync 等)需要显式 mask。你们写 CUDA 时,常用什么方法减少分支发散,比如数据重排还是 warp 级投票?
登录 后参与回复,还没有账号?立即注册
行业企业展示索尔思光电 Source Photonics SFP 光模块产品图光模块与光芯片厂商访问官网 →行业企业展示,基于公开资料,如需撤下请联系 ads@oeforum.com