探讨 GPT-4o 生成随机性的来源,从已知且可控的因素到不透明且不可控的机制。
毫无疑问,GPT-4o 的输出存在随机性。毕竟,模型在选择每个 token 时是从概率分布中进行采样的。但我一直没有意识到,这些概率本身并不是完全确定性的。即使在使用相同的提示词、固定的随机种子(seed)以及将温度(temperature)设为 0 的情况下,GPT-4o 仍然会引入微妙却令人困惑的随机性。
这不是一个可以修复的问题,甚至可能不是 OpenAI 能够修复的问题——这篇文章的核心结论就是如此。在本文中,我们将深入分析 GPT-4o 输出中的各种随机性来源,并详细拆解其采样过程。我们将明确指出这个问题——概率值会变化,并对 OpenAI 官方有关确定性的指导进行批判性审视。
确定性(determinism)意味着相同的输入始终会产生相同的输出,就像数学函数一样。虽然大型语言模型(LLM)的创造性往往是有价值的,但在许多场景下,确定性至关重要:
如果没有确定性,我们很难判断不同的输出是由于我们调整了提示词,还是仅仅因为模型的随机性导致的。
为了简化分析,我们让 GPT-4o 进行一个最基本的二元选择任务:掷硬币。我们使用 OpenAI API 向最新版本的 GPT-4o(gpt-4o-2024-08-06)发送以下请求:
import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
prompt = 'Flip a coin. Return Heads or Tails only.'
response = client.chat.completions.create(
model='gpt-4o-2024-08-06',
messages=[{'role': 'user', 'content': prompt}],
)
print(response.choices[0].message.content)
执行代码后,我得到了 "Heads"。你可能会得到 "Tails",或者如果运气够好,可能会看到一些更奇怪的结果。
然而,当我们多次运行这个代码时,我们发现了一个有趣的现象——GPT-4o 的“硬币”并不公平。例如,在运行 10 次后,我得到了以下结果:
Heads
Heads
Heads
Heads
Heads
Heads
Tails
Heads
Heads
Heads
很明显,GPT-4o 更倾向于返回 "Heads",但这并不奇怪。研究表明,人类在想象掷硬币时,80% 的时候会选择 "Heads"(Bar-Hillel, Peer, & Acquisti, 2014)。GPT-4o 可能从人类数据中学到了这种倾向。
但这只是表象,真正的问题在于:GPT-4o 的硬币抛掷结果并不是完全确定的,即使在所有可控参数固定的情况下。
为了进一步分析,我们不直接生成完整的响应,而是请求 GPT-4o 返回 token 级别的 log 概率(logprobs),以便精确计算 "Heads" 与 "Tails" 的概率分布。
import os
import math
from openai import OpenAI
from tabulate import tabulate
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
prompt = 'Flip a coin. Return Heads or Tails only.'
response = client.chat.completions.create(
model='gpt-4o-2024-08-06',
max_tokens=1,
logprobs=True,
top_logprobs=20,
messages=[{'role': 'user', 'content': prompt}],
)
logprobs_list = response.choices[0].logprobs.content[0].top_logprobs
data = []
total_pct = 0.0
for logprob_entry in logprobs_list:
token = logprob_entry.token
logprob = logprob_entry.logprob
pct = math.exp(logprob) * 100 # 转换为百分比
total_pct += pct
data.append([token, logprob, pct])
print(
tabulate(
data,
headers=["Token", "Log Probability", "Percentage (%)"],
tablefmt="github",
floatfmt=("s", ".10f", ".10f")
)
)
print(f"\nTotal probabilities: {total_pct:.6f}%")
运行后,可能会得到如下结果:
Token | Log Probability | Percentage (%) |
---|---|---|
Heads | -0.0380541235 | 96.27% |
T | -3.2880542278 | 3.73% |
惊人的是,"Heads" 的概率高达 96.27%,远远高于 "Tails"。
更重要的是,如果你多次运行这段代码,你会发现每次的概率 都不完全相同。即使所有参数(seed、temperature、system_fingerprint)都保持一致,GPT-4o 仍然会返回不同的 log 概率值。这意味着——GPT-4o 的 log 概率本身是不稳定的。
OpenAI 在其文档中提到:
“如果 seed、请求参数和 system_fingerprint 保持一致,那么模型输出在大多数情况下将是相同的。但由于模型的固有非确定性,仍然存在输出不同的可能性。”
我们尝试了一切可能的方法来锁定确定性,包括:
然而,这些措施 都无法消除 GPT-4o 的随机性。
GPT-4o 被广泛认为采用了 Mixture-of-Experts(MoE) 架构,即每个请求被分配给多个“专家”模型之一进行处理。Google DeepMind 的研究表明,这种架构可能会引入不可避免的随机性:
“当 token 被分配到不同的专家时,它们会相互竞争可用的计算资源,从而导致非确定性。”
换句话说,GPT-4o 的不同请求可能会被路由到不同的专家,而这些专家可能会返回不同的 log 概率值。这种分配是由 OpenAI 的基础设施动态决定的,因此是不可控的。
GPT-4o 的非确定性 是其架构本身导致的,无法通过参数调整消除。这对研究者、开发者和提示工程师来说可能是一个重大挑战:
如果 OpenAI 能提供更透明的 API(例如,指示请求被分配到哪个专家),或许我们可以更好地理解这种随机性。但在目前的情况下,我们只能接受这个事实——GPT-4o 的随机性是不可避免的。