核心改进
- 执行循环与 API 服务 — 优化执行循环的 CPU 性能瓶颈,同时重构 API 服务层。
- 简单灵活的调度器 — 统一处理 prefill 和 decode 两类请求。
- 零开销前缀缓存 — 基于哈希的前缀缓存,配合 LRU 驱逐策略。 缓存命中率对性能的影响是非线性的:命中 0% 时几乎没有收益,但命中超过 50%、75% 时,性能分别有约 2 倍、4 倍的提升。
- 清晰的张量并行架构 — V0 为了解决进程间通信开销,把调度器和 worker 0 放在同一进程中,减少把输入数据广播到 workers 时的进程开销。这种非对称设计增加了复杂度。V1 让调度器和 worker 0 在独立进程中运行,形成对称架构;worker 端缓存请求状态,每个步骤只传输增量更新,从而把进程间通信降到最低。
- 高效的数据输入 — V1 实现了 Persistent Batch 技术,缓存输入张量并每步只应用差异,同时广泛使用 NumPy 操作替代 Python 原生操作,最大限度减少更新张量时的 CPU 开销。
- torch.compile 与分段 CUDA 图 — 降低内核启动与图捕获开销。
- FlashAttention 3 — V1 的最后一块拼图,见下文。
- 增强多模态 LLM 支持。
批处理策略的演进
朴素批处理与静态批处理
类似 CPU 单线程处理:整个批次要等每个序列都处理完毕,才一起释放资源并返回结果。这会造成大量 GPU 占用,尤其当同一批次内序列长度差异较大时,对吞吐量影响明显。Persistent Batch(连续批处理)
类似 CPU 优化中的流水线原理,目标是尽可能不让 GPU 闲置。它采用迭代级调度,批大小在每次迭代时确定。一旦批中某个序列完成生成,就可以在其位置插入新序列,从而实现比静态批处理更高的 GPU 利用率。 现实情况比这个简化模型更复杂:预填充阶段需要计算,且与生成阶段的计算模式不同,因此很难与 token 生成一起批处理。连续批处理框架目前通过超参数来管理这个问题——例如等待已服务请求数与等待结束序列标记的请求数之比(waiting_served_ratio)。
FlashAttention 3 集成
vLLM V1 的最后一块拼图是集成 FlashAttention 3。鉴于 V1 中高度的动态性(例如在同一批次中组合预填充和解码),一个灵活且高性能的注意力内核至关重要。FlashAttention 3 有效地满足了这一要求,为广泛的功能提供了强大支持,同时在各种用例中保持了出色的性能。相关笔记
PagedAttention 与 KV Cache
Prefill 与 Decode 两阶段的计算特征,以及分页式 KV Cache 管理。
FlashAttention 分块计算
从 online softmax 的公式推导到分块 Attention 算子的实现。