Month 4 · Kernel & 性能
下到 CUDA 层。理解为什么 FlashAttention 这种"重写 attention"的东西能快 3 倍。
Month 1-3 一直在 Python 层。这个月下到 csrc/ 和 Triton。
目标不是能徒手写 CUDA 算子,而是能读懂、能调试、能给已有 kernel 加 benchmark。
这一层最难补,但回报是面试时立刻显出底子。
softmax(QK^T / sqrt(d))V 谁都会写。
PyTorch 一行 F.scaled_dot_product_attention 就能跑。
那 FlashAttention 凭什么快 3 倍?它没改公式,到底改了什么?
先猜,再展开答案
关键洞察:现代 GPU 的瓶颈不是算力,是显存带宽。
朴素 attention 要把 N×N 的 attention matrix 实例化在 HBM(GPU 主显存)。 对 N=4096,这个矩阵 16M 个 float,一来一回搬 64MB 数据。 SM (compute unit) 大部分时间在等显存,不在算。
FlashAttention 的做法:
- 把 Q、K、V 切成 tile,每次只把一对 tile 加载到 SRAM(GPU 内 L1 级缓存,几十 KB,巨快)。
- 在 SRAM 里算完这对 tile 的部分 attention,累加到输出。
- 用"online softmax"技巧让 tile-by-tile 计算等价于一次性算。
- 从头到尾 attention matrix 不实例化,只有最终 O 矩阵搬回 HBM。
本质 = IO-aware,工艺级地少搬数据。算力没省,但显存搬运省了一个数量级。
01GPU 内存层级 · 必知
这是最基础的硬件背景。看完这张图,FlashAttention 才能 click。
记住的数字:SRAM 比 HBM 快 10×。任何能减少 HBM 访问的优化,回报都是显著的。 FlashAttention 是工程界 IO-aware 的代表作。
02FlashAttention 的核心 trick · online softmax
softmax 看起来不可分(需要先求总和再归一):
softmax(x_i) = exp(x_i) / Σ exp(x_j)
看似要等所有 x_j 算完才能算分母。online softmax 的技巧:分块算时,每块结束维护一个 running max + running sum,下一块来了就校正之前的累加值。数学等价、可分块。
03vLLM 用了哪些 kernel
vLLM 不写自己的所有 kernel。生态分工:
| 组件 | 来源 | 位置 |
|---|---|---|
| 朴素 attention (prefill) | FlashAttention v2/v3 (Dao-AILab) | 外部依赖 flash-attn |
| Paged attention (decode) | vLLM 自写 | csrc/attention/paged_attention_*.cu |
| Triton attention | vLLM 自写 (跨架构) | vllm/attention/ops/ 或 vllm/v1/attention/ |
| RMSNorm / RoPE / activation | vLLM 自写小算子 | csrc/layernorm_kernels.cu 等 |
| GEMM (大矩阵乘) | cuBLAS / cuBLASLt | NVIDIA 闭源 |
| MoE expert kernel | vLLM + 社区 | vllm/model_executor/layers/fused_moe/ |
"vLLM 自写"的最多的是 paged attention——因为它要支持非连续 KV 地址(block table 索引),这是其他 attention 库不直接支持的。
04读源码 · csrc/attention
这是本月最硬核的一段。看不懂不要怕,目标只是认出结构。
paged_attention_v1.cu 和 paged_attention_v2.cu。v2 在 v1 基础上加了 partition (分片处理超长 sequence)。
csrc/attention/paged_attention_v1.cu · paged_attention_v1_kernel__shared__修饰的变量 = SRAM 上的 buffer。- 外层 for 循环遍历 KV block,里面 for 遍历 token in block。
block_table索引把逻辑 block → 物理 block 偏移。- online softmax 的 max 和 sum 累加变量。
csrc/attention/attention_kernels.cu 或 pybind.cppTORCH_LIBRARY_FRAGMENT 或 PYBIND11_MODULE。
05Triton · 比 CUDA 友好的中间层
CUDA 上手太陡。Triton 是 OpenAI 出的Python-like DSL,编译成 CUDA。 vLLM 用 Triton 写跨架构 kernel(AMD ROCm 也能跑)。
动手任务(不可跳):
- 1: vector add — 学 program_id / load / store。
- 2: fused softmax — 学 SRAM 上做归约。
- 3: matmul — 学 block tiling、autotune。
做完 tutorial 回头看 vLLM 的 Triton kernel:
vllm/attention/ops/paged_attn.py (或 v1 路径下)06动手 · 跑 benchmark 画图
本月的硬核作业。这一份数据将出现在你的简历/blog 上。
实验设计
- 在云上 A100 / A10 跑
benchmarks/benchmark_throughput.py或benchmark_serving.py。 - 变量:batch size (1, 8, 32, 64)、prompt 长度 (128, 1024, 4096)、output 长度 (64, 256)。
- 记录:throughput (tokens/s)、TTFT、p50/p99 latency。
- 用 matplotlib 画图:
- throughput vs batch size(应该 sub-linear 增长)
- TTFT vs prompt length(应该线性)
- 显存占用 vs 并发数
对照实验(可选但加分)
- 对比
--enforce-eager(关 CUDA graph)vs 默认。CUDA graph 减少 launch overhead,在 decode 阶段差异显著。 - 对比不同 attention backend(
VLLM_ATTENTION_BACKEND=FLASH_ATTNvsXFORMERSvsFLASHINFER)。 - 对比
--max-num-seqs不同值看 throughput-latency Pareto。
- Throughput 随 batch size 增长但不线性:到某个点 KV 显存撑爆,preempt 开始,曲线压平。
- TTFT 在大 batch 下显著上升(prefill 排队)。
- FlashAttention vs XFormers 差异在 ~10-30%。
- CUDA graph 在 decode 重的负载下给 5-15% 提速。
07本月 PR 候选
- 给某个 Triton kernel 加 autotune(不同 BLOCK_SIZE 上 benchmark 自动选最佳)。
- 新 GPU 架构(如 H100、B100)的 kernel 适配补全。
- 修一个 numerical issue(fp16 vs fp32 累加导致的精度问题)。
- 补 benchmark:给某个少 cover 的 workload 加 benchmark 脚本 + baseline 数据。
08速补材料
- CMU 15-418 Parallel Computing · 课程主页 — 第 1-2 课讲缓存层级 + GPU 架构,比 CS162 强。
- CUDA C++ Programming Guide · chapter 4-6(thread hierarchy, memory model)。一个下午翻完。
- Triton 官方 docs · tutorial 1-5 必做、6-7 选做(FP8)。
- FlashAttention 论文 · v1 (NeurIPS'22) 读懂思想;v2/v3 可选。