vLLM 请求生命周期:从 HTTP 入口到 Token 流出

本文以 vLLM V1 为对象,沿着一个在线推理请求的生命周期,说明 API、引擎、调度器、KV Cache、执行器和模型运行器之间的调用关系。不同版本的目录和函数名会调整,以下内容按 V1 的职责划分展开。

1. 全链路

Client → API Server → Tokenizer/InputProcessor → AsyncLLM/EngineClient
       → IPC → EngineCore → Scheduler → SchedulerOutput
       → Executor → Worker → ModelRunner
       → Model Forward + Attention + KV Cache + Sampling
       → ModelOutput → EngineCore 更新 → Detokenizer/OutputProcessor → Client

主循环可以压缩为:

while has_unfinished_requests():
    plan = scheduler.schedule()
    result = executor.execute_model(plan)
    outputs = scheduler.update(result)

schedule 生成本轮工作集合,execute_model 完成 GPU 计算,update 把结果写回请求。未完成请求进入下一轮,完成请求释放资源。

2. API Server:建立外部请求边界

客户端通过 OpenAI 兼容接口发送模型名、prompt、采样参数和长度限制。API Server 负责协议解析、参数检查、请求 ID、取消和流式连接,不直接执行 GPU 计算。

async def create_completion(http_request):
    params = parse_request(http_request)
    request_id = make_request_id()
    async for output in engine_client.generate(
        request_id, params.prompt, params.sampling_params
    ):
        yield encode_sse(output)

3. Tokenizer 与 InputProcessor

模型接收的是整数 token ID,而不是字符串。Tokenizer 将文本编码,InputProcessor 处理 chat template、停止条件和采样参数。

“介绍 KV Cache” → [101, 2054, 893, ...]

内部请求可抽象为:

class Request:
    request_id: str
    prompt_token_ids: list[int]
    output_token_ids: list[int]
    num_computed_tokens: int
    max_new_tokens: int
    sampling_params: SamplingParams
    status: RequestStatus

新请求的 num_computed_tokens 为 0,随后进入 Scheduler 的 waiting 队列。

4. 前端进程与 EngineCore

在线服务常把前端和推理核心分开:

Frontend:HTTP、tokenize、输出解码
    ↓ IPC(常见为 ZMQ)
EngineCore:调度、执行驱动、状态更新

前端通过 EngineClient 发送新请求和取消信号,EngineCore 维护请求表、队列和主循环。这样网络事件不会阻塞 GPU 推理。

5. EngineCore 的 step()

EngineCore 每次只推进请求的一小段状态:

def step(self):
    plan = self.scheduler.schedule()
    result = self.executor.execute_model(plan)
    outputs = self.scheduler.update(result)
    return outputs

一轮的顺序是:读取队列和资源 → 生成计划 → 分发 GPU 任务 → 模型前向和采样 → 更新 token 与 KV 状态 → 返回增量结果。

6. Scheduler:组成一轮执行批次

Scheduler 调度的是 token 计算任务,而不是原始字符串或单个 kernel。它通常维护:

waiting:等待首次执行
running:已经开始、通常处于 decode
swapped:资源不足而暂时暂停
finished:生成结束、等待清理

决策依据包括请求到达顺序、prompt 尚未计算的 token 数、本轮 token budget、KV Cache block 数、batch/序列长度限制,以及 chunked prefill、抢占和优先级配置。

默认策略具有 FCFS 倾向,但不是简单 FIFO:running 请求需要持续 decode,waiting 请求按顺序尝试进入 batch,资源不足时延后或抢占。Scheduler 通常不会每轮读取 GPU 利用率直接做控制,而是通过 token 预算、队列状态和缓存容量间接平衡吞吐与延迟。

Prefill 与 Decode

Prefill 处理尚未计算的 prompt,并建立 KV Cache,一次可处理大量 token,通常偏计算密集。Decode 在历史 KV Cache 上生成新 token,每轮通常一个 token,更受显存带宽和单步延迟影响。

同一轮可以混合两类任务:

A:decode,1 token
B:decode,1 token
C:prefill,128 token

真实实现未必保存 mode 字段,而是根据 prompt 长度、num_computed_tokens 和 num_scheduled_tokens 推断阶段。

简化调度代码

def schedule(self):
    budget = self.max_num_scheduled_tokens
    tasks = []

    for req in self.running:                 # decode
        if budget == 0: break
        if not self.kv_cache.can_allocate(req, 1):
            self.preempt(req)
            continue
        self.kv_cache.allocate(req, 1)
        tasks.append((req.request_id, 1))
        budget -= 1

    while self.waiting and budget > 0:       # prefill
        req = self.waiting[0]
        remaining = len(req.prompt_token_ids) - req.num_computed_tokens
        n = min(remaining, budget)
        if not self.kv_cache.can_allocate(req, n): break
        self.waiting.pop(0)
        self.running.append(req)
        self.kv_cache.allocate(req, n)
        tasks.append((req.request_id, n))
        budget -= n

    return SchedulerOutput(tasks)

7. SchedulerOutput 与 KV Cache

SchedulerOutput 是请求状态到 GPU 输入之间的中间表示:

SchedulerOutput(
    scheduled_requests=[
        {"request_id": "A", "num_scheduled_tokens": 1,
         "block_table": [7, 12]},
        {"request_id": "B", "num_scheduled_tokens": 128,
         "block_table": [3, 8, 15]},
    ],
    finished_requests=[],
)

它描述本轮执行哪些请求、每个请求多少 token、序列长度、位置索引和 KV block 映射。

Attention 的 Key/Value 历史结果保存在 KV Cache。vLLM 采用固定大小 block,逻辑 block 不要求对应连续物理显存:

逻辑 block [0, 1, 2] → Block Table → 物理 block [7, 12, 19]

因此可以按需分配和释放,减少碎片。Scheduler 接纳请求前必须确认 block 足够;结束后释放,资源紧张时可抢占或换出。

8. Executor、Worker 与 ModelRunner

Executor 负责把计划送到执行进程,不重新决定请求顺序。单卡时直接调用 Worker,多卡时协调多个 Worker 以及 Tensor/Pipeline Parallel:

class Executor:
    def execute_model(self, plan):
        return self.worker.execute_model(plan)

class Worker:
    def execute_model(self, plan):
        return self.model_runner.execute_model(plan)

Worker 管理本 GPU 的模型、设备上下文和本地 KV Cache。ModelRunner 才把计划转换为张量并触发模型前向:

def execute_model(self, plan):
    input_ids = self.prepare_input_ids(plan)
    positions = self.prepare_positions(plan)
    attn_meta = self.prepare_attention_metadata(plan)
    logits = self.model(input_ids, positions, attn_meta)
    token_ids = self.sampler(logits)
    return ModelOutput(next_token_ids=token_ids)

准备内容包括 input_ids、position_ids、slot_mapping、block_table、序列长度和 Attention metadata。Prefill 输入一段 prompt;Decode 输入新 token,同时读取历史 KV Cache。

9. 模型前向与采样

input_ids → Embedding → Transformer Blocks
          → Attention + KV Cache → MLP/MoE → logits

Attention Backend 根据 metadata 选择 FlashAttention、FlashInfer 或 PagedAttention 等 kernel,完成 KV 的读写。Sampler 根据 logits 和 temperature、top-k、top-p、重复惩罚等参数选择 token:

next_token_id = sampler(logits, temperature=0.7, top_p=0.9)

10. 状态更新与流式输出

模型输出返回 EngineCore 后,系统把 token 写入对应请求并判断结束:

def update(self, output):
    req = self.requests[output.request_id]
    req.output_token_ids.append(output.token_id)
    req.num_computed_tokens += 1
    if is_finished(req, output):
        req.status = FINISHED
        self.kv_cache_manager.free(req)
    else:
        req.status = RUNNING

结束条件包括 EOS、stop sequence、最大输出长度和客户端取消。未完成请求回到 running,下一轮继续 decode。

生成的 token ID 由 Detokenizer 增量转换成文字,再由 OutputProcessor 封装为 SSE:

token ID → Detokenizer → 增量文本 → SSE → 客户端

11. 一个请求的时间线

请求 A 的 prompt 有 4 个 token,需要生成 3 个 token:

第 1 轮:prefill 4 token,建立 KV Cache,生成 A1
第 2 轮:decode 1 token,读取 KV Cache,生成 A2
第 3 轮:decode 1 token,读取 KV Cache,生成 A3
第 4 轮:遇到 EOS 或长度上限,释放 KV Cache

请求 B 在第 2 轮到达时,可以形成 A decode + B prefill。第 3 轮再形成 A decode + B decode。这就是 continuous batching:每轮重新组成 batch,请求完成后立即退出,等待请求可以补入。

12. 外部组件接入

Mooncake:分布式 KV Cache

Mooncake 适合接在 KV Cache Manager、Connector 和 ModelRunner 的缓存加载路径:

KVCacheManager → Mooncake Connector → 远端 GPU/CPU/内存节点
class ExternalKVConnector:
    def lookup(self, prefix_hash): ...
    def load(self, blocks): ...
    def store(self, blocks): ...
    def evict(self, blocks): ...

请求进入后计算 prompt 前缀哈希并查询远端缓存。命中时只对未命中部分执行 prefill;未命中时完成本地计算,并把 KV block 写回远端。Scheduler 不必整体替换,但必须知道已有多少 token 可复用,以便计算剩余工作量。

DeepEP:MoE Expert Parallel

DeepEP 位于 MoE Router 与 Expert 执行之间:

ModelRunner → MoE Router → DeepEP Dispatch → 远端 Experts
                                      ↑              ↓
                                      └─ DeepEP Combine

Router 决定 token 属于哪些 Expert,DeepEP 负责 dispatch/combine 和跨 GPU All-to-All。接入点通常是 MoE Runner、Expert Parallel 通信后端和 ModelRunner。Scheduler 仍负责 batch 与 token budget;若通信对 batch 形状有约束,再把这些约束作为额外元数据传入执行阶段。

Attention Backend

ModelRunner → Attention Layer → AttentionBackend → FlashAttention/FlashInfer/Kernel

Scheduler 提供序列长度、block table、slot mapping 和 metadata,具体 kernel 由 AttentionBackend 决定。

Prefill-Decode 分离

PD 分离把两类工作放入不同实例:

Router → Prefill Workers
      └→ Decode Workers

系统必须解决 KV Cache 跨实例传输、请求状态同步和两类资源路由。它需要扩展 Router、KV Connector 和执行层,不等于简单替换单实例 Scheduler。

13. 源码阅读顺序

vllm/entrypoints/openai/api_server.py
vllm/v1/engine/async_llm.py
vllm/v1/engine/core.py
vllm/v1/core/sched/scheduler.py
vllm/v1/core/kv_cache_manager.py
vllm/v1/core/block_pool.py
vllm/v1/executor/
vllm/v1/worker/gpu_model_runner.py
vllm/attention/backends/

阅读过程中持续追踪两个对象:Request 的状态变化,以及每一轮 Batch 实际提交了哪些 token。

14. 总结

HTTP → token 化 → waiting
→ Scheduler 生成计划 → Executor/Worker 分发
→ ModelRunner 执行 prefill/decode
→ Attention 读写 KV Cache → Sampler 产生 token
→ EngineCore 更新 → Detokenizer 流式返回
→ 完成并释放资源

Scheduler 决定本轮处理哪些请求及其 token 数;ModelRunner 将计划转换为张量并执行模型;KV Cache 保存可复用历史状态;OutputProcessor 将 token 转成客户端文本。Mooncake、DeepEP 和 Attention kernel 分别位于缓存、MoE 通信和注意力计算路径,扩展应围绕这些边界增加 Connector、Backend 或通信实现。