Temperature等核心参数详解
> **本文适合谁**
Temperature 等核心参数详解:调参不再靠感觉
本文适合谁
已经能调用 LLM API 的开发者,想搞清楚 temperature、top_p、max_tokens 这些参数到底在做什么,调参不再靠感觉猜测。
LLM 的 API 不只是传一个 Prompt 进去那么简单。Temperature、Top-p、Max tokens……这些参数直接控制模型的行为。同一段 Prompt,不同的参数设置,可能产生截然不同的输出——既可能是结构清晰的 JSON,也可能带着多余的解释文字。
这篇讲清楚每个参数的原理,让你调参有依据,而不是靠猜。
1.1 为什么需要这些参数:先理解 LLM 是如何生成文字的
1.1.1 直觉理解:模型在做概率采样
LLM 每次生成下一个 token,实际上是在做概率采样:给所有候选 token 打分,然后按某种规则挑选一个。
假设模型预测"今天天气很"之后的下一个词:
- "好":概率 60%
- "热":概率 20%
- "差":概率 15%
- 其他词:合计 5%
如果每次都选概率最高的("好"),输出永远一样,可重复,但缺乏变化。
如果按概率随机采样,"好"会被选 60% 的时候,"热"会被选 20% 的时候,输出有多样性。
Temperature、Top-p、Top-k 这三个参数,控制的就是"怎么做这个采样"。
1.2 Temperature:随机性的总开关
1.2.1 原理:Temperature 在 Softmax 里做什么
LLM 为每个候选 token 生成一个"原始分数"(logits)。这些分数通过 Softmax 函数转换成概率分布。
Temperature 参数改变的是在 Softmax 之前对 logits 的处理:把所有 logits 除以 Temperature(T),然后再进行 Softmax 计算。
- T 很小(趋近于 0):logits 被放大,Softmax 后概率分布极度集中——最高分 token 几乎得到全部概率。结果:几乎完全确定性,每次输出相同。
- T = 1:不改变 logits,保持原始概率分布。
- T 很大:所有 logits 被缩小到接近 0,Softmax 后概率分布趋于均匀。结果:高度随机,生成结果不可预期。
1.2.2 实际含义
Temperature = 0:每次都选概率最高的那个 token。输出确定,可复现,不存在随机性。同一个 Prompt 跑一百次,结果一模一样。
Temperature = 0.1 到 0.3:轻微随机,主要还是选高概率 token,但偶尔会有微小变化。适合代码生成、数据提取等需要准确性的场景。
Temperature = 0.7 到 1.0:按照接近原始概率分布随机采样。输出有自然的多样性,但不会过于离谱。适合文章写作、助手对话。
Temperature > 1.0:概率分布被"拉平",低概率的 token 也有机会被选中,输出更随机、更有创意,但也更容易跑偏。
Temperature = 2 或更高:基本不会用在生产环境里,输出质量太差,词汇选择会变得莫名其妙。
1.2.3 好的 Prompt vs 差的 Prompt(Temperature 场景)
差的做法:用同一个 Temperature 应对所有场景。
好的做法:根据任务性质选择合适的 Temperature:
| 场景 | 推荐 Temperature | 原因 |
|---|---|---|
| JSON 数据提取 | 0 | 要求确定性,格式必须一致 |
| 代码生成 | 0.1 - 0.2 | 准确性优先,轻微变化可接受 |
| 客服标准回复 | 0.2 - 0.3 | 稳定但不机械 |
| 文章写作、摘要 | 0.7 | 需要流畅自然的表达 |
| 头脑风暴 | 1.0 | 多样性优先 |
| 创意写作 | 1.0 - 1.3 | 追求新颖,可以接受偶尔跑偏 |
Temperature 参数对输出概率分布的影响——低值使分布集中,高值使分布趋于均匀
1.3 Top-p:核采样,另一种控制随机性的方式
1.3.1 为什么需要 Top-p:固定数量候选的问题
Top-k 方案(只从概率最高的 k 个 token 中采样)有一个根本问题:固定数量的候选不能适应变化的概率分布。
想象两种极端情况:
- 某些时刻,下一个词几乎确定(如"北京是中国的"后面,"首都"的概率可能高达 90%+),这时设 k=50 是强行引入 49 个几乎不可能的词。
- 某些时刻,下一个词有很多合理选项(如"今天"后面可以接几十个有意义的词),这时 k=50 可能还不够覆盖所有合理选项。
1.3.2 Top-p 的解决思路
Top-p 不固定候选数量,而是固定累积概率。把所有 token 按概率从高到低排序,一个一个加入候选集,直到累积概率超过 p 为止。
- 概率分布集中时(下一词几乎确定),候选集自动变小
- 概率分布分散时(下一词有很多选项),候选集自动变大
Top-p = 0.9:把候选 token 按概率排序,累积概率超过 90% 之后,后面的 token 全部排除,只从前面 90% 的集合里采样。
Top-p = 1:不截断,考虑所有 token(默认值)。
Top-p = 0.1:只从概率最高的极少数 token 里选,输出非常保守。
1.3.3 实用结论:Temperature 和 Top-p 通常只调一个
同时调两个没有太大意义。OpenAI 的文档也明确建议不要同时改两个。
推荐做法:
- 只调 Temperature,Top-p 保持默认的 1
- 或者把 Temperature 固定在 1,只调 Top-p
1.4 Max Tokens:控制的是输出,不是输入
1.4.1 最容易被误解的参数
Max tokens 只控制模型输出的最大长度,不影响能发送的输入长度。
发给模型的 Prompt 可以很长(受上下文窗口限制),但模型的回复不会超过 max_tokens 规定的 token 数。
设太小会发生什么:输出被硬截断,句子可能在中间断掉,JSON 可能不完整,代码可能只有一半。模型不会告诉你它被截断了,它就是突然停了。这是很多新手遇到的"模型输出不完整"问题的根源。
设太大会有什么后果:主要是成本。API 按实际使用的 token 计费,max_tokens 只是上限,不代表每次都会用满。但设一个合理的上限可以防止模型失控地输出一大段。
不同场景的参考经验值:
- 简单问答、分类任务 → max_tokens = 200 到 500
- 代码生成(中等复杂度) → max_tokens = 1000 到 2000
- 长文章生成 → max_tokens = 2000 到 4000
- 文档摘要(长文本) → max_tokens = 500 到 1000
1.5 System Prompt vs User Prompt:各司其职
1.5.1 两者的本质区别
System Prompt(系统提示):定义模型的角色、行为规范、输出格式、限制条件。在对话开始前就设定好,通常在整个会话期间保持不变。模型被训练成对 System Prompt 有更高的遵从度。
User Prompt(用户提示):每一轮对话里用户发的具体输入。
System Prompt 是员工手册,User Prompt 是具体的工作任务。
1.5.2 为什么 System Prompt 的优先级更高
这和训练过程有关。在 RLHF 对齐训练时,大量的训练数据包含 System Prompt,模型学会了将 System Prompt 中的指令视为基本约束,User Prompt 中的请求在不违反 System Prompt 的前提下执行。
1.5.3 一个常见错误
把格式要求放在 User Prompt 里,结果模型偶尔会"忘记"。格式要求放进 System Prompt,稳定性会高很多。
import os
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com"
)
# 这段代码在做什么:
# 格式要求放在 System Prompt,确保每次调用都严格遵守 JSON 格式
messages = [
{
"role": "system",
"content": (
"你是一个信息提取助手。"
"从用户输入中提取姓名、年龄、职位,以 JSON 格式输出。"
"只输出 JSON,不要任何解释文字。"
"格式:{\"name\": \"...\", \"age\": ..., \"role\": \"...\"}"
)
},
{"role": "user", "content": "张三,28岁,在某互联网公司做后端工程师"}
]
1.6 Stop Sequences:让模型在对的地方停下来
Stop sequences(停止序列)是一个很实用但经常被忽视的参数。可以指定一个或多个字符串,当模型输出中出现这些字符串时,立即停止生成。
这在结构化输出的场景里特别有用:
# 让模型生成一段 SQL,只需要 SQL 本身,不需要后面的解释
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
stop=["```\n", "---"] # 在代码块结束符或分隔线处停止
)
Stop sequences 不会出现在最终输出里,模型在触发停止条件之前就已经停了。
1.7 n 和 Seed:批量生成和复现
1.7.1 n:同时生成多个答案
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
n=3, # 同时生成 3 个独立的回复
temperature=0.8
)
# 取出三个回复
for choice in response.choices:
print(choice.message.content)
print("---")
n > 1 的场景:
- A/B 测试:生成多个版本的文案,让人来选最好的那个
- 自洽性投票:对于推理类问题,生成 5 个答案,取出现最多次的——错误推理路径各不相同,但正确路径往往殊途同归
- 候选集生成:生成 3 个候选方案,再用规则或另一个模型打分筛选
注意:n = 3 并不是调用 API 3 次,而是一次调用里生成 3 个回复,计费按实际生成的 token 总量算。
1.7.2 Seed:让输出可复现
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
temperature=0.7,
seed=42 # 固定种子,相同输入产生高度一致的输出
)
Seed 的主要用途是调试和测试。开发阶段需要确认某个 Prompt 改动是否真的改善了输出,固定 seed、改 Prompt、对比前后差异,结果才有可比性。
注意:seed 保证的不是绝对相同,而是高度一致。模型升级后即使 seed 相同,输出也可能有细微差异。
1.8 完整代码示例
import os
from openai import OpenAI
# 这段代码在做什么:
# 演示两种典型场景的参数配置——结构化提取和创意写作
client = OpenAI(
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com"
)
def extract_info(user_input: str) -> str:
"""
结构化数据提取场景:
- temperature=0:确定性输出,每次结果一致
- max_tokens=200:JSON 不需要太长
- seed=42:方便调试时对比
"""
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{
"role": "system",
"content": (
"你是一个信息提取助手。"
"从用户输入中提取姓名、年龄、职位,以 JSON 格式输出。"
"只输出 JSON,格式:{\"name\": \"...\", \"age\": ..., \"role\": \"...\"}"
)
},
{"role": "user", "content": user_input}
],
temperature=0,
max_tokens=200,
seed=42
)
return response.choices[0].message.content
def generate_titles(topic: str) -> list[str]:
"""
创意写作场景:
- temperature=0.9:高随机性,产生多样化标题
- n=3:一次生成 3 个候选,让人来挑
- max_tokens=100:标题不需要太长
"""
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{
"role": "system",
"content": "你是一个擅长写技术博客标题的编辑。风格简洁有力,引人好奇。"
},
{"role": "user", "content": f"为主题《{topic}》生成一个吸引人的标题"}
],
temperature=0.9,
max_tokens=100,
n=3
)
return [choice.message.content for choice in response.choices]
# 使用示例
result = extract_info("王芳,32岁,在某电商公司担任产品经理")
print("提取结果:", result)
# 你应该看到:{"name": "王芳", "age": 32, "role": "产品经理"}
titles = generate_titles("Java 开发者如何学习 AI")
for i, title in enumerate(titles, 1):
print(f"候选标题 {i}:{title}")
# 你应该看到 3 个不同风格的标题,每次运行略有不同
常见错误和解决方法:
| 错误现象 | 可能原因 | 解决方法 |
|---|---|---|
| 输出在中间断掉 | max_tokens 设太小 | 增大 max_tokens |
| 每次输出格式不一样 | temperature 太高或没有格式约束 | 降低 temperature,在 System Prompt 里明确格式 |
| 输出太单调重复 | temperature = 0 且缺乏多样性需求 | 根据场景适当提高 temperature |
| 输出奇怪词汇 | temperature > 1.5 | 降低 temperature,通常 1.3 以内就够 |
1.9 不同场景的参数推荐配置
| 场景 | Temperature | Top-p | Max Tokens | 备注 |
|---|---|---|---|---|
| JSON / 结构化提取 | 0 | 1 | 500 | 必须确定性输出 |
| 代码生成 | 0.1 | 1 | 2000 | 轻微随机,避免完全死板 |
| 代码调试 / 推理 | 0 | 1 | 2000 | 用推理模型效果更好 |
| 客服标准回复 | 0.2 | 1 | 300 | 稳定但不机械 |
| 文章写作 | 0.7 | 1 | 3000 | 自然流畅 |
| 头脑风暴 / 创意 | 1.0 | 1 | 1000 | 多样性优先 |
| 摘要生成 | 0.3 | 1 | 600 | 保留关键信息 |
| 翻译 | 0.2 | 1 | 输入的 1.5 倍 | 准确优先 |
| 分类 / 打标签 | 0 | 1 | 50 | 输出固定,不需要长度 |
这张表是起点,不是定论。实际项目里以这些数值作为初始配置,再根据实际输出质量微调。
1.10 小结
| 参数 | 控制什么 | 最常用场景 |
|---|---|---|
| temperature | 输出随机性总开关,0=确定,1=自然,>1=随机 | 几乎每个场景都要调 |
| top_p | 核采样,限制候选 token 的累积概率阈值 | 通常与 temperature 二选一调 |
| max_tokens | 最大输出 token 数(只影响输出!) | 防止截断和控制成本 |
| stop | 触发停止的字符串 | 结构化输出、防止多余文字 |
| n | 同时生成多个回复 | A/B 测试、自洽采样 |
| seed | 固定随机种子,提高可复现性 | 调试和测试 |
调参的本质是在"准确性"和"多样性"之间找平衡点。Temperature 是最直接的旋钮,其他参数是辅助。搞清楚这几个参数的含义,写 Prompt 的时候会更有底气,系统出了问题也更容易定位是参数的问题还是 Prompt 的问题。