输出处理、reasoning token 与 streaming 边界#
执行模型前面已经把 logits、sampling、finish 语义和 speculative 路径讲得很深了,但如果在 sample 之后就停下,整条 execution model 仍然会少最后一段非常关键的边界:token 怎样被整理成 BatchTokenIDOutput,怎样附着 output logprobs、reasoning token 统计和 finish 元信息,什么时候进入 detokenizer,什么时候又仍然只在 manager 之间以 token 级对象流动。
这章真正补的,是 execution model 最后那段很容易被 request lifecycle 吞掉、其实却属于执行后半段的对象加工链。只有把这段读清楚,execution model 才算真正覆盖了“从 forward 一直到对外 streaming chunk”的完整后半程。
先把 sample 之后的对象链重新画清楚#
很多读者能理解 forward -> sample,却不太清楚 sample 之后系统还要走一段正式的 output processor 和 stream boundary。下面这张图的作用,就是把这段后半程重新压成一条稳定链:
flowchart LR
Sample["sampled token ids"] --> Proc["scheduler_output_processor_mixin"]
Proc --> Batch["BatchTokenIDOutput"]
Batch --> Detok["DetokenizerManager"]
Detok --> Str["BatchStrOutput"]
Str --> TM["TokenizerManager / ReqState / streaming merge"]图里最重要的不是“有五个节点”,而是它明确说明:sample 之后还存在一段正式的 execution-side 后处理链。它既属于执行模型,又决定了 request lifecycle 后半段最终长成什么形状。
真正值得先读的,不是 detokenizer,而是 output processor#
scheduler_output_processor_mixin.py 很容易被误以为只是“收尾辅助 mixin”。从 execution model 的角度看,它其实承担了最后一跳非常关键的职责:
- 更新
req.output_ids - 更新 reasoning token 计数
- 调
req.check_finished(...) - 附着 output logprobs / top logprobs / token-id logprobs
- 组织
BatchTokenIDOutput stream_output(...)
也就是说,sample 选出 token 之后,真正把它变成“稳定输出语义”的工作,主要发生在这里,而不是等 detokenizer 再做一遍。这个判断特别重要,因为它会直接影响排障顺序:很多看起来像 detokenizer 问题的现象,其实更早就已经在 output processor 边界上定型了。
reasoning token 不是独立流,而是同一输出流上的附加语义#
_maybe_update_reasoning_tokens(...) 最值得技术书强调的地方,是它揭示了 reasoning token 在系统里的真实位置。它不是另一条输出流,也不是 parser 事后硬分出来的结果,而是当前输出 token 序列上的一层附加计数语义。系统会根据 think_end_id 或相近边界去更新 req.reasoning_tokens。
这特别值钱,因为它让读者意识到:reasoning 不是“另跑一套模型”,而是对同一输出流加上的另一层解释。这种“同一流上叠加多层语义”的写法,正是 execution model 后半段真正复杂的地方。
_handle_finished_req(...) 是 request state 真正收口的点之一#
从 output processor 视角看,采样出 token 并不等于请求就已经 finished。更稳的理解是:
- sampled token 先进入 request state
- request 再经过 finish 检查
- 之后 finished request 才会被当成稳定对象继续往后送
这意味着 execution 层不是 sample 完就结束,而是要先把 request state 转到一个稳定 finished 语义,后面的 streaming、logging、exporter 和 detokenizer 才能安全工作。这一点和 5.8 LogitsProcessor、采样输出与 finish 语义 形成了很自然的回扣。
stream_output_generation(...) 真正管理的是一组增量边界#
这一段非常值得读,因为它把很多分散字段重新拧成了一根绳:
reasoning_tokensoutput_token_logprobs_val/idx- top logprobs
- token_ids_logprobs
- output text / token ids 的增量边界
send_output_token_logprobs_offset
这里最值得点破的,不是字段多,而是系统在显式维护“已经发到哪里”的偏移边界。尤其是 send_output_token_logprobs_offset,它说明 output logprobs 不是 finished 后一次性整包组出来的,而是允许按已经发送到的 offset 做增量发送。这正是流式输出能稳住的关键工程细节之一。
BatchTokenIDOutput 和 BatchStrOutput 其实就是两个不同进程边界上的对象#
这两个结构特别值得单独记住:
BatchTokenIDOutput更接近 execution-side 的 token 级输出对象BatchStrOutput更接近 detokenizer 之后、准备回到 tokenizer manager 的文本级输出对象
只要把这两者区分开,你在排障时就能更准确地回答:当前故障是在 detokenizer 之前,还是之后。也就是说,它们不是简单的“一个更低层、一个更高层”,而是两个正式进程边界之间的不同对象形态。
DetokenizerManager.handle_batch_token_id_out(...) 不是只做 decode#
detokenizer 这一侧的职责,如果只总结成“把 token 变文本”,会低估它在输出链里的位置。更准确的理解是:它会把 BatchTokenIDOutput 转成 BatchStrOutput,同时继续保留:
reasoning_tokens- output token logprobs
- finish metadata
也就是说,detokenizer 并不是把 token 级语义全都抹平,而是把 token 级和文本级输出语义捆在一起继续往回送。这就是 execution model 和 request lifecycle 在这里真正咬合的方式。
streaming backlog 往往先暴露的是这一层的问题#
如果这一层出了问题,最常见的症状通常不会表现成“模型完全不工作”,而会表现成:
- chunk 边界不稳定
- reasoning content 和普通 content 对不齐
- output logprobs 增量重复或丢失
- detokenizer 后的文本回流明显滞后于 token 生产
这说明 streaming backlog 不只是 request lifecycle 问题,也常常是 output processor 边界问题。也正因此,遇到 streaming 异常时,更稳的入口通常不是先怀疑 detokenizer,而是先回到 sample 之后的这段加工链。
最容易出现的三种误判#
第一,误把 detokenizer 当成唯一的输出处理层。
实际上 output processor 在 detokenizer 之前就已经做了大量输出语义加工。
第二,误以为 reasoning token 只是 parser 结果。
execution 后处理层自己就在持续维护 reasoning token 计数。
第三,误以为 output logprobs 是 finished 后一次性组装出来的。
源码明确支持按 offset 做增量发送,而不是总是整包重建。
真正怀疑问题在输出处理边界时,先怎么查#
更稳的顺序通常是:
- 看 sampled token 之后,
scheduler_output_processor_mixin.py是否正确更新了 request。 - 看
reasoning_tokens和 output logprobs offset 是否正常推进。 - 看
BatchTokenIDOutput到BatchStrOutput的转换是否保留了关键元信息。 - 最后再看
TokenizerManager一侧的 streaming merge 是否只是放大了前面的边界问题。
这种顺序的价值,在于它先确认“执行层怎样把 token 变成正式输出对象”,再去检查更外层的回包合并。
小结#
这章真正补齐的,是 execution model 最后那段经常被忽略的后处理链:token 被 sample 出来,不等于已经变成稳定输出;output processor 负责把 token 序列加工成可流式发送、可附带证据、可做 finish 判断的对象;detokenizer 再把 token 级输出跨到文本级输出。
到这里,execution model 才真正覆盖了“从 forward 到对外 streaming chunk”的完整后半段。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。