4. GPU硬件架构与性能模型
要读懂 Nsight Compute 给出的 sm__throughput、dram__throughput、l1tex__throughput 这些指标,必须先建立一张“硬件地图”。否则你看到的只是一堆百分比,无法判断瓶颈究竟卡在计算单元、寄存器、共享内存,还是显存带宽上。本节从 SM 内部结构出发,逐层向上搭建 NVIDIA GPU 的性能模型,为后续指标解读提供参照系。
一、SM:GPU 的调度与执行单元
GPU 由多个 SM(Streaming Multiprocessor,流式多处理器)组成,SM 是真正执行线程的硬件单元。以 Ampere GA102 为例,一个 SM 包含 4 个处理块(Processing Block),每个处理块有 32 个 FP32 核心、1 个 Warp 调度器、1 个寄存器文件、1 个 LD/ST 单元和 1 个 SFU。因此一个 SM 共有 128 个 FP32 核心、4 个 Warp 调度器、4 个寄存器文件。
关键点在于:Warp 调度器每个时钟周期只能发射 1 条指令。如果某个 Warp 因为等待内存数据而停顿,调度器会切换到另一个就绪 Warp。这就是 GPU 用“线程级并行”掩盖延迟的核心机制——不是让单个线程跑得更快,而是让更多线程轮流占用执行单元。
二、Warp:32 线程的执行粒度
GPU 以 Warp 为单位调度线程,一个 Warp 固定包含 32 个线程。同一个 Warp 内的 32 个线程执行同一条指令,但操作各自的数据(SIMT 模型)。这带来两个直接后果:
- 分支发散(Divergence):如果 Warp 内线程走不同分支,硬件会串行执行各分支路径,只有走该路径的线程活跃,其余线程被屏蔽。发散越严重,有效吞吐越低。
- 合并访问(Coalescing):如果 32 个线程访问连续地址,一次内存事务就能取回全部数据;若地址分散,则需多次事务,带宽利用率骤降。
Nsight Compute 中的 smsp__thread_inst_executed_per_inst_executed 指标就反映 Warp 内平均活跃线程数,理想值为 32,低于 32 说明存在发散。
三、寄存器与共享内存:SM 内的稀缺资源
每个 SM 的寄存器文件容量固定(如 GA102 为 64K 个 32 位寄存器/SM)。寄存器按线程分配,一个线程使用的寄存器数决定了 SM 能同时驻留多少 Warp。例如每个线程用 64 个寄存器,则一个 SM 最多驻留 64K/64 = 1024 个线程,即 32 个 Warp。若寄存器用量翻倍到 128,驻留 Warp 数减半,延迟隐藏能力下降。
共享内存(Shared Memory)是 SM 内可编程的高速暂存区,延迟约 20~30 个时钟周期,远低于全局内存的 400~800 周期。它按 Block 分配,用于线程间数据交换、数据复用(如矩阵分块)和避免重复读取全局内存。共享内存的典型陷阱是 Bank Conflict:32 个 Bank 若多个线程访问同一 Bank 的不同地址,访问会被串行化。Nsight Compute 的 l1tex__data_bank_conflicts_pipe_lsu_mem_shared 直接量化这一开销。
四、L1/L2 缓存与内存层次
从 SM 向外,内存层次依次为:寄存器 → 共享内存/L1 → L2 → DRAM。每一层的容量、延迟和带宽差异巨大:
| 层级 | 典型容量 | 延迟(周期) | 作用域 |
|---|---|---|---|
| 寄存器 | 64K×32bit/SM | ~1 | 线程私有 |
| 共享内存/L1 | 128~192KB/SM | ~20-30 | Block 内共享 / SM 内缓存 |
| L2 | 4~6MB(GA102) | ~200 | 全 GPU 共享 |
| DRAM | 10~80GB | ~400-800 | 全局 |
L1 和共享内存共享同一块物理 SRAM,可通过 cudaFuncAttributePreferredSharedMemoryCarveout 调整划分比例。L2 则是全 GPU 共享的末级缓存,所有 SM 的全局内存请求都经过它。Nsight Compute 中 lts__t_sector_hit_rate 反映 L2 命中率,dram__bytes.sum 反映实际 DRAM 流量。
五、吞吐量模型:三个约束取最小值
GPU 的实际吞吐量由三个约束共同决定,取其中最小值:
- 计算吞吐:SM 数 × 每 SM 核心数 × 频率 × 每周期指令数。例如 RTX 3090 有 82 个 SM,每 SM 128 个 FP32 核心,Boost 频率 1.7GHz,理论 FP32 吞吐 = 82 × 128 × 2 × 1.7e9 ≈ 35.6 TFLOPS。
- 内存吞吐:带宽 × 访问效率。RTX 3090 的 GDDR6X 带宽约 936 GB/s,若合并访问效率只有 50%,有效带宽降至 468 GB/s。
- 延迟隐藏能力:驻留 Warp 数 × 每 Warp 可独立发射的指令数。若每个 SM 只能驻留 16 个 Warp,而每个 Warp 平均每 4 条指令就停顿一次,则调度器经常无就绪 Warp 可发射。
sm__throughput.avg.pct_of_peak_sustained_elapsed 和 dram__throughput.avg.pct_of_peak_sustained_elapsed。若前者接近 100% 而后者较低,说明计算受限;若后者接近 100%,说明内存带宽受限;若两者都不高,则很可能是延迟隐藏不足(Occupancy 低或依赖链太长)。
六、Occupancy:延迟隐藏的量化指标
Occupancy(占用率) = 实际驻留 Warp 数 / SM 支持的最大 Warp 数。它受三个因素限制:
- 寄存器用量:每线程寄存器数 × 线程数 ≤ 寄存器文件容量。
- 共享内存用量:每 Block 共享内存 × Block 数 ≤ SM 共享内存容量。
- Block 数上限:每个 SM 最多驻留的 Block 数(如 32 个)。
例如一个 Kernel 每线程用 40 个寄存器、每 Block 256 线程、每 Block 用 32KB 共享内存。寄存器限制:64K/(40×256) ≈ 6.4,取整为 6 个 Block;共享内存限制:128KB/32KB = 4 个 Block;Block 数上限 32。最终取最小值 4 个 Block,即 1024 个线程 = 32 个 Warp。若 SM 最大支持 64 个 Warp,则 Occupancy = 50%。
Occupancy 不是越高越好,但过低会直接削弱延迟隐藏能力。Nsight Compute 的 sm__warps_active.avg.pct_of_peak_sustained_active 就是这一指标。实践中,先确保 Occupancy 不低于 50%,再根据具体瓶颈调整寄存器或共享内存用量。