跳到主要内容

数据截至 (上游 commit f25c580af159)

vLLM — 架构与原理

30 秒导读: vLLM 是开源界事实标准的 LLM 推理服务引擎(UC Berkeley Sky Computing Lab 发起)。它解决的问题是:把一个大模型放到 GPU 上对外服务时,吞吐被 KV cache 的显存浪费和「一批请求必须同进同出」的批处理方式拖死。vLLM 的两个成名答案——PagedAttention(像操作系统管内存页一样管 KV cache)和 continuous batching(每个解码步都重新决定谁在这一批里)——让它在同样硬件上把吞吐抬高一个数量级。本文档按由浅入深的顺序讲清这套系统的原理与真实实现。


1. 这是什么(零基础也能懂)

一句话定义

vLLM 是一个高吞吐的 LLM 推理与服务引擎:你给它一个 Hugging Face 模型,它给你一条命令就能起量的 OpenAI 兼容 API 服务,或者一个离线批量推理的 Python 库。

它要解决谁的什么问题

设想你买了一个 70B 模型想给全公司当 API 用。naive 做法是:来一条请求,跑完,再跑下一条。这会撞两堵墙:

  • 显存墙。 每生成一个 token,注意力都要读它前面所有 token 的 Key/Value 向量(即 KV cache)。这些向量占的显存常常比模型权重还大,而传统实现为每条请求预留一段连续显存,请求真实长度又不可预知——大量显存被「占着没用」。
  • 吞吐墙。 传统 batching 是「凑一批、一起跑完、再放下一批」。同一批里有 10 个 token 的回答也有 2000 个 token 的回答,短的早就结束了,GPU 却陪长的空跑。

vLLM 就是把这两堵墙拆掉的那一层:显存按固定大小的块(block)随用随分批组成每个 step 都可变

它能做什么

能力具体支持
服务接口OpenAI 兼容 API(chat/completion/responses)、Anthropic Messages、gRPC、离线 LLM
核心优化PagedAttention、continuous batching、chunked prefill、prefix caching(前缀缓存)
执行优化piecewise / full CUDA graph、torch.compile、FlashAttention / FlashInfer / MLA 等注意力后端
解码算法贪心/采样、parallel sampling(n>1)、beam search、投机解码(EAGLE、n-gram、Medusa 等,vllm/v1/spec_decode/
量化FP8、NVFP4、INT8/INT4、GPTQ/AWQ、GGUF 等
并行张量并行(TP)、流水并行(PP)、数据并行(DP)、专家并行(EP)、上下文并行
模型面200+ 架构:稠密 LLM、MoE、Mamba 混合、多模态、embedding/奖励模型
硬件NVIDIA/AMD/Intel GPU、CPU,及 TPU 等插件后端

用起来什么样

两种形态。命令行起服务(一条命令就是一个 OpenAI 服务):

# 摘自官方 quickstart 的典型用法
vllm serve Qwen/Qwen3-8B --tensor-parallel-size 2
# 然后就能用 openai SDK 打 http://localhost:8000/v1/chat/completions

Python 里离线批量跑(LLM 类,定义在 vllm/entrypoints/llm.py:67):

from vllm import LLM, SamplingParams

llm = LLM(model="Qwen/Qwen3-8B") # 加载模型、建 KV cache、编译/捕获 CUDA graph
outs = llm.generate(["写一首关于秋天的诗"] * 1000, SamplingParams(max_tokens=128))
# 1000 条请求会自动 continuous batching,不用你管批怎么组

一句话直觉

把 vLLM 想成一台「token 流水线工厂」的操作系统。 KV cache 显存是物理内存,PagedAttention 是它的分页内存管理器(MMU);每条请求是一个进程,调度器每个时钟周期(step)都重新决定哪些进程上 CPU——不占满 token 预算不罢休;进程结束不立刻清场,留下的「内存页」还带着哈希,下一个带相同前缀的请求来了直接认领。


2. 顶层全景(它大概怎么转)

2.1 进程与部件拓扑

V1 引擎(本 commit 的唯一主线,V0 已移除)是一套多进程流水线

OpenAI HTTP 请求


┌──────────────────┐ zmq/共享内存 ┌───────────────────────┐
│ 前端进程 │ ─────────────► │ EngineCore 进程 │
│ AsyncLLM │ 请求 │ run_busy_loop 死循环: │
│ · InputProcessor │ │ ① Scheduler.schedule │
│ · OutputProcessor│ ◄───────────── │ ② Executor 执行 │
│ · 增量 Detokenize│ token 输出 │ ③ update_from_output │
└──────────────────┘ └──────────┬────────────┘
│ collective_rpc 广播
┌────────────▼───────────┐
│ Worker 进程 × N(每卡1个)│
│ GPUModelRunner: │
│ 备输入→forward→采样 │
└────────────────────────┘

怎么读这张图: 左边是用户看得到的部分(收请求、把 token 流转成文本流);中间是引擎的「大脑」,每转一圈生产一个 step 的 token;右边是真正碰 GPU 的部分,一个进程对应一张卡。三个框在默认部署里是三个(或更多)进程,靠消息队列解耦,谁都不会因为别人慢而空等。

2.2 部件一句话职责

部件干什么在哪个文件
AsyncLLM异步前端:收请求、校验、把输出推给调用方vllm/v1/engine/async_llm.py:72
InputProcessor文本→token id、多模态预处理、算前缀哈希vllm/v1/engine/input_processor.py:38
EngineCoreProc引擎核心进程外壳:busy loop + 进程间握手vllm/v1/engine/core.py:1027
Scheduler每个 step 决定哪些请求跑、各跑几个 token、分到哪些 KV 块vllm/v1/core/sched/scheduler.py:74
KVCacheManager / BlockPoolKV cache 块的分配、引用计数、前缀缓存哈希表vllm/v1/core/kv_cache_manager.py:118vllm/v1/core/block_pool.py:143
Executor(UniProc/MultiProc/Ray)把一步执行铺到 1..N 个 Workervllm/v1/executor/abstract.py:38
GPUModelRunner每卡上的执行体:组 batch 输入、跑模型、采样vllm/v1/worker/gpu_model_runner.py:501
CudagraphDispatcher运行时决定这一步用哪种 CUDA graph 模式vllm/v1/cudagraph_dispatcher.py:13
Samplerlogits → 采样 token(温度、top-k/top-p、惩罚项)vllm/v1/sample/sampler.py:21
OutputProcessor + 增量 detokenizertoken → 文本增量、按 stop 条件收尾、推流vllm/v1/engine/output_processor.py:132vllm/v1/engine/detokenizer.py:31

2.3 主线走一遍(一个请求的完整旅程)

不进代码,先建立全局直觉:

  1. 入队。 前端把 prompt 变成 token id 序列,按固定块大小预计算好每块的链式哈希(供前缀缓存命中用),包装成 EngineCoreRequest 发给 EngineCore。
  2. 等待调度。 请求进 Scheduler 的 waiting 队列。每个 step,调度器先保住 running 里的老请求,再用剩余 token 预算从 waiting 里拉新请求——这就是 continuous batching:批的成员每个 step 都重新决定。
  3. 分块。 调度器通过 KVCacheManager 问:这个 prompt 的前缀有多少块已经在缓存里?命中的直接引用计数 +1 复用;没命中的从空闲块池拿新块。显存不够就把优先级最低的请求抢占回等待队列。
  4. 执行。 SchedulerOutput(谁、几个 token、用哪些块)广播给所有 Worker;每个 GPUModelRunner 把 batch 展平成一维 token 序列跑 forward,从各自最后一位置取 logits 采样出本步 token。
  5. 回流。 采样结果回 EngineCore,update_from_output 更新每条的进度、检查停止条件(EOS、长度上限、stop 字符串);活着的下一步继续,结束的释放块回池。
  6. 出字。 token 增量经 detokenizer 变成文本增量,前端按 SSE 流式推给用户。

3. 阅读地图

建议按编号顺序读;每章开头都有「这章讲什么」。

章节你会学到前置
01 PagedAttention 与 KV cache 块管理为什么 KV cache 是显存杀手;分页 + 引用计数 + LRU 驱逐;前缀缓存的链式哈希
02 continuous batching 调度器token 预算、RUNNING/WAITING 双队列、chunked prefill、抢占与重算01
03 V1 引擎架构为什么拆成多进程;busy loop;async scheduling;输出处理链02
04 模型执行与 CUDA graphGPU 上一步的全貌;块表;CUDA graph 三模式与运行时派发;显存 profiling02
05 分布式与 API 服务Executor 抽象与多卡;vllm serve 到引擎的调用链03、04
06 巧妙之处·边界·对比·代码地图可抄走的设计、已知局限、和兄弟引擎的取舍全部

只想快速建立心智模型:读本章 + 01 + 02 即可。要改调度策略:重点 02 + 03。要接新硬件/新注意力后端:重点 01 + 04。


4. 巧妙之处(预告)

以下是读完全文后值得带走的精华,细节和出处见 06

  • 分页显存 + 链式哈希:KV cache 块既能按 LRU 驱逐,又能靠「父块哈希 + 本块 token」的链式哈希秒级命中前缀缓存——同一份数据结构服务两个目的。
  • 调度与执行的解耦:调度器只产出一个纯数据的 SchedulerOutput,Worker 缓存请求状态、每步只收 diff,通信成本压到最低。
  • CUDA graph 运行时派发:预捕获一组 batch 尺寸的图,运行时把任意 batch 「向上取整」到最近的已捕获尺寸,小批量解码几乎零 CPU 开销。

5. 边界与局限(速览)

  • 它是推理引擎,不是训练框架——没有反向传播;训练侧请看兄弟库(如 verl 把 vLLM 当 rollout 后端用)。
  • 性能特性随小版本剧烈变化;硬件相关调优(块大小、CUDA graph 尺寸集、后端选择)比配置项表面看起来更影响结果。
  • 多进程 + 多后端意味着启动要经历「加载 → profiling → torch.compile → CUDA graph 捕获」的完整热身,冷启动以十秒到分钟计。

详细的坑与横向对比见 06


6. 横向对比(一句话版)

项目与 vLLM 的不同取舍
SGLang同样做高性能 serving,但另有 RadixAttention(基数树前缀缓存)与结构化生成前端语言;vLLM 走块池 + 链式哈希路线
llama.cpp面向本地/边缘、CPU 与量化优先;vLLM 面向数据中心 GPU 吞吐优先
transformers研究与易用优先的模型库,generate() 朴素批处理;vLLM 是生产 serving 引擎
verlRL 训练框架,把 vLLM 作为推理后端嵌入训练循环

7. 代码地图(全局导航)

完整版(含更多符号)在 06;这里是「从哪儿跳进源码」的最小集合:

主题文件路径关键符号
引擎核心主循环vllm/v1/engine/core.pyEngineCore.stepEngineCoreProc.run_busy_loop
调度决策vllm/v1/core/sched/scheduler.pyScheduler.scheduleScheduler.update_from_output
KV 块池vllm/v1/core/block_pool.pyBlockPool.get_new_blocksBlockPool.free_blocks
前缀缓存命中vllm/v1/core/single_type_kv_cache_manager.pyFullAttentionManager.find_longest_cache_hit
块哈希vllm/v1/core/kv_cache_utils.pyhash_block_tokensKVCacheBlock
GPU 一步执行vllm/v1/worker/gpu_model_runner.pyGPUModelRunner.execute_model
CUDA graph 派发vllm/v1/cudagraph_dispatcher.pyCudagraphDispatcher.dispatch
采样vllm/v1/sample/sampler.pySampler.forward
多卡执行器vllm/v1/executor/multiproc_executor.pyMultiprocExecutor.collective_rpcWorkerProc
服务入口vllm/entrypoints/cli/serve.pyServeSubcommand