可复现性与非确定性。
把 temperature 设成 0 并不能让模型变得确定,而原因并不是几乎所有人给出的那个:你这次请求的输出,取决于同一批次里还挤进了别人的多少请求。有实验室记录到,对同一个模型发出 1000 次完全相同的贪心请求,回来了 80 种不同的补全。对智能体来说,这意味着一次出错的运行通常无法靠"再跑一遍"复现——所以可复现性只能来自你记录下来的东西,而不是你能重新执行的东西。
temperature 0 是一条采样规则,不是一份保证。
temperature 控制的是模型如何在它已经算出来的下一令牌概率中做选择。取 0 时,选择变成"永远取概率最高的令牌"——贪心解码——这确实消除了温度与采样中那种有意为之的随机性。但它消除的也就只有这些。
概率本身是 GPU 上数十亿次浮点运算的产物,而这些运算并不保证每次跑出来都逐比特一致。当两个候选令牌分别停在 0.4013 与 0.4011 时,末几位的差异就足以翻转谁是"最高"。贪心解码接着会把这一次翻转放大成一整句完全不同的话,因为后面每一个令牌都以那个变了的令牌为条件。
随机种子解决不了这件事。seed 参数固定的是采样时那一次伪随机抽取;它固定不了产生那个待采样分布的算术过程。提供随机种子的厂商通常把它标注为"尽力而为",原因正在于此。
根源是批处理,不是"GPU 天生随机"。
常见的那套解释——浮点加法不满足结合律,GPU 线程完成顺序不确定,于是求和结果略有出入——只对了一半。现代推理服务器跑的那些算子,在喂入相同输入时大多是可重复的。真正的不稳定来自一个不那么显眼的地方。
推理服务器会把你的请求和同一时间窗内到达的其他请求打成一批,而许多算子会依据批大小改变自己内部的归约策略。矩阵乘法、RMSNorm 和注意力,在批大小为 4 时的任务切分方式与批大小为 64 时并不相同。切分不同,求和顺序不同,末几位就不同——而且是对你的令牌不同,尽管你的输入一个字都没改。变量是服务器负载,那是其他客户的属性,并且完全不对你暴露。
- 修法是存在且清楚的:批不变算子,在任何批大小下都采用同一套归约策略。用上它,成千上万次重复的贪心运行会返回逐比特一致的文本。
- 它不是免费的。已公开的实现比批自适应版本大约慢 1.6–2 倍,尽管后续工作已把这个开销压下去不少。
- 托管 API 不提供这个选项。你买的是吞吐,而吞吐恰恰就是确定性要付的代价——所以除非你自己跑推理服务并有意打开了它,否则请默认非确定。
在这之上还叠着两个来源,从外面看两者一模一样:厂商悄悄把一个浮动模型别名指向了新的检查点;以及你的请求落在了同一集群中不同世代的硬件或算子上。前者见灰度发布、版本化与固定——那是你真正能掌控的那一个。
它会打坏什么,按代价从大到小排。
一次略有出入的聊天补全无伤大雅。智能体循环才是微小分歧变成结构性问题的地方,因为每一步都是下一步的条件:
- 你没法靠重跑来调试。报告说智能体删错了分支;你用同样的输入跑一遍,它表现完美。你的复现方式没问题——你想复现的那次运行已经不存在了。
- 什么都没改,评估分数也会晃。同一天、同一个已固定的模型、同一套题,重打一次分就是另一个数。如果你的发布闸门是"准确率不得下降",你就必须先知道这个晃动有多大才读得懂回归——这正是评估要跑多次而非只跑一遍的原因。
- 缓存不再命中。任何以生成字符串为键的缓存——一份计划、一个归一化后的查询、一段摘要——每次都会拿到新的键。这与提示词缓存是两回事,后者以你的输入前缀为键,不受影响。
- 轨迹分岔得很快。第 2 步一个不同的工具参数,就会把这次运行带上原来根本没走过的路。到第 10 步,两次运行已几乎毫无共同之处,所以连局部重放都告诉不了你多少东西。
让可复现性来自记录,而不是重新执行。
有产出的做法,是别再逼模型重复自己,转而让这次运行在真正要紧的意义上可复现:事后能被完整重建。这是一个日志决策,而且很便宜。
- 记录真正发出去的请求,而不是模板。完整渲染后的提示词原文、发出去的工具 schema、解析后的模型快照,以及每一次工具调用连同它的参数和返回。这就是智能体可观测性的实质,也是唯一能在非确定性中幸存下来的产物。
- 让工具对着已记录的结果重放。把工具响应钉死成第一次返回的内容再跑一遍智能体,就把模型的波动与环境的波动隔离开了,也把一份飘忽的缺陷报告变成了一个固定的测试用例。
- 去测量方差,而不是假装它不存在。把评估集跑五遍,看散布有多大。一套运行间散布有两个点的题,检测不出一个点的回归——知道这一点,比任何单次分数都值钱。
- 能钉的都钉住。带日期的模型快照、版本化的提示词、版本化的工具 schema。这些都换不来逐比特一致的输出——它们换来的是:当行为变了,你说得出到底是什么变了。
就当每一次运行都是独一无二的来设计:每次调用都把渲染后的提示词记下来;凡是小于你实测的运行间散布的评估差异,一律当噪声;永远不要提交一个断言精确生成字符串的测试。如果你确实需要逐比特一致的输出——on-policy 强化学习是那个诚实的场景——你需要自己的推理栈加批不变算子,并且要在承诺之前就把那份变慢算进预算。
延伸阅读:温度与采样看你确实能控制的那部分随机性,预填充、解码与 KV 缓存看服务器拿你的批次到底在做什么,智能体评估看如何为永不重复的轨迹打分。