推理基础设施演进:从经典服务到 vLLM V1
主线是「在线 LLM 推理」的系统约束如何推动栈演进,并以 vLLM V1 作为近期引擎重构的锚点。具体 flag 与默认行为以你使用的 vLLM 版本官方文档为准。
0. 读前对齐:本文要解决什么问题
目标读者:后端与 ML 系统工程师(要写或调推理服务)、以及希望把「PagedAttention / continuous batching / V1 EngineCore」放进正确上下文的算法同学。不要求会写 CUDA。
不覆盖的范围:不逐行讲 kernel;不绑定某一云厂商托管推理产品;不把某一固定 benchmark 当作普适结论。部署参数与 VLLM_USE_V1 等开关请以 vLLM 官方文档 为准。
读完应能回答:
- 推理服务的瓶颈通常落在算力、显存还是 CPU 与调度?
- vLLM 0.x 用哪些机制把「KV + 批」从痛点变成一等公民?
- V1 相对 V0,主要在进程模型、执行循环、调度抽象上改了什么,动机是什么?
- 统一按 token 调度后,与 chunked prefill、prefix cache、投机解码如何概念上衔接?
- 上线时该看哪些指标、何时值得坚持 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:
- 推理基础设施的主战场是 KV + 调度 + CPU/GPU 重叠,不单是算子。
- vLLM 0.x 用 PagedAttention 与 continuous batching 把显存与批处理变成可组合抽象。
- V1 通过 API Server / Engine Core 分离 与 统一 token 调度,降低 CPU 争用与状态机复杂度,便于默认叠特性。
- 迁移应用真实流量压 TTFT 尾延迟 与 OOM,并保留版本回退。
- 生态选型按 延迟 / 吞吐 / 可控 / 绑定硬件 四维决策,避免唯 benchmark 论。
延伸阅读(完整 URL):
- vLLM V1: A Major Upgrade to vLLM's Core Architecture
- vLLM V1 使用指南
- Inside vLLM: Anatomy of a High-Throughput LLM Inference System
- vLLM 设计:Architecture Overview(若路径随版本变更,从文档首页导航至 Architecture / Design)
附录 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、算子、分布式、量化、观测各一项「健康标准」与「异常信号」。