线程层次结构
本文聚焦层次结构、线程块设计、线程束概念、Occupancy 优化。
层次结构回顾
MUSA 采用三层线程层次结构:
网格
├── 线程块 0
│ ├── 线程 0
│ ├── 线程 1
│ └── ...
├── 线程块 1
│ ├── 线程 0
│ └── ...
└── 线程块 N
| 层级 | 说明 | 特点 |
|---|---|---|
| 线程 | 执行的最小单元 | 每个线程执行相同代码,处理不同数据 |
| 线程块 | 线程的集合 | 块内线程可同步、共享内存 |
| 网格 | 所有线程块的集合 | 一次内核函数启动的所有线程 |

为什么线程块之间不能同步? 线程块可被调度到 GPU 上的任意 MP 执行,执行顺序不确定。如果允许跨线程块同步,程序将依赖特定的执行顺序,导致无法在不同 GPU 之间移植。
线程块设计
线程块大小选择
| 线程块大小 | 适用场景 |
|---|---|
| 128 | 寄存器需求高的内核函数 |
| 256 | 通用场景,推荐起点 |
| 512 | 计算密集型内核函数 |
| 1024 | 最大占用率场景 |
推荐原则:
- 线程块大小应为线程束大小的倍数(MP31: 32,MP21/MP22: 128)
- 常用值:128、256、512
网格大小计算
// 向上取整计算网格大小
int blockSize = 256;
int gridSize = (n + blockSize - 1) / blockSize;
线程块限制
| 参数 | 最大值 |
|---|---|
| 每个线程块的线程数 | 1024 |
| 线程块维度 (x) | 1024 |
| 线程块维度 (y) | 1024 |
| 线程块维度 (z) | 64 |
| 共享内存大小 | 48 KB |
网格限制
| 参数 | 最大值 |
|---|---|
| 网格维度 (x) | 2^31 - 1 |
| 网格维度 (y) | 65535 |
| 网格维度 (z) | 65535 |
线程束概念
什么是线程束
线程束是 GPU 硬件的基本执行单元,包含固定数量的线程:
| GPU 架构 | 线程束大小 |
|---|---|
| MTT S5000 (MP31) | 32 线程 |
| MP21/MP22 | 128 线程 |
线程束执行
线程束执行一条指令:
┌─────────────────────────────────────┐
│ 1. 取指 (Fetch) │
│ 2. 译码 (Decode) │
│ 3. 执行 (Execute) - 所有线程同时 │
│ 4. 写回 (Write-back) │
└─────────────────────────────────────┘
线程束与线程块的关系
- 一个线程块包含多个线程束
- 线程块大小 = 256 时,包含 8 个线程束(S5000,32 线程/线程束)
- 线程束是硬件调度的基本单位
当线程束内线程执行路径出现分支时,硬件会序列化执行各分支:
// 可能引起分支分化
if (threadIdx.x % 2 == 0) {
// 路径 A:一半线程执行
data[idx] = data[idx] * 2.0f;
} else {
// 路径 B:另一半线程执行
data[idx] = 0.0f;
}
// 总执行时间 = 路径 A 时间 + 路径 B 时间
更多详情,参见 执行模型。
线程同步
线程块内同步
__syncthreads(); // 等待线程块内所有线程到达同步点
使用场景:共享内存读写同步
__shared__ float shared_data[256];
// 步骤 1:写入共享内存
shared_data[threadIdx.x] = input[threadIdx.x];
// 步骤 2:同步,确保所有线程完成写入
__syncthreads();
// 步骤 3:读取其他线程的数据
float neighbor = shared_data[threadIdx.x + 1];