监控与可观测
这一课你将能做到
- 给 FastAPI 推理服务打 Prometheus 指标:QPS、延迟直方图、预测分布计数
- 用 Docker 拉起 Prometheus + Grafana,建一个能看到 p99 延迟的面板
- 用 Evidently 对比训练数据与线上近期数据,生成数据漂移报告并读懂 PSI/KS 结论
为什么要学
模型上线后有三个「悄悄变坏」的方式:服务变慢(系统层)、预测分布漂移(数据层)、业务指标下滑(效果层)。第 8 课的探针只能告诉你容器活着,不能告诉你模型判得还准不准——而模型劣化往往在用户抱怨之前已持续数周。
监控就是给模型装仪表盘和预警器。人话:后端监控看「服务死没死」,ML 监控还要看「模型的输入世界变了没有」。本站判题超时模型的典型事故是:站点上线了 Rust 语言支持,提交特征分布突变,模型对所有 Rust 提交给出离谱预测——没有漂移监控,这件事要等运营报障才发现。
先建立图像
三层监控体系:
① 系统层(Prometheus/Grafana):QPS、p99 延迟、错误率、容器资源
→ 回答「服务还能不能用」
② 数据层(Evidently):特征分布 vs 训练基线的漂移
→ 回答「输入世界变了没有」
③ 效果层(业务指标 + 延迟标签回填):线上准确率/F1、超时拦截率
→ 回答「模型判得还准不准」
闭环:③或②报警 → 第 6 课编排器触发重训 → 第 5 课门禁 → 晋升
跟着做
步骤 1:给推理服务打指标
pip install prometheus-client evidently
# src/serve.py(在原服务上叠加指标)
import time
import joblib
import pandas as pd
from fastapi import FastAPI, Request
from prometheus_client import Counter, Histogram, make_asgi_app
app = FastAPI(title="判题超时预测服务")
model = joblib.load("models/timeout-v1.joblib")
PREDICTS = Counter(
"model_predicts_total", "预测次数",
["model", "bucket"]) # bucket: low/mid/high 概率分段
LATENCY = Histogram(
"model_predict_latency_seconds", "预测耗时",
buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5))
@app.post("/predict")
def predict(code_lines: int, loop_depth: int,
lang_java: int, problem_rank: int):
start = time.perf_counter()
X = pd.DataFrame([dict(code_lines=code_lines, loop_depth=loop_depth,
lang_java=lang_java, problem_rank=problem_rank)])
proba = float(model.predict_proba(X)[0, 1])
LATENCY.observe(time.perf_counter() - start)
bucket = "high" if proba > 0.8 else "mid" if proba > 0.3 else "low"
PREDICTS.labels(model="timeout-v1", bucket=bucket).inc()
# 生产环境:此处把 X + proba 异步写入预测日志表,供效果回填与漂移检测
return {"timeout_probability": round(proba, 4)}
app.mount("/metrics", make_asgi_app()) # 暴露 /metrics 给 Prometheus
步骤 2:Prometheus 抓取 + Grafana 面板
# prometheus.yml
global: { scrape_interval: 15s }
scrape_configs:
- job_name: timeout-api
static_configs:
- targets: ["host.docker.internal:8000"] # Linux 用宿主机 IP
docker run -d --name prom -p 9090:9090 \
-v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
docker run -d --name grafana -p 3000:3000 grafana/grafana
# Grafana(localhost:3000, admin/admin) 添加数据源 http://prom:9090 后建面板:
# p99 延迟: histogram_quantile(0.99, rate(model_predict_latency_seconds_bucket[5m]))
# QPS: rate(model_predicts_total[5m])
压测几下接口,回 Grafana 看曲线动起来。PromQL 里 rate(...[5m]) 是「5 分钟窗口增速」,延迟分位必须对 _bucket 直方图算,均值会掩盖长尾。
步骤 3:Evidently 漂移报告
# src/drift_report.py —— 每日对比训练基线与近期数据
import pandas as pd
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset
train_df = pd.read_parquet("data/processed/judge_clean.parquet") # 基线
today_df = train_df.sample(1000, random_state=7) # 演示:近期数据
report = Report(metrics=[DataDriftPreset()])
report.run(reference_data=train_df, current_data=today_df)
report.save_html("drift.html")
report.save_json("drift.json")
result = report.as_dict()
drifted = [c["column"] for c in result["metrics"][1]["result"]["drift_by_columns"].values()
if c["drift_detected"]]
print("漂移特征:", drifted or "无")
# 集成方式:编排器每日跑本脚本,drifted 非空则告警/触发重训
python src/drift_report.py
# 打开 drift.html:每个特征一张分布对比图,附 PSI/KS 统计量与是否漂移的结论
讲清楚要点
- 监控要分层设 SLO:系统层定义可用性与 p99 延迟(如「99% 请求 < 100ms」),数据层定义漂移阈值,效果层定义业务底线(如「超时拦截率 < 70% 告警」)。没有 SLO 的监控只是录像回放。
- 数据漂移 ≠ 概念漂移:数据漂移是 P(X) 变了(新增语言、题目类型变化),概念漂移是 P(y|X) 变了(判题机升级、超时规则调整)。前者看特征分布(Evidently),后者必须看真实标签回填后的效果。
- 预测留痕是效果监控的前提:每次请求把「特征 + 预测 + 模型版本」写入日志表,真实结果出来后回填标签。没有留痕,线上准确率永远算不出来。
- 标签延迟要设计:超时结果几秒内可得,可以实时回填;「用户是否流失」要等 30 天,只能用代理指标(点击率)+ 延迟评估。设计管道时先画清楚每个特征/标签的时间轴。
- 告警必须连 runbook:每条告警写明「现象 → 排查三步 → 处置动作 → 升级路径」。报警器没人会处理,等于半夜装了个只会响不会救火的烟感。
你可能会懵的地方
- 没有真实标签时怎么知道模型还准不准? 三招:代理指标(业务转化、人工抽检准确率);延迟标签回填(结果出来再算一次历史准确率);输入漂移作先行指标(漂移不一定掉点,但掉点几乎都伴随漂移或概念变化)。
- 检测到漂移就该立刻重训吗? 不。先分级:轻微漂移(PSI < 0.1)观察即可;持续中度漂移触发 challenger 重训对比;效果确实下降才晋升。盲目重训会引入「没坏先修」的新风险,且掩盖概念漂移的真因。
- PSI 和 KS 怎么选? PSI(群体稳定性指数)输出单一数值、对分箱敏感,适合日度看板(<0.1 稳定 / 0.1~0.25 关注 / >0.25 显著漂移);KS 是分布距离的非参检验,小样本更稳。两者都给「变化了吗」,给不了「变化好不好」——好坏要连到效果层。
- Prometheus 标签能不能放 user_id? 不能。标签基数爆炸(每用户一个时间序列)会打爆时序数据库。模型版本、概率分段这类低基数值可以,user_id 只进日志/明细表,不进指标。
- 什么时候用 Pushgateway? 批量任务(第 9 课的 batch_score)进程短命、抓取可能错过,把指标主动 push 给网关再被 Prometheus 拉取。常驻服务一律用标准 /metrics 抓取,不要滥用网关。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 只监控 CPU/内存/错误率 | 模型劣化完全不可见 | 三层齐备:系统 + 数据漂移 + 效果 |
| 延迟监控只看平均值 | 长尾卡顿被平均掉 | p95/p99 分位 + 直方图 |
| 每条请求全量写详细日志 | 存储成本与 PII 风险双高 | 采样落盘 + 敏感字段脱敏 |
| 指标标签带 user_id | 时间序列基数爆炸 | 低基数标签(版本/分段),明细进日志 |
| 报警发了没人知道怎么处理 | 疲劳后告警被静音 | 每条告警绑 runbook,定期演练 |
卡点急救
- Grafana 面板无数据:先确认 Prometheus Targets 页目标是 UP;数据源地址要写容器网络可达地址,不是 localhost。
- histogram_quantile 结果恒为 0:算分位必须用
_bucket后缀的直方图指标,且查询窗口内要有新增样本(先压测产生流量)。 - Evidently 报列类型不匹配:基线与当前数据的列类型/枚举必须一致,先过第 3 课的 schema 校验再进漂移检测。
面试常问
- 设计一个线上模型的监控体系。 三层:系统层 Prometheus/Grafana(QPS、p99、错误率,定 SLO);数据层 Evidently 日度对比训练基线(PSI/KS 分特征报警);效果层预测留痕 + 标签回填计算线上 F1 与业务指标。漂移与效果双门禁接入重训闭环,所有告警配 runbook。
- 数据漂移和概念漂移的区别与检测方法? 数据漂移是输入分布 P(X) 变化,用 PSI/KS 对比特征分布即可发现;概念漂移是输入到输出的映射 P(y|X) 变化,必须依赖真实标签回填后的效果对比(或按新标签重训对比历史样本的预测差异)。工程上前者是先行指标,后者是最终裁决。
- 为什么延迟监控要看 p99 而不是平均值? 用户体感由最慢请求决定;平均会把长尾摊平——99% 的 10ms 和 1% 的 5s 平均后只有 60ms,看似健康实则每百次请求就有一个超时。分位数 + 直方图才能定位长尾来源。
自测三问
- 三层监控各自回答什么问题?分别用什么工具?
- 预测留痕表需要哪些字段?为什么它是效果监控的前提?
- PSI 0.3 意味着什么?接下来该做什么、不该立刻做什么?
小结一句话
模型监控 = 系统层保可用(Prometheus p99/SLO)+ 数据层报漂移(Evidently PSI/KS)+ 效果层裁决策(预测留痕与标签回填),三层接进重训闭环才算完整。
延伸
- Prometheus Python 客户端——四种指标类型与 ASGI 集成的官方说明。
- Evidently 官方文档——漂移预设、自定义指标与 CI 集成。
- 下一课:可解释 AI——用 SHAP/LIME 回答「模型为什么这么判」,支撑合规与业务沟通。