内容简介

《大模型推理系统揭秘:从 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 的方法:

  1. 先从工作负载定义问题;
  2. 再用指标定位矛盾;
  3. 用请求生命周期建立全局视角;
  4. 从调度、内存和执行三个核心战场深入,并看解码这一步如何被采样、投机与结构化输出扩展;
  5. 先沿规模扩展到多卡与多实例(PD 分离),再看模型、请求形态(LoRA 与多模态)与硬件三种变化各应停在哪一层;
  6. 回到源码,用实现细节验证系统抽象;
  7. 最后把同一套框架用到第二个引擎(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 读者可以用同一套方法(问题 → 指标 → 生命周期 → 调度 / 内存 / 执行三个战场 → 扩展)自行对照。

章节目录(建议按顺序阅读)

  1. 为什么 LLM Serving 比传统 DL 推理难?
  2. 如何衡量一个 LLM Serving 系统?
  3. 鸟瞰 vLLM:一个请求如何穿过整个推理系统?
  4. Scheduler:GPU 这一轮到底给谁用?
  5. KV Cache:LLM Serving 的第一号内存问题
  6. GPU 执行:如何让每个 Token 算得更快?
  7. 解码的扩展:采样、投机解码与结构化输出
  8. Multi-GPU:一张卡不够时如何扩展?
  9. PD 分离:从资源混部走向计算解耦
  10. 模型适配:如何跟上变化极快的模型世界?
  11. 请求形态的扩展:multi-LoRA 与多模态
  12. 硬件解耦:如何不让芯片差异污染 Serving 核心?
  13. Serving Infra 的下一站:从模型执行器到分布式智能操作系统
  14. 回到源码:一次请求在 vLLM 内部的真实旅程
  15. vLLM 与 SGLang:同一个请求穿过两套 Serving 系统
  16. 系列总结与通关自测

本系列讲的是 memory-bound 的 LLM serving;图像与视频生成模型的推理是另一半——单请求就 compute-bound、没有 KV cache、batch 几乎不提吞吐、请求时长可预测,几乎每一个系统答案都相反。那一半在 Infra 地图的 11《扩散模型推理基础设施:从一次去噪到一个生成服务》。

版本说明:本系列基于 vLLM v0.27.1(tag 6e448d0,2026-08-11)源码分析。文中路径、类名和函数名均以该版本为准;由于 vLLM 迭代较快,阅读时请结合实际版本进行对照。

  1. 因为生成是逐 token、有状态、长度未知的:每个 decode 步只算一个 token 却要读一遍全部权重(memory-bound,单请求 GPU 利用率个位数)→ 必须把很多请求批在一起(计算与调度:continuous batching、chunked prefill、token budget);每个请求要保留全部历史的 KV cache、随生成增长且何时结束未知 → 必须管理一块动态的、不可预估的显存(PagedAttention、块分配、抢占、prefix cache);模型放不进一张卡或吞吐不够 → 通信(TP / PP / EP、PD 分离、KV 传输);请求之间的公平、SLO、多轮会话的前缀复用 → 状态管理。每一环都是上一环的后果,所以一个「跑模型」的程序长成了一个调度 + 内存 + 通信 + 状态的系统。 ↩

  2. 由 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、公平性之间逐项权衡。 ↩

  3. 放在 GPU 显存里的 KV cache,以固定大小的 block(如 16 个 token)为单位由 BlockManager 分配——PagedAttention 让逻辑上连续的序列映射到物理上不连续的块,碎片与预留浪费接近零;复用靠 prefix caching:按块内容哈希,相同前缀(system prompt、多轮历史、beam 的公共部分)的块引用计数 +1 直接命中、跳过这段 prefill;释放时机:请求结束或被抢占时归还块,prefix cache 的块引用归零后进入 LRU 空闲列表、满了才真正驱逐;显存不够时把整个请求换出到 CPU(swap)或丢弃 KV 重算(recompute)。第五章把 KV cache 当作「请求状态」而不是临时张量来讲它的生命周期。 ↩

  4. 分两类问题:没把 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 → 带宽 → 精度 → 融合 → 投机」的顺序每项给出收益区间。 ↩

  5. 因为它们各自改变了一步 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 三层。 ↩

  6. 会,而且已经在发生:请求变长、交互变多、模型组合变复杂之后,系统管理的对象不再是一次推理,而是持续存在的状态(多轮会话的 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 接口与指标暴露接进去。 ↩

这篇对你有用?

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/deep-dive-into-vllm.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×