同一个 prompt,模型每次给出来的回复都不一样。第一次觉得很新鲜,多试几次就开始怀疑人生——这玩意儿是不是不稳定?其实不是 bug,是模型在“采样”的时候故意加入了随机性。
我们在做文本生成时,模型最后会给每个可能的 token 打一个分数,也就是 logits。把 logits 过一遍 softmax,就变成了一个概率分布。理论上,每一步直接选概率最大的那个 token 就行了,这叫贪心解码。但问题是,人类说话写东西并不是每一步都选“最标准”的那个词。如果模型总是选概率最高的 token,生成的文本很容易变得重复、无聊,甚至有点机械。
为了让输出更像人,我们会在概率分布上做采样。今天解析三个最常见的采样策略:top-k、top-p 和 temperature。
top-k:固定候选名单,简单但有点愣
top-k 的思路非常直接:每一步只从概率最高的 k 个 token 里随机抽,其他低概率的 token 直接不考虑。
比如 k=10,那就把这 10 个 token 的概率重新归一化,然后按照新概率随机选一个。这样既保留了“大概率 token 更容易被选中”的特性,又给那些分数稍低但可能更合适的 token 一些机会。
听起来不错,对吧?但有一个很尴尬的问题:k 到底设多少?
这个 k 很难调。因为语言模型在不同位置的概率分布差别非常大。有些地方分布很集中,前 3 个 token 可能已经占了 90% 的概率,这时候如果你设 k=50,就会把大量概率很低的垃圾 token 也放进候选池,抽出来的东西可能莫名其妙。而有些地方分布很平坦,前 50 个 token 的概率都差不多,这时候如果 k 设小了,比如 k=5,又会丢掉很多合理的选项。
top-k 虽然简单,但固定 k 值很难适应所有上下文。这是它被很多人吐槽的地方。
top-p:核采样,动态圈定候选池
为了解决 top-k 的“k 值选择困难症”,有人提出了 top-p,也叫核采样(nucleus sampling)。
它的做法是:每一步从概率最高的 token 开始累加,一直加到累积概率超过某个阈值 p,然后把参与累加的这些 token 作为候选集合,只在这个集合里随机采样。
举个例子,假设 p=0.9,那么模型会从概率最高的 token 开始往下加,直到这些 token 的总概率达到 90%。这个集合可能有时候只有 3 个 token,有时候有 50 个,完全取决于当前概率分布的形状。
这样做的最大好处是候选集大小是动态的。分布集中的时候,候选池自然小;分布平坦的时候,候选池自然大。它只关注概率分布的核心部分,把那些长尾的低概率 token 直接忽略掉。
我个人更喜欢用 top-p 而不是 top-k,因为它省去了很多调 k 的烦恼。当然,top-p 也有自己的问题,比如 p 设得太高,尾巴还是会混进来;p 设得太低,又会退化成贪心。但总体来说,它比固定 k 灵活不少。
temperature:调节分布的“锐度”
如果说 top-k 和 top-p 是在决定“从哪些 token 里抽”,那 temperature 就是在决定“概率分布本身有多尖”。
temperature 的想法借用了热力学的概念。高温意味着系统更混乱,粒子更容易跳到低能态;低温则更有序。对应到模型里,logits 就有点像能量,我们把 logits 除以一个温度系数 T,再送进 softmax:
softmax(logits / T)
当 T=1 时,分布不变,就是原始概率。
当 T < 1 时,比如 0.5,logits 被放大,softmax 之后的分布会变得更尖锐。概率最高的 token 会被进一步抬高,其他 token 的机会被压低。极端情况下 T 趋近于 0,模型几乎只会选概率最高的那个 token,也就是退化成贪心解码。这种方式输出更稳定,但也更无聊。
当 T > 1 时,比如 1.5,logits 被缩小,分布会变得更平缓。原本概率不高的 token 也会获得相对更高的采样机会,输出会更多样,但也更容易跑偏,甚至出现胡言乱语。
所以 temperature 本质上是在质量和多样性之间做权衡。温度低,质量更稳但多样性差;温度高,多样性上来了但风险也大。
实际使用中的一点感受
这几个参数不是非此即彼的,很多生成任务里会组合使用。比如常见的做法是先用 temperature 调整分布的锐度,再用 top-p 或 top-k 截断候选集。
我的习惯是:
- 如果做事实性问答、代码生成这类对准确性要求高的任务,temperature 会设低一些,比如 0.2 到 0.5,同时配合一个适中的 top-p,比如 0.9。
- 如果是写文案、写故事、闲聊,想要更多创造力和变化,temperature 会设到 0.7 到 1.0,top-p 可以设 0.9 左右。
- top-k 我一般不太单独用,要么不设,要么设一个比较大的值,比如 50,再让 top-p 去做进一步过滤。
当然,不同模型对参数的敏感程度不一样,这些数值只是参考。调参的时候最好自己多试几组,看看输出效果。
调参时建议先固定 temperature 和 top-p,用网格搜索跑几个组合,观察输出质量和多样性的变化,比盲目调参高效得多。
总结一下
采样策略说白了就是两个问题:
- 从哪些 token 里抽? 这是 top-k 和 top-p 负责的。
- 抽的时候概率分布长什么样? 这是 temperature 负责的。
top-k 固定候选池大小,简单但僵化;top-p 动态圈定核心候选集,更灵活;temperature 控制概率分布的陡峭程度,调节随机性的大小。
没有哪一组参数是万能的,理解它们各自在哪个环节起作用,调起来会更有方向感。希望这篇分享能帮你少踩一点我当初调参时踩过的坑。