自回归生成与解码:模型怎样逐个输出 token
生成式语言模型每一步通常输出一组词表分数。解码器把这些分数转成一个 token,再把它接回上下文。用户看见的整段回答,是这个循环的结果。理解循环后,就能区分模型学到了什么、解码规则选了什么,以及服务为什么在某处停止。推理模型先“想”再答,想的那段也是这个循环吐出来的 token,同样按输出计费。
一段文本的概率怎样分解
给定提示 ,输出 的概率可写为:
这是概率链式法则。自回归模型学习这些条件分布;最大似然训练常把乘积改写为负对数概率之和。训练时可以用真实前缀同时计算许多位置的目标,推理时前缀来自模型此前的输出,因此早期错误会改变后面的输入。
每一步用隐藏向量投影出 个 logits,softmax 后得到候选 token 分布。这是“某个片段作为下一步输出”的概率,不是“整句事实正确”的概率。模型可以非常稳定地输出一个错误名字;降低温度不会自动补充缺失知识。
贪心选择与序列搜索
贪心解码每步选概率最大的 token。它便宜、行为相对易解释,却不保证整段序列的概率最大。例如人为构造的两步分布里,第一步 A 为 0.6,B 为 0.4;走 A 后最佳结尾概率为 0.5,走 B 后最佳结尾为 0.9。贪心选中的路径概率是 ,另一条却有 。
Beam search 同时保留若干前缀,扩大序列搜索范围。它需要额外计算和缓存,而且不同长度的概率不可直接当成质量分数:多乘几项小于 1 的概率,长句的联合概率往往更低,所以实现常加入长度处理。更高似然也不等于更好的开放式写作。Holtzman 等人的研究分析了最大化似然解码在开放式文本中的重复与退化,并提出 nucleus sampling。
任务不同,选择不同。短标签或严格抽取可能偏好稳定输出;创作希望覆盖多种合理延续。不能把某一种设置宣布为所有任务的最优值。
温度、top-k 与 top-p
这三个参数是本地和开放权重运行时里的旋钮:Transformers 的 GenerationConfig 和 llama.cpp 的服务器(--temp、--top-k、--top-p)都直接提供。当前托管的前沿推理模型大多限制或拒绝它们,改用 effort 档位,见下一节。
温度作用于 logits,而不是给文本直接加“创造力”:
温度小于 1 会让分布更集中,大于 1 会使分布更平。零温度通常是实现提供的贪心选项或极限行为,不能直接把 代入公式。温度也不是置信度校准:在一个生成分布上变得更确定,不代表真实错误率下降。
Top-k 只保留得分最高的 k 个候选,再归一化采样。Top-p 按概率排序,保留累计质量至少达到 p 的最短前缀,再归一化。若示例分布为 [0.50,0.25,0.15,0.10],top-p 为 0.8 会保留前三项,总质量 0.90;不是把第三项切成 0.05。两种过滤与温度的先后顺序会影响结果,应按实际框架定义;Transformers 的 GenerationConfig 参数说明给出了对应策略。
下面只计算同一组假想 logits 的温度效果,不调用模型:
import math
def distribution(logits, temperature):
scaled = [v / temperature for v in logits]
peak = max(scaled)
weights = [math.exp(v - peak) for v in scaled]
total = sum(weights)
return [round(v / total, 4) for v in weights]
for t in (0.5, 1.0, 2.0):
print(t, distribution([2.0, 1.0, 0.0], t))
随着温度升高,第一项概率下降,其余项上升;原始排序不变。采样时仍可能选中小概率项。固定随机种子有助于复现实验,但服务实现、硬件算子、批量调度等变化仍可能影响完整结果。
保持分数不变,拖动温度滑块:候选项的概率会变化,但大小顺序不变。
这里用三个虚构候选项,分数为 [2, 1, 0]。试试把 T 调到 0.2、1 和 2。
A 始终是概率最大的候选项。升高温度会让概率分布更均匀,但不会补充知识,也不会检查 token 是否正确。此演示不调用模型。
推测解码先提出候选,再通过接受或校正保持目标采样分布,以减少昂贵的串行调用。
托管推理模型用 effort 代替采样参数
截至 2026-10-29,两家主要服务商的做法如下。Anthropic 的 thinking 文档列出了 Claude Fable 5.1、Opus 5.5、Sonnet 5.5、Haiku 5.5 等当前模型。在这些模型上,非默认的 temperature、top_p 或 top_k 每次请求都返回 400 错误;从 API 版本 2026-10-21 起,请求里只要带上这三个参数之一,不论取值,在所有模型上都返回 400。OpenAI 的最新模型指南要求:reasoning effort 不是 none 时,去掉 temperature、top_p 和 top_logprobs,Chat Completions 还要去掉 logprobs。两家都让你改调 effort:Anthropic 的 effort 决定整段回答花多少功夫,包括想得多深;OpenAI 用 reasoning.effort,Chat Completions 里叫 reasoning_effort。
推理 token 也出自同一个逐 token 循环。模型先生成一段思考,再生成你看到的回答,两段都算输出。Anthropic 说明,思考 token 按输出 token 计费,即使思考文本没有返回给你,并且和回答文本一起计入 max_tokens。OpenAI 的推理指南也说,推理 token 在 API 里看不见,却占用上下文窗口,按输出 token 计费;max_output_tokens 限制的是推理 token、可见输出和不可见格式 token 的总数。
所以输出上限要同时留给思考和回答。上限太小,模型可能还没写出一个可见字就停了:OpenAI 这时返回 status: "incomplete",incomplete_details.reason 为 max_output_tokens,输入和推理 token 照样收费。想压低花费,先调低 effort;Anthropic 的说法是,effort 只是软性引导,max_tokens 才是硬上限。
停止是系统决定的一部分
模型可以输出 EOS,服务也可以因为最大输出长度、停止字符串或取消请求而结束。它们代表不同结果:正常终止、预算用尽和用户中断不应都被当成“回答完成”。结构化任务尤其要检查结束原因,避免把被截断的 JSON 当成成功。
托管接口会把结束原因写进响应。Anthropic Messages API 的 stop_reason 有下面几种取值;OpenAI Responses API 在撞上输出上限或上下文窗口时,返回上一节说的 incomplete 状态。
停止字符串可能与正常内容重合;要弄清长度参数统计的是生成的 token,还是输入加输出的总长度;只想限制新增输出时,用接口提供的输出长度参数。模型的上下文窗口还要容纳提示、已生成内容,以及实现所需的其他序列部分。输出变长会增加计算和缓存占用。输入阶段与逐步生成阶段的成本差别见推理延迟与吞吐。
JSON 或工具调用可以在解码时屏蔽不符合语法的 token。这样的约束能提高结构合法性,却不能保证字段里的日期真实、SQL 查询合理或工具参数符合业务约束。完整链路仍要解析、做 schema 检查,并对执行前提作确定性验证。
怎样比较解码设置
固定模型、模板和输入集,再比较几种设置;托管推理模型上比较的是 effort 档位。开放式任务每题生成多次,记下结束原因;分类与抽取比较正确率、有效格式比例、延迟和成本。同一个错误重复十次,说明设置更稳定,不说明它更正确。需要概率作为动作依据时,继续读校准、阈值与弃权;需要评估整套回答系统时,读检索与生成评估。