LLM工程化 进阶 性能监控 推理指标 GPU监控 LLM Ops

LLM性能指标监控:从吞吐量到响应延迟的完整指南

AIEng Hub
阅读约 20 分钟

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 ITLGPU 利用率
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 性能监控建立在三个基础上:

  1. 正确的指标选择:理解 TTFT、ITL、TPOT 等核心指标的含义和关联
  2. 合理的 SLO 定义:从用户体验出发,设定有意义的阈值
  3. 持续的性能诊断:基于数据定位瓶颈,有针对性地优化

性能监控不是目的,而是持续改进的手段。