确定性种子:为什么供应商将其视为“提示”而非“契约”
CI 测试只有一个断言:相同的模型、相同的温度、相同的提示词、相同的种子(seed)、相同的输出字符串。它在每个开发者的笔记本电脑上都通过了,在前一百次 CI 运行中也通过了,但在三周内每五十次运行就会出现一次随机失败(flake),直到最后有人承认这种模式是真实存在的。第一个假设是显而易见的——测试工具中某处存在非确定性依赖——三天的调查却一无所获。实际原因隐藏在供应商 API 参考文档的一个脚注中:“seed 提供尽力而为的确定性。”团队读到了参数名称,并将其视为一种契约。而供应商记录的只是一个提示。
这是托管推理的一种特定失败模式,它困扰着那些围绕单一心智模型设计测试基础设施的团队:模型是其输入的纯函数,而 seed 是使函数具有可重现性的关键。在生产环境中,这个模型的这两个部分都是错误的。API 表面与底层物理原理之间的差距如此之大,以至于团队在供应商明确否认的假设之上,构建了整个评估和回归测试栈。
采样确定性并非执行确定性
在每个公开了该参数的 LLM API 中,seed 参数控制着从模型输出分布中选择 token 的随机数生成器。将 seed 设置为一个固定的整数,在给定相同的概率分布时,采样器将始终选择相同的 token。这是一个有意义的保证,但它只是对技术栈中一个特定层级的保证:采样器。
采样器之下的一层是产生该分布的前向传播(forward pass)。那一层根本没有进行采样——它是矩阵乘法、注意力机制、归一化,有时还有混合专家(MoE)路由,在与其他请求共享的 GPU 上执行。seed 对这些完全没有影响。你采样器所采样的分布取决于内核实现、请求到达时的批次组成(batch composition)、请求被路由到的分片(shard),以及在混合专家模型中,取决于与你的请求碰巧被分到同一批次的其他请求所决定的路由决策。
换句话说,在给定分布的情况下,seed 使采样器具有确定性。而分布本身是你无法控制的变量的函数,且你的供应商并未公开这些变量。两个具有相同 seed、相同提示词、相同参数、相同模型版本的请求,可能会因为它们进入了不同的批次而产生不同的输出。
Batch Size 是隐藏的输入
关于这一点,最清晰的已发表分析源于一项将推理时非确定性的根本原因追溯到三个操作中批次不变性(batch-invariance)失败的研究:归一化、矩阵乘法和注意力机制。标准的 GPU 内核实现并不是批次不变的——token i 的数值结果取决于有多少其他 token 与它同时被处理,因为底层加法运算的约简顺序(reduction order)会随着批次形状(batch shape)而改变。浮点运算不符合结合律,因此不同的约简顺序会产生数值上不同的结果,这种差异会级联影响到下游的所有层。
对于典型的托管端点,你的请求所在的批次取决于你的数据包到达网关时服务器的繁忙程度、队列中的其他请求以及调度程序选择如何对它们进行分组。从用户的角度来看,批次大小实际上是一个由其他流量驱动的随机变量。你的请求正与陌生人的请求被分到同一批次,而前向传播的数值结果取决于这些陌生人是谁。
修复这个问题并非易事。已发表的参考实现构建了这三个操作的批次不变版本,并演示了 1,000 个相同的输入产生 1,000 个位完全相同(bit-identical)的输出。其成本非常高——在初始版本中比标准实现慢约 60%——而且工程量巨大,以至于没有任何主要的商业供应商将其引入通用端点。生产环境推理的默认假设是吞吐量比可重现性更重要,而可重现性的成本是大多数客户尚未要求的权衡。
混合专家模型增加了另一层复杂性
对于混合专家模型,批次依赖问题更加复杂。MoE 路由决定每个 token 被发送到哪些专家,并且路由在容量限制下运行——每个专家在每个批次中只能处理有限数量的 token。当批次满载时,token 会竞争专家插槽,而关于哪些 token 能获得其首选专家的决策,取决于批次中存在的其他 token。
实际后果是,token 在网络中的路径不仅仅是该 token 自身的函数,而是该 token 及其共同分批的 token 群体的函数。相同的输入提示词,在使用相同的 seed 和参数发送两次时,可能会在两次运行中路由经过不同的专家,因为共同分批的群体不同。这一分析指出 MoE 是早期大模型非确定性的主要来源,且该分析仍然适用于当前一代的 MoE 部署。
对于稠密(dense)模型,批次大小效应产生的方差较小,表现为偶尔的 token 级发散。对于 MoE 模型,这可能会产生更大的影响,因为专家路由是一个离散的决策,对单个 token 的单次路由选择不同,就可能改变整个下游轨迹。
What "system_fingerprint" Actually Tells You
OpenAI 的响应负载中包含一个 system_fingerprint 字段,其文档说明的目的是让你在后端发生可能影响可复现性的更改时能够察觉。文档中对这一契约的描述非常精确:如果两次运行之间的指纹(fingerprint)、种子(seed)和请求参数完全匹配,则输出“大多是相同的(mostly be identical)”。在这个句子中,“大多(mostly)”这个词承载了很重的分量。指纹并不保证具有相同指纹的两个请求一定会产生相同的输出。它保证的是,如果指纹不同,你就应该预料到会出现偏移(drift)。
这是使用该字段的正确方式,但大多数在托管推理上构建回归测试的 团队并没有这样使用它。常见的模式是对精确的输出字符串进行断言,并将指纹变化视为稍后调查的带外事件。真正符合 API 契约的模式是在每次响应时记录指纹,将任何指纹变化视为预期发生偏移的原因,并设计断言以容忍即使在指纹匹配时仍存在的细微差异。
Anthropic 的 Claude API 在其公共接口中根本没有暴露 seed 参数,且其文档也未声明运行间的可复现性。那些针对 Claude 构建回归测试、将 temperature 固定为 0 并对字节级一致的输出字符串进行断言的团队,实际上是在文档约定的范围之外运行。测试大多会通过,但偶尔会随机失败(flake),而这些随机失败并不是测试基础设施的 Bug —— 它们正是供应商在按其文档描述的那样运作。
The Test Suite That Measures the Load Balancer
对托管推理进行字节级精确断言的更深层次问题在于,测试衡量的是测试作者原本并不打算衡量的东西。测试本应衡量模型的行为,而它实际衡量的是模型行为加上供应商的路由拓扑、当前的批次构成、当前的内核构建,以及供应商自测试编写以来调整过的任何后端配置。
当测试通过时,说明断言对所有这些变量都具有鲁棒性。而当测试出现随机失败时,你并不知道是哪个变量发生了变化。供应商不会告诉你他们何时轮转了分片、何时发布了新的内核构建、何时更改了批次策略,或是在数据中心之间转移了流量。你在 CI 中看到了随机失败,却没有任何关于究竟是什么发生了变动的信号 。
这使得在情况糟糕的时候,这种测试比没有测试更糟糕,因为它迫使工程团队将时间耗费在追踪那些与团队能修复的任何东西都无关的随机失败上。执行断言的代码路径实际上是由属于别人的负载均衡器控制的。
Patterns That Survive Provider Drift
值得进行的替换方案,大致按工程成本增加的顺序排列如下:
针对语义内容而非字节序列进行断言。 对于回归测试来说,问题几乎从不是“模型是否生成了相同的字符串”,而是“模型是否生成了含义相同的字符串”。使用带有固定评分提示词和确定性评分标准的 LLM-as-a-judge,比字符串相等检查更能抵抗微小的输出偏移,而且这通常也是团队在回归测试中真正需要的。
在每次评估运行中记录指纹。 将指纹视为数据中的一个干扰变量,而不仅仅是在出问题时才扫一眼的元数据。当回归分数发生变化时,首要回答的问题是指纹是否发生了变化。如果是,那么回归就有了一个已知的结构性原因,在不重新训练基准的情况下,跨越该变化的对比就不是对等的。对于没有指纹字段的供应商,请记录模型版本字符串和时间戳,并将它们作为替代参考。
当可复现性确实至关重要时,固定到专属部署。 共享基础设施上的托管推理是以可复现性换取吞吐量。如果你的测试套件、评估流水线或生产回放将运行间的可复现性视为硬性要求,答案是使用专属端点 —— 无论是供应商管理的带有保证部署的专属实例, 还是具有批次无关内核的自托管推理。这两种选择都比默认端点更昂贵,而这正是可复现性真正的成本。
将确定性作为一个采购问题。 被这个问题困扰的通常是那些根据价格和延迟选择供应商、签署合同后,在第四个月才发现 SLA 中完全没有关于确定性条款的团队。询问这些问题的廉价时机是在评估期间。正确的问题不是“你们是否支持 seed 参数” —— 每个供应商都会回答支持。正确的问题是:在什么条件下相同的 seed 会产生相同的输出?哪些后端更改会导致该保证失效?以及在这些更改上线前,合同约定的通知期是多久?
The Architectural Reframe
能够经受住时间考验的构架视角是:托管推理中的确定性是部署(deployment)的属性,而非模型(model)的属性。模型是一个数学对象,在给定概率分布的情况下,原则上可以进行确定性采样。而部署是一个具有共享硬件、批次执行和基于负载路由的分布式系统,该系统拥有独立于模型的确定性属性,且模型的 API 大多无法暴露这些属性。
将 seed 视为契约的团队,构建了一个可靠性寄托在别人负载均衡器上的测试套件。而将 seed 视为提示(hint)、将部署视为可复现性实际来源的团队,则构建了能够衡量其真正想要衡量之物的评估和回归基础设施。这两个团队在一段时间内都会看到相同的随机失败,但只有其中一个团队能够根据信号采取行动。
这个教训的廉价版本是:停止对托管推理进行字节级精确字符串断言。完整版本是:围绕你实际拥有的部署重新设 计评估契约,并将部署作为评估结果的一个参数,而非一个不可见的常量。Seed 是采样器上的一个旋钮,而产生分布的是系统,而这个系统并不属于你。
- https://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/
- https://cookbook.openai.com/examples/reproducible_outputs_with_the_seed_parameter
- https://platform.openai.com/docs/guides/advanced-usage
- https://152334h.github.io/blog/non-determinism-in-gpt-4/
- https://arxiv.org/pdf/2408.04667
- https://www.lmsys.org/blog/2025-09-22-sglang-deterministic/
- https://www.vellum.ai/llm-parameters/seed
- https://www.keywordsai.co/blog/llm_consistency_2025
- https://simonwillison.net/2025/Sep/11/defeating-nondeterminism/
- https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/reproducible-output
