5.1 Token 生成循环与执行模型

Token 生成循环与执行模型#

这章解决什么问题#

这一章解决的不是“请求怎样进入系统”,而是“一个 batch 已经准备好之后,模型前向、logits 处理、sampling 和 decode 怎样接起来”。如果没有这层理解,前面的生命周期和架构章节会告诉你请求经过了哪些模块,但你仍然不知道真正生成 token 的那一轮循环是如何推进的。

这里最重要的切分是:调度器决定“这轮应该跑谁”,执行模型决定“这一轮怎样跑”。也正因为这样,本章不再讨论请求排队和 KV 复用策略,而是把注意力放在 ForwardBatchModelRunner.forward(...)LogitsProcessorOutputsample(...) 和 speculative decoding 的关系上。

为什么执行模型要单独成章#

源码已经明确把调度层和执行层拆开了。python/sglang/srt/model_executor/forward_batch_info.py 文件头直接写出数据流:ScheduleBatch -> ModelWorkerBatch -> ForwardBatch。这说明调度器并不直接操作模型前向所需的低层 tensor,而是先把请求集合组织成更高层的 batch,再逐步降到执行层。

这类设计有很强的工程理由。调度器关心的是 waiting queue、running batch、prefill / decode 切换与 token 预算;执行层关心的是当前轮 forward 需要哪些 tensor、哪些 logits 要被处理、下一步 sample 该如何产生 next token。如果把这两层揉在一起,调度逻辑会污染执行细节,执行细节又会反向拖慢调度路径。

下面这张图专门解决“调度结束后,执行模型内部到底怎样推进”的理解障碍。它比纯文字多解释了两个关键点:一是 ForwardBatch 在循环里的位置,二是 speculative decoding 并不是另一条完全独立的 pipeline,而是嵌在同一执行链里的模式分支。

flowchart LR
    A["ScheduleBatch"] --> B["ModelWorkerBatch"]
    B --> C["ForwardBatch.init_new(...)"]
    C --> D["ModelRunner.forward(...)"]
    D --> E["LogitsProcessorOutput"]
    E --> F["sample(...) / next token"]
    F --> G["decode continuation"]
    G --> C
    C -. TARGET_VERIFY / DRAFT_EXTEND .-> H["speculative decoding path"]
    H -. verify / accept / reject .-> F

读这张图时,最重要的是看清闭环:batch 进入 ForwardBatch,前向产生 logits,sampling 选出 next token,随后又回到下一轮 decode。speculative decoding 并没有打破这个环,而是把 “verify / draft extend” 插进了 ForwardBatch -> sample 之间。

ForwardBatch 是真正进入前向的最低层视角#

forward_batch_info.py 看,ForwardBatchModelRunner 管理,主要保存低层 tensor 数据,并用 ForwardMode 区分 EXTENDDECODEMIXEDTARGET_VERIFYDRAFT_EXTEND 等不同模式。这个枚举非常关键,因为它把“当前这一轮到底在干什么”编码成了可传递的执行状态,而不是散落在不同分支里的布尔值。

这也解释了为什么 execution model 不能只讲 sampling。真正的 token 生成循环,是从 batch 变成 ForwardBatch 开始的:不同模式决定 attention backend 走哪条路径,决定 decode 是否可走 graph,决定 speculative verify / draft extend 属于哪一类 forward。sampling 只是在这些执行模式之上的最后一步选择。

ModelRunner 如何把前向与采样接起来#

python/sglang/srt/model_executor/model_runner.py 的职责是“跑模型前向”。这里的关键不是逐行解释所有初始化逻辑,而是抓住接口关系:ForwardBatch 进入 ModelRunner.forward(...),结果经过 LogitsProcessor 形成 LogitsProcessorOutput,再由 sampler 选出 next token。这条链路也能在 python/sglang/bench_one_batch.py 里看到缩小版:forward_batch = ForwardBatch.init_new(...),随后 model_runner.forward(forward_batch),再调用 model_runner.sample(...)

这说明执行层在结构上分成了两步。第一步是根据当前 ForwardMode 计算出本轮 logits;第二步是把 logits 和 sampling 参数结合起来决定接下来产生什么 token。对理解系统很重要的一点是:SGLang 不把 sampling 当成模型的一部分,而是把它放在执行链的后半段,以便不同调用路径可以共享同一套 logits 结果。

sampling 参数怎样影响 token 生成#

python/sglang/srt/sampling/sampling_params.py 明确定义了 SamplingParams,其中包含 temperaturetop_ptop_kmin_pfrequency_penaltypresence_penaltyjson_schemaregexebnf 等参数。这里很重要的一点是:这些参数并不是 HTTP API 才有的“表面参数”,它们会一路进入执行链,直接影响 next-token 选择。

这也解释了为什么结构化生成要放在执行模型之后讲。json_schemaregexebnf 并不是额外附加在 API 上的注释,而是 sampling 约束的一部分。换句话说,结构化生成不是“另一个服务”,而是执行模型里的约束模式。

speculative decoding 插在哪一层#

forward_batch_info.pyForwardMode 和官方 docs/advanced_features/speculative_decoding.md 可以看出,speculative decoding 不是把主循环换掉,而是在同一条执行链里加入 TARGET_VERIFYDRAFT_EXTEND 等模式。也就是说,它本质上仍然属于 token generation loop,只是把“先草拟、再验证”的策略编码成不同 forward mode 和不同 batch 处理路径。

这对理解 tradeoff 很重要。speculative decoding 的价值在于减少目标模型逐 token 推进的成本,但代价是执行链变得更复杂,forward mode、draft model、verification path 和调度路径之间的耦合度会上升。因此,本章把它放在执行模型内部,而不是放在调度章节里讨论。

本章对应哪些代码路径#

这一章最重要的文件级锚点包括 python/sglang/srt/model_executor/forward_batch_info.pypython/sglang/srt/model_executor/model_runner.pypython/sglang/srt/sampling/sampling_params.pypython/sglang/bench_one_batch.pydocs/advanced_features/speculative_decoding.md

要继续追执行细节,推荐顺序是:先看 ForwardModeForwardBatch,再看 ModelRunner 如何初始化与 forward,接着看 SamplingParams 如何约束采样,最后再回到 speculative decoding 文档或实现去看 TARGET_VERIFY / DRAFT_EXTEND 这些模式怎样插入主循环。这样读下来,更容易把“请求是怎么来的”和“token 是怎么生成的”分成两层来理解。

小结#

执行模型这一层真正回答的是:在 request 已经被组织成 batch 之后,SGLang 怎样把它变成一次次可推进的 token generation loop。这里的关键不是某个单点优化,而是 ForwardBatchModelRunner、sampling 和 speculative mode 之间的分工。只要这层看清楚,后面理解结构化生成、tool parser 和 API 参数为什么会影响 runtime 行为,就会顺畅很多。