vLLM 技术内幕——像操作系统管理内存一样管理 KV 缓存
vLLM 技术内幕——把显存当操作系统管
基于 vLLM 主线(commit
8151f2ad,2026-08-12)与稳定版 v0.27.1(2026-08-11)。源码位置都钉到该 commit,论文是 Kwon 等人的 PagedAttention(SOSP 2023,arXiv:2309.06180)。一句话先行:大模型服务的钱,一大半烧在显存上。vLLM 把操作系统管内存的老办法——分页、页表、引用计数、写时复制——搬进了 KV 缓存,浪费从七成压到 4% 以内,吞吐最高翻 24 倍。下面拆开看它是怎么做到的。
1. 穷在哪:六七成显存白白扔掉
跑大模型推理,贵的不只是算力,更是显存。一个 13B 模型,每个 token 的 KV 缓存约 800 KB(论文 §3);2048 token 的一段话,光 KV 就吃 1.6 GB。上百个请求一并发,KV 总量经常比模型本身还大。
老引擎(FasterTransformer、Orca、直接拿 HuggingFace Transformers 跑)有个笨办法:每个请求的 KV 预先划一整块连续显存,长度按"最长能到多少"算。好比请客先按 100 人订桌,结果只来 30 人,70 张桌子空着。论文拿真实负载(ShareGPT)一测:KV 里真正有货的部分只有 20.4%–38.2%,六七成显存纯浪费(碎片 + 预留空位)。
vLLM 是 UC Berkeley 搞出来的,招式就一招:学操作系统管内存。从此"显存浪费"被摁到 4% 以下,吞吐相对 HF Transformers 最高 24 倍,相对同期最强的 FasterTransformer、Orca 也有 2–4 倍。
今天的 vLLM 早不只是一个显存技巧,而是一整套推理服务引擎:OpenAI 兼容 API、多种并行、量化、前缀缓存、投机解码、结构化输出、LoRA,单卡到跨节点都能铺。
2. PagedAttention:KV 缓存的"格子间革命"
2.1 切块 + 索引,跟操作系统一模一样
做法像把大仓库改成格子间:每个序列的 KV 按 16 个 token 一块切开(论文 §7.2 试过,这是利用率和碎片的甜点)。物理显存里块和块不挨着没关系,靠一张**块表(block table)**记"第几块逻辑 → 住哪间物理"——跟操作系统的页表(虚拟页 → 物理页)一个模子:块就是页,token 就是字节,序列就是进程。
算注意力时也不拼大矩阵了,逐块读:拿当前 query 跟每间房里的 key 算点积,再把 value 按块累加。块表保证各家只看自家货,不串门。
2026 年 V1 代码里这套架子还在:
BlockPool给整块 GPU KV 显存预建一排"房间卡"KVCacheBlock(vllm/v1/core/block_pool.py:174)。- 每张卡记着
block_id、ref_cnt(几家合租)、满间时的前缀哈希、空房链表指针(vllm/v1/core/kv_cache_utils.py:118)。 - 每个请求的块表就是
req_to_blocks: dict[request_id, list[KVCacheBlock]],按逻辑顺序排队,每项指一间物理房(vllm/v1/core/single_type_kv_cache_manager.py:357)。 - 写货时
PagedAttention.write_to_paged_cache调融合算子reshape_and_cache把新 KV 塞进对应房间(vllm/v1/attention/ops/paged_attn.py:31);KV 张量按块排成[2, num_blocks, num_kv_heads, head_size/16, block_size, 16](paged_attn.py:17)。
2.2 三个实打实的好处
显存几乎不浪费。 碎片只剩每个序列最后那间没住满的房(不到 16 token 的量),实测浪费不足 4%。同样显存能接的并发,相对 Orca(Oracle) 多 2.2 倍、相对 Orca(Max) 多 4.3 倍(论文 §6.2)。
吞吐翻几倍。 同等延迟下相对 FasterTransformer 和 Orca 2–4 倍(论文摘要);ShareGPT 上相对 FasterTransformer 最高 22 倍;相对 HF Transformers 最高 24 倍,相对 TGI 最高 3.5 倍。
货能合租。 每间房带 ref_cnt。并行采样时,所有样本的 prompt 指着同一批房;真分叉了才对最后一间合租房写时复制:谁要写一间 ref_cnt>1 的房,就另开新房、复印、老房计数减一(_apply_cow,single_type_kv_cache_manager.py:403)。实测:并行采样省 6.1%–9.8% 显存,beam search 省 37.6%–55.2%(ShareGPT 上最高 66.3%)。
代价也得说:论文 §7.1 测过,PagedAttention 内核比 FasterTransformer 的连续内核慢约 20%–26%——但端到端还是碾压,省下的显存换来的并发早就把这点内核开销赚回来了。
3. 调度:公交车式上下客 + 长面包切片
3.1 连续批处理:到站就上下,不等整车
vLLM 沿用 Orca 的迭代级调度:每次前向一结束,干完的下车,新来的上车。PagedAttention 让"加人减人"不用重新包整车,所以每两次前向之间都能变阵,几乎零成本。
V1 引擎主循环就是一遍遍 step()(vllm/v1/engine/core.py:581):
scheduler_output = scheduler.schedule(...) # 这步拉谁上车
future = executor.execute_model(...) # 非阻塞跑一轮
model_output = future.result()
engine_core_output = scheduler.update_from_output(...) # 结算、下车、还房卡
干完的请求在 update_from_output 里触发 finish_requests,kv_cache_manager.free() 把房间"从尾往头"还给池子(kv_cache_manager.py:568),下一轮 schedule() 马上把排队的人补进来。上下车都在两次前向之间,不等整条线路跑完——"连续"说的就是这个。
一轮 step() 全程长这样:
3.2 不分 prefill 和 decode:长面包切着吃
V0 时代还分"备菜阶段"(prefill)和"上菜阶段"(decode),一个长 prompt 的 prefill 一次前向独占整步,别人全罚站。V1 把这堵墙拆了:prompt 和生成的 token 一视同仁,调度决策就是一张小纸条 {request_id: num_tokens}——这步给你几个 token 名额(V1 博客 §2)。
纸条背后是两个饭量上限(scheduler.py:439 附近):
token_budget = max_num_scheduled_tokens(默认 =max_num_batched_tokens= 2048);input_budget = max_num_batched_tokens,卡这步拼起来的总 token 数。
每个请求这步能吃几个 token,看三者谁最小:剩的量 / token_budget / input_budget(scheduler.py:882)。prompt 超了预算?切块,剩下的下几步接着吃,这步里 decode 照常跑。这就是分块预填充,V1 默认开、不用配(docs/usage/v1_guide)。还能用 long_prefill_token_threshold 给单个 prefill 块再设上限。
V0 和 V1 的本质差别:V0 只能跑"纯备菜"或"纯上菜"的专车;V1 一车混装,备菜上菜一起拉。
光讲道理容易晕,看个例子:3000 token 的长 prompt 进来,A、B 两个请求正在 decode,每步预算 2048:
长 prompt 切两刀,块1 占满 Step 1,块2 在 Step 2 收尾;A、B 的 decode 步步不停,没被长 prompt 堵死。首 token 不用再"等整条长 prompt 一次算完",decode 吞吐几乎无损——这就是切片的好处。
4. 前缀缓存:公共开头只算一遍
很多请求开头都一样:同一段 system prompt、同一组 few-shot 例子。算一遍存下来,后面复用,就是前缀缓存。
哈希是"连锁印章":每块的哈希 = 上一块哈希 + 本块 token ID(+ LoRA/多模态附加键)。一个印章就代表"到这块为止的整条前缀"(kv_cache_utils.py:577)。
找货:find_longest_cache_hit 从第 0 块顺着印章往后盖,第一个盖不上的停(后面必然全 miss),去 cached_block_hash_to_block 表查(single_type_kv_cache_manager.py:729)。命中的块 touch()——引用加一,原本在待淘汰队的就摘出来(block_pool.py:702)。
存货:前向之后 cache_blocks 把新住满的块登记印章、入表(single_type_kv_cache_manager.py:425)。只存验过货的 token,投机解码的草稿不存(kv_cache_manager.py:555)。
扔货:房间释放时引用减一;还带着印章、计数归零的进队尾(最后扔,留着当缓存),没印章的进队头(马上复用)。队是 O(1) 双向链表,中间删除不心疼——论文说的"常数时间淘汰"就是它。
V0 的前缀缓存 Python 开销大,默认关;V1 重做后 0% 命中时吞吐损失 <1%,于是 V1 默认开(config/cache.py:96)。实测:LLaMA-13B 上 341 token 的 few-shot 前缀,相对 Orca(Oracle) 吞吐 3.58 倍。
命中公共开头时,请求走的是这条近道——命中段直接住现房、跳过计算,只算没命中的尾巴:
5. 房卡不够了:抢座只重算,不搬家
KV 缓存管理器(vllm/v1/core/kv_cache_manager.py:118)就是"房卡管理员"。每步 allocate_slots() 先算要几间新房、池里够不够;不够就抢座。_preempt_request(scheduler.py:1284)下手干脆:被抢的人 num_computed_tokens 清零、房卡全收、回排队——纯重算。
这里有个跟论文、跟 V0 都不一样的事实:
- 论文 §4.5 当年做了两种抢座恢复:Swap(把房搬到 CPU)和 Recompute(扔了重算)。§7.3 实测:重算开销不随块大小变,Swap 在小块时贵得飞起;重算延迟从不超过 Swap 的 20%,块 16–64 时两者差不多。当年默认就是重算。
- V1 直接砍了 Swap,只留重算(
docs/usage/v1_guide明确写 “GPU<>CPU KV Cache Swapping: Removed”)。道理还是那个:在浪费已干掉的前提下,再养一套搬家公司又复杂又不值。被抢的人回排队后,之前的前缀可能还缓存在池里,还能少算一段。
6. 全链路:从 HTTP 到 GPU
6.1 四种进程,各干各的
V1 用多进程拆活儿,吞吐最高、CPU 开销最低(出自 docs/design/arch_overview.md):
- API Server:门口迎宾,收 HTTP(OpenAI 兼容)、分词、多模态装货,结果流式送回。用 ZMQ 跟 EngineCore 说话。默认 1 个,DP>1 时跟 DP 数对齐。
- EngineCore:后厨总管,跑调度、管房卡、指挥 GPU 工人。每个 DP rank 一个,死循环不停派活。
- GPU Worker:每卡一个,背权重、跑前向、管本卡显存。一个 EngineCore 下工人总数 =
DP × PP × TP。 - DP Coordinator:DP>1 才有,跨 rank 匀活儿。
6.2 一个请求的一生
代码钉在哪:
- 入口
api_server.py:110的build_async_engine_client()→AsyncLLM.from_vllm_config()(:168)。 AsyncLLM(vllm/v1/engine/async_llm.py:72)只接三样:InputProcessor(请求→EngineCoreRequest)、OutputProcessor(EngineCoreOutputs→RequestOutput)、EngineCoreClient(→ZMQ 客户端)。AsyncMPClient(core_client.py:977):input 走 ROUTER、output 走 PULL(:554)。EngineCore(core.py:104)外面包着EngineCoreProc(:1005);run_engine_core(:1269)是后台入口,run_busy_loop(:1373)是那个转不停的圈。- 调度结果经
Executor发到Worker.execute_model(gpu_worker.py:1026),再到GPUModelRunner.execute_model(gpu_model_runner.py:4277):更新状态 → 备料 → 建注意力元数据 → 前向 → 算 logits → 采样(sample_tokens,:4656)。
6.3 并行:四层叠罗汉
Executor 在 vllm/v1/executor/abstract.py:48 的 get_class() 里三选一:
uni→UniProcExecutor:工人跟总管住一屋,调试用;mp(默认)→MultiprocExecutor:每卡一个WorkerProc;ray→RayExecutorV2/RayDistributedExecutor:跨节点大场面。
并行四个维度随便叠:
- TP(张量并行):一层切几瓣,单机多卡最常用;
- PP(流水线并行):按层切段,
max_concurrent_batches拿 batch 填气泡; - DP(数据并行):几套完整副本,DP Coordinator 匀活儿;
- EP(专家并行):MoE 的专家分卡住,
v0.9.0起接 NIXL,后面还有elastic_ep动态伸缩。
7. 家底(截至 v0.27.1):模型、量化、硬件、服务
7.1 模型:240 个原生架构
主线登记 240 个原生模型架构:123 纯文本 + 117 多模态(含语音识别/转录、实时语音)。另有池化模型:embedding 39、分类 5、token 分类 7、打分/重排 12、奖励模型 7。
主流家族基本全有:Llama 2/3/4、Mistral/Mixtral/Ministral、Qwen2/2.5/3/3.5 和 VL/Omni 系、DeepSeek-V2/V3/V3.2/V4 与 R1、Gemma 2/3/3n/4、GPT-OSS、Phi、GLM-4.x/5.x、Kimi(K2/K3/VL)、MiniMax(M2/M3/VL)、Nemotron、Granite、InternLM、Jamba、Mamba2、Falcon、OLMo;视觉有 InternVL 2/2.5/3、LLaVA 系、Pixtral、Molmo;语音有 Whisper、Qwen3-ASR、Kimi-Audio、GLM-ASR、FireRedASR2;视频有 LLaVA-NeXT-Video、InternVideo。
榜上无名怎么办?Transformers 建模后端兜底(encoder-only/decoder-only/MoE、图文音多模态、trust_remote_code 自定义模型),插件系统补 encoder-decoder(BART、Florence-2 等)。注意红线:encoder-decoder 核心已不再原生支持,必须走插件——成熟度的明确边界。
7.2 量化:能省都省
GPTQ(AutoAWQ、GPTQModel)、BitsAndBytes(NF4/INT8)、LLM Compressor(FP8-W8A8、INT4-W4A16、INT8-W4A8、INT8-W8A8)、Marlin 高性能内核(GPTQ/AWQ/FP8/FP4)、FP4 系(NVFP4、MXFP4、MXFP6、MXFP8)、AMD Quark、TorchAO、Intel Neural Compressor;KV 本身也能压(FP8 KV Cache)。
7.3 硬件:CUDA 是主场,其他都在位
- NVIDIA CUDA:主场,Ampere 起全支持;
- AMD ROCm:
v0.10.0起 V1 默认开; - Intel XPU(Gaudi/GPU)、CPU(x86/ARM/Apple/IBM Z);
- Google TPU:核心内置,文档单列;
- AWS Trainium/Inferentia:搬出核心,独立插件 vllm-neuron(beta,基于 0.21.0)维护;
- Apple Silicon:vLLM-Metal 插件支持。
一句话:CUDA/ROCm/XPU/CPU/TPU 住核心,Neuron 和苹果走插件;v0.12.0 起有硬件插件系统,第三方后端不用再往核心里塞。
7.4 服务与高级玩法
- OpenAI 兼容 API:
/v1/completions、/v1/chat/completions(含 batch)、/v1/responses(含 cancel)、/v1/embeddings、音频转录/翻译;另有/rerank、/score、/classify、/tokenize、/detokenize、/generate、/pooling。 - 结构化输出:默认
xgrammar(auto),v0.12.0起guided_*统一到structured_outputs。 - 投机解码:EAGLE、MTP、Medusa 等;V1 用
num_tokens_with_spec把草稿 token 纳入统一调度,只有验过的才落盘。草稿咋来、咋验,看这张图:
- 前缀缓存:默认开(§4)。
- LoRA:多 LoRA 并发,
max_loras卡上限。 - 分离式 prefill/decode:实验性,KV 连接器把 prefill 算出的 KV 搬给 decode,NIXL、Mooncake、LMCache、offloading 等九种连接器。
8. 跟邻居比:护城河是显存效率
| 维度 | vLLM | SGLang | TGI (HF) | TensorRT-LLM |
|---|---|---|---|---|
| 实现语言 | Python 为主 | Python 为主 | Rust + Python | C++/Python(编译成引擎) |
| 显存管理 | PagedAttention(块表) | RadixAttention(radix 树缓存) | 连续分配(简单) | 分页 + paged KV |
| 调度特色 | 统一调度 + 长面包切片 | N+1 调度 | 连续批处理 | in-flight 批处理 |
| 模型广度 | 极广(240+ 架构) | 广 | 中(HF 生态) | 窄(NVIDIA 认证) |
| 硬件 | CUDA/ROCm/XPU/CPU/TPU + 插件 | NVIDIA 为主 | 多硬件 | 仅 NVIDIA |
| 定位 | 通用推理服务引擎 | 强推理/agent 框架 | HF 官方服务 | NVIDIA 极限性能 |
差别是结构性的:TGI 是 HF 亲儿子,稳但并行弱;TensorRT-LLM 在 N 卡上压极限,代价是模型少、要编译、闭源;SGLang 和 vLLM 路线最近(都 Python、都分页/radix 缓存、都接 xgrammar),拼的是调度细活和生态。vLLM 的护城河就一条:PagedAttention 换来的显存效率,撑起了模型广度和并行组合。
9. 长大时间线
- 2023-06:vLLM 首发,PagedAttention 亮相(论文还没出)。
- 2023-09:SOSP 论文发表,2–4 倍吞吐成行业基准线。
- 2024 下半年:连续批处理之外,补上切片预填充、前缀缓存、投机解码。
- 2025-01:V1 引擎实验发布(
VLLM_USE_V1=1),调度器/KV 管理/worker/采样/API 全重做。 - 2025-03:v0.8.0,V1 在 CUDA 上转正(还能
VLLM_USE_V1=0回退)。 - 2025-05:v0.9.0,DP/EP/PD 雏形、NIXL、PyTorch 2.7 / CUDA 12.8。
- 2025-07:v0.10.0,清 V0 代码、ROCm 默认 V1、Responses API、MXFP4 MoE。
- 2025-12:v0.12.0,PyTorch 2.9、
guided_*并入structured_outputs、xformers 后端退役、Model Runner V2 实验。 - 2026 全年:约两周一发;到 v0.27.1(2026-08-11),V0 从代码树彻底删除,
LLMEngine/AsyncLLMEngine只是 V1 的别名。
写报告别写错:“V0 vs V1” 现在是历史名词。2026 年代码里没有 V0,
vllm/engine/llm_engine.py六行纸,把LLMEngine指回 V1。要对比只能讲 2025 年初之前的旧架子(单体LLMEngine调度和 worker 住一屋、vllm/core/scheduler.py、vllm/worker/worker.py),并注明"已退市"。
10. 小结: tuning 清单
起点是个局部优化——KV 从"整块连续"改成"按页管理"。但它撬动了整条链:显存不碎了,公交车式批处理才便宜;块能合租、能盖章,前缀缓存和并行采样才成立;调度不分备菜上菜了,长面包切片才顺理成章。今天它是模型广度、量化、多硬件、多并行、OpenAI 兼容、高级推理的完整引擎,地基全是 PagedAttention。
选型直说:通用、大规模、模型杂、还要 OpenAI 兼容——vLLM;锁死 N 卡追单模型极限——拉 TensorRT-LLM 来比;规模小、抱 HF 大腿——TGI 更轻;复杂推理/agent 流程——SGLang 同量级。跑顺了绕不开四个旋钮:KV 块大小、max_num_batched_tokens 预算、前缀缓存开关、切片在 TTFT 和吞吐之间那笔账。
参考来源
- Kwon et al., Efficient Memory Management for LLM Serving with PagedAttention, SOSP 2023. arXiv:2309.06180. DOI 10.1145/3600006.3613165
- vLLM 官方文档(docs.vllm.ai,arch_overview / v1_guide / quantization / disaggregated_prefill 等),访问于 2026-08
- vLLM 博客:2023-06 发布帖、2025-01 V1 公告、2025-09 “Anatomy of vLLM”
- vLLM 源码(vllm-project/vllm,commit
8151f2ad,2026-08-12):v1/engine/core.py、v1/core/sched/scheduler.py、v1/core/kv_cache_manager.py、v1/core/block_pool.py、v1/attention/ops/paged_attn.py、v1/executor/、entrypoints/openai/api_server.py - 版本里程碑:
v0.8.0–v0.27.1发布说明
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)