第 11 章 采样
本章回答:logits 如何变成下一个 token?温度、top-k、top-p、重复惩罚如何作用于批中不同请求?结构化输出(grammar 约束)在哪里介入?延迟采样(delay sampling)是什么?
11.1 Sampler 的位置
采样是前向与调度之间的最后一步 GPU 工作:TpModelWorker.forward_batch_generation 在 model_runner.forward 返回 logits 后立即调用 model_runner.sample(srt/managers/tp_worker.py:598 的方法体内,:675 与 :686 两处调用点),ModelRunner.sample 委托给 srt/layers/sampler.py:109 的 Sampler。
批内每个请求可以有不同的采样参数,SamplingBatchInfo(采样信息的批形态)把逐请求的温度、top-k、top-p、惩罚打包成批张量,Sampler.forward(sampler.py:141)按其内容选择核函数路径。
11.2 采样主路径
Sampler.forward 的分支(sampler.py:141–420)按复杂度递进:
- 贪婪/纯 argmax:
need_top_p_sampling与need_top_k_sampling均为假时直接取 argmax(sampler.py:203的判定); - top-k / top-p:CUDA 上用 flashinfer 的
top_k_top_p_sampling_from_probs,其他平台按is_hip/is_musa/is_xpu分派(sampler.py:1–60的平台导入块);ROCm 上还可走aiter的greedy_sample,源码注释记录了一个已知坑:该 kernel 在全 NaN/inf logit 行会返回越界 token id(sampler.py:58起的注释),需要防御性修正——这是「测试表达可观察契约、注释记录已知边界」的实例; - 顺序过滤路径:
top_k_renorm_prob→top_p_renorm_prob(sampler.py:370–400)。
与采样并行的还有:min_p 采样、逐请求的自定义 logit processor(Req.custom_logit_processor 字段,srt/managers/schedule_batch.py:999 构造参数可见)、惩罚项(repetition/frequency/presence)在 SamplingBatchInfo 内累积。这些都发生在 logits → 概率 之间。
11.3 结构化输出:grammar 的介入点
受限解码(json_schema / regex / EBNF,--grammar-backend xgrammar)不是采样后的校验,而是采样前的掩码:
srt/constrained/grammar_manager.py:30的GrammarManager在调度器内维护每请求的 FSM(xgrammar、outlines 等后端,srt/constrained/base_grammar_backend.py:373的工厂分发);- 每个 decode 步,FSM 依据当前已生成 token 序列计算合法 token 集,转成位掩码注入采样;
Sampler.forward中sampling_info.grammars is not None的分支应用掩码(apply_grammar_mask一族),使非法 token 概率为 −inf。
掩码的时机与 overlap 调度有耦合:第 6 章已看到 event_loop_overlap 中 Run sample of the current batch — It depends on the result of the last batch (e.g., grammar), so we run it after the last batch is processed 的注释(srt/managers/scheduler.py:2004)——带 grammar 的批必须等上一批结果落账后才能采样,否则掩码基于过期的 token 历史。这是 overlap 优化在正确性面前的显式让步,is_disable_overlap_for_batch(srt/managers/scheduler.py:2015)里 need_grammar_sync 分支同样是这个约束的体现。
11.4 延迟采样:把采样推迟一拍
TpModelWorker.forward_batch_generation 中有一条专门路径(srt/managers/tp_worker.py:668 起的条件分支):
# srt/managers/tp_worker.py:645 附近(节选)
if (
self.enable_overlap
and not self.enable_spec
and (
forward_batch.sampling_info.grammars is not None
or (
envs.SGLANG_ENABLE_DELAY_SAMPLE.get()
and not forward_batch.is_prefill_only
)
)
):
def sample_batch_func():
batch_result.next_token_ids = self.model_runner.sample(
logits_output, forward_batch
)
...
return batch_result
batch_result.delay_sample_func = sample_batch_func
return batch_result即:采样不在此刻执行,而是打包成 delay_sample_func,由第 6 章 overlap 循环末尾的 launch_batch_sample_if_needed 在上一批结果处理完之后调用。收益(源码注释与结构一致):logits 在 GPU 上,采样这段 GPU 工作可以塞进「CPU 处理上一批结果」的窗口;代价:本批 token 的可见性又延后一拍,与 grammar 同步约束共同构成 overlap 模式的时序边界。
11.5 本章收束
- 采样参数是批张量(
SamplingBatchInfo),同一批内逐请求异构;路径按 need_top_k/p 与平台分派。 - 结构化输出是采样前掩码而非事后校验;FSM 状态推进要求「先落账上一批、再掩码这一批」,这是 overlap 与 grammar 的时序契约。
- 延迟采样把采样作为一拍内最后一个 GPU 动作调度,最大化 CPU/GPU 重叠;投机解码与 prefill-only 是明确排除项。
- 下一章:token 已经选出,看它如何流回用户的屏幕。