LLM输出随机性解析:温度、种子与垂直AI稳定性实践
在垂直业务里接 LLM 时,我们常常默认模型输出是稳定的:同样的 prompt,今天跑和明天跑应该一样,最多只是语气略有变化。但实际上,绝大多数主流 LLM 在生成文本时并不是在“查答案”,而是在“掷骰子”——从一个概率分布里做随机采样。本文会用工程视角拆解 LLM 的随机性来源,用可复现代码演示 temperature、top_p、seed 对输出的影响,并给出垂直 AI 应用中应对随机性的最佳实践。无论是正在做 RAG 检索问答、Agent 编排,还是在做 LLM 评测,都应该先把“模型输出会变”这条底层逻辑想清楚。
1. LLM 为什么会“掷骰子”
1.1 从“补全文本”到“概率采样”
LLM(Large Language Model)的任务本质上不是回答正确,而是根据上下文预测下一个 token 的概率分布。以“中国的首都是”为例,模型不会直接输出“北京”,而是计算词表中每个 token 的条件概率:
- P(“北京”) = 0.87
- P(“上海”) = 0.05
- P(“北”) = 0.02
- ……
如果使用贪心解码(greedy decoding),每次只取概率最高的 token,那么输出是确定的。但 ChatGPT、Claude、文心一言等大多数对话产品在默认情况下并不会使用贪心解码,而是从这个概率分布里按权重随机抽样。采样时,概率越高的 token 被选中的可能性越大,但概率低的 token 也有机会被选中。
这就是“掷骰子”的直观含义:模型的输出不是一个定值,而是一个随机变量。
1.2 为什么概率分布是随机的真正根源
很多初学者以为是 API 服务端做了随机化处理,其实根源在于:
- 训练完成后,模型的参数是固定的,输入固定,模型最后一层给出的 logits 是确定的。
- 但生成阶段往往启用采样模式,从 softmax 转换出的概率分布中随机抽取 token。
- 即使不采样,在不同硬件、不同批处理大小、不同浮点精度下,logits 也可能有微小差异,最终经温度缩放后影响采样结果。
所以“LLM 掷骰子”有两层含义:
- 采样进程本身引入了随机性。
- 模型推理时的数值计算存在不可避免的微小波动。
这两层各不相同,但都会影响同一个 prompt 的多次输出。
1.3 为什么垂直 AI 更怕“随机”
通用对话场景里,用户问“讲个笑话”,每次不同反而显得更有趣。但在垂直 AI 应用中,情况完全不一样:
- 医疗 AI 问诊:同样症状描述,今天建议去 A 科室,明天建议去 B 科室。
- 法律文书生成:关键条款多次生成不一致。
- 金融风控报告:同一个借贷申请,两次审核理由不同。
- 代码生成与 SQL Agent:同样的结构化查询,偶尔多一个过滤条件。
- 自动化测试脚本:同一接口用例,偶发断言失败。
当 LLM 被嵌入业务流程时,随机性会直接转化为业务不可靠、审计困难、评测不稳定。我们需要先接受一个现实:在很多场景下,你得到的结果只是“概率采样的一次实现”。
2. 环境准备与版本说明
为了把原理讲透,我们需要一个能复现的实验环境。下面配置以通用 Python 环境为例,不绑定特定云厂商。
2.1 推荐运行环境
- 操作系统:Linux / macOS / Windows WSL2 均可
- Python:3.9+ 或 3.10
- 模型访问方式:OpenAI 兼容 API 或本地部署模型
OpenAI 兼容 API 的 Python SDK 安装命令:
pip install openai python-dotenv如果想要本地跑一个小型模型,可以用 HuggingFace Transformers:
pip install transformers torch这里不写死具体版本,因为 LLM 生态更新非常快。使用时建议先锁定openai和transformers的大版本,再按项目实际安装。
2.2 示例项目结构
llm-dice-demo/ ├── .env # API Key 配置 ├── requirements.txt ├── 01_probability_demo.py # 单一 prompt 多次调用 ├── 02_temperature_demo.py # 温度参数影响 ├── 03_seed_demo.py # 固定随机种子 ├── 04_self_consistency.py # 自一致性示例 └── logs/ └── llm_results.jsonl2.3 API Key 配置
在.env文件中写入:
OPENAI_API_KEY=你的密钥 OPENAI_BASE_URL=https://api.openai.com/v1然后写一个通用的加载代码:
import os from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("OPENAI_API_KEY") BASE_URL = os.getenv("OPENAI_BASE_URL")3. 核心机制拆解:温度、采样、随机种子
3.1 softmax 与 logits
在模型生成时,神经网络最后一层会为每个 token 输出一个未归一化的分数 logits。假设词表只有 4 个 token,logits 为:
import numpy as np logits = np.array([1.0, 2.0, 3.0, 4.0]) probs = np.exp(logits) / np.sum(np.exp(logits)) print(probs)输出示例:
[0.0320586 0.08714432 0.23688282 0.64391426]这个probs就是模型的概率分布。生成时模型要从中挑选一个 token。
3.2 temperature:控制分布的“锐化程度”
温度系数temperature对 logits 进行缩放:
scaled_logits = logits / temperature- temperature 越小,概率差距越大,越接近贪心。
- temperature 越大,概率分布越平缓,随机性越强。
- temperature = 0 时,概率无限集中于最大 logits,等价于贪心。
代码演示:
def softmax_with_temperature(logits, temperature): logits = np.array(logits) scaled = logits / temperature exp_logits = np.exp(scaled - np.max(scaled)) return exp_logits / np.sum(exp_logits) logits = [1.0, 2.0, 3.0, 4.0] for temp in [0.1, 0.5, 1.0, 2.0]: probs = softmax_with_temperature(logits, temp) print(f"temperature={temp}: {probs}")可以明显看到:
| temperature | 输出倾向 |
|---|---|
| 0.1 | 基本固定取最大概率 token |
| 0.5 | 高概率 token 占绝对优势 |
| 1.0 | 常规采样,低概率 token 偶尔出现 |
| 2.0 | 分布趋于均匀,输出更随机 |
3.3 top_p、top_k:截断采样空间
除了 temperature,常用采样参数还有:
top_k:只从概率最高的 K 个 token 中采样。top_p(核采样):累积概率达到 p 的最小 token 集合中采样。
工程上通常推荐temperature和top_p不要同时大幅调整,优先保持一个固定,调另一个。
3.4 随机种子(seed)有什么用?
很多推理引擎和 API 支持seed参数,用于固定采样起点。如果模型和推理引擎支持完全确定性采样,那么固定相同 seed 后,相同输入会输出相同结果。
OpenAI Chat Completions 接口示例:
from openai import OpenAI client = OpenAI() response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "请用一句话解释什么是 RAG"}], temperature=0.7, seed=42, )这里需要强调:不是所有模型服务都保证 seed 绝对可复现。有些 API 即使传了 seed,在不同节点或不同部署版本下仍可能变化。
3.5 量化精度对随机性的影响:fp16、bf16、fp32
模型推理时常见的浮点精度包括 fp32、fp16、bf16。如果使用不同精度跑同一个模型,某些 token 的 logits 可能会因为舍入误差出现细微变化。当 temperature 较高时,这种细微变化会在采样阶段被放大。这也是为什么“本地 fp16 明明设置了 seed,和 API 结果还是不一致”。
所以在做评测、回归测试时,要固定推理环境:同一版本模型权重、同一推理引擎、同一浮点精度、同一批处理设置。
4. 实战:用代码看到 LLM 的“骰子”
4.1 同一 prompt 连续调用 10 次
先写一个最基础的统计脚本,重复调用同一 prompt,观察回答是否一致。
# 01_probability_demo.py import json from openai import OpenAI client = OpenAI() prompt = "请用一句话说明为什么 LLM 具有随机性。" results = [] for i in range(10): resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.8, ) answer = resp.choices[0].message.content.strip() results.append(answer) print(f"第{i+1}次: {answer[:60]}") # 统计不相同个数 unique_results = set(results) print(f"\n10 次回答中,去重后共 {len(unique_results)} 种结果")如果 temperature=0.8,大概率你不会看到 10 次完全一致的答案。这就是采样带来的随机性。
4.2 固定 temperature=0 就一定稳定吗?
把上例中 temperature 改为 0,再次运行。你会发现多数情况下会得到相同的输出,但如果模型服务商在推理阶段仍然使用非确定性采样,或者模型有随机性,仍可能出现个别差异。
一个更可靠的做法是同时固定 seed:
# 03_seed_demo.py for i in range(5): resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "1+1=?"}], temperature=0, seed=2025, ) print(resp.choices[0].message.content)如果服务端完全兼容,输出会稳定为同一结果。但注意:temperature=0和seed并不能掩盖所有变化——比如服务端负载均衡到不同模型版本、增量部署、浮点差异,都会影响结果。
4.3 用自一致性投票提升可靠结果
自一致性(self-consistency)思路很简单:同一个问题采样多次,对结果做投票或语义聚类,选择出现次数最多的答案。这在垂直 AI 中比单次回答更可靠。
下面示例模拟多轮回答并统计答案:
# 04_self_consistency.py from collections import Counter from openai import OpenAI client = OpenAI() def ask_once(question: str, temperature: float = 0.4) -> str: resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": question}], temperature=temperature, ) return resp.choices[0].message.content.strip() question = "某商品原价 200 元,打 8 折后再减 20 元,最终价格是多少?" answers = [] for _ in range(5): ans = ask_once(question) answers.append(ans) print("回答:", ans) # 统计 counter = Counter(answers) print("\n投票结果:", counter.most_common(1)[0][0])这种方式在代码生成、选择题、事实性问答、SQL 生成等场景中很有效。但对于开放式写作类任务,自一致性意义不大,因为不存在唯一答案。
4.4 一个稳定的 prompt 实验模板
在实际项目里,建议把实验封装成函数,并记录每次采样的元信息,方便后续分析。
def call_llm(prompt, model, temperature=0.7, seed=None, max_tokens=512): params = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": temperature, "max_tokens": max_tokens, } if seed is not None: params["seed"] = seed resp = client.chat.completions.create(**params) return { "content": resp.choices[0].message.content, "model": resp.model, "seed": seed, "temperature": temperature, }记录的内容建议包含:请求时间、模型名、temperature、seed、原始提问、输出内容、耗时。
5. 垂直 AI 业务中的常见问题与排查
5.1 LLM 多次生成结果不一致,影响自动化测试
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 同一测试用例多次执行结果不同 | 启用了采样生成;模型服务多副本 | 固定 temperature=0、seed;使用断言时做语义相似度;测试隔离 |
| 本地复现和 API 结果不同 | 浮点精度、推理引擎、模型版本不一致 | 统一模型版本和推理环境;固定 bf16/fp16 |
| 生产环境偶发异常输出 | 增量发布 / 多模型版本路由 | 锁定模型版本;做模型版本灰度管理 |
| 结果在评测时好时坏 | 评测集顺序或批次变化 | 使用离线批量推理;固定随机种子;多次采样取最佳或投票 |
5.2 temperature=0 为什么还不稳定?
这是最常见的疑惑。原因可能是:
- 服务端未真正实现确定性解码,即使 temperature=0,可能仍有少量随机性。
- 多线程推理时数值累加顺序变化。
- 模型切为了同一个别名背后的多个版本。
排查步骤:
- 确认使用的模型 ID 是否带版本,还是映射 alias。
- 查看服务端是否有
seed参数,并手动传入固定值。 - 同一环境跑多次,观察是否还有差异。
- 如果仍有差异,联系模型服务商确认是否承诺确定性输出。
5.3 RAG 召回不同导致生成不同
很多垂直应用基于 RAG。用户会误以为 LLM 随机,但实际上可能是检索结果发生了变化:
- 向量库数据更新。
- 向量化模型版本变化。
- 检索 top-k 设置改变。
- 文本切分策略不同。
所以排查随机性问题时,先记录检索返回的上下文哈希,再观察生成结果。
5.4 Agent 工具调用顺序导致随机
Agent 场景中,LLM 的下一步动作选择也是采样结果。即使主模型 seed 固定,工具返回结果不同也会影响后续轨迹。更合理的设计是:对高风险动作设置校验规则,不能完全依赖 LLM 决定是否执行写操作。
6. 垂直 AI 工程实践与建议
6.1 输出结构化并做模式校验
不要直接信任 LLM 的原始文本。对于 JSON 输出,尽量使用函数调用或结构化输出;拿到后做 JSON Schema 校验。
import json from jsonschema import validate schema = { "type": "object", "properties": { "diagnosis": {"type": "string"}, "confidence": {"type": "number", "minimum": 0, "maximum": 1} }, "required": ["diagnosis", "confidence"] } def parse_llm_json(text): try: data = json.loads(text) validate(instance=data, schema=schema) return data except Exception as e: print("解析或校验失败:", e) return None你需要提前定义好每个关键节点的输出 schema,不能只靠 prompt 约定。
6.2 用多采样与投票降低随机风险
对于关键决策型任务,建议采样 3 到 5 次:
- 事实类:多数投票。
- 代码生成:选通过编译和测试的版本。
- 评估类:计算多次分数的中位数或均值。
自一致性不是银弹,但它确实能大幅降低单次采样导致的“离谱结果”。当然成本也会线性上升,需要根据业务价值评估。
6.3 构建固定环境回归集
在垂直业务中,至少要维护一套“回归用例集”:
- 每个用例包含:输入、期望行为、允许多样性范围。
- 每次模型升级或 prompt 调整后,用同一批用例跑回归。
- 回归脚本固定 temperature、seed、采样次数。
建议使用哈希记录模型输入和输出,方便定位问题。
6.4 控制 prompt 中对抗随机性的约束
下面是一些工程上有效的 prompt 设计原则:
- 明确要求只输出 JSON,不输出解释。
- 要求一步步推理,但只在内部思考,外部只给结论。
- 重要回答前增加“请基于给定知识库回答,不要猜测”。
- 对必须确定的内容,要求使用“拒绝回答”而不是瞎编。
这里有个反常识点:有时候要求 LLM“必须准确”并不能消除随机性,只能减少概率。真正可靠的是下游拦截。
6.5 日志与可观测性
每次 LLM 调用都应该记录:
- 请求 ID
- 模型名和版本
- 参数(temperature、top_p、seed)
- 输入消息哈希
- 输出内容
- 响应耗时
- 上层业务决策结果
这样当出现“上一次能通过,这次不能通过”的问题时,可以快速回溯是采样差异还是数据变化。
6.6 明确安全边界
在垂直场景中,LLM 的随机性可能带来合规风险。对于医疗、金融、司法等强监管场景,建议:
- 强制人工审核关键结论。
- 不让 LLM 直接执行高风险操作。
- 对输出做敏感内容过滤。
- 保存完整推理链路和安全审计日志。
- 对拒绝服务的场景有兜底规则。
这些都是工程上必须做的“护栏”,不是可选项。
7. 结语:接受随机性,把它当成工程变量
LLM 的随机性不是一个 bug,而是目前生成式模型的底层特性。我们真正要做的不是“消除随机性”,而是在产品架构中把这种不确定性管理起来。采样参数、seed、结构化输出、自一致性、回归测试、日志追踪,这些手段组合起来,才能让垂直 AI 应用达到可接受的稳定性。
下一次当你的 Agent 在两次运行中表现不一致,先别急着改 prompt。查一下温度参数、随机种子、模型版本、检索结果和推理环境。很多时候,问题不在 LLM“变笨了”,而是它在某一轮掷出的骰子不符合你的预期。
