LLM性能指标监控:从吞吐量到响应延迟的完整指南
LLM 推理服务的性能监控与传统 Web 服务有着本质区别:延迟与输入/输出长度正相关、GPU 利用率波动的解释需要理解推理阶段、吞吐量的优化空间远超传统服务。本文构建了一套完整的 LLM 性能指标体系。
一、核心指标体系
1.1 性能指标全景
LLM 性能指标体系
├── 延迟指标 (Latency)
│ ├── TTFT (首token延迟)
│ ├── ITL (token间延迟)
│ ├── TPOT (每输出token时间)
│ └── End-to-End (端到端延迟)
│
├── 吞吐量指标 (Throughput)
│ ├── RPS (每秒请求数)
│ ├── TPS (每秒token数)
│ └── Goodput (有效输出率)
│
├── 资源指标 (Resource)
│ ├── GPU 利用率
│ ├── GPU 内存占用
│ ├── KV Cache 效率
│ └── 内存带宽利用率
│
└── 业务指标 (Business)
├── 成本/请求
├── 成本/token
├── 缓存效率
└── SLO 达标率
1.2 延迟指标体系
import time
import numpy as np
from collections import deque
from dataclasses import dataclass
from typing import List
@dataclass
class LatencyMetrics:
"""单次请求的延迟指标"""
ttft_ms: float # 首token延迟 (ms)
itl_avg_ms: float # 平均token间延迟
itl_p50_ms: float # 中位数token间延迟
itl_p95_ms: float # P95 token间延迟
total_time_ms: float # 总耗时
num_output_tokens: int # 输出token数
tpot_ms: float # 每token耗时
class LatencyMonitor:
"""
延迟监控器
实时计算 P50/P95/P99 延迟
"""
def __init__(self, window_size=1000):
self.window_size = window_size
self.ttft_values = deque(maxlen=window_size)
self.itl_values = deque(maxlen=window_size)
self.end_to_end = deque(maxlen=window_size)
def record_request(self, latency: LatencyMetrics):
"""记录一次请求的延迟数据"""
self.ttft_values.append(latency.ttft_ms)
self.itl_values.append(latency.itl_avg_ms)
self.end_to_end.append(latency.total_time_ms)
def get_summary(self) -> dict:
"""获取延迟统计摘要"""
def percentile(values, p):
if not values:
return 0
return np.percentile(list(values), p)
return {
'ttft': {
'p50': percentile(self.ttft_values, 50),
'p95': percentile(self.ttft_values, 95),
'p99': percentile(self.ttft_values, 99),
'avg': np.mean(list(self.ttft_values)) if self.ttft_values else 0,
},
'itl': {
'p50': percentile(self.itl_values, 50),
'p95': percentile(self.itl_values, 95),
'p99': percentile(self.itl_values, 99),
},
'e2e': {
'p50': percentile(self.end_to_end, 50),
'p95': percentile(self.end_to_end, 95),
'p99': percentile(self.end_to_end, 99),
},
'sample_count': len(self.end_to_end)
}
1.3 吞吐量指标
class ThroughputMonitor:
"""
吞吐量监控
"""
def __init__(self, window_seconds=60):
self.window_seconds = window_seconds
self.request_timestamps = deque()
self.token_counts = deque()
self.token_timestamps = deque()
def record_request(self, num_output_tokens: int):
"""记录一次请求"""
now = time.time()
self.request_timestamps.append(now)
self.token_timestamps.extend([now] * num_output_tokens)
# 清理窗口外的数据
cutoff = now - self.window_seconds
while self.request_timestamps and self.request_timestamps[0] < cutoff:
self.request_timestamps.popleft()
while self.token_timestamps and self.token_timestamps[0] < cutoff:
self.token_timestamps.popleft()
def get_throughput(self) -> dict:
"""获取当前吞吐量"""
elapsed = min(self.window_seconds,
time.time() - self.request_timestamps[0]
if self.request_timestamps else 1)
return {
'rps': len(self.request_timestamps) / elapsed,
'tps': len(self.token_timestamps) / elapsed,
'requests_in_window': len(self.request_timestamps),
'window_seconds': elapsed
}
二、GPU 资源监控
2.1 关键 GPU 指标
# 使用 nvidia-smi 监控 GPU
nvidia-smi --query-gpu=index,timestamp,name,temperature.gpu,utilization.gpu,\
utilization.memory,memory.total,memory.used,power.draw \
--format=csv -l 5
# 输出示例
# index, timestamp, name, temperature.gpu, utilization.gpu (%),
# utilization.memory (%), memory.total (MiB), memory.used (MiB), power.draw (W)
# 0, 2026/05/11 10:00:00, NVIDIA A100 80GB, 45, 78%, 65%, 81920 MiB, 63488 MiB, 275 W
2.2 GPU 利用率分析
import subprocess
import re
class GPUMonitor:
"""
GPU 指标监控与解读
关键指标解读:
- GPU Utilization (%): 核心计算单元利用率
高 → 计算密集型(Prefill 阶段)
低 → 内存密集型(Decode 阶段)或空闲
- Memory Utilization (%): 显存使用率
高 → KV Cache 占用多
增长中 → 有长序列请求
- Power Draw (W): 功耗
TDP 附近 → 满负荷运行
低于 TDP → 可能受内存带宽限制
"""
def __init__(self, interval=10):
self.interval = interval
self.history = deque(maxlen=360) # 1小时数据
def get_metrics(self) -> dict:
"""获取当前 GPU 指标"""
result = subprocess.run(
['nvidia-smi', '--query-gpu=utilization.gpu,utilization.memory,'
'memory.used,memory.total,temperature.gpu,power.draw',
'--format=csv,noheader,nounits'],
capture_output=True, text=True
)
lines = result.stdout.strip().split('\n')
gpus = []
for i, line in enumerate(lines):
parts = [p.strip() for p in line.split(', ')]
if len(parts) >= 6:
gpu = {
'index': i,
'gpu_util': float(parts[0]),
'mem_util': float(parts[1]),
'mem_used_mb': float(parts[2]),
'mem_total_mb': float(parts[3]),
'temp_c': float(parts[4]),
'power_w': float(parts[5]),
'timestamp': time.time()
}
gpus.append(gpu)
self.history.append(gpus)
return gpus
2.3 KV Cache 效率指标
| 指标 | 公式 | 说明 | 健康值 |
|---|
| KV Cache 利用率 | 已用块/总块数 | PagedAttention 效率 | > 70% |
| 内存碎片率 | 碎片块/总块数 | 内存回收效率 | < 10% |
| 缓存命中率 | prefix_cache_hits/total | 前缀缓存效率 | > 30% |
| 平均序列长度 | total_tokens/num_seqs | 服务请求特征 | 视场景而定 |
三、SLO 定义与监控
3.1 LLM 服务 SLO
class LLMSLO:
"""
LLM 服务 SLO 定义与跟踪
"""
SLO_DEFINITIONS = {
'ttft_p95': {
'description': '95% 请求的首token延迟不超过 2s',
'threshold': 2000, # ms
'measurement': 'ttft',
'percentile': 95,
},
'itl_p95': {
'description': '95% 的token间延迟不超过 100ms',
'threshold': 100, # ms
'measurement': 'itl',
'percentile': 95,
},
'error_rate': {
'description': '错误率不超过 2%',
'threshold': 2.0, # %
'measurement': 'error_rate',
},
'availability': {
'description': '服务可用性不低于 99.9%',
'threshold': 99.9, # %
'measurement': 'availability',
},
}
def __init__(self):
self.slo_status = {}
def check_slo(self, current_metrics: dict) -> dict:
"""检查当前指标是否达标"""
for slo_name, slo_def in self.SLO_DEFINITIONS.items():
value = current_metrics.get(slo_def['measurement'])
if value is not None:
if slo_def['measurement'] in ('error_rate',):
passed = value <= slo_def['threshold']
elif slo_def['measurement'] == 'availability':
passed = value >= slo_def['threshold']
else:
passed = value <= slo_def['threshold']
self.slo_status[slo_name] = {
'passed': passed,
'current': value,
'threshold': slo_def['threshold']
}
return self.slo_status
3.2 错误率分类
| 错误类型 | 原因 | 处理方式 | 允许占比 |
|---|
| API 超时 | 上游 LLM 服务无响应 | 重试 + 降级 | < 1% |
| Rate Limit | 触发速率限制 | 指数退避重试 | < 0.5% |
| 内容过滤 | 安全策略拦截 | 返回友好提示 | < 1% |
| 模型错误 | 模型推理异常 | 切换到备用模型 | < 0.1% |
| 客户端错误 | 请求格式错误 | 返回错误信息 | < 0.5% |
四、仪表盘设计
4.1 性能概览面板
┌─────────────────────────────────────────────────────────────┐
│ LLM 性能监控 2026-05-11 10:30 │
├─────────────────────────────────────────────────────────────┤
│ SLO 状态 │
│ ✅ TTFT P95: 1.2s / 2.0s ✅ ITL P95: 45ms / 100ms │
│ ✅ 错误率: 0.8% / 2.0% ✅ 可用性: 99.95% / 99.9% │
├─────────────────────────────────────────────────────────────┤
│ 实时指标 当前值 过去1小时 │
│ ──────────────────────────────────────────────────── │
│ RPS (每秒请求) 45.2 ▁▂▃▅▇▆▅▄▃▂▁ │
│ TPS (每秒token) 1,230 ▂▃▅▇▆▅▄▃▂▁▂▃ │
│ GPU 利用率 (%) 72% ▃▅▇▆▅▄▃▂▁▂▃▅ │
│ 平均 TTFT (ms) 320 ▂▃▅▇▆▅▄▃▂▁▂▃ │
│ 平均 ITL (ms) 42 ▂▁▂▃▅▇▆▅▄▃▂▁ │
├─────────────────────────────────────────────────────────────┤
│ 模型分布 请求量 P50 ITL P50 TTF │
│ GPT-4o-mini 23,400 35ms 280ms │
│ Claude Haiku 8,100 45ms 350ms │
│ GPT-4o 2,300 55ms 450ms │
└─────────────────────────────────────────────────────────────┘
4.2 告警规则
alerts:
- name: high_ttft
condition: "p95_ttft > 2000ms for 5min"
severity: critical
action: ["notify_team", "scale_up"]
- name: high_error_rate
condition: "error_rate > 5% for 3min"
severity: critical
action: ["notify_team", "fallback_model"]
- name: gpu_overload
condition: "gpu_mem_usage > 95% for 2min"
severity: warning
action: ["throttle_requests", "scale_up"]
- name: cost_spike
condition: "hourly_cost > 2x_baseline"
severity: warning
action: ["investigate"]
五、性能基准测试
5.1 基准测试方案
# 使用 vLLM benchmark 脚本
python -m vllm.entrypoints.openai.benchmark \
--model meta-llama/Llama-2-7b-hf \
--num-prompts 100 \
--request-rate 10 \
--max-num-seqs 256 \
--dataset /path/to/sharegpt.json
# 输出示例
# ============ Serving Benchmark Result ============
# Request throughput: 9.8 req/s
# Output token throughput: 456.2 tok/s
# Total Token throughput: 1234.5 tok/s
# ---------- Latency ----------
# Average TTFT: 321.4 ms
# Average ITL: 41.2 ms
# Average TPOT: 38.5 ms
# P99 TTFT: 892.1 ms
# P99 ITL: 98.3 ms
5.2 基准测试指标解读
| 场景 | 期望 TPS | 期望 P95 TTFT | 期望 P95 ITL | GPU 利用率 |
|---|
| Llama-2-7B (A100) | > 800 tok/s | < 500ms | < 50ms | > 70% |
| Llama-2-13B (A100) | > 400 tok/s | < 800ms | < 80ms | > 75% |
| Llama-2-70B (2xA100) | > 200 tok/s | < 1500ms | < 100ms | > 80% |
| Qwen-2.5-72B (2xA100) | > 250 tok/s | < 1200ms | < 80ms | > 80% |
六、常见问题排查
6.1 性能问题诊断
| 问题现象 | 可能原因 | 诊断方法 | 解决方案 |
|---|
| TTFT 过高 | Prompt 过长 | 检查平均 input length | 限流长 prompt;启用前缀缓存 |
| ITL 波动大 | 批次大小不稳定 | 检查 Continuous Batching 效率 | 调整 max-num-seqs |
| GPU 利用率低 | 批次太小 | 检查并发数 | 增加并发;增大 batch size |
| GPU 内存不足 | 序列过多 | 检查 KV Cache 分配 | 降低并发;启用 PagedAttention |
| 错误率突增 | 模型服务不稳定 | 检查错误类型分布 | 切换备用模型 |
6.2 性能调优思路
def diagnose_performance_bottleneck(metrics: dict) -> str:
"""
诊断性能瓶颈
典型的瓶颈模式:
- GPU 利用率低 + 内存利用率高 → 内存带宽瓶颈
- GPU 利用率高 + TTFT 高 → 计算瓶颈
- GPU 利用率高 + 内存利用率低 → 计算瓶颈(Prefill)
- GPU 利用率低 + 内存利用率低 → 系统侧瓶颈(网络/调度)
"""
gpu_util = metrics.get('gpu_util', 0)
mem_util = metrics.get('mem_util', 0)
ttft = metrics.get('ttft_p50', 0)
itl = metrics.get('itl_p50', 0)
if gpu_util < 50 and mem_util < 50:
return "⚠️ 系统侧瓶颈:检查网络延迟、请求量、调度效率"
elif gpu_util > 80 and mem_util < 60:
return "⚠️ 计算瓶颈(Prefill阶段):考虑升级GPU或使用量化"
elif gpu_util < 60 and mem_util > 80:
return "⚠️ 内存带宽瓶颈(Decode阶段):增大max-num-seqs或升级内存带宽"
elif gpu_util > 70 and mem_util > 70:
return "✅ 负载合理:GPU和内存均衡利用"
else:
return "✅ 状态正常"
七、实施路线图
| 阶段 | 内容 | 产出 |
|---|
| 基础阶段 | 接入延迟 + 错误率 + 吞吐量指标 | Grafana 面板 |
| 进阶阶段 | 添加 GPU 监控 + SLO 跟踪 | 告警系统 |
| 优化阶段 | 性能瓶颈诊断 + 自动调优 | 优化建议系统 |
| 高级阶段 | 成本分析 + 容量规划 | 自动伸缩策略 |
总结
有效的 LLM 性能监控建立在三个基础上:
- 正确的指标选择:理解 TTFT、ITL、TPOT 等核心指标的含义和关联
- 合理的 SLO 定义:从用户体验出发,设定有意义的阈值
- 持续的性能诊断:基于数据定位瓶颈,有针对性地优化
性能监控不是目的,而是持续改进的手段。