home/tutorial/M5 mini-vLLM

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 个核心模块

FastAPI server ~50 行 Engine + Scheduler ~150 行 BlockManager ~80 行 ModelRunner ~150 行 Llama (HF) + FlashAttn 外部依赖 M2 学的 M3 学的
总计 < 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 一行)
卡住时:对照 vLLM 同位置的代码,不丢人。

04关键设计决策清单

开工前先回答下面这些问题,写在 design.md 里:

  1. block size 选多少? vLLM 选 16。你为啥选 16/8/32?(权衡:lookup overhead vs 碎片)
  2. KV cache 多大? 算式:(显存 - 模型权重 - activation buffer) / (2 × layers × heads × head_dim × dtype_size × block_size)。手算一遍。
  3. token budget 怎么定? 一步最多算多少 token?太大 batch step 慢,太小吞吐低。
  4. preempt 选 recompute 还是 swap? 第一版 recompute(更简单)。
  5. attention backend? FlashAttention v2 prefill + 自己 gather KV + PyTorch attention for decode(最简);或都用 paged_attention kernel from vLLM。
  6. Sampling 怎么实现? 至少支持 temperature + top_p。直接 PyTorch 即可。

05"对账"练习

这是这个月最有价值的副产物:

每写完一个模块,立刻打开 vLLM 同位置的代码对照:

把这些对照写在 NOTES.md 里。这份对照笔记 = 你的 blog 草稿

06Benchmark 期望

对照 vLLM 同 model 同负载,你应该看到:

指标你的 mini-vLLMvLLM差距来源
单请求 throughput~50-100 tok/s~100-150 tok/s没 CUDA graph / 没 fused kernel
32 并发 throughput~500-1000 tok/s~2000-3000 tok/sTriton attention / 编译优化
TTFT (4K prompt)~200-400 ms~100-200 msFlashAttention v2 vs v3、kernel 选择
显存利用率~85%~92%没 prefix caching、没 swap

差距是好事——讲清楚每个差距来源,比追平 vLLM 重要得多。 "我做了 X 取了 Y 折中"是工程叙事,"我比 vLLM 慢"不是。

07仓库交付物 checklist

08blog 第 2 篇 · "我从零写了一个 mini vLLM"

这是这个月的另一个产出。结构:

  1. (钩子) 我花了 3 周写一个 500 行 Python 的 LLM serving engine,比 vLLM 慢 3 倍但学到了 10 倍。
  2. (对照) vLLM 在内存 / 调度 / kernel 三层各做了什么,我相应做了什么。
  3. (差距分析) 我的 3× 慢具体来自哪里——一一拆解。
  4. (意外) 写的过程中发现自己 M2 漏掉的细节(举 2-3 个具体例子)。
  5. (结论) "用到再学"在这个项目里的实际操作 + 你以为懂的代码,写一遍才知道哪里不懂。
💡 投递 timing
这篇 blog 上线后立刻在 Twitter / r/LocalLLaMA / vLLM Slack 转。 reach 比你想的高——这类"我重新发明轮子"内容是 infra 圈最爱的格式。 你的下一个工作面试很可能就来自这篇。

09本页自检

Month 5 结束时这些应该全部 ✓