
传统业务监控只关注接口响应、CPU内存、网络状态,完全适配不了大模型推理场景。大模型服务监控是面向推理全链路的专属可观测体系,覆盖请求接入、文本分词、模型前向计算、Token 生成、结果返回、资源释放全部环节。
它不是简单的服务器资源查看,而是针对大模型GPU 依赖、Token生成、排队推理、显存波动独有特性设计的监控体系,专门解决普通监控无法识别的模型级故障与性能瓶颈。


推理耗时是衡量大模型服务响应能力的第一优先级指标,也是用户最直观能感受到的体验指标。分为首Token耗时TTFT和完整推理总耗时两个维度,不能只用单一总耗时做评判。

细分维度:
监控意义:
Token速率以Token/秒为单位,代表大模型文本生成的核心效率,是评估模型吞吐能力的关键指标。主要分为输入Token速率、输出Token速率、单请求平均 Token数、全局总Token吞吐量四类观测维度。

细分维度:
监控意义:
显存是大模型运行的生命线,几乎所有线上崩溃故障都和显存溢出、显存占用过高直接相关。监控显存不能只看整体使用率,需要拆分为模型权重固定显存、推理动态计算显存、瞬时峰值显存、显存碎片率。

细分维度:

监控意义:
大模型推理无法无限制并发,受限于 GPU 算力与显存上限,必然会产生请求排队机制,队列长度是并发治理的核心指标。重点观测实时当前队列数、历史队列峰值、单请求平均排队等待时长、队列溢出拒绝请求数量。

细分维度:
监控意义:
大模型服务的报错和普通接口报错差异极大,需要按层级分类统计,才能精准定位根因。整体划分为服务层错误、推理层错误、硬件资源层错误、业务规则层错误四大类。

细分维度:
监控意义:
健康巡检是大模型监控的兜底机制,以自动化定时任务方式,全方位扫描服务潜在隐患。巡检覆盖五大核心维度:进程存活状态、接口连通可用性、GPU与显存资源负载、性能指标基线、错误率基线。
细分维度:
监控意义:
完整大模型推理服务分为五层架构:接入层、队列调度层、推理计算层、硬件资源层、结果返回层:
监控系统会在每一层做埋点采集,逐层定位性能瓶颈与故障点位。
主流四种采集方式适配大模型场景,可单独使用也可组合部署。
指标从产生到应用形成闭环链路:指标采集→时序存储→聚合计算→可视化展示→分级告警→健康巡检复盘。


搭建专属大模型监控体系,是生产环境落地大模型业务的必备基础工程。
以下基于FastAPI搭建的ChatGLM3-6B大模型服务示例,内置全局监控数据管理、GPU显存自动采集、跨域支持,提供普通与流式多种对话接口,开放 /monitor监控指标接口,可实时统计推理耗时、TTFT、请求队列、错误量等核心指标,适配前端可视化监控,适合轻量级生产可观测落地场景,在应用上线初期我们通过基础监测来观察模型的运行情况,显卡采用RTX 4090参考。

导入运行大模型、接口服务、监控统计所需的所有库。
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware # 跨域
from transformers import AutoTokenizer, AutoModel # 模型加载
import torch
import uvicorn
import time
import subprocess # 读取GPU
from threading import Lock # 线程安全重点说明:
这是整个监控系统的核心,所有指标都存在这里。
class LLMMonitorData:
# 健康状态
service_health = 1
model_loaded = 1
# 耗时
last_ttft_seconds = 0.0
last_inference_seconds = 0.0
# Token
token_speed = 0.0
total_input_tokens = 0
total_output_tokens = 0
total_inference_count = 0
# GPU
gpu_mem_total_mb = 0
gpu_mem_used_mb = 0
gpu_mem_usage_pct = 0.0
# 队列 & 错误
request_queue_length = 0
error_total = 0
monitor = LLMMonitorData()主要用途:
核心价值:
允许浏览器直接访问/monitor接口,保障前端页面跨域请求接口!
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)核心价值:监控页面能实时拉取数据,不报错、不跨域。
通过调用系统命令nvidia-smi精准采集GPU实时显存数据,确保监控数值真实可靠。

def update_gpu_metrics(gpu_id=0):
try:
res = subprocess.check_output(
["nvidia-smi", f"--id={gpu_id}",
"--query-gpu=memory.total,memory.used",
"--format=csv,noheader,nounits"], encoding="utf-8"
)
total, used = map(int, res.strip().split(", "))
monitor.gpu_mem_total_mb = total
monitor.gpu_mem_used_mb = used
monitor.gpu_mem_usage_pct = round(used / total * 100, 2)
except:
pass核心用途:
作为整个前端可视化监控页面的唯一数据来源,承载全部大模型运行监控指标。
@app.get("/monitor")
def get_monitor():
update_gpu_metrics()
metrics = f"""llm_service_health {monitor.service_health}
llm_model_loaded {monitor.model_loaded}
llm_last_ttft_seconds {monitor.last_ttft_seconds:.4f}
llm_last_inference_seconds {monitor.last_inference_seconds:.4f}
llm_token_speed {monitor.token_speed:.2f}
llm_gpu_mem_usage_pct {monitor.gpu_mem_usage_pct}
llm_request_queue_length {monitor.request_queue_length}
llm_error_total {monitor.error_total}
llm_total_inference_count {monitor.total_inference_count}
"""
return metrics主要用途:
@app.post("/chat")
def chat(question: str):
if model is None:
monitor.error_total += 1
raise HTTPException(status_code=500, detail="模型未加载")
monitor.request_queue_length += 1
start_time = time.time()
try:
input_tokens = len(tokenizer.encode(question))
monitor.total_input_tokens += input_tokens
response, _ = model.chat(tokenizer, question, history=[])
output_tokens = len(tokenizer.encode(response))
monitor.total_output_tokens += output_tokens
# 耗时统计
cost = time.time() - start_time
monitor.last_inference_seconds = cost
monitor.last_ttft_seconds = cost * 0.35
monitor.token_speed = output_tokens / cost if cost > 0 else 0
monitor.total_inference_count += 1
return {"question": question, "answer": response}
except Exception as e:
monitor.error_total += 1
raise HTTPException(status_code=500, detail=f"推理出错: {str(e)}")
finally:
monitor.request_queue_length -= 1核心价值:
前端交互式聊天界面必须采用流式返回机制,才能实现好的感知交互效果。

async def generate_stream():
try:
first_record = False
out_cnt = 0
for resp, _ in model.stream_chat(tokenizer, prompt, history=[]):
if not first_record:
monitor.last_ttft_seconds = time.time() - start_time
first_record = True
out_cnt += 1
chunk = {"choices": [{"delta": {"content": resp}}]}
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
cost = time.time() - start_time
monitor.last_inference_seconds = cost
monitor.token_speed = out_cnt / cost if cost > 0 else 0
monitor.total_output_tokens += out_cnt
monitor.total_inference_count += 1
yield "data: [DONE]\n\n"
except Exception as e:
monitor.error_total += 1
yield f"data: {json.dumps({'error': str(e)})}\n\n"
finally:
monitor.request_queue_length -= 1
return StreamingResponse(generate_stream(), media_type="text/event-stream")重点说明:
这套基于FastAPI搭建的大模型服务监控方案,整体符合早期的落地需求,在模型应用最初我们调试监控使用了一个时段,应用不仅实现了ChatGLM3模型的正常调用,还集成了核心的监控能力,既不用依赖Prometheus、Grafana这类复杂中间件,轻量化就能搞定可观测需求。我们把模型服务、接口开发、指标统计、硬件监控做了模块化拆分,全局统一管理服务健康、推理耗时、TTFT、Token 速率、GPU显存、请求队列、错误统计等核心维度,每个接口请求都会自动埋点统计,还能通过nvidia-smi实时抓取显卡真实负载,数据真实又精准。同时支持普通响应和流式输出两种模式,流式接口更是前端聊天界面的标配,逐字推送能极大优化用户交互体验,也是行业生产环境的通用标准。
重点是考虑的早期的轻便,兼顾了功能性、实用性和轻量化。少了晦涩难懂的底层原理,我们可以先吃透接口分层、全局监控变量设计、跨域配置、流式响应这几个核心要点,弄懂每个监控指标的实际意义;其次可以慢慢拆解模块单独调试,慢慢掌握请求队列计数、耗时埋点、GPU指标采集的逻辑。后续还能在此基础上扩展告警、日志记录、限流排队功能,一步步根据实际需要优化成可直接上线的大模型服务架构。
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from transformers import AutoTokenizer, AutoModel, AutoConfig
import torch
import uvicorn
from modelscope import snapshot_download
import warnings
import json
import time
import subprocess
from threading import Lock
warnings.filterwarnings("ignore")
# ===================== 全局监控变量 =====================
class LLMMonitorData:
# 健康状态
service_health = 1
model_loaded = 1
# 耗时
last_ttft_seconds = 0.0
last_inference_seconds = 0.0
# Token
token_speed = 0.0
total_input_tokens = 0
total_output_tokens = 0
total_inference_count = 0
# GPU
gpu_mem_total_mb = 0
gpu_mem_used_mb = 0
gpu_mem_usage_pct = 0.0
# 队列 & 错误
request_queue_length = 0
error_total = 0
monitor = LLMMonitorData()
queue_lock = Lock()
# ===================== 原有模型配置不变 =====================
model = None
tokenizer = None
model_name = "ZhipuAI/chatglm3-6b"
cache_dir = "/home/model"
app = FastAPI(title="ChatGLM3-6B 带监控服务")
# 解决前端跨域
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 加载模型
print("正在下载/校验模型缓存...")
local_model_path = snapshot_download(model_name, cache_dir=cache_dir)
print(f"正在加载模型: {local_model_path}")
tokenizer = AutoTokenizer.from_pretrained(local_model_path, trust_remote_code=True)
config = AutoConfig.from_pretrained(local_model_path, trust_remote_code=True)
if not hasattr(config, 'max_length'):
config.max_length = config.seq_length if hasattr(config, 'seq_length') else 8192
try:
if not torch.cuda.is_available():
raise RuntimeError("未检测到GPU")
model = AutoModel.from_pretrained(
local_model_path,
config=config,
trust_remote_code=True,
low_cpu_mem_usage=True,
torch_dtype=torch.float16,
device_map="auto"
)
monitor.service_health = 1
monitor.model_loaded = 1
print("模型加载成功 ✅")
except Exception as e:
monitor.service_health = 0
monitor.model_loaded = 0
print(f"模型加载失败: {e}")
# ===================== 工具函数:更新GPU显存 =====================
def update_gpu_metrics(gpu_id=0):
try:
res = subprocess.check_output(
[
"nvidia-smi", f"--id={gpu_id}",
"--query-gpu=memory.total,memory.used",
"--format=csv,noheader,nounits"
], encoding="utf-8"
)
total, used = map(int, res.strip().split(", "))
monitor.gpu_mem_total_mb = total
monitor.gpu_mem_used_mb = used
monitor.gpu_mem_usage_pct = round(used / total * 100, 2)
except:
pass
# ===================== 新增:监控指标接口 /monitor =====================
@app.get("/monitor")
def get_monitor():
update_gpu_metrics()
# 输出纯文本键值,前端直接解析
metrics = f"""llm_service_health {monitor.service_health}
llm_model_loaded {monitor.model_loaded}
llm_last_ttft_seconds {monitor.last_ttft_seconds:.4f}
llm_last_inference_seconds {monitor.last_inference_seconds:.4f}
llm_token_speed {monitor.token_speed:.2f}
llm_gpu_mem_total_mb {monitor.gpu_mem_total_mb}
llm_gpu_mem_used_mb {monitor.gpu_mem_used_mb}
llm_gpu_mem_usage_pct {monitor.gpu_mem_usage_pct}
llm_request_queue_length {monitor.request_queue_length}
llm_error_total {monitor.error_total}
llm_total_input_tokens {monitor.total_input_tokens}
llm_total_output_tokens {monitor.total_output_tokens}
llm_total_inference_count {monitor.total_inference_count}
"""
return metrics
# 健康检查接口
@app.get("/health")
def health_check():
return {
"status": "healthy" if monitor.service_health else "unhealthy",
"model_loaded": monitor.model_loaded
}
# ===================== 原有业务接口不变,植入监控统计 =====================
@app.post("/chat")
def chat(question: str):
if model is None:
monitor.error_total += 1
raise HTTPException(status_code=500, detail="模型未加载")
monitor.request_queue_length += 1
start_time = time.time()
try:
input_tokens = len(tokenizer.encode(question))
monitor.total_input_tokens += input_tokens
response, _ = model.chat(tokenizer, question, history=[])
output_tokens = len(tokenizer.encode(response))
monitor.total_output_tokens += output_tokens
# 耗时统计
cost = time.time() - start_time
monitor.last_inference_seconds = cost
monitor.last_ttft_seconds = cost * 0.35 # 模拟首Token耗时
monitor.token_speed = output_tokens / cost if cost > 0 else 0
monitor.total_inference_count += 1
return {"question": question, "answer": response}
except Exception as e:
monitor.error_total += 1
raise HTTPException(status_code=500, detail=f"推理出错: {str(e)}")
finally:
monitor.request_queue_length -= 1
@app.post("/v1/chat/check")
def chat_check(payload: dict):
if model is None:
monitor.error_total += 1
raise HTTPException(status_code=500)
prompt = payload.get("prompt", "")
monitor.request_queue_length += 1
start_time = time.time()
try:
input_tokens = len(tokenizer.encode(prompt))
monitor.total_input_tokens += input_tokens
resp, _ = model.chat(tokenizer, prompt, history=[])
output_tokens = len(tokenizer.encode(resp))
monitor.total_output_tokens += output_tokens
cost = time.time() - start_time
monitor.last_inference_seconds = cost
monitor.last_ttft_seconds = cost * 0.35
monitor.token_speed = output_tokens / cost if cost > 0 else 0
monitor.total_inference_count += 1
return {"question": prompt, "answer": resp}
except:
monitor.error_total += 1
raise HTTPException(500)
finally:
monitor.request_queue_length -= 1
@app.post("/v1/chat/completions")
async def chat_completions(payload: dict):
if model is None:
monitor.error_total += 1
raise HTTPException(500)
prompt = payload.get("prompt", "")
is_stream = payload.get("stream", False)
if not prompt:
raise HTTPException(400)
monitor.request_queue_length += 1
start_time = time.time()
input_tokens = len(tokenizer.encode(prompt))
monitor.total_input_tokens += input_tokens
if not is_stream:
try:
resp, _ = model.chat(tokenizer, prompt, [])
output_tokens = len(tokenizer.encode(resp))
monitor.total_output_tokens += output_tokens
cost = time.time() - start_time
monitor.last_inference_seconds = cost
monitor.last_ttft_seconds = cost * 0.35
monitor.token_speed = output_tokens / cost if cost > 0 else 0
monitor.total_inference_count += 1
return {"choices": [{"message": resp}]}
except:
monitor.error_total += 1
raise
finally:
monitor.request_queue_length -= 1
async def generate_stream():
try:
first_record = False
out_cnt = 0
for resp, _ in model.stream_chat(tokenizer, prompt, history=[]):
if not first_record:
monitor.last_ttft_seconds = time.time() - start_time
first_record = True
out_cnt += 1
chunk = {"choices": [{"delta": {"content": resp}}]}
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
cost = time.time() - start_time
monitor.last_inference_seconds = cost
monitor.token_speed = out_cnt / cost if cost > 0 else 0
monitor.total_output_tokens += out_cnt
monitor.total_inference_count += 1
yield "data: [DONE]\n\n"
except Exception as e:
monitor.error_total += 1
yield f"data: {json.dumps({'error': str(e)})}\n\n"
finally:
monitor.request_queue_length -= 1
return StreamingResponse(generate_stream(), media_type="text/event-stream")
if __name__ == "__main__":
print("✅ 服务启动,自带 /monitor 监控接口")
print("📊 监控地址:http://0.0.0.0:8000/monitor")
uvicorn.run(app, host="0.0.0.0", port=8000)<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>大模型服务监控中心</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft Yahei", sans-serif;
}
body {
background: #f5f7fa;
color: #333;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 10px;
padding: 5px 0;
border-bottom: 1px solid #e2e8f0;
}
.header h1 {
font-size: 26px;
color: #165DFF;
letter-spacing: 1px;
}
.header p {
color: #666;
margin-top: 6px;
}
/* 单行指标卡片 一排布局 */
.card-wrap {
background: #fff;
border-radius: 12px;
padding: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
margin-bottom: 10px;
}
.card-container {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 10px;
}
.monitor-card {
text-align: center;
padding: 10px;
border-radius: 8px;
background: #f7f8fa;
}
.card-title {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.card-value {
font-size: 22px;
font-weight: bold;
color: #1d2129;
}
.card-unit {
font-size: 13px;
color: #999;
margin-left: 4px;
}
/* 图表容器 */
.chart-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.chart-box {
background: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
height: 300px;
}
/* 状态颜色 */
.status-normal {
color: #00b42a;
}
.status-warning {
color: #ff7d00;
}
.status-error {
color: #f53f3f;
}
/* 响应式 小屏幕自动换行 */
@media (max-width: 1200px) {
.card-container {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 768px) {
.card-container {
grid-template-columns: repeat(2, 1fr);
}
.chart-container {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="header">
<h1>大模型服务全维度监控中心</h1>
<p>实时对接服务接口 · 推理性能 · GPU资源 · 队列状态 · 错误统计</p>
</div>
<!-- 单行合并所有指标 -->
<div class="card-wrap">
<div class="card-container">
<div class="monitor-card">
<div class="card-title">服务健康状态</div>
<div class="card-value status-normal" id="healthVal">正常</div>
</div>
<div class="monitor-card">
<div class="card-title">首Token耗时</div>
<div class="card-value" id="ttftVal">0.00<span class="card-unit">s</span></div>
</div>
<div class="monitor-card">
<div class="card-title">推理总耗时</div>
<div class="card-value" id="inferVal">0.00<span class="card-unit">s</span></div>
</div>
<div class="monitor-card">
<div class="card-title">Token速率</div>
<div class="card-value" id="tokenSpeedVal">0.0<span class="card-unit">Token/s</span></div>
</div>
<div class="monitor-card">
<div class="card-title">显存使用率</div>
<div class="card-value" id="vramUsageVal">0.0<span class="card-unit">%</span></div>
</div>
<div class="monitor-card">
<div class="card-title">请求队列</div>
<div class="card-value" id="queueVal">0<span class="card-unit">个</span></div>
</div>
<div class="monitor-card">
<div class="card-title">累计错误</div>
<div class="card-value status-error" id="errorVal">0<span class="card-unit">次</span></div>
</div>
<div class="monitor-card">
<div class="card-title">累计请求</div>
<div class="card-value" id="totalReqVal">0<span class="card-unit">次</span></div>
</div>
<!-- <div class="monitor-card">
<div class="card-title">模型版本</div>
<div class="card-value" style="font-size:16px">V2.1<span class="card-unit">.0</span></div>
</div> -->
</div>
</div>
<!-- 图表区域 -->
<div class="chart-container">
<div class="chart-box">
<canvas id="timeChart"></canvas>
</div>
<div class="chart-box">
<canvas id="vramChart"></canvas>
</div>
<div class="chart-box">
<canvas id="queueChart"></canvas>
</div>
<div class="chart-box">
<canvas id="tokenChart"></canvas>
</div>
</div>
<script>
// ========== 配置你的后端接口地址 ==========
const API_URL = "http://192.168.3.6:8000/monitor";
// ========================================
// 图表时间标签
const timeLabels = [];
for(let i=30; i>=0; i--) {
timeLabels.push(i+"s");
}
// 图表数据集
let ttftData = new Array(31).fill(0);
let inferData = new Array(31).fill(0);
let vramData = new Array(31).fill(0);
let queueData = new Array(31).fill(0);
let tokenData = new Array(31).fill(0);
let lastTotalReq = 0; // 上一次累计请求数
// 1. 推理耗时图表
const timeCtx = document.getElementById('timeChart').getContext('2d');
const timeChart = new Chart(timeCtx, {
type: 'line',
data: {
labels: timeLabels,
datasets: [
{
label: '首Token耗时(s)',
data: ttftData,
borderColor: '#165DFF',
backgroundColor: 'rgba(22,93,255,0.1)',
tension: 0.4,
fill: true
},
{
label: '推理总耗时(s)',
data: inferData,
borderColor: '#00b42a',
backgroundColor: 'rgba(0,180,42,0.1)',
tension: 0.4,
fill: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#333' } } },
scales: {
x: { ticks: { color: '#666' }, grid: { color: '#eee' } },
y: { ticks: { color: '#666' }, grid: { color: '#eee' } }
}
}
});
// 2. GPU显存图表
const vramCtx = document.getElementById('vramChart').getContext('2d');
const vramChart = new Chart(vramCtx, {
type: 'line',
data: {
labels: timeLabels,
datasets: [{
label: '显存使用率(%)',
data: vramData,
borderColor: '#ff7d00',
backgroundColor: 'rgba(255,125,0,0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#333' } } },
scales: {
x: { ticks: { color: '#666' }, grid: { color: '#eee' } },
y: { ticks: { color: '#666' }, grid: { color: '#eee' } }
}
}
});
// 3. 请求队列图表
const queueCtx = document.getElementById('queueChart').getContext('2d');
const queueChart = new Chart(queueCtx, {
type: 'bar',
data: {
labels: timeLabels,
datasets: [{
label: '排队请求数',
data: queueData,
backgroundColor: '#722ED1',
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#333' } } },
scales: {
x: { ticks: { color: '#666' }, grid: { color: '#eee' } },
y: { ticks: { color: '#666' }, grid: { color: '#eee' } }
}
}
});
// 4. Token速率图表
const tokenCtx = document.getElementById('tokenChart').getContext('2d');
const tokenChart = new Chart(tokenCtx, {
type: 'line',
data: {
labels: timeLabels,
datasets: [{
label: 'Token速率(Token/s)',
data: tokenData,
borderColor: '#f53f3f',
backgroundColor: 'rgba(245,63,63,0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#333' } } },
scales: {
x: { ticks: { color: '#666' }, grid: { color: '#eee' } },
y: { ticks: { color: '#666' }, grid: { color: '#eee' } }
}
}
});
// 拉取真实接口数据
async function fetchMonitorData() {
try {
const res = await fetch(API_URL);
const text = await res.text();
// 解析Prometheus格式简单键值
function getMetric(key) {
let reg = new RegExp(key + "\\s+([\\d.]+)");
let match = text.match(reg);
return match ? parseFloat(match[1]) : 0;
}
// 读取真实指标
const health = getMetric("llm_service_health");
const ttft = getMetric("llm_last_ttft_seconds");
const infer = getMetric("llm_last_inference_seconds");
const tokenSpeed = getMetric("llm_token_speed");
const vramUsage = getMetric("llm_gpu_mem_usage_pct");
const queue = getMetric("llm_request_queue_length");
const error = getMetric("llm_error_total");
const totalReq = getMetric("llm_total_inference_count");
const queueWithRandom = queue;
const totalReqWithIncrement = lastTotalReq === 0
? totalReq : lastTotalReq;
lastTotalReq = totalReqWithIncrement;
// 更新卡片数值(保留单位)
document.getElementById("healthVal").innerText = health === 1 ? "正常" : "异常";
document.getElementById("ttftVal").innerHTML = ttft.toFixed(2) + "<span class=\"card-unit\">s</span>";
document.getElementById("inferVal").innerHTML = infer.toFixed(2) + "<span class=\"card-unit\">s</span>";
document.getElementById("tokenSpeedVal").innerHTML = tokenSpeed.toFixed(1) + "<span class=\"card-unit\">Token/s</span>";
document.getElementById("vramUsageVal").innerHTML = vramUsage.toFixed(1) + "<span class=\"card-unit\">%</span>";
document.getElementById("queueVal").innerHTML = queueWithRandom + "<span class=\"card-unit\">个</span>";
document.getElementById("errorVal").innerHTML = Math.floor(error) + "<span class=\"card-unit\">次</span>";
document.getElementById("totalReqVal").innerHTML = Math.floor(totalReqWithIncrement) + "<span class=\"card-unit\">次</span>";
// 滚动图表数据
ttftData.shift(); ttftData.push(ttft);
inferData.shift(); inferData.push(infer);
vramData.shift(); vramData.push(vramUsage);
queueData.shift(); queueData.push(queueWithRandom);
tokenData.shift(); tokenData.push(tokenSpeed);
// 刷新图表
timeChart.update();
vramChart.update();
queueChart.update();
tokenChart.update();
} catch (e) {
console.log("接口拉取失败,请检查服务是否启动、跨域是否放行");
}
}
// 每2秒刷新一次
setInterval(fetchMonitorData, 2000);
// 首次立即执行
fetchMonitorData();
</script>
</body>
</html>原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。