《生产环境AI推理服务根因诊断实战》

3. 监控与可观测性:日志、指标、追踪三大支柱

在诊断生产环境AI推理服务故障时,仅依赖“服务是否存活”这类粗粒度判断远远不够。推理服务通常涉及客户端接入层、负载均衡、推理引擎(如Triton、vLLM)、模型加载器、GPU资源池、特征存储等多个环节,故障可能发生在任意一跳。可观测性(Observability)正是为了回答三个核心问题:哪里出了问题(日志)、问题有多严重(指标)、一次请求到底经历了什么(追踪)。本章围绕日志、指标、追踪三大支柱,讲解如何用Prometheus、Grafana、Jaeger构建面向AI推理服务的可观测体系。

3.1 三大支柱的分工与关联

三大支柱并非彼此孤立,而是从不同维度描述同一系统的运行状态。日志记录离散事件,指标提供聚合视图,追踪还原请求链路。对于AI推理服务,三者缺一不可:日志告诉你模型报错的具体堆栈,指标告诉你GPU利用率是否打满,追踪告诉你慢请求卡在了预处理还是模型推理阶段。

核心关系:指标用于发现异常,日志用于定位原因,追踪用于还原全链路。三者结合才能完成从“发现问题”到“定位根因”的闭环。
AI推理服务可观测性三大支柱 AI推理服务 客户端请求 ↓ 预处理/Token化 ↓ 模型推理(GPU) ↓ 后处理/采样 ↓ 返回响应 📋 日志 (Logs) 离散事件记录 错误堆栈/访问日志 ELK / Loki 📊 指标 (Metrics) 聚合数值序列 GPU/延迟/吞吐 Prometheus 🔗 追踪 (Traces) 请求全链路还原 Span/ Trace ID Jaeger 工具链 Prometheus Grafana Jaeger Loki ELK OpenTelemetry 发现异常 → 定位原因 → 还原链路,三者协同完成根因诊断

3.2 日志:从ELK到Loki的实践

AI推理服务的日志具有高并发、大流量、多实例的特点。一次批量推理可能产生数百条日志,而GPU节点上的日志采集不能影响推理性能。生产环境推荐采用Filebeat + Kafka + Logstash + Elasticsearch的异步采集链路,或者使用更轻量的Promtail + Loki方案,后者与Grafana集成更紧密,且对象存储成本更低。

日志采集的关键在于结构化。原始文本日志难以检索,必须统一为JSON格式,至少包含以下字段:

{
  "timestamp": "2025-06-18T14:23:45.123Z",
  "level": "ERROR",
  "service": "inference-server",
  "trace_id": "8f3a2b9c1d4e5f6a",
  "model_name": "resnet50",
  "gpu_id": 2,
  "error_code": "CUDA_OOM",
  "message": "CUDA out of memory. Tried to allocate 256 MiB",
  "request_id": "req_20250618_142345"
}

在排查GPU显存溢出(OOM)时,通过error_code=CUDA_OOM过滤日志,再按gpu_id聚合,可以快速定位是哪张卡、哪个模型实例触发的异常。如果日志中缺少trace_id,则无法关联到具体请求,诊断效率会大幅下降。

3.3 指标:Prometheus监控体系搭建

Prometheus采用拉取(Pull)模型,通过HTTP端点周期性采集指标。对于AI推理服务,除了常规的CPU、内存、网络指标外,还必须关注以下AI专属指标:

指标类别 指标名称 说明 告警阈值建议
GPU利用率 gpu_utilization_percent GPU计算核心利用率 > 95% 持续5分钟
显存占用 gpu_memory_used_bytes 当前显存使用量 > 90% 总显存
推理延迟 inference_latency_seconds P50/P95/P99延迟 P99 > 2s
吞吐量 inference_requests_total 每秒处理的请求数 低于基线50%
批处理大小 batch_size_current 当前动态批处理大小 持续为1(未生效)
排队长度 inference_queue_length 等待处理的请求数 > 100

在Prometheus中配置采集任务时,需要为每个推理服务实例添加独立的job,并通过标签区分模型版本和GPU节点:

scrape_configs:
  - job_name: 'inference-gpu'
    static_configs:
      - targets: ['10.0.1.10:9100', '10.0.1.11:9100']
        labels:
          gpu_node: 'node-a'
          model: 'resnet50-v2'
  - job_name: 'inference-metrics'
    metrics_path: '/metrics'
    static_configs:
      - targets: ['10.0.1.10:8080', '10.0.1.11:8080']

Grafana负责将Prometheus中的指标可视化。针对AI推理服务,建议创建三个独立Dashboard:GPU资源总览(显存、利用率、温度)、推理服务质量(延迟分布、吞吐、错误率)、队列与批处理(排队长度、批大小、超时请求数)。当P99延迟飙升时,首先查看GPU利用率是否打满,再检查队列长度是否堆积,这两个指标往往能直接指出瓶颈所在。

3.4 追踪:Jaeger还原请求全链路

日志和指标能告诉你“发生了什么”,但无法回答“这个慢请求经过了哪些环节”。Jaeger通过Trace ID将一次推理请求的完整调用链串联起来,每个环节称为一个Span。对于AI推理服务,典型的Span链路如下:

Trace: 8f3a2b9c1d4e5f6a
├── [0ms]    HTTP入口 (gateway)
│   ├── [2ms]    鉴权/限流 (auth)
│   ├── [5ms]    请求预处理 (preprocess)
│   │   ├── [3ms]    文本Token化 (tokenizer)
│   │   └── [2ms]    特征拼接 (feature_merge)
│   ├── [150ms]  模型推理 (inference)
│   │   ├── [120ms]  GPU执行 (cuda_kernel)
│   │   └── [30ms]   采样/解码 (sampler)
│   └── [8ms]    后处理 (postprocess)
└── [165ms] 总耗时

在Jaeger UI中,可以按service=inference-server过滤,再按model_name=resnet50分组,查看不同模型的耗时分布。如果发现GPU执行Span耗时异常,但GPU利用率并不高,说明可能存在数据加载瓶颈或CPU与GPU同步等待,此时需要进一步查看cudaMemcpy的耗时。

生产环境接入Jaeger时,推荐使用OpenTelemetry SDK进行埋点,它同时支持日志、指标、追踪三种信号的统一上报,避免为每个工具单独接入SDK。在Python推理服务中,只需在关键函数上添加装饰器即可完成埋点:

from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

tracer = trace.get_tracer("inference.tracer")

@tracer.start_as_current_span("model_inference")
def run_inference(input_tensor):
    # 模型推理逻辑
    with tracer.start_as_current_span("gpu_execute"):
        output = model(input_tensor)
    return output

3.5 案例:一次P99延迟突刺的联合诊断

某生产环境AI翻译服务在晚高峰出现P99延迟从800ms飙升至3s的故障。监控团队按以下流程完成根因定位:

  1. Grafana查看指标:发现inference_queue_length从20飙升至500,gpu_utilization_percent维持在99%,但inference_requests_total并未明显增长,排除流量突增。
  2. Jaeger查看追踪:随机抽取慢请求Trace,发现gpu_execute Span耗时正常(约120ms),但preprocess Span耗时从5ms增至800ms,定位到预处理环节。
  3. 日志定位细节:按trace_id过滤日志,发现大量tokenizer_timeout错误,提示分词器线程池耗尽。
  4. 根因确认:分词器使用同步阻塞调用,在并发请求增加时线程池被打满,导致请求排队。

修复方案是将分词器改为异步调用,并增加线程池大小。修复后P99延迟回落至850ms,队列长度恢复正常。

3.6 常见误区与复盘

误区一:日志采集影响推理性能。在GPU节点上使用同步日志写入,会导致推理线程阻塞。正确做法是使用异步日志队列,或直接通过UDP发送到日志采集器。
误区二:指标采集频率过高。Prometheus默认15秒拉取一次,如果缩短到1秒,会对推理服务产生额外开销。GPU指标建议5-10秒采集一次,业务指标保持15秒即可。
误区三:追踪采样率设置不当。100%采样在高并发下会产生海量数据,建议采用尾部采样策略:只对P99以上的慢请求进行全链路追踪,正常请求按1%概率采样。

另一个常见问题是三大支柱数据割裂。日志、指标、追踪必须通过trace_id和request_id关联起来。在日志中打印trace_id,在指标中增加trace_id标签(或至少保留request_id),在追踪中记录日志索引,才能在诊断时从Grafana跳转到Jaeger,再从Jaeger跳转到Loki日志,形成完整的诊断闭环。

最后需要强调的是,可观测性建设不是一次性工作。每次故障复盘后,应检查现有监控项是否覆盖了本次故障的盲区,并持续补充告警规则和Dashboard面板。只有将三大支柱真正融入日常运维流程,才能在AI推理服务出现异常时快速完成根因诊断。