Month 5 · 原创项目 · mini-vLLM
从零写一个能 serve 一个请求的 paged-attention engine。这是简历最重的那张牌。
到目前为止,你都在读别人的代码。这个月反过来: 把 M2-M4 学到的东西用自己的代码重新发明一遍。 发明的过程会暴露所有你以为懂、其实没懂的地方。 这正是这一步的价值。
驱动问题
给你一台 A10、一个 7B Llama、PyTorch 和 FlashAttention 现成 kernel。
不允许 import vllm。你要在 2 周内 serve 一个 OpenAI 兼容请求。
从哪儿开始写第一行代码?
先想一想,再展开建议路径
反直觉答案:从最简单的 single-request loop 开始,先丑后美。
不要先写 block manager、不要先想 batching、不要先用 FastAPI。
第一周的目标只是:"一个 hardcoded prompt 进去,几行 token id 出来"。 第二周才加 paged KV、batching、HTTP。
这就是 v0.1 → v0.5 的渐进路径。
01项目范围 · 明确做什么和不做什么
✅ 做
- 单卡 fp16 推理 Llama 3.1 8B
- 自己的 block manager(固定 16 token block)
- 自己的 continuous batching scheduler(FCFS + recompute preempt)
- OpenAI-compatible chat API (FastAPI)
- SSE 流式输出
- 跟 vLLM 对照的 benchmark
❌ 不做(这版不要)
- 多 GPU TP/PP
- 量化(fp16 即可)
- 多 LoRA
- 自己写 attention CUDA kernel(用 FlashAttention 或 PyTorch)
- 编译器优化(torch.compile 可选)
- 支持多种模型架构(只 Llama)
02架构 · 4 个核心模块
总计 < 500 行 Python 跑通最小可用。简洁是目的,不是约束。
03每周里程碑
Week 1
单请求跑通 (v0.1)
load model → tokenize hardcoded prompt → 朴素 KV cache (contiguous) → loop 一个 token 一个 token decode → 打印输出。不要 paged,不要 batching。证明你能让 7B 模型在你手上跑出文本。
Week 1.5
加 BlockManager (v0.2)
改成 block-based KV cache。block size 16。维护 block table。Attention 用 paged 版本(vLLM 已开源 paged kernel 可以用,或自己写 PyTorch gather 版)。仍是单请求。
Week 2
加 Scheduler (v0.3)
多请求 batching。FCFS + token budget + preempt。Engine 主循环每 step 调 schedule()。这是整个项目最难的一步。
Week 2.5
加 HTTP layer (v0.4)
FastAPI + SSE streaming + OpenAI 协议字段。能用
openai-python SDK 打你的 server。Week 3
Benchmark + 写 README (v0.5)
跟 vLLM 同 model 同 workload 跑对比。差距应该在 2-5×(你慢)。分析差距来源写进 README——这部分才是简历加分项。
⚠ 别在 v0.1 卡住
Week 1 最容易卡的地方:
- HF model 输出格式 vs 你以为的格式(特别是 logits shape)
- fp16 精度差导致输出乱码(用 bf16 通常好一点)
- 没正确处理 KV cache 的增长(每 step append 一行)
04关键设计决策清单
开工前先回答下面这些问题,写在 design.md 里:
- block size 选多少? vLLM 选 16。你为啥选 16/8/32?(权衡:lookup overhead vs 碎片)
- KV cache 多大? 算式:
(显存 - 模型权重 - activation buffer) / (2 × layers × heads × head_dim × dtype_size × block_size)。手算一遍。 - token budget 怎么定? 一步最多算多少 token?太大 batch step 慢,太小吞吐低。
- preempt 选 recompute 还是 swap? 第一版 recompute(更简单)。
- attention backend? FlashAttention v2 prefill + 自己 gather KV + PyTorch attention for decode(最简);或都用 paged_attention kernel from vLLM。
- Sampling 怎么实现? 至少支持 temperature + top_p。直接 PyTorch 即可。
05"对账"练习
这是这个月最有价值的副产物:
每写完一个模块,立刻打开 vLLM 同位置的代码对照:
- vLLM 的
BlockManager跟我的有什么不同?为什么它多了 X 字段? - vLLM 的
schedule()比我的多 50% 代码量。多在哪些 case? - vLLM 在哪些地方做了我没意识到的优化?
把这些对照写在 NOTES.md 里。这份对照笔记 = 你的 blog 草稿。
06Benchmark 期望
对照 vLLM 同 model 同负载,你应该看到:
| 指标 | 你的 mini-vLLM | vLLM | 差距来源 |
|---|---|---|---|
| 单请求 throughput | ~50-100 tok/s | ~100-150 tok/s | 没 CUDA graph / 没 fused kernel |
| 32 并发 throughput | ~500-1000 tok/s | ~2000-3000 tok/s | Triton attention / 编译优化 |
| TTFT (4K prompt) | ~200-400 ms | ~100-200 ms | FlashAttention v2 vs v3、kernel 选择 |
| 显存利用率 | ~85% | ~92% | 没 prefix caching、没 swap |
差距是好事——讲清楚每个差距来源,比追平 vLLM 重要得多。 "我做了 X 取了 Y 折中"是工程叙事,"我比 vLLM 慢"不是。
07仓库交付物 checklist
README.md:架构图 + 启动命令 + 限制列表 + benchmark 结果。design.md:上面 §04 的决策记录。NOTES.md:跟 vLLM 的对照笔记。benchmarks/:benchmark 脚本 + 数据 + 画图代码。tests/:至少覆盖 BlockManager 和 Scheduler 的关键路径。- 一段 30 秒的 GIF / 视频:从启动到收到 streaming token。
08blog 第 2 篇 · "我从零写了一个 mini vLLM"
这是这个月的另一个产出。结构:
- (钩子) 我花了 3 周写一个 500 行 Python 的 LLM serving engine,比 vLLM 慢 3 倍但学到了 10 倍。
- (对照) vLLM 在内存 / 调度 / kernel 三层各做了什么,我相应做了什么。
- (差距分析) 我的 3× 慢具体来自哪里——一一拆解。
- (意外) 写的过程中发现自己 M2 漏掉的细节(举 2-3 个具体例子)。
- (结论) "用到再学"在这个项目里的实际操作 + 你以为懂的代码,写一遍才知道哪里不懂。
💡 投递 timing
这篇 blog 上线后立刻在 Twitter / r/LocalLLaMA / vLLM Slack 转。
reach 比你想的高——这类"我重新发明轮子"内容是 infra 圈最爱的格式。
你的下一个工作面试很可能就来自这篇。