如果把大模型推理服务想象成一条永不停歇的流水线,那么推理引擎就是那台“总控器”。不过这条流水线生产的不是标准零件,而是两种截然不同的工作负载:
- prefill:一次性吞下整段 prompt,建立上下文,偏计算密集,天然拥抱大 batch。
- decode:逐 token 生成,偏访存与同步密集,天然害怕抖动与额外开销。
当 prefill 和 decode 挤在同一套硬件上时,我们就不得不开始面对一连串系统设计问题:吞吐和延迟如何同时被照顾?公平性如何在资源硬约束下成立?当 KV cache 成为真正的瓶颈时,谁先走、谁后走、谁又被迫让路?
也正是在这个意义上,我们会发现 nano-vLLM 很适合作为一张“结构地图”:在它身上,我们能清晰地看到推理引擎最精简的骨架。工业级系统再怎么复杂,也不过是在这副骨架上不断“填肉”。
如果你想把这些抽象落到实现上,可以先对照 推理引擎必须解决的本质问题,再看 nano-vLLM 如何用一个永不停止的 step 循环 来驱动整个系统。
1. 问题本质:两个阶段的冲突与统一
prefill 与 decode 的冲突,本质上是“计算与存储”的拉扯。
- prefill 更像把信息写入“工作集”:用一次性的大算力,把上下文压进 KV cache,换来后续可复用的注意力记忆。
- decode 更像让工作集驱动计算:每一步都要从 KV cache 里读出越来越大的历史,再把一点点增量写回去。
所以我们真正要抓住的,不是“有没有某个优化”,而是我们愿不愿意承认并管理这对结构性矛盾。
- 如果我们让 prefill 一口气吃完所有新请求,decode 的尾延迟往往会被拖得很难看。
- 如果我们把 decode 的稳定性放在第一位,新请求的 TTFT 又会被牺牲。
在 nano-vLLM 里,我们会看到一种最直观的处理方式:系统每一步要么只做 prefill,要么只做 decode。这个选择没有对错,它更像是一面镜子,让我们能看见矛盾的原始形态,并由此理解工业级演进为什么必然走向“更复杂但更稳定”的混合推进。
如果你想进一步确认“这种矛盾如何被写进接口契约”,可以对照 从 step 契约反推调度器约束。
2. 模块划分:控制面与数据面分离的必然性
带着这对矛盾,我们再往下问一个更工程的问题:当系统需要长期演进时,我们应该怎么切模块,才能让优化不会互相踩踏?
这里有一条很朴素但很有效的原则:策略与机制解耦,也就是控制面与数据面分离。为了把后面几章的直觉对齐,你可以先把引擎想成操作系统内核的一小块:
- 控制面回答“下一步算什么”:如何把请求组织成可执行批次,如何在 prefill/decode 之间取舍,如何在资源不足时做裁决。
- 数据面回答“怎么算得快”:如何把批次变成张量,如何把计算路径变得可复用,如何把 GPU 的吞吐与 CPU 的开销重新配平。
在 nano-vLLM 里,我们能看到这条分工被压缩成一个非常干净的骨架:引擎的核心是一段永不停止的节拍循环,外部看起来像“调度 → 执行 → 回填状态”。这个循环之所以重要,是因为它定义了接口契约。一旦契约稳定,我们就能分别演进调度策略与执行优化,而不至于让它们彼此污染。
如果你想对照实现,可以从 Engine 的节拍与模块契约 入手;如果你还想看“数据面如何被抽象成执行器接口”,可以接着读 执行器接口与职责边界。
3. 调度器:在有限资源下做最优决策
当我们把接口契约钉死之后,系统里的第一位“答题者”就是调度器:它要把抽象的系统目标,翻译成每一步可执行的 batch。
我们可以把调度器理解成“政策制定者”,因为它面对的不是理想化的最优,而是硬约束下的现实取舍。最典型的硬约束通常来自两类预算:
- batch 预算:一次能处理多少 token、多少序列。
- KV 预算:能不能为新请求分配足够的 KV 块,能不能为运行中的请求继续追加。
这很像操作系统的进程调度:waiting 是就绪队列,running 是执行队列;资源不足时的“抢占”不是为了优雅,而是为了让系统不崩。
但推理调度往往更残酷:OS 的时间片主要在争 CPU,而推理系统的瓶颈常常在显存里的 KV。一旦 KV 卡住,吞吐、尾延迟、公平性会一起变糟。于是工业界通常会沿着几条路径不断演进:continuous batching 让 decode 更连续,chunked prefill 避免长 prompt 形成 head-of-line 阻塞,更强的优先级与成本模型则把“系统目标”显式写进裁决逻辑里。
nano-vLLM 的调度策略很朴素,所以它更像是一面镜子:我们能在最小系统里看清楚哪些决策已经不可避免,也更容易理解工业级系统为什么必须在这些点上变复杂。
如果你想把这条“演进主线”落到实现细节,可以按顺序对照 prefill 调度、decode 调度,再回到 postprocess 的状态回填与资源回收语义。
4. KV Cache 管理:虚拟内存的翻版
只要并发上来,我们很快会意识到一个事实:推理引擎真正的“内存”是 KV cache。很多时候,调度器看起来是在调 token,实际上是在调页。
如果我们把 KV cache 看成虚拟内存,很多设计会立刻变得直观:
- 块(block)像页(page):固定大小,便于分配、回收与碎片控制。
- 映射表(block table)像页表:把“逻辑位置”映射到“物理块”。
- 前缀共享像共享页:相同前缀的请求可以复用同一段历史,减少重复占用。
- copy-on-write 是工业级必修课:共享带来收益,也带来一致性与分叉成本。
在 nano-vLLM 里,我们能看到“分页思想”如何把 KV 管理从玄学变成资源会计:一旦你能把 KV 表述成可分配、可回收的块,调度器的很多决策就开始变得可解释、可推导。
当然,工业级系统会把这一层推得更远:从简单共享走向更强的前缀缓存结构(例如基于树的索引),从“只分配不淘汰”走向 LRU/代际管理,从单机显存走向 swap/offload,甚至走向把 prefill 与 decode 的 KV 体系拆开。
当我们理解了 nano-vLLM 的分页思想后,再去看这些复杂实现,就不会轻易迷失在细节里了。如果你想验证这套思想如何落地为具体的接口与数据结构,可以对照 Block Manager 的接口与核心方法。
5. 执行器:从灵活到极致,靠的是“预编译换运行时”
如果说调度器决定“算什么”,那么执行器就要把这个决定变成一次真正的 GPU 计算,并把结果带回来。听起来很简单,但难点在于:我们不可能同时拥有无限灵活性和极致性能。
你可以先用一个粗粒度分工来建立直觉:
- prefill 更偏灵活:长度差异大、批次形态变化多,系统更需要快速适配。
- decode 更偏固定:每步增量很小、循环次数巨大,任何 CPU 开销都会被放大。
因此我们在工业界经常看到同一条高性能系统哲学:用预编译换取运行时效率。它在推理里通常表现为图固化、算子融合、形状分桶等路径,本质上都在做同一件事:承认 decode 的形状变化是有限的,然后用“提前准备好的快路径”把每一步的非 GPU 开销压到最低。
在 nano-vLLM 里,我们能看到“快路径”这个思想如何以最小成本嵌入推理引擎骨架里。我们一旦抓住这点,再去看更复杂的系统优化,就更容易识别它们究竟在压哪一类开销。
如果你想对照实现,可以从 执行器的设计与实现 入手,重点看它如何组织“输入打包、运行路径与返回值契约”。
6. 工业级演进:从 nano 到 vLLM/SGLang
现在我们可以把视角再拉远一点:把 nano-vLLM 当作“骨架”,工业级系统就是“骨架上长出的肌肉”。为了不陷入碎片化的优化清单,我们可以沿着四条主线去理解演进:
- 调度策略:从阶段分离走向 continuous batching,从长 prompt 的阻塞走向 chunked prefill,再走向更细粒度的目标函数(吞吐、尾延迟、SLO、公平性的组合)。
- 缓存管理:从简单分页走向更强的前缀缓存结构与淘汰机制,从“单一显存池”走向 swap/offload,以及围绕共享引入更成熟的 copy-on-write 语义。
- 执行优化:从减少 kernel launch 与 CPU 开销,走向图固化/分桶/融合,再叠加量化、FlashAttention 等算子级优化,最终把瓶颈重新推回带宽与并行通信。
- 架构扩展:从单机单卡走向多卡并行(TP/PP/DP),再走向把 prefill 与 decode 解耦的服务化架构(例如 disaggregated prefill/decode),以便分别优化两种工作负载。
理解 nano 的意义在于:我们拿到了一把“抽象的钥匙”。当你再去读 vLLM、SGLang,或者任何推理引擎的实现时,你会更关注它们如何处理同一组矛盾与权衡,而不是被某个具体工程细节带跑。
如果你想把这张“抽象地图”继续放大,可以从两条主线扩展阅读:vLLM 通常被概括为 paged attention(分页化 KV 管理)与 continuous batching(连续批处理);SGLang 则更强调面向程序化推理工作流的表达与调度(尤其是复杂多轮交互与工具调用场景)。
结语:从代码中看到思想
如果你一路读到这里,我们其实已经达成了一个很实用的共识:读一个小而完整的系统,价值往往不在于“我们学会某个框架怎么写”,而在于我们能从中提炼出可迁移的设计思想。
推理引擎的本质并不神秘:它是在同一套硬件与同一份 KV 资源上,长期管理 prefill 与 decode 这对结构性冲突;它必须在硬约束下持续做决策,并把决策变成稳定可复用的执行路径。
接下来,你只需要沿着文中的链接回到 nano-vLLM-1.md,把关键机制逐一对照清楚,然后再去更大的系统里验证它们如何被工程化。