Warp divergence,中文通常叫线程束发散,是 CUDA 性能优化中最基础也最重要的概念之一。
它的核心定义是:
同一个 warp 内的 32 个线程,在执行到某个分支时,走了不同的路径,导致硬件必须串行执行这些路径。
在 SIMT(Single Instruction, Multiple Threads)架构下,一个 warp 内的 32 个线程理论上应该执行同一条指令,只是数据不同。一旦出现分支,硬件无法让 32 个线程同时走两条路,只能:
这样原本一次能做完的事情,变成了两次甚至多次,吞吐直接下降。
一句话总结:warp divergence 让 SIMT 的并行执行退化成串行执行,浪费了 warp 内的并行度。
CUDA 的 GPU 以 warp 为调度单位,一个 warp 包含 32 个线程。
在理想情况下:
这就是 SIMT 的并行来源。
假设代码是:
if (condition) {
// 路径 A
} else {
// 路径 B
}
如果同一个 warp 内:
那么硬件会:
原本一条路径能完成的工作,变成了两条路径串行执行。
假设一个 warp 内分支有 $k$ 条不同路径,且各路径线程数不均:
如果分支嵌套,路径数指数增长,性能下降更严重。
以下情况也会导致 divergence:
switch-case 多分支;for (i = 0; i < n; i++) 中每个线程的 $n$ 不同;return 或 break;即使分支体很短,只要出现 divergence,硬件仍要:
所以频繁的小分支累积起来也很可观。
这是最典型的场景。
在 batch 内,不同序列长度不同,需要 padding 或 mask:
如果实现不当,就会出现 divergence。
具体表现:
if (mask == 0) continue;if (col_idx >= seq_len) break;自回归 attention 中,每个 query 只能看前面的 key:
$$ \text{mask}(i, j) = \begin{cases} 0 & j \le i \ -\infty & j > i \end{cases} $$
在 tiling 实现中,如果 tile 跨越对角线,同一个 warp 内不同线程可能走不同分支。
MoE(Mixture of Experts)中:
这会导致严重的 divergence,也是 MoE kernel 优化的难点。
处理变长序列时:
比如 packing、unpadding、位置编码等。
某些算子中有条件逻辑:
如果按元素判断,容易 divergence。
量化 kernel 中:
稀疏 kernel 中:
核心思路:
让同一个 warp 内的线程尽量走同一条路径。
方法:
对于短分支,可以用谓词执行:
// 不推荐:分支
if (cond) {
y = a;
} else {
y = b;
}
// 推荐:谓词
y = cond ? a : b;
编译器有时会自动做,但显式写更可控。
谓词执行不是完全没有代价,但比分支轻。
很多分支可以用数学表达式替代:
max、min 代替 if-else;step、select 等函数。比如 causal mask 可以用:
$$ \text{score} = \text{score} + \text{mask} \cdot (-\infty) $$
其中 mask 是 0 或 1,避免显式分支。
对于循环次数不一致:
__syncthreads() 对齐。比如:
for (int i = 0; i < max_len; i++) {
if (i < len) {
// 有效计算
}
}
虽然仍有谓词,但避免了不同线程循环次数不同导致的 divergence。
CUDA 提供 warp-level 原语:
__ballot_sync:投票;__shfl_sync:warp 内 shuffle;__any_sync、__all_sync:聚合判断。这些可以帮助在 warp 内做统一决策,减少 divergence。
如果不同路径差异很大:
比如 MoE 中:
这些库已经做了大量优化:
如果不是必须手写,优先用成熟库。
在 warp 内或 block 内:
用 Nsight Compute 看:
不要凭感觉,要用数据定位。
for (int j = 0; j < seq_len; j++) {
if (j <= i) {
score += q[i] * k[j];
}
}
同一个 warp 内,不同线程的 $i$ 不同,导致 j <= i 判断结果不同,出现 divergence。
方法一:用 mask 替代分支
float mask = (j <= i) ? 0.0f : -INFINITY;
score += q[i] * k[j] + mask;
方法二:tiling + 对角线特殊处理
方法三:按 query 分块,让 warp 内 query 连续
Nsight Compute 中有个指标叫 branch efficiency:
$$ \text{Branch Efficiency} = \frac{\text{实际执行的分支数}}{\text{理想无 divergence 的分支数}} $$
如果 branch efficiency 低,说明 divergence 严重。
但要注意:
这是一道很好的 CUDA 基础面试题,适合考察候选人对 SIMT 执行模型和 divergence 的理解。它既能考察基础原理,也能延伸到 Transformer 自定义算子的实际优化。如果候选人能把 divergence 的原理、性能损失、Transformer 中的典型场景以及多种避免手段都讲清楚,并能结合 profiling 数据做分析,基本可以判断其具备 CUDA 算子优化的扎实基础。
评论专区
评论加载中...登录后即可发表评论