跳到主要内容

3.6 采样与生成:三个旋钮决定它怎么说话

流水线尽头,还差最后一步​

3.5 节的流水线走到尽头,模型交出来的是一张概率表,不是句子。从概率表到落在屏幕上的那个字,中间还有一道工序:怎么选。

最老实的选法是每次都拿概率第一名(贪心)。听起来稳妥,实际写长文很容易翻来覆去说车轱辘话——高概率的路径未必像人话(原因见深入层)。所以主流做法是抽签,但抽签前允许你动三个旋钮改造骰子:温度、top-k、top-p。

亲手转一转:

🎮 动手实验
采样实验台:温度、top-k、top-p 三个旋钮
灰色空心条 = 模型给出的原始概率(来自第 1 章那个迷你语言模型);蓝色实心条 = 三道工序处理后的最终概率。被砍掉的候选会划掉。调旋钮,看分布怎么变形。
今天天▌
top-k:
气
80%
向
20%
💡 要点:模型给的概率只是「原材料」,真正抽签前还要过三道工序——温度把分布调尖(保守)或调平(狂野),top-k 只留前 k 名,top-p 留下概率累计刚够 p 的「核心圈」。ChatGPT 界面里的 temperature 参数就是这里的 T。工序顺序:温度 → top-k → top-p → 重新归一化 → 掷骰子。

灰色空心条是模型给的原始概率,蓝色实心条是三道工序处理后真正参与抽签的概率。请按顺序做四个实验:

实验 ①:温度拉到 0.1。 分布被削成一根独苗,几乎每次都选第一名——组件会提示:T 趋近 0 就是「贪心解码」。适合要答案稳定的场景(数学、写代码)。

实验 ②:温度拉到 2.5,点「✍️ 自动写 15 字」。 分布被抹平,冷门字频频上位,几句话就语无伦次。高温 = 创造力和胡说八道一起来。

实验 ③:温度调回 1.0,top-k 选「前 3 名」。 第 4 名之后全部 ✂️ 出局,剩下 3 个候选把概率蛋糕重新分掉。

实验 ④:top-p 拉到 0.7。 按概率从高到低累加,刚凑够 70% 就收手,圈外全砍。注意它和 top-k 的区别:核心圈的大小会自动伸缩——分布很尖时圈里可能只剩 1 个字,分布平缓时能容七八个。

三道工序的固定顺序:温度(重塑分布)→ top-k(留前 k 名)→ top-p(留核心圈)→ 重新归一化 → 掷骰子。

第四道工序:把走不通的路直接封死​

前三道工序都在按概率筛候选:留高分的、砍长尾的。它们改变的是「哪些字更可能被选中」。

但有一类需求,概率帮不上忙。

假设你要模型只输出 JSON,好让程序直接解析。你在提示词里写得清清楚楚「只返回 JSON,不要有任何多余的话」。它照做了九十九次,第一百次开头来了句「好的,这是您要的结果:」——程序解析失败,整条流水线断掉。

提示词是一句请求,模型有很大概率听,但没有保证。而下游的解析器要的是 100%。

有一个办法能把这件事从「大概率」变成「必然」:

在每一步采样之前,把「会导致格式非法」的候选字直接划掉——概率再高也不许选。

这叫约束解码(constrained decoding)。

它的位置正好在三道工序旁边:温度、top-k、top-p 是按分数筛,约束解码是按规则筛。规则说不行的,分数再高也出局。

举个具体的:模型已经输出了 {"name": ,接下来合法的只有 "(开始一个字符串)、数字、t/f/n(true/false/null)、{、[ 这几类。那么 好、的、这 这些字,无论模型给它们多高的概率,这一步都被划掉了。模型只能在合法的候选里挑。

于是「输出的 JSON 语法合法」不再是一个概率事件,而是一个结构上的保证——因为语法上走不通的路,采样器根本没有提供。

这套机制不只用于 JSON。凡是能写成规则的,都能这么约束:固定的枚举值(只能答「是/否/不确定」)、正则表达式(日期格式)、完整的语法(一段 SQL、一个函数调用)。你在各家 API 里见到的「结构化输出」「JSON 模式」,底下就是这个东西;下篇讲的工具调用能稳定吐出合法参数,靠的也是它。

它保证的是「格式」,不是「对」

这是最容易误读的一点。约束解码能保证输出一定是合法的 JSON,但它对内容一无所知:

  • 字段值可以是编的——{"住宿标准": 8000} 语法完美,数字是错的;
  • 它甚至可能逼着模型说错话。如果规则要求「必须从 A/B/C 里选一个」,而正确答案是「都不对」,模型被迫在三个错误答案里挑一个——你把它的「我不知道」这条路也一起封死了。

由此得到一条实用建议:给约束留一个出口。枚举里加上「不确定」、schema 里允许某个字段为 null,比逼模型硬答要好。

还有一个隐性代价:约束会把模型往它原本不想走的方向推。如果你的 schema 和模型的自然表达差得很远(比如强迫它先输出结论字段、再输出理由字段),质量可能下降——因为它失去了「先想再答」的机会(8.4 节讲的思维链正是靠「先写过程」起作用的)。把推理字段放在前面、结论字段放在后面,往往比反过来效果好。

骰子改造工艺,至此讲全​

回头看,这条线我们埋了很久:0.3 节说「生成文字 = 掷特制骰子」;1.1 节你按过「🎲 按概率抽一个字」——当时模型给什么骰子就掷什么。本节补上了最后一环:掷之前还能改造骰子。你在各家 API 里见到的 temperature、top_p 参数,就是这里的旋钮,一模一样。

这个实验在哪里不准确?

组件里的概率来自第 1 章那个查表迷你模型,不是真正的 Transformer。但结论不打折扣:采样发生在模型之后,它面对的只是「一张概率表」,不管这张表是查表查出来的还是千亿参数算出来的,改造工艺完全一致。另外,真实产品往往还有 frequency penalty、repetition penalty(给已出现过的词降分、抑制复读)等旋钮,思路同源,这里不展开。

小结​

本节要点
  • 模型给概率,采样定文字;这一步不涉及训练,是纯「推理期工艺」。
  • 温度重塑分布:低温保守稳定,高温奔放混乱;T → 0 即贪心。
  • top-k 留前 k 名,top-p 留「累计刚够 p」的自伸缩核心圈,都是砍长尾防怪词。
  • 大模型每次回答不一样 = 采样的随机性;想要稳定,调低温度。
  • 还有第四道工序 约束解码:前三道按分数筛,它按规则筛——把「会导致格式非法」的候选直接置为 −∞-\infty,让不合法的路根本走不通。各家的「JSON 模式 / 结构化输出」就是它。
  • 它保证格式不保证内容,还可能逼模型在错误选项里硬选——记得给约束留个「不确定」的出口,并把推理字段放在结论字段前面。
📝 动手自测0/4
1. 把温度 T 从 1.0 调到 2.0,下一个字的概率分布会怎样?
2. top-p = 0.9 的准确含义是?
3. 你要求模型只输出 JSON,提示词写得很清楚,但偶尔它还是会加一句「好的,这是结果:」。约束解码怎么根治这个问题?
4. 同一个问题问两次,大模型的回答不一样。想让它每次都一样,最直接的做法是?

延伸阅读​