又一次对 Python 推理栈的反叛
5 月底,一个叫 tiny-vllm 的项目悄悄登上了 Hacker News 的 Show HN 板块。作者 jmaczan 用纯 C++ 和 CUDA 从零写了一个 LLM 推理引擎,定位很直接——把 vLLM 的核心思想(PagedAttention、Continuous Batching 这一套)剥离出 Python 框架,做成一个能让人一周读完源码的最小实现。
名字里带个 "tiny",但目标并不小。作者在 README 里把话说得很明白:这不是玩具,是想做一个生产可用、但代码体量保持在数千行级别的推理引擎。对比一下 vLLM 现在的代码库——光 Python 部分就十几万行,加上 CUDA kernel、各种 backend 适配、调度器、分布式逻辑,新人想搞清楚一次 forward pass 到底发生了什么,得啃上好几天。
这就是 tiny-vllm 想解决的问题。

为什么又是"重写 vLLM"
这两年类似的项目其实不少。Karpathy 的 llm.c、llama2.c 走的是教学路线,几百行 C 跑通推理;llama.cpp 则是把工程做到极致,吃掉了消费级硬件的本地推理市场。但介于两者之间——既要有 vLLM 这种服务化引擎的吞吐能力,又要保持代码可读——这块一直没有特别合适的选项。
vLLM 自己也很尴尬。它最初是 UC Berkeley Sky Computing Lab 的研究项目,PagedAttention 论文出来之后火了,紧接着被工业界大量采用,然后就开始无止境地往里加东西:支持 NVIDIA、AMD、Intel、TPU、AWS Trainium、Gaudi,要做 tensor parallel、pipeline parallel、expert parallel,要兼容 GPTQ/AWQ/INT4/INT8/FP8 各种量化,还要对接 FlashAttention 和 FlashInfer。功能堆叠下来,核心调度逻辑被掩盖在层层抽象里。
更关键的是 Python 本身的开销。vLLM 是 "Python 主体 + 预编译 C++/CUDA 二进制" 的混合架构,调度器、请求队列、采样逻辑、KV cache 管理大量跑在 Python 侧。在小 batch、低延迟场景下,GIL 和解释器开销是实打实能测出来的。社区里关于 "vLLM 的 Python overhead" 的讨论已经持续了一年多,官方也在做异步引擎、ZMQ 进程间通信之类的优化,但本质问题没解决。
Tiny-vLLM 的思路就比较激进——整个引擎都用 C++,CUDA kernel 直接调,没有任何 Python 中间层。
技术实现:抓住核心三件事
看了下当前的实现,作者把精力集中在了三个点上:
1. PagedAttention 的 C++ 实现
这是 vLLM 的灵魂。传统注意力机制里,KV cache 是按请求连续分配的,导致大量内存碎片——一个 max_seq_len=4096 的槽位,实际只用到 200 token,剩下的就浪费了。PagedAttention 借鉴操作系统的虚拟内存,把 KV cache 切成固定大小的 block(一般 16 token 一块),按需分配,物理上不连续但逻辑上是一个序列。
Tiny-vLLM 把这套 block manager 完全用 C++ 重写了一遍。block table 是普通的 std::vector,分配/回收走简单的 free list。相比 vLLM 里那个继承了好几层的 BlockManager 抽象,读起来确实清爽。
2. Continuous Batching 调度
这是 vLLM 吞吐量比传统静态 batching 高一个数量级的关键。Cade Daniel 那篇被反复引用的文章——"continuous batching enables 23x throughput"——说的就是这件事。简单讲,就是 decode 阶段每生成一个 token 就重新组 batch,谁结束了立刻替换成新的请求,不让 GPU 空转。
Tiny-vLLM 的调度器是个纯 C++ 实现,事件循环跑在主线程,CUDA stream 异步发射。没有 Ray、没有 multiprocessing、没有 asyncio——这对部署的人来说意味着不用再调那一堆超时和死锁。
3. CUDA kernel 直接复用
作者很务实,没去重写 FlashAttention——这玩意儿 Tri Dao 那帮人写了好几年,谁重写谁吃亏。Tiny-vLLM 直接调用 FlashAttention 的 cu 文件,自己只写 paged attention 的 dispatch 和一些辅助 kernel(rotary embedding、RMSNorm、sampling 之类的小算子)。
和 llama.cpp、SGLang 比起来怎么样
这是大家最关心的问题。我的看法是——三者解决的不是同一个问题。
llama.cpp 的强项在量化和跨平台。它能在树莓派上跑 4-bit 的 Llama,能在 M 系列 Mac 上用 Metal 加速,能在没有 GPU 的服务器上纯 CPU 推理。但它的并发模型很弱,做线上服务不是它的设计目标。
SGLang 走的是另一条路——在 vLLM 之上加更激进的优化,RadixAttention、零开销调度器,主打高并发结构化输出。代码量比 vLLM 还大,更不是给人读的。
Tiny-vLLM 卡的是一个空位:想要 vLLM 的服务能力,但又希望能完全掌控代码。这个需求其实挺真实的,尤其是云厂商和做推理服务的创业公司,他们需要在引擎里加各种私有逻辑——自定义的负载均衡、特殊的采样策略、和自家 KV cache 存储系统的对接——在十几万行的 vLLM 里改这些,每次版本升级都是一场灾难。
还有什么不到位
要泼点冷水。当前 tiny-vllm 的 README 也坦白了,项目还在早期:
- 模型支持单一。目前只验证了 Llama 系列,Qwen、DeepSeek、Mixtral 这些主流模型都还没适配。每加一个模型架构都要写新的 kernel dispatch。
- 量化支持基本没有。FP16/BF16 推理跑通了,GPTQ/AWQ/FP8 这些主流量化方案都还没影。
- 分布式推理是个大坑。tensor parallel 还能写写,pipeline parallel 和 expert parallel 一旦要做,代码量会迅速膨胀,"tiny" 这个定位能不能守住,是个问题。
- 生态空白。vLLM 现在有 OpenAI 兼容 API、Prometheus metrics、LoRA 热加载、speculative decoding 一整套外围设施,tiny-vllm 都得从头补。
所以这个项目现在更适合两类人:一类是想搞懂 PagedAttention 和 Continuous Batching 内部机制的研究者和工程师,把它当作一份带注释的实现来读;另一类是有特殊定制需求、愿意基于一个小代码库 fork 出去自己魔改的团队。
直接拿来替换生产环境的 vLLM?现在还不到时候。
一个更大的趋势
往后退一步看,tiny-vllm 不是孤例。过去一年里,"绕开 Python、用系统语言重写推理栈" 已经成了一个明显的潮流:Mistral 自家的 mistral.rs 用 Rust 写,Hugging Face 的 text-generation-inference 一直在往 Rust 迁移核心逻辑,Karpathy 的实验性项目越来越多用 C/CUDA 直接搞。
这背后是一个共识——LLM 推理已经过了"先跑通再说"的阶段,现在每一毫秒延迟、每一份 GPU 利用率都要抠。Python 在快速迭代研究代码上无可替代,但在生产推理这条链路上,它的边际成本越来越难接受。
vLLM 自己其实也意识到了这个问题,最近版本里在做 V1 引擎重构,目标之一就是把更多调度逻辑下沉到 C++。但船大难掉头,从零重写的 tiny-vllm 反而能跑得更轻。
这种 "小而锋利的开源替代品" 出现的速度和频率,可能比我们想象的还要快。
参考来源
- jmaczan/tiny-vllm - GitHub — Tiny-vLLM 项目主页,包含完整源码、构建说明和设计文档