🧸 ELI5 · 推理系统底层解剖

解剖大模型推理黑盒:vLLM 架构剖析

基于 Aleksa Gordić 深度长文,10 个环环相扣的手绘卡通两格漫画,拆解 vLLM V1 核心引擎与 PagedAttention 底层奥秘。

小绿:正在执行每步前向推理的 Model Runner 核心
小橙:客户端流入的 Prompt 与动态批次 Token
小蓝:PagedAttention 物理分页显存块与 KV Cache
红虚线:静态 Padding 气泡 / 抢占驱逐 / 显存碎片

⚡ 核心主线:为什么原生 PyTorch 推理会卡死?

一句话真相: 传统深度学习是一车人坐满才能发车(静态批处理),短句子要拼命填无意义空格(Padding 气泡),长句子又把显存切成不可再分的连续大块,导致 60%~80% 的昂贵 GPU 显存全死在碎片与等待上!vLLM 就像给显卡装上了操作系统的虚拟内存与流式调度器,让每个 Token 即到即算、零气泡挤满算力!

多路请求 调度器 分页显存池 PagedAttention 连续批处理 Zero-Bubble vLLM V1 引擎:扁平大超序列单次 Kernel 吞吐 高吞吐流
已学完
1

动态迭代批处理 (Continuous Batching)

💡 一句话解释: 传统方式是公交车必须等全车所有人都到站才开下一趟;连续批处理是有人到站马上下车、新人立刻上车!
# 传统静态 Batch 满篇 Padding 气泡 | vLLM 每次 step() 动态纳新与回收完成序列
烦恼:短句子全在陪长句子发呆
短请求早就完了 被迫填充 Padding 长请求慢悠悠生成... 算力有大半浪费在空转气泡上

一人没走完,全车陪着等

办法:下车一个立刻补位一个,零等待
Req 1 Req 2 新加入 Req 4 无缝插拔,流水线永远打满

走一个进一个,永远不空座

解决了什么:在每个 Token 解码迭代级别调度请求,消灭所有静态 Padding 气泡,吞吐翻数倍。
代价是什么:每步都必须重新动态拼装 Batch 索引,对调度器开销与算子设计要求极高。
已学完
2

显存虚拟化分页 (PagedAttention)

💡 一句话解释: 借鉴操作系统的虚拟内存:不要预留整条长显存,而是切成一个个 16-Token 的物理小格子,按需拼积木!
# 逻辑块到物理块映射:block_size=16, free_block_queue 双向链表管理 Logical Block 0,1,2 ➔ Block Table ➔ Physical GPU Pages [7, 42, 105]
烦恼:预先占满整条显存,大量浪费
写了3字 预留未用 2048 长度虚占 内部碎片高达 60%~80%

盲猜最大长度,显存全虚耗

办法:页表映射,满 16 个字才领一个新块
显存浪费率从 80% 暴降至 <4%

物理页零碎拼装,严丝合缝

解决了什么:彻底消灭无法预估序列长度带来的内部显存碎片,并发容量直接提升 2~4 倍。
代价是什么:需要定制 GPU 核心算子(FlashAttention / Paged Kernel),无法直接用标准 PyTorch 连续张量。
已学完
3

显存自救:抢占与重计算 (Preemption)

💡 一句话解释: 当所有人都在疯长把显存挤爆时,调度器会暂时把倒霉的低优先级任务“请下台”腾空间,等有空再重算它!
# 显存块不足时:V1 采用 Recompute Preemption 释放低优先级 KV 块并重置回 WAITING
烦恼:所有人都不停,瞬间一同 OOM 崩溃
💥 OOM 硬撑着分配,导致服务整体挂死

盲目硬抢,整船人一起沉

办法:请后排先回休息室,腾出显存
高优保送 退回等待 保证核心服务绝不宕机崩服

有序避让,大局永远稳健

解决了什么:在突发极限并发下通过主动驱逐低优先级任务,彻底免疫显存耗尽导致的服务器进程崩溃。
代价是什么:被驱逐的任务需要重新经历一次 Prefill 重算,拉长了被抢占请求的单次端到端延迟。
已学完
4

消灭长文本卡顿:分块预填充 (Chunked Prefill)

💡 一句话解释: 别让一个 32K 字的巨无霸 Prompt 把整张卡霸占 3 秒钟,切成小段跟别人吐字混着算!
# 设置 long_prefill_token_threshold 限制每步最大预填充 Token 数,与 Decode 完美混跑
烦恼:巨无霸插队,所有人吐字被定身
定身 超长 32K Prompt 其他在线会话瞬间出现剧烈卡顿

一个大块头,卡死全排队

办法:大石化碎冰,每步匀速嚼几块
Chunk Dec Chunk Dec ITL 吐字抖动直降 90%!

细嚼慢咽,大家都能往前走

解决了什么:将大算力 Prefill 任务打碎,允许 Decode 穿插进行,消除流式吐字的间歇性突刺打嗝。
代价是什么:大文本自身到达首字的时间(TTFT)会被稍微拉长几个迭代周期。
已学完
5

前缀哈希树匹配 (Prefix Caching)

💡 一句话解释: 每 16 个字打一个哈希指纹,所有请求共享前缀;发现指纹对得上,直接拿老显存块用!
# 16-token chunk 哈希链:BlockHash 存入 cached_block_hash_to_block 字典
烦恼:几千个用户带着相同长背景反复读
相同背景 相同背景 算力白白耗在重复提示词上

每个人进来,都把前言算一遍

办法:哈希指纹秒对暗号,公共块原地复用
公共哈希块 ref_count > 1 免Prefill 公共前缀命中,延迟降至毫秒

指纹一验,直接跳到正文

解决了什么:在多轮对话与长系统提示词场景下,直接复用已算好的物理显存块,算力利用率翻倍。
代价是什么:被引用的块不可被驱逐释放,高频复用场景下会长期霸占物理显存槽位。
已学完
6

硬件图录制:CUDA Graph 极速回放

💡 一句话解释: Python 每次指挥 GPU 发射算子都有延迟,先在热身时把全套动作“录成宏”,以后一键硬件回放!
# 预先热身捕获常用 Batch Size:避免每次 forward pass 几百个 CPU Launch 开销
烦恼:CPU 啰嗦下令,GPU 算完干等
Python GPU 算子1发射 算子2发射 CPU Launch Overhead 占大头

领导啰嗦指挥,工人闲得抠脚

办法:排练一次录成宏,一键通电触发
K1 K2 K3 整条流水线在 GPU 内部光速自驱

一键重放,硬件流水线狂飙

解决了什么:将多层数百次算子发射封装为单一硬件图执行,单步推理延迟立减 20%~40%。
代价是什么:要求张量内存地址与尺寸完全固定,短文本需填充到预捕获尺寸,增加了引擎初始化时间。
已学完
7

规矩约束:状态机引导解码 (Guided FSM)

💡 一句话解释: 当模型必须输出严格 JSON 时,别在后面苦苦祈祷它别出错,用有限状态机每一步把非法词直接遮盖掉!
# Structured Output Manager:利用 XGrammar 等工具预构建词表掩码状态图
烦恼:模型瞎编乱写,吐出非法 JSON 废品
NaN? 💥 {"key": ??? 缺少约束导致业务解析大面积失败

信马由缰,格式随时暴毙

办法:状态机护栏,违规道路全封闭
状态0 状态1 百分百保证语法合规且零重试

加装路障,只能走合规道路

解决了什么:在 Logits 阶段直接屏蔽语法非法词,实现 100% 保证 JSON/Regex 结构化输出。
代价是什么:复杂庞大的正则表达式或文法初始化构建 FSM 会占用额外内存与启动时间。
已学完
8

打破访存墙:投机采样 (Speculative)

💡 一句话解释: Decode 阶段每吐一个字都要搬运一遍几百 GB 的大模型权重;让小草稿模型一口气猜 5 个字,大模型只看一眼批量审核!
# 内存受限的 GEMV ➔ 批处理算力受限的 GEMM,大幅拉升有效吐字吞吐
烦恼:大象漫步,每动一步都要搬座山
70GB 权重搬运 1词 访存带宽利用率极低,算力全闲置

搬万斤砖,只垒一块瓦

办法:小鸟快速叼枝,大鹏一次过目
草稿 大模型并行验 单次请求生成速度飙升 2~3 倍!

小弟先猜,大哥一次盖章

解决了什么:将单步串行访存瓶颈转化为矩阵并行验证,显著降低单请求生成延迟。
代价是什么:草稿模型需额外显存驻留,若猜中率低可能适得其反浪费算力。
已学完
9

资源解耦:P/D 分离架构 (Disaggregated)

💡 一句话解释: Prefill 是算力饕餮,Decode 是带宽吞噬者;把两张卡拆开各司其职,中间用高速网络飞速传 KV 账本!
# 算力卡专跑 Prefill ➔ RDMA 高速传 KV Cache ➔ 访存卡专跑 Decode 生成
烦恼:同居一室,算力与访存互相掐架
吃算力 吃带宽 混在一起无法同时调优到硬件极限

同吃一锅饭,互相扯后腿

办法:分工建站,专业算力与生成池解耦
Prefill 节点 (算力打满) RDMA Decode 节点 (大批吐字) 集群级端到端最佳资源配比

分工包干,各自发挥极致

解决了什么:将计算密集型与带宽密集型两阶段硬件隔离,实现吞吐与首字时延双双登顶。
代价是什么:节点间跨机网络需极高规格 RDMA/InfiniBand 支持,且系统调度复杂度极高。
下一站
10

集群基石:分布式异步服务架构

💡 一句话解释: 从单进程小作坊进化到全天候现代化大工厂:API 网关、调度引擎、多卡 Worker 各自独立进程,并发无上限!
# InprocClient (单进程) ➔ MultiProcExecutor ➔ DPLBAsyncMPClient (分布式数据并行与负载均衡)
烦恼:单进程一旦被 GIL 锁住全网瘫痪
主进程卡顿 GPU空转 Python GIL 锁严重制约多卡吞吐

一个前台忙,后台全停摆

办法:解耦多进程独立跑,网关异步调度
API Core GPU0 GPU1 工业级高并发,支撑数万 QPS

多进程分工,生产级无限扩展

解决了什么:将网络接入、引擎核心与多 GPU 运行器彻底拆分到独立进程,破除 Python GIL 束缚。
代价是什么:进程间通信(IPC)增加了共享内存与信号管道管理开销。

📏 性能衡量尺:vLLM 系统四大优化杠杆平衡

在推理系统架构中,任何一项技术的启用都在以下四个维度相互权衡:

系统吞吐 Throughput 首字时延 TTFT (ms) 吐字抖动 ITL (ms) 显存利用率 VRAM Eff 支点:GPU 硬件 Roofline 物理边界

🧩 PagedAttention 分页

大幅改善: 显存碎片率降至 <4%,并发容量翻倍

牺牲妥协: 需要专用 GPU 算子维护页表索引

⚡ Chunked Prefill 削峰

大幅改善: ITL 吐字抖动暴降,消除掉帧卡顿

牺牲妥协: 单个长文本的首字响应时间轻微拉长

🏭 P/D 分离集群

大幅改善: 吞吐量与低延迟同时达到硬件物理极限

牺牲妥协: 极度依赖昂贵的高速机间 RDMA 网络

👶 零基础版:一个请求在 vLLM 里的一生

当你在终端敲下一个 Prompt 到它吐完字,vLLM 内部经历了哪些步骤?

阶段步骤 内部关键模块 发生了什么(大白话) 关键优化点
1. 进门接单 Preprocessor & EngineCore 把文字分词(Tokenize)并封装为标准 Request,丢进 Waiting 等待队列 异步轻量接收,不堵塞服务网关
2. 领座位牌 Scheduler & KV Cache Manager 查 Prefix 缓存有没有命中;从空闲池领 16-Token 物理分页块,移到 Running 队列 Radix Tree 哈希秒对前缀,零碎片分配
3. 混装发车 Model Runner 把所有人本轮要算的 Token 摊平成一条大超序列,拼装好 Attention 掩码 消灭 Padding 气泡,完美对齐 CUDA 图
4. 猛火出炉 Model Executor (GPU Kernel) 调用 PagedAttention 与 FlashAttention 算子一次过,计算 Logits 并采样新词 显存带宽打满,张量核心高速运转
5. 结账清台 Output Processor 把新词反分词流式推给用户;遇到结束符释放物理显存块归还空闲池 连续批处理:立刻容纳下一位新顾客

提示:以上全流程均源自 Aleksa Gordić 博客中对 vLLM V1 核心引擎架构的最新源码级剖析。

🗺️ 一遍过完:深入 vLLM 源码级学习路线

所有资料已全部通过真实网络连接核验,涵盖从设计原理解读到官方架构文档:

🚩 第一站:Aleksa Gordić 原文精读 (Inside vLLM)
为什么去: 业内公认最硬核且通俗的 vLLM 逆金字塔全景深度解析长文。
预计耗时: 25 分钟
立即前往 ↗
🚩 第二站:vLLM V1 引擎官方设计总纲 (V1 Design Guide)
为什么去: 官方架构规范,详解 V0 废弃后 V1 架构的全新调度与多进程架构。
预计耗时: 15 分钟
立即前往 ↗
🚩 第三站:PagedAttention 经典奠基论文 (SOSP'23)
为什么去: 探究加州大学伯克利分校团队如何从操作系统虚拟内存获取灵感。
预计耗时: 20 分钟
立即前往 ↗