8.人工智能实战:大模型服务“看起来正常却突然变慢”?Prometheus + Grafana + GPU 指标构建全链路监控体系

人工智能实战:大模型服务"看起来正常却突然变慢"?Prometheus + Grafana + GPU 指标构建全链路监控体系


一、问题场景:线上最怕的不是报错,而是"偶发性变慢"

在前面的系统优化中,我们已经完成了:

text 复制代码
1. FastAPI 封装大模型推理接口
2. vLLM 提升并发吞吐
3. Redis 队列削峰填谷
4. 限流、熔断、降级保护系统
5. 多 GPU 提升推理能力

测试环境压测结果也比较漂亮:

text 复制代码
平均延迟:1.2s
P95:2.8s
错误率:< 1%
GPU 利用率:70% ~ 85%

但真正上线后,业务侧反馈了一个很典型的问题:

text 复制代码
"接口大多数时候很快,但偶尔会突然卡一下。"
"用户没有看到明确报错,但感觉系统不稳定。"
"日志里没有明显异常,但体验就是不好。"

这类问题最难排查。

因为它不是接口完全不可用,也不是服务直接崩溃,而是:

text 复制代码
系统局部变慢,但你不知道慢在哪里。

一开始我也犯了一个错误:只去看应用日志。

结果日志里只有:

text 复制代码
request success
request success
request success

看起来全是成功。

但用户体验并不好。

最后排查发现,问题不在接口逻辑,而在:

text 复制代码
队列等待时间变长 + 某些请求 token 数过大 + GPU 短时间进入高负载

也就是说,问题发生在"链路中间",而不是最终错误日志里。

这也是大模型系统和普通 Web 系统最大的区别:

text 复制代码
普通接口看错误日志,大模型系统必须看全链路指标。

二、真实问题:只看日志为什么定位不了大模型故障?

很多传统后端系统,排查问题主要看:

text 复制代码
1. 应用日志
2. 错误堆栈
3. CPU / 内存
4. 慢 SQL

但大模型推理服务不一样。

一个请求进入系统后,完整链路通常是:

text 复制代码
Client
  ↓
API Gateway
  ↓
参数校验
  ↓
Redis Queue
  ↓
Worker
  ↓
vLLM
  ↓
GPU
  ↓
返回结果

其中任何一层变慢,用户看到的都是:

text 复制代码
接口响应慢

但应用日志只会告诉你:

text 复制代码
最终成功或失败

它不会告诉你:

text 复制代码
1. 请求在队列里等了多久
2. prompt 输入了多少 token
3. 输出生成了多少 token
4. vLLM 推理阶段耗时多少
5. GPU 利用率是否瞬间打满
6. P99 是否已经异常
7. 是否某类长文本请求拖慢了短请求

所以,排查大模型服务问题,不能只看日志,要建立一套可观测体系。


三、原因分析:大模型系统到底要监控什么?

很多人一上来就装 Prometheus、Grafana,然后采一堆指标。

但指标不是越多越好。

真正有用的指标,一定要能回答问题。

大模型服务至少要回答这 5 个问题:

text 复制代码
1. 请求有没有变多?
2. 请求有没有变慢?
3. 慢在哪里?
4. GPU 是否成为瓶颈?
5. 队列是否已经积压?

所以我们需要按层设计指标。


四、指标分层设计

1. 接口层指标

接口层负责回答:

text 复制代码
服务对用户表现如何?

核心指标:

text 复制代码
QPS
成功率
错误率
平均延迟
P95 延迟
P99 延迟

注意,平均延迟只能看趋势,不能代表用户体验。

真正要盯的是:

text 复制代码
P95 / P99

因为大模型服务经常出现:

text 复制代码
平均值很好看,P99 已经爆炸

2. 请求内容指标

大模型请求不是普通 JSON 请求。

它的成本和以下因素强相关:

text 复制代码
prompt 长度
input tokens
max_tokens
output tokens

同样是一次请求:

text 复制代码
20 token 输入 + 64 token 输出

和:

text 复制代码
2000 token 输入 + 512 token 输出

消耗完全不同。

所以必须记录:

text 复制代码
输入 token 数
输出 token 数
总 token 数

3. 队列层指标

如果系统加了 Redis 队列,必须监控:

text 复制代码
队列长度
任务等待时间
任务执行时间
任务失败数

队列长度非常关键。

因为它是系统过载最早出现的信号之一。

如果你只看接口响应时间,通常已经晚了。


4. 模型推理层指标

推理层需要关注:

text 复制代码
模型调用耗时
生成速度 tokens/s
超时次数
失败次数

尤其是:

text 复制代码
tokens/s

它比单纯的请求耗时更能反映模型真实性能。


5. GPU 层指标

GPU 层至少要监控:

text 复制代码
GPU 利用率
显存使用量
显存使用比例
GPU 温度
GPU Power

但这里有个坑:

text 复制代码
GPU 利用率高,不一定代表系统高效;
GPU 利用率低,也不一定代表系统空闲。

例如:

text 复制代码
队列积压严重,但 Worker 没有正常消费

此时 GPU 利用率可能不高,但用户已经很慢。

所以 GPU 指标必须和队列指标、延迟指标一起看。


五、解决方案:Prometheus + Grafana + 自定义指标

这套方案的目标不是"装一个监控面板",而是构建一个可以定位问题的系统。

架构如下:

text 复制代码
FastAPI / Worker
   ↓ 暴露 /metrics
Prometheus
   ↓ 采集指标
Grafana
   ↓ 展示趋势
Alertmanager
   ↓ 告警

本文先实现核心部分:

text 复制代码
1. FastAPI 暴露接口指标
2. Worker 暴露任务指标
3. 采集 GPU 指标
4. Prometheus 抓取
5. Grafana 展示
6. 配置基础告警规则

六、可复现项目结构

text 复制代码
llm-monitor-demo/
├── app.py
├── worker.py
├── metrics.py
├── gpu_metrics.py
├── requirements.txt
├── prometheus.yml
└── alert_rules.yml

七、安装依赖

bash 复制代码
pip install fastapi uvicorn prometheus-client redis rq transformers

如果你只是复现监控逻辑,不需要真正加载大模型,可以先用 sleep 模拟推理。


八、定义统一指标 metrics.py

python 复制代码
from prometheus_client import Counter, Histogram, Gauge

# 请求总数
LLM_REQUEST_TOTAL = Counter(
    "llm_request_total",
    "Total LLM requests",
    ["endpoint", "status"]
)

# 请求延迟
LLM_REQUEST_LATENCY = Histogram(
    "llm_request_latency_seconds",
    "LLM request latency seconds",
    ["endpoint"],
    buckets=[0.1, 0.3, 0.5, 1, 2, 3, 5, 10, 20, 30]
)

# 输入 token
LLM_INPUT_TOKENS = Histogram(
    "llm_input_tokens",
    "Input tokens per request",
    buckets=[32, 64, 128, 256, 512, 1024, 2048, 4096]
)

# 输出 token
LLM_OUTPUT_TOKENS = Histogram(
    "llm_output_tokens",
    "Output tokens per request",
    buckets=[32, 64, 128, 256, 512, 1024]
)

# 队列长度
LLM_QUEUE_SIZE = Gauge(
    "llm_queue_size",
    "Current LLM queue size"
)

# GPU 利用率
GPU_UTILIZATION = Gauge(
    "gpu_utilization_percent",
    "GPU utilization percent",
    ["gpu_index"]
)

# GPU 显存使用
GPU_MEMORY_USED = Gauge(
    "gpu_memory_used_mb",
    "GPU memory used in MB",
    ["gpu_index"]
)

九、FastAPI 接口埋点 app.py

这里为了可复现,先用 time.sleep() 模拟模型推理。

真实项目中,把 mock_llm() 替换成你的 vLLM 或模型调用即可。

python 复制代码
import time
import random
from fastapi import FastAPI, Response
from pydantic import BaseModel, Field
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST

from metrics import (
    LLM_REQUEST_TOTAL,
    LLM_REQUEST_LATENCY,
    LLM_INPUT_TOKENS,
    LLM_OUTPUT_TOKENS
)

app = FastAPI(title="LLM Monitor Demo")


class ChatRequest(BaseModel):
    prompt: str = Field(..., min_length=1, max_length=4000)
    max_tokens: int = Field(default=128, ge=1, le=512)


def estimate_tokens(text: str) -> int:
    # 简化估算:真实项目建议用 tokenizer 统计
    return max(1, len(text) // 2)


def mock_llm(prompt: str, max_tokens: int):
    input_tokens = estimate_tokens(prompt)
    output_tokens = random.randint(32, max_tokens)

    # 模拟推理耗时:输入越长、输出越多,耗时越高
    cost = 0.2 + input_tokens * 0.001 + output_tokens * 0.01
    time.sleep(min(cost, 5))

    return {
        "answer": "这是模拟的大模型回答",
        "input_tokens": input_tokens,
        "output_tokens": output_tokens
    }


@app.post("/chat")
def chat(req: ChatRequest):
    endpoint = "/chat"
    start = time.time()

    try:
        result = mock_llm(req.prompt, req.max_tokens)

        cost = time.time() - start

        LLM_REQUEST_TOTAL.labels(endpoint=endpoint, status="success").inc()
        LLM_REQUEST_LATENCY.labels(endpoint=endpoint).observe(cost)
        LLM_INPUT_TOKENS.observe(result["input_tokens"])
        LLM_OUTPUT_TOKENS.observe(result["output_tokens"])

        return {
            "answer": result["answer"],
            "input_tokens": result["input_tokens"],
            "output_tokens": result["output_tokens"],
            "cost_seconds": round(cost, 3)
        }

    except Exception as e:
        LLM_REQUEST_TOTAL.labels(endpoint=endpoint, status="error").inc()
        raise e


@app.get("/metrics")
def metrics():
    return Response(
        generate_latest(),
        media_type=CONTENT_TYPE_LATEST
    )

启动服务:

bash 复制代码
uvicorn app:app --host 0.0.0.0 --port 8000

访问指标:

bash 复制代码
curl http://127.0.0.1:8000/metrics

可以看到类似指标:

text 复制代码
llm_request_total{endpoint="/chat",status="success"} 10
llm_request_latency_seconds_bucket{endpoint="/chat",le="1.0"} 8
llm_input_tokens_bucket{le="512.0"} 10

十、GPU 指标采集 gpu_metrics.py

如果机器有 NVIDIA GPU,可以通过 nvidia-smi 获取基础指标。

python 复制代码
import subprocess
from metrics import GPU_UTILIZATION, GPU_MEMORY_USED


def collect_gpu_metrics():
    cmd = [
        "nvidia-smi",
        "--query-gpu=index,utilization.gpu,memory.used",
        "--format=csv,noheader,nounits"
    ]

    try:
        output = subprocess.check_output(cmd).decode("utf-8").strip()
    except Exception:
        return

    for line in output.splitlines():
        parts = [x.strip() for x in line.split(",")]
        if len(parts) != 3:
            continue

        gpu_index, util, memory_used = parts

        GPU_UTILIZATION.labels(gpu_index=gpu_index).set(float(util))
        GPU_MEMORY_USED.labels(gpu_index=gpu_index).set(float(memory_used))

接入到 FastAPI 中:

python 复制代码
from gpu_metrics import collect_gpu_metrics

@app.get("/metrics")
def metrics():
    collect_gpu_metrics()
    return Response(
        generate_latest(),
        media_type=CONTENT_TYPE_LATEST
    )

注意:

text 复制代码
生产环境更推荐使用 NVIDIA DCGM Exporter。
这里用 nvidia-smi 是为了方便复现。

十一、Prometheus 配置 prometheus.yml

yaml 复制代码
global:
  scrape_interval: 5s

scrape_configs:
  - job_name: "llm-service"
    metrics_path: "/metrics"
    static_configs:
      - targets: ["host.docker.internal:8000"]

如果 Prometheus 和服务在同一台 Linux 机器上,也可以写:

yaml 复制代码
targets: ["127.0.0.1:8000"]

启动 Prometheus:

bash 复制代码
docker run -d \
  --name prometheus \
  -p 9090:9090 \
  -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus

浏览器打开:

text 复制代码
http://127.0.0.1:9090

查询:

promql 复制代码
llm_request_total

十二、压测生成数据

安装 locust:

bash 复制代码
pip install locust

编写 locustfile.py

python 复制代码
from locust import HttpUser, task, between
import random


class LLMUser(HttpUser):
    wait_time = between(0.5, 2)

    @task
    def chat(self):
        prompts = [
            "解释一下Transformer",
            "请用工程师视角解释大模型部署中的KV Cache",
            "写一段关于人工智能系统架构的长文,要求包含性能、稳定性、监控和部署。",
        ]

        self.client.post("/chat", json={
            "prompt": random.choice(prompts),
            "max_tokens": random.choice([64, 128, 256, 512])
        })

启动压测:

bash 复制代码
locust -f locustfile.py --host=http://127.0.0.1:8000

十三、核心 PromQL 查询

1. QPS

promql 复制代码
sum(rate(llm_request_total[1m]))

2. 错误率

promql 复制代码
sum(rate(llm_request_total{status="error"}[1m]))
/
sum(rate(llm_request_total[1m]))

3. P95 延迟

promql 复制代码
histogram_quantile(
  0.95,
  sum(rate(llm_request_latency_seconds_bucket[5m])) by (le)
)

4. P99 延迟

promql 复制代码
histogram_quantile(
  0.99,
  sum(rate(llm_request_latency_seconds_bucket[5m])) by (le)
)

5. 输入 token 分布

promql 复制代码
histogram_quantile(
  0.95,
  sum(rate(llm_input_tokens_bucket[5m])) by (le)
)

6. GPU 利用率

promql 复制代码
gpu_utilization_percent

十四、Grafana 面板设计

一个真正有用的大模型监控面板,至少要包含以下区域:

text 复制代码
1. 请求总览
   - QPS
   - 成功率
   - 错误率

2. 延迟分布
   - Avg
   - P95
   - P99

3. Token 分布
   - Input tokens P95
   - Output tokens P95

4. 队列状态
   - Queue size
   - Waiting time

5. GPU 状态
   - GPU Util
   - GPU Memory

我建议把下面两个指标放在同一行:

text 复制代码
P99 延迟 + Queue Size

因为如果 P99 上升,同时 Queue Size 上升,基本可以判断:

text 复制代码
系统开始排队。

如果 P99 上升,但 Queue Size 不上升,可能是:

text 复制代码
模型推理本身变慢,或者某类长 token 请求变多。

十五、告警规则 alert_rules.yml

yaml 复制代码
groups:
  - name: llm-alerts
    rules:
      - alert: LLMHighErrorRate
        expr: |
          sum(rate(llm_request_total{status="error"}[1m]))
          /
          sum(rate(llm_request_total[1m])) > 0.05
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "LLM error rate is too high"

      - alert: LLMHighP99Latency
        expr: |
          histogram_quantile(
            0.99,
            sum(rate(llm_request_latency_seconds_bucket[5m])) by (le)
          ) > 10
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "LLM P99 latency is too high"

      - alert: LLMGPUHighMemory
        expr: gpu_memory_used_mb > 22000
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "GPU memory usage is high"

Prometheus 加载规则:

yaml 复制代码
rule_files:
  - "alert_rules.yml"

十六、验证结果:监控如何帮助定位问题?

压测时我刻意构造三类请求:

text 复制代码
短 prompt + 少输出
短 prompt + 多输出
长 prompt + 多输出

从 Grafana 可以观察到:

text 复制代码
1. QPS 没有明显变化
2. P99 延迟突然升高
3. Input tokens P95 同步升高
4. GPU 显存缓慢上升

这说明问题不是流量变大,而是:

text 复制代码
请求结构变化导致推理成本上升。

如果只看接口日志,你看到的是:

text 复制代码
请求成功
请求成功
请求成功

但通过指标可以明确定位:

text 复制代码
长文本请求比例变高 → KV Cache 压力变大 → P99 上升

这就是监控系统的价值。


十七、踩坑记录

坑 1:只看平均延迟

平均延迟最容易骗人。

例如:

text 复制代码
90% 请求耗时 1s
10% 请求耗时 20s

平均值看起来可能还能接受,但 P99 已经非常差。

所以大模型服务必须看:

text 复制代码
P95 / P99

坑 2:只监控 GPU,不监控队列

GPU 利用率低,不代表系统没问题。

如果 Worker 挂了,队列不断堆积,GPU 可能很空,但用户请求已经无法处理。

所以必须同时看:

text 复制代码
Queue Size + GPU Util + P99

坑 3:指标命名混乱

一开始我把指标写成:

text 复制代码
request_count
latency
token

后来接入多个服务后完全乱掉。

建议统一前缀:

text 复制代码
llm_request_total
llm_request_latency_seconds
llm_input_tokens
llm_output_tokens

坑 4:高频调用 nvidia-smi

nvidia-smi 本身有开销,不建议高频执行。

测试环境可以 5 秒采集一次。

生产环境建议:

text 复制代码
NVIDIA DCGM Exporter

坑 5:没有给指标加 label

例如同一个系统里有多个模型:

text 复制代码
qwen
llama
deepseek

最好给指标加:

text 复制代码
model_name
route
status

否则后面无法区分到底是哪一个模型慢。


十八、适合收藏的监控 Checklist

text 复制代码
接口层:
[ ] QPS
[ ] 成功率
[ ] 错误率
[ ] P95 延迟
[ ] P99 延迟

请求层:
[ ] input tokens
[ ] output tokens
[ ] max_tokens
[ ] prompt length

队列层:
[ ] queue size
[ ] waiting time
[ ] task success
[ ] task failed

模型层:
[ ] model latency
[ ] tokens/s
[ ] timeout count

GPU层:
[ ] GPU utilization
[ ] GPU memory used
[ ] GPU memory ratio
[ ] GPU temperature

十九、经验总结

这次问题给我的最大经验是:

text 复制代码
大模型系统不是写完接口就结束,而是必须具备可观测能力。

普通 Web 系统慢了,可能查日志、查 SQL 就能定位。

但大模型服务慢了,问题可能在:

text 复制代码
token 分布
队列等待
KV Cache
GPU 显存
推理调度

如果没有指标,你只能猜。

而线上系统最怕的就是:

text 复制代码
靠猜排查问题。

二十、优化建议

后续可以继续增强:

text 复制代码
1. 使用 DCGM Exporter 采集 GPU 指标
2. 接入 Alertmanager 做自动告警
3. 增加 OpenTelemetry Trace
4. 对不同模型增加 model_name 标签
5. 将队列等待时间单独统计
6. 按 token 区间统计不同请求成本
7. 监控 tokens/s 作为模型吞吐指标

一句话总结:

text 复制代码
没有监控的大模型系统,就像没有仪表盘的飞机。
能飞,但你不知道什么时候会出事。
相关推荐
王牌狮AIen2 分钟前
AI营销智能体实战:OPC如何重构自主获客闭环?
大数据·人工智能·重构·数据挖掘·geo·ai营销
代码有点萌2 分钟前
ComfyUI 新手实战记录:一次跑通 AI 绘图工作流
人工智能
元启数宇3 分钟前
机电设计AI不只是消防:给排水、暖通、强弱电如何进入自动化?
运维·人工智能·自动化
我登哥MVP6 分钟前
VS Code 安装 Claude Code 并接入 DeepSeek V4 Model
人工智能·python·node.js·agent·codex·deepseek·claude code
unique6 分钟前
AI Native 调研报告
人工智能
云烟成雨TD7 分钟前
Spring AI Alibaba 1.x 系列【73】两步 RAG
java·人工智能·spring
ai产品老杨8 分钟前
解耦视频高并发与边缘计算AI布控:基于Docker的高性能安防平台,破局GB28181/RTSP协议兼容与源码交付痛点
人工智能·音视频·边缘计算
CHrisFC9 分钟前
LIMS 系统 AI 建设路径:从自动化到智能化的演进之路
运维·人工智能·自动化
饼干哥哥10 分钟前
一口气搭了300个AI Agents并发处理跨境运营的dirty work
人工智能
AI行业学习11 分钟前
CC‑Switch v3.16.1-下载、配置、安装(2026‑06‑01 最新官方版)
开发语言·人工智能·windows·python