在将大型语言模型(LLMs)从实验室走向生产环境的过程中,模型的稳定运行、性能优化以及资源的高效利用成为了至关重要的环节。正如任何复杂的软件系统一样,对大模型的部署进行精细的监控和运维,可以帮助我们及时发现潜在问题、诊断瓶颈、评估效率,并为进一步的性能调优提供数据支持。

本篇文章将重点介绍如何利用业界广泛认可的开源监控解决方案——Prometheus和Grafana,来实时跟踪大模型推理服务中的关键性能指标,特别是推理延迟和显存占用。我们将深入到具体的实现方法、需要关注的指标以及如何通过可视化界面来提升运维效率。

一、 为什么需要监控大模型的推理性能?

在深入监控技术之前,我们先来理解一下为什么要花时间在监控上:

性能保障与SLA(服务水平协议): 用户对AI服务的响应速度有普遍的期望。低延迟、高吞吐量是直接衡量服务质量的关键。监控可以帮助我们确保服务达到预设的SLAs。

资源成本优化: 大模型推理往往需要昂贵的GPU资源。通过监控可以发现资源利用率低下的情况(如GPU空闲、显存未充分利用),从而进行优化,降低运营成本。反之,也能及时发现资源瓶颈,防止因资源不足导致的服务降级。

故障预测与快速定位:

延迟升高: 可能预示着算力不足、内存瓶颈、模型本身的计算复杂度问题,或是下游依赖的延迟增加。

显存溢出(OOM): 可能由过大的Batch Size、过长的序列,或模型在特定输入下产生了异常大的中间激活值引起。

GPU利用率异常: 过低可能表示算力分配不均或模型计算效率低;过高则可能接近瓶颈。

预测性维护: 某些指标(如GPU温度、功耗)的异常增长,可能预示着硬件可能出现故障。

模型版本迭代与A/B测试: 当部署新模型版本或进行A/B测试时,监控是评估新版本性能、与旧版本对比的直接依据。

二、 Prometheus + Grafana:强大的监控组合拳

Prometheus是一个开源的系统监控和告警工具包。它采用拉(Pull)模型抓取(Scrape)指标数据,并将这些时间序列数据存储在自己的时序数据库(TSDB)中。Prometheus的设计哲学是“服务发现”和“多维度数据模型(标签pairs)”,使其非常适合监控动态变化的分布式系统。

Grafana是一个领先的开源数据可视化和分析工具。它能够连接多种数据源(包括Prometheus),并提供强大的仪表盘(Dashboard)功能,可以将采集到的时间序列数据以图表、仪表盘等多种形式直观地呈现出来。

为何选择它们?

成熟稳定: Prometheus和Grafana都是久经考验的开源项目,拥有庞大的社区支持。

灵活性: Prometheus可以通过Exporter(指标采集器)获取几乎任何系统或应用的指标;Grafana则提供了极其灵活的可视化配置。

强大的查询语言(PromQL): Prometheus的PromQL是一门功能强大的时间序列数据库查询语言,能够进行数据聚合、过滤、计算等复杂操作,为Grafana的仪表盘提供丰富的数据支撑。

生态系统完善: 易于与其他系统集成,如Kubernetes(用于服务发现和部署)。

三、 关键监控指标与采集方案

要有效地监控大模型推理,我们需要从多个维度收集数据。

1. 核心指标:推理延迟与显存占用

a. 推理延迟(Inference Latency)

这是衡量模型响应速度的最直接指标。

目标: 跟踪单个请求从进入模型到返回结果的时间。

单位: 毫秒(ms)或秒(s)。

采集方案:

应用层埋点: 在模型推理代码中,记录请求进入模型的开始时间(t_start_inference)和收到完整响应的结束时间(t_end_inference)。

计算逻辑: latency = t_end_inference - t_start_inference。

Prometheus指标: 推荐使用Summary或Histogram类型的指标。

Summary: 直接计算并汇总(count, sum, sum of squares)指定百分位(Quantiles)的延迟值,如P50, P90, P95, P99。

Histogram: 将延迟值划分到不同的桶(Buckets)中,例如 0-10ms, 10-50ms, 50-100ms, 100-200ms, >200ms 等。通过Histogram,我们可以精确计算任意百分位的延迟,以及总请求数(count)和请求的累加总耗时(sum)。Histogram在Prometheus中更常用,因为它可以通过histogram_quantile函数计算出近似的百分位值。

Prometheus暴露示例(Python + Prometheus Client):

<PYTHON>

from prometheus_client import Summary, Gauge, Counter, Histogram

import time

import numpy as np

# --- 指标定义 ---

# Histgram for inference latency (in seconds)

# buckets: 0.01s, 0.05s, 0.1s, 0.2s, 0.5s, 1.0s, 2.0s, 5.0s, 10.0s, +Inf

LATENCY_HISTOGRAM = Histogram(

'llm_inference_latency_seconds',

'Histogram of LLM inference latency',

['model_name', 'batch_size'], # Add labels for granularity

buckets=[0.01, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0, float('inf')]

)

# Gauge for current batch size

CURRENT_BATCH_SIZE = Gauge(

'llm_inference_batch_size',

'Current batch size for LLM inference',

['model_name']

)

# Counter for total requests processed

TOTAL_REQUESTS = Counter(

'llm_inference_total_requests',

'Total number of LLM inference requests processed',

['model_name', 'status'] # status could be 'success', 'error'

)

# --- 模拟推理函数 ---

def simulate_llm_inference(data_batch, model_name="example_llm"):

batch_size = len(data_batch)

CURRENT_BATCH_SIZE.labels(model_name=model_name).set(batch_size)

start_time = time.time()

try:

# --- 模拟模型核心计算 ---

# This part is highly dependent on the actual model framework (PyTorch, TensorFlow, etc.)

# and the underlying hardware (GPU).

# For demonstration, we simulate a variable latency based on batch size and data,

# and then record it.

# Simulate latency: base latency + length-dependent latency + batch_size dependency

# A real model's latency would depend on GPU utilization, sequence length, operations per token.

avg_seq_len = np.mean([len(item) for item in data_batch]) if data_batch else 0

simulated_delay = 0.05 + (avg_seq_len * 0.0001) + (batch_size * 0.002)

time.sleep(simulated_delay)

# --- end simulation ---

end_time = time.time()

latency = end_time - start_time

# Record latency in the histogram

LATENCY_HISTOGRAM.labels(model_name=model_name, batch_size=batch_size).observe(latency)

TOTAL_REQUESTS.labels(model_name=model_name, status='success').inc()

print(f"Inference successful. Batch size: {batch_size}, Latency: {latency:.4f}s")

return "response", latency # Return something to indicate completion

except Exception as e:

end_time = time.time()

latency = end_time - start_time # Include time spent before error

LATENCY_HISTOGRAM.labels(model_name=model_name, batch_size=batch_size).observe(latency)

TOTAL_REQUESTS.labels(model_name=model_name, status='error').inc()

print(f"Inference failed. Batch size: {batch_size}, Error: {e}")

raise # Re-raise the exception

# --- 模拟使用 ---

if __name__ == "__main__":

from prometheus_client import start_http_server

# Start up the server to expose the metrics.

start_http_server(8000)

print("Prometheus metrics server started on port 8000")

# Simulate incoming requests

while True:

# Simulate a batch of requests arriving

current_batch = [np.random.rand(np.random.randint(32, 128)) for _ in range(np.random.randint(1, 16))] # Simulate batch of variable size and seq length

if not current_batch: # If no requests generated

time.sleep(1)

continue

simulate_llm_inference(current_batch)

time.sleep(0.5) # Simulate arrival rate

b. 显存占用(GPU Memory Usage)

显存占用是决定模型能否运行、Batch Size大小以及处理长序列能力的关键。

目标: 实时跟踪GPU显存的使用情况,包括已分配(Used)、可用(Free)以及总容量(Total)。

单位: 兆字节(MB)或千兆字节(GB)。

采集方案:

GPU Exporter: Prometheus本身不直接访问GPU硬件。需要一个GPU Exporter来暴露GPU相关的指标。最常用的Exporter是 dcgm-exporter (NVIDIA Data Center GPU Manager Exporter) 或 nvidia-smi-exporter。这些Exporter通常会通过NVIDIA的驱动API(如NVML)来获取GPU信息。

关键指标:

gpu_memory_used_bytes:已使用的显存量。

gpu_memory_total_bytes:GPU总显存量。

gpu_utilization:GPU计算单元的利用率。

gpu_power_usage:GPU功耗。

gpu_temperature:GPU温度。

Prometheus暴露示例(使用dcgm-exporter等工具,这里仅展示概念):

dcgm-exporter 会暴露类似 DCGM_FI_DEV_FB_USED (Used FB Memory) 等指标。

如果直接使用nvidia-smi命令,可以通过nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits获取。Exporter会解析这些输出转化为Prometheus可识别的指标。

Prometheus指标示例:

gpu_memory_used_bytes{gpu="0", model="example_llm"}

gpu_memory_total_bytes{gpu="0"}

gpu_utilization{gpu="0", model="example_llm"}

2. 其他辅助监控指标

除了核心的延迟和显存,以下指标也非常重要:

GPU利用率(GPU Utilization): 衡量GPU计算单元的工作负载。过低指示算力未充分利用,过高则可能接近瓶颈,导致延迟升高。

吞吐量(Throughput):

Requests Per Second (RPS): sum(rate(llm_inference_total_requests{status="success"}[1m]))

Tokens Per Second (TPS): 如果能统计生成/处理的Token数,则更具参考意义。

Batch Size: 跟踪实际处理的Batch Size,可以帮助分析其与延迟和GPU利用率的关系。

序列长度统计: Batch中平均序列长度、最大序列长度、请求的序列长度分布。

QPS/TPS(Query Per Second / Token Per Second): 结合延迟信息,是评估模型吞吐量的最终指标。

模型加载时间/JIT编译时间: 对于动态加载或即时编译的模型,这可能是首次延迟高的原因。

四、 Grafana仪表盘设计

收集到数据后,我们需要一个直观的方式来展示它们。Grafana就是为此而生。

1. Dashboard的核心面板(Panels)

平均/P95/P99 推理延迟:

图表类型: Graph(折线图)

PromQL 查询(Histogram):

<PROMQL>

// P95 Latency for all models

histogram_quantile(0.95, sum by (model_name, batch_size) (rate(llm_inference_latency_seconds_bucket[5m])))

// P99 Latency for a specific model

histogram_quantile(0.99, sum by (batch_size) (rate(llm_inference_latency_seconds_bucket{model_name="example_llm"}[5m])))

// Average Latency (sum / count) - Use with caution, P95/P99 more robust

sum(rate(llm_inference_latency_seconds_sum[5m])) by (model_name) / sum(rate(llm_inference_latency_seconds_count[5m])) by (model_name)

考虑因素: 使用 rate(metric_bucket[5m]) 来计算每秒的桶增长率,然后通过 histogram_quantile 计算百分位数。在Graph面板中,可以按 model_name 或 batch_size 进行分组显示。

GPU显存占用:

图表类型: Graph(折线图)或 Status panel(显示当前读数)

PromQL 查询:

<PROMQL>

// Memory used by specific GPU

sum by (gpu) (avg_over_time(gpu_memory_used_bytes{gpu="0"}[5m])) / (1024*1024) // Convert bytes to GB

// Total GPU Memory

avg_over_time(gpu_memory_total_bytes{gpu="0"}[5m]) / (1024*1024)

// Memory utilization percentage

(

avg_over_time(gpu_memory_used_bytes{gpu="0"}[5m]) / avg_over_time(gpu_memory_total_bytes{gpu="0"})

) * 100

注: avg_over_time 用于获取近期平均值,[5m] 表示5分钟的窗口。

GPU利用率:

图表类型: Gauge(仪表盘)或 Graph(折线图)

PromQL 查询:

<PROMQL>

// GPU Utilization (%)

avg_over_time(gpu_utilization{gpu="0"}[5m])

吞吐量 (RPS):

图表类型: Graph(折线图)

PromQL 查询:

<PROMQL>

// Requests Per Second

sum by (model_name) (rate(llm_inference_total_requests{status="success"}[1m]))

Batch Size 趋势:

图表类型: Graph(折线图)

PromQL 查询:

<PROMQL>

// Average batch size per model

avg by (model_name) (llm_inference_batch_size)

GPU 详细信息:

图表类型: Table(表格)

PromQL 查询: 结合GPU利用率、显存占用、功耗、温度等指标,查看所有GPU的详细状态。

2. 仪表盘设计原则:

布局清晰: 将最核心的指标(延迟、吞吐量、显存)放在顶层,其他辅助指标放在下方。

分组展示: 如果有多个模型、多个GPU,合理使用Grafana的模板变量(Templating)和变量过滤器,允许用户选择特定的模型或GPU进行查看。

设置告警规则: 在Grafana中为关键指标(如P99延迟超过阈值、显存占用过高、GPU利用率过低)设置告警规则。

五、 部署架构与实践建议

1. 部署架构

典型的部署架构如下:

大模型应用服务: 运行着你的大模型推理代码(如使用uvicorn、gunicorn、TF Serving、TorchServe等)。在代码中集成 Prometheus Python Client,将指标暴露在HTTP服务上(通常是 /metrics 端点)。

Prometheus Server: 配置Prometheus,使其能够通过服务发现(如Kubernetes Service Discovery, Consul, DNS SRV records)自动找到你的模型服务实例。然后,Prometheus定期抓取(Scrape)这些实例暴露的 /metrics 端点。

GPU Exporter: 独立部署(如作为DaemonSet在Kubernetes节点上),将GPU指标暴露给Prometheus。

Grafana Server: 配置Grafana,添加Prometheus作为数据源。然后在Grafana中创建仪表盘,使用PromQL查询Prometheus中存储的指标数据。

2. 实践建议

自动化部署: 强烈建议使用Kubernetes等容器编排平台。Prometheus的Kubernetes Service Discovery可以自动发现部署的模型服务和GPU Exporter,省去手动配置的麻烦。

标准化指标命名: 使用一致的指标前缀(如 llm_)和标签(如 model_name, gpu, batch_size),便于查询和管理。

合理设置抓取间隔: Prometheus的抓取间隔(scrape_interval)影响了数据的实时性和Prometheus Server的负载。通常,15-30秒是比较折中的选择。

告警是关键: 不要只做可视化,务必配置关键指标的告警规则,并与告警工具(如Alertmanager)集成,确保问题能够及时通知到相关人员。

关注长尾延迟: P99延迟比平均延迟更能反映真实用户体验,尤其是对于高并发服务。

监控Batch Size与序列长度: 了解Batch处理的实际情况,有助于分析性能变化的原因。

CPU和网络监控: 除了GPU,CPU利用率(尤其是用于数据预处理、模型加载、Batch组装的)和网络带宽(GPU间通信、API请求/响应)也可能成为瓶颈,应一并监控。

六、 结语

大模型的上线只是开始,持续的监控和运维是确保其长期稳定、高效运行的基石。通过巧妙地结合Prometheus强大的指标采集和存储能力,以及Grafana出色的可视化和分析功能,我们可以构建一个全面、实时的监控体系,深入洞察大模型推理服务的健康状况,从容应对性能挑战,优化资源成本,最终提升用户满意度。掌握好这一套监控工具,将为你在大模型落地实践中保驾护航。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐