vLLM高吞吐推理系统全景拆解

文章目录

    • [1. 先给结论:vLLM 本质上解决的是什么问题](#1. 先给结论:vLLM 本质上解决的是什么问题)
    • [2. 从最小例子开始:一个离线引擎到底包含什么](#2. 从最小例子开始:一个离线引擎到底包含什么)
    • [3. 引擎启动时,真正重的事情发生在哪里](#3. 引擎启动时,真正重的事情发生在哪里)
      • [3.1 Init device](#3.1 Init device)
      • [3.2 Load model](#3.2 Load model)
      • [3.3 Initialize KV cache](#3.3 Initialize KV cache)
    • [4. 请求进入引擎后,vLLM 是怎么跑起来的](#4. 请求进入引擎后,vLLM 是怎么跑起来的)
    • [5. vLLM 为什么能快:关键在调度器,而不是"批量大"](#5. vLLM 为什么能快:关键在调度器,而不是“批量大”)
      • [5.1 两类完全不同的工作负载](#5.1 两类完全不同的工作负载)
      • [5.2 调度器在干什么](#5.2 调度器在干什么)
    • [6. PagedAttention 到底解决了什么](#6. PagedAttention 到底解决了什么)
    • [7. 连续批处理为什么重要](#7. 连续批处理为什么重要)
    • [8. 五个必须理解的高级特性](#8. 五个必须理解的高级特性)
      • [8.1 Chunked Prefill](#8.1 Chunked Prefill)
      • [8.2 Prefix Caching](#8.2 Prefix Caching)
      • [8.3 Guided Decoding](#8.3 Guided Decoding)
      • [8.4 Speculative Decoding](#8.4 Speculative Decoding)
      • [8.5 Disaggregated P/D](#8.5 Disaggregated P/D)
    • [9. 从单 GPU 到多 GPU:为什么需要 MultiProcExecutor](#9. 从单 GPU 到多 GPU:为什么需要 MultiProcExecutor)
    • [10. 从单机推理到在线服务:vLLM 的外层服务栈怎么搭](#10. 从单机推理到在线服务:vLLM 的外层服务栈怎么搭)
      • [10.1 Headless 节点在做什么](#10.1 Headless 节点在做什么)
      • [10.2 API 节点在做什么](#10.2 API 节点在做什么)
      • [10.3 一个请求的完整生命周期](#10.3 一个请求的完整生命周期)
    • [11. 性能指标到底该怎么看:不是只盯 tokens/s](#11. 性能指标到底该怎么看:不是只盯 tokens/s)
      • [11.1 延迟和吞吐为什么会冲突](#11.1 延迟和吞吐为什么会冲突)
    • [12. vLLM 官方是怎么做 benchmark 的](#12. vLLM 官方是怎么做 benchmark 的)
    • [13. 这篇文章最值得带走的 6 个认知](#13. 这篇文章最值得带走的 6 个认知)
    • [14. 谁最应该读原文](#14. 谁最应该读原文)
    • [15. 延伸阅读](#15. 延伸阅读)
    • [16. 最后的话](#16. 最后的话)

很多人用 vLLM,停留在两层认知:

  • 它很快

  • 它支持很多大模型部署特性

但如果你继续往下问一句: 它到底为什么快,内部到底是怎么组织起来的?

答案就不再是一个命令行参数,而是一整套围绕 调度KV Cache连续批处理多进程执行分布式服务 搭起来的系统工程。

这篇文章是对 vLLM 官方长文 Inside vLLM: Anatomy of a High-Throughput LLM Inference System 的中文本地化整理。

我不做逐段直译,而是按中文技术读者更容易吸收的方式,重组为一条从单机引擎到多机服务的理解路径。

原文作者是 Aleksa Gordic,分析基于 commit 42172ad,聚焦 vLLM 的 V1 engine。如果你希望建立对现代 LLM 推理系统的整体心智模型,这篇非常值得读。


1. 先给结论:vLLM 本质上解决的是什么问题

vLLM 要解决的不是"让模型跑起来",而是:

  • 在有限显存下,让更多请求同时被服务

  • 在交互延迟和整体吞吐之间做更优平衡

  • 把单卡、单机、多卡、多机、在线服务串成统一架构

你可以把它理解为一个分层系统:

  1. 最内层是 LLM Engine / Engine Core

  2. 中间层是 Scheduler + KV Cache Manager + Model Executor

  1. 外层是 Async Client + Load Balancer + API Server

  2. 再往外才是你熟悉的 OpenAI 兼容接口和服务部署命令

也就是说,vLLM 的重点从来不只是"模型推理",而是高吞吐、低浪费、可扩展的推理系统设计


2. 从最小例子开始:一个离线引擎到底包含什么

原文先从一个最简单的离线推理例子讲起:

python 复制代码
from vllm import LLM, SamplingParams

prompts = [
    "Hello, my name is",
    "The president of the United States is",
]

sampling_params = SamplingParams(temperature=0.8, top_p=0.95)

llm = LLM(model="TinyLlama/TinyLlama-1.1B-Chat-v1.0")
outputs = llm.generate(prompts, sampling_params)

这个例子看起来很简单,但背后已经有一整套引擎结构在工作。

作者给出的核心拆分是:

  • vLLM config:模型、缓存、并行策略等全部配置项

  • processor:把原始输入转成引擎请求

  • engine core client:与 Engine Core 通信

  • output processor:把底层输出转成用户看到的结果

其中最关键的是 Engine Core。它内部至少有三块:

  • Scheduler

  • Model Executor

  • KV Cache Manager

如果你只记一句话,可以记这个:

vLLM 的性能,很大程度不是来自"模型本身",而是来自对请求调度和 KV Cache 的管理方式。


3. 引擎启动时,真正重的事情发生在哪里

很多人以为 LLM(...) 只是"加载模型"。

其实在 vLLM 里,初始化阶段已经做了大量系统级准备工作。

3.1 Init device

这一步会做:

  • 绑定 CUDA 设备

  • 检查 dtype 是否可用

  • 根据 gpu_memory_utilization 评估显存空间

  • 初始化分布式配置

  • 创建 model_runner

  • 创建 InputBatch

简单说,它不是只把模型丢到 GPU,而是在为后续高吞吐执行搭好缓冲区、索引结构和运行环境。

3.2 Load model

这部分比较直观:

  • 实例化模型结构

  • 加载权重

  • model.eval()

  • 视情况执行 torch.compile()

3.3 Initialize KV cache

这一段才是重点中的重点。它会:

  • 计算每层 KV cache 的规格

  • 做一次 profiling forward

  • 估算显存里到底能放下多少 KV blocks

  • 分配 KV cache tensor

  • 绑定到 attention 层

  • 预热并捕获 CUDA Graph

也就是说,vLLM 启动时并不是只在"准备推理",而是在预构建一个可复用的高效执行环境

这也是为什么它后面能把内核启动开销、KV Cache 命中率和批处理效率做得比较激进。


4. 请求进入引擎后,vLLM 是怎么跑起来的

generate() 的主线非常清晰,可以概括为两步:

  1. 把请求喂进引擎

  2. 循环执行 step()

每个请求进入系统时,会经历:

  • 生成 request id

  • 对 prompt 做 tokenize

  • 封装成 EngineCoreRequest

  • 进入调度器的 waiting 队列

而后续真正反复执行的是 step()

每次 step 都包含三件事:

  1. Schedule:这一步要跑哪些请求

  2. Forward pass:真正过模型

  1. Postprocess:写回 token、判停、释放资源

这三个阶段看起来普通,但基本就是整个 vLLM Engine 的最小运行循环。


5. vLLM 为什么能快:关键在调度器,而不是"批量大"

原文里一个非常重要的点是:

vLLM 的核心优势,不是简单把请求拼成一个 batch,而是区分不同阶段的请求,并动态混合调度

5.1 两类完全不同的工作负载

推理系统里主要有两类请求阶段:

  • Prefill:对整段 prompt 做前向计算

  • Decode:每次只生成一个新 token

它们的性能特征非常不同:

  • Prefill 通常更偏 compute-bound

  • Decode 通常更偏 memory-bandwidth-bound

为什么?

因为 decode 每次虽然只算一个 token,但仍然要把大模型权重搬出来参与计算。

这意味着,调度器不能把两者当成同一种东西处理。

vLLM V1 的一个重要改进,就是能在同一个 step 中更灵活地混合 prefill 和 decode,而不是像旧版本那样非此即彼。

5.2 调度器在干什么

调度器会优先看 running 队列里的 decode 请求,然后再处理 waiting 里的 prefill 请求。

每次调度都要考虑:

  • 当前 token budget 还剩多少

  • 每个请求这一步要分配多少新 token

  • 需要分配多少 KV blocks

  • 空闲 block 够不够

这里就引出一个核心方法:allocate_slots

它大致做三件事:

  1. 算出需要多少 KV blocks

  2. 检查 block pool 是否还有足够空间

  1. 真的把 block 分给请求

所以你可以把调度器理解成一个"请求编排器",而 KV Cache Manager 更像"显存页分配器"。


6. PagedAttention 到底解决了什么

vLLM 最广为人知的关键词之一就是 PagedAttention

但如果只停留在"这是一个很厉害的注意力优化",其实并没有真正理解它。

更容易理解的方式是:

PagedAttention 的核心不是改写注意力公式,而是把 KV Cache 的内存管理做成类似分页系统。

也就是说,token 对应的 KV 不再要求必须连续地、大块地分配在显存里,而是被映射到一个个 block 上。

这样做的好处是:

  • 显存利用率更高

  • 碎片更少

  • 更容易支持动态请求混合

  • 更容易回收和复用 KV Cache

KV Cache Manager 会维护一个 free_block_queue,里面是所有可用的 KV blocks。

请求需要缓存时,从里面取;请求结束时,再把 blocks 还回去。

这就是为什么作者会说:
KV Cache Manager 是 PagedAttention 的心脏。


7. 连续批处理为什么重要

如果你只做离线推理,一次性把 prompts 全部喂进去,问题还相对简单。

但在线服务不是这样:

  • 新请求会随时进来

  • 老请求还在继续 decode

  • 每个请求长度都不同

这时如果你还坚持"一个固定 batch 从头跑到尾",资源利用会很差。

vLLM 的做法是 continuous batching,也就是连续批处理:

  • 每个 step 之后都重新考虑当前系统里有哪些请求

  • 新请求可以随时加入

  • 老请求继续占用自己的 KV Cache

  • 所有序列在前向时会被拼成一个"大序列"处理

这就是为什么它不需要传统意义上的右侧 padding 批处理,而是把不同请求压到一个统一执行流中。

这套机制跟 PagedAttention 配合起来,才真正构成了 vLLM 的高吞吐基础。


8. 五个必须理解的高级特性

原文后半部分进入高级能力。这里我建议你重点抓五个概念。

8.1 Chunked Prefill

问题背景很简单:

如果有一个超长 prompt,一次 prefill 可能会占满整个 step,导致其他请求全部排队。

Chunked prefill 的思路就是把长 prompt 切块处理。

你可以把它理解成:

  • 不让单个超长请求独占整个引擎时间片

  • 用多次 step 完成长 prompt 的 prefill

  • 在吞吐和延迟之间做更平衡的调度

对于真实线上服务,这一点很重要,因为长 prompt 一旦不加控制,很容易拖垮整体交互体验。

8.2 Prefix Caching

这是另一个非常实用的特性。

如果多个请求共享相同前缀,就没必要反复重新计算同样的 token。

vLLM 的做法是:

  • 把前缀按 block 切分

  • 对完整 block 计算哈希

  • 把 block hash 映射到缓存中的 KV block

  • 后续遇到同样前缀时直接复用

本质上就是:

不要重复做已经做过的 prefill。

这对共享 system prompt、长上下文模板、多轮 agent 工作流都非常有价值。

8.3 Guided Decoding

如果你需要 JSON、语法约束、结构化输出,就会遇到 guided decoding。

这里的核心思路是:

  • 每一步解码时,不让所有 token 都参与采样

  • 而是先根据语法状态机构造一个合法 token mask

  • 把不合法 token 的 logits 直接打成负无穷

这样模型最终只能生成符合目标结构的结果。

作者这里提到,vLLM 会通过类似 xgrammar 的后端来完成语法编译与 mask 维护。

所以 guided decoding 本质上不是"采样技巧",而是把形式语言约束融入解码循环

8.4 Speculative Decoding

这是近两年推理加速里很重要的一条线。

它的基本思想是:

  • 先让一个更轻量的 drafter 猜几个 token

  • 再让大模型一次性验证这些 token

  • 如果猜对了,就等于一次大模型前向拿回多个 token

原理上,它是在保持最终分布一致的前提下,尽量减少"大模型一 token 一 forward"的低效过程。

vLLM V1 里重点支持的提案方式包括:

  • n-gram

  • EAGLE

  • Medusa

这块如果你做高并发推理优化,非常值得重点看。

8.5 Disaggregated P/D

这里的 P/D 指的是 Prefill / Decode 分离。

这是一个很有系统味道的设计:

  • Prefill 更偏算力密集

  • Decode 更偏显存带宽敏感

既然两者资源特征不同,那就没必要强绑在同一执行实例里。

于是就有了:

  • Prefill worker 专注做前缀计算

  • Decode worker 专注做逐 token 生成

  • 中间通过 KV Cache 服务交换缓存

这样做的好处是,你可以分别优化 TTFTITL,而不是把一切问题都扔给一个统一实例硬扛。


9. 从单 GPU 到多 GPU:为什么需要 MultiProcExecutor

当模型太大,单卡装不下时,问题就进入并行执行阶段。

此时 vLLM 的核心执行器会从 UniProcExecutor 过渡到 MultiProcExecutor

它负责的事情可以概括为:

  • world_size 拉起多个 worker 进程

  • 给每个 worker 配置通信管道

  • 广播执行任务

  • 收集各 worker 结果

从调用者角度看,接口几乎没变,依然是 execute_model

但在内部,已经从"单进程直接调 worker"变成了"父进程协调多个 GPU worker 并做跨进程通信"。

这也是一个很典型的系统工程思想:

对上层接口尽量保持稳定,把复杂性吸收在执行层内部。


10. 从单机推理到在线服务:vLLM 的外层服务栈怎么搭

很多人看到 vllm serve 就以为事情结束了。

但原文真正精彩的地方,是把在线服务的内部路径讲得非常具体。

10.1 Headless 节点在做什么

在多节点场景下,headless server 节点负责:

  • 启动多个 EngineCore 进程

  • 每个进程内部再维护输入线程、输出线程和主循环

  • 与前端 API 节点握手

  • 等待调度与执行请求

也就是说,它更像"纯算力后端"。

10.2 API 节点在做什么

API server 节点上会跑:

  • AsyncLLM

  • DPLBAsyncMPClient

  • DPCoordinator

  • FastAPI / Uvicorn 接口

这里的关键不只是"收请求",而是:

  • 选择把请求发给哪个 engine replica

  • 跟踪各 replica 的负载

  • 异步收集输出

  • 把最终结果流式或非流式返回给用户

也就是说,在线服务层本质上是一层:

异步前端 + 负载均衡 + 分布式执行协调器

10.3 一个请求的完整生命周期

原文最后把一次 /v1/completions 请求的路径完整串起来了。

压缩成中文,就是这条链:

  1. 请求打到 FastAPI 路由

  2. 路由层做 tokenize 和 metadata 封装

  1. AsyncLLM.generate() 把请求交给异步客户端

  2. 负载均衡逻辑选一个合适的 engine

  1. 请求进入对应 engine 的 input socket

  2. engine 主线程反复调用 step()

  1. 输出线程把结果发回前端

  2. API server 再把响应回给调用方

这样你会发现:

用户看到的是一个 HTTP 接口,但系统内部其实是异步任务、消息队列、进程编排、GPU worker 和缓存管理共同完成的一条流水线。


11. 性能指标到底该怎么看:不是只盯 tokens/s

如果你只用一个指标评价推理系统,通常会犯错。

原文把几个核心指标讲得很清楚:

  • TTFT:首 token 延迟

  • ITL:相邻 token 间延迟

  • TPOT:平均每输出 token 的时间

  • E2E Latency:端到端完成时间

  • Throughput:吞吐

  • Goodput:满足 SLO 的有效吞吐

为什么一定要区分这些指标?

因为在线交互和离线批处理的优化目标完全不同:

  • 交互产品更关心 TTFT 和 ITL

  • 离线任务更关心整体 Throughput

而这两者往往互相拉扯。

11.1 延迟和吞吐为什么会冲突

作者用 roofline 视角解释了一个常见现象:

  • batch 很小时,单请求 ITL 低,但总吞吐不高

  • batch 增大后,吞吐上升,但每一步的执行时间也可能上升

尤其 decode 阶段经常是带宽受限,而不是纯算力受限。

所以"多塞点请求进去"不一定总是更优,关键在于你想优化哪类指标。

这也是为什么 vLLM 里会有:

  • continuous batching

  • chunked prefill

  • prefll/decode 分离

  • speculative decoding

这些看似分散的特性,本质上都在服务同一个目标:
更精细地调和延迟与吞吐。


12. vLLM 官方是怎么做 benchmark 的

原文还提到 vLLM 自带的 benchmark 能力:

bash 复制代码
vllm bench latency
  --model <model-name>
  --input-tokens 32
  --output-tokens 128
  --batch-size 8

此外还有:

  • vllm bench throughput

  • vllm bench serve

三者分别偏向:

  • 测延迟

  • 测离线吞吐

  • 模拟真实在线服务负载

这也提醒我们一件事:

没有脱离场景的"推理性能"。

你必须先明确自己是在做:

  • 聊天机器人

  • agent 调度

  • 批量生成

  • 数据清洗

  • 合成数据生产

然后再看应该重点关心哪一组指标。


13. 这篇文章最值得带走的 6 个认知

读完这篇 vLLM 长文,我觉得最值得保留的不是具体类名,而是下面这 6 个系统认知:

1) LLM 推理优化首先是系统问题,不只是算子问题

真正影响线上表现的,往往不是一个 kernel,而是:

  • 请求怎么排

  • KV Cache 怎么管

  • 新老请求怎么混

  • 多卡多机怎么协同

2) Prefill 和 Decode 是两种不同世界

它们计算特征不同,指标诉求不同,最优调度方式也不同。

理解这一点,才容易理解为什么会有 chunked prefill、spec decode、P/D 分离。

3) KV Cache 是推理系统的基础设施

PagedAttention、prefix caching、外部 KV transfer,本质上都围绕 KV Cache 展开。

谁把 KV Cache 管得好,谁就更容易做出高吞吐系统。

4) "持续批处理"比"静态大 batch"更贴近真实线上环境

线上请求是流式进入的,不会乖乖排队等你凑齐一个完美 batch。

所以 continuous batching 才是更现实的优化方向。

5) 高吞吐的代价是架构复杂度

从 UniProc 到 MultiProc,再到 DP 协调和 API server,复杂度是层层叠加的。

vLLM 的价值之一,就是把这份复杂度尽可能封装掉。

6) 性能优化永远要和业务指标绑在一起

如果你的目标是聊天体验,就别只盯总 tokens/s。

如果你的目标是离线吞吐,也别为了首 token 漂亮数字牺牲整体产能。


14. 谁最应该读原文

我觉得这篇最适合三类人:

  • 正在做 LLM 推理平台或模型服务的工程师

  • 想深入理解 vLLM / SGLang 等推理框架内部设计的人

  • 已经会部署模型,但对"为什么这样设计"还没有完整心智模型的人

如果你只是想"把服务跑起来",官方文档可能已经够用。

但如果你想真正理解现代 LLM serving 是如何从单机脚本演化成分布式推理系统的,这篇原文非常值得精读。


15. 延伸阅读

如果你后续还想继续往深处读,原文中尤其建议重点延伸这几个主题:

  • PagedAttention

  • Prefix Caching

  • Speculative Decoding

  • Disaggregated Prefill / Decode

  • Multi-GPU 与 Multi-Node Serving


16. 最后的话

这篇文章最打动我的,不是它列了多少特性,而是它把一个现代推理系统拆得非常诚实:

  • 先从最小离线引擎讲起

  • 再进入调度、缓存和执行

  • 然后一路扩展到多进程、多卡、多机和在线服务

  • 最后回到性能指标与 benchmark

这样的写法非常适合建立全局视角。

如果你最近正好在看推理框架,或者在做自己的 LLM serving 栈,我会建议你把这篇当成一篇"系统地图"来读。

哪怕你暂时不需要记住所有类名,只要抓住它背后的设计逻辑,也会对后续很多工程判断很有帮助。

相关推荐
海兰6 分钟前
【实战】HiMarket本地化部署指南
人工智能·ubuntu·架构·银行系统
zhangshuang-peta8 分钟前
MCP:把不确定性变成工程能力
人工智能·ai agent·mcp·peta
m0_5648768419 分钟前
提示词工程手册学习
人工智能·python·深度学习·学习
AI精钢44 分钟前
谷歌时隔一年发布“更加开源“的 Gemma 4,意图何为?
人工智能·云原生·开源·aigc
洞见新研社1 小时前
从算力到电力,谁在搭建AI时代的“能源基座”?
人工智能·能源
小程故事多_801 小时前
自然语言智能体控制框架,重塑AI Agent的协作与执行范式
人工智能·架构·aigc·ai编程·harness
2501_933329551 小时前
技术深度拆解:Infoseek舆情系统的全链路架构与核心实现
开发语言·人工智能·分布式·架构
aosky1 小时前
OmniVoice:支持 600+ 语言的零样本语音克隆 TTS 系统
人工智能·tts
无忧智库2 小时前
数字化转型 | 全面揭秘企业经营的数字化解决方案 —— 从挑战到突破
大数据·人工智能
Circle Studio2 小时前
AI算力发展的未来趋势
大数据·人工智能