大模型参数调优前提:搞懂模型如何"选词"?
在深入探讨各项参数如何调节之前,有必要先厘清一个基础概念:大语言模型每生成一个词语,本质上都是在执行一次"概率选择题"。这个过程并没有想象中那么高深莫测。

举个例子,当你输入"今天天气真",模型会为其词表中的每一个候选词计算出得分(这个原始分数称为 logits),然后通过数学转换将这些分数转化为选择概率:
词表概率分布(示例):"好"→ 0.35(35%)"不错"→ 0.20(20%)"热"→ 0.15(15%)"冷"→ 0.10(10%)"糟糕"→ 0.05(5%)...其他几千个词 → 0.15(15%)
接下来的核心环节就是采样(Sampling)——依据这个概率分布,从中抽取一个词语作为最终输出。
那么问题来了:究竟怎样采样才算合理?
- 每次都选概率最高的那个?听起来很稳妥,但过于死板,生成的内容会千篇一律、缺乏新意。
- 完全按概率随机抽取?又过于混乱,容易偏离主题,甚至产生无逻辑的内容。
- 因此,就需要 Temperature 和 Top P 这两把"标尺"来灵活调控采样过程。
一、Temperature:控制模型输出的"创意温度"
1.1 一句话理解
简而言之,Temperature(温度参数)调节的是概率分布的"形态"——是更加尖锐集中,还是更加平滑分散。
- Temperature 值偏高 → 概率分布被拉平,各候选词被选中的概率差异缩小 → 输出更随机、更具创意,但也可能偏离预期轨道。
- Temperature 值偏低 → 概率分布变得更加陡峭,高概率词的优势被进一步放大 → 输出更确定、更保守,也就是俗称的"不会整活"。
- Temperature 直接设为 0 → 等同于每次只选择概率最高的那个词,这种方法称为贪心解码(Greedy Decoding)。
1.2 数学原理(一句话即可讲透)
背后的数学计算其实简洁得出奇:
logits_after = logits / temperatureprobs = softmax(logits_after)
操作非常简单:将模型计算出的原始得分(logits)除以温度值,再进行一次 softmax 转换。温度越低,分母越小,数值被放大,概率差距随之拉大;温度越高,分母越大,数值被缩小,概率分布就变得更加均匀。
通过一个直观示例就能完全理解。下面用简易代码模拟五个候选词在不同温度下的概率分布变化:
import torchimport torch.nn.functional as Fdef apply_temperature(logits, temperature):"""logits: 模型原始输出 (vocab_size,)temperature: 温度参数"""# 核心就这一行:logits 除以 temperaturescaled_logits = logits / temperature# 然后 softmax 转成概率probs = F.softmax(scaled_logits, dim=-1)return probs# ============ 直观感受不同温度的效果 ============logits = torch.tensor([2.0, 1.0, 0.5, 0.1, -1.0])# 5个候选词for t in [0.1, 0.5, 1.0, 2.0, 5.0]:probs = apply_temperature(logits, t)print(f"T={t}: {probs.numpy().round(3)}")
输出结果:
T=0.1: [0.881 0.107 0.006 0.004 0.001] ← 几乎锁定第一个词T=0.5: [0.576 0.241 0.105 0.051 0.026] ← 倾向于第一个,但其他也有机会露头T=1.0: [0.351 0.192 0.117 0.071 0.031] ← 原始分布,这就是默认状态T=2.0: [0.227 0.185 0.149 0.114 0.077] ← 分布越来越均匀了T=5.0: [0.167 0.154 0.141 0.124 0.104] ← 几乎接近完全随机的均匀分布
1.3 用一个比喻来理解
我们可以把模型选词想象成选秀节目中评委的打分机制:
| Temperature | 比喻 |
|---|---|
| T → 0 | 评委只看最高分,其他人一律无视(典型的"独裁"模式) |
| T = 0.3 | 评委极度偏爱心目中的最高分选手,低分者基本没有机会 |
| T = 1.0 | 评委公正打分,按分数比例抽选,分数越高概率越大 |
| T = 3.0 | 评委彻底佛系,不管分数高低,人人都有机会 |
| T → ∞ | 完全随机抽签,分数彻底失去参考意义 |
1.4 不同应用场景的推荐参数
| 应用场景 | 推荐 Temperature | 选择理由 |
|---|---|---|
| 代码生成 | 0.0 ~ 0.2 | 代码必须精确无误,容不得"自由发挥" |
| 翻译任务 | 0.1 ~ 0.3 | 需要忠实还原原文含义,不能偏离原意 |
| 问答/摘要 | 0.3 ~ 0.7 | 准确是底线,同时可保留一定的灵活空间 |
| 创意写作 | 0.7 ~ 1.0 | 追求多样性和意外惊喜 |
| 头脑风暴 | 1.0 ~ 1.5 | 越天马行空越好,越不常规越有价值 |
| 角色扮演 | 0.8 ~ 1.2 | 既要自然流畅,又要有鲜明的性格和棱角 |
二、Top P(核采样):精准限定随机范围
2.1 为什么需要 Top P?
Temperature 虽然好用,但它是对整个概率分布"无差别处理"。这就带来了一个尴尬局面:有时模型对某个词的倾向性极强,比如"好"的概率已经高达 0.9,其他成千上万个词加起来才 0.1。此时即便你把 Temperature 调得再高,模型大概率还是只会选择那个"好",因为差距太大,难以拉平。
但在另一些场景中,概率分布较为均匀,多个候选词的概率比较接近:
"继续往下说" 的续写概率:"因为"→ 0.25"所以"→ 0.20"而且"→ 0.18"但是"→ 0.15"不过"→ 0.10...其他 → 0.12
在这种情况下,你最希望的是让模型把那几个高概率的词纳入候选,从中挑选一个,而不是偶尔蹦出一个概率只有 0.01 的"怪词"把整句话带偏。
这正是 Top P(也称核采样,Nucleus Sampling)要解决的核心问题。它好比在一场演出中,只从最热门的几个节目里选择,而不去理会那些冷门的怪选项。
2.2 核心思路
Top P 的操作其实非常朴素:将候选词按概率从高到低排序,然后从排头开始逐个累加概率,直到累计值超过某个设定的阈值 P 为止。至此,后面那些概率更低、更冷门的选项直接被淘汰,只在这个划定的"圈子"内进行采样。
Top P = 0.9 的示例:排序后的概率:"好"→ 0.35累计: 0.35✅"不错"→ 0.20累计: 0.55✅"热"→ 0.15累计: 0.70✅"冷"→ 0.10累计: 0.80✅"糟糕"→ 0.05累计: 0.85✅"还行"→ 0.06累计: 0.91❌ 超过 0.9,截断!...其他全部丢弃最终采样范围: [“好”, “不错”, “热”, “冷”, “糟糕”]
2.3 代码实现
看一遍代码会更清晰:
import torchimport torch.nn.functional as Fdef top_p_sampling(logits, top_p=0.9):"""logits: (vocab_size,) 模型原始输出top_p: 核采样的概率阈值"""# 1. softmax 得到概率probs = F.softmax(logits, dim=-1)# 2. 按概率从高到低排序sorted_probs, sorted_indices = torch.sort(probs, descending=True)# 3. 计算累计概率cumulative_probs = torch.cumsum(sorted_probs, dim=-1)# 4. 找到累计概率超过 top_p 的位置# 移除累计概率超过 top_p 的词sorted_indices_to_remove = cumulative_probs - sorted_probs > top_p# 5. 把被移除的词的概率设为 0sorted_probs[sorted_indices_to_remove] = 0.0# 6. 重新归一化sorted_probs = sorted_probs / sorted_probs.sum()# 7. 从过滤后的分布中采样next_token = torch.multinomial(sorted_probs, num_samples=1)# 8. 映射回原始词表索引actual_token = sorted_indices[next_token]return actual_token.item()# ============ 使用示例 ============logits = torch.randn(50000)# 假设词表大小 50000# Top P = 0.9:在前 90% 概率的词中采样token = top_p_sampling(logits, top_p=0.9)print(f"采样结果: {token}")
2.4 Top P 的推荐参数范围
| Top P 值 | 实际效果 | 适用场景 |
|---|---|---|
| 0.1 | 极度保守,几乎只选最高概率词 | 代码生成、数学计算 |
| 0.5 | 偏保守,在少数候选中选择 | 翻译、事实性问答 |
| 0.9 | 最常用的默认值 | 通用对话、写作 |
| 0.95 | 较开放,允许更多选择 | 创意写作、头脑风暴 |
| 1.0 | 不做任何过滤 | 等于未启用 Top P |
三、Top K:Top P 的"经典前辈"
3.1 Top K 的定义
在 Top P 出现之前,业界更常用的是 Top K。它的逻辑更加简单直接:不管概率分布如何,只保留概率最高的 K 个词,其余的全部丢弃。
Top K = 3 的示例:原始概率:"好"→ 0.35✅ 保留"不错"→ 0.20✅ 保留"热"→ 0.15✅ 保留"冷"→ 0.10❌ 丢弃"糟糕"→ 0.05❌ 丢弃...其他全部丢弃重新归一化:"好"→ 0.35/0.70 = 0.50"不错"→ 0.20/0.70 = 0.29"热"→ 0.15/0.70 = 0.21
3.2 Top K 与 Top P 的对比
| 对比维度 | Top K | Top P |
|---|---|---|
| 过滤方式 | 固定保留 K 个词,死板但有下限 | 动态保留累计概率达 P 的词,灵活适应 |
| 适应性 | ❌ 不够灵活。假设设定 K=50,但可能前2个词就占了99%的概率,剩下48个纯属摆设。 | ✅ 自动适应分布波动 |
| 极端情况 | 如果概率分布非常集中,Top K 会浪费时间在无效候选上。 | 不会浪费采样空间,精准淘汰尾部 |
| 实现难度 | 简单 | 稍复杂 |
3.3 代码实现
可以看到,Top K 的思路就是直接截断,非常干脆:
def top_k_sampling(logits, top_k=50):"""Top K 采样"""# 1. 找到概率最高的 K 个top_k_values, top_k_indices = torch.topk(logits, top_k)# 2. 过滤:把不在 Top K 中的 logits 设为 -inffiltered_logits = torch.full_like(logits, float('-inf'))filtered_logits.scatter_(0, top_k_indices, top_k_values)# 3. softmax 采样probs = F.softmax(filtered_logits, dim=-1)next_token = torch.multinomial(probs, num_samples=1)return next_token.item()
四、Repetition Penalty:有效防止内容复读
4.1 常见问题
大语言模型有一个相当普遍的"不良习性":特别喜欢重复自己已经说过的话。
用户: 讲个笑话模型: 从前有个人,从前有个人,从前有个人...
出现这种现象的原因在于,模型生成过程中,已经输出的词会反向影响后续的概率分布,使其潜意识里更倾向于"自我重复"。
4.2 Repetition Penalty 的解决方案
解决思路也非常直接:如果发现某个词已经出现过了,就人为地给它"降低分数",压低它再次被选中的概率。
def apply_repetition_penalty(logits, token_ids, penalty=1.2):"""logits: (vocab_size,) 当前步的 logitstoken_ids: 已经生成过的 token 列表penalty: 惩罚系数 (> 1.0)"""for token_id in set(token_ids):# 如果 logits > 0,除以 penalty(降低概率)# 如果 logits < 0,乘以 penalty(也降低概率)if logits[token_id] > 0:logits[token_id] = logits[token_id] / penaltyelse:logits[token_id] = logits[token_id] * penaltyreturn logits# 示例logits = torch.tensor([2.0, 1.5, 0.8, -0.5, -1.0])already_generated = [0, 2]# 词 0 和词 2 已经生成过了penalized = apply_repetition_penalty(logits.clone(), already_generated, penalty=1.2)print(f"惩罚前: {logits}")print(f"惩罚后: {penalized}")# 惩罚前: tensor([ 2.0,1.5,0.8, -0.5, -1.0])# 惩罚后: tensor([ 1.67,1.5,0.67, -0.6, -1.0])# ↑词0降了↑词2降了
五、参数组合:实战调参指南
5.1 常见参数组合推荐
理论讲完,直接上干货。针对不同任务,这里总结了一套基础配置供参考:
# ============ 不同场景的参数配置 ============configs = {"代码生成": {"temperature": 0.1,"top_p": 0.1, # 几乎只选最高概率,极保守"top_k": 1, # 等于贪心解码,不提则已,提则唯一"repetition_penalty": 1.0,# 代码不怕重复,函数名、变量名本来就常重复"max_tokens": 2048,},"中文翻译": {"temperature": 0.3,"top_p": 0.85,"top_k": 40,"repetition_penalty": 1.1,"max_tokens": 1024,},"通用对话": {"temperature": 0.7,"top_p": 0.9, # 最经典的组合,绝大多数情况下的选择"top_k": 50,"repetition_penalty": 1.1,"max_tokens": 2048,},"创意写作": {"temperature": 1.0,"top_p": 0.95,"top_k": 100,"repetition_penalty": 1.15,"max_tokens": 4096,},"头脑风暴": {"temperature": 1.2,"top_p": 0.95,"top_k": 100,"repetition_penalty": 1.2, # 创意场景最怕复读,重复的想法可太无聊了"max_tokens": 4096,},}
5.2 调参的黄金法则
在实际调试中,可以记住下面这棵简易的决策树,遇到问题对号入座即可:
┌──────────────────────────────────────────────────┐│ 调参决策树│├──────────────────────────────────────────────────┤│││输出太无聊/太重复?││├── 是 → 提高 temperature (0.7 → 1.0) │││提高 top_p (0.9 → 0.95)│││││输出太随机/胡说八道?││├── 是 → 降低 temperature (1.0 → 0.5) │││降低 top_p (0.95 → 0.85) │││││输出一直复读? ││├── 是 → 提高 repetition_penalty (1.1 → 1.2) │││降低 temperature │││││输出太短就停了?││├── 是 → 检查 max_tokens 是否太小│││检查 stop_sequences 设置│││││想要每次回答一致?││└── 是 → temperature = 0(贪心解码)│││└──────────────────────────────────────────────────┘
5.3 Temperature 和 Top P 能否同时使用?
当然可以,而且在实际应用中,强烈推荐两者搭配使用。它们的作用方式是正交的,各司其职,互不冲突:
- Temperature:管控整体概率分布的"松紧度"或"锐利度",属于宏观层面的调整。
- Top P:管控在调整后的分布中,究竟允许哪些词参与最后的抽签,属于微观层面划定范围。
两者可以独立调节。一个完整的采样流程在代码中大致如下:
# 完整的采样流程def sample_with_params(logits, temperature=0.7, top_p=0.9, top_k=50):"""完整采样流程:Temperature → Top K → Top P → 采样"""# Step 1: 应用 Temperaturelogits = logits / temperature# Step 2: 应用 Top K 过滤if top_k > 0:top_k_values, top_k_indices = torch.topk(logits, min(top_k, logits.size(-1)))filtered_logits = torch.full_like(logits, float('-inf'))filtered_logits.scatter_(-1, top_k_indices, top_k_values)logits = filtered_logits# Step 3: 应用 Top P(核采样)if top_p < 1.0:sorted_logits, sorted_indices = torch.sort(logits, descending=True)cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)sorted_indices_to_remove = cumulative_probs - F.softmax(sorted_logits, dim=-1) > top_psorted_logits[sorted_indices_to_remove] = float('-inf')logits = torch.scatter(logits, -1, sorted_indices, sorted_logits)# Step 4: 采样probs = F.softmax(logits, dim=-1)next_token = torch.multinomial(probs, num_samples=1)return next_token
六、采样策略全景概览
除了上文重点介绍的几个方法,行业内还有一些进阶策略,这里快速列一个清单,作为知识储备:
| 采样策略 | 基本原理 | 优缺点分析 |
|---|---|---|
| Greedy | 每步只选最高概率词,简单直接 | ✅ 确定性强,❌ 容易复读、文本不够自然 |
| Beam Search | 每步保留 N 条最优路径,兼顾多步决策 | ✅ 能寻找全局较优解,❌ 生成文本往往不自然,多样性不足 |
| Top K | 保留前 K 个候选词,简单粗暴 | ✅ 实现简单,❌ 不自适应,概率分布不均匀时效果打折扣 |
| Top P (Nucleus) | 保留累计概率达 P 的词,灵活适应 | ✅ 自适应、效果好,❌ 实现稍复杂 |
| Min P | 过滤概率低于最高概率×P 的词 | ✅ 提供更精细的自适应过滤机制 |
| Typical Sampling | 过滤概率远离"典型"概率值的词 | ✅ 在随机性和生成质量之间寻找平衡 |
写在最后
Temperature 和 Top P 虽然只是两三个看似不起眼的参数,但它们实实在在地塑造着模型输出的"个性特征":
- Temperature 很低 + Top P 保守 → 像一位严谨得有些刻板的工程师,严谨有余而灵性不足。
- Temperature 中等 + Top P 适中 → 像一个正常的对话伙伴,能够好好聊天,不会出幺蛾子。
- Temperature 很高 + Top P 开放 → 像一个天马行空的艺术家,时不时给你带来意想不到的惊喜。
掌握了这些参数背后的逻辑,你就能在不同的使用场景中,像调音师一样精准地操控模型输出,让语言模型说出你最想听的内容。
调参这件事,本质不在于"试数字",而在于真正理解"每个参数到底在控制什么"。希望这篇文章能帮你建立起这种直观的认知。
