Hang Zhengyang
English ↗

推理基础设施演进:从经典服务到 vLLM V1

主线是「在线 LLM 推理」的系统约束如何推动栈演进,并以 vLLM V1 作为近期引擎重构的锚点。具体 flag 与默认行为以你使用的 vLLM 版本官方文档为准。


0. 读前对齐:本文要解决什么问题

目标读者:后端与 ML 系统工程师(要写或调推理服务)、以及希望把「PagedAttention / continuous batching / V1 EngineCore」放进正确上下文的算法同学。不要求会写 CUDA。

不覆盖的范围:不逐行讲 kernel;不绑定某一云厂商托管推理产品;不把某一固定 benchmark 当作普适结论。部署参数与 VLLM_USE_V1 等开关请以 vLLM 官方文档 为准。

读完应能回答:

  1. 推理服务的瓶颈通常落在算力、显存还是 CPU 与调度?
  2. vLLM 0.x 用哪些机制把「KV + 批」从痛点变成一等公民?
  3. V1 相对 V0,主要在进程模型、执行循环、调度抽象上改了什么,动机是什么?
  4. 统一按 token 调度后,与 chunked prefill、prefix cache、投机解码如何概念上衔接?
  5. 上线时该看哪些指标、何时值得坚持 V0 或做 A/B?

1. 推理基础设施在整条链路里处于什么位置

训练负责在离线数据上更新权重,优化的是「单次训练 job 的样本吞吐与收敛」。推理在固定权重下对请求做前向,优化的是「单位硬件上的请求 SLA 与成本」。服务再叠一层:鉴权、配额、路由、多副本、弹性扩缩、可观测与回滚。本文聚焦「推理 + 服务里最像系统」的那一段:批调度、KV 生命周期、引擎循环与进程边界。

从「模型能力」到「可 SLA 的服务」之间,典型还要经过:权重加载与格式、量化与校准策略、tokenizer 与多模态预处理、attention 与 MLP 算子实现、KV cache 分配与回收、请求排队与批内组合、采样与流式输出、以及分布式下的张量并行 / 流水线并行。任一环节掉队,都会出现「GPU 不满但 P99 很高」或「吞吐上去质量抖动」的现象。

术语约定(后文沿用):

  • Prefill:对 prompt(及已生成前缀)做并行前向,一次性算出一批 KV 并产出首个或首批 logits;常对应「首包」前的计算突发。
  • Decode:自回归步进,每步通常只追加少量新 token,但要读全量历史 KV(或等价压缩表示)。
  • TTFT(Time To First Token):从请求进入到首个 token 可见的延迟,受排队、prefill、调度与 I/O 共同影响。
  • 吞吐:例如 tokens/s(单卡或整机)、或 completed requests/s;需区分是否含流式 chunk 开销。
  • KV cache:每层、每头对历史 token 的 K/V 张量缓存;是长上下文与并发下的显存大户。
  • 批调度:如何把多个进行中的请求拼进同一 GPU step,在公平性、尾延迟与利用率之间折中。
[Client] -> [API / Gateway] -> [Engine: schedule + KV + model step]
                |                    |
           tokenize /              GPU kernels
           multimodal prep         (FlashAttention 等)

2. 经典时代:在 vLLM 之前,推理栈长什么样

2.1 静态批处理与资源碎片

早期常见做法是静态批:凑满一批固定形状再送进 GPU,或按固定最大长度 padding。请求长度差异大时,短请求要等长请求凑批,或被迫 pad 到上限,造成算力空转与尾延迟拉长。在线流量波动时,还会出现「批凑不齐 GPU 饿着」或「硬凑批导致某些请求被饿死」的两难。

2.2 KV cache:从「能跑」到「能扛并发」

单请求推理可以按「最大序列长度 × 层数 × 头数 × 隐维度」粗估 KV 占用。并发一上来,若每个序列预留一块连续、固定长度的 KV 缓冲区,显存会迅速被 padding 与预留浪费占满;实际可服务的并发数远低于「理论 FLOPs」暗示的上限。于是系统瓶颈常常不是矩阵乘不够快,而是显存与碎片不允许再塞请求。

2.3 早期框架与自研服务的共性痛点

典型痛点包括:调度逻辑与 KV 生命周期耦合松散(难以做细粒度抢占与回收)、prefill 与 decode 两阶段策略不统一(调参复杂、边界情况多)、CPU 侧 tokenizer 与 Python 循环与 GPU 步同步差导致 GPU 等 CPU。算子层(如 FlashAttention)可以显著降低单步 HBM 流量,但若上层仍静态批或 KV 预留粗放,端到端 SLA 仍可能很差。vLLM 0.x 先把「KV + 连续批」做成引擎核心抽象,为 V1 进一步压低 CPU 与统一调度打下基础。


3. vLLM 0.x:一次「把内存与调度说清楚」的范式升级

3.1 PagedAttention 与 KV 的页式管理

PagedAttention 把每个序列的 KV 切成固定大小的块(block),在显存里用块表记录逻辑 token 到物理块的映射,类似虚拟内存到物理页。好处是:新 token 只申请新块,序列变长时不必为整段预留连续空间;不同序列可共享物理池,减少 padding 与外部碎片。与 OS 分页的类比止于「块表 + 按需分配」:GPU 侧仍要配合高效的块分配器与 attention kernel,且延迟敏感路径要避免频繁小块抖动。

3.2 Continuous Batching(连续批处理)

Continuous batching(或 iteration-level scheduling)在每个 decode 步(以及 prefill 的合适粒度)重新决定批内有哪些请求参与前向,已完成或暂不可调度的请求可退出,新请求可插入。相对静态批,它用更细的调度粒度换更高 GPU 占用与更短排队时间;代价是调度器与 KV 管理必须同事务化,且要处理公平性(例如长 prefill 是否挤压 decode)。

3.3 与 FlashAttention 等算子层优化的关系

FlashAttention 一类优化主要降低 attention 的 HBM 读写量与提高算子融合度,解决的是「单步算子多快」。PagedAttention + continuous batching 解决的是「多请求下显存与步进如何组织」。两者互补:没有好的 KV 与批策略,kernel 再快也会被等批、等 CPU、等内存吃掉;没有高效 attention,KV 省下来的显存也换不来足够吞吐。

3.4 0.x 架构下仍常见的瓶颈

当功能叠多(chunked prefill、prefix caching、投机解码、多模态)时,V0 引擎常见张力包括:Python 与单进程事件循环承载过多 I/O 与预处理,导致 GPU 等 CPU;prefill 与 decode 分两阶段调度使边界规则复杂、难以统一优化;调度状态分散在多处时,观测与回放困难。V1 的目标之一是把执行循环与进程边界收紧,让「每步谁干什么」更清晰。


4. 为何需要 V1:设计目标与要解的矛盾

官方对 vLLM V1 的表述可概括为:在保持用户侧 API 相对稳定的前提下,重写调度、KV 管理、worker 协调、采样与 API server 周边,得到更模块化、更低 CPU 开销、更易把多项优化默认打开的引擎。矛盾在于:在线推理的瓶颈会从「纯 GPU FLOPs」转向「CPU 预处理、调度、跨进程通信、内存分配」与「多特性组合下的状态机复杂度」。

V0 在规模化场景下的典型张力:多模态(图像/音频预处理重)、投机解码(额外小模型步与验证逻辑)、prefix cache(块级复用与失效)、chunked prefill(长 prompt 切分与 decode 交错)同时存在时,若仍用较粗的执行循环,容易出现尾延迟抖动与维护成本陡增。

本文对「V1」的界定:指 V1 引擎核心(Engine Core、统一 token 调度、多进程架构等),不是「vLLM 第一个大版本号」的日常语义。启用方式以官方为准(常见为环境变量 VLLM_USE_V1=1,见 v1 guide);默认是否开启随发行版变化,升级前请读 release note。


5. vLLM V1 总览:进程模型与职责切分

5.1 API Server 与 Engine Core 的多进程分离

V1 将 HTTP/OpenAI 兼容 API 与 GPU 侧引擎循环拆到不同进程:API Server 侧重连接、请求解析、tokenizer、多模态预处理、流式回写;Engine Core 侧重调度、KV 生命周期、驱动模型前向与采样决策。动机是避免「一个进程里 GIL + I/O + 紧循环」互相抢时间片:GPU 步应尽量不被 tokenize 或网络 flush 打断,I/O 高峰也不应拖死调度逻辑。

5.2 EngineCore 执行循环:把 GPU 重叠与 CPU 工作拆开讲

Engine Core 内形成更清晰的 step loop:调度决定本步各请求贡献多少 token(含 chunked prefill 与 decode),再触发模型执行与 KV 更新,最后采样并产出 token 流。API 侧可在等待 GPU 的窗口并行做 detokenization、流式 chunk 切分 等,使 CPU 密集工作与 GPU 步重叠。多模态场景下,把重预处理移出紧耦合路径,对 TTFT 尤其关键。

5.3 与 V0 的复用边界

倾向复用的包括:具体模型实现、许多 GPU kernel、分布式张量并行 / 流水线并行等成熟组件。在 V1 重写或大改的包括:调度器抽象、KV cache 管理、worker 编排、采样路径与 API server 周边胶水。对用户而言,许多入口 API 保持兼容,但内部 trace、日志与调参项可能变化,迁移时应用同一套负载做 A/B。


6. V1 调度器:从「prefill vs decode」到统一 token 视角

V1 调度的一大叙事是:不再把 prefill 与 decode 当成两套硬编码阶段拼在一起,而是把每步工作统一成「本步各请求要跑多少 token」。实现层面常用形如 { request_id: num_tokens } 的映射表达:有的请求本步是 decode 的 1 个 token,有的是 chunked prefill 的一段,有的是投机解码里待验证的若干 token。统一抽象的好处是:调度决策与资源 accounting(KV 块、算力预算)用同一套语言,减少边界 bug,也更容易与 prefix cache、speculation 组合。

与周边特性的衔接(概念层):

  • Chunked prefill:长 prompt 切成多段参与多步调度,每步的 num_tokens 对应一段,避免单次 prefill 独占 GPU 过久、恶化他人 TTFT。
  • Prefix caching:共享前缀对应 KV 块复用;调度需与块失效策略一致,避免命中错误前缀或回收竞态。
  • Speculative decoding:草稿 token 与验证步可映射为「某些请求本步多 token、某些请求仅验证」,统一调度器更易做原子接受/回退与资源回滚。

工程上仍需显式策略回答:公平性(decode 是否长期被长 prefill 挤压)、饥饿(低优先级请求是否永不入批)、多租户配额(硬隔离 vs 软限流)。V1 简化的是模型内部状态机,不是产品层 SLA 规则。


7. V1 中的 KV cache 管理、Worker 与采样路径

7.1 KV cache manager 在 V1 中的角色

KV manager 仍是 PagedAttention 块池 的守护者:分配、回收、块表维护、prefix 块引用计数、与调度器协同决定「本步哪些块参与 attention」。V1 的变化主要在与 Engine Core 循环紧耦合:调度一确定 token 计划,KV 侧就同步更新块需求,减少跨模块往返。细节以你所用版本的 design doc 为准。

7.2 Worker 与模型执行协调

Worker 负责执行具体模型前向(可能多进程 / 多线程配合分布式)。单卡场景相对直接;张量并行(TP) 时需在一次 model step 内做 collective;流水线并行(PP) 时微批与 stage 对齐更敏感。V1 通过更清晰的 core loop,把「一步内各 rank 要跑什么」与调度状态对齐,降低异步边角(仍以官方并行文档为准)。

7.3 Sampler 与解码策略

Sampler 在 logits 之后:温度、top-k/top-p、重复惩罚、结构化约束(如 JSON/grammar)等。投机解码路径上,采样还要与「草稿提议 / 主模型验证 / 接受长度」协同。V1 将采样与引擎循环边界收拢,有利于流式输出与批量验证时的稳定顺序;约束解码若走外部库,仍需注意与多进程间序列化开销。


8. 生态位:vLLM V1 与周边系统如何拼图

没有「永远最优」的推理栈,只有约束匹配。粗略对比维度:

维度 vLLM V1 TensorRT-LLM 等重度编译栈 TGI / 其他服务框架 自研
迭代速度 高(Python + 模块化引擎) 中低(编译与插件成本) 中高 视团队而定
极限单请求延迟 强依赖模型与配置 常极强(高度融合) 因版本而异 可定向优化
高并发 KV / 批 核心强项之一 强但绑定 NVIDIA 生态多 成熟方案多 需自建全套
可控与可观测 好(开源、可 fork) 部分黑盒 中等 完全可控成本高

硬件侧:HBM 带宽与kernel 融合决定单步上限;NVLink / 网络决定 TP/PP 扩展;量化(W8A8、FP8、KV cache 量化)减轻显存与带宽,但引入校准与精度风险。V1 倾向把多项优化默认纳入引擎路径,仍建议用真实负载验证 TTFT 与吞吐的帕累托前沿。


9. 落地与迁移:从读到用

启用与回退:以官方 v1 guide 为准配置 VLLM_USE_V1 等开关;发版说明中若标注某特性在 V1 仍为 beta,应先在预发环境压测。回退策略通常是关 V1 或固定镜像版本,并保留同一模型权重与 tokenizer。

建议观测指标:

  • GPU:SM 利用率、HBM 吞吐、是否频繁出现「低利用高队列」。
  • CPU:API 进程 CPU、tokenize 耗时分布;若 CPU 打满而 GPU 闲,优先查 I/O 与预处理并行度。
  • 调度:队列深度、batch 内请求数、每步 prefill vs decode token 占比(若可导出)。
  • 延迟:P50/P99 TTFT、inter-token latency;长 prompt 与短 prompt 分桶。
  • 显存:KV 池占用率、OOM 前是否有块分配失败尖峰。

何时仍用 V0 或混合部署:线上依赖的某插件、自定义 hook 或调度假设尚未在 V1 验证;或短期内需要与旧 trace 完全对齐做排障。应用同 workload 做灰度,比较 P99 与 OOM 率,而不是只看平均吞吐。


10. 小结与延伸阅读

Takeaway:

  1. 推理基础设施的主战场是 KV + 调度 + CPU/GPU 重叠,不单是算子。
  2. vLLM 0.x 用 PagedAttention 与 continuous batching 把显存与批处理变成可组合抽象。
  3. V1 通过 API Server / Engine Core 分离 与 统一 token 调度,降低 CPU 争用与状态机复杂度,便于默认叠特性。
  4. 迁移应用真实流量压 TTFT 尾延迟 与 OOM,并保留版本回退。
  5. 生态选型按 延迟 / 吞吐 / 可控 / 绑定硬件 四维决策,避免唯 benchmark 论。

延伸阅读(完整 URL):


附录 A:术语表

术语 一句话
PagedAttention 按块管理 KV 显存、块表映射逻辑序列,减少预留与碎片。
Continuous Batching 每步(或每迭代)动态重组批内请求,提高 GPU 占用、降低排队。
Engine Core V1 中负责调度、KV、驱动模型步与采样的引擎执行核(多进程架构中的一环)。
Chunked prefill 将长 prompt 分多步 prefill,缓和单次占用与对他人 TTFT 的影响。
Prefix caching 复用共享前缀对应的 KV 块,加速相同 system prompt 等场景。
Speculative decoding 小模型草稿 + 大模型并行验证,用额外算力换 decode 步数减少。

附录 B:插图与表格占位

  • 图 1:V0(单进程紧耦合)vs V1(API Server 进程 ↔ Engine Core 进程)数据流与 GIL/CPU 边界。
  • 图 2:统一调度下,三个请求在时间轴上的 chunked prefill / decode / 投机验证交错示意。
  • 表 1:推理栈自检清单:队列、批、KV、算子、分布式、量化、观测各一项「健康标准」与「异常信号」。