约束 mask、grammar 同步与 overlap 限制#
执行模型前面已经解释了 sampling、finish 语义、output processing 和结构化生成会在这附近相遇,但如果没有一章把“grammar 到底怎样真正影响采样和执行模式”单独讲透,这条桥仍然会显得断裂。很多读者会自然把 grammar 想成“sample 之后再检查输出是否合法”;源码真正做的事情远不止如此。
这章的价值,正在于把 grammar 从结构化生成章节里重新拉回 execution loop:它不仅会改写 vocab 空间,还会改变 token 同步策略,甚至在 overlap + spec 场景下直接收紧执行路径。也就是说,这不是“功能层的约束”,而是 execution model 自己的一部分。
先把 grammar 放回采样边界#
如果只读结构化生成章节,你会知道:
- grammar backend 能生成 vocab mask
GrammarManager会准备 grammar 对象
但你仍然不知道这些对象怎样真正进入 execution loop。下面这张图的职责,就是把这条桥明确压成一条 execution-side 链:
flowchart LR
Grammar["req.grammar / vocab mask"] --> SInfo["sampling_info.grammars"]
SInfo --> Samp["Sampler / token selection"]
Samp --> Sync["TP sync when grammars present"]
Sync --> Out["output processing / accept_token"]图里最重要的一点是:grammar 并不是“采样之外的附属层”,而是直接卡在 sampling 边界上,并继续影响后面的同步和输出处理。
grammar 真正进入执行层的入口其实在 ScheduleBatch#
这也是很容易被忽略的一点。schedule_batch.py 在构造 ModelWorkerBatch 前,会显式检查:
- 如果
self.sampling_info存在 - 且
self.has_grammar - 就把
self.sampling_info.grammars = [req.grammar for req in self.reqs]
这说明 grammar 对象并不是停在 scheduler 外面的旁路字段,而是在准备 worker batch 时,就已经被正式塞进 sampling metadata。换句话说,从这一刻开始,结构化约束已经是 execution 输入,而不是结构化生成章节独有的上游概念。
这类桥接点特别值钱,因为它能让读者真正看清:为什么 execution model 和 structured generation 在书里必须互相回扣。
grammar 存在时,sampler 为什么必须更保守地同步 token#
sampler.py::_sync_token_ids_across_tp(...) 的条件特别值得读。正常情况下,SGLang 为了性能,并不默认同步最终 token ids across TP;但当:
sampling_info.grammars存在
时,系统会显式触发一次 all_reduce 来同步最终 token ids。源码注释已经把原因写得很直白:平时不做默认同步是为了性能,但使用 grammar,尤其是 xgrammar 时,不同 TP rank 之间出现稀有非确定性的风险更高,因此必须更保守地同步,避免 rank desync 之后挂死。
这是一条特别像“好系统书的桥章节”该讲的内容,因为它把结构化生成从功能层一下拉到了分布式一致性层。只要这一点读稳了,读者就不再会把 grammar 只看成“输出格式更规整”,而会开始看到它对执行路径也有真实代价。
vocab mask 的本质,是直接改写采样支持集#
从 xgrammar_backend.py、llguidance_backend.py、outlines_backend.py 这几条后端路径看,grammar 对象都会实现:
allocate_vocab_mask(...)fill_vocab_mask(...)apply_vocab_mask(...)
这意味着 grammar 对 execution model 的直接影响并不是“抽象上的约束”,而是把当前可选 vocab 空间直接改写成一个受当前 grammar 状态限制的支持集。execution model 到这里,不是“生成之后再过滤”,而是在 sampling 前就直接收窄了候选空间。
这也是为什么第 5 节不能只讲 sampler 算法,而必须把 grammar mask 一起讲进去。否则采样边界会显得不完整。
overlap + spec + grammar 的组合为什么会触发收紧#
这一点是 execution model 最像工程折中的地方之一。scheduler.py 里会判断:
- 当前 batch 是
spec_v2 batch.has_grammarbatch.forward_mode.is_decode()- 且结果队列不为空
一旦满足这些条件,need_grammar_sync 为真,本轮就会关闭 overlap。这个判断特别值钱,因为它说明 grammar 的代价不是“稍微让 sampling 慢一点”,而是会进一步反向约束 execution model 允许使用的优化组合。
这可以非常明确地写成一本系统书里的 tradeoff:
- overlap + spec 的收益是更高并行度
- grammar 的代价是更严格的一致性和同步要求
- 当前实现选择在这个组合下保守退回,优先保证正确性
这类 tradeoff 一旦讲透,读者就更容易理解为什么系统并不总是“功能和性能一起要到最大”。
这章属于 execution model,而不是结构化生成的尾注#
因为到这里,讨论的重点已经不再是 grammar 对象怎样创建,而是它一旦存在,会怎样改写:
- sampling metadata
- TP 同步策略
- overlap 允许性
- output processing 之后的
accept_token(...)
这些都已经是 execution loop 自己的行为边界,而不是单纯的上游 grammar 生命周期。因此把这章放在第 5 节,比把它塞回第 6 节更能帮助读者形成稳定心智模型。
最容易出现的三种误判#
第一,误以为 grammar 只是输出后验证。
实际上它会在采样前直接改写可选 token 空间。
第二,误以为使用 grammar 只会影响功能正确性,不会影响执行模式。
源码已经明确显示它会改写 overlap 和 TP 同步策略。
第三,误以为 sampler 的 token 同步逻辑和结构化生成无关。
恰恰相反,grammar 是触发更保守同步策略的重要条件之一。
真正怀疑结构化约束导致执行层行为变化时,更稳的顺序#
建议按这个顺序:
- 先看
ScheduleBatch.has_grammar是否为真。 - 再看
sampling_info.grammars是否真的被填进 worker batch。 - 再看 sampler 是否因为 grammar 走了额外 TP token 同步。
- 再看当前 batch 是否因此关闭了 overlap。
- 最后才判断问题主要在 grammar object 自身,还是 execution path 被迫收紧。
这条顺序最重要的意义,是它先验证“grammar 有没有真正进入 execution model”,再去判断它在 execution model 里造成了哪类代价。
小结#
这章真正补齐的,是 execution model 和 structured generation 之间最关键的一座桥:grammar 对象一旦进入 sampling_info,就会直接改写采样支持集、TP 同步策略和执行模式组合。
读懂这一层之后,execution model 才算真正把“约束怎样进入 loop”讲透。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。