ARTICLE · 1341760
Nano-vLLM源码学习(1):项目全景与四层架构
📑 目录
1. 为什么值得读 nano-vllm 2. 目录结构速览 3. 四层架构 4. 一个请求的生命周期 5. 与 vLLM 的关系 总结
1. 为什么值得读 nano-vllm
vLLM、SGLang 这类生产框架的代码量在十万行以上,读的时候很难指着某一行说「批处理就是在这里拼起来的」。nano-vllm 用约 1200 行 Python 把一个推理引擎的主干实现了一遍,适合拿来对照源码学习。
打个比方,vLLM 像一座有地下管网、消防系统、电梯调度的摩天大楼,nano-vllm 则是一栋结构裸露的样板房:墙体、承重柱、水电走线都露在外面,一眼能看出房子是怎么立起来的。它不做生产环境需要的那些外围功能(大量模型适配、各种量化方案、集群编排),只保留算法与工程主干。
GeeeekExplorer,主页标注 DeepSeek、南京大学) | |
nanovllm/包实际 1385 行(含空行,按 2026-04-26 的提交统计) | |
pyproject.tomltorch>=2.4、triton>=3.0、transformers>=4.51、flash-attn、xxhash | |
Qwen/Qwen3-0.6Bmodels/下只有 Qwen3 一个实现 | |
torch.compile、CUDA Graph |
它特别适合两类人:第一次系统学习推理 / Serving 的开发者,以及准备大模型推理岗、需要「能讲到源码级细节」的面试者。熟悉 nanoGPT 的读者可以这样类比:
| nano-vllm | vLLM 等推理引擎 | 批处理、KV 管理、调度、低延迟执行 |
会写模型的 forward之后,推理引擎要解决的是另外几件事:batch 如何动态组装、KV 如何分块存放、何时该 prefill 何时该 decode。nano-vllm 的代码就围绕这几件事展开。
💡 提示:读源码前建议先过一遍第 2 章。本章不再重复 PagedAttention、Continuous Batching 这些概念本身,而是看它们在代码里怎么实现。
2. 目录结构速览
把仓库 clone 下来,核心代码都在 nanovllm/包里,根目录另有 example.py(最简用法)和 bench.py(吞吐基准)。先建立「文件名 → 职责」的映射:
●●●nanovllm/├── __init__.py # 对外导出 LLM, SamplingParams├── config.py # Config 全局配置(dataclass)├── llm.py # LLM:直接继承 LLMEngine,类体只有 pass├── sampling_params.py # SamplingParams 采样超参├── engine/│ ├── llm_engine.py # 引擎主循环:调度 → 前向 → 采样 → 更新│ ├── scheduler.py # Prefill/Decode 调度与抢占│ ├── sequence.py # 单条请求的状态机│ ├── block_manager.py # KV Cache 的分块分配与回收│ └── model_runner.py # GPU 侧执行:张量准备、Context、CUDA Graph├── layers/│ ├── attention.py # Triton 写 KV + FlashAttention 前向│ ├── linear.py # 列/行/QKV/合并 并行 Linear│ ├── embed_head.py # 词表并行 Embedding 与 LMHead│ ├── layernorm.py # RMSNorm│ ├── activation.py # SiluAndMul(SwiGLU)│ ├── rotary_embedding.py # RoPE 旋转位置编码│ └── sampler.py # 温度采样(指数噪声 + argmax)├── models/│ └── qwen3.py # Qwen3 模型实现└── utils/ ├── context.py # 全局推理上下文(is_prefill、cu_seqlens 等) └── loader.py # safetensors 权重加载按目录看分工:engine/决定这一步算谁、怎么调度;layers/和 models/做矩阵运算;utils/负责跨模块传递上下文和加载权重。最外层的 __init__.py只导出 LLM和 SamplingParams两个名字,用户只需要接触这两个入口。
文件体量也能说明重点在哪里。最大的四个文件是 model_runner.py(257 行)、qwen3.py(216 行)、linear.py(156 行)和 block_manager.py(120 行),分别对应执行、模型、张量并行和 KV 管理;llm.py只有 5 行。
3. 四层架构
把这些文件按「谁调用谁」归类,可以得到下面的四层结构。橙色实线箭头是逐层调用,虚线是执行层把 KV Cache 和 logits 交回引擎层:
🔌 第一层:接口层(llm.py+ sampling_params.py+ config.py)
用户只接触 LLM(提供 generate)和 SamplingParams(温度、最大生成长度等)。Config在引擎初始化时把显存比例、最大批大小、张量并行度、KV 块大小等全局约束固定下来。这一层的对外 API 很小,不懂调度也能跑通推理。
🧠 第二层:引擎层(engine/llm_engine.py)
LLMEngine持有调度器、模型执行器和分词器,跑一个循环:从调度器拿一批序列 → 交给执行器做一次前向并采样出下一个 token → 把结果写回序列状态,直到所有请求结束。
🗂️ 第三层:调度层(scheduler.py+ sequence.py+ block_manager.py)
这一层决定「这一步跑谁、它们的 KV 放哪儿」。Scheduler决定本轮是 prefill 还是 decode、把哪些序列拼成一个 batch;Sequence跟踪每条请求的 token、已缓存长度和状态;BlockManager按 PagedAttention 的思路给 KV 分配物理块。这一层只管资源分配与生命周期,不碰矩阵乘法,所以 BlockManager放在 engine/而不是 layers/。
⚙️ 第四层:执行层(model_runner.py+ models/qwen3.py+ `layers/*`)
ModelRunner把调度器选出的序列转成张量:构造 input_ids、positions、slot_mapping、block_tables等,设置全局 Context,然后调用 Qwen3前向。layers/是算子级实现,其中 attention.py有两项任务:把新的 K/V 写进 Cache,以及调用 FlashAttention 计算注意力。
config.py | |
llm.py | generate,内部转交 LLMEngine |
llm_engine.py | |
scheduler.py | |
sequence.py | |
block_manager.py | |
model_runner.py | |
attention.py | |
sampler.py | |
qwen3.py |
4. 一个请求的生命周期
分层说的是静态结构,下面这张图说的是数据流:一个字符串从进入引擎到输出完整文本经过的路径。后面 3.2~3.6 各节分别展开这条链路上的一段。
图中菱形是主循环 step(),蓝色框属于调度器,绿色框在 GPU 上执行,下方六个框组成每一轮的循环体:
按步骤复述一遍(面试里常问「请求进来之后走哪条代码路径」):
1. 📥 用户调用 llm.generate(prompts, sampling_params)。prompt 可以是字符串,也可以是已经编码好的 token id 列表;字符串由add_request内部的 HuggingFace 分词器编码。 2.add_request为每条 prompt 创建一个Sequence,放进Scheduler的waiting队列。 3. ⚙️ 主循环每执行一次step():Scheduler根据 Token Budget 和 KV 余量选出本轮要跑的序列,并标记这是一次prefill(处理 prompt)还是decode(逐 token 生成)。 4.ModelRunner把这批序列转成 GPU 张量,写好全局Context,跑一遍Qwen3。前向过程中Attention把新算出的 K/V 存进 KV Cache,再用 FlashAttention 算注意力。 5. 模型输出的 hidden state 过ParallelLMHead得到 logits,Sampler按温度采样出下一个 token。 6.Scheduler.postprocess把新 token 写回对应Sequence,判断是否命中 EOS 或达到max_tokens。结束的序列释放 KV 块,未结束的留到下一轮 decode。 7. 📤 所有序列结束后,generate按seq_id排序还原请求顺序,解码成字符串返回。
📌 关键点:Prefill 和 Decode 不混在同一个 batch 里,这一点与 vLLM V0 的默认调度一致。prefill 一次处理整段 prompt,属于算力密集型;decode 每条序列每步只算一个 token,属于访存密集型。调度器每一轮要么全做 prefill、要么全做 decode,3.5 会展开这个策略。
5. 与 vLLM 的关系
面试里常被问「nano-vllm 和 vLLM 什么关系」,可以概括为思想同源,工程裁剪。
✅ 推荐表述:nano-vllm 把 vLLM 的主干算法与工程路径压缩到「可读范围」,目的不是替代 vLLM 上生产。它继承了 PagedAttention、Continuous Batching、FlashAttention、TP 等核心机制,省略的是量化、海量模型适配、集群编排这些工程细节。
❌ 不要这样说:「nano-vllm 比 vLLM 落后 / 简陋」。这种说法混淆了教学项目与生产系统的定位。
📝 总结
nano-vllm 用约 1200 行 Python 实现了一个推理引擎的主干,适合作为读 vLLM 类系统之前的样本。 代码按接口层 → 引擎层 → 调度层 → 执行层四层组织,调用方向自上而下,KV Cache 与 logits 自下而上回流。 一个请求的生命周期是一条流水线:分词 → 入队 → 调度 → 张量化 → 前向(写 KV + 注意力)→ 采样 → 写回 → 解码。 Prefill 与 Decode 分批调度,是后续理解调度器的关键前提。
📚 参考资料
nano-vllm GitHub 仓库 nanoGPT GitHub 仓库 Efficient Memory Management for Large Language Model Serving with PagedAttention(Kwon et al., 2023) vLLM 官方文档 - 架构概览















































































