想象一下,你刚刚使用 vLLM 部署了一个全新的大型语言模型(LLM),它的响应速度比你想象的还要快。然而,在高峰期,突然出现了一些问题:响应速度急剧下降,成本失控,你不得不手忙脚乱地去查找原因。这听起来是不是很熟悉?你不是一个人。成功的 LLM 部署和代价高昂的失败之间的区别,往往在于 可观测性

可观测性 就像是 LLM 的私人健康监护仪。就像你不会在不监测心率的情况下跑马拉松一样,你不应该在生产环境中运行 LLM 而不监控其关键指标。本文将深入探讨如何利用 OpenTelemetryPrometheusGrafanaJaeger 构建生产级 LLM 可观测性,并以 vLLM 为例,阐述其具体应用。

LLM可观测性的重要性

传统的软件监控方法在面对 LLM 时常常显得力不从心。LLM 的复杂性带来了独特的 可观测性 挑战,主要体现在以下几个方面:

  • Token 使用与成本: 每个生成的 token 都会产生费用。监控 token 使用情况有助于成本优化和容量规划。
  • 延迟与吞吐量: 快速的响应时间对于用户体验至关重要。观察每次 token/请求的延迟和吞吐量(token/秒)等指标可以确保流畅的操作。
  • GPU 利用率: LLM 是资源密集型应用。高效的 GPU 利用率是最大化硬件投资的关键。
  • 模型性能: 运营指标可以暗示 token 生成错误或其他问题,从而指导改进。
  • 分布式追踪: 像检索增强生成 (RAG) 或多代理管道这样的复杂架构需要端到端跟踪,以快速识别瓶颈和故障点。

构建 LLM 任务控制中心

构建一个强大的 LLM 可观测性 平台需要选择合适的工具。本文选择了一套强大的开源工具栈,它们可以协同工作,提供全面的监控能力。

  • RAG 应用: 一个 FastAPI 应用程序,模拟 RAG 管道,并进行检测以进行跟踪和指标收集。
  • vLLM: 你的快速推理引擎,也是本文的主角。最新版本的 vLLM 现在支持 OpenTelemetry,并公开了一个 Prometheus 可以抓取的指标端点。
  • OpenTelemetry: 遥测数据的通用翻译器。OpenTelemetry (OTel) 是一个开源 可观测性 框架,它提供了一种标准化的方法来收集、生成和导出来自云原生应用程序和基础设施的遥测数据(指标、日志和跟踪)。
  • Jaeger: 你的请求侦探(跟踪每一步)。使用 OTel 显示跟踪。
  • Prometheus: 永不休眠的数据收集器。
  • Grafana: 你那美观、可定制的仪表板。

这些组件之间的交互方式如下:

  1. vLLM 处理 LLM 推理请求,并通过 OpenTelemetry 生成指标和跟踪数据。
  2. RAG 应用 执行检索增强生成流程,同样通过 OpenTelemetry 生成指标和跟踪数据。
  3. Jaeger 收集来自 vLLMRAG 应用 的跟踪数据,用于可视化请求的端到端流程。
  4. Prometheus 定期从 vLLMRAG 应用 的指标端点抓取指标数据。
  5. Grafana 连接到 Prometheus 并使用抓取的指标数据创建可定制的仪表板,用于实时监控 LLM 的性能和资源使用情况。

设置监控堆栈 (简单方法)

无需手动设置所有内容。本文选择 Docker Compose 来处理繁重的工作,并使你可以轻松试用该堆栈。

先决条件

在开始之前,请确保你拥有:

  • 已安装并运行 Docker 或 Podman
  • 一台具有 GPU 的 VM
  • 基本熟悉 vLLM、PrometheusGrafana 概念

项目结构

首先,让我们组织我们的文件:完整的代码可以在 GitHub 仓库中找到。

├── docker-compose.yml
├── prometheus.yml
├── rag-app/
│   ├── Dockerfile
│   └── app.py
├── vllm-server/
│   └── Dockerfile
└── grafana/
    ├── provisioning/
    │   ├── datasources/
    │   │   └── prometheus.yml
    │   └── dashboards/
    │       └── dashboard.yml
    └── dashboards/
        ├── vllm-dashboard.json
        └── rag-dashboard.json

步骤 1:Docker Compose 配置

以下是完整的 docker-compose.yml,用于协调整个监控交响曲:

services:
  ## Mocked RAG application (embedding + vector search mocked)
  rag-app:
    build:
      context: rag-app  # Builds the image using Dockerfile from the rag-app directory
    container_name: rag-app
    ports:
      - "8002:8002"  # FastAPI main API endpoint
      - "8001:8001"  # Prometheus metrics endpoint
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=grpc://jaeger:4317  # Send traces to Jaeger
    depends_on:
      - jaeger
      - prometheus

  ## vLLM server for LLM inference
  vllm-server:
    build: vllm-server  # Builds the image from the vllm-server directory
    container_name: vllm-server
    ports:
      - "8000:8000"  # vLLM inference API port
    environment:
      - MODEL_NAME=facebook/opt-125m  # Name of the model to serve
      - OTEL_SERVICE_NAME=vllm-server  # Service name used in tracing
      - NVIDIA_VISIBLE_DEVICES=all  # Make all GPUs visible to the container
      - NVIDIA_DRIVER_CAPABILITIES=compute,utility  # Required for GPU compute workloads
      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=grpc://jaeger:4317  # Send traces to Jaeger
      - OTEL_EXPORTER_OTLP_TRACES_INSECURE=true  # Allow insecure (non-TLS) trace export
      - VLLM_LOGGING_LEVEL=DEBUG  # Set logging level
    command: vllm serve facebook/opt-125m --otlp-traces-endpoint=grpc://jaeger:4317  # Start vLLM server
    depends_on:
      - jaeger
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia  # Use the NVIDIA runtime
              count: all      # Use all available GPUs
              capabilities: [gpu]  # Request GPU capability

  ## Jaeger: distributed tracing backend
  jaeger:
    image: jaegertracing/all-in-one:1.57  # Jaeger all-in-one image
    container_name: jaeger
    ports:
      - "16686:16686"   # Jaeger UI
      - "4317:4317"     # OTLP gRPC endpoint
      - "4318:4318"     # OTLP HTTP endpoint
      - "6831:6831/udp" # Jaeger agent UDP port
      - "6832:6832/udp"
      - "14250:14250"
      - "14268:14268"
      - "14269:14269"
      - "5778:5778"
      - "9411:9411"     # Zipkin compatible port

  ## Prometheus: metrics collector
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml  # Mount custom config
    ports:
      - "9090:9090"  # Prometheus UI and API

  ## Grafana: metrics dashboard and visualization
  grafana:
    image: grafana/grafana
    container_name: grafana
    ports:
      - "3000:3000"  # Grafana dashboard UI
    environment:
      - GF_SECURITY_ADMIN_USER=admin  # Default admin username
      - GF_SECURITY_ADMIN_PASSWORD=admin  # Default admin password
    volumes:
      - grafana-storage:/var/lib/grafana  # Persist Grafana data
      - ./grafana/dashboards:/var/lib/grafana/dashboards  # Mount custom dashboards
      - ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards  # Auto-provision dashboards
      - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources  # Auto-provision data sources
    depends_on:
      - prometheus
      - jaeger

volumes:
  grafana-storage:  # Named volume for persistent Grafana data

此设置不仅仅是监控,它还是一个具有内置 可观测性 的 RAG 应用程序:

  • RAG 应用程序功能:
    • 公开 API 端点 (端口 8002) 和指标 (端口 8001)
    • 将跟踪发送到 Jaeger 以实现端到端可见性
    • 模拟真实的 RAG 管道
  • vLLM 配置亮点:
    • 使用轻量级 facebook/opt-125m 模型(非常适合测试)
    • 具有 NVIDIA 运行时的 GPU 支持
    • 启用调试日志以进行故障排除
    • 直接 OTLP 跟踪到 Jaeger
  • 监控集成:
    • Jaeger 通过 OTLP 收集来自 RAG 应用程序和 vLLM 的跟踪
    • Prometheus 使用 RAG 应用程序和 vLLM 公开的 /metrics 端点从多个端点抓取指标。
    • Grafana 提供具有持久性存储的统一仪表板
    • Jaeger UI 可视化端到端跟踪。

步骤 3:配置 Prometheus 抓取

你的 prometheus.yml 应该定位到两个服务:

global:
  scrape_interval: 5s

scrape_configs:
  - job_name: 'rag-app'
    static_configs:
      - targets: ['rag-app:8001']

  - job_name: 'vllm-server'
    static_configs:
      - targets: ['vllm-server:8000']

Prometheus 将每 5 秒从这些端点收集指标:http://vllm-server:8000/metrics 和 http://rag-app:8001/metrics

步骤 4:Grafana Provisioning

Grafana 可以预配置为自动连接到 Prometheus 并加载仪表板。 这涉及:

  • datasources/datasource.yml:将 Prometheus 定义为数据源,指向 Docker 网络内的 http://prometheus:9090。
# Save this as ./grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
  - name: prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: true
  • dashboards/dashboard.yml:指示 Grafana 从已挂载仪表板目录中的 .json 文件加载仪表板定义。 在 GitHub 仓库中,本文提供了两个预定义的仪表板,一个用于 vLLM 服务器,另一个用于 RAG 应用程序。

步骤 4:启动你的监控帝国

使用以下命令运行整个堆栈:

docker compose up -d

几分钟之内,你将拥有:

  • RAG 应用程序:http://localhost:8002(你的应用程序)
  • vLLM 服务器:http://localhost:8000(模型服务)
  • Jaeger UI:http://localhost:16686(跟踪可视化)
  • Prometheus:http://localhost:9090(指标存储)
  • Grafana:http://localhost:3000(仪表板 — admin/admin)

预期结果

  • 成功的 vLLM 启动日志:
docker compose ps
NAME          IMAGE                            COMMAND                  SERVICE       CREATED          STATUS          PORTS
grafana       grafana/grafana                  "/run.sh"                grafana       25 seconds ago   Up 24 seconds   0.0.0.0:3000->3000/tcp, :::3000->3000/tcp
jaeger        jaegertracing/all-in-one:1.57    "/go/bin/all-in-one-…"   jaeger        25 seconds ago   Up 24 seconds   0.0.0.0:4317-4318->4317-4318/tcp, :::4317-4318->4317-4318/tcp, 0.0.0.0:5778->5778/tcp, :::5778->5778/tcp, 0.0.0.0:9411->9411/tcp, :::9411->9411/tcp, 0.0.0.0:14250->14250/tcp, :::14250->14250/tcp, 0.0.0.0:14268-14269->14268-14269/tcp, :::14268-14269->14268-14269/tcp, 0.0.0.0:16686->16686/tcp, :::16686->16686/tcp, 5775/udp, 0.0.0.0:6831-6832->6831-6832/udp, :::6831-6832->6831-6832/udp
prometheus    prom/prometheus                  "/bin/prometheus --c…"   prometheus    25 seconds ago   Up 24 seconds   0.0.0.0:9090->9090/tcp, :::9090->9090/tcp
rag-app       vllm_opentelemetry-rag-app       "uvicorn rag_app:app…"   rag-app       25 seconds ago   Up 24 seconds   0.0.0.0:8001-8002->8001-8002/tcp, :::8001-8002->8001-8002/tcp
vllm-server   vllm_opentelemetry-vllm-server   "vllm serve facebook…"   vllm-server   25 seconds ago   Up 24 seconds   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp
  • Prometheus targets 应该显示:
rag-app:8001 - UP
vllm-server:8000 - UP
  • Jaeger 跟踪显示:

此示例跟踪显示了从 RAG 应用程序到嵌入、向量数据库、LLM 生成以及最终到 vLLM 服务器的端到端流程。 每个服务及其组件都清楚地显示为跨度。

  • Grafana 应该显示:
    • vLLM 和 RAG 指标的预配置仪表板
    • 实时指标更新。 vLLM 仪表板应显示以下所有指标。 你可以配置你的自定义面板。
      • 请求延迟
      • Token 吞吐量
      • 每个输出 Token 的时间延迟
      • 首个 Token 的时间延迟
      • 请求提示长度
      • 请求生成长度
      • 请求预填充和解码时间
      • 序列组中的最大生成 Token

生产环境最佳实践

  • 持久存储:PrometheusGrafana 挂载卷,以在重启时保留指标和仪表板配置。
  • 安全性: 更改默认密码,保护网络访问,并考虑对所有 UI 使用 HTTPS。
  • 可扩展性: 对于大型部署,请探索分布式 Prometheus (Thanos, Mimir) 和可扩展的 Jaeger 后端 (Elasticsearch, Cassandra)。 使用 OpenTelemetry Collector 进行可靠的摄取。
  • 警报:Grafana 中设置警报以检测高延迟、错误峰值或低吞吐量,并主动通知你的团队。
  • 集中式日志记录: 使用 ELK 或 Loki 等日志记录解决方案补充指标和跟踪,以进行更深入的调试。
  • 高级可观测性: 集成现代的、以 LLM 为中心的工具,例如 Langsmith、Helicone、Instana 或 traceloop,以增强你的 AI 可观测性

vLLM可观测性的实践案例

假设你正在使用 vLLM 服务一个在线问答系统。通过 Grafana 仪表板,你观察到在用户提问高峰期,vLLM 的请求延迟显著增加,而 GPU 利用率并未达到峰值。这表明瓶颈可能在于 CPU 资源或网络带宽。通过 Jaeger 的分布式追踪,你可以进一步追踪请求的整个生命周期,发现预处理步骤(例如分词)占用了大量时间。最终,你通过优化预处理代码,显著降低了延迟,提高了系统的整体吞吐量。

另一个例子是,通过监控 vLLM 的 token 吞吐量,你可以发现某些模型在特定类型的查询上表现不佳,导致 token 生成速度下降。这可能意味着模型需要针对这些特定查询进行微调或调整参数。

总结

可观测性 改变了我们管理和优化 LLM 部署的方式。通过将 vLLMOpenTelemetryJaegerPrometheusGrafana 集成,你将获得可操作的见解,这些见解有助于诊断问题并确保大规模平稳、经济高效且可靠的运营。

这个 可观测性 堆栈无法检测模型偏差、幻觉、有害内容或个人身份信息 (PII) 等挑战。 对于这些,请考虑 IBM 的 watsonx.governance 等治理工具。

无论你是 MLOps 工程师还是 AI 爱好者,构建强大的 可观测性 基础都是你掌握 LLM 运营的第一步。 深入研究、实验,释放 vLLM 的全部潜力!