地图 《AI-Infra 工程师学习地图》 第 9 / 14 个系列
- Python 在 AI-Infra
- C++ 在 AI-Infra
- PyTorch 深度实践
- Transformer 原理与实现
- 现代 LLM 结构
- GPU Kernel 工程
- 通信与互联
- 大规模训练工程
- 大模型推理系统揭秘
- RL 后训练基础设施
- 扩散模型推理基础设施
- AI 平台工程
- AI-Infra 开源贡献指南
- ML 编译器内部即将发布
上一个系列:大规模训练工程下一个系列:RL 后训练基础设施
内容简介
《大模型推理系统揭秘:从 vLLM 看 LLM Serving Infra 核心技术》以 vLLM v0.27.1 为主要分析对象,从 LLM Serving 的问题本质出发,系统讲解请求生命周期、性能指标、调度、KV Cache、GPU 执行、解码的扩展(采样、投机解码与结构化输出)、多卡并行、Prefill/Decode 分离、模型适配、请求形态的扩展(multi-LoRA 与多模态)、硬件抽象、Serving Infra 的演进方向与源码走读,以及对照 SGLang 的同题比较,共十五篇正文加一篇总结自测。
本系列不局限于算子优化,也不止于源码解读,而是试图回答一个更完整的问题:
一个文本生成请求,为什么会逐渐演化成一个涉及计算、显存、调度、通信与状态管理的复杂系统?1
在这个视角下,LLM Serving 不再只是“把模型跑起来”,而是一个持续协调请求、Token、计算资源、中间状态和网络通信的动态系统。
文章将按照以下主线展开:
问题定义 → 指标体系 → 系统全景 → 单机性能优化 → 多卡与集群扩展 → 模型、请求与硬件适配 → 未来演进展望 → 源码落地
读者可以按顺序建立完整认知,也可以根据自身关注点选择调度、KV Cache、GPU 执行、多卡并行或集群 Serving 等主题阅读;每段主线包含哪几篇、带着具体问题该从哪篇进入,见下文「系列的整体主线」与「怎么读:按目标选篇」。
为什么写这个系列?
大模型能力快速演进,但真正决定其能否稳定、低成本、大规模服务用户的,往往不是模型本身,而是模型背后的 Serving Infra。
目前关于 LLM 推理的讨论,常常分散在几个局部方向:
- 有的关注 CUDA Kernel、量化和算子性能;
- 有的关注 Batch、调度和吞吐;
- 有的关注 KV Cache 或 PagedAttention;
- 有的直接进入源码,罗列类名和调用链。
这些内容各自重要,却容易让读者难以形成整体认识:不同技术究竟在解决什么问题?它们之间如何相互影响?为什么某项优化在一个场景有效,在另一个场景却可能带来新的瓶颈?
本系列文章希望提供一种系统化视角:
LLM Serving 的本质,是围绕生成过程,对请求、Token、计算资源和中间状态进行持续协调。
从这个角度看:
- 调度解决“这一轮为哪些请求计算多少 Token”;
- KV Cache 解决“历史状态如何保存、复用和释放”;
- 执行优化解决“已经分配的计算如何高效完成”;
- 多卡并行解决“计算如何切分、状态如何组织”;
- 集群化部署解决“计算、状态和请求如何跨设备、跨实例流动”。
选择 vLLM,是因为它集中体现了现代 LLM Serving 的许多关键思想,也提供了观察推理系统演进的典型样本。本系列既关注 vLLM 的具体实现,也希望借此建立一套可以迁移到其他推理框架和 AI 基础设施的分析方法。
适合哪些读者?
本系列主要面向以下读者:
AI Infra 与推理系统工程师
适合负责模型部署、推理服务、GPU 资源管理、性能优化和在线系统建设的工程师。
如果你已经能够部署模型,但希望进一步理解以下问题,本系列会比较适合:
- 吞吐和时延究竟由什么决定;
- Continuous Batching 与 Chunked Prefill 如何工作;
- KV Cache 为什么会成为核心瓶颈;
- 多卡并行和 Prefill/Decode 分离如何进行工程权衡;
- 如何从源码定位性能和稳定性问题。
大模型应用与平台工程师
适合负责 LLM API、RAG、Agent、模型网关和推理平台的开发者。
即使不直接编写 CUDA Kernel,也有必要理解:
- 请求为什么会排队;
- 长上下文为什么会显著增加成本;
- 不同模型和硬件为什么需要不同适配;
- 如何根据业务目标选择部署和扩展方案。
计算机系统与机器学习系统研究者
适合关注操作系统、分布式系统、编译器、计算机体系结构和机器学习系统的学生、研究人员与技术人员。
本系列将从一个真实的工程系统出发,讨论:
- 生成式工作负载如何改变传统推理范式;
- 动态调度与状态管理之间的关系;
- GPU、网络和分布式系统如何共同影响服务性能;
- AI Serving 为什么逐渐呈现出分布式系统甚至操作系统的特征。
希望系统学习 LLM Serving 的开发者
适合已经掌握 Python、深度学习和 Transformer 基础,希望从“会调用推理框架”进一步走向“理解推理框架如何工作”的读者。
不要求一开始就掌握 CUDA、分布式训练或 vLLM 的全部源码,但需要具备基本的模型推理和系统知识。
系列的整体主线
十五篇正文可以读成一个问题被不断放大的过程:先说清楚难在哪、怎么衡量;再在一台机器上解决调度、内存、执行三个战场;一张卡不够时扩到多卡、一个实例不够时拆成多实例;然后看引擎如何承受模型、请求形态、硬件三种外部变化;最后展望它会长成什么、回到源码验收,再用同一套框架对照 SGLang。
%% 图:系列主线:问题定义 → 指标体系 → 系统全景 → 单机性能优化 → 多卡与集群扩展 → 模型、请求与硬件适配 → 未来演进 → 源码落地
flowchart TB
P1["问题定义<br/>01 为什么难"] --> P2["指标体系<br/>02 怎么衡量"] --> P3["系统全景<br/>03 一个请求的路径"]
P3 --> P4["单机性能优化<br/>04 调度 · 05 KV Cache · 06 GPU 执行 · 07 解码的扩展"]
P4 --> P5["多卡与集群扩展<br/>08 Multi-GPU · 09 PD 分离"]
P4 --> P6["模型、请求与硬件适配<br/>10 模型 · 11 LoRA 与多模态 · 12 硬件"]
P5 --> P7["未来演进<br/>13 下一站"]
P6 --> P7
P7 --> P8["源码落地<br/>14 回到源码"] --> P9["对照 SGLang<br/>15 两套系统"] --> P10["16 总结与通关自测"]
八段的分工:
| 主线段 | 篇 | 回答的问题 | 组织轴 |
|---|---|---|---|
| 问题定义 | 01 | LLM Serving 和传统 DL 推理有什么本质不同 | 工作负载 |
| 指标体系 | 02 | 用什么尺子判断系统好不好 | 工作负载 |
| 系统全景 | 03 | 一个请求在 vLLM 里经过哪些对象、哪些进程 | 请求生命周期 |
| 单机性能优化 | 04–07 | 这一轮给谁算、状态放哪、每步怎么算得更快、采样这一步怎么变 | 系统层次:调度 / 内存 / 执行 |
| 多卡与集群扩展 | 08–09 | 一张卡不够怎么切,一个实例不够怎么拆 | 规模:卡 → 实例 |
| 模型、请求与硬件适配 | 10–12 | 模型变、请求变、芯片变时,变化应该停在哪一层 | 变化来源 |
| 未来演进 | 13 | 执行器会长成什么样的分布式系统 | 演进 |
| 源码落地 | 14 | 前面的抽象能不能在对象与调用链里找到 | 验收 |
| 对照 SGLang | 15 | 同一个请求在另一套系统里走哪条路,差异来自出发点还是时间差 | 验收 |
编排上有一个约定:每一篇先把要解决的矛盾摆出来,再讲 vLLM 怎么做。跨篇反复出现的概念——KV Cache、token 级调度、Prefill / Decode 两种 workload、边界与契约——在第十六篇的「贯穿线」里有专门的整理。
章节结构与分章导读
1. 先定义问题:Serving 到底难在哪里?
第一章从工作负载本质出发,解释 LLM 推理为什么不同于传统的静态 Batch 推理。
我们会看到,Prefill 更像一次高并行度的批量计算,而 Decode 更像大量低计算量、强状态依赖的迭代计算。两者混合在同一个服务系统中,带来了批处理、调度、显存管理和时延控制上的连锁问题。
这一章的目标不是介绍某个具体实现,而是回答:
- LLM Serving 的基本计算单元是什么?
- 为什么请求之间很难保持同步?
- 为什么“模型能跑”并不等于“服务系统能高效运行”?
- 后续所有优化究竟是在解决哪一类瓶颈?
只有先把问题定义清楚,后面的技术才不会变成彼此孤立的名词。
2. 再建立指标:系统到底好不好?
当问题边界明确之后,第二章讨论如何衡量系统。
吞吐、TTFT、ITL、E2E Latency、并发数、显存占用等指标,分别描述了系统的不同侧面。它们并不是可以随意替换的“性能数字”,而是对应不同的用户体验、资源瓶颈和优化方向。
例如:
- TTFT 变差,可能意味着 Prefill 排队或调度受阻;
- ITL 变差,可能意味着 Decode 阶段受到带宽、通信或批次膨胀影响;
- 吞吐下降,可能意味着 GPU 利用率不足,也可能意味着 KV Cache 容量限制了并发;
- 显存占用升高,未必代表计算更充分,也可能意味着状态管理效率较差。
因此,本系列会把指标与系统动作连接起来:
先判断哪个指标出了问题,再判断问题发生在哪个阶段,最后决定应该优化调度、内存、执行还是通信。
3. 接着画全景:一个请求如何穿过系统?
第三章不急于深入某个局部,而是先建立 vLLM 的全局地图。
我们会从两个视角观察系统:
- 静态视角:有哪些核心组件、对象和执行角色;
- 动态视角:一个请求从进入、排队、Prefill、Decode,到完成和资源回收,经历了哪些状态变化。
这一章解决的是“对象在哪里、请求怎么流动、模块如何协作”的问题。后续章节中的 Scheduler、KV Cache Manager、Worker、Model Runner、Executor 等概念,都会回到这条请求链路中重新定位。
这样做的目的,是避免只记住局部类名和函数名,却无法理解它们在完整生命周期中的作用。
4. 然后进入第一个核心战场:调度
当多个请求同时进入系统,最直接的问题就是:
GPU 下一轮计算,究竟应该服务哪些请求、多少 Token,以及以什么顺序服务?2
第四章围绕这个问题展开,讨论 Continuous Batching、Chunked Prefill、Token Budget、Admission Control 和 Preemption。
这里的调度并不是简单的队列管理,而是在多个目标之间持续做权衡:
- 尽可能提高 GPU 利用率;
- 控制请求的首 Token 时延;
- 保证 Decode 的持续推进;
- 防止长 Prefill 阻塞短请求;
- 在显存不足时决定是否暂停、换出或拒绝请求。
调度因此成为连接“用户体验”和“硬件利用率”的第一层控制面。
5. 再进入第二个核心战场:内存与状态
调度决定“谁来计算”,但系统还必须回答另一个问题:
这些请求已经计算过的历史状态,应该放在哪里、如何复用、何时释放?3
第五章以 KV Cache 为中心,解释 PagedAttention、Block 管理、分配与回收、Prefix Cache 以及相关的生命周期控制。
KV Cache 并不是普通的临时张量。它是 Decode 阶段持续依赖的请求状态,也是限制并发规模、影响调度策略和决定扩展方式的关键资源。
因此,本系列会把 KV Cache 放在比“显存优化技巧”更高的位置来理解:
LLM Serving 的核心,不只是执行模型,更是持续管理模型生成过程中的状态。
这一视角也会自然引出后面的 PD 分离、KV Transfer 和集群路由问题。
6. 再看第三个核心战场:执行效率
有了调度和内存管理之后,还需要回答:
在已经选定的请求和 Token 上,GPU 如何把每一步计算做得更快?4
第六章进入执行层,讨论 Kernel Launch、CUDA Graph、算子融合、显存带宽、低精度、投机解码等技术。
这一章重点区分两类常被混淆的问题:
- 系统没有把 GPU 喂饱:通常与调度、Batch 组织或数据准备有关;
- GPU 已经在工作,但每一步仍然很慢:通常与 Kernel、访存、同步、通信或执行形态有关。
通过这种区分,可以更准确地理解为什么 GPU 算力很强,LLM Serving 仍然可能受限于带宽、Launch Overhead 或小规模 Decode。
7. 采样这一步还能怎么变:解码的扩展
第六章结束于”一轮 batch 在 GPU 上跑完 forward 并采样出 token”。第七章紧接着回答:采样这一步还能怎么变?
同样是”下一个 token”,为什么加上 top-p、加上 draft 模型、加上 JSON schema 之后,调度器、KV 管理和 model runner 都得改?5
这一章讨论三种对 decode 每一步输出方式的扩展:
- logits processors:温度、top-p、惩罚、thinking budget 等如何在同一个 batch 内按请求向量化地生效;
- 投机解码:draft 多个 token 再验证,改变了调度器的 token 预算与 KV 预分配、model runner 的输入形状、采样器的拒绝采样;
- 结构化输出:grammar → bitmask → logits 掩码,与调度器的异步编译协同,并与投机解码逐 token 对账。
三者同时牵动第四章的调度、第五章的 KV 与第六章的执行,所以必须放在第六章之后、多卡之前——如果留到多卡与模型适配之后再讲,就要反复回头。
8. 从单卡走向多卡:扩展会引入什么新问题?
单卡上的调度、内存和执行问题,在多卡环境中会进一步叠加通信和拓扑约束。
第八章比较 DP、TP、PP、EP、CP 等并行方式,重点不在于罗列概念,而在于说明它们分别解决什么问题、引入什么代价,以及适合什么场景。
核心关注点包括:
- 计算如何切分;
- KV Cache 如何组织;
- 卡间通信发生在哪里;
- 通信能否与计算重叠;
- 并行策略如何受到模型结构和硬件拓扑影响。
这一章希望建立一个基本判断框架:
多卡扩展不是简单地“增加 GPU 数量”,而是重新设计计算、状态和通信之间的关系。
9. 从资源混部走向计算解耦:为什么需要 PD 分离?
多卡解决的是一个模型装不下、一张卡算不动;但当模型规模、请求量和服务目标继续增长时,单个实例同时承担 Prefill 和 Decode 本身可能不再是最优选择——这是“一张卡不够”之后的第二级扩展:从多卡走向多实例。
第九章讨论 Prefill/Decode Disaggregation,以及由此带来的 KV Transfer、路由、网络、状态亲和性和故障恢复问题。
PD 分离并不是简单地把两个阶段部署到不同机器上。它重新定义了请求状态的流动方式:
- Prefill 产生的 KV Cache 如何传给 Decode;
- 请求应该路由到哪一个实例;
- 网络传输成本是否抵消了计算收益;
- 如何处理实例负载变化和状态迁移;
- 故障发生时,状态是否能够恢复或重建。
因此,是否采用 PD 分离,最终取决于工作负载、硬件资源、网络条件和服务目标,而不是某个单一的性能结论。
PD 分离放在多卡之后、适配三章之前,是因为它和第八章同属“规模”这条线:切分计算(多卡)与拆开阶段(多实例)解决的都是扩展问题;模型、请求与硬件的变化是另一条线,接下来三章集中讨论。
10. 从固定模型走向模型生态:抽象如何承受变化?
前面九章处理的是一个固定模型如何被高效地调度、缓存、执行与扩展。从第十章起的三章换一个方向,看引擎如何承受三种外部变化——模型变、请求变、芯片变——以及每种变化应该停在哪一层。
模型结构正在快速演进。不同模型可能在 Attention、MoE、位置编码、缓存布局、权重格式和执行路径上存在明显差异。
第十章讨论 Serving 系统如何承接这些差异,并分析模型适配从临时 Patch 走向体系化抽象的过程。
这里关注的并不只是“新模型能不能运行”,还包括:
- 模型差异应该被隔离在哪一层;
- 通用执行路径如何复用;
- 特殊结构如何获得专门优化;
- 新增适配是否会污染核心代码;
- 正确性、性能和可维护性如何同时保证。
最终需要区分两个层次:
能跑,是模型适配的起点;能够高效、稳定、可维护地运行,才是 Serving Infra 的真正目标。
11. 一个请求还可以带什么进来:请求形态的扩展
第十章讲”一个新模型如何接进来”,隐含的假设是一个模型、一份权重、一串 token。第十一章回答:一个请求还可以带什么进来?
当一个 batch 里的请求各带不同的 LoRA、各带几张图片时,”一个模型、一份权重、一串 token”的假设在哪里破了?vLLM 用什么把它重新缝起来,代价是多少?
这一章讨论两种在模型适配之上的”请求形态”扩展:
- multi-LoRA:同一 batch 内不同 adapter 的 Triton kernel,LoRA 权重的槽位、LRU 与换入换出,
max_loras/max_lora_rank的显存账,对调度公平性与 CUDA Graph 的影响; - 多模态:图像 / 音频 / 视频的 token 化与占位符,encoder 的独立预算与 encoder cache,多模态 prefix cache 的哈希,encoder 输出与 KV 之争。
两者都给第十二章的硬件抽象提出了新约束(Triton kernel 与 ViT 注意力后端如何跨硬件),也把新问题放回第九章的 PD 分离(多模态 encoder 放哪一侧、LoRA 在 PD 两池怎么同步)——所以放在模型适配之后、硬件抽象之前。
12. 从单一硬件走向多后端:如何保持核心稳定?
模型和请求都是软件侧的变化;当 Serving 系统需要支持不同 GPU、不同加速器和不同软件栈时,硬件差异同样不能无限向上渗透。
第十二章围绕 Platform、Backend、Device、Kernel 和执行后端之间的边界展开,讨论硬件抽象的价值与局限。
真正的硬件解耦,并不是把所有差异都藏起来,而是明确:
- 哪些逻辑属于 Serving 核心;
- 哪些逻辑属于硬件平台;
- 哪些算子必须针对设备单独实现;
- 哪些性能特性无法通过统一抽象抹平;
- out-of-tree 适配在什么情况下合理、又会付出什么维护成本。
这一章会把“可移植性”从口号还原成具体的代码组织和职责边界问题。
13. 从执行器走向系统:Serving Infra 的下一站
前面的章节主要围绕“如何把请求执行得更好”,第十三章则进一步抽象:
未来的 Serving 系统,是否会从一个模型执行器,演化为管理计算、内存、通信和状态的分布式系统?6
这一章讨论状态平面、自动并行、弹性伸缩、故障恢复、异构资源调度以及面向 Agent 的长生命周期任务等方向。
随着请求变长、交互变多、模型组合变复杂,Serving 系统管理的对象将不再只是一次推理,而可能是持续存在的上下文、工具调用、缓存状态和跨服务任务。
因此,所谓“AI Serving Operating System”,并不是一个简单的新名词,而是对这一演进方向的概括:
把模型推理从一次函数调用,提升为由系统统一管理的计算与状态过程。
14. 最后回到源码:把抽象还原成工程事实
最后一章回到 vLLM v0.27.1 源码,将前面建立的概念映射到具体对象、模块和调用链。
我们会沿着一次请求的真实生命周期,串起:
- 请求如何进入系统;
- Scheduler 如何决定本轮执行内容;
- KV Cache 如何分配和更新;
- Worker 与 Model Runner 如何执行计算;
- 输出如何返回;
- 请求结束后资源如何回收。
源码阅读不会被当作独立的“附录”,而是作为前面所有抽象的验证过程:
如果一个概念无法在对象、状态变化和调用链中找到对应关系,它就还没有真正落地。
最终,这组文章希望形成的不是一份 API 说明,也不是若干优化技巧的集合,而是一套理解 LLM Serving 的方法:
- 先从工作负载定义问题;
- 再用指标定位矛盾;
- 用请求生命周期建立全局视角;
- 从调度、内存和执行三个核心战场深入,并看解码这一步如何被采样、投机与结构化输出扩展;
- 先沿规模扩展到多卡与多实例(PD 分离),再看模型、请求形态(LoRA 与多模态)与硬件三种变化各应停在哪一层;
- 回到源码,用实现细节验证系统抽象;
- 最后把同一套框架用到第二个引擎(SGLang)上,区分哪些差异来自出发点、哪些只是时间差。
沿着这条线阅读,vLLM 不再只是一个“推理框架”,而会呈现为一个持续协调请求、Token、状态、计算、显存和通信的动态系统。
15. 对照 SGLang:同一个请求穿过另一套系统
源码走读之后,用前十四篇建立的框架看第二个主流引擎。SGLang v0.5.18 与 vLLM 在同一个请求的路径上做了不同的选择:vLLM 一个调度器指挥 N 个被动 worker、调度结果跨进程传递,SGLang 每个 TP rank 各跑一份相同的调度器、与 ModelRunner 同进程;vLLM 一个 token 预算、无 prefill / decode 阶段、事后逐个抢占,SGLang 先 prefill 后 decode、用 new_token_ratio 预估准入、批量撤回带反馈;vLLM 的 KV 复用是块哈希表与引用计数,SGLang 是基数树(RadixAttention)与锁计数;PD 分离一边是 KVConnector 加外部 proxy,一边是内置的两级队列加 Rust 网关。本篇逐层并排这些差异,区分哪些来自两个项目的出发点(PagedAttention 的块 vs RadixAttention 的树)、哪些来自定位(执行层 vs 带编排)、哪些只是时间差,最后给出按工作负载而不是按 benchmark 的选择表,并解释一张吞吐图里藏着多少变量。
功能可以补,数据结构很难换:块与树各自决定了命中粒度、驱逐方式、锁定方式和调度器能看到什么。
16. 系列总结与通关自测
最后一篇不讲新内容:把十五篇正文压成一张「问题 → 结论 → 必记数字」的表并逐篇回顾,拎出贯穿全系列的几条线与常见误区,然后给一套三段式通关自测——十道判断与计算、五道跨篇综合、若干道面试题,答案各自折叠,附「读过 / 掌握 / 能教人」的判据。各篇末尾的自测检验的是一篇读懂了没有,这一篇检验的是十五篇能不能连起来用;读完正文再做。
怎么读:按目标选篇
按顺序读是建立完整认知最省力的路径。带着具体问题来的读者,可以从下表找入口,再按需要补前置:
| 你想弄清楚的问题 | 先读 | 再补 |
|---|---|---|
| GPU 利用率很高但吞吐上不去、TTFT 为什么抖 | 02、04 | 01、06 |
| 并发上限由什么决定、长上下文为什么贵 | 05 | 01、04 |
| 一步 decode 的时间花在哪,CUDA Graph / 量化 / 投机解码分别省什么 | 06 | 07 |
| 开了投机解码或 JSON 输出后,调度、KV、model runner 为什么都要改 | 07 | 04、05 |
| TP / PP / EP / DP 怎么选、通信发生在哪 | 08 | 01、05 |
| 要不要做 PD 分离、KV 怎么传、代价多大 | 09 | 05、08 |
| 新模型(MLA、MoE、MTP)接进 vLLM 要动哪几层 | 10 | 03、05 |
| multi-LoRA 与多模态请求对调度、KV、CUDA Graph 的影响 | 11 | 04、05、10 |
| 一块新芯片怎么接、哪些代码里不该出现设备判断 | 12 | 03、10 |
| vLLM 之上的集群层(llm-d、Dynamo)在做什么、边界在哪 | 13 | 09 |
| 想顺着源码把一次请求走一遍 | 03、14 | 04、05 |
| vLLM 与 SGLang 怎么选、差异在哪 | 15 | 03、05、09 |
三条常见路线:
- 只想建立判断:01 → 02 → 03 → 05 → 16。读完能解释吞吐与时延由什么决定、KV Cache 为什么是第一号约束,用第十六篇的 A 组自测检验。
- 做部署与调优:在上面基础上加 04 → 06 → 07 → 08 → 09。读完能把
max_num_batched_tokens、max_num_seqs、block_size、TP / PP、投机解码的 K 与 PD 分离这些旋钮对应到指标上。 - 做适配与贡献:按顺序读完 01–15,重点是 10 → 11 → 12 的三层 / 五层边界与 14 的调用链,再做第十六篇的 B、C 组自测。
前置要求与说明
前置要求
- 理解 Transformer 的结构与算量:参数量、Prefill / Decode 的 FLOPs 与访存量、KV Cache 的大小公式、GQA / MLA 对 KV 的影响。本系列直接使用这些结论而不再推导,缺少这部分基础的读者可先补 Transformer 算量与 KV Cache 的推导;
- 理解 PyTorch 运行时的基本结构:Tensor 与算子分发、CUDA stream 与同步、
torch.compile的大致工作方式; - 能读 Python,能顺着 import 在一个几十万行的代码库里定位对象;
- 对 GPU 的 memory-bound / compute-bound 有基本判断。
不要求:
- 写过 CUDA kernel 或 Triton——FlashAttention、PagedAttention、量化 kernel 的内部实现属于 kernel 工程的范畴,本系列只讨论 vLLM 如何选择和调用它们;
- 了解 NCCL 内部或集合通信算法——多卡章节会用到 all-reduce / all-to-all 的代价结论,正文会给出理解所需的最小推导,NCCL 与互联硬件的原理不在本系列展开;
- 有分布式训练经验——TP / PP / EP 在训练侧的推导不在本系列展开,本系列关注它们在推理中的取舍与 vLLM 的实现。
版本与硬件基线
- 源码:vLLM v0.27.1(tag
6e448d0,2026-08-11)。全系列的文件路径、类名和函数名以此为准;vLLM 目录结构变动很快,读旧版本或新版本时请以本地源码对照。文中不引用行号。 - 引擎:v0.27.1 只有 V1 引擎;
vllm/v1/worker/gpu_model_runner.py是默认的 Model Runner,vllm/v1/worker/gpu/model_runner.py是可通过VLLM_USE_V2_MODEL_RUNNER启用的新实现,文中以前者为主线并在涉及处标注。 - 硬件:默认以 H100 SXM(80 GB,约 3.35 TB/s,BF16 约 989 TFLOPS) 为估算基准,A100 随文标注。文中所有毫秒级、GB/s 级数字除明确注明”实测”外均为理论下界或量级估算,用于建立判断,不是 benchmark 结果。
- 模型:示例以 Llama-3-8B / 70B 为主,MoE 与 MLA 以 DeepSeek-V3 为例。
- 对照引擎:第十五篇对照 SGLang v0.5.18(tag
71de97b,2026-08-20),路径与默认值以该版本为准。
关于其他推理引擎
本系列以 vLLM 为分析对象。SGLang、TensorRT-LLM、LMDeploy 在同一位置的不同选择会在相关章节提及,但正文不展开;第十五篇专门用全系列的框架对照 SGLang,TensorRT-LLM 与 LMDeploy 读者可以用同一套方法(问题 → 指标 → 生命周期 → 调度 / 内存 / 执行三个战场 → 扩展)自行对照。
章节目录(建议按顺序阅读)
- 为什么 LLM Serving 比传统 DL 推理难?
- 如何衡量一个 LLM Serving 系统?
- 鸟瞰 vLLM:一个请求如何穿过整个推理系统?
- Scheduler:GPU 这一轮到底给谁用?
- KV Cache:LLM Serving 的第一号内存问题
- GPU 执行:如何让每个 Token 算得更快?
- 解码的扩展:采样、投机解码与结构化输出
- Multi-GPU:一张卡不够时如何扩展?
- PD 分离:从资源混部走向计算解耦
- 模型适配:如何跟上变化极快的模型世界?
- 请求形态的扩展:multi-LoRA 与多模态
- 硬件解耦:如何不让芯片差异污染 Serving 核心?
- Serving Infra 的下一站:从模型执行器到分布式智能操作系统
- 回到源码:一次请求在 vLLM 内部的真实旅程
- vLLM 与 SGLang:同一个请求穿过两套 Serving 系统
- 系列总结与通关自测
本系列讲的是 memory-bound 的 LLM serving;图像与视频生成模型的推理是另一半——单请求就 compute-bound、没有 KV cache、batch 几乎不提吞吐、请求时长可预测,几乎每一个系统答案都相反。那一半在 Infra 地图的 11《扩散模型推理基础设施:从一次去噪到一个生成服务》。
版本说明:本系列基于 vLLM v0.27.1(tag
6e448d0,2026-08-11)源码分析。文中路径、类名和函数名均以该版本为准;由于 vLLM 迭代较快,阅读时请结合实际版本进行对照。
-
因为生成是逐 token、有状态、长度未知的:每个 decode 步只算一个 token 却要读一遍全部权重(memory-bound,单请求 GPU 利用率个位数)→ 必须把很多请求批在一起(计算与调度:continuous batching、chunked prefill、token budget);每个请求要保留全部历史的 KV cache、随生成增长且何时结束未知 → 必须管理一块动态的、不可预估的显存(PagedAttention、块分配、抢占、prefix cache);模型放不进一张卡或吞吐不够 → 通信(TP / PP / EP、PD 分离、KV 传输);请求之间的公平、SLO、多轮会话的前缀复用 → 状态管理。每一环都是上一环的后果,所以一个「跑模型」的程序长成了一个调度 + 内存 + 通信 + 状态的系统。 ↩
-
由 Scheduler 每轮用一个 token budget(
max_num_scheduled_tokens)决定:先推进正在 decode 的请求(每个 1 个 token,保证已接受的请求不饿死),剩余预算分给等待队列里的新请求做 prefill——chunked prefill 让长 prompt 只推进预算剩余的那部分、下轮继续;顺序按队列(FCFS)或优先级,admission control 在显存(KV 块)不够时不接新请求,preemption 在 decode 请求的 KV 块分配失败时把最晚到的请求换出 / 重算。所以答案是:服务所有能分到 KV 块的 decode 请求 + 预算内尽量多的 prefill token,顺序是「decode 优先、新请求按到达 / 优先级」——第四章在 GPU 利用率、TTFT 与 TPOT、公平性之间逐项权衡。 ↩ -
放在 GPU 显存里的 KV cache,以固定大小的 block(如 16 个 token)为单位由 BlockManager 分配——PagedAttention 让逻辑上连续的序列映射到物理上不连续的块,碎片与预留浪费接近零;复用靠 prefix caching:按块内容哈希,相同前缀(system prompt、多轮历史、beam 的公共部分)的块引用计数 +1 直接命中、跳过这段 prefill;释放时机:请求结束或被抢占时归还块,prefix cache 的块引用归零后进入 LRU 空闲列表、满了才真正驱逐;显存不够时把整个请求换出到 CPU(swap)或丢弃 KV 重算(recompute)。第五章把 KV cache 当作「请求状态」而不是临时张量来讲它的生命周期。 ↩
-
分两类问题:没把 GPU 喂饱——每步几十上百个小 kernel 的 launch 开销与 CPU 侧准备时间在 decode 里占大头,用 CUDA Graph 把一步的 kernel 序列录下来一次提交、把采样与输入准备向量化;kernel 本身慢——decode 是 memory-bound,所以用低精度权重(FP8 / INT4)与 KV cache 量化减少每步读的字节、算子融合减少中间张量往返、FlashAttention / PagedAttention kernel 做 attention 的 tiling;再往上是投机解码用一次前向验证多个草稿 token(把 memory-bound 的步数折算成 compute)与 MoE 的专家并行 kernel。第六章按「launch → 带宽 → 精度 → 融合 → 投机」的顺序每项给出收益区间。 ↩
-
因为它们各自改变了一步 decode 的形状或契约:top-p 等 logits processors 要求采样器对同一个 batch 内不同请求按各自参数向量化处理(温度、惩罚要查每个请求的历史 token),model runner 的采样阶段与调度器传下来的请求元数据都得扩展;投机解码让一步不再是「每请求 1 个 token」而是 \(k+1\) 个——调度器的 token 预算与 KV 块预分配要按 \(k+1\) 算、model runner 的输入形状变成不规则、采样器要做拒绝采样并把被拒的 KV 回滚;JSON schema 约束要求每步在采样前用 FSM / 语法状态屏蔽非法 token——状态每请求一份、要随接受的 token 推进,与投机解码叠加时还要能回退,调度器要知道这个请求带着一个语法状态。所以「下一个 token」的扩展必须穿过调度器、KV 管理和 model runner 三层。 ↩
-
会,而且已经在发生:请求变长、交互变多、模型组合变复杂之后,系统管理的对象不再是一次推理,而是持续存在的状态(多轮会话的 KV、agent 的工具调用上下文、跨请求的缓存)、多种资源(prefill 与 decode 的异构算力、KV 的多级存储、跨节点的传输)与多个模型(draft / target、路由、多 LoRA),这就需要独立的状态平面(KV 的全局寻址与迁移)、自动并行与弹性伸缩、故障恢复、异构调度与长生命周期任务——即「AI Serving Operating System」。vLLM 在其中的位置是单节点 / 单实例的执行引擎:它把一张或一组卡上的调度、KV 管理与 kernel 做到最好,而集群级的路由、PD 分离编排、KV 传输与状态平面由它之上的一层(llm-d、Dynamo、Mooncake 一类)承担,vLLM 通过 KV connector、disaggregated serving 接口与指标暴露接进去。 ↩
地图 《AI-Infra 工程师学习地图》 第 9 / 14 个系列
- Python 在 AI-Infra
- C++ 在 AI-Infra
- PyTorch 深度实践
- Transformer 原理与实现
- 现代 LLM 结构
- GPU Kernel 工程
- 通信与互联
- 大规模训练工程
- 大模型推理系统揭秘
- RL 后训练基础设施
- 扩散模型推理基础设施
- AI 平台工程
- AI-Infra 开源贡献指南
- ML 编译器内部即将发布
上一个系列:大规模训练工程下一个系列:RL 后训练基础设施
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/deep-dive-into-vllm.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。