Month 1 · 入口与请求生命周期
目标:能在白板上画出 "curl 进来到 token 流出" 的完整调用链。
这个月不碰 KV cache、不碰 CUDA、不碰调度细节。 你只做一件事:跟着一个真实请求,把它走过的每一层代码都摸一遍。 这一层走通,后面所有深入细节都有"挂靠点"。
驱动问题
你 curl http://localhost:8000/v1/chat/completions ...。
这个 HTTP 请求会经过哪些 Python 对象、被哪些线程/进程处理、最后是谁真的把它送上 GPU?
画一张图,需要画几个框?
先猜一下层数(不用准确)
大概 5–7 层:HTTP server → API handler → engine wrapper (async) → engine core → scheduler → worker → model forward。 每层做的事不一样:HTTP 解析、tokenize、调度入队、batch 组装、GPU forward、detokenize、流式输出。
这一节就是去把这张图真正画对。
01需要的先验知识
下面这些大致懂就行。模糊的现在补,不熟悉的标记一下,月底再看:
- Python async/await:知道
asyncio.create_task()、await让出控制权、event loop 是单线程的。
速补:Real Python 的 asyncio 教程,30 分钟扫一遍即可。 - FastAPI / Starlette:HTTP handler 是 async function;middleware 链;SSE (Server-Sent Events) 流式响应。
- Producer-consumer 队列:一边塞、一边消费、用锁/CV 同步。
- Tokenization:知道 text → token id 的转换是 HuggingFace tokenizer 在做。具体算法(BPE / SentencePiece)这里不需要。
💡 触发的 CS162 章节
这个月会撞到的 OS 概念是 I/O 模型(阻塞/非阻塞/async/epoll)。
深入不必,知道"async 是一种把多个 I/O 操作交错在一个线程里"的实现就够。
真正用得上 OS 调度概念是 Month 3,那时候再展开。
02vLLM 的总体架构
记住两个版本概念:
- v0 引擎 (历史架构,
vllm/engine/):单进程异步。 - v1 引擎 (vLLM 0.6.4+ 默认,
vllm/v1/):API server 和 engine 分进程,scheduler 在 engine 进程,跑得更快、CPU 不再卡瓶颈。
新代码统统看 v1。下图是 v1 架构的简化版:
v1 把"接收请求"和"执行模型"拆到不同进程,靠 ZMQ 通信。这样 GIL 不再卡瓶颈。
03请求生命周期 · 一步一步
下面这套调用链是 v1 在 vLLM main 分支的形态(路径可能微调,但结构稳定)。
拿这张表去对照你 clone 下来的代码。
| # | 发生了什么 | 关键代码位置 |
|---|---|---|
| 1 | HTTP 请求到 FastAPI route handler | vllm/entrypoints/openai/api_server.py |
| 2 | 构造 ChatCompletionRequest,绑模板 → prompt text | vllm/entrypoints/openai/serving_chat.py |
| 3 | Tokenize prompt → list[int] | vllm/transformers_utils/tokenizer.py |
| 4 | 构造 EngineCoreRequest 通过 ZMQ 发到 engine | vllm/v1/engine/async_llm.py |
| 5 | Engine 主循环把它加入 waiting 队列 | vllm/v1/engine/core.py 中 run_busy_loop() |
| 6 | 每一步 schedule() 决定哪些 request 进入这一步的 batch | vllm/v1/core/sched/scheduler.py |
| 7 | BlockManager 给每个 sequence 分配 KV blocks | vllm/v1/core/kv_cache_manager.py |
| 8 | Executor 把 batch 序列化发给 worker | vllm/v1/executor/ |
| 9 | Worker 在 GPU 上跑 model.forward() | vllm/v1/worker/gpu_model_runner.py |
| 10 | Attention kernel 用 block table 访问 KV | csrc/attention/ (CUDA) 或 vllm/v1/attention/ (Triton) |
| 11 | 采样得到下一个 token,回写 KV | vllm/v1/sample/sampler.py |
| 12 | Detokenize 这个 token → str,发回 API server | vllm/v1/engine/output_processor.py |
| 13 | SSE 流式 yield 给 HTTP client | 回到 step 2 的 streaming generator |
⚠ 路径可能小变
vLLM 改名/重构频繁。如果上面某个文件找不到,用 git grep:比如 git grep -l "run_busy_loop" 就能定位。
这是日常技能 —— 教程里的路径只是"线索",不是"地图"。
04这个月你要读的 5 段代码
下面 5 个 anchor,按顺序读完,每段带一个核心问题。
读 ① · 1–2 小时
HTTP 入口。看它怎么注册 routes、怎么启动 engine、怎么 graceful shutdown。
这里有几个 async 的"分叉点"?streaming response 是怎么从 engine 拿数据的?
读 ② · 1 小时
Chat 协议的具体处理:应用 chat template、处理 tool calling、构造 prompt。这是协议层,与 vLLM 引擎无关。
如果我想加一个新的 sampling 参数,要改几个文件?
读 ③ · 2–3 小时(最重要)
两个进程通过 ZMQ 通信的本体。这里读懂,你就理解了"为什么 v1 比 v0 快"。
API server 进程和 engine 进程各持有什么状态?它们之间发什么消息?为什么不放一个进程?
读 ④ · 30 分钟(先看结构,不细究算法)
vllm/v1/core/sched/scheduler.py ·
不要试图全看懂。只看:函数签名、return 什么、循环结构。
Month 3 会深入。
schedule()这个函数的 input 是什么 state?output 是什么 state?
读 ⑤ · 30 分钟
vllm/v1/worker/gpu_model_runner.py ·
Worker 这一头。看它怎么把 scheduler 给的 batch 转成 PyTorch 输入。
execute_model()PagedAttention 在哪里"接通"了 KV cache?看
attn_metadata 怎么构造的。05动手作业 · 画一张请求生命周期图
读完上面 5 段,不看任何东西,自己在白板/Notion/纸上画:
- 所有出现的进程 / 线程框(标注哪个是 async 的、哪个是 worker subprocess)
- 每个框里存的关键状态(队列、cache、KV pool)
- 跨进程的消息箭头(ZMQ payload 是什么形状)
- "一次 decode step" 这条主路径标红
画完后跟本文 §02 那张图对照。发现的不一致就是你下个月的研究方向。
06第一个非 trivial 的 PR(可选)
Month 1 KPI 是至少一个 PR 已 merge。除了 Week 0 的 typo PR,本月可选挑战:
- 给 API server 加一个新的 OpenAI 兼容字段(参考最近相似 PR 的写法)。
- 修一个 streaming response 的 edge case bug(用
label:bug在 issue 列表筛)。 - 给
serving_chat.py写测试覆盖一个未测的分支。
💡 找 issue 的诀窍
label:good-first-issue 标签下的 issue 通常已被人盯,抢手。
更可靠的来源:看最近 merged PR 的 reviewer 留言里 "we should also do X..." 这种隐藏 todo,主动 ping 那位 reviewer。