01 · 从 Prompt 到第一个 Token
最小使用方式
nano-vLLM 有意让公开 API 接近 vLLM:创建 LLM、创建 SamplingParams,然后调用 generate()。
python
from nanovllm import LLM, SamplingParams
llm = LLM(
"/path/to/Qwen3-0.6B",
enforce_eager=True,
tensor_parallel_size=1,
)
params = SamplingParams(temperature=0.6, max_tokens=64)
outputs = llm.generate(["Hello, Nano-vLLM."], params)
print(outputs[0]["text"])nanovllm/llm.py 几乎没有逻辑:LLM 只是继承 LLMEngine。真正入口位于 engine/llm_engine.py。
一次调用的五个阶段
- Tokenizer:字符串被编码成 token id。
- Sequence:请求被包装成带状态、长度、采样参数和 block table 的对象。
- Scheduler:请求进入 waiting 队列,之后被挑选进 Prefill 或 Decode 批次。
- ModelRunner:准备 GPU 输入,执行 Qwen3 模型并得到 logits。
- Sampler:按 temperature 选择下一个 token,回到调度循环。
generate() 不是“一次模型调用”,而是一个循环:只要还有未完成请求,就不断 step()。每次 step 都要重新调度、执行并更新状态。
为什么先开 enforce_eager=True
Eager 模式更容易调试:每一步按照普通 PyTorch 执行,堆栈清楚。等你确认功能正确,再关闭它,让 ModelRunner 捕获 CUDA Graph。学习阶段先追求可观察性,再追求性能。
读源码时只追一条请求
第一次阅读不要同时理解多进程、Tensor Parallel 和 CUDA Graph。给一个 prompt,使用单 GPU、Eager 模式,在以下位置加日志:
LLMEngine.add_request():打印 token 数。Scheduler.schedule():打印 waiting / running。LLMEngine.step():打印is_prefill。Scheduler.postprocess():打印新 token 与完成条件。
这样你会得到一条完整、可验证的请求生命周期。
HTML INTERACTIVE LAB01 · 从 Prompt 到第一个 Token
单独打开 ↗课后习题等待完成
`LLM.generate()` 为什么需要反复调用 `step()`?
动手任务
复制上面的最小示例,将 max_tokens 改为 8,并在纸上写出你预期的 generate → step → schedule → execute → postprocess 循环。
下一节:整体架构