AI 基建
0%
第十一部分 · 实践与运营 · 第 90 章

非确定系统的可靠性

作者Changkun Ou
阅读时长约 11 分钟

经典的站点可靠性工程依赖一个假设,而语言模型恰好打破了它:同一个请求,由一个健康的系统服务,会返回同一个响应。当响应是从一个分布里采样出来的,建立在这一假设之上的整套装置,精确匹配的健康探测、确定性的重试、对「正确回复」的二元判定,就不再能描述这个系统了。一个永远不会两次相同的输出,其 服务水平指标(SLI),也就是判断服务事件是否有效的指标,必须度量分布,而不是度量字节串。同一套算术还解释了为什么一个每一步都可靠的智能体,在一项长任务上仍会变得不可靠,并留下两个稳妥回应:追求确定性,或者接纳采样并校验返回的结果。当系统变成随机的,可靠性并不会消失;它从响应转移到了分布上,而整套运维工具必须跟着转移过去。

2026-06-21T23:30:34.570941 image/svg+xml Matplotlib v3.11.0, https://matplotlib.org/ 0.0 2.5 5.0 7.5 10.0 12.5 15.0 17.5 20.0 依赖步骤数 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 端到端成功率 p = 0.99 p = 0.95
图 90.1. 非确定性系统中可靠性复合的示意图。看似很高的单步成功率,在长依赖工作流中仍可能急剧下降。理想化曲线,非实测。

不再奏效的健康探测

一个常规服务,是靠向它问一个已知答案的问题、再检查答案是否返回来监控的。探测是确定性的,因为服务本身是确定性的:负载均衡器 ping 一下 /health,期待 200,看到别的就把这个实例移出轮转。可靠性的整套文献都建立在这之上,从把可用性目标换算成一份可容许的失败量的错误预算开始 (Beyer et al. 2016)。这一切都没有假设过服务每次可能返回一个不同却同样有效的答案。

一个由模型支撑的服务,在核心处违反了这一假设。同一个问题问两遍,token 就不一样,因为生成是从下一个 token 的分布里采样的(第 31 章)。两个答案可以都对,可以都错,也可以一对一错,而没有任何精确匹配的探测能分辨它落在哪种情形里。问题不是服务宕了,而是「在线」不再意味着「正确」。模型服务在生产里失效,往往就发生在这个差距里:端点返回 200,延迟正常,内容却是错的。

所以,为这些系统谈可靠性,首先要把一个确定性服务允许混为一谈的三个问题分开。服务可达吗(可用性)?它及时回答了吗(延迟)?答案好吗(质量)?前两个原样保留。第三个是新的,而它才是决定产品是否可用的那一个。

为一个永不相同的答案定义服务水平指标

办法是不再去度量响应,转而度量分布。SLI是一个窗口内有效事件的占比 (Beyer et al. 2016)。对可用性,事件是一个请求,「有效」意味着被回答了;这个定义不需要改。对质量,事件仍是一个请求,但「有效」必须交给某个能读懂自由格式答案并对它做判断的东西来裁定,这就是 第 52 章 的评测机器,只不过对准的是线上流量,而非一套固定的测试集。

因此,一个质量 SLI 是一个采样的、被裁判过的度量。取生产响应中的一小部分,用一个自动裁判或一份评分量规给每一条打分,再报告通过率。质量通过率是有噪声的,因为裁判本身是一个模型,而流量在漂移,所以一个质量 服务等级目标(SLO) 是一条带状区间而非一条线:目标是「在滚动的一天里,被采样的答案中至少 92% 通过量规」,并在下置信界跌破下限时告警,而不是在单条样本失败时告警。错误预算于是以窗口内失败的样本计,它会被一次糟糕的部署、一段漂移的提示词,或一次上游模型变更消耗,正如一份延迟预算会被一个变慢的依赖消耗一样。

下层约束

可达成的任务可靠性,是由下面一层、由智能体的步数定下的。如果 第 38 章 里那套 harness 的每一步以概率 pp 独立成功,那么一项需要 nn 步顺序完成的任务,成功概率是 pnp^n,而它跌得很快:在 p=0.99p = 0.99 时,一项五十步的任务已经逼近 0.610.61。因此,一个加在任务上的可靠性目标,会向下伸回架构里,给 nn 设上限,或者强制设置检查点,让失败的一步可以重试而不必重启整个任务。SLO 不只是度量系统;它规定了系统被允许走多少步。一个承诺 95% 任务成功、却让智能体不设防地跑两百步的运维者,承诺的是算术所禁止的东西(参照 第 41 章第 71 章)。

为什么可靠的一步不等于可靠的一项任务

上面的约束关系,是运营智能体时最重要的一个事实,值得把它看成一条曲线而非一句话。「我的模型 99% 的时候是对的」与「我的智能体把活干完了」之间的差距,就是 pppnp^n 之间的差距。

图 90.2. 独立的步骤相乘时的任务成功率:每一步以 p 可靠,一项 n 步的任务便以 pⁿ 的概率成功。拖动单步可靠性,看任务成功率跌得有多快,又在哪里越过二分之一这条抛硬币的线。在 p = 0.99 时,一项五十步的任务已经逼近 0.61。
import numpy as np
import matplotlib.pyplot as plt

steps = np.arange(1, 201)
for p in (0.999, 0.99, 0.98, 0.95):
    plt.plot(steps, p**steps, label=f"单步 p={p}")
plt.axhline(0.5, ls="--", color="gray")
plt.xlabel("任务步数 n")
plt.ylabel("任务成功率 p^n")
plt.title("可靠的一步不等于可靠的任务")
plt.legend(); plt.ylim(0, 1); plt.tight_layout(); plt.show()

# 一项任务走到多少步时,失败的概率就超过成功了?
for p in (0.999, 0.99, 0.98, 0.95):
    n_half = int(np.ceil(np.log(0.5) / np.log(p)))
    print(f"p={p}: task is coin-flip reliable at n={n_half} steps")

由此跟出两个运维上的后果。第一,提高单步可靠性,在长任务上有被放大的回报,这正是为什么单步误差的一点小幅下降,可能比一个大特性更要紧。第二,提高任务可靠性最便宜的办法往往是减小 nn:把三个脆弱的步骤折叠成一次稳健的调用,或者设检查点,让可恢复点之间的有效 nn 保持很小。两者都是被一个运维目标逼出来的架构决策,是约束落到实处的样子。

回退:让一条糟糕的样本不等于一次失败

如果任何单条响应都可能是错的,那么一个可靠的系统就需要一个并非简单重复第一次的第二次机会。回退链就是这些第二次机会的有序集合,而它的次序编码了一份成本与延迟的预算。

第一环是重试,也是最危险的一环,因为重试一个随机失败,只有在失败确实是随机时才站得住。当错误只是一次糟糕的采样、而底层请求是可答的,重采样有用;当请求是模型系统性答错的那一类,重采样毫无用处,徒增成本与延迟,在那里它只是把一个缺陷藏在更高的账单后面。因此,重试应当有界,并且最好由一个成本低的检查来把关、确认上一个答案确实失败了,好让预算花在可恢复的错误上,而不是花在没希望的错误上。

后面的环改换应答者,而非重复它。当低成本路径未通过校验器(一种成本低的程序化检查,区别于由 LLM 充当的裁判)时,从一个快的小模型回退到一个更强、更慢的模型,这与一开始把容易的流量送给低成本模型的路由恰好相反(第 76 章)。当两个模型都失败时,从一个生成的答案回退到一个缓存的或模板化的答案,让一个降级但正确的响应胜过一个流畅却错误的响应。这条链画在 图 90.3 中。

req 请求 cheap 快速模型 req->cheap v1 通过 校验器? cheap->v1 done 返回答案 v1->done retry 有界重试 / 重采样 v1->retry 否,预算仍有余量 strong 更强模型 v1->strong 否,预算耗尽 retry->v1 v2 通过 校验器? strong->v2 v2->done det 缓存或模板化的 确定性答案 v2->det det->done
图 90.3. 回退链是第二次机会的有序集合。每一环只在前一环未通过其校验器时才被尝试,而次序以渐增的成本与延迟换取渐增的可靠性。末端那一环是一条本身不会答不出来的确定性路径。

末端环最关键。一条以另一个模型收尾的回退链,收尾在又一个会失败的东西上;一条以确定性路径收尾的链,一个缓存答案、一个模板化响应、一句明确的「我现在答不了这个」,则有一个下限。这里的纪律是:永远留一个末端环,让它在前面每一个模型都失败时,仍有可以返回的结果。

优雅降级,与工具的可靠性

回退是换掉应答者。降级是留下应答者、缩小任务。当一个智能体超出预算,或一个依赖正在失败,可靠的做法是少做而非失败:卸掉可选的工具,仅凭已在上下文里的东西作答,把检索到的上下文缩到排名最高的若干块(第 31 章),或者返回一个标注了自身不确定性的部分结果,而不是花一笔无界的算力去追一个完整的结果。一个只能要么完全成功、要么完全失败的系统,没有降级模式,而一个没有降级模式的系统,恰恰会在可靠性最要紧的那种负载下硬生生崩溃。

智能体调用的工具,本身就是一个可靠性面,而它们受那套更老的、确定性的章法管辖,因为它们是确定性服务。其中三个模式分量最重。超时给任何一次工具调用的等待设一个界,好让一个慢依赖不能拖住整项任务;它的预算取自延迟分布的尾部、而非中位数,因为规模一大,那次罕见的慢调用就成了常见的用户体验 (Dean and Barroso 2013)。幂等让一个被重试的副作用变得安全,好让一次在效果已经提交之后才超时的工具调用,在重试时不会把效果提交两次;一次写操作上的幂等键,正是让上面那条回退链能去重试一个工具的前提。熔断器停止调用一个已经在失败的依赖,好让一个挣扎中的服务获得喘息恢复的余地,而不是被来自每一个停滞任务的重试反复捶打 (Nygard 2018)。它的三个状态画在 图 90.4 中。

breaker closed 闭合(CLOSED) 放行调用,计数失败 open 打开(OPEN) 快速失败,不再调用 closed->open 失败率 > 阈值 half 半开(HALF-OPEN) 放一次试探通过 open->half 冷却结束 half->closed 试探成功 half->open 试探失败
图 90.4. 熔断器是一个挡在失败依赖前面的三态机。CLOSED 放行调用并计数失败;一旦失败率越过阈值它就跳到 OPEN、快速失败而不再调用;冷却之后它转入 HALF-OPEN、放一次试探通过,成功则闭合,失败则重新打开。

对冲是把工具可靠性接回随机内核的主要模式。当一次调用的尾延迟成了问题,就在一段短延迟之后再发一份同样的请求,取先返回的那一个 (Dean and Barroso 2013)。它花一点额外算力来削尾,而且它与采样相得益彰:把同一个提示词的两次采样并行发出,既给了一个延迟对冲,又给了第二份用来相互校验的采样,代价是为落败的那一份也付了钱。

取舍:确定性,还是被校验的采样

在上面每一种技术之下,都压着一个尚未解决的选择,而一个运维者必须选边。一条路追求确定性:把温度(采样的旋钮,取零即贪心的、确定性的解码,取值越高注入的随机性越多)设为零,钉死一个种子,试图把章法其余部分所假设的确定性服务找回来。它提供了可复现性,让调试与回归测试变得可处理,但不能保证正确性,因为那一次贪心解码可能自信地错,而且现在它每次都以同样的方式错。即便温度为零,确定性也要靠工程做出来,而不是默认就有:运行间方差的主因并不是并行浮点运算本身,而是数值随批大小变化的内核。服务器负载一变,同一个提示词就落进不同的动态批形状,归约以不同的方式切分工作,求和结果的细微出入便足以翻转被选中的 token (He 2025)。批不变(batch-invariant)内核补上了这个缺口,如今服务引擎已把它作为选项发布(vLLM 的 VLLM_BATCH_INVARIANT=1,SGLang 的确定性模式),逐位一致的解码成了可以买到的功能,吞吐代价在早期内核上接近 2 倍,调优后的集成则降到 1.3 倍上下 (vLLM 2026; SGLang Team 2025)。另一条路接纳采样并倚靠校验:承认响应会变,在模型与用户之间放一个校验器、一个裁判,或一次跨样本的投票,让可靠性来自检查输出、而非来自钉死输入。它通过冗余获得稳健,并提供一条通往比任何单次贪心解码都更高质量的路径,代价是每个答案更多的算力,以及一个本身也会出错的校验器。无论选哪边,可靠性都是用算力买来的:用吞吐买可复现,或者用校验买稳健。

有争议之处
  • SLO 对开放式生成究竟适不适用? 一派认为,任何成功能由量规裁定的任务,都能像别的任务一样带一个质量 SLO;另一派认为,对真正开放式的工作,「成功」太含糊,没法落到一个数字上,一个质量 SLO 度量的是裁判,多过度量系统。
  • 重试一个随机失败,是有原则的还是一种坏味道? 当失败是一次糟糕采样时,重采样是自然的修法;可一旦模型在某个请求上系统性地错,重试只会抬高成本、同时藏起缺陷。一次重试是否站得住,取决于一个系统在运行时通常分辨不了的区别。
  • 确定性,还是采样加校验? 钉死一个批不变、带种子的服务并付出它的吞吐代价,或者接纳方差、在它前面放一个校验器。前者让失效可复现;后者让失效可承受。这个领域尚未定下哪个才是对的默认,而多数生产系统最后落到一种别扭的混合上。

围着采样建立可靠性

能力、效率、信任在这里彼此尖锐地拉扯。长任务上的能力被 pnp^n 框住,所以那些抬高单步成功率、或削减步数的可靠性工作,才是让一个智能体得以去尝试更长工作的东西。效率是每一次第二次机会的成本:重试、对冲的副本、回退到更强的模型、采样式裁判,全都在用额外算力换可靠性;回退次序则是用金钱和延迟换取更高质量下限的控制参数。信任就是质量 SLI 本身,是承认「端点在线」从来不是核心承诺,而让一个随机系统变得可运营的那一步,是去度量、预算、并就答案好不好告警,而不只是就答案到没到告警。最根本的限制是,机器人在 第 16 章、智能体在本章都会遇到:当系统的工作单元是一次采样,就只能围着采样去建可靠性,假装它是确定的并不会让它变可靠。

延伸阅读

  • Beyer, Betsy; Jones, Chris; Petoff, Jennifer; Murphy, Niall Richard. Site Reliability Engineering: How Google Runs Production Systems (SLI、SLO、错误预算). O'Reilly Media, 2016. sre.google
  • Dean & Barroso, “The Tail at Scale” (尾延迟、对冲请求), 2013. research.google
    Dean 与 Barroso 阐释了大规模分布式系统中尾延迟主导总响应时间的原因,并提出通过对冲请求与冗余执行来降低高百分位延迟的技术。
  • Nygard, Michael T.. Release It! Design and Deploy Production-Ready Software (超时、熔断器、舱壁、稳定性模式). Pragmatic Bookshelf, 2018.
    Nygard 总结了用于构建可在故障中存活的生产软件的稳定性与韧性模式,如断路器、隔板与超时。
  • He, “Defeating Nondeterminism in LLM Inference” (随批大小变化的内核是温度为零仍不确定的根因), 2025. thinkingmachines.ai
    He 把 LLM 推理的不确定性归因于数值随动态批大小变化的归约核,而非单纯的并发,并给出批不变内核,使温度为零的解码逐位一致,解码耗时约为原来的 1.6 至 2 倍。
  • vLLM, “Batch Invariance” (逐位一致的解码作为服务选项,VLLM_BATCH_INVARIANT=1), 2026. docs.vllm.ai
    vLLM 的批不变模式换用确定性内核,让同一请求无论怎样组批都产生相同输出,以吞吐换可复现性。

评论

登录后评论