读 forward_batch_info.py:执行对象如何真正落地#
前面的运行时架构和执行模型已经反复把 ScheduleBatch、ModelWorkerBatch 和 ForwardBatch 当成理解执行主线的关键对象,但如果没有一章真正面对 python/sglang/srt/model_executor/forward_batch_info.py 这棵树,这些对象在很多读者脑中仍然会停留在抽象层:知道它们重要,却说不清它们在源码里到底怎样长成一个可执行对象。
这一章的作用,就是把这棵树压成一条稳定的源码阅读骨架,让读者从“知道有 ForwardBatch”推进到“知道执行对象到底在哪一层被编出来、为什么必须在这里编出来”。
先把这棵树放回调度与执行的接缝#
forward_batch_info.py 真正有价值的地方,不是它定义了几个 dataclass,而是它正好位于几条主线的接缝处:
- 对调度与内存来说,
ScheduleBatch在这里被进一步压成执行视图 - 对运行时架构来说,
ModelRunner真正消费的就是这里编出来的对象 - 对执行模型来说,很多行为分支都会先看
ForwardMode
也就是说,如果 ModelRunner 是执行壳的汇合点,那么 ForwardBatch 就是执行壳真正吃下去的对象形状。它不只是“某个结构体”,而是调度视图真正变成执行视图的最后一层桥。
下面这张图的职责,就是把这一层桥明画出来。
flowchart LR
S["ScheduleBatch"] --> M["ModelWorkerBatch"]
M --> F["ForwardBatch.init_new(...)"]
F --> R["ModelRunner.forward()"]
F --> Modes["ForwardMode / CaptureHiddenMode / spec_info"]这张图比“有三个对象名”多解释的一件事是:ForwardBatch 不是执行层里自己长出来的小对象,而是调度对象被正式编译成执行对象的最后一步。
文件头注释本身就是阅读入口#
这棵树一个很好的地方在于,作者在文件头就把最重要的流向写明白了:
ScheduleBatch -> ModelWorkerBatch -> ForwardBatch
并且还明确区分了它们的职责:
ScheduleBatch更偏 scheduler 侧的高层 CPU 视图ModelWorkerBatch是给 worker 的 GPU 相关子集ForwardBatch才是ModelRunner真正消费的低层 tensor 视图
这段注释之所以值得技术书强调,是因为它已经把读法写出来了。面对这种文件,更好的策略不是一上来滚字段列表,而是先接受它提供的坐标:这是一棵执行对象编译树。
第一主角其实是 ForwardMode#
如果只记一个枚举,最该记的就是 ForwardMode。因为它几乎定义了执行对象的“人格集合”:
EXTENDDECODEMIXEDIDLETARGET_VERIFYDRAFT_EXTENDDRAFT_EXTEND_V2PREBUILTSPLIT_PREFILLDLLM_EXTEND
这非常重要,因为它告诉你:执行层从来不只是“prefill / decode”两种朴素模式。speculative、split prefill、disaggregation、dLLM 这些更复杂人格,都会先在这里变成正式的执行模式,而不是等到 ModelRunner 内部才突然长出来。
因此,这个枚举的意义并不只是实现细节,而是整本执行模型在代码层最值得反复回扣的语义枢纽。
CaptureHiddenMode 揭示了另一件事:返回语义很早就被编进执行对象#
前面的章节已经分别讲过 hidden states 返回和旁路语义,但回到这棵树再看,会更容易理解:
CaptureHiddenMode和ForwardMode是同时进入ForwardBatch的
这说明返回语义不是等模型跑完之后才“再看要不要附加一点东西”,而是在执行对象构造阶段就已经被编进这轮 forward 的人格里。对工程实现来说,这种设计很合理:是否抓 full hidden states、last hidden states,最好在执行前就决定,而不是在执行后临时拼装。
真正读 ForwardBatch 时,不要背字段,要按职责分组#
这棵 dataclass 很厚,但更稳的阅读方式不是逐个字段扫,而是按职责分组。
第一组是基础执行输入:
input_idsreq_pool_indicesseq_lensout_cache_loc
第二组是 extend / prefix 相关状态:
extend_seq_lensextend_prefix_lensextend_start_locextend_logprob_start_lens_cpu
第三组是采样与返回相关状态:
return_logprobtop_logprobs_numstoken_ids_logprobssampling_infocapture_hidden_mode
第四组是模型与输入变体相关状态:
input_embedsreplace_embedstoken_type_idsmm_inputsmm_input_embeds
第五组则是更复杂执行人格的附加状态:
spec_infois_prefill_onlytbo_*return_hidden_states_before_norm
只要这样分组去看,ForwardBatch 就不再只是“一个很厚的对象”,而会变成一轮 forward 所需人格开关与低层数据的统一汇合点。
init_new(...) 才是这棵树最值得盯住的入口#
如果只读一个函数,那一定是 ForwardBatch.init_new(batch, model_runner)。因为这里真正发生的是“把 worker 视图编译成执行视图”。更准确地说,它会持续做五类工作:
- 先把
ModelWorkerBatch里的核心字段搬进ForwardBatch - 再处理 logprob、global token、spec_info、LoRA 等附加人格
- 再按
forward_mode计算 positions、extend 视图和 mrope - 再处理 SWA、ngram embedding、多模态、split prefill 等路径
- 最后在需要时准备 LoRA batch
也就是说,这个函数真正像一台编译器,而不是数据搬运器。它接收的是调度层已经塑形好的 worker 视图,输出的则是 ModelRunner 可直接消费的执行对象。
positions 和 mrope 为什么直到这里才真正被决定#
这也是阅读这棵树时最值得技术书点破的一点。ScheduleBatch 更关心的是:
- 高层请求状态
- waiting / running 视图
- batch 如何组织
而 positions、extend prefix、mrope 这些东西,更贴近真正的 forward 低层语义。它们直到 ForwardBatch.init_new(...) 才被编出来,并不是拖延,而是边界选择得更合理。换句话说,执行层需要的那些低层位置语义,不应该提前污染 scheduler 的高层对象。
这也解释了为什么很多位置相关问题,更稳的源码入口其实是这棵树,而不是一上来就钻进 tokenizer 或 scheduler.py。
is_prefill_only 和 spec_info 分别在提醒你两种复杂度#
is_prefill_only 告诉你:执行对象本身就允许“不进入 next-token 生成语义”的人格存在。这让 embedding、pooling、rerank 或部分 scoring 路径可以在同一执行壳里被正式表示出来,而不是被看成例外。
spec_info 则在提醒另一种复杂度:一旦进入 speculative 路径,变化不只是 ForwardMode 枚举值变了,而是整轮 forward 所需的补充状态也一起进入了执行对象本体。也正因为这样,speculative 一旦打开,读源码时你会明显感觉 ForwardBatch 变厚了。
这章和 5.1、5.x、3.3 的边界#
这章最容易和其他章节看起来重叠,但它们实际回答的是不同问题:
- 5.1 Token 生成循环与执行模型
讲的是执行循环主线怎样推进。 - 第 5 节里其他执行模型章节
讲的是采样、输出、旁路返回怎样运作。 - 运行时架构里关于
ForwardBatch的章节
讲的是这个对象站在什么层。
而这章补的是:forward_batch_info.py 这棵源码树本身怎样读。也就是说,它把“抽象对象”重新还原成了“源文件里的真实组织方式”。这正是代码导读章和原理章最不一样的地方。
最容易出现的三种误判#
第一,误以为 ForwardBatch 只是 ScheduleBatch 的 tensor 化版本。
实际上它还统一承载了很多执行人格开关与返回语义。
第二,误以为 ForwardMode 只是普通枚举。
它其实是这整棵执行对象树的第一人格分流器。
第三,误以为 positions、mrope、多模态状态更像模型内部细节。
在这棵树里,它们才真正被执行化。
真正顺着源码读时,推荐的骨架#
最稳的顺序仍然是:
- 文件头的数据结构流向注释
ForwardModeCaptureHiddenModeForwardBatch字段分组ForwardBatch.init_new(...)
这样读,你先知道这棵树在整个系统里的位置,再进入具体的编译过程,就不容易被大 dataclass 和大量附加状态本身吓散。
小结#
forward_batch_info.py 最值得单独成章的地方,不在于它定义了很多类,而在于它真正完成了一个关键动作:把调度对象编译成执行对象。
到这里,运行时架构、执行模型和代码导读关于 ForwardBatch 的主线,才算在源码层真正闭环。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。