speculative acceptance、new_accepted_len 与 finish 传播#
这章解决什么问题#
执行模型前面的章节已经讲了 speculative decoding 的存在、Spec V2 带来的复杂度升级,以及 Req.check_finished() 怎样把输出序列转成结束语义,但还有一层非常关键的连接没有被单独讲透:
- speculative 路径到底怎样决定“这一轮真正接受了多少 token”
- 这个 accepted length 又怎样继续影响:
output_ids- finish 判断
- logprobs / output processor
- speculative 统计指标
如果不把这一层讲清楚,读者会知道 speculative 有 draft / verify 两步,却仍然很难准确回答:
- 为什么
new_accepted_len会直接改变 finish 语义 - 为什么
spec_accepted_tokens、spec_verify_ct和 acceptance histogram 必须在输出阶段继续被维护
为什么这层值得单独成章#
因为 speculative 执行的真正复杂度,不只在“多跑了一条 draft 路径”,而在于:
- 它把“本轮到底算接受了几个 token”这件事变成了一个显式运行时状态
而这个状态又不会只停留在 speculative worker 内部。它会继续流入:
Req.output_idsReq.check_finished(new_accepted_len)BatchTokenIDOutput- request-level speculative metrics
这意味着 acceptance 不是 speculative 的局部细节,而是执行后半段的核心传播变量。
一张图:speculative 的关键不是 draft 和 verify 本身,而是 accepted length 如何沿链路往后传#
这张图解决的理解障碍是:很多读者会把 speculative 想成“验证后挑一个结果”,但真正重要的是“这个结果如何继续影响后面的 finish 和输出语义”。
flowchart LR
Draft["draft path"] --> Verify["target verify"]
Verify --> Accept["accepted length / accepted tokens"]
Accept --> Out["req.output_ids append"]
Accept --> Finish["Req.check_finished(new_accepted_len)"]
Accept --> Stats["spec_accepted_tokens / histogram"]
Finish --> Meta["finish_reason / output meta"]图比纯文字多解释的一点是:acceptance 不是 speculative worker 的终点,而是 output processor 和 finish 传播的起点。
为什么 new_accepted_len 是这条链的真正关键字段#
schedule_batch.py::check_finished(new_accepted_len=1) 这个签名本身就已经很说明问题:
- 普通路径默认只接受一个新 token
- speculative 路径则可能一次接受多个 token
这意味着 finish 判断的粒度天然依赖 acceptance 结果,而不是写死为“最后一个 token 看一眼就行”。
从系统设计角度看,这非常合理,因为:
- stop token
- stop string
- grammar termination
- max_new_tokens
这些条件都可能在“这一轮新接受的多个 token”里面中途命中。
如果 still 用单 token 视角,finish 语义就会被错误传播。
Req.check_finished(new_accepted_len) 为什么在 speculative 路径里更重要#
你可以把它理解成一个“窗口化 finish 检查”:
- 普通 decode:窗口通常就是最后一个 token
- speculative:窗口变成“这一轮新接受的多个 token”
因此,new_accepted_len 真正改变的是 finish 检查的观察范围,而不是只改一个计数器。
这也解释了为什么 speculative 路径下 finish 传播会更复杂:
- 不是看“最后一个 token 是什么”
- 而是看“刚刚被确认进入输出序列的这一段里发生了什么”
为什么 acceptance 会继续影响 logprobs 与输出边界#
scheduler_output_processor_mixin.py 里,输出侧会按:
send_output_token_logprobs_offsetoutput_ids_
来决定当前该把哪一段 output logprobs 发出去。
一旦 speculative 一轮接受了多个 token,这里的“当前新输出边界”就不再是简单 +1,而是会被 accepted length 推进得更远。
也就是说,acceptance 结果不只影响 finish,也会影响:
- 本轮哪些 output token 算“新 token”
- 哪些 logprobs / top-logprobs / token-id-logprobs 应该一并推进
这就是为什么 acceptance 必须被放在执行后半段来讲,而不是只留在 speculative worker 章节。
spec_accepted_tokens、spec_verify_ct 和 acceptance histogram 为什么要跟着请求走#
你在:
- speculative worker
scheduler_output_processor_mixin.pyBatchTokenIDOutput
里都会看到这些统计值被继续维护。
这说明它们不是“调试时顺手看看”的临时变量,而是请求级正式统计:
spec_verify_ct:做了多少次验证spec_accepted_tokens:总共接受了多少 token- acceptance histogram:每次接受长度的分布
从技术书角度看,这很值得强调,因为它说明上游并不把 speculative 只当性能黑盒,而是保留了能解释其行为的 request-level 统计语义。
为什么这章和 5.2 / 5.7 / 5.8 不重复#
这三章各自回答的是不同层级的问题:
- 5.2:speculative decoding 作为执行模式整体是什么
- 5.7:多模式叠加后,复杂度为什么升级
- 5.8:采样输出怎样变成 finish 和对外语义
而这一章补的是它们之间的中间桥:
- speculative acceptance 如何把“模式差异”真正传播到 output 和 finish 语义里
也就是说,这章讲的是:
- speculative 的局部结果怎样长成后半段的正式输入
这一层最容易出现的误判#
1. 以为 speculative 只是验证后决定“用哪几个 token”#
它还会继续影响 finish 判断窗口和输出侧统计边界。
2. 以为 new_accepted_len 只是辅助计数#
它直接决定 Req.check_finished() 看哪一段新 token。
3. 以为 speculative 指标只是性能调试信息#
源码已经把它们做成了 request-level 正式输出统计的一部分。
如果你要顺着源码读这条 acceptance 传播链,推荐顺序是什么#
建议按下面顺序:
- speculative worker 里 accepted length / accepted tokens 的产出点
Req.check_finished(new_accepted_len)scheduler_output_processor_mixin.py里 output/logprob 边界推进BatchTokenIDOutput里 speculative 统计字段
这样读,你先看到 acceptance 怎么来,再看到它如何继续影响后半段,不容易把 speculative 和 finish 语义人为切断。
小结#
这一章真正要补齐的,是 speculative 执行路径里此前仍然隐含的一条核心传播链:
- verify 之后最重要的结果不是“通过或失败”本身
- 而是这一轮究竟接受了多少 token
- 这个 accepted length 会继续决定 finish、输出边界和 request-level speculative 统计
到这里,执行模型里关于 speculative 的解释就不只停在模式与复杂度,也真正覆盖了它怎样改变后半段输出语义。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。