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上,并通过以下三重机制锁死环境:

  1. Node Affinity + Taints/Tolerations :为GPU节点打上 hardware=gpu 标签,并设置 taint: nvidia.com/gpu=:NoSchedule ,确保只有声明了 tolerations 的模型服务Pod才能调度到GPU节点,避免CPU密集型服务抢占GPU资源;
  2. Init Container预检 :每个Pod启动前,先运行一个Init Container,执行 nvidia-smi --query-gpu=name,driver_version --format=csv,noheader,nounits 和 cat /proc/sys/vm/max_map_count ,校验驱动版本与内核参数,不达标则Pod启动失败,触发告警;
  3. 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 开发环境准备:让本地调试无限逼近生产

本地开发环境必须是生产环境的“缩小镜像”,否则调试毫无意义。我们强制要求:

  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;
  2. 本地Feature Store模拟 :用Docker Compose启动Redis + MinIO(模拟S3), feast apply 命令将离线特征定义同步到本地Redis, feast materialize 将Hive样本数据灌入MinIO;
  3. 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在线。

发布流程严格执行:

  1. git commit -m "deploy: fraud_v2 with ONNX runtime 1.15.1" ;
  2. Argo CD检测到Git变更,自动 kubectl apply ;
  3. Argo CD执行健康检查: curl -f http://pod-ip:8080/readyz ,连续3次成功才标记Synced;
  4. 发布后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/v1beta1 API需提前部署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分钟。现在,这个检查是发布前的强制门禁。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐