vLLM 推理引擎:请求处理流程深度解析

1 minute read

Published:

整体流程

一个 LLM 推理请求从 HTTP 进来,到响应返回,经过以下链路:

flowchart TD
    A["HTTP 请求<br>(包含 tools)"] --> B["jinja 渲染<br>(tools → prompt 模板)"]
    B --> C["tokenize<br>(文本 → token IDs)"]
    C --> D["加入调度队列"]
    D --> E["scheduler 调度<br>(资源检查 + 选请求)"]
    E --> F{"新请求?"}
    F -->|是| G["prefill<br>批量计算 KV cache"]
    F -->|否| H["decode<br>逐 token 生成"]
    G --> I["post_process<br>(收集输出 token)"]
    H --> I
    I --> J{"生成完成?<br>(eos / max_tokens)"}
    J -->|否| E
    J -->|是| K["detokenize<br>(token IDs → 文本)"]
    K --> L["API 层组装响应"]
    L --> M["返回客户端"]

各环节对应代码

环节文件
API 入口vllm/entrypoints/openai/api_server.py
引擎主循环vllm/v1/engine/core.py
调度器vllm/v1/core/sched/scheduler.py
模型执行vllm/v1/worker/gpu/model_runner.py

完整请求链路

请求进来
  → jinja 渲染(tools 格式化进 prompt)
  → tokenize(文本 → token IDs)
  → scheduler 调度
    → 新请求 → 分配尽量多的 token(prefill)
    → 已有请求 → 分配 1 个 token(decode)
  → model_runner 执行
    → 多个 token → prefill 路径(并行算 KV cache)
    → 1 个 token → decode 路径(复用 KV cache)
  → 返回结果

代码组织

请求入口
  vllm/entrypoints/openai/api_server.py

引擎主循环(每 step 调一次)
  vllm/v1/engine/core.py → step()
    │
调度器(决定每轮谁跑、跑多少 token)
  vllm/v1/core/sched/scheduler.py → schedule()
    │
模型执行器(实际调用模型 forward)
  vllm/v1/worker/gpu_model_runner.py → execute_model()
    │
Attention 后端(根据 query_len 隐式区分 prefill/decode)
  vllm/v1/attention/backends/flash_attn.py

Prefill/Decode 的”隐式传递”

scheduler 到 model_runner 之间没有 if is_prefill 这样的标签。区分是靠 num_scheduled_tokens 这个数字隐式传递的:

scheduler:  request_A → num_scheduled_tokens = 512("这是 prefill")
            request_B → num_scheduled_tokens = 1  ("这是 decode")
                  ↓
model_runner: max_query_len = max(512, 1)
              _is_uniform_decode(max_query_len) → False
              → 不走 decode 优化路径
                  ↓
attention 后端: query_len = 512 → 调 prefill kernel
                query_len = 1   → 调 decode kernel

这种设计的好处是调度器不需要关心模型执行器的内部实现,两者通过 num_scheduled_tokens 这个数字契约解耦。