深蓝黑背景封面。左侧上下两块分布图面板。上面板标题「temperature:改分布锐度」,左右两组概率柱:左组标注「T = 0.2 尖锐」,一根高柱配四根矮柱,概率几乎全压在最大项;右组标注「T = 2.0 平坦」,五根柱子高度接近,候选之间差距被抹平。下面板标题「top-k / top-p:圈候选范围」,六根从高到低排列的概率柱,前三根为青色高亮、其余暗灰,旁注「top-k = 3:只留前 3 个,其余置零再归一化」;第四根柱后有一条紫色竖虚线,旁注「top-p = 0.9:按概率累加,凑够 90% 就截断」。右侧大标题「temperature、top-p、top-k」「解码参数到底怎么调」分两行,副标题「温度调分布锐度,截断圈候选范围」,小注「创作要发散、问答要稳、代码要准」。左上标注「知识延伸 · 解码策略」,右上圆角胶囊「Day 07 · 12-04」。 图 1:本文封面,温度改形状、截断圈范围的两个面板。

这个系列的前一篇真题解析讲过「大模型为什么会变复读机」,成因机制那边已经讲透,本篇不重复展开。这里换个视角,写一份解码参数的调节手册:模型每生成一个 token 都要从概率分布里做一次选择,贪心、束搜索、temperature、top-k、top-p 这些旋钮各自改变什么、不同任务怎么配。文中的分布示例为演示构造,只做定性说明。

先分两条路线:确定性解码和采样解码。

确定性路线不掷骰子:贪心每步直接取概率最大的 token;束搜索(beam search)每步保留累计分数最高的 num_beams 条候选序列,最后挑整体最优的那条,num_beams=1 时退化为贪心。这条路线稳定、可复现,代价是容易掉进高概率的循环套路,长文本尤其容易复读。采样路线掷骰子:按分布随机抽,Hugging Face 的 generate 里用 do_sample=True 开启,num_beams=1 加 do_sample=True 就是多项式采样。关键认知:temperature、top-k、top-p 这三个参数只对采样路线生效——不开采样,调它们没有任何效果,这是最常见的误用点。

temperature:改分布的形状,不动候选名单。

温度的全部改动只有一处:softmax(z_i / T),logit 先除以 T 再过 softmax。T 小于 1 把 logit 差距放大,分布变尖,头部 token 更垄断;T 大于 1 把差距抹平,尾部 token 也有机会;T 趋近 0 时分布收缩成一个点,效果近似贪心;默认 T=1 保持原分布。用一组构造的 logit 实测(Node.js 22.15.0,node decode.js),词表六个 token「的/是/我/在/一/不」,logit 为 [4.0, 2.5, 2.0, 1.0, 0.5, -1.0]:

function softmax(logits, T = 1) {
  const m = Math.max(...logits);
  const e = logits.map(z => Math.exp((z - m) / T));
  const s = e.reduce((a, b) => a + b, 0);
  return e.map(v => v / s);
}
function topK(probs, k) { // 只留前 k 个,其余置零再归一化
  const idx = probs.map((p, i) => [p, i]).sort((a, b) => b[0] - a[0]).slice(0, k);
  const s = idx.reduce((a, [p]) => a + p, 0);
  return idx.map(([p, i]) => [TOKENS[i], p / s]);
}
function topP(probs, p0) { // 按概率累加,凑够 p0 就截断
  const sorted = probs.map((p, i) => [p, i]).sort((a, b) => b[0] - a[0]);
  const kept = []; let acc = 0;
  for (const [p, i] of sorted) { kept.push([p, i]); acc += p; if (acc >= p0) break; }
  const s = kept.reduce((a, [p]) => a + p, 0);
  return kept.map(([p, i]) => [TOKENS[i], p / s]);
}

实测输出:

T=0.2: P(的)=99.9%, P(是)=0.1%, P(不)=0.0%
T=1: P(的)=69.2%, P(是)=15.4%, P(不)=0.5%
T=2: P(的)=43.1%, P(是)=20.4%, P(不)=3.5%
top-k=3 候选集: 的 73.6%, 是 16.4%, 我 10.0%
top-p=0.9 尖分布(T=1): 留 3 个候选 — 的, 是, 我
top-p=0.9 平分布(T=3): 留 5 个候选 — 的, 是, 我, 在, 一
贪心: 永远是「的」
采样 T=0.2 连采 8 次: 的 / 的 / 的 / 的 / 的 / 的 / 的 / 的
采样 T=2.0 连采 8 次: 的 / 是 / 的 / 是 / 我 / 不 / 是 / 我

T=0.2 时第一名拿走 99.9%,基本等于贪心;T=2 时尾部的「不」从 0.5% 涨到 3.5%——低概率垃圾 token 混进来的风险同步放大。这就是不能只靠拉高温度追求多样性的原因:温度没有判断力,它把好的和差的候选一起抬升。

top-k 与 top-p:温度管形状,截断管范围。

top-k 的做法最直接:只保留概率最高的 k 个 token,其余概率置零再归一化,k=1 时退化为贪心。它的短板是不看分布形状——分布很尖时,第 2 到第 k 名可能都是垃圾照样留下;分布很平时,第 k+1 名可能也很合理却被一刀切掉。top-p(核采样)补上这个缺口:按概率从高到低累加,凑够 p 就截断,候选数随分布形状自适应。上面的实测看得很清楚:同样 top-p=0.9,尖分布只留 3 个候选,平分布自动放到 5 个;top-k=3 则永远是 3 个。Hugging Face 的默认值也能看出定位:top_k 默认 50、top_p 默认 1.0、temperature 默认 1.0——框架默认几乎什么都不拦,截断是你主动给采样路线上的保险,拦的就是被温度抬起来的长尾垃圾。

不同任务怎么配。

创作类(文案、故事、头脑风暴)要发散:T 0.7~1.0 配 top-p 0.9 是常见起点,多样性交给采样,截断负责挡长尾。问答、摘要、翻译要稳:T 0~0.3 或直接贪心;怕回答死板可以给一点温度,但 top-p 别放到 0.9 以上。代码生成要准:贪心或 T 不超过 0.2,少采样——代码里一个偏门 token 就是编译错误,发散没有收益。还有两个纪律:top-k 和 top-p 一般只开一个,叠加使用出了问题说不清是谁在起作用;束搜索属于确定性路线,适合「有标准答案」的输入到输出映射任务,开放式文本里靠加大 num_beams 治不了单调,反而可能更复读。

深蓝黑背景概念图。顶部一行小字「概念图 · 解码参数」,主标题「先定确定性,再圈候选:三个参数各管一件事」。第一区是策略分叉条:左边灰色卡「确定性路线:贪心每步取 argmax,束搜索保留 B 条候选再选整体最优——稳定但易复读」,右边青色卡「采样路线:按分布随机抽,temperature / top-k / top-p 只在这条路线上生效」。第二区三张参数卡:「temperature」卡写公式 softmax(z_i ÷ T),T 小分布变尖更确定、T 大分布变平更多样,T 趋近 0 近似贪心;「top-k」卡写只留概率最高的 k 个、其余置零再归一化,k = 1 时退化为贪心,候选数是固定的;「top-p」卡写按概率从高到低累加、凑够 p 就截断,候选数随分布形状自适应,分布平时留得多、尖时留得少。第三区是任务配置三联卡:「创作:T 0.7 ~ 1.0 + top-p 0.9,要发散」「问答:T 0 ~ 0.3 或直接贪心,要稳」「代码:贪心或 T ≤ 0.2,少采样,要准」。底部总结条:温度管形状、top-k 与 top-p 管范围,两个截断一般只开一个;最底一行小字补充采样路线要防低概率垃圾 token,这就是截断存在的理由。 图 2:两条解码路线、三个参数的分工与任务配置表。

收个尾。 被问「生成结果太死或太飘怎么办」,答题链条是:先确认在不在采样路线(do_sample 开了没),再定性问题——太死就升温或放宽 top-p,太飘就降温或收紧 top-p,复读先查重复惩罚类参数和训练数据,而不是一味升温。你自己调生成参数时踩过最久的坑是哪个?温度的手感,还是截断的候选范围?欢迎聊聊。