vLLM:从 KV Cache 到高吞吐推理服务

这组文章面向已经理解 Transformer 自注意力、但刚开始阅读 vLLM 源码的读者。它不按目录罗列类名,而是从推理服务的三本成本账出发:每个 token 要做多少计算,历史状态怎样占用和流动,多个长度不同的请求怎样共享有限的 GPU 时间与显存。

总体知识图

Transformer 自回归生成
        │
        ├─ 重算历史太贵 ───────────→ KV Cache
        │                                │
        │                                ├─ 容量与并行公式
        │                                ├─ 分页分配 → PagedAttention
        │                                └─ 跨请求复用 → Prefix Caching
        │
        ├─ 请求长度和到达时间不同 ──→ Continuous Batching
        │                                ├─ Scheduler / token budget
        │                                └─ Chunked Prefill
        │
        ├─ 单个执行步骤仍有成本 ─────→ 量化 / 推测解码
        │
        └─ Attention 中间结果搬运昂贵 → FlashAttention

上述机制由 vLLM V1 的前端、EngineCore、Scheduler、KVCacheManager、
ModelExecutor、GPUModelRunner 与 attention backend 串成一次完整请求。

分页、缓存、调度和 kernel 不是互斥菜单。PagedAttention 组织 KV 的物理存储,Automatic Prefix Caching 复用兼容的完整前缀块,Scheduler 决定本轮推进哪些 token,FlashAttention 等 backend 执行具体注意力计算。只有把层次分开,才能解释为什么“功能已开启”不等于“端到端一定更快”。

推荐阅读顺序

  1. KV Cache:从历史重算到容量公式
    先建立 prefill、decode、K/V shape 和单 token 容量的共同语言。

  2. vLLM V1 架构:请求怎样穿过推理服务
    区分 HTTP 前端、核心循环、控制面消息和 GPU tensor。

  3. PagedAttention:从显存碎片到 slot mapping
    沿 logical block、physical block、BlockTable 和 slot 看分页 KV 的完整寻址链。

  4. Automatic Prefix Caching:从 hash chain 到物理块复用
    解释什么前缀可以安全复用,以及命中率为什么不能直接换算成延迟收益。

  5. Scheduler:Continuous Batching 与 Chunked Prefill
    用逐轮时间线理解 token budget、请求状态、抢占和长 prompt 切块。

  6. 量化与推测解码:怎样减少每轮成本
    分清权重、激活、KV 与累加器,再理解 proposer/target 的分布修正。

  7. FlashAttention:从 IO-aware tiling 到 Hopper 异步流水
    从 online softmax 走到 FlashAttention v1、v2、v3,并与近似 attention 区分。

七篇教程的详细目录

1. KV Cache:先把每个 token 的状态账算对

2. V1 架构:从 API 到 GPU 的对象变换

3. PagedAttention:从碎片到 kernel 地址

4. Automatic Prefix Caching:精确身份与生命周期

5. Scheduler:把“阶段”还原成统一进度

6. 量化与推测解码:两条独立成本轴

7. FlashAttention:完整 attention 的 IO 优化

完整知识依赖图

Transformer hidden state
  └─ Q/K/V projection + RoPE
       └─ 自回归 KV Cache
            ├─ 容量公式 ── TP/PP/DP 与 dtype
            ├─ 物理存放 ── PagedAttention
            │                 ├─ BlockPool / BlockTable / slot mapping
            │                 └─ ref count + hash ── APC
            ├─ 每轮增长 ── Scheduler
            │                 ├─ Continuous Batching
            │                 ├─ Chunked Prefill
            │                 ├─ preemption/recompute
            │                 └─ speculative token budget
            ├─ 每元素 byte ── KV quantization
            └─ 每步 attention IO ── FlashAttention/backend

HTTP/chat template/tokenizer
  → EngineCoreRequest
  → EngineCore/Scheduler(CPU 控制面)
  → SchedulerOutput / block ids
  → GPUModelRunner(打包 tensors/metadata)
  → model layer / attention backend(GPU 数据面)
  → sampled ids → detokenize/SSE

建议按依赖从上往下读。若直接从 FlashAttention kernel 开始,很容易不知道 block table 从哪里来;若只读 Scheduler,又会把 token budget 与显存 byte 混为一谈。

根据问题查文章

我的问题 先读 重点章节
K/V 是从哪里算出来的? KV Cache QKV projection、shape、RoPE
为什么生成 5 个 token 只回灌 4 个? KV Cache prefill/decode 与停止边界
TP=16 为什么 KV 没继续减半? KV Cache rank-local heads 与复制
HTTP 请求最后怎样变成 GPU tensor? V1 架构 两条调用链与对象生命周期
async、process、worker、rank 有什么区别? V1 架构 进程边界图
block table [7,3,11] 怎么读? PagedAttention logical→physical→slot
slot 是不是一个 K/V? PagedAttention slot payload 与 cache spec
ref=0 为什么还能命中? PagedAttention/APC free queue 与 cached 候选
APC 命中 99%,为什么速度没快 99%? APC counter delta 与指标拆分
开头一个 token 不同,后面为何全 miss? APC parent hash chain
num_computed_tokens 为何在 GPU 完成前增加? Scheduler 已提交进度与 output 校正
placeholder 会不会让 EOS 后多算? Scheduler async placeholder
Chunked Prefill 会减少总 FLOPs 吗? Scheduler 6000-token 时间线
INT4 权重是否先完整还原成 BF16? 量化/推测解码 W4A16 kernel 数据流
KV pool 启动已分配,为何请求还要 allocate? 量化/分页 物理 pool 与逻辑 ownership
第三个 draft 被拒后从哪里取新 token? 量化/推测解码 正残差分布
FlashAttention 为什么仍是 $N^2$? FlashAttention exact tiling 与 IO
v1 已并行,v2 还优化什么? FlashAttention sliced-K→sliced-Q
Q/K 同时 Hadamard 为何保持分数? FlashAttention $QRR^TK^T$ 推导

术语索引

术语 本系列中的精确定义
prefill 并行处理尚未计算的输入前缀并建立各层 KV
decode 用新 token 与已有 KV 继续 next-token prediction
TTFT 请求起点到首个输出 token 的时间;起点必须声明
ITL/TPOT 连续输出 token 之间/每输出 token 的延迟
block 固定数量 token slots 的分配/布局单位,需说明层次
slot 一个 token 在 physical block 内的缓存位置,不是单个标量
block table 每请求 logical block index 到 physical block id 的映射
KV pool 引擎启动时预留的物理 cache 存储;不同 replica 通常独立
APC 跨兼容请求复用精确 full-prefix KV blocks
token budget 单次 iteration 最多安排的 token positions,不是输出上限
rank 分布式 worker 进程在全局或并行组中的编号
backend 定义 attention metadata/layout/capability 并执行具体路径的实现
exact attention 保留完整连接/数学目标;仍会有浮点舍入
registered candidate 源码中可发现的实现,不代表本次运行已选择

证据标记与阅读约定

文章把结论分成六层:通用数学/算法、论文描述、vLLM v0.20.0 静态源码、注册候选、启动日志/trace 的动态路径、profiler/benchmark 的性能结果。出现“可能”“理论 payload”“教学伪代码”不是含糊,而是在阻止跨层推理:

源码有实现 ≠ 当前配置选中
当前配置选中 ≠ 这次真的执行
这次执行过 ≠ 它是性能瓶颈
容量减半 ≠ 延迟减半 ≠ 吞吐翻倍

阅读时始终追问四件事

这套文章以 vLLM v0.20.0 为主要源码基线。版本相关的类名和路径均按该版本表述;算法结论与实测结论则分别标注,避免用一次实验替代通用规律。

主要资料