LLM 推理框架的优化目标与手段
之前我对推理框架的理解比较散,知道有 Continuous Batching、有 FlashAttention、有量化,但到底在优化什么、为什么优化这些,其实没串起来。聊完之后才觉得,还是得先回到目标上,不然容易把手段当成目的。
一个常见的误解:推理框架只改 Attention
刚开始接触这块的时候,我默认推理框架的主要工作就是加速 Attention。毕竟 Transformer 里 Attention 是 ,看起来最吓人,FlashAttention、PagedAttention 这些名字也最响。
但其实不是。 Attention 占了计算量的显著部分,但模型参数的大头在 MLP(通常 2/3 以上),推理时的 HBM 带宽也大量消耗在读取 MLP 权重上。vLLM、SGLang 这些框架对 MLP 的优化非常多,只是性质和 Attention 不一样:
- 算子融合:把 gate_proj、up_proj、activation、down_proj 打包成一个 kernel,减少反复读写 HBM
- 权重量化:FP8/INT8/INT4 的 MLP 权重,降低显存带宽压力
- MoE 优化:Grouped GEMM、专家路由 overlap、热门专家缓存
- CUDA Graph:捕获整个 decode step,消除 CPU 调度开销
MLP 没有 KV Cache,不是因为大家懒得做,而是因为 MLP 的计算本身是无状态的——每个 token 的 MLP forward 独立,没有历史状态需要复用。Attention 的 KV Cache 是为了避免重复计算历史 token 的 Key 和 Value,MLP 不存在这个问题。
所以推理框架的优化是全栈的,不是只围着 Attention 转。
优化的四个目标
聊具体手段之前,先分清楚到底在优化什么指标。不同场景对这些指标的敏感度差别很大:
| 目标 | 含义 | 敏感场景 |
|---|---|---|
| TTFT | 首 token 延迟 | 聊天机器人、实时交互 |
| TBT | 相邻 token 的生成间隔 | 流式输出、代码补全 |
| 吞吐 | 单位时间生成的 token 数 | 高并发服务、批处理 |
| 显存效率 | 同等 GPU 能塞多少并发 | 成本敏感、长上下文 |
这些目标经常打架。比如增大 batch size 提升吞吐,但会恶化 TTFT;降低精度提升速度,但可能损失质量。没有万能配置,只有针对场景的权衡。
优化的四个层次
graph TD
A[模型/算法层] --> B[算子/Kernel 层]
B --> C[运行时/调度层]
C --> D[并行/分布式层]
D --> E[硬件执行]
A1[KV Cache / MLA] --> A
A2[量化 / MoE] --> A
A3[FlashAttention] --> A
B1[算子融合] --> B
B2[CUDA Graph] --> B
B3[Triton Kernel] --> B
C1[Continuous Batching] --> C
C2[Chunked Prefill] --> C
C3[Prefix Caching] --> C
D1[TP / PP] --> D
D2[DP / EP] --> D
1. 模型/算法层
这一层改动的是模型本身或计算方式:
- KV Cache:标准做法,缓存历史注意力状态
- PagedAttention / RadixAttention:把 KV Cache 分页管理,消除显存碎片
- FlashAttention / FlashInfer:IO-aware 的 attention 算法,减少 HBM 访问
- MLA:压缩 KV Cache 维度,从架构上减少需要缓存的数据量
- Speculative Decoding:小模型快速生成候选,大模型并行验证
- 量化:FP8/INT8/INT4 权重和激活
- MoE:只激活部分专家,降低实际计算量
2. 算子/Kernel 层
这一层让单次计算更快:
- 算子融合:减少 kernel launch 和显存来回搬运
- 手写 Kernel:用 Cutlass / Triton 针对特定 shape 和精度做定制
- CUDA Graph:捕获静态计算图,消除 CPU 调度开销
- 编译优化:Torch.compile / XLA 自动融合、算子选择
3. 运行时/调度层
这一层让请求排布更合理:
- Continuous Batching:动态插入新请求,避免 GPU 空转
- Chunked Prefill:把长 prefill 切成块,和 decode 混排,减少阻塞
- Prefix Caching:复用重复的系统提示词或多轮对话前缀
- Disaggregated Serving:把 Prefill 和 Decode 拆到不同 GPU 上,各自优化
4. 并行/分布式层
这一层横向扩展:
- Tensor Parallelism (TP):把单层网络切开,通信量在卡间
- Pipeline Parallelism (PP):把不同层放不同机器,流水线执行
- Sequence Parallelism (SP):把序列维度切开,配合 Ring Attention
- Expert Parallelism (EP):MoE 模型里不同专家放不同卡
一个容易搞混的点
我以前容易把”量化”和”KV Cache 压缩”混为一谈。量化是降低权重或激活的精度,减少数据搬运量;KV Cache 压缩是减少需要缓存的数据量(比如 MLA 把 KV 压到低维 latent space)。两者可以叠加,但解决的问题不一样。
同样,Continuous Batching 和 Chunked Prefill 也经常被混淆:
- Continuous Batching 解决的是”GPU 不要空转”——新请求动态加入当前 batch
- Chunked Prefill 解决的是”长 prefill 不要饿死 decode”——把 prefill 切成小段调度
写到这才发现,我之前对推理框架的理解确实是一张散的名词表。现在至少能把”目标”和”手段”分开看,也能理解为什么同样一个技术在不同场景下的效果完全不一样——比如 Continuous Batching 能提升吞吐,但会拉长 TTFT。