夜雨聆风学习资料网

ARTICLE · 1341760

Nano-vLLM源码学习(1):项目全景与四层架构

Nano-vLLM源码学习(1):项目全景与四层架构

📑 目录

  • 1. 为什么值得读 nano-vllm
  • 2. 目录结构速览
  • 3. 四层架构
  • 4. 一个请求的生命周期
  • 5. 与 vLLM 的关系
  • 总结

1. 为什么值得读 nano-vllm

vLLM、SGLang 这类生产框架的代码量在十万行以上,读的时候很难指着某一行说「批处理就是在这里拼起来的」。nano-vllm 用约 1200 行 Python 把一个推理引擎的主干实现了一遍,适合拿来对照源码学习。

打个比方,vLLM 像一座有地下管网、消防系统、电梯调度的摩天大楼,nano-vllm 则是一栋结构裸露的样板房:墙体、承重柱、水电走线都露在外面,一眼能看出房子是怎么立起来的。它不做生产环境需要的那些外围功能(大量模型适配、各种量化方案、集群编排),只保留算法与工程主干。

📊 维度
📝 说明
作者
Xingkai Yu(GitHub GeeeekExplorer,主页标注 DeepSeek、南京大学)
代码量
README 称约 1200 行;nanovllm/包实际 1385 行(含空行,按 2026-04-26 的提交统计)
协议 / 热度
MIT;截至 2026-11-01 约 16.2k stars、2.8k forks
版本
pyproject.toml
标注 0.2.0,依赖 torch>=2.4、triton>=3.0、transformers>=4.51、flash-attn、xxhash
示例模型
Qwen/Qwen3-0.6B
,目前 models/下只有 Qwen3 一个实现
优化套件
前缀缓存、张量并行、torch.compile、CUDA Graph

它特别适合两类人:第一次系统学习推理 / Serving 的开发者,以及准备大模型推理岗、需要「能讲到源码级细节」的面试者。熟悉 nanoGPT 的读者可以这样类比:

🔎 项目
对应「完整版」
教学侧重点
nanoGPT
大规模语言模型训练
训练循环、Transformer Block
nano-vllmvLLM 等推理引擎批处理、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
全局超参,与 HuggingFace 配置对齐
llm.py
对外 generate,内部转交 LLMEngine
llm_engine.py
推理主循环:调度 → 前向 → 采样 → 更新
scheduler.py
选序列、组 batch、Prefill/Decode 策略、抢占
sequence.py
单请求状态机
block_manager.py
KV 块分配、回收与前缀缓存
model_runner.py
GPU 侧执行、Context 注入、CUDA Graph
attention.py
Triton 写 KV + FlashAttention 两条路径
sampler.py
按温度从 logits 采样下一个 token
qwen3.py
Transformer 层堆叠与权重映射

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
代码量
约 1200 行,单仓库可通读
十万行量级、多模块
核心思想
分块 KV、连续批处理、FlashAttention、张量并行
同类思想 + 更完整的生产特性
功能覆盖
Qwen3 路径 + 教学向最小闭环
多模型生态、量化、API Server、分布式
定位
学习、面试、二次实验
生产 Serving

✅ 推荐表述: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 官方文档 - 架构概览

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料