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 的具体实现