SamplingBatchInfo、vocab mask 与采样元数据流#

这章解决什么问题#

执行模型前面的章节已经解释了 logits、sampler、grammar sync 和 output processing,但还有一层真正把“请求侧采样参数”压成“执行侧采样元数据”的桥没有单独展开:SamplingBatchInfo。如果不把这层讲出来,读者会知道 SamplingParams 很重要,也会知道 sampler 最后拿到了若干 tensor,却不清楚中间到底发生了哪一次批量化压缩。

这一章专门解释这条元数据流。

为什么 SamplingBatchInfo 值得单独讲#

因为它是执行循环里非常典型的“中间抽象”:

  • 向前,它承接每个 Req 上零散的 SamplingParams
  • 向后,它把这些参数压成 sampler 真正消费的 batched tensor 与 mask

也就是说,它不是又一个普通数据类,而是 execution model 在采样边界上的正式收敛层。

一张图:采样元数据是怎样被压缩成 batch 级对象的#

这张图解决的理解障碍是:很多读者会觉得“每个请求有一组采样参数,sampler 再逐条取出来用”,而源码其实早就把它们收敛成批量对象了。

flowchart LR
    Req["per-request SamplingParams"] --> SBI["SamplingBatchInfo.from_schedule_batch()"]
    SBI --> Tensors["temperatures / top_ps / top_ks / min_ps"]
    SBI --> Penalty["penalizer / logit_bias / custom processor"]
    SBI --> Grammar["grammars / vocab_mask"]
    Tensors --> Sampler["Sampler.forward()"]
    Penalty --> Sampler
    Grammar --> Sampler

图比纯文字多解释的一点是:采样不是把一堆 request 参数逐条搬进 sampler,而是先做一次批量语义归并。

from_schedule_batch(...) 真正做了什么#

sampling_batch_info.pyfrom_schedule_batch(...) 是这条桥的核心入口。它至少会做这些事:

  1. 从每个 request 收集 temperaturetop_ptop_kmin_p
  2. 生成 batched tensor,例如 temperaturestop_pstop_ksmin_ps
  3. 计算 is_all_greedyneed_top_p_samplingneed_top_k_samplingneed_min_p_sampling
  4. 生成 sampling_seed
  5. 处理 logit_bias
  6. 构造 custom logit processor 的 batched 视图
  7. 初始化 penalty orchestrator

这说明 SamplingBatchInfo 真正承担的是“把每请求参数空间降维成批量采样执行计划”。

为什么 is_all_greedyneed_top_p_sampling 这些布尔值很重要#

它们看起来只是小优化字段,但实际上直接决定 sampler 会走哪条路径。例如:

  • 全 greedy 时可以直接 argmax
  • 没有 top-p/top-k/min-p 时可以走 simple sampling case

也就是说,SamplingBatchInfo 不只在搬运数据,它还在提前做一次执行路径分类。

vocab_mask 在这一层怎样进入采样#

前面结构化生成章节已经解释了 grammar backend 如何生成 allocate_vocab_mask(...) / fill_vocab_mask(...) / apply_vocab_mask(...)。在执行模型里,真正把它接进采样的是:

  • self.grammars
  • update_regex_vocab_mask()
  • self.vocab_mask
  • self.apply_mask_func

这里的关键在于:

  1. 先从 grammar 列表里找一个可用 grammar 作为 mask 分配器
  2. 为整个 batch 分配 mask tensor
  3. 再逐 request 调 fill_vocab_mask(...)
  4. 最后把 mask 移到执行设备上

这意味着 grammar 对 execution 的影响,不是抽象意义上的“限制输出”,而是非常具体的“修改 batch 级采样可见词表”。

apply_logits_bias(...) 为什么是 execution model 的一个重要关口#

这段函数把多条采样侧信号串在了一起:

  • additive penalties
  • scaling penalties
  • penalizer orchestrator
  • vocab mask
  • logit bias

这说明 sampler 真正拿到的 logits,并不是模型前向直接吐出来那一版,而是已经经过一轮“批量采样语义变换”的 logits。书里把这层讲出来,能明显降低读者对“为什么最终采样结果和裸 logits 不一样”的困惑。

custom logit processor 为什么也在这里汇总#

SamplingBatchInfo 不只是处理内建 top-p/top-k/min-p。它还会把 custom logit processor 按字符串归类,再构造对应 mask tensor。也就是说,系统已经把“用户自定义 logits 变换”也纳入了这套 batched metadata 模型,而不是把它留在每请求的孤立逻辑里。

这进一步说明:SamplingBatchInfo 是采样语义的总汇点,而不是某个单独功能的容器。

SamplingBatchInfo 为什么属于执行模型,而不是 sampling 参数文档附录#

因为这里讨论的不是“字段列表有什么”,而是:

  • 这些字段如何被批量化
  • 如何改变 execution path
  • 如何转成 sampler 真正消费的 tensor 和 mask

这是很典型的 execution model 关注点,而不是 API 文档关注点。

这一层最容易出现的误判#

1. 把 SamplingParams 直接等同于 sampler 输入#

中间还隔着一层 SamplingBatchInfo 的批量化压缩。

2. 把 grammar mask 看成 sampler 额外外挂#

它在这里已经成为 batched sampling metadata 的正式一部分。

3. 以为 top-p/top-k/min-p 的存在只影响数学公式#

它们还会改变 sampler 走哪条执行路径。

如果你要顺着采样边界读源码,先怎么走#

建议按这个顺序:

  1. 先看 SamplingParams
  2. 再看 SamplingBatchInfo.from_schedule_batch(...)
  3. 再看 update_regex_vocab_mask()apply_logits_bias()
  4. 最后再看 Sampler.forward() 如何消费这些 batched 元数据

小结#

这一章真正要补齐的,是 execution model 里非常关键的“采样收敛层”:

  • SamplingBatchInfo 把每请求采样语义收敛成 batch 级执行元数据
  • grammar mask、penalty、custom processor 都在这一层汇合
  • 只有把这层讲透,sampler 才不会显得像一个突然拿到很多 tensor 的黑箱

到这里,执行模型对“采样元数据怎样流动”才真正完整。