GPU 架构

GPU 硬件 推理优化

GPU 架构

CPU 和 GPU 的设计哲学完全相反。CPU 的目标是让一条指令跑得越快越好,用了大量晶体管做分支预测、乱序执行、超大 cache——本质是优化延迟(latency)。GPU 反过来,不追求单条指令快,而是同时扔出几千个线程,靠数量压死带宽——优化吞吐(throughput)。

这对推理很重要:矩阵乘法的每个输出元素相互独立,天然适合大量并行;而 Decode 阶段一次只生成一个 token,并行度低,GPU 算力大量空置——这就是为什么 Decode 是瓶颈。

执行单元:从芯片到线程

GPU 的层级结构:

GPU
└── SM(Streaming Multiprocessor)× N
    ├── CUDA Core(标量浮点/整数运算)× 多个
    ├── Tensor Core(矩阵乘法专用)× 多个
    └── Shared Memory / L1 Cache(片上,快)

A100 有 108 个 SM,GA100 架构每个 SM 有 4 个 Tensor Core partition。

程序员写的是 thread,实际执行单位是 warp(32 个线程一组),硬件把一个 warp 的 32 个线程当成一条宽指令执行(SIMT:Single Instruction Multiple Threads)。多个 warp 组成 block,block 分配到某个 SM 上跑,shared memory 是 block 内所有 warp 共享的。

warning: SIMT 的隐患:控制流分叉 同一个 warp 的 32 个线程必须执行同一条指令。如果代码里有 if/else,有些线程走 if,有些走 else,warp 就要执行两遍:先让走 if 的线程活跃、走 else 的线程掩蔽,再反过来。利用率减半。 写 GPU kernel 要尽量让同一 warp 内的线程走相同分支。

内存层级:速度和容量的权衡

层级 位置 延迟 典型大小
Shared Memory / L1 SM 片上 23–33 CPI 几十 KB/SM
L2 Cache 芯片上(跨 SM 共享) 200 CPI 几十 MB
HBM(Global Memory) 芯片外(显存) 290 CPI 40–80 GB

Shared Memory 比 HBM 快 10 倍以上,这就是 tiling 和 fused kernel 的基础:尽量把数据搬到 shared memory 里反复使用,减少对 HBM 的读写。

H100 的 SRAM 总带宽约 33 TB/s,HBM 带宽约 3.35 TB/s。中间结果写一次 HBM 再读回来,等效于浪费了 10 倍的时间。

Tensor Core:矩阵乘法专用硬件

从 V100 开始引入。普通 CUDA Core 是标量单元,每次算一个数;Tensor Core 是矩阵单元,一次算一个 16×16 矩阵块。对矩阵乘法,Tensor Core 比 CUDA Core 快 >10 倍——同样 32 个线程,Tensor Core 路径每时钟算 4096 次乘加,CUDA Core 算 32 次。

LLM 推理里绝大多数算力(Attention、FFN)都是矩阵乘法,所以 Tensor Core 利用率是衡量 GPU 效率的关键指标。低精度(FP32→FP16)还有额外收益:数据量减半,同样带宽能搬更多数,arithmetic intensity 翻倍。

详细的 Tensor Core vs CUDA Core 对比、精度分工、以及 Blackwell 引入的 TMEM,参见 Tensor Core 与 CUDA Core。

算力增长 vs 带宽增长:20 年的分裂

指标 20 年增长倍数 每两年增长
GPU 计算能力(FLOP/s) 60000x ~3x
内存带宽(GB/s) 100x ~1.6x

算力涨得比带宽快太多了。这不是暂时的失衡,是结构性的。Tensor Core 引入后矩阵算力又跳了一跳,但 HBM 带宽没有同步跟上。

结果就是 memory wall:大量操作不是被算力限制,而是被数据搬运限制。Decode 阶段每步只生成一个 token,整个权重矩阵搬过来只做几次乘法,arithmetic intensity 极低,完全卡在带宽上(见 内存带宽瓶颈)。

让 GPU 跑快的六个方向

1. 控制流收敛:消除 warp 内分叉,让 32 个线程走同一条路。

2. 低精度:FP32→FP16→INT8,数据量缩小,带宽压力降低,Tensor Core 利用率提升。

3. Operator Fusion:多个 kernel 合并,中间结果不落 HBM(参见 Fused Kernel)。

4. Recomputation(激活重算):backward 不存中间激活,直接重算,用算力换显存带宽——现在算力过剩,这笔账合算。

5. Memory Coalescing(内存合并访问):同一 warp 的 32 个线程最好访问 32 个连续地址,硬件会把它们合并成一次 DRAM burst。如果访问的是随机地址,就变成 32 次独立请求,带宽利用率骤降。

6. Tiling:把大矩阵切成小 tile,载入 shared memory,tile 内反复使用,减少 HBM 访问。如果 tile 大小是 T,global memory 访问量就减少 T 倍。FlashAttention 就是 attention 的 tiling 实现(参见 Fused Kernel)。

Wave Quantization:一个反直觉的性能坑

A100 有 108 个 SM。一个矩阵乘法被拆成多少个 tile,就叫多少个 “wave”。

当 tile 总数是 108 的倍数,每个 SM 承担整数个 tile,完美利用;当 tile 总数是 109,就需要 2 个 wave,第二个 wave 只有 1 个 tile 但占用了 108 个 SM 的调度周期——实际利用率 1/108。

实际案例:维度从 1792 变成 1793,tile 数从 98 跳到 120(超过 108),从单 wave 变成双 wave,速度骤降。

Karpathy 在 nanoGPT 里把 vocab size 从 50257 改成 50304(最近的 64 倍数),速度提升 25%,原因就是 tiling alignment 让 wave 数整除了 SM 数。

tip: 实践建议 矩阵维度尽量取 64 或 128 的倍数。不是为了"对齐"而对齐,是为了 tile 数整除 SM 数,避免 wave quantization 性能坑。

相关来源

  • cs336-lecture5-gpus:本页主要来源,Part 1-2
  • 内存带宽瓶颈:arithmetic intensity 和 memory-bound 分析
  • Fused Kernel:tiling 和 operator fusion 的具体实现