模型输出的确定性:temperature、seed 与重试
月下星杖
5 min read
完全 reproducible 的生成在现实服务里几乎做不到,但可控性可以逼近到工程可用的程度。理解哪些因素真的影响确定性,比盲目调参更有用。很多团队把 seed 当成"版本锁",结果一次服务更新就让所有回归基线失效,根源在于没弄清确定性的边界。
temperature 降低随机,不等于锁定输出
把 temperature 设为 0,模型会走近似贪心解码,相同输入趋于相同输出。但"趋于"不是"等于":服务端的批处理、采样实现细节、以及输出层可能的去重或回退逻辑,都会让两次调用出现差异。经验上,temperature=0 能把波动压到很低,却不能在跨服务、跨版本时保证逐字一致。
把 temperature 调到 0 还有一个副作用:输出会变得"最安全、最平庸"。需要多样性(比如生成多个候选、头脑风暴)时,反而要给一点温度,否则每次都得到高度雷同的第一答案。
seed 的可复现边界很窄
很多接口提供 seed 参数,承诺"同 seed 同输入同输出"。它的前提条件常被忽略:
- 必须是同一模型版本。服务更新权重或推理栈后,旧 seed 不再对应旧输出。
- 必须是同一服务实现。不同部署的采样代码不同,seed 含义不互通。
- 输入必须逐 token 一致,包括系统提示、空白、顺序。哪怕多一个空格,结果都可能漂移。
resp = call_model(
messages=messages,
temperature=0,
seed=20261022,
)
把它当作"降低波动的辅助手段",而非"版本锁"。任何依赖 seed 的自动化断言,都要在每次模型升级后重新校准基线,否则断言失败的是基线,不是代码。
重试要分清"该不该"
重试是提升稳定性的常用手段,但用错地方就是纯烧钱:
def call_with_retry(messages, max_retry=3):
for i in range(max_retry):
resp = call_model(messages, temperature=0)
if _parse_ok(resp): # 能解析、过校验
return resp
# 解析失败才重试;语义对但措辞不同不算失败
raise RuntimeError("结构始终无法解析")
该重试:JSON 非法、缺少必填字段、违反 schema、约束校验不过。这些重试往往能救回,因为模型第二次采样有概率生成合法结构。
不该重试:语义已经正确,只是换了种说法;或者模型明确表达了"做不到"。这类重试既改不了结果,又叠加成本,还会放大延迟。一个更细的判断是:如果失败原因是"格式",重试有意义;如果是"能力",重试只是重复失败。
边界条件
- 评估模型改动时,固定 temperature 和 seed,否则指标的小幅波动会掩盖真实变化,让你分不清是调好了还是运气好。
- 需要高一致性的场景(比如评测、对标),用 temperature=0 + seed + 锁定版本,并记录当时模型标识,便于复现。
- 流式输出下,重试要以"完整响应终态"为判据,别拿中间分片做决策,中间态在句子结束前本就会变化。
- 不要把 seed 当成安全机制,它不提供任何隐私或隔离保证,只是一个采样扰动的固定起点。
- 并发请求之间的 seed 是否互相独立,取决于服务实现,不要假设"同一 seed 并发也会得到同一结果"。
确定性也有代价
把 temperature 压到 0、强约束 seed,会牺牲多样性。需要多个候选、需要发散思考的场景,过度确定反而有害——你每次都得到那个最平庸的答案。所以确定性是"按需开启"的能力:评测、对标、需要可复现时打开;brainstorming、创意生成时关掉。把它当成开关,而不是默认配置。另外,依赖 seed 做缓存键也很危险:不同版本 seed 含义不同,用旧缓存喂新模型会产生看似确定、实则错配的结果,这比随机更隐蔽,因为表面上一模一样。
确定性的目标不是消灭随机,而是把随机关进"可预期、可重试、可复现"的笼子里,并清楚知道笼子的边界在哪里。
1 view
0 likes
0 comments