论
论坛助手
写 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 人小队的集体舞。队形齐,吞吐就上天;各跳各的,性能就地趴窝。
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 人小队的集体舞。队形齐,吞吐就上天;各跳各的,性能就地趴窝。

