framsouza/inference-incident-response
GitHub: framsouza/inference-incident-response
一份面向 AI 推理工作负载的 incident 响应指南,提供针对推理平台特有故障模式的可观测性、分层分类法和自动化分诊方案。
Stars: 0 | Forks: 0
# AI 推理的 incident 管理理念
如果事先没有进行适当的定义,AI 推理的 incident 管理很快就会变得极具挑战性。工程师们习惯了处理传统的 web-app incident,而对 Inference 工作负载进行同样的处理可能会大相径庭,如果没有提前考虑周全,难度会大得多。
本指南列出了一些处理 AI 推理工作负载 incident 的思路。此外,本指南还假定您已经熟悉 prefill、decode、KV cache 和 health probes,并且您在 Kubernetes 的 gateway 后面运行了类似 vLLM、TensorRT-LLM、NVIDIA Dynamo 或 Triton 的服务。
## 为什么推理 incident 不是 web-app incident
人们很容易倾向于直接使用在无状态 web 服务上行之有效的 SRE 直觉,并原封不动地应用到这里。千万不要这么做。Inference 平台的故障方式与传统三层应用不同,一些常规操作可能会使情况变得更糟,甚至可能破坏整个模型的可用性。
在 incident 期间,有几个值得注意的差异:
- **工作负载的有状态性是 HTTP 服务所不具备的。** KV cache 在 GPU 内存中保存着实时的对话状态。在 web 层上,您可能想都不想就简单地“直接重启 pod”,但这种粗暴的做法会丢弃正在进行的生成和已缓存的上下文,将瞬间的延迟波动变成所有活动会话的请求丢失。首先要记住的是,不要“仅仅”重启 pod。
- **请求寿命长且成本高昂。** 单次生成可能运行数十秒,并消耗大量的 GPU 时间。作为 web 弹性标配的激进重试机制,可能会将计算任务堆积在已经饱和的 engine 上,从而加深故障,而不是熬过去。
- **模型是一个单一的、庞大的、共享的 artifact。** 加载权重需要数分钟时间和大量内存。在负载下触发重新加载、精度更改或配置交换的“修复”操作,并不能像 web 应用上的代码部署那样优雅地失败。如果推送了错误的 quantization、batch size 或并行设置,可能会立刻让整个模型对所有用户离线,而不仅仅是降低某一条请求路径的性能。
- **容量扩展既困难又缓慢。** 您无法在几秒钟内通过自动扩容来解决 GPU 短缺问题。重新路由和分流负载是您的快速杠杆,而像传统 web 应用那样增加硬件则行不通。
实际后果是,在这里,错误的 runbook 或条件反射式的修复更加危险。在 web 应用上,糟糕的缓解措施通常只会让您看到几个错误页面。而在 Inference 平台上,同样的错误可能会导致整个模型崩溃。这正是为什么 runbook 需要针对特定的推理故障模式、需要预先关联到 incident 中的原因,也是为什么快速、可逆的操作(重新路由、重启单个节点)要优先于任何涉及模型本身操作的原因。
## 需要调试的推理层
每个推理 incident 都存在于少数几个层中的一个,给它们命名可以让响应者保持清晰的方向感。自下而上分别是:
- **Infrastructure**:平台运行所依赖的计算、网络、编排和存储。节点、调度器、互联网络(NVLink、NVSwitch、InfiniBand)、部署模板、sidecar。当这一层出现故障时,pod 将无法进入就绪状态,并且不稳定性会集中在特定节点上。
- **GPU**:加速器本身及其内存。这是 KV cache 存在的地方,是发生 OOM kill 的地方,也是损坏或性能下降的显卡暴露问题的地方。它位于 Infrastructure 层内部,但值得单独列出,因为在谈论 Inference 时,GPU 内存压力和硬件故障有它们自己的信号和修复方法。
- **Inference engine**:加载权重并运行请求的运行时服务:vLLM、TensorRT-LLM、Dynamo、Triton。超时、延迟飙升、批处理和缓存行为、崩溃以及泄漏都源自这里。这是 incident 期间最繁忙的层。
- **Gateway**:负责终止连接、跨 engine 和区域路由流量、应用速率限制,并将错误转换返回给客户端的前门。路由不均、节流、连接超时和错误转换的状态码都存在于这一层。
大多数真实的 incident 都是跨层的:一个性能下降的 GPU 会填满 cache 并导致 engine(Inference engine)停滞,进而触发 gateway 作为 408 暴露出来的超时(Gateway)。关键技能在于发现哪一层最先出现问题,因为修复方案就应该应用在那里。下面的分类部分将把这变成一条标记规则。
## 无声的失败:当 200 OK 并非成功时
这是让习惯了 web 的团队措手不及的故障模式,也是本指南能补充的最重要的一点:在 Inference 平台上,一个请求可以返回 `200 OK`,具有正常的延迟、干净的状态码分布,但这依然可能是一场全面的 incident。模型在线、响应迅速且正在回答。它只是回答得*错误*。当产品出现故障时,下面章节中的每个仪表板却都是绿色的。
在 web 应用上,契约是二元的。请求要么成功,要么抛出错误,您的状态码会告诉您是哪一种。Inference 打破了这个契约,因为“成功”是一个质量问题,而不是一个交付问题。字节返回得很好。但它们是否是*正确*的字节,HTTP 对此毫无意见,延迟、错误率或 GPU 利用率也无法说明这一点。这就是您的整个分类法和状态码表都无法察觉的一类 incident,也是用户比您先注意到的一类 incident。
究竟是什么导致了无声的质量回退:
- **加载了错误的模型或权重。** 某次发布指向了一个过时的 checkpoint、一个标记错误的 adapter 或是之前的版本,导致每个响应都出现微妙(或严重)的偏差。延迟完美。没有任何错误。
- **精度或 quantization 回退。** 您将模型移至 FP8 以节省内存,评估了一次就发布了,但后来的 kernel、engine 或配置更改改变了准确性。输出全面退化,唯一可见的信号是答案变得更糟了。
- **退化的生成。** 由错误的采样配置、损坏的 stop-token 或 chat-template 不匹配导致的重复循环、提前截断、空白补全或失控的输出。请求成功完成并返回了垃圾内容。
- **Tokenizer 或模板漂移。** 服务端的 tokenizer 或 prompt 模板不再与模型训练时的内容匹配。格式悄悄崩溃,结构化输出和 tool-calling 失效,而且任何地方都没有报错。
- **数值损坏。** NaN 或错误的 speculative-decoding 路径产生了看起来合理但错误的 token。pipeline 永远不会抛出异常,只是不断地输出带有 200 状态码的废话。
这之所以危险,原因在于检测,而不是缓解。本指南前面部分的所有内容检测的都是*可用性*问题:engine 变慢、节点死亡、gateway 正在节流。而质量回退不会触发其中任何一个。如果您唯一的检测手段是延迟和状态码,那么您发现这些 incident 的方式将和那些最严重的中断一样——都是客户告诉您您的模型变笨了。
因此,质量需要自己的可观测性,与可用性信号并存:
- **Golden-set canary 探针。** 一小套固定的 prompt,带有已知的良好预期输出,在每个部署上持续运行。当 canary 答案偏离其基线时,您就会在真实用户受到影响之前发现一起质量 incident。这是价值最高的单一控制手段,它能立即捕获错误的权重和 quantization 回退。
- **实时流量上的输出形状信号。** 输出长度分布、空响应率、重复率和格式有效率(JSON 是否解析成功,tool call 是否符合 schema)。这些在响应流上计算成本很低,并且当生成发生退化时会急剧变化,无需判断其含义。
- **LLM-as-judge 采样。** 使用单独的评估器模型对少量真实响应样本进行评分,并作为滚动质量指标进行跟踪。每次调用的成本很高,因此采用采样而不是全覆盖的方式,但它能捕获形状信号遗漏的细微退化。
- **Guardrail 和验证器命中率。** 如果您已经运行了输出验证器或安全过滤器,其拒绝率的突然飙升就是一个强烈的早期信号,表明生成质量发生了变化。
- **加载时的版本和配置断言。** 在 engine 接收流量之前,断言模型版本、checkpoint hash、精度和 tokenizer 完全符合预期,并将其集成到包含单元测试和模型质量测试的 CI/CD 中。这项廉价的预检可以将无声的、长达数小时的质量 incident 变成响亮的部署时失败。
有两个后果值得带入本指南的其余部分。首先,质量回退值得拥有自己的 SLO 和自己的告警路径,与可用性区分开来,因为就 HTTP 而言,它们不消耗任何错误预算。其次,它们改变了分类法:无声的回退几乎总是**模型配置失败**,任何状态码都不会指引您找到它,这正是为什么最早原因标记规则和版本断言如此重要的原因。当仪表板是绿色的而用户感到不快时,这就是首先要查看的地方。
## 1. 可观测性优先
您无法管理一个您看不见的 incident。在下面任何自动化构建具有价值之前,信号必须存在并且是可信的。这是最不引人注目的前提,也是团队最常跳过并最终后悔的部分。incident 或严重告警应该自动触发,而不是等待最终用户注意到并报告它。我们的目标是保持主动,以尽可能小的用户影响来解决 incident。
对于 Inference 平台来说,“具备可观测性”意味着三个层已经连接并相互关联:
**请求级信号。** 这些描述了单个推理请求,是您最早的故障预警。
- 首个 token 的生成时间(受 prefill 限制)
- Token 间延迟(受 decode 限制)
- 末尾 token 的生成时间,即用户可见的完整延迟
- HTTP 状态分布,按代码细分
**Engine 和硬件信号。** 这些告诉您请求信号为什么会发生变化。
- GPU 利用率和内存压力
- KV cache 占用率和逐出率
- Batch size、队列深度、待处理的 token
- Engine 重启、OOM kill、崩溃循环
**平台信号。** Engine 之下的层。
- 节点健康状态、调度状态、pod 就绪状态
- 网络和互联网络健康状态
- 存储和协调层延迟
将它们关联起来的意义在于,单个 incident 通常会同时出现在这三个层中。首个 token 生成时间增加(请求层)是由于 KV cache 填满(Engine 层)导致的,而 KV cache 填满是因为某个节点掉线,流量堆积到了其相邻节点上(平台层),这是一个 incident,而不是三个。如果您的仪表板无法将它们放在同一条时间线上,您的响应者将浪费最初的二十分钟仅仅去拼凑出整体情况。
**定义 SLO,而不仅仅是阈值。** 静态的“如果延迟 > X 则告警”在缓慢退化时触发得太晚,而在突发流量时又会引发狼来了般的误报。在真正反映用户痛苦的信号(成功率、尾部延迟)上定义目标,并基于错误预算消耗率进行告警。消耗率告警可以捕获固定阈值容易错过的缓慢失血,并且它为您提供了一个干净、有据可依的自动开启 incident 的触发器。
阅读请求信号是一项值得明确说明的技能。首个 token 生成期间的延迟增加指向 prefill,它是计算受限的。token 之间的延迟增加指向 decode,它是内存受限的。它们有不同的原因和不同的解决方法,因此请在仪表板上将这两个指标分开,而不是将它们合并为一个“延迟”数字。
## 2. 将 incident 工具连接到您的可观测性系统
一旦信号建立起来,就将它们连接到 incident 管理工具,这样宣告和运行 incident 就不会变成手忙脚乱的 scramble。Rootly 是这里常见的选择;PagerDuty、incident.io 或 FireHydrant 发挥着同样的作用。重要的是连接方式,而不是品牌。
您想要的流程是:
**SLO 违规时自动开启。** 当 SLO 警告消耗率告警或硬阈值被触发时,incident 将自动创建。不需要人工去注意、判断其真实性并在确认后宣告 incident。消除这一人工步骤正是您挽回决定这是小波动还是大中断的关键分钟所在。如果可能,严重性应直接根据告警本身来设定,这样涉及两个 endpoint 的大量 500 错误开启时的严重级别就会高于单个安静 endpoint 的缓慢偏移。
**自动创建 Slack 频道。** 在 incident 开启的那一刻,应该建立一个专属频道并将相关人员拉入其中。每个人都聚集在一个地方,时间线会根据那里发布的内容自动捕获,您不必再在私聊中到处追踪上下文。incident 工具应该负责这一切:创建频道、呼叫响应者、分配角色、附加仪表板、runbook,全部由触发器自动完成。
**在创建 incident 时附加证据。** 这是区分快速响应和冷启动的做法。每个 incident 在到达时都应包含:
- 受影响服务或 endpoint 的**仪表板**,已链接并将时间范围限定在 incident 窗口内。仪表板必须讲述您的 Inference 平台正在发生什么的完整故事。
- 触发的**告警**,包含其查询语句和违规的具体数值。
- 相关故障模式的**runbook**,这样响应者就能直接看到首要操作步骤,而不是到处寻找。
提前将仪表板、告警和 runbook 连接到每个告警定义中,才能实现这一自动化。当 incident 开启时,这些链接已经存放在频道中了。响应者阅读这三样东西,就能了解自己所处的状况。
## 3. 随 incident 一起交付分类法一套关于什么坏了的共享词汇表,能让响应者在几秒钟内完成分类,并直接跳转到正确的 runbook,而不是把每个 incident 都当成新问题来对待。将分类标签作为一个必填字段直接放在 incident 记录中。
以下四个类别是划分 Inference 平台故障的一种方式。它是一个起点,而非金科玉律。请根据您的平台实际经历的情况来调整类别,如果两个团队对于某事属于哪一类一直存在分歧,那说明您需要改进分类,而不是放弃它。
**Infrastructure 失败**:Engine 之下的计算、网络和编排层。坏的 GPU 节点、计算分配延迟、互联中断、配置错误的部署模板、sidecar 和服务网格故障、调度失败、存储、协调层问题。通常表现为 pod 卡住、与节点相关的不稳定性或部署时失败。一旦识别出问题,恢复通常很快:您只需重启或重新平衡,而不是编写代码。
**模型配置失败**:模型的打包方式及其输入和输出的形成方式。无效或缺失的权重、缺失或格式错误的请求/响应头、编码错误、无效输出。通常表现为在发布确切开始时出现的 5xx 错误。最干净的缓解措施通常是回滚部署,而且该类别中的大部分问题都可以通过 pipeline 中的头部/schema 检查来预防。
**Inference engine 失败**:承受真实负载的 serving engine。长生成时间导致的请求超时、资源耗尽、延迟飙升、KV cache 错误、崩溃、内存泄漏、采样约束错误。通常表现为长请求上的 408 错误、与负载相关的 5xx 以及不断上升的尾部延迟。这是大多数平台上最大且最繁忙的类别。
**Operational 失败**:流程和可观测性层本身。低流量 endpoint 的检测滞后、错误的告警阈值、缺失的遥测数据、错误代码转换错误、路由不平衡、发布失误。通常表现为一个持续时间远超预期的 incident,因为之前没人察觉,或者流量严重倾斜到了某一个副本上。
**标记规则。** 真实的 incident 会触及不止一个类别。为了保持标签的一致性,请分配时间线中出现得最早的类别,即第一个实际出错的地方,而不是您看到的最后一个症状。一个表现为 engine 超时的坏节点属于 Infrastructure incident。
在您甚至还没打开仪表板之前,一个快速的状态码捷径有助于进行分诊:
| 代码 | 首先猜测 | 指向 |
|---|---|---|
| 500 | 流量到达了无法提供服务的 engine | 路由 / health-probe 缺口 |
| 408 | 长请求超时 | 启用流式传输(连接活跃度) |
| 503 | 过载 | 容量,在负载均衡器上进行 pushback |
| 429 | 被节流 | 路由不平衡,缺少按客户端限制 |
| 424 | 依赖项失败 | Sidecar / 配置故障 |
| 504 | Gateway 超时 | 后端缓慢或无法访问 |
如果 500 错误在您的错误混合中占主导地位,那么第一个问题不是“为什么 engine 会崩溃”,而是“为什么流量仍然被路由到无法提供服务的 engine 上”。这是一个路由和 health-probe 修复问题,而且比代码更改更快。
## 4. 使用 AI agent 进行自动化分诊
这是该流程的最佳情况版本,也是值得努力构建的方向。当创建 incident 时,agent 会立即执行人类值班人员在半梦半醒中会做的第一遍分诊,并在响应者甚至还没打开笔记本电脑之前,将其发现发布到 incident 频道中。
一个有用的分诊 agent 会从四个来源提取信息:
- **指标 (Metrics)**:哪个 SLO 被消耗了,当前的请求和 engine 信号与一小时前相比如何,尾部延迟集中在何处。
- **日志**:在受影响的时间窗口内激增的错误特征,这些特征在 gateway、engine 和节点之间是相互关联的。
- **最近变更**:在发病前刚刚部署、扩展或重新配置了什么。过去十分钟内的发布是 agent 能够提供的最有用的线索,而这正是人类在压力下忘记去检查的东西。
- **当前平台状态**:节点健康状况、容量余量、该 endpoint 是隔离的还是与其他同样在退化的 endpoint 共享一个 gateway。
据此,agent 会向频道发布一份初步分诊,包含建议的分类标签、最可能的根本原因及其背后的证据、影响范围,以及从附带的 runbook 中提取的建议的首个缓解措施。它可能会表达这样的意思:endpoint X 上的 SLO 消耗,408 错误率急剧上升,发病前九分钟有一次部署,一个节点不健康,可能是由于糟糕的发布导致的 inference-engine 超时,建议的第一个操作是重新路由并回滚。
响应者不是在听从 agent 的命令。他们是从一个附有证据的假设开始的,而不是面对一张白纸,这就是快速 incident 和慢速 incident 之间的区别。随着时间的推移,agent 提出的标签和缓解措施将成为您的分类法和 runbook 实际效果如何的训练数据。
让那些昂贵、不可逆的操作保持由人工控制。重新路由流量和重启节点对于 agent 来说推荐是安全的,并且最终可以在策略下执行。发布热修复或删除部署还不是可以交给 agent 的事情。agent 的工作是压缩诊断时间,而不是做出需要判断力的决定。在这种用例中,需要诸如 human-in-the-loop 之类的技术。
## 5. 快速缓解,正确修复
有一条操作原则将整个流程联系在一起,值得单独说明。
首先通过快速、可逆的操作来恢复服务。将流量重新路由到健康的 endpoint 或区域,或者重启并重新平衡糟糕的节点。这些操作能在几分钟内缩小影响窗口,而且如果操作错了,也不会产生任何代价。只有在服务恢复健康之后,您才应去寻求昂贵、缓慢的操作:热修复(这会将 incident 拖入整个验证和发布周期),或增加容量(这需要等待节点)。首要原则是稳定平台,然后才将重点放在根本原因上。
一些值得预先配置并准备好的高价值操作:
- **连接活跃度(流式传输)**,用于长时间运行的请求。长生成即使在后端即将返回有效响应时,也会触发前端 408 超时。流式传输可以保持连接活跃,并使这些误报的超时消失。如果您提供长输出服务,这是您可以做出的杠杆率最高的单一更改。
- **感知容量的路由**,使流量优先流向具有真实 GPU 余量的 endpoint,并且永远不会落在已经饱和的 endpoint 上。将其与每 endpoint 隔离配对,这样一个饱和的模型就不会在同一个 gateway 后面拖垮其相邻的模型。
- **KV cache offloading**,用于多轮工作负载,从 GPU 溢出到主机内存,然后再到磁盘或网络存储,这样返回的会话就不必支付全额的重新计算成本。像 NVIDIA Dynamo 这样的框架原生支持这种层次结构。
- **Load-balancer pushback** 限制,针对待处理 token 和请求,以及按客户端节流,从而以比扩容更快的速度、并且以可逆的方式稳定一个饱和的 engine。
## 总结
该流程的端到端步骤为:
1. 可观测性已到位,并在请求、engine 和平台层之间建立了关联,针对用户实际感受到的指标定义了 SLO。
2. SLO 消耗或阈值违规会在您的 incident 工具中自动开启一个 incident。
3. 创建一个专门的 Slack 频道,并将相关人员拉入其中。
4. incident 在到达时已预加载了分类字段、相关仪表板、触发的告警以及匹配的 runbook。
5. AI agent 根据日志、指标、最近变更和当前状态发布初步分诊,并提出建议的标签和首选缓解措施。
6. 响应者通过快速、可逆的操作恢复服务,然后安排正确的修复。
7. 事后复盘记录了发生的事情,最重要的是,记录了机器可以代替人类完成哪些工作,这将成为下个季度的自动化任务。
8. 安排会议讨论根本原因,以及为了避免再次发生而采取了哪些措施。
这一切几乎都与 incident 发生时的个人英雄主义无关。它关乎的是您在 incident 发生前就建立好的可观测性、系统连接和自动化,这样当系统在凌晨 3 点发生故障时,响应者走进的是一个已经为他们整理好了一半的房间。
我希望这能为您如何在此类环境中处理 incident 提供一些启发。
标签:API集成, SRE, 人工智能, 偏差过滤, 可观测性, 大模型推理, 子域名突变, 用户模式Hook绕过, 运维监控