前言:你有没有遇到过这种情况------同一个问题问 ChatGPT 三次,得到三个完全不同的回答?或者想让模型"严谨一点"却不知道怎么调?其实背后就是 Temperature 和 Top P 在捣鬼。今天咱们就把这两个参数拆得透透的,顺便把 Top K、Repetition Penalty 这些"配角"也一起讲了,保证你调参不再靠玄学
先搞懂一个前提:模型是怎么"选词"的?
在聊参数之前,必须先理解一件事:模型每一步输出,本质上都是在做选择。
当你输入"今天天气真",模型会对词表里的每个词打一个分数(logits),然后转成概率:
erlang
词表概率分布(示例):
"好" → 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)
1.2 数学原理(很简单)
Temperature 的计算公式:
ini
logits_after = logits / temperature
probs = softmax(logits_after)
就这么简单------把 logits 除以 temperature,再做 softmax。
python
import torch
import torch.nn.functional as F
def apply_temperature(logits, temperature):
"""
logits: 模型原始输出 (vocab_size,)
temperature: 温度参数
"""
# 核心:logits 除以 temperature
scaled_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)}")
输出结果:
ini
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] ← 接近均匀分布
看到规律了吗? Temperature 越小,概率越集中在高分的词上;Temperature 越大,概率分布越"平",低分词也有机会被选中。
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 | 需要自然但有个性 |
踩坑经验 :Temperature 绝对不能设成负数!我见过有人把 temperature 设成 -1,然后模型直接报错。另外,temperature 太高(比如 > 2)会导致输出完全不可控,出现大量乱码或无意义内容。
二、Top P(核采样):精准控制随机范围
2.1 为什么需要 Top P?
Temperature 虽然能控制整体随机性,但有个问题:它对概率分布"一刀切"。
假设模型输出"好"的概率是 0.9,其他所有词加起来才 0.1。这时候即使 Temperature 设到 1.0,模型也几乎只会选"好"------因为概率差距太大了。
但有些场景下,概率分布比较均匀:
arduino
"继续往下说" 的续写概率:
"因为" → 0.25
"所以" → 0.20
"而且" → 0.18
"但是" → 0.15
"不过" → 0.10
...其他 → 0.12
这时候你希望模型在前几个高概率词里选,而不是偶尔蹦出一个概率只有 0.01 的词。
Top P(也叫核采样 Nucleus Sampling) 就是干这个的。
2.2 核心思想
把概率从高到低排序,累加到超过阈值 P 就停下来,只在这个范围内采样。
css
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 代码实现
python
import torch
import torch.nn.functional as F
def 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. 把被移除的词的概率设为 0
sorted_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 K 比 Top P 更简单粗暴:只保留概率最高的 K 个词,其余全部丢弃。
makefile
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 vs Top P
| 维度 | Top K | Top P |
|---|---|---|
| 过滤方式 | 固定保留 K 个词 | 动态保留累计概率达 P 的词 |
| 适应性 | ❌ 不适应概率分布变化 | ✅ 自动适应 |
| 极端情况 | K=50,但可能前 2 个词就占了 99% | 不会浪费采样空间 |
| 实现难度 | 简单 | 稍复杂 |
为什么 Top P 更好? 举个极端例子:
模型非常确定下一个词是"好"(概率 0.99),其他词概率都很小。
- Top K=50:会保留 49 个几乎不可能的词,浪费计算
- Top P=0.9:只保留"好"(0.99 > 0.9),干净利落
反过来,如果概率分布很均匀:
- Top K=5:可能只覆盖了 40% 的概率空间,丢掉了大量合理选项
- Top P=0.9:自动保留足够多的候选词
所以现在主流做法是 Top P 为主,Top K 为辅(或者干脆不用 Top K)。
3.3 代码实现
python
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 设为 -inf
filtered_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 问题来了
大模型有个臭毛病:喜欢复读。
makefile
用户: 讲个笑话
模型: 从前有个人,从前有个人,从前有个人...
这是因为模型在生成过程中,已经生成的词会影响后续的概率分布,导致模型倾向于"重复自己说过的话"。
4.2 Repetition Penalty 怎么解决?
核心思想:如果某个词已经出现过了,就人为降低它的概率。
python
def apply_repetition_penalty(logits, token_ids, penalty=1.2):
"""
logits: (vocab_size,) 当前步的 logits
token_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] / penalty
else:
logits[token_id] = logits[token_id] * penalty
return 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降了
penalty 推荐值 :通常设为 1.1 ~ 1.3。太小没效果,太大会让模型说话不自然(刻意避开常用词)。
五、参数组合:实战调参指南
5.1 常见组合推荐
python
# ============ 不同场景的参数配置 ============
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 调参的黄金法则
scss
┌──────────────────────────────────────────────────┐
│ 调参决策树 │
├──────────────────────────────────────────────────┤
│ │
│ 输出太无聊/太重复? │
│ ├── 是 → 提高 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:在调整后的分布上,截断低概率的尾部
两者是正交的,可以独立调节:
python
# 完整的采样流程
def sample_with_params(logits, temperature=0.7, top_p=0.9, top_k=50):
"""
完整采样流程:Temperature → Top K → Top P → 采样
"""
# Step 1: 应用 Temperature
logits = 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_p
sorted_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 的组合。Beam Search 主要用于翻译和摘要任务。Top K 一般作为 Top P 的补充(先 Top K 粗筛,再 Top P 精筛)。
写在最后
Temperature 和 Top P 看起来只是两个数字,但它们直接影响模型的"性格":
- Temperature 低 + Top P 低 → 严谨的工程师
- Temperature 中 + Top P 中 → 正常的对话伙伴
- Temperature 高 + Top P 高 → 天马行空的艺术家
理解了这些参数,你就能根据不同场景精准调参,让模型输出最符合你期望的内容。
调参的本质不是"试数字",而是理解"每个参数在控制什么"。 希望这篇文章帮你建立起了这个直觉
**觉得有帮助?点赞收藏,评论区聊聊你平时常用的参数配置 **