定位与总体架构
本章回答:vLLM 解决什么问题、由哪些进程与组件构成、一次推理请求如何穿过整个系统。它是后续所有章节的地图。
LLM 服务的核心矛盾
LLM 推理的算力成本可以摊薄,但显存容量无法:每个请求都要为 KV Cache 预留一段随序列长度线性增长、且在生成结束前不能被别人占用的显存。传统实现按"最长可能序列"连续预留,实际利用率常常不到两成——碎片和预留浪费把并发度卡在个位数。
vLLM 的回答是 PagedAttention:把每个请求的 KV Cache 切成固定大小的 block(如 16 个 token 一块),按需分配、用完即还,由一个全局块池统一调度。配合连续批处理(请求随到随进、随完随走)与前缀缓存(相同前缀共享 block),vLLM 把吞吐量从"能不能跑"的问题变成了"调度做得好不好"的问题。
V1 引擎的进程模型
快照中的 vLLM 只有 V1 引擎(V0 已删除)。它的静态结构是一个三层进程树:
flowchart TB
C["客户端<br/>HTTP / WebSocket / Python"] --> API["API 进程<br/>FastAPI 路由 + AsyncLLM + OutputProcessor"]
API -- "ZMQ + msgpack<br/>EngineCoreRequest" --> EC["EngineCore 进程<br/>Scheduler + 结构化输出"]
EC -- "ZMQ<br/>EngineCoreOutputs" --> API
EC -- "execute_model<br/>(进程内 / 子进程 / Ray)" --> W["Worker 进程(每 GPU 一个)<br/>GPUModelRunner + 模型 + KV Cache"]
W -- "ModelRunnerOutput" --> EC各层职责用一句话说清:
- API 进程:协议解析、tokenizer 渲染(
renderers/)、流式输出分发。它不持有任何 GPU 资源,因此可以水平扩展(--api-server-count)。 - EngineCore 进程(
vllm/v1/engine/core.py:111):全局唯一的调度大脑,持有 Scheduler、KV Cache 元数据和结构化输出管理器,每步产出SchedulerOutput并消费ModelRunnerOutput。 - Worker 进程(
vllm/v1/worker/gpu_worker.py:183):每个 GPU 一个,持有真正的模型权重与 KV Cache 张量,执行前向计算。
进程间只有两条消息通道:下行 EngineCoreRequest/SchedulerOutput(msgspec.msgpack 序列化,vllm/v1/engine/core_client.py:20),上行 EngineCoreOutputs。张量从不跨 EngineCore↔API 进程边界——只有 token id 和统计信息。
一次请求的完整生命周期
以 /v1/chat/completions 为例,黄金路径一共九跳:
sequenceDiagram
participant C as 客户端
participant A as API 进程
participant E as EngineCore 进程
participant W as Worker / GPU
C->>A: POST /v1/chat/completions
A->>A: 渲染 prompt + 多模态预处理
A->>E: add_request(EngineCoreRequest)
E->>E: Request 构造 + 文法编译
loop 每个 engine step
E->>E: scheduler.schedule() → SchedulerOutput
E->>W: executor.execute_model()
W->>W: _update_states → 前向 → 采样
W-->>E: ModelRunnerOutput
E->>E: scheduler.update_from_output()
end
E-->>A: EngineCoreOutputs(ZMQ)
A->>A: OutputProcessor + 增量反词元化
A-->>C: SSE 流式 delta- 入口:
vllm serve启动 FastAPI 应用,路由处理函数调用AsyncLLM.generate()(vllm/v1/engine/async_llm.py:664); - 跨进程下发:
AsyncLLM.add_request()(async_llm.py:372)把请求封装后放入 ZMQ input 队列; - EngineCore 收请求:
EngineCore.preprocess_add_request()(vllm/v1/engine/core.py:1049)构造内部Request对象(vllm/v1/request.py:60),结构化输出在此时异步编译文法; - 调度:
Scheduler.schedule()(vllm/v1/core/sched/scheduler.py:557)在 token budget 内为 running/waiting 请求分配 KV block,产出SchedulerOutput(vllm/v1/core/sched/output.py:232); - 执行:
EngineCore.step()(core.py:633)把SchedulerOutput交给执行器;Worker 侧GPUModelRunner.execute_model()(vllm/v1/worker/gpu_model_runner.py:4149)维护持久批次、构建 attention metadata 并运行前向; - 采样:
Sampler.forward()(vllm/v1/sample/sampler.py:72)完成温度、惩罚、logits 处理器与采样; - 回传:
Scheduler.update_from_output()(scheduler.py:1967)更新请求状态、检测停止条件,产出EngineCoreOutputs; - 流式恢复:API 进程的
output_handler后台任务(async_llm.py:791)拉取输出,OutputProcessor(vllm/v1/engine/output_processor.py:464)做增量反词元化并投递到每请求的流队列; - 协议输出:路由处理函数把 delta 包装成 SSE 事件发回客户端。
这个九跳链条就是本系列的主线:第 4 章展开第 1–3 跳,第 5–8 章逐个深入第 4–7 跳的每个组件。
三个最有辨识度的机制
PagedAttention 与块池。KV Cache 按 block 分配,KVCacheManager(vllm/v1/core/kv_cache_manager.py:131)在每步调度时调用 get_computed_blocks() 找已缓存前缀、allocate_slots() 分配新块。块用 BlockHash 作内容寻址键,天然支持前缀共享与 LRU 淘汰。
统一的 token 视角调度。源码里有一段关键注释(scheduler.py:559):调度器没有"prefill 阶段"与"decode 阶段"之分——每个请求只有 num_computed_tokens 和 num_tokens_with_spec 两个计数器,每步调度器把 num_computed_tokens 往前推。这一个模型同时表达了 chunked prefill、前缀缓存命中和投机解码,是理解调度器的钥匙。
进程边界即信任边界。Scheduler 状态只在 EngineCore 进程存在,模型与 KV 张量只在 Worker 进程存在;任何一端崩溃都可以被检测(EngineCoreClient 的 ZMQ 心跳与 BackgroundResources,core_client.py:460)而不会半死不活。
本章收束
- vLLM 用 PagedAttention 把"显存够不够"转化为"块分配算法",这是它所有调度能力的物质基础;
- V1 引擎是三层进程树:API 无 GPU 状态、EngineCore 独占调度、Worker 独占执行;
- 一次请求依次穿过入口渲染 → 跨进程下发 → 调度 → 执行 → 采样 → 流式恢复九跳。
下一章先给出整个仓库的地图,把上面提到的模块放进各自的目录与依赖方向里。