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 直接量化这一开销。

GPU 内存层次与吞吐量模型 SM(流式多处理器) Warp 调度器 ×4 每周期发射 1 条指令 FP32 核心 ×128 4 处理块 × 32 寄存器文件 64K × 32bit / SM 共享内存 / L1 ~20-30 周期延迟 L1 / Tex 缓存(与共享内存共享物理空间) 命中延迟 ~30 周期 LD/ST 单元 + SFU 负责全局/共享内存访问 L2 缓存 全 GPU 共享 命中延迟 ~200 周期 DRAM 显存 HBM/GDDR 延迟 ~400-800 周期 延迟与带宽对比(相对量级) 共享 L1 L2 DRAM(延迟最高,带宽有限) 吞吐量模型:实际吞吐 = min(计算吞吐, 内存吞吐, 延迟隐藏能力) 计算吞吐 = SM 数 × 每 SM 核心数 × 频率 × 每周期指令数 内存吞吐 = 带宽 × 访问效率(合并度 × 缓存命中率) 延迟隐藏 = 驻留 Warp 数 × 每 Warp 独立指令数

四、L1/L2 缓存与内存层次

从 SM 向外,内存层次依次为:寄存器 → 共享内存/L1 → L2 → DRAM。每一层的容量、延迟和带宽差异巨大:

层级典型容量延迟(周期)作用域
寄存器64K×32bit/SM~1线程私有
共享内存/L1128~192KB/SM~20-30Block 内共享 / SM 内缓存
L24~6MB(GA102)~200全 GPU 共享
DRAM10~80GB~400-800全局

L1 和共享内存共享同一块物理 SRAM,可通过 cudaFuncAttributePreferredSharedMemoryCarveout 调整划分比例。L2 则是全 GPU 共享的末级缓存,所有 SM 的全局内存请求都经过它。Nsight Compute 中 lts__t_sector_hit_rate 反映 L2 命中率,dram__bytes.sum 反映实际 DRAM 流量。

五、吞吐量模型:三个约束取最小值

GPU 的实际吞吐量由三个约束共同决定,取其中最小值:

  1. 计算吞吐:SM 数 × 每 SM 核心数 × 频率 × 每周期指令数。例如 RTX 3090 有 82 个 SM,每 SM 128 个 FP32 核心,Boost 频率 1.7GHz,理论 FP32 吞吐 = 82 × 128 × 2 × 1.7e9 ≈ 35.6 TFLOPS。
  2. 内存吞吐:带宽 × 访问效率。RTX 3090 的 GDDR6X 带宽约 936 GB/s,若合并访问效率只有 50%,有效带宽降至 468 GB/s。
  3. 延迟隐藏能力:驻留 Warp 数 × 每 Warp 可独立发射的指令数。若每个 SM 只能驻留 16 个 Warp,而每个 Warp 平均每 4 条指令就停顿一次,则调度器经常无就绪 Warp 可发射。
判断瓶颈的实用方法:在 Nsight Compute 中先看 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%,再根据具体瓶颈调整寄存器或共享内存用量。

本节核心结论:GPU 性能 = min(计算吞吐, 内存吞吐, 延迟隐藏能力)。SM 和 Warp 决定计算粒度,寄存器和共享内存决定驻留能力,L1/L2/DRAM 决定数据供给速度。后续所有 Nsight Compute 指标,都可以映射到这三个约束中的某一个。看到指标异常时,先问:它属于计算、内存还是延迟隐藏?