03 · Sequence:请求的状态机
先建立直觉:RUNNING 不是“缓存已完成”
一个请求已经是
RUNNING,并且 Scheduler 已经安排了 token,为什么num_cached_tokens仍可能是 0?
先观看这段动画。它跟随同一个 Sequence,依次展示 WAITING → RUNNING → FINISHED,以及 Scheduler 写下计划、ModelRunner 执行、KV Cache 提交进度的先后关系。
先记住这一句:num_scheduled_tokens 是这一轮的工作计划;模型实际执行并完成后,num_cached_tokens 才会增长。两者在某一刻可能相等,但它们不是同一个概念。
Sequence 不只是 token 列表
把 Sequence 看成一个请求的随身档案。它同时保存原始 prompt、后来生成的 completion、当前总 token 数、缓存进度、本轮工作计划、block table 与采样参数。这些信息集中在上游 nanovllm/engine/sequence.py 的 Sequence 对象中。
三个状态
WAITING → RUNNING → FINISHEDWAITING:尚未获得本轮执行资格,通常也还没有完整的 block table。RUNNING:已分配缓存资源,参与 Prefill 或 Decode。FINISHED:遇到 EOS 或生成长度上限,随后释放 block。
四种长度不要混淆
| 字段 / 属性 | 含义 | 由谁在什么时候改变 |
|---|---|---|
num_tokens | 当前总 token 数,prompt + completion | 创建时写入 prompt;Decode 后由 append_token() 增加。 |
num_prompt_tokens | 初始 prompt 长度,创建后不变 | 创建 Sequence 时固定。 |
num_cached_tokens | 已经写入 KV Cache 的 token 数 | 模型执行与后处理完成后更新。 |
num_scheduled_tokens | 本轮被 Scheduler 选中的 token 数 | Scheduler 在本轮决策时写下计划。 |
特别注意:scheduled 不等于 cached。Scheduler 先计划,ModelRunner 执行成功后,postprocess 才更新 cached 数量。
block table 是逻辑地址表
Sequence.block_table 保存该请求使用的物理 KV block id。token 的逻辑位置通过:
logical_block = token_position // block_size
physical_block = block_table[logical_block]
slot = physical_block * block_size + offset映射到 KV Cache 的具体槽位。下一章 Scheduler 和后续 BlockManager 都依赖这张表。
去哪里看代码
不要一次打开所有引擎文件。先从 Sequence.__init__ 看状态和计数的初值,再看 append_token() 如何只更新 token 档案。随后沿着教程的调用链追踪:
LLMEngine.step → Scheduler.schedule → ModelRunner.run → Scheduler.postprocess这条链路分别负责本轮计划、真实执行、token 追加和缓存进度提交。动画中的代码卡片是概念化锚点,不是逐行源码复刻。
动手验证:预测 → 运行 → 解释
先扮演一次 Sequence,再点击实验按钮。你要关注的不是“状态是否变了”,而是哪一个字段在这个时间点应该变化。
| 步骤 | 你要做什么 | 你应该观察什么 |
|---|---|---|
| 1. 预测 | 装载 8-token 的预设 Sequence,选择“调度后哪个字段仍为 0”。 | RUNNING 不会自动让 cached 增加。 |
| 2. 运行 | 依次点击“计划 Prefill”和“提交缓存”;随后执行一次 Decode。 | scheduled 先增长,cached 后增长;Decode 只让 completion 前进一步。 |
| 3. 解释 | 核对预测,并用“状态、计划、执行、提交”四个词解释日志。 | 字段变化由不同的引擎阶段负责,而不是由一个按钮同时完成。 |
`num_scheduled_tokens` 与 `num_cached_tokens` 的正确关系是什么?
先在互动实验中完成一次“计划 Prefill → 提交缓存 → Decode”,再写一句话说明三个动作各自改变了哪个字段。
下一节:Scheduler