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 复制代码
没有监控的大模型系统,就像没有仪表盘的飞机。
能飞,但你不知道什么时候会出事。
相关推荐
梦想画家8 小时前
RAG应用基石:从六种文档切分算法看语义完整性
人工智能·算法·rag
Touch_Base8 小时前
护照、身份证与罚单:动力电池出海的隐性门槛
大数据·人工智能·创业创新·esg·可持续
ACP广源盛139246256738 小时前
ASW3742@ACP# 产品规格详解
网络·人工智能·嵌入式硬件·计算机外设·电脑
迦南的迦 亚索的索8 小时前
AI_09_Coze_多模态和循环结构
人工智能
郑寿昌8 小时前
国产信创环境下OpenClaw热更新与权限校验改造方案
人工智能
Black蜡笔小新8 小时前
企业私有化AI训练推理一体工作站/企业级AI模型工作站DLTM训推一体工作站助力智慧医疗智能化转型
人工智能·机器学习
科研前沿8 小时前
像素即坐标・室外无边界:2026 最新无感定位技术,驱动数字孪生实景可控—— 镜像视界技术白皮书
大数据·人工智能·算法·重构·空间计算
十铭忘8 小时前
构建一个自己的论文阅读器1——pdf论文转markdown
人工智能
byzh_rc8 小时前
[AI工具从入门到入土] 命令行
网络·人工智能·python·深度学习·matplotlib