temperature=0、seed=0 也不等于完全确定, 工程实践的过程中总会有取舍, 请允许理想与现实的偏差.

大佬的镇楼图

概览

在真实工程环境下,做到如下:

  • temperature = 0
  • 随机种子(seed)固定为 0
  • 模型权重不变

LLM 输出依然不能保证严格的确定性。
原因来自多个层级:采样配置、数值误差、批量调度、MoE 架构、基础建设等。

工程实践中,更现实的目标不是“位级完全一致”,而是用参数 + 架构 + 缓存,把模型行为控制在业务可接受的稳定性范围内。

下文按“出现概率”给一个原因排行榜,再给出可操作的规避策略与代价。

参数如何影响推理

理想化推理链路:

固定权重 + 固定输入 + greedy 解码(每步选 argmax) + 完全确定的数值计算
⇒ 输出必然完全一致。

temperature 符合直觉:

  • 越高 → 分布越“平”,更多随机采样;
  • 越低 → 分布越“尖”,趋向于 greedy;
  • 理论上 temperature=0 会极端放大最大 logit,对应“总是选最大值”。

seed 作用更窄:

  • 仅控制“伪随机数生成器”的序列;
  • 只对依赖随机采样的步骤生效;
  • 对数值并行、batch 调度、非确定性 kernel 没有直接约束。

因此,温度=0 + 种子固定,最多是“更接近 deterministic”,而不是“数学意义的 deterministic”。介不就是光速与绝对零度嘛。

推理过程与参数参与情况

工程中概率排行

配置没有真正关掉随机性

高频误区:

  • 仍然在用采样:top_p < 1top_k > 1,只是温度降到 0;
  • 请求 n > 1,服务端对多条采样结果再做内部选择;
  • provider 对 temperature=0 做了特殊处理或者忽略0,比如强制改成一个很小但非 0 的温度;
  • 文档写的是 “mostly deterministic” 或 “best effort reproducibility”,并未承诺严格一致。

这些都意味着你以为关掉了随机性,实际上还在采样,而且 seed 只保证“这条采样序列可复现”,并不保证不采样。工程上遇到的多数“T=0 还在变”的案例,根因都在这一层。

参数建议
1
2
3
4
5
6
7
8
9
10
11
{
"model": "xxx",
"temperature": 0.0, // 或一个非常小的值,例如 0.1
"top_p": 1.0, // 不再做 nucleus 截断
"n": 1, // 不要生成多候选再让服务端挑
"seed": 42, // 固定为某个整数,在同一测试中保持不变
"presence_penalty": 0.0, // 禁用额外的随机去重行为
"frequency_penalty": 0.0,
"max_tokens": 256, // 评估/回归测试时也建议固定
"stop": null // 如无特殊需要,不要动态变化
}

批次产生差异

在云端 API 上,请求通常如下情况:

  • 被和其他用户的请求一起打包进不同大小的 batch;
  • 由底层推理引擎根据 batch 维度选择不同的并行 kernel 或归约策略。

这会导致:

  • 浮点加法顺序变化(不满足结合律);
  • attention / matmul / RMSNorm 的归约路径不同;
  • logits 在 1e-6 量级上产生差异;
  • 若两个 token 概率本来就非常接近,argmax 可能翻转,后续生成路径完全分叉。

《Non-Determinism of “Deterministic” LLM Settings》在理论“应当确定”的配置下反复测试,发现输出字符串的一致率明显小于100%,下游任务准确率在不同 run 间可以差十几个百分点。

对云 API 来说,这是最常见且几乎不可控的非确定性来源。用户也不可能决定好每个批次元素的归约顺序.

浮点并行本身就是非确定性的

即便你在本地单机推理,只要使用 GPU / 并行 kernel,也会碰到:

  • 并行归约(atomicAdd 等)导致累加顺序未定义;
  • cudnn/cublas/自定义 kernel 采用了不同实现路径;
  • 多线程抢占导致不同 run 间执行顺序略有差异。

数值差异微小,但 softmax + argmax 会放大这些差异;自回归生成会进一步放大第一步 argmax 的差异。

PyTorch 论坛中多次讨论:即使 model.eval(),也需要额外开启 deterministic 模式,
否则多次 inference 仍然无法 bit-wise 一致。

批次与浮点计算可以合在一起看,下图是一个简单的归因链路示意:

浮点非结合性效应

解码实现细节

不同框架/服务对于 “temperature=0” 的实现并不统一:

  • 有的分支直接走 greedy,不采样;
  • 有的把 0 改成一个很小的正数,仍然进行采样;
  • 有的在低温下仍然允许 nucleus / top-k 筛选后采样。

再叠加 tie-breaking 策略:

  • 概率相等或近似相等时,是按 token id 排序选第一个
  • 还是仍然用 RNG 做一次随机决策

这些实现级细节,很容易在“理论上相同配置”的两次调用之间,积累成肉眼可见的输出差异。

实际代码里,几个主流高 star 项目对 temperature=0 的处理就已经完全不一样:Transformers 直接视为非法值, vLLM 把它解释成“强制 greedy 并重写 top_p/top_k”, llama.cpp 则在不同版本中先后把非正温度当作 greedy 的捷径、后来又要求配合 top‑k 才能得到真正的 greedy 行为。
这本身就说明:“temperature=0”的语义强依赖具体框架实现,目前并没有统一标准。

模型架构的非确定性(MoE 路由等)

对采用 Mixture-of-Experts(MoE)的模型:

  • 每个 token 会先经过 gating 网络决定路由到哪些专家子网络;
  • 为了负载均衡,路由逻辑中可能包含截断、近似甚至随机裁剪;
  • 当不同 batch 下竞争同一专家时,调度顺序变化会改变路由结果。

模型/系统版本漂移

在云端服务里,以下情况都很常见:

  • 模型权重热更新、系统 prompt 调整;
  • 不同 region / 集群挂载了略有差异的模型快照;
  • 路由策略在多个版本间做灰度。

同一个 model name 在不同时刻/不同 region 调用,底层实际上可能已经不是同一个模型实例。
这更常见于“隔一段时间”再次调用发现结果不同,而非“连续两次立刻不同”。

输入并非真正完全一致

常见人祸因素:

  • prompt 拼装引入 time、userid、skill 等隐变量;
  • system prompt / few-shot demo 在不同调用间细微变化;
  • 不可见字符(BOM、零宽空格)或换行差异。

日志里看上去完全一样,但序列化出来并不一样。这一类问题本身不深,只说明排查非确定性前先校验输入字节级是否一致。

如何尽量确定性

下面区分两种场景:云 API 调用 / 自建推理。

云场景

  1. 参数层面尽量去随机

    • 使用:temperature 非 0 但极低(如 0.01–0.1),避免 0 被特殊处理;
    • top_p = 1、不使用 top_kn = 1
    • 查阅各家文档中关于 deterministic / best-effort 的说明,按官方建议配置参数。
  2. 利用 seed + 缓存,构造“业务上的确定性”

    • 定义一个请求签名:
      (model, temperature, top_p, system prompt, user prompt, seed, 其他参数)
    • 请求真实调用 API,确认符合后缓存完整输出
    • 后续相同签名的请求,直接返回缓存结果。
    • “相同参数 + prompt ⇒ 永远返回同一条缓存结果”。
  3. 把 LLM 当作带噪声组件,用上层逻辑兜底

    • 结构化输出(JSON、SQL、DSL):
      • LLM 生成候选 → 用 schema / parser 严格校验 → 不合格则重试/修正;
    • 分类/打标/评分任务:
      • 多次调用 + 多数票 / 平均;
    • 业务侧预期里显式允许小范围不一致,不要把一次 LLM 响应当作“权威真相”。

云场景解决策略

自建场景

  1. 禁用采样,使用 greedy / 确定性 beam
  • 在 vLLM / Transformers 中显式设置:do_sample=False
  • 不设 top_p / top_k,尽量使用纯 greedy;
  • 使用 beam search 时,禁用任何随机 beam 相关功能。
  • 代价:输出模式更单一,对需要发散思维的任务不友好, 专一模型尚可如此。
  1. 固定所有可见随机种子

    • torch.manual_seed(seed)
    • torch.cuda.manual_seed_all(seed)
    • 同时固定 Python / NumPy 等种子。
    • 只能控制“采样/随机算子”的不确定性,对浮点并行无能为力。
  2. 开启框架 deterministic 模式,禁用非确定性 CUDA 算子, 以 PyTorch 为例:

    • torch.use_deterministic_algorithms(True)
    • 关闭 torch.backends.cudnn.benchmark
    • 配置 CUBLAS_WORKSPACE_CONFIG 等环境变量。
    • 代价: 性能显著下降(吞吐、延迟都会受影响),部分高性能 kernel 不可用,只能退回更保守实现。
  3. 控制 batch 行为,必要时牺牲拼 batch

    • 不跨请求拼 batch,每个请求单独跑;
    • 或使用专门实现了 batch-invariant kernel 的推理库。
    • 代价:GPU 利用率显著下降,成本上升;在高 QPS 场景往往难以接受,只适合“离线评测 / 基准测试 / 审计”这类场景。

自建场景解决策略

总结

从研究与工程实践看:

  • 即使在 temperature=0、seed 固定的配置下,LLM 仍然表现出显著的非确定性;
  • 想在云端 API 上做到严格 deterministic,几乎不现实;
  • 在自建推理里,做到“足够接近 deterministic”往往需要明显牺牲性能与吞吐。

更合理的心智模型是:

LLM = 强大但带噪声的推理器
——在设计系统时,默认它“不完全可复现”,通过参数、缓存和上层逻辑来吸收这种噪声。

适合追求强一致性的环节(计费、风控、合规决策),应优先考虑确定性模型或规则系统;
LLM 更适合作为“辅助决策 + 文本代理”,而不是唯一的“权威判官”。

这样设计出来的系统,更符合当下大模型技术的真实边界。


文章主要借助 Perplexity 检索文献和生成配图,对其中一篇核心参考强烈推荐原文阅读 Defeating Nondeterminism in LLM Inference


本站由 钟意 使用 Stellar 1.33.1 主题创建。
又拍云 提供CDN加速/云存储服务
vercelnetlifycloudflare 提供托管服务
湘ICP备2023019799号-1
总访问