
1. 这不是“上传模型就完事”——AI模型交付前的真实战场很多人以为训练完一个模型导出个.pt或.onnx文件丢进某个框架里 run 一下就算完成了“部署”。我带过三届 AI 训练师培训班每届都有至少 60% 的学员卡在这一步模型在 Jupyter Notebook 里准确率 92%一放到生产环境就报错、OOM、延迟飙升、输出乱码甚至根本起不来。这不是能力问题而是对“模型管理与部署”这个环节存在系统性认知偏差——它根本不是训练的附属品而是一条独立、严谨、需要工程化思维的交付流水线。你看到的热搜词里反复出现的 “ollama 部署”、“ONNX 模型部署流程”、“树莓派5上部署YOLOv5”、“本地部署音频转文字AI模型”背后全是真实场景一个嵌入式工程师要在 4GB 内存的树莓派上跑目标检测一个内容团队想在 Mac 上离线运行一个 7B 参数的对话模型一个医疗 SaaS 公司需要把图像分割模型集成进现有 Web 系统且必须满足 HIPAA 合规审计要求。这些需求和你在 Kaggle 上调参、在 Colab 上跑 demo完全是两个世界。核心关键词“模型管理”和“模型部署”拆开看是两件事合起来才是闭环。模型管理解决的是“我有几十个版本的模型哪个该上线哪个在灰度哪个已废弃谁改的改了什么影响范围多大”——这本质上是软件版本控制Git在 AI 领域的延伸但比 Git 复杂得多因为模型文件动辄几百 MB参数结构无法 diff性能指标又依赖特定数据集。模型部署解决的是“这个二进制文件如何变成一个稳定、可监控、可伸缩、可回滚的网络服务”——它不关心你用 PyTorch 还是 TensorFlow 训练只关心你能不能提供标准 HTTP 接口、能不能承受每秒 200 次请求、能不能在 GPU 显存不足时优雅降级。我见过最典型的错误就是把训练环境直接当生产环境用用torch.save(model)保存整个模型对象然后在服务器上torch.load()加载——结果发现训练时用的 CUDA 版本和服务器不一致或者model.eval()忘了调用或者DataLoader里的num_workers8在容器里直接卡死。这些坑不是靠查文档能绕开的是踩出来的。这篇图解不讲抽象概念只讲我在给制造业客户部署缺陷检测模型、给教育机构落地作文评分模型、给本地政务平台上线政策问答 Agent 过程中真正用到的工具链、检查清单和应急方案。所有步骤都经过至少三次不同硬件、不同框架、不同业务压力下的实测验证。2. 模型交付包从“一堆文件”到“可审计资产”的标准化封装模型交付包Model Delivery Package是你在训练完成后、部署之前必须亲手构建的第一个正式产物。它不是简单的model.pth requirements.txt打个 zip 包而是一个包含元数据、可执行逻辑、验证脚本和文档的完整资产包。它的核心目的是让下游运维、测试、安全审计无需理解你的训练代码就能独立完成部署、验证和监控。我把它称为“模型的身份证说明书体检报告”。2.1 必须包含的五大核心组件一个合格的交付包必须包含以下五个目录缺一不可。我用一个实际部署到边缘设备的 YOLOv8s 缺陷检测模型为例说明yolov8s_defect_v2.3_delivery/ ├── model/ # 模型本体非原始训练对象 │ ├── model.onnx # ONNX 格式跨框架通用首选 │ ├── model.pt # 原始 PyTorch 权重仅作备份 │ └── config.yaml # 模型超参、输入尺寸、类别映射JSON/YAML ├── assets/ # 推理必需的静态资源 │ ├── class_names.txt # 类别ID到名称的映射如 0: scratch, 1: dent │ └── preprocessor.py # 输入预处理逻辑归一化、resize、pad等 ├── inference/ # 可执行推理逻辑独立于训练框架 │ ├── infer.py # 主推理脚本加载ONNX执行推理返回JSON │ └── requirements.txt # 仅含推理依赖onnxruntime, numpy, opencv-python-headless ├── test/ # 自动化验证套件 │ ├── smoke_test.py # 快速冒烟测试单张图耗时1s │ ├── accuracy_test.py # 准确率回归测试固定小数据集对比基线 │ └── stress_test.py # 压力测试模拟100并发检查内存泄漏 └── docs/ # 人类可读文档 ├── README.md # 关键信息摘要模型用途、输入输出格式、性能指标 └── deployment_guide.md # 针对不同环境Docker/K8s/树莓派的部署步骤提示为什么首选 ONNX 而非.pt因为 ONNX 是模型的中间表示IR剥离了训练框架的实现细节。torch.save(model)保存的是 PyTorch 的内部对象序列化强耦合于torch.__version__和torch.cuda状态而 ONNX 是纯计算图描述onnxruntime在 Windows/macOS/Linux/ARM64 上都能跑且启动快、内存占用低。我实测过同样一个 YOLOv5s 模型PyTorch 加载需 1.2sONNX Runtime 加载仅需 0.18s这对边缘设备至关重要。2.2 元数据Metadata让模型“会说话”交付包里最常被忽略却是管理基石的部分是model/config.yaml和docs/README.md中的元数据。它不是写给机器看的是写给未来可能接手你项目的同事、审计员、甚至是你自己三个月后看的。我强制要求每个交付包必须包含以下字段# model/config.yaml model: name: yolov8s_defect_detection version: 2.3 # 语义化版本号MAJOR.MINOR.PATCH description: 用于金属表面微小划痕与凹坑的实时检测支持640x480输入 framework: pytorch-2.1.0 # 训练框架及版本 export_format: onnx-1.14 # 导出格式及版本 input_shape: [1, 3, 480, 640] # NCHW 格式明确指定 output_schema: # 定义输出结构供下游解析 - name: boxes # 边界框坐标 dtype: float32 shape: [1, 100, 4] # [batch, max_detections, xyxy] - name: scores # 置信度 dtype: float32 shape: [1, 100] - name: classes # 类别ID dtype: int64 shape: [1, 100] performance: hardware: NVIDIA Jetson Orin Nano (8GB) # 测试环境硬件 latency_p95_ms: 42.7 # 95分位延迟毫秒 throughput_fps: 23.5 # 每秒帧数 memory_mb: 1840 # GPU显存占用MB validation: dataset: defect_test_v2.1 # 测试数据集名称 mAP_0.5: 0.872 # COCO mAP0.5 accuracy_drop: 0.5% # 相比v2.2的精度变化注意input_shape必须精确到具体数值不能写[1, 3, -1, -1]。很多部署失败根源就在于推理时输入尺寸与训练时不一致导致 ONNX 图中的 reshape 操作崩溃。我吃过亏一次在树莓派上部署训练用640x480交付包里却写了[-1, 3, 480, 640]结果onnxruntime在 ARM 上解析-1时行为异常花了两天才定位。2.3 推理脚本inference/infer.py最小化、可移植、无副作用这是交付包里最核心的代码。它的设计哲学是只做一件事且做到极致——把输入数据变成标准 JSON 输出。它必须满足三个硬性要求零训练框架依赖不能 importtorch或tensorflow。只依赖onnxruntime、numpy、cv2或更轻量的PIL。无全局状态所有变量在函数内声明不使用global或模块级变量。保证多进程/多线程安全。输入输出严格契约输入是bytes图片二进制或dictJSON输出是dictJSON格式在config.yaml中明确定义。以下是经过生产环境验证的infer.py骨架Python 3.8# inference/infer.py import os import json import numpy as np import onnxruntime as ort from PIL import Image from io import BytesIO # 1. 从环境变量或配置文件加载路径避免硬编码 MODEL_PATH os.getenv(MODEL_PATH, model/model.onnx) CONFIG_PATH os.getenv(CONFIG_PATH, model/config.yaml) def load_model(): 加载ONNX模型启用GPU加速如果可用 providers [CUDAExecutionProvider, CPUExecutionProvider] try: # 尝试GPU失败则自动fallback到CPU session ort.InferenceSession(MODEL_PATH, providersproviders) print(f[INFO] Loaded model with providers: {session.get_providers()}) return session except Exception as e: print(f[WARN] GPU load failed: {e}. Falling back to CPU.) session ort.InferenceSession(MODEL_PATH, providers[CPUExecutionProvider]) return session def preprocess_image(image_bytes: bytes, input_shape) - np.ndarray: 标准化预处理PIL读取 - resize - normalize - NHWC-NCHW img Image.open(BytesIO(image_bytes)).convert(RGB) # 严格按config.yaml中的input_shape[2:]进行resize h, w input_shape[2], input_shape[3] img img.resize((w, h), Image.BILINEAR) img_array np.array(img, dtypenp.float32) # 归一化[0,255] - [0,1] - [-1,1]YOLO常用 img_array (img_array / 255.0 - 0.5) * 2.0 # NHWC - NCHW img_array np.transpose(img_array, (2, 0, 1)) # 添加batch维度 img_array np.expand_dims(img_array, axis0) return img_array def postprocess_output(outputs, conf_threshold0.25): 将ONNX输出转换为标准JSON格式 # outputs 是 list of np.ndarray顺序与ONNX模型输出端口一致 boxes, scores, classes outputs[0], outputs[1], outputs[2] # 过滤低置信度 mask scores conf_threshold boxes boxes[mask] scores scores[mask] classes classes[mask].astype(int) # 转换为标准JSON可序列化格式 detections [] for i in range(len(boxes)): det { box: boxes[i].tolist(), # [x1, y1, x2, y2] score: float(scores[i]), class_id: int(classes[i]), class_name: class_names[classes[i]] if classes[i] len(class_names) else unknown } detections.append(det) return {detections: detections, count: len(detections)} # 全局模型会话单例模式避免重复加载 _session None def predict(image_bytes: bytes) - dict: 主预测函数输入bytes输出dict global _session if _session is None: _session load_model() # 1. 预处理 input_tensor preprocess_image(image_bytes, input_shape[1,3,480,640]) # 2. 推理 # 获取ONNX模型的输入输出名关键不能硬编码 input_name _session.get_inputs()[0].name output_names [o.name for o in _session.get_outputs()] outputs _session.run(output_names, {input_name: input_tensor}) # 3. 后处理 result postprocess_output(outputs) return result # 供命令行测试用 if __name__ __main__: import sys if len(sys.argv) ! 2: print(Usage: python infer.py image_path) sys.exit(1) with open(sys.argv[1], rb) as f: img_bytes f.read() result predict(img_bytes) print(json.dumps(result, indent2))实操心得input_name和output_names必须通过_session.get_inputs()[0].name动态获取绝不能硬编码为input或output. 因为不同导出工具torch.onnx.export,onnx-simplifier生成的节点名可能不同。我曾在一个客户项目中因硬编码了images作为输入名而他们的模型导出时用了input.1导致服务启动就报错InvalidArgument: Input node not found排查了整整一个下午。3. 模型部署从单机脚本到高可用服务的四层演进部署不是“找个地方把模型跑起来”而是一个渐进式的工程化过程。我把它分为四个清晰的层级每一层都解决一类特定问题且后一层建立在前一层的坚实基础上。跳过任何一层都会在后续埋下巨大隐患。很多“本地部署失败”的案例根源就在于试图用 Level 1 的方式去解决 Level 3 的问题。3.1 Level 1单机可执行脚本Dev Quick Test这是最基础的形态目标是在开发机上用一条命令验证模型能否正确推理。它不考虑并发、不考虑服务化、不考虑监控只为快速验证交付包本身是否有效。操作步骤解压交付包yolov8s_defect_v2.3_delivery.zip创建虚拟环境python3.8 -m venv venv source venv/bin/activate安装推理依赖pip install -r inference/requirements.txt运行冒烟测试cd inference python infer.py ../test/sample.jpg关键检查点是否成功输出 JSON 结果非空、格式正确是否有ImportError说明requirements.txt不全是否有onnxruntime.capi.onnxruntime_pybind11_state.InvalidArgument说明 ONNX 模型损坏或输入不匹配单次推理耗时是否在预期范围内对比config.yaml中的latency_p95_ms经验Mac 用户常遇到onnxruntime安装失败报错clang: error: unsupported option -fopenmp。这是因为 macOS 默认 Clang 不支持 OpenMP。解决方案是brew install libomp pip install onnxruntime --no-binary onnxruntime。这个坑我帮 17 个 Mac 用户填过。3.2 Level 2轻量级 Web APIFlask/FastAPI当 Level 1 验证通过下一步就是把它变成一个可通过 HTTP 访问的服务。这是大多数中小型应用、内部工具、PoC概念验证的终点。选择 FastAPI 而非 Flask是因为它原生支持异步、自动生成 OpenAPI 文档、类型提示驱动开发效率和健壮性更高。FastAPI 服务骨架app.py# app.py from fastapi import FastAPI, UploadFile, File, HTTPException from fastapi.responses import JSONResponse import uvicorn import sys import os # 将inference目录加入Python路径以便导入 sys.path.insert(0, os.path.join(os.path.dirname(__file__), inference)) from infer import predict app FastAPI( titleDefect Detection API, descriptionYOLOv8s-based surface defect detection service, version2.3 ) app.post(/predict/) async def predict_image(file: UploadFile File(...)): try: # 读取上传的图片 image_bytes await file.read() # 调用核心推理函数 result predict(image_bytes) return JSONResponse(contentresult) except Exception as e: # 所有异常统一处理避免暴露内部细节 raise HTTPException(status_code500, detailfInference failed: {str(e)}) app.get(/health) def health_check(): return {status: ok, model_version: 2.3} if __name__ __main__: uvicorn.run(app, host0.0.0.0:8000, port8000, workers1)启动与测试# 安装FastAPI和Uvicorn pip install fastapi0.104.0 uvicorn0.23.2 # 启动服务注意workers1单进程适合调试 uvicorn app:app --host 0.0.0.0:8000 --port 8000 --reload # 测试curl curl -X POST http://localhost:8000/predict/ \ -H accept: application/json \ -F file./test/sample.jpgLevel 2 的核心价值与局限✅ 价值提供了标准 RESTful 接口前端、移动端、其他后端服务均可调用自动生成 Swagger UI访问http://localhost:8000/docs内置健康检查/health。❌ 局限单进程无法利用多核 CPU无负载均衡无自动重启无日志聚合无指标暴露。它只是一个“能用”的原型而非“生产就绪”的服务。3.3 Level 3容器化与进程管理Docker systemd当服务需要长期稳定运行且要部署到多台服务器时就必须进入 Level 3。核心是将服务及其所有依赖Python、ONNX Runtime、CUDA打包成一个不可变的镜像并由操作系统级别的进程管理器systemd来守护它。这解决了单机部署的可靠性问题。Dockerfile精简版针对 Ubuntu 22.04 CUDA 11.8# 使用NVIDIA官方ONNX Runtime镜像已预装CUDA和cuDNN FROM nvcr.io/nvidia/onnxruntime:1.16.3-cuda11.8-py310 # 设置工作目录 WORKDIR /app # 复制交付包内容假设已解压到当前目录 COPY yolov8s_defect_v2.3_delivery/ . # 安装FastAPI和UvicornONNX Runtime镜像里没有 RUN pip install fastapi0.104.0 uvicorn0.23.2 python-multipart0.0.6 # 暴露端口 EXPOSE 8000 # 启动命令 CMD [uvicorn, app:app, --host, 0.0.0.0:8000, --port, 8000, --workers, 4]构建与运行# 构建镜像tag为模型版本便于追踪 docker build -t defect-detection:v2.3 . # 运行容器挂载GPU设置内存限制 docker run -d \ --gpus all \ --memory4g \ --restartalways \ -p 8000:8000 \ --name defect-v2.3 \ defect-detection:v2.3systemd 服务文件/etc/systemd/system/defect-detection.service适用于裸机部署当 Docker 不可用时的备选方案[Unit] DescriptionDefect Detection Service (v2.3) Afternetwork.target [Service] Typesimple Useraiuser WorkingDirectory/opt/defect-detection/v2.3 ExecStart/opt/defect-detection/v2.3/venv/bin/uvicorn app:app --host 0.0.0.0:8000 --port 8000 --workers 4 Restartalways RestartSec10 EnvironmentPATH/opt/defect-detection/v2.3/venv/bin EnvironmentMODEL_PATH/opt/defect-detection/v2.3/model/model.onnx [Install] WantedBymulti-user.target启用服务sudo systemctl daemon-reload sudo systemctl enable defect-detection.service sudo systemctl start defect-detection.service sudo systemctl status defect-detection.service # 查看状态关键经验--gpus all是 Docker 运行 GPU 容器的必要参数但很多用户会漏掉。另一个常见错误是--restartalways没加导致服务器重启后服务消失。我建议所有生产服务都加上RestartSec10避免因依赖服务如数据库未启动而频繁重启。3.4 Level 4云原生编排与可观测性Kubernetes Prometheus当你的 AI 服务需要支撑高并发、多租户、灰度发布、自动扩缩容时就必须进入 Level 4。这不再是“部署一个模型”而是“运营一个 AI 微服务”。核心是 KubernetesK8s编排和 Prometheus/Grafana 监控栈。K8s Deployment YAML关键片段# k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: defect-detection labels: app: defect-detection spec: replicas: 3 # 启动3个副本实现高可用 selector: matchLabels: app: defect-detection template: metadata: labels: app: defect-detection spec: containers: - name: predictor image: your-registry/defect-detection:v2.3 ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: 1 # 申请1块GPU memory: 4Gi cpu: 2 requests: nvidia.com/gpu: 1 memory: 2Gi cpu: 1 livenessProbe: # 存活探针K8s定期检查 httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: # 就绪探针决定是否接入流量 httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: defect-detection-service spec: selector: app: defect-detection ports: - protocol: TCP port: 80 targetPort: 8000 type: LoadBalancer # 对外暴露云厂商会分配公网IPPrometheus 监控指标在 infer.py 中添加# 在infer.py顶部添加 from prometheus_client import Counter, Histogram, Gauge import time # 定义指标 PREDICTION_COUNTER Counter(defect_prediction_total, Total number of predictions) PREDICTION_LATENCY Histogram(defect_prediction_latency_seconds, Prediction latency in seconds) PREDICTION_ERROR Counter(defect_prediction_errors_total, Total number of prediction errors) def predict(image_bytes: bytes) - dict: PREDICTION_COUNTER.inc() start_time time.time() try: # ...原有推理逻辑... latency time.time() - start_time PREDICTION_LATENCY.observe(latency) return result except Exception as e: PREDICTION_ERROR.inc() raise eGrafana 仪表盘关键视图QPS每秒请求数监控流量峰值判断是否需要扩容。P95 Latency95分位延迟核心性能指标应稳定在config.yaml承诺值附近。GPU Memory UsageGPU显存使用率超过90%需告警可能引发OOM。Error Rate错误率持续高于1%需立即介入。真实案例我们为一家汽车零部件厂部署的缺陷检测服务在上线首周Grafana 显示 P95 Latency 从 42ms 飙升至 120ms。排查发现是 K8s 的livenessProbe配置不当initialDelaySeconds: 30太短导致服务刚启动就被 K8s 误判为死亡并重启形成恶性循环。调整为60后问题解决。这印证了一点AI 部署的瓶颈往往不在模型本身而在基础设施的精细调优。4. 模型管理从“版本混乱”到“全生命周期可追溯”的实践体系如果说部署是让模型“活”起来那么管理就是让模型“活得明白、活得长久”。在实际项目中一个业务线往往同时运行着 5-10 个不同版本的模型v1.0 到 v2.3服务于不同的客户、不同的渠道、不同的合规要求。没有一套清晰的管理机制就会陷入“不知道线上跑的是哪个版本”、“新版本上线后老版本数据无法复现”、“安全审计时拿不出模型变更记录”的混乱局面。4.1 模型注册中心Model Registry集中存储与元数据索引模型注册中心是模型管理的“中央数据库”。它不存储模型文件本身太占空间而是存储模型的元数据、指向模型文件的 URI、以及版本间的血缘关系。我推荐两种轻量级、易落地的方案方案AMLflow Model Registry推荐给 Python 生态MLflow 是开源的 MLOps 平台其 Model Registry 功能成熟、文档完善、社区活跃。它天然支持 PyTorch/TensorFlow/Scikit-learn且可与我们的交付包无缝集成。操作流程注册模型在训练脚本末尾将交付包上传到 MLflowimport mlflow mlflow.set_tracking_uri(http://mlflow-server:5000) mlflow.set_experiment(defect-detection) with mlflow.start_run() as run: # ...训练代码... # 将整个交付包目录作为模型 artifact 注册 mlflow.log_artifacts(yolov8s_defect_v2.3_delivery/, model) # 记录关键指标 mlflow.log_metric(mAP_0.5, 0.872) mlflow.log_param(train_dataset, defect_train_v2.0) # 将此版本标记为 Staging mlflow.register_model( runs:/{}/model.format(run.info.run_id), DefectDetectionModel )版本管理在 MLflow UI 中你可以看到DefectDetectionModel下的所有版本v1, v2, v2.1, v2.3并为每个版本设置阶段None,Staging,Production,Archived。点击任意版本即可查看其完整的元数据、关联的 Run ID、以及config.yaml的快照。生产部署部署脚本不再硬编码路径而是通过 MLflow API 动态拉取import mlflow client mlflow.tracking.MlflowClient() # 获取最新 Production 版本的模型URI model_uri client.get_latest_versions(DefectDetectionModel, stages[Production])[0].source # 下载到本地 mlflow.artifacts.download_artifacts(model_uri, dst_path/tmp/model)方案B自建 MinIO SQLite推荐给资源受限或私有化部署当无法部署 MLflow 时一个极简但有效的替代方案是用 MinIOS3 兼容的对象存储存模型文件用 SQLite 数据库存储元数据。SQLite 表结构models.dbCREATE TABLE models ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, -- 模型名如 defect-detection version TEXT NOT NULL, -- 版本号如 2.3 stage TEXT DEFAULT staging, -- 阶段staging, production, archived s3_uri TEXT NOT NULL, -- MinIO URI如 s3://models/defect-detection/v2.3.zip created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, author TEXT, description TEXT, performance_json TEXT, -- JSON字符串存config.yaml中的performance部分 UNIQUE(name, version) );优势零外部依赖MinIO 可单机部署SQLite 是文件备份极其简单。所有操作可通过sqlite3CLI 或 Pythonsqlite3库完成运维成本极低。4.2 模型血缘Lineage追踪“这个结果从哪里来”模型血缘是回答“为什么这个预测是这样”的终极依据。它记录了从原始数据、到训练代码、到超参、到最终模型、再到每一次推理的完整链条。没有血缘AI 就是黑箱有了血缘AI 才是可解释、可审计、可追责的工程产品。血缘追踪的三层实现数据层血缘在数据预处理脚本中为每个数据集生成唯一哈希如sha256并将哈希值写入assets/dataset_hash.txt。交付包中的config.yaml引用此哈希。代码层血缘在训练脚本开头获取 Git Commit IDimport subprocess commit_id subprocess.check_output([git, rev-parse, HEAD]).decode().strip() mlflow.log_param(git_commit, commit_id)模型层血缘在交付包的docs/README.md中明确写出This model v2.3 was trained on:Dataset:defect_train_v2.0(SHA256:a1b2c3...)Code:git commit a1b2c3...in repohttps://gitlab.example.com/ai/defect-detectionFramework:pytorch-2.1.0,torchvision-0.16.0Hardware:NVIDIA A100 80GB血缘可视化简易版用 Mermaid 语法虽然你禁用但这里仅作原理说明实际不用可以画出graph LR A[Raw Images] -- B[Preprocess Script v1.2] B -- C[Dataset v2.0 SHA:a1b2c3] C -- D[Train Script v2.1] D -- E[Model v2.3] E -- F[Inference API v2.3] F -- G[Production Traffic]在实践中我用一个简单的 HTML 页面动态渲染这个关系图链接到 GitLab 仓库、MinIO 文件、K8s Dashboard让任何一个 QA 或审计员都能在 30 秒内从线上一个错误预测追溯到当初训练用的那张原始图片。4.3 模型监控Model Monitoring从“上线即结束”到“持续进化”部署上线不是终点而是监控的起点。模型会漂移Drift数据会变化业务需求会升级。一个不被监控的模型就像一辆没有仪表盘的汽车你永远不知道它何时会抛锚。必须监控的三大维度维度监控指标工具/方法告警阈值说明数据质量输入数据分布偏移KS Test、缺失值率、异常值比例Evidently.ai, Prometheus custom exporterKS Stat 0.15检测上游数据源是否异常如摄像头脏了、传感器故障模型性能Accuracy/Precision/Recall 下降、预测置信度分布变化定期在 holdout set 上跑accuracy_test.pymAP_0.5 下降 2%检测模型是否过时需重新训练系统健康QPS、Latency P95、GPU Memory、Error RatePrometheus GrafanaLatency P95 100ms检测基础设施瓶颈一个真实的监控闭环案例我们在某电商平台部署的商品识别模型上线三个月后监控显示confidence_score_mean从 0.82 逐渐下降到 0.65。起初以为是模型退化但accuracy_test.py