机器学习模型生产化落地:从Notebook到Kubernetes的工程实践
1. 项目概述:这不是一次“部署”,而是一场从实验室到产线的系统性迁移
“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题里藏着太多被轻描淡写却重若千钧的词。“Notebook”不是指纸质本子,而是Jupyter里那个写着
model.fit()
、
plt.show()
、一切看起来都闪闪发光的交互式沙盒;“Production”也不是简单地把模型跑起来,而是它得在凌晨三点的订单洪峰里不掉链子,在客户上传模糊图片时给出稳定置信度,在数据库字段悄悄变更后仍能正确解析输入,在运维同事重启服务器后自动恢复服务,甚至在某天你休假时,它还在 quietly 处理着上万条实时风控请求。我做过27个从0到1落地的ML项目,其中19个卡在Part 2(模型训练完成)和Part 3(API封装)之间,真正走到Part 4并稳定运行超6个月的,只有8个。而这第4部分,恰恰是区分“AI玩具”和“AI资产”的分水岭。它不讲AUC有多高,只关心P99延迟是否压在120ms以内;不炫耀F1-score,只盯着日志里每小时出现几次
KeyError: 'user_profile'
;不谈Transformer结构多优雅,只问模型镜像体积能不能从1.8GB压到420MB以适配边缘网关。这篇内容面向的不是刚学完scikit-learn的新人,而是已经把模型调到满意、正对着Dockerfile发呆、被SRE同事微信轰炸“接口又503了”的实战者。它解决的核心问题很朴素:
当你的模型不再只服务于你自己,而要成为业务流水线中一个可信赖、可监控、可回滚、可计费的环节时,你该亲手拧紧哪几颗螺丝?
后面所有内容,都基于我在电商推荐、金融反欺诈、工业设备预测性维护三个垂直场景中踩过的坑、写的脚本、改过的K8s YAML、以及凌晨两点和值班工程师一起盯屏排查OOM的实录。
2. 整体设计思路:为什么必须放弃“一键部署”幻觉,转向分层治理架构
2.1 拒绝“Notebook即服务”的诱惑:从单点可靠到系统可靠
很多团队的第一反应是:把
.ipynb
文件用
nbconvert
转成Python脚本,再用Flask包一层,扔进Docker,
docker run -p 5000:5000
——完事。我试过,也上线过。结果呢?第一个月,模型API平均响应时间从180ms跳到420ms;第二周,因依赖库版本冲突导致特征工程模块静默失败,线上推荐列表变成随机播放;第三天,用户上传一张12MB的扫描件PDF,Flask直接OOM崩溃,整个服务不可用。问题出在哪?根本不在模型本身,而在于这种“单体式封装”把四个完全异构的系统强行焊死在一个进程里:
数据加载层(I/O密集)、特征计算层(CPU密集)、模型推理层(GPU/CPU混合)、服务编排层(网络/并发)
。它们对资源的需求、故障模式、扩缩容节奏、监控粒度全都不一样。就像把锅炉房、配电室、控制台和客服中心全塞进同一间玻璃房——温度一高,锅炉报警,配电跳闸,控制台黑屏,客服电话全占线。真正的生产就绪(Production-Ready),第一步就是解耦。我们最终采用的四层分离架构是:
- 接入层(Ingress Layer) :Nginx + Lua脚本做请求预检(大小限制、格式校验、基础鉴权),拒绝非法流量于门外,避免脏数据一路穿透到模型层;
- 服务层(Serving Layer) :使用Triton Inference Server(NVIDIA)或KServe(原KFServing)管理模型生命周期,支持同模型多版本灰度、GPU显存隔离、动态批处理(Dynamic Batching);
- 计算层(Compute Layer) :将特征工程逻辑彻底剥离,用独立的Feature Store服务(如Feast或自建Redis+Presto集群)提供低延迟特征查询,模型服务只负责纯推理;
- 可观测层(Observability Layer) :Prometheus采集指标(QPS、P99延迟、GPU利用率、内存RSS)、Loki收集结构化日志(含trace_id)、Jaeger追踪跨服务调用链。
这个架构不是为了炫技,而是每一层都对应一个明确的SLO(Service Level Objective)。比如接入层SLO是“99.9%请求在50ms内完成预检”,服务层SLO是“99.5%推理请求在150ms内返回”,计算层SLO是“99.99%特征查询在20ms内完成”。当某个SLO告警,你能精准定位到是哪一层出了问题,而不是在几百行日志里大海捞针。
2.2 模型交付物标准化:为什么
.pkl
文件永远不该出现在生产镜像里
新手常犯的致命错误:把训练好的
model.pkl
直接COPY进Docker镜像。这看似简单,实则埋下三颗雷:
环境漂移(Environment Drift)
、
安全漏洞(Security Vulnerability)
、
回滚失效(Rollback Failure)
。我亲眼见过一个项目,因为训练环境用的是
scikit-learn==1.0.2
,而生产镜像里
pip install -r requirements.txt
装的是
1.2.0
,导致
RandomForestClassifier.predict_proba()
返回的数组维度错乱,线上转化率报表连续三天显示为负数。更糟的是,
.pkl
文件本质是Python对象序列化,它会把训练时的完整类路径、函数引用、甚至本地临时变量都打包进去。一旦生产环境缺少某个未声明的依赖(比如
joblib
的某个子模块),反序列化直接抛
ModuleNotFoundError
,服务启动失败。我们强制推行的模型交付标准是:
模型必须导出为ONNX(Open Neural Network Exchange)格式,且仅包含推理所需权重与计算图,不含任何Python运行时上下文
。ONNX的优势在于:
- 跨框架兼容 :PyTorch训练的模型,导出ONNX后,可用ONNX Runtime(ORT)在CPU/GPU上高效执行,无需安装PyTorch;
- 版本锁定 :ONNX模型文件本身不依赖Python库版本,只要ORT版本兼容,模型就能跑;
-
安全可控
:ONNX文件是二进制协议,无法执行任意代码,规避了
pickle的远程代码执行(RCE)风险; - 性能优化 :ORT内置图优化器(Graph Optimizer),能自动融合算子、消除冗余节点,实测比原生PyTorch推理快1.8倍。
导出过程不是简单调
torch.onnx.export()
就完事。我们要求必须通过
onnx.checker.check_model()
验证模型结构合法性,并用
onnx.shape_inference.infer_shapes()
补全所有张量shape信息——这是后续TensorRT加速和动态批处理的前提。对于非神经网络模型(如XGBoost),我们统一转换为PMML(Predictive Model Markup Language)格式,用JPMML-Evaluator在Java服务中加载,确保与核心交易系统技术栈一致。
2.3 环境一致性保障:Docker不是银弹,Kubernetes才是生产护城河
很多人以为用了Docker就解决了环境问题。错。Docker只保证“构建时”环境一致,不保证“运行时”环境一致。比如,你的Dockerfile里写了
FROM nvidia/cuda:11.7.1-devel-ubuntu20.04
,但生产集群的GPU驱动是
515.65.01
,而CUDA 11.7.1要求驱动>=
515.48.07
——差一个小版本,
nvidia-smi
能看见卡,
torch.cuda.is_available()
却返回False。更隐蔽的是内核参数:默认Ubuntu 20.04的
vm.max_map_count=65530
,而Elasticsearch要求
>=262144
,如果你的服务依赖ES做日志检索,容器会因内核参数不足而反复CrashLoopBackOff。这就是为什么我们绝不允许裸Docker部署,所有服务必须跑在Kubernetes上,并通过以下三重机制锁死环境:
-
Node Affinity + Taints/Tolerations
:为GPU节点打上
hardware=gpu标签,并设置taint: nvidia.com/gpu=:NoSchedule,确保只有声明了tolerations的模型服务Pod才能调度到GPU节点,避免CPU密集型服务抢占GPU资源; -
Init Container预检
:每个Pod启动前,先运行一个Init Container,执行
nvidia-smi --query-gpu=name,driver_version --format=csv,noheader,nounits和cat /proc/sys/vm/max_map_count,校验驱动版本与内核参数,不达标则Pod启动失败,触发告警; -
RuntimeClass隔离
:为GPU工作负载定义专用RuntimeClass
nvidia-runtime,绑定nvidia-container-runtime,确保容器运行时与驱动深度集成,而非使用默认的runc。
这套机制让我们的模型服务上线成功率从73%提升到99.2%,最常触发的告警不再是“模型加载失败”,而是“GPU显存分配超限”,这恰恰说明问题已收敛到可量化、可优化的工程范畴。
3. 核心细节解析:从模型加载到请求处理的每一毫秒都在被精密计算
3.1 模型加载阶段:冷启动时间如何从47秒压到1.2秒
模型服务最大的痛点之一是冷启动(Cold Start):Pod刚被K8s调度起来,第一次请求进来时,模型加载、权重解析、GPU显存分配、CUDA上下文初始化……整个过程耗时漫长,导致首请求超时(Timeout),触发上游重试,形成雪崩。我们曾有一个实时风控模型,冷启动峰值达47秒,导致支付链路P99延迟飙升至8秒。优化不是靠堆硬件,而是拆解加载流水线:
| 阶段 | 原耗时 | 瓶颈分析 | 优化方案 | 优化后耗时 |
|---|---|---|---|---|
| ONNX模型加载 | 8.3s |
onnx.load()
解析二进制流,生成Python对象树
|
改用
onnx.load_model_from_string()
+ 内存映射(mmap),跳过Python对象构建
| 0.9s |
| ORT Session创建 | 22.1s | 默认启用所有优化器(Graph Optimizer),遍历复杂计算图 |
关闭
enable_mem_pattern=False
,禁用
execution_mode=ExecutionMode.ORT_SEQUENTIAL
,预编译CUDA kernel
| 3.2s |
| GPU显存预分配 | 14.5s | ORT首次运行时动态申请显存,触发CUDA上下文初始化 | 在Session创建后,立即执行一次空推理(dummy input),强制显存分配与上下文建立 | 0.8s |
| 权重加载到GPU | 2.1s | 权重从CPU内存拷贝到GPU显存 |
使用
providers=['CUDAExecutionProvider']
+
provider_options=[{"device_id": 0}]
,启用零拷贝(Zero-Copy)
| 0.3s |
关键技巧在于: 把耗时操作前置到Pod就绪探针(Readiness Probe)之前 。我们在K8s Deployment中配置:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 120 # 给足冷启动时间
readinessProbe:
exec:
command: ["sh", "-c", "python /app/check_warmup.py"] # 执行空推理校验
initialDelaySeconds: 5
periodSeconds: 10
check_warmup.py
会向本地ORT Session提交一个1x1的dummy tensor,成功则返回0,K8s才将Pod标记为Ready,接入流量。实测后,冷启动稳定在1.2±0.3秒,P99延迟回归正常基线。
3.2 请求处理流水线:为什么“同步推理”是生产环境的自杀行为
很多教程教你在Flask路由里直接写
model.predict(input)
。这在测试时没问题,但生产中等于给服务埋雷。原因有三:
阻塞主线程
(一个慢请求卡住整个Worker)、
无并发控制
(1000QPS涌入,瞬间创建1000个Python线程,内存爆满)、
无超时熔断
(下游DB慢,模型等待30秒,拖垮整个服务)。我们必须重构为异步非阻塞流水线。核心组件是:
- 请求队列(Request Queue) :使用Redis Stream作为消息队列,生产者(API Gateway)将请求序列化为JSON推入Stream,消费者(Worker Pool)拉取处理;
-
Worker Pool(工作池)
:基于Celery + Redis Broker,每个Worker进程独占一个ORT Session(避免多线程共享Session的CUDA上下文竞争),Worker数量根据GPU显存严格计算:
max_workers = floor(GPU_MEMORY_GB / MODEL_MEMORY_PER_INSTANCE_GB); -
超时熔断(Circuit Breaker)
:在Worker中嵌入
tenacity库,对session.run()设置stop=stop_after_attempt(2)+wait=wait_exponential(multiplier=1, min=1, max=10),连续2次超时则熔断10秒,返回降级响应(如{"score": 0.5, "reason": "model_unavailable"})。
这个设计让服务具备弹性:当GPU负载达90%,新请求自动排队;当模型异常,熔断器快速失败,保护下游;当流量突增,K8s HPA根据Redis Stream Pending Count指标自动扩容Worker Pod。我们不再需要祈祷“别出问题”,而是设计“出问题时怎么优雅退场”。
3.3 特征服务(Feature Serving):为什么不能让模型服务自己查数据库
新手常把特征获取逻辑写进模型服务:
def predict(request): user_features = db.query("SELECT ... WHERE user_id = %s", request.user_id); model_input = np.concatenate([user_features, request.item_features]); return model.predict(model_input)
。这会导致灾难性后果:
数据库连接池耗尽
(每个请求开一个DB连接)、
SQL注入风险
(拼接字符串)、
特征不一致
(模型训练用的是离线快照,服务用的是实时DB,时间窗口错位)。正确的做法是:
特征计算与特征供给彻底分离
。我们采用两套特征管道:
-
离线特征(Batch Features)
:每天凌晨用Spark计算全量用户画像(如“过去30天购买频次”、“品类偏好得分”),写入Hive分区表,再通过Airflow任务同步到Redis Hash(key=
feature:user:{id},field=purchase_freq_30d); -
实时特征(Streaming Features)
:用Flink消费Kafka用户行为日志(点击、加购、支付),实时更新Redis Sorted Set(key=
recent_actions:{user_id},score=timestamp,value=action_type),供模型查询“最近10分钟行为序列”。
模型服务通过统一Feature Store SDK查询:
from feast import FeatureStore
store = FeatureStore(repo_path="/path/to/feast/repo")
entity_df = pd.DataFrame({"user_id": [123], "event_timestamp": [pd.Timestamp.now()]})
features = store.get_historical_features(
entity_df=entity_df,
features=["user_features:purchase_freq_30d", "user_features:recent_actions_10m"]
).to_df()
SDK内部自动路由到Redis(毫秒级)或Fallback到Hive(秒级),并缓存查询结果(LRU Cache,TTL=5min)。这样,模型服务只做纯数学运算,特征供给的延迟、一致性、扩展性全部由Feature Store保障。
4. 实操全流程:从本地开发到K8s集群的12步落地清单
4.1 开发环境准备:让本地调试无限逼近生产
本地开发环境必须是生产环境的“缩小镜像”,否则调试毫无意义。我们强制要求:
-
使用VS Code Dev Container
:Dockerfile基于
nvidia/cuda:11.7.1-devel-ubuntu20.04,预装onnxruntime-gpu==1.15.1、redis-py==4.6.0、feast==0.32.0,并挂载NVIDIA Container Toolkit; -
本地Feature Store模拟
:用Docker Compose启动Redis + MinIO(模拟S3),
feast apply命令将离线特征定义同步到本地Redis,feast materialize将Hive样本数据灌入MinIO; -
Mock GPU环境
:在无GPU机器上,用
export CUDA_VISIBLE_DEVICES=""强制ORT使用CPU Execution Provider,并在代码中添加assert ort_session.get_providers() == ['CPUExecutionProvider'],确保CPU路径100%覆盖。
提示:Dev Container的
devcontainer.json中必须配置"runArgs": ["--gpus", "all"],否则VS Code终端无法访问GPU。我曾因漏配此参数,在本地调试通过,上线后因GPU不可用而全线崩溃。
4.2 模型导出与验证:ONNX不是终点,而是起点
以PyTorch图像分类模型为例,导出脚本
export_onnx.py
必须包含:
import torch
import onnx
from onnx import shape_inference, checker
# 1. 加载训练好的模型
model = torch.jit.load("model.pt")
model.eval()
# 2. 构造符合生产输入规范的dummy input
# 生产要求:batch_size=1, channels=3, height=224, width=224, dtype=float32
dummy_input = torch.randn(1, 3, 224, 224, dtype=torch.float32)
# 3. 导出ONNX,指定dynamic_axes实现动态batch
torch.onnx.export(
model,
dummy_input,
"model.onnx",
export_params=True,
opset_version=14,
do_constant_folding=True,
input_names=['input'],
output_names=['output'],
dynamic_axes={
'input': {0: 'batch_size'}, # 第0维(batch)动态
'output': {0: 'batch_size'}
}
)
# 4. 强制进行shape inference,补全所有tensor shape
onnx_model = onnx.load("model.onnx")
onnx_model = shape_inference.infer_shapes(onnx_model)
onnx.save(onnx_model, "model.onnx")
# 5. 严格校验
checker.check_model(onnx_model) # 抛异常则失败
print("ONNX model validated successfully!")
导出后,必须用
onnxruntime
在本地验证推理一致性:
import numpy as np
import onnxruntime as ort
# 加载ONNX模型
ort_session = ort.InferenceSession("model.onnx", providers=['CPUExecutionProvider'])
# 生成与训练时完全相同的输入(注意归一化!)
img = cv2.imread("test.jpg") / 255.0 # 训练时用的0-1归一化
img = np.transpose(img, (2, 0, 1))[np.newaxis, ...] # (1,3,224,224)
# PyTorch原生推理
with torch.no_grad():
pt_output = model(torch.from_numpy(img)).numpy()
# ONNX Runtime推理
ort_output = ort_session.run(None, {'input': img.astype(np.float32)})[0]
# 检查数值一致性(容忍浮点误差)
np.testing.assert_allclose(pt_output, ort_output, rtol=1e-3, atol=1e-4)
print("ONNX inference matches PyTorch!")
注意:
rtol=1e-3是经验阈值,CNN模型通常在此范围内。若超限,检查是否遗漏了model.eval()或torch.no_grad(),或ONNX导出时opset_version过低。
4.3 Docker镜像构建:瘦身、提速、防漏洞的黄金三角
生产Docker镜像不是越小越好,而是要在
启动速度、运行效率、安全合规
三者间找平衡。我们的
Dockerfile
遵循:
# 阶段1:构建阶段(Build Stage)
FROM nvidia/cuda:11.7.1-devel-ubuntu20.04 AS builder
# 安装构建依赖
RUN apt-get update && apt-get install -y python3.8-dev python3.8-venv && rm -rf /var/lib/apt/lists/*
# 创建虚拟环境,安装构建时依赖
RUN python3.8 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --upgrade pip
RUN pip install onnxruntime-gpu==1.15.1 onnx==1.13.1
# 复制源码,安装应用依赖
COPY requirements.txt .
RUN pip install -r requirements.txt
# 阶段2:生产阶段(Production Stage)
FROM nvidia/cuda:11.7.1-runtime-ubuntu20.04
# 复制构建阶段的venv,而非重新pip install
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 复制ONNX模型和应用代码
COPY model.onnx /app/model.onnx
COPY app/ /app/
# 创建非root用户(安全刚需)
RUN groupadd -g 1001 -f app && useradd -s /bin/bash -u 1001 -g app app
USER app
# 设置启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "1", "--threads", "4", "app:app"]
关键点解析:
-
多阶段构建
:
builder阶段安装编译工具(如gcc),production阶段只复制编译好的wheel包,镜像体积从1.8GB降至420MB; -
venv复用
:避免在生产镜像中重复
pip install,既提速又保证依赖版本绝对一致; -
非root用户
:K8s Pod Security Policy强制要求,
USER app后,容器内进程UID=1001,无法执行apt-get等危险操作; -
精简基础镜像
:
-runtime-后缀镜像只含CUDA运行时,不含编译器,攻击面更小。
构建命令必须带
--no-cache
和
--progress=plain
:
docker build --no-cache --progress=plain -t my-ml-service:v1.2.0 .
--no-cache
防止Docker复用旧层导致依赖污染;
--progress=plain
输出详细日志,便于排查
pip install
失败原因。
4.4 K8s部署与观测:让每一次发布都可追溯、可回滚、可审计
生产K8s部署不是
kubectl apply -f deploy.yaml
就结束,而是一套发布仪式。我们使用GitOps模式,所有YAML存于Git仓库,通过Argo CD自动同步。核心资源清单包括:
-
ConfigMap
:存储模型元数据(
model_name: fraud_v2,onnx_version: 1.15.1,input_shape: [1,3,224,224]); - Secret :加密存储Redis密码、Feature Store Token;
-
Deployment
:配置
replicas: 3,strategy.type: RollingUpdate,maxSurge: 1,maxUnavailable: 0(滚动更新时,新Pod就绪后再杀旧Pod); - Service :ClusterIP暴露内部端口,配合Ingress暴露公网;
-
HorizontalPodAutoscaler (HPA)
:基于
redis_stream_pending_count指标(自定义指标)扩容,阈值设为50(即Stream中待处理请求超50个时扩容); -
PodDisruptionBudget (PDB)
:
minAvailable: 2,确保滚动更新或节点维护时,至少2个Pod在线。
发布流程严格执行:
-
git commit -m "deploy: fraud_v2 with ONNX runtime 1.15.1"; -
Argo CD检测到Git变更,自动
kubectl apply; -
Argo CD执行健康检查:
curl -f http://pod-ip:8080/readyz,连续3次成功才标记Synced; -
发布后10分钟,Prometheus告警规则检查:
rate(http_request_duration_seconds_bucket{job="ml-service", le="0.15"}[5m]) / rate(http_request_duration_seconds_count{job="ml-service"}[5m]) < 0.995(P99延迟达标率),不达标则自动回滚。
实操心得:HPA的
custom.metrics.k8s.io/v1beta1API需提前部署Prometheus Adapter,并在values.yaml中配置Redis Stream指标抓取规则。我们用redis-cli --scan --pattern "stream:*" | xargs -I {} redis-cli llen {}脚本定时上报Pending Count,精度足够指导扩缩容。
5. 常见问题与排查技巧:那些凌晨三点教会我的事
5.1 典型问题速查表
| 现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
Pod状态为
CrashLoopBackOff
| CUDA驱动版本不匹配 |
kubectl logs <pod> -c init-container
| 检查Init Container日志,升级节点驱动或降级CUDA镜像 |
API返回
503 Service Unavailable
| Readiness Probe失败 |
kubectl describe pod <pod>
查看Events
|
检查
/readyz
端点是否因冷启动超时,延长
initialDelaySeconds
|
| P99延迟突然飙升至2s+ | Redis连接池耗尽 |
redis-cli info clients | grep connected_clients
|
增加Feature Store SDK的
max_connections=100
,或拆分Redis实例
|
模型输出全为
NaN
| 输入数据未归一化 |
kubectl logs <pod> | grep "nan"
|
在预处理层添加
np.nan_to_num(input, nan=0.0)
兜底
|
| GPU显存占用100%但利用率<10% | 动态批处理未生效 |
nvidia-smi dmon -s u -d 1
|
检查ONNX导出时是否设置了
dynamic_axes
,及ORT Session是否启用
enable_sequential_execution=False
|
5.2 独家避坑技巧:血泪换来的3个Checklist
Checklist 1:模型输入校验必须前置到Ingress层
不要等请求到达模型服务才做校验。我们在Nginx Ingress Controller的
nginx.ingress.kubernetes.io/configuration-snippet
中嵌入Lua:
location ~ ^/predict$ {
content_by_lua_block {
local json = require("cjson")
local data = ngx.req.get_body_data()
if not data then
ngx.status = 400
ngx.say(json.encode({error="empty request body"}))
return
end
local parsed = json.decode(data)
if not parsed.image_base64 or #parsed.image_base64 > 5*1024*1024 then
ngx.status = 413
ngx.say(json.encode({error="image too large, max 5MB"}))
return
end
}
}
这能在毫秒级拒绝超大请求,避免其穿透到后端消耗GPU资源。
Checklist 2:特征缺失必须有确定性降级策略
Feature Store查询失败时,绝不能让模型服务抛
KeyError
。我们在SDK中强制实现:
def get_feature(user_id: str, feature_name: str) -> float:
try:
value = redis.hget(f"feature:user:{user_id}", feature_name)
return float(value) if value else DEFAULT_FEATURE_VALUES[feature_name]
except Exception as e:
# 上报Metrics:feature_fetch_failure_total{feature="purchase_freq_30d"}
metrics.inc("feature_fetch_failure_total", {"feature": feature_name})
return DEFAULT_FEATURE_VALUES[feature_name] # 如purchase_freq_30d默认=0.0
所有默认值在ConfigMap中集中管理,确保训练与服务一致。
Checklist 3:GPU监控必须细化到显存碎片率
nvidia-smi
显示显存占用95%,但ORT报
OutOfMemoryError
,大概率是显存碎片化。我们用
nvidia-ml-py3
库采集
nvmlDeviceGetMemoryInfo().used / nvmlDeviceGetMemoryInfo().total
(总占用率)和
nvmlDeviceGetUtilizationRates().memory
(显存带宽利用率),当
used > 0.9
且
memory < 30
时,触发告警——说明显存被大量小块占用,需重启Pod释放。
最后分享一个小技巧:在模型服务健康检查端点
/healthz中,除了检查进程存活,务必加入ort_session.run()空推理,确保GPU上下文和显存分配正常。我们曾因忽略此点,在一次K8s节点升级后,所有Pod健康检查通过,但实际请求全部超时,排查耗时47分钟。现在,这个检查是发布前的强制门禁。
更多推荐



所有评论(0)