AI 服务错误归因:推理失败到底该怪模型还是基础设施
一、背景与问题
AI 推理服务的错误分类是一个被低估的问题。一次推理请求返回 500,错误日志可能写着 "inference failed"。但这个 "failed" 的含义太宽了:可能是 GPU OOM(基础设施问题),可能是模型加载失败(基础设施+模型问题),可能是输入 token 超限(业务逻辑问题),可能是外部 API 超时(依赖服务问题),也可能是模型输出格式异常(模型质量问题)。
不区分错误类型,后果很直接:错误率 1% 时你只知道"有 1% 的请求失败了",不知道该给谁提 bug。GPU 团队说"GPU 正常",模型团队说"模型没问题",基础设施团队说"服务没宕机",然后问题没人修。
错误归因的目标不是找到"谁的责任"(那是管理问题),是建立错误类型和故障域的映射关系,让每次错误能快速定位到对应的排查路径。GPU OOM 去 GPU 指标面板看内存曲线,模型输出异常去模型质量指标看输出分布,外部 API 超时去依赖服务追踪看调用链路。归因就是建立这个映射。
二、错误分类体系
flowchart TD A[推理请求错误] --> B{错误根因分类} B --> C[基础设施层] B --> D[模型层] B --> E[业务逻辑层] B --> F[依赖服务层] C --> C1[GPU OOM] C --> C2[节点不可达] C --> C3[磁盘 IO 错误] C --> C4[K8s 资源驱逐] D --> D1[模型加载失败] D --> D2[输出格式异常] D --> D3[推理超时-模型侧] D --> D4[量化精度丢失] E --> E1[输入超限] E --> E2[参数校验失败] E --> E3[权限拒绝] F --> F1[外部 API 超时] F --> F2[向量库不可达] F --> F3[缓存服务故障] style C fill:#b71c1c,color:#fff style D fill:#880e4f,color:#fff style E fill:#4a148c,color:#fff style F fill:#311b92,color:#fff四层错误分类,每层对应不同的排查路径和修复团队:
| 错误层级 | 排查路径 | 修复团队 | 恢复策略 |
|---|---|---|---|
| 基础设施 | GPU/节点/存储指标面板 | 基础设施/SRE | 自动重启/扩容 |
| 模型 | 模型质量指标/输出分布 | 模型/算法 | 降级备用模型 |
| 业务逻辑 | 请求日志/参数校验 | 业务开发 | 返回错误码 |
| 依赖服务 | 依赖追踪/外部服务指标 | 外部服务负责人 | 重试/熔断 |
三、实现方案
3.1 错误码标准化
推理服务的错误码必须区分层级。不要用一个INTERNAL_ERROR涵盖所有场景:
// 错误码定义:层级前缀 + 具体错误 const ( // 基础设施层 INF_ ErrGPUOOM = "INF_GPU_OOM" ErrNodeUnreachable = "INF_NODE_UNREACHABLE" ErrDiskIO = "INF_DISK_IO_ERROR" // 模型层 MOD_ ErrModelLoad = "MOD_LOAD_FAILED" ErrOutputFormat = "MOD_OUTPUT_FORMAT_ERROR" ErrInferenceTimeout = "MOD_INFERENCE_TIMEOUT" // 业务逻辑层 BIZ_ ErrInputTooLong = "BIZ_INPUT_TOKEN_LIMIT" ErrParamInvalid = "BIZ_PARAM_INVALID" // 依赖服务层 DEP_ ErrExternalAPI = "DEP_EXTERNAL_API_TIMEOUT" ErrVectorDB = "DEP_VECTOR_DB_UNREACHABLE" ) func handleError(ctx context.Context, err error) { span := trace.SpanFromContext(ctx) // 从错误码提取层级 code := extractErrorCode(err) layer := code[:3] // INF, MOD, BIZ, DEP span.SetAttribute("error.code", code) span.SetAttribute("error.layer", layer) span.SetStatus(codes.Error, err.Error()) }error.layer属性是关键。它让 Prometheus 可以按层级聚合错误率:
# 各层错误率 sum(rate(inference_request_total{status="error"}[5m])) by (error_layer) # 基础设施层错误占比 sum(rate(inference_request_total{status="error", error_layer="INF"}[5m])) / sum(rate(inference_request_total{status="error"}[5m]))当基础设施层错误占比从 10% 涨到 60%,排查方向立刻转向 GPU 和节点指标,不再在模型代码里浪费时间。
3.2 自动归因逻辑
某些错误类型可以根据上下文自动归因,不需要人工判断:
func autoAttribute(ctx context.Context, err error) string { // GPU OOM:检查 GPU 内存指标 if strings.Contains(err.Error(), "CUDA out of memory") { return "INF_GPU_OOM" } // 模型加载失败:检查模型文件是否存在 if strings.Contains(err.Error(), "model not found") { if !modelExistsOnDisk() { return "INF_DISK_IO_ERROR" // 文件缺失→存储问题 } return "MOD_LOAD_FAILED" // 文件存在但加载失败→模型问题 } // 推理超时:区分模型侧和基础设施侧 if strings.Contains(err.Error(), "timeout") { gpuUtil := getGPUUtilization() if gpuUtil > 90 { return "INF_GPU_OVERLOAD" // GPU 满载→基础设施侧 } return "MOD_INFERENCE_TIMEOUT" // GPU 未满载→模型侧 } // 默认:未归因 return "UNATTRIBUTED" }推理超时的归因逻辑值得说明:GPU 利用率 > 90% 时超时,是因为 GPU 资源不足(基础设施问题);GPU 利用率正常但仍然超时,是因为模型计算本身慢(模型问题)。这个区分直接影响修复策略——前者扩容 GPU 节点,后者优化模型或降低 batch size。
3.3 未归因错误的处理
自动归因不可能覆盖所有场景。UNATTRIBUTED类错误必须人工分析并补充归因规则。流程:
- 每周统计未归因错误的数量和错误消息分布。
- 对于出现频率 > 5次/周的未归因错误,编写新的归因规则并加入
autoAttribute。 - 未归因错误的占比应控制在 < 10%。超过 10% 说明归因体系覆盖不全,需要补充规则。
四、告警与故障响应
4.1 分层告警规则
不同层级的错误对应不同的告警级别和响应流程:
| 错误层级 | 告警级别 | 响应时间 | 响应动作 |
|---|---|---|---|
| 基础设施 (INF) | P1 | 5 分钟 | 自动扩容+人工确认 |
| 模型 (MOD) | P2 | 15 分钟 | 降级备用模型+人工排查 |
| 依赖服务 (DEP) | P2 | 15 分钟 | 熔断降级+通知外部团队 |
| 业务逻辑 (BIZ) | P3 | 不告警 | 日志记录+用户提示 |
基础设施层 P1 是因为 GPU OOM 或节点不可达会导致整个推理服务不可用,影响面最大。业务逻辑层不告警是因为输入校验错误是正常业务行为,不是故障。
4.2 错误率趋势与容量规划
按层级统计错误率趋势,直接关联容量规划:
# 基础设施层错误率趋势(用于GPU容量规划) sum(rate(inference_request_total{status="error", error_layer="INF"}[1d])) # 模型层错误率趋势(用于模型质量评估) sum(rate(inference_request_total{status="error", error_layer="MOD"}[1d]))基础设施层错误率持续上升→GPU 容量不足,需要扩容。模型层错误率持续上升→模型质量下降,需要重新训练或更换模型版本。两个趋势对应完全不同的资源投入方向,混在一起看就无法做决策。
4.3 踩坑记录
错误消息的格式不统一。不同推理框架的错误消息格式不同(vLLM 和 TGI 的 OOM 消息文本不同),自动归因的字符串匹配需要覆盖多种格式。建议维护一个错误消息 → 错误码的映射表,定期更新。
跨层级的复合错误。一个请求可能同时遇到"GPU 内存不足"和"输入超限"——输入太长导致 GPU OOM。这种复合错误的归因优先级:基础设施层优先。因为 GPU OOM 是直接导致服务不可用的原因,输入超限是间接诱因。在 Span 上标注两个错误码,但error.layer只取优先级最高的那个。
错误归因不能替代根因分析。归因把错误分类到正确的排查路径,但不告诉你具体的根因。GPU OOM 的归因指向基础设施层,但根因可能是输入 batch_size 设置太大、可能是模型参数加载了双份、也可能是其他服务占用了 GPU 内存。归因是第一步,根因分析是第二步,两者不能混淆。
五、总结
推理服务的错误归因不是追责,是建立错误类型和排查路径的映射。没有归因,每次错误都是"推理失败",排查方向模糊;有了归因,GPU OOM 直奔 GPU 指标面板,模型异常直奔模型质量面板,响应效率提升数倍。
三个关键实践:
- 错误码按层级前缀区分:INF(基础设施)、MOD(模型)、BIZ(业务)、DEP(依赖),
error.layer属性让 Prometheus 按层级聚合。 - 自动归因覆盖高频场景:GPU OOM、模型加载失败、推理超时根据上下文条件自动判断层级。
- 未归因错误持续补充规则:占比 > 10% 说明覆盖不全,每周补充高频未归因错误的归因规则。
基础设施不需要漂亮话。错误归因的目标不是把"推理失败"变成更详细的错误描述,是把每个错误指向正确的排查路径。指向对了,30 分钟解决问题;指向错了,30 分钟还在争论该谁查。