人工智能实战:大模型服务“看起来正常却突然变慢”?Prometheus + Grafana + GPU 指标构建全链路监控体系
一、问题场景:线上最怕的不是报错,而是“偶发性变慢”
在前面的系统优化中,我们已经完成了:
1. FastAPI 封装大模型推理接口 2. vLLM 提升并发吞吐 3. Redis 队列削峰填谷 4. 限流、熔断、降级保护系统 5. 多 GPU 提升推理能力测试环境压测结果也比较漂亮:
平均延迟:1.2s P95:2.8s 错误率:< 1% GPU 利用率:70% ~ 85%但真正上线后,业务侧反馈了一个很典型的问题:
“接口大多数时候很快,但偶尔会突然卡一下。” “用户没有看到明确报错,但感觉系统不稳定。” “日志里没有明显异常,但体验就是不好。”这类问题最难排查。
因为它不是接口完全不可用,也不是服务直接崩溃,而是:
系统局部变慢,但你不知道慢在哪里。一开始我也犯了一个错误:只去看应用日志。
结果日志里只有:
request success request success request success看起来全是成功。
但用户体验并不好。
最后排查发现,问题不在接口逻辑,而在:
队列等待时间变长 + 某些请求 token 数过大 + GPU 短时间进入高负载也就是说,问题发生在“链路中间”,而不是最终错误日志里。
这也是大模型系统和普通 Web 系统最大的区别:
普通接口看错误日志,大模型系统必须看全链路指标。二、真实问题:只看日志为什么定位不了大模型故障?
很多传统后端系统,排查问题主要看:
1. 应用日志 2. 错误堆栈 3. CPU / 内存 4. 慢 SQL但大模型推理服务不一样。
一个请求进入系统后,完整链路通常是:
Client ↓ API Gateway ↓ 参数校验 ↓ Redis Queue ↓ Worker ↓ vLLM ↓ GPU ↓ 返回结果其中任何一层变慢,用户看到的都是:
接口响应慢但应用日志只会告诉你:
最终成功或失败它不会告诉你:
1. 请求在队列里等了多久 2. prompt 输入了多少 token 3. 输出生成了多少 token 4. vLLM 推理阶段耗时多少 5. GPU 利用率是否瞬间打满 6. P99 是否已经异常 7. 是否某类长文本请求拖慢了短请求所以,排查大模型服务问题,不能只看日志,要建立一套可观测体系。
三、原因分析:大模型系统到底要监控什么?
很多人一上来就装 Prometheus、Grafana,然后采一堆指标。
但指标不是越多越好。
真正有用的指标,一定要能回答问题。
大模型服务至少要回答这 5 个问题:
1. 请求有没有变多? 2. 请求有没有变慢? 3. 慢在哪里? 4. GPU 是否成为瓶颈? 5. 队列是否已经积压?所以我们需要按层设计指标。
四、指标分层设计
1. 接口层指标
接口层负责回答:
服务对用户表现如何?核心指标:
QPS 成功率 错误率 平均延迟 P95 延迟 P99 延迟注意,平均延迟只能看趋势,不能代表用户体验。
真正要盯的是:
P95 / P99因为大模型服务经常出现:
平均值很好看,P99 已经爆炸2. 请求内容指标
大模型请求不是普通 JSON 请求。
它的成本和以下因素强相关:
prompt 长度 input tokens max_tokens output tokens同样是一次请求:
20 token 输入 + 64 token 输出和:
2000 token 输入 + 512 token 输出消耗完全不同。
所以必须记录:
输入 token 数 输出 token 数 总 token 数3. 队列层指标
如果系统加了 Redis 队列,必须监控:
队列长度 任务等待时间 任务执行时间 任务失败数队列长度非常关键。
因为它是系统过载最早出现的信号之一。
如果你只看接口响应时间,通常已经晚了。
4. 模型推理层指标
推理层需要关注:
模型调用耗时 生成速度 tokens/s 超时次数 失败次数尤其是:
tokens/s它比单纯的请求耗时更能反映模型真实性能。
5. GPU 层指标
GPU 层至少要监控:
GPU 利用率 显存使用量 显存使用比例 GPU 温度 GPU Power但这里有个坑:
GPU 利用率高,不一定代表系统高效; GPU 利用率低,也不一定代表系统空闲。例如:
队列积压严重,但 Worker 没有正常消费此时 GPU 利用率可能不高,但用户已经很慢。
所以 GPU 指标必须和队列指标、延迟指标一起看。
五、解决方案:Prometheus + Grafana + 自定义指标
这套方案的目标不是“装一个监控面板”,而是构建一个可以定位问题的系统。
架构如下:
FastAPI / Worker ↓ 暴露 /metrics Prometheus ↓ 采集指标 Grafana ↓ 展示趋势 Alertmanager ↓ 告警本文先实现核心部分:
1. FastAPI 暴露接口指标 2. Worker 暴露任务指标 3. 采集 GPU 指标 4. Prometheus 抓取 5. Grafana 展示 6. 配置基础告警规则六、可复现项目结构
llm-monitor-demo/ ├── app.py ├── worker.py ├── metrics.py ├── gpu_metrics.py ├── requirements.txt ├── prometheus.yml └── alert_rules.yml七、安装依赖
pipinstallfastapi uvicorn prometheus-client redis rq transformers如果你只是复现监控逻辑,不需要真正加载大模型,可以先用 sleep 模拟推理。
八、定义统一指标 metrics.py
fromprometheus_clientimportCounter,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])# 输入 tokenLLM_INPUT_TOKENS=Histogram("llm_input_tokens","Input tokens per request",buckets=[32,64,128,256,512,1024,2048,4096])# 输出 tokenLLM_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 或模型调用即可。
importtimeimportrandomfromfastapiimportFastAPI,ResponsefrompydanticimportBaseModel,Fieldfromprometheus_clientimportgenerate_latest,CONTENT_TYPE_LATESTfrommetricsimport(LLM_REQUEST_TOTAL,LLM_REQUEST_LATENCY,LLM_INPUT_TOKENS,LLM_OUTPUT_TOKENS)app=FastAPI(title="LLM Monitor Demo")classChatRequest(BaseModel):prompt:str=Field(...,min_length=1,max_length=4000)max_tokens:int=Field(default=128,ge=1,le=512)defestimate_tokens(text:str)->int:# 简化估算:真实项目建议用 tokenizer 统计returnmax(1,len(text)//2)defmock_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.01time.sleep(min(cost,5))return{"answer":"这是模拟的大模型回答","input_tokens":input_tokens,"output_tokens":output_tokens}@app.post("/chat")defchat(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)}exceptExceptionase:LLM_REQUEST_TOTAL.labels(endpoint=endpoint,status="error").inc()raisee@app.get("/metrics")defmetrics():returnResponse(generate_latest(),media_type=CONTENT_TYPE_LATEST)启动服务:
uvicorn app:app--host0.0.0.0--port8000访问指标:
curlhttp://127.0.0.1:8000/metrics可以看到类似指标:
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获取基础指标。
importsubprocessfrommetricsimportGPU_UTILIZATION,GPU_MEMORY_USEDdefcollect_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()exceptException:returnforlineinoutput.splitlines():parts=[x.strip()forxinline.split(",")]iflen(parts)!=3:continuegpu_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 中:
fromgpu_metricsimportcollect_gpu_metrics@app.get("/metrics")defmetrics():collect_gpu_metrics()returnResponse(generate_latest(),media_type=CONTENT_TYPE_LATEST)注意:
生产环境更推荐使用 NVIDIA DCGM Exporter。 这里用 nvidia-smi 是为了方便复现。十一、Prometheus 配置 prometheus.yml
global:scrape_interval:5sscrape_configs:-job_name:"llm-service"metrics_path:"/metrics"static_configs:-targets:["host.docker.internal:8000"]如果 Prometheus 和服务在同一台 Linux 机器上,也可以写:
targets:["127.0.0.1:8000"]启动 Prometheus:
dockerrun-d\--nameprometheus\-p9090:9090\-v$(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml\prom/prometheus浏览器打开:
http://127.0.0.1:9090查询:
llm_request_total十二、压测生成数据
安装 locust:
pipinstalllocust编写locustfile.py:
fromlocustimportHttpUser,task,betweenimportrandomclassLLMUser(HttpUser):wait_time=between(0.5,2)@taskdefchat(self):prompts=["解释一下Transformer","请用工程师视角解释大模型部署中的KV Cache","写一段关于人工智能系统架构的长文,要求包含性能、稳定性、监控和部署。",]self.client.post("/chat",json={"prompt":random.choice(prompts),"max_tokens":random.choice([64,128,256,512])})启动压测:
locust-flocustfile.py--host=http://127.0.0.1:8000十三、核心 PromQL 查询
1. QPS
sum(rate(llm_request_total[1m]))2. 错误率
sum(rate(llm_request_total{status="error"}[1m])) / sum(rate(llm_request_total[1m]))3. P95 延迟
histogram_quantile( 0.95, sum(rate(llm_request_latency_seconds_bucket[5m])) by (le) )4. P99 延迟
histogram_quantile( 0.99, sum(rate(llm_request_latency_seconds_bucket[5m])) by (le) )5. 输入 token 分布
histogram_quantile( 0.95, sum(rate(llm_input_tokens_bucket[5m])) by (le) )6. GPU 利用率
gpu_utilization_percent十四、Grafana 面板设计
一个真正有用的大模型监控面板,至少要包含以下区域:
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我建议把下面两个指标放在同一行:
P99 延迟 + Queue Size因为如果 P99 上升,同时 Queue Size 上升,基本可以判断:
系统开始排队。如果 P99 上升,但 Queue Size 不上升,可能是:
模型推理本身变慢,或者某类长 token 请求变多。十五、告警规则 alert_rules.yml
groups:-name:llm-alertsrules:-alert:LLMHighErrorRateexpr:|sum(rate(llm_request_total{status="error"}[1m])) / sum(rate(llm_request_total[1m])) > 0.05for:2mlabels:severity:warningannotations:summary:"LLM error rate is too high"-alert:LLMHighP99Latencyexpr:|histogram_quantile( 0.99, sum(rate(llm_request_latency_seconds_bucket[5m])) by (le) ) > 10for:3mlabels:severity:warningannotations:summary:"LLM P99 latency is too high"-alert:LLMGPUHighMemoryexpr:gpu_memory_used_mb>22000for:2mlabels:severity:warningannotations:summary:"GPU memory usage is high"Prometheus 加载规则:
rule_files:-"alert_rules.yml"十六、验证结果:监控如何帮助定位问题?
压测时我刻意构造三类请求:
短 prompt + 少输出 短 prompt + 多输出 长 prompt + 多输出从 Grafana 可以观察到:
1. QPS 没有明显变化 2. P99 延迟突然升高 3. Input tokens P95 同步升高 4. GPU 显存缓慢上升这说明问题不是流量变大,而是:
请求结构变化导致推理成本上升。如果只看接口日志,你看到的是:
请求成功 请求成功 请求成功但通过指标可以明确定位:
长文本请求比例变高 → KV Cache 压力变大 → P99 上升这就是监控系统的价值。
十七、踩坑记录
坑 1:只看平均延迟
平均延迟最容易骗人。
例如:
90% 请求耗时 1s 10% 请求耗时 20s平均值看起来可能还能接受,但 P99 已经非常差。
所以大模型服务必须看:
P95 / P99坑 2:只监控 GPU,不监控队列
GPU 利用率低,不代表系统没问题。
如果 Worker 挂了,队列不断堆积,GPU 可能很空,但用户请求已经无法处理。
所以必须同时看:
Queue Size + GPU Util + P99坑 3:指标命名混乱
一开始我把指标写成:
request_count latency token后来接入多个服务后完全乱掉。
建议统一前缀:
llm_request_total llm_request_latency_seconds llm_input_tokens llm_output_tokens坑 4:高频调用 nvidia-smi
nvidia-smi本身有开销,不建议高频执行。
测试环境可以 5 秒采集一次。
生产环境建议:
NVIDIA DCGM Exporter坑 5:没有给指标加 label
例如同一个系统里有多个模型:
qwen llama deepseek最好给指标加:
model_name route status否则后面无法区分到底是哪一个模型慢。
十八、适合收藏的监控 Checklist
接口层: [ ] 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十九、经验总结
这次问题给我的最大经验是:
大模型系统不是写完接口就结束,而是必须具备可观测能力。普通 Web 系统慢了,可能查日志、查 SQL 就能定位。
但大模型服务慢了,问题可能在:
token 分布 队列等待 KV Cache GPU 显存 推理调度如果没有指标,你只能猜。
而线上系统最怕的就是:
靠猜排查问题。二十、优化建议
后续可以继续增强:
1. 使用 DCGM Exporter 采集 GPU 指标 2. 接入 Alertmanager 做自动告警 3. 增加 OpenTelemetry Trace 4. 对不同模型增加 model_name 标签 5. 将队列等待时间单独统计 6. 按 token 区间统计不同请求成本 7. 监控 tokens/s 作为模型吞吐指标一句话总结:
没有监控的大模型系统,就像没有仪表盘的飞机。 能飞,但你不知道什么时候会出事。