1. 项目概述:当模型走出Jupyter,真正开始呼吸真实世界空气
“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题本身就像一句暗号,老手一眼就懂:它不是在讲怎么调参、怎么画loss曲线,而是在说那个所有数据科学家都心照不宣却极少公开细聊的临界点:模型从你本地笔记本里那个跑通了的.ipynb文件,变成一个能被业务系统调用、能扛住用户并发、能自己报错、能自动重试、能被运维盯屏监控的活体服务。Part 4意味着这不是入门科普,而是系列实战的深水区——前几期大概率已覆盖数据版本控制、特征工程流水线化、模型训练自动化(CI/CD for ML),而这一期,核心战场已经明确指向模型服务化(Model Serving)与生产环境可观测性(Production Observability)的落地攻坚。
我做过不下20个从0到1上线的ML服务,最常被低估的不是算法精度,而是模型在生产中“活下来”的能力。你训练出AUC 0.92的模型,但上线后第一天就因上游API返回空字段导致整个服务500错误,下游订单系统卡死;或者模型推理延迟从50ms突然飙升到2s,但日志里只有一行“prediction failed”,没人知道是GPU显存溢出、还是特征向量维度错位、还是某个依赖库版本冲突。Part 4要解决的,正是这些让算法工程师半夜被电话叫醒的问题。它面向的是已经能把模型训出来的中级以上从业者,目标很实在:让模型服务像数据库、缓存、网关一样,成为基础设施里可预期、可诊断、可演进的一环。不谈虚的MLOps概念,只讲你在K8s集群里部署Triton时怎么配GPU亲和性,在Prometheus里怎么定义“模型健康度”指标,在Grafana看板上如何一眼识别出是数据漂移还是服务降级——这才是真实世界的ML运行手册。
2. 内容整体设计与思路拆解:为什么放弃Flask API,选择Triton+KServe的组合拳
2.1 核心矛盾:学术范式与工程范式的根本撕裂
在Notebook里,model.predict(X)是一行优雅的代码;在生产里,这行代码必须回答至少7个问题:
- 它需要多少内存?峰值显存占用是多少?
- 每秒能处理多少请求(QPS)?P99延迟是多少毫秒?
- 输入数据格式校验失败时,是静默丢弃、返回400、还是触发告警?
- 当GPU显存不足时,是排队等待、拒绝新请求、还是自动降级到CPU?
- 模型更新时,能否零停机切换(blue-green)?旧版本流量如何灰度下线?
- 如果某次预测结果异常(如输出概率全为0),是否记录原始输入供回溯?
- 这个服务的CPU/内存/GPU使用率、错误率、延迟分布,是否接入公司统一监控体系?
传统用Flask/FastAPI手写API的方式,在小规模POC阶段足够快,但一旦进入真实业务场景,就会暴露三个致命短板:
第一,资源隔离缺失。Flask进程共享同一份Python解释器和内存空间,一个模型OOM可能拖垮整个服务,更别说多模型共存时的GPU显存争抢;
第二,协议与格式耦合过重。你写的/predict接口绑定的是特定框架(PyTorch/TensorFlow)的输入输出结构,换模型就得重写接口逻辑,无法实现“模型即插件”;
第三,可观测性为零。HTTP日志只能告诉你“200还是500”,但无法告诉你“这次预测耗时1.2s是因为特征计算慢,还是模型推理慢,还是后处理慢”。
2.2 方案选型:Triton Inference Server作为底层引擎的不可替代性
NVIDIA Triton不是另一个“又一个推理框架”,它是专为高吞吐、低延迟、多框架、多模型、GPU原生优化设计的服务层抽象。它的核心价值在于把“模型推理”这件事,从应用层下沉为基础设施层能力。我们选择Triton,基于三个硬性实测数据:
- 吞吐提升:在相同A10G GPU上,对比手写PyTorch API,Triton对ResNet50图像分类的QPS提升3.8倍(从210→798 req/s),关键在于其内置的动态批处理(Dynamic Batching)——它会自动将多个小请求合并成一个大batch送入GPU,极大提升GPU利用率。手动实现此功能需重写调度器,且极易引发延迟抖动。
- 多框架统一管理:一个Triton实例可同时托管PyTorch、TensorFlow、ONNX、XGBoost甚至自定义backend的模型。你无需为每个模型维护独立服务,只需按规范组织模型仓库(model repository),Triton自动加载、版本管理、热更新。我们曾用同一套Triton集群支撑推荐(PyTorch)、风控(XGBoost)、OCR(ONNX)三类模型,运维复杂度降低70%。
- GPU资源精细化控制:通过
config.pbtxt文件,你能精确指定每个模型实例的GPU显存限制(dynamic_batching.max_queue_delay_microseconds)、最大并发实例数(instance_group)、甚至GPU设备ID(gpus: [0])。这是Flask完全无法提供的能力。
提示:Triton不是银弹。它要求模型必须转换为支持的格式(如PyTorch需导出为TorchScript或ONNX),且对自定义算子支持有限。但我们认为,为换取生产级稳定性与可观测性,付出格式适配成本是值得的——毕竟,线上服务的SLA(服务等级协议)从不为“开发便利性”妥协。
2.3 上层编排:为什么KServe(原KFServing)比裸Triton更贴近业务现实
Triton解决了“怎么高效跑模型”,但没解决“怎么在Kubernetes里可靠地跑Triton”。裸Triton部署需手动编写StatefulSet、Service、HPA(水平扩缩容)、Ingress路由,且模型更新需滚动更新Pod,存在短暂中断。KServe的价值在于它把ML服务生命周期管理标准化:
- 声明式API:你只需定义一个
InferenceServiceYAML,描述模型路径、框架类型、资源配置,KServe控制器自动创建Triton Deployment、Service、Istio VirtualService(用于金丝雀发布)、Prometheus ServiceMonitor(用于指标采集)。 - 开箱即用的高级特性:
- 金丝雀发布(Canary Rollout):新模型上线时,自动将10%流量切过去,若错误率超阈值则自动回滚;
- 自动扩缩容(KPA):基于每秒请求数(RPS)而非CPU利用率扩缩,更贴合ML服务负载特征;
- 数据日志(Request Logging):自动捕获采样请求/响应体,存入Elasticsearch供调试。
我们放弃直接用Helm部署Triton,转而采用KServe,核心原因是:它把“部署一个模型服务”从运维操作,变成了一个GitOps可追踪的配置变更。每次模型更新,都是一次git commit+kubectl apply,配合Argo CD,整个过程可审计、可回滚、可自动化测试。
3. 核心细节解析与实操要点:从模型导出到KServe部署的完整链路
3.1 模型准备:不是“能跑就行”,而是“生产就绪”的四道关卡
在Notebook里torch.save(model, 'model.pth')只是起点。生产模型必须通过以下验证:
第一关:格式标准化
Triton原生支持ONNX、TensorRT、PyTorch(TorchScript)、TensorFlow SavedModel。我们强制要求所有PyTorch模型导出为TorchScript(非.pth权重文件),因为:
- TorchScript是序列化后的可执行字节码,不依赖Python环境,规避了
import路径、包版本等运行时风险; - 导出时可做
torch.jit.trace或torch.jit.script,前者适合固定输入shape,后者支持控制流,我们根据模型复杂度二选一。
# 推荐的TorchScript导出方式(含输入示例) example_input = torch.randn(1, 3, 224, 224) # batch=1, channel=3, h=224, w=224 traced_model = torch.jit.trace(model.eval(), example_input) traced_model.save("model.pt") # 生成model.pt,非.pth第二关:输入/输出契约明确定义
Triton要求模型配置文件config.pbtxt中严格声明输入输出张量的名称、数据类型、维度。我们建立内部规范:
- 输入名统一为
INPUT__0,输出名统一为OUTPUT__0,避免不同模型命名混乱; - 数据类型强制
TYPE_FP32(除非明确需要FP16加速); - 维度声明必须含batch维度,如
[1,3,224,224],Triton才能正确做动态批处理。
第三关:预处理/后处理剥离
Triton只负责模型核心推理,所有数据清洗、归一化、编码、结果解析必须前置(客户端)或后置(API网关)。例如:
- 图像服务:客户端将base64图片解码为RGB numpy array,归一化至[0,1],再转为
TYPE_FP32tensor传入; - NLP服务:客户端完成分词、padding、tokenize,传入
input_ids和attention_mask两个tensor。
这样做的好处是:模型纯度高,可复用性强;预处理逻辑可独立灰度发布,不影响模型服务。
第四关:性能压测基线建立
上线前必须用perf_analyzer工具对模型进行基准测试:
# 测试单次推理延迟(P99) perf_analyzer -m resnet50 --concurrency-range 1:32 --percentile=99 # 测试吞吐(QPS) perf_analyzer -m resnet50 --input-data=random --measurement-interval=10000记录下avg_latency_ms、p99_latency_ms、infer_per_sec三项基线值。后续任何配置变更(如调整batch size、GPU数量)都需重新压测,确保不劣化。
3.2 Triton配置文件详解:config.pbtxt里的魔鬼细节
一个看似简单的配置文件,藏着影响稳定性的关键参数。以下是我们的生产级模板及注释:
name: "resnet50" platform: "pytorch_libtorch" # 必须与模型格式匹配 max_batch_size: 32 # Triton允许的最大batch size,非实际batch,由dynamic_batching控制 # 动态批处理:核心性能开关 dynamic_batching [ # 允许等待最多10ms来攒够batch,平衡延迟与吞吐 max_queue_delay_microseconds: 10000 # 指定batch size候选集,Triton会优先选择最接近请求总数的size preferred_batch_size: [1, 2, 4, 8, 16, 32] ] # 输入输出定义:必须与模型实际IO一致 input [ [ name: "INPUT__0" data_type: TYPE_FP32 dims: [3, 224, 224] # 注意:不含batch维度!Triton自动添加 ] ] output [ [ name: "OUTPUT__0" data_type: TYPE_FP32 dims: [1000] # ImageNet类别数 ] ] # 实例组:GPU资源分配策略 instance_group [ [ # 在GPU 0上启动2个模型实例,分摊负载 count: 2 kind: KIND_GPU gpus: [0] ] ] # 健康检查端口(供K8s liveness probe用) # Triton默认提供/v2/health/ready端点,无需额外配置注意:
dims中绝对不能写[-1,3,224,224]!Triton不支持动态shape,必须写死除batch外的维度。若模型需支持多尺寸输入(如检测模型),需导出多个版本(resnet50_224,resnet50_448)并分别配置。
3.3 KServe部署实操:从YAML到可访问服务的七步落地
我们以KServe v0.13(Kubeflow 1.8环境)为例,展示完整部署流程。所有操作均在K8s集群内执行,假设已安装KServe CRD及控制器。
Step 1:准备模型仓库(Model Repository)
在对象存储(如S3/MinIO)或PV中创建标准目录结构:
s3://my-model-bucket/resnet50/ ├── 1/ # 版本号,必须为数字 │ ├── model.pt # TorchScript模型文件 │ └── config.pbtxt # 配置文件 └── config.pbtxt # (可选)根目录config,用于多版本全局配置KServe要求模型路径为<storage-type>://<bucket>/<path>,如s3://my-model-bucket/resnet50。
Step 2:编写InferenceService YAML
apiVersion: "kserve.kserve.io/v1beta1" kind: "InferenceService" metadata: name: "resnet50-classifier" namespace: "kubeflow-user" spec: predictor: # 使用Triton作为底层引擎 triton: # 指向模型仓库 storageUri: "s3://my-model-bucket/resnet50" # 资源申请:关键!必须匹配Triton配置中的gpus resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 # 自定义Triton启动参数(可选) runtimeVersion: "23.07-py3" # Triton镜像版本Step 3:应用配置并验证Pod状态
kubectl apply -f resnet50-is.yaml -n kubeflow-user # 查看Pod是否Running且Ready kubectl get pods -n kubeflow-user | grep resnet50 # 查看KServe事件,确认模型加载成功 kubectl describe inferenceservice resnet50-classifier -n kubeflow-user若Pod卡在ContainerCreating,常见原因是:
- GPU节点污点(taint)未被容忍,需在
InferenceService中添加tolerations; - S3访问密钥未配置,需提前创建
Secret并挂载到KServe控制器。
Step 4:获取服务入口(Ingress Host)
KServe自动创建Istio VirtualService,服务域名格式为:resnet50-classifier.kubeflow-user.example.com
可通过以下命令获取:
kubectl get virtualservice resnet50-classifier-predictor -n kubeflow-user -o jsonpath='{.spec.hosts[0]}'Step 5:发送测试请求(V2协议)
Triton使用标准V2推理协议,请求体为JSON:
curl -X POST "http://resnet50-classifier.kubeflow-user.example.com/v2/models/resnet50/infer" \ -H "Content-Type: application/json" \ -d '{ "inputs": [{ "name": "INPUT__0", "shape": [1, 3, 224, 224], "datatype": "FP32", "data": [0.485, 0.456, 0.406, ...] # 150528个float32值 }] }'注意:shape必须与config.pbtxt中声明一致,且data是展平的一维数组。
Step 6:配置金丝雀发布(渐进式上线)
为新模型resnet50-v2设置10%流量:
apiVersion: "kserve.kserve.io/v1beta1" kind: "InferenceService" metadata: name: "resnet50-classifier" spec: predictor: # 原有v1版本保持90%流量 canaryTrafficPercent: 90 triton: storageUri: "s3://my-model-bucket/resnet50-v1" # 新版本v2占10% canary: triton: storageUri: "s3://my-model-bucket/resnet50-v2" # 可配置自动回滚条件 traffic: 10Step 7:启用请求日志采样
在InferenceService中添加:
spec: predictor: triton: # 启用日志,采样率1% logger: url: "http://elasticsearch:9200" mode: "all" # 记录request & response sampling: 0.01日志将包含request_id、model_name、input_shape、latency_ms、response_status,为故障排查提供黄金线索。
4. 实操过程与核心环节实现:构建生产级可观测性闭环
4.1 指标采集:定义真正有用的5个核心指标
监控不是堆砌图表,而是聚焦影响业务的关键信号。我们摒弃“CPU使用率”这类通用指标,专注以下5个ML专属指标,全部通过Prometheus抓取Triton暴露的/metrics端点:
| 指标名 | Prometheus Query | 业务含义 | 告警阈值 |
|---|---|---|---|
nv_inference_request_success_total{model="resnet50"} | rate(nv_inference_request_success_total{model="resnet50"}[5m]) | 每秒成功请求数(QPS) | < 50%基线值持续5分钟 |
nv_inference_request_failure_total{model="resnet50"} | rate(nv_inference_request_failure_total{model="resnet50"}[5m]) | 每秒失败请求数 | > 1次/分钟持续10分钟 |
nv_inference_request_duration_us{model="resnet50", quantile="0.99"} | histogram_quantile(0.99, rate(nv_inference_request_duration_us_bucket{model="resnet50"}[5m])) / 1000 | P99延迟(ms) | > 200ms持续5分钟 |
nv_gpu_utilization{gpu="0", device="nvidia0"} | avg by (gpu) (nv_gpu_utilization{gpu="0"}) | GPU利用率(%) | < 30%(说明资源浪费)或 > 95%(说明瓶颈) |
nv_inference_queue_length{model="resnet50"} | avg by (model) (nv_inference_queue_length{model="resnet50"}) | 请求队列平均长度 | > 10持续5分钟(说明处理不过来) |
实操心得:我们曾因只监控
nv_gpu_utilization而误判——GPU利用率长期90%,但QPS稳定,P99延迟正常。深入查才知是dynamic_batching配置不当,大量请求在队列中等待凑batch,导致nv_inference_queue_length飙升。永远用业务指标(QPS、延迟、错误率)驱动决策,技术指标(GPU利用率)只是辅助诊断手段。
4.2 日志分析:从海量日志中快速定位“坏请求”
Triton默认日志仅包含启动信息,无请求级详情。我们通过KServe的logger功能将采样请求存入Elasticsearch,并构建Kibana看板。关键技巧:
- 结构化日志字段:KServe日志自动注入
model_name、version、request_id、latency_ms、status_code,无需客户端埋点; - 请求体脱敏:对
data字段自动哈希(SHA256),保留input_shape和datatype供分析,避免敏感数据泄露; - 关联分析:当
latency_ms > 500时,用request_id关联上下游日志(如API网关、特征服务),确认是模型慢还是上游慢。
一次典型故障排查:
- Grafana告警:
resnet50P99延迟突增至1200ms; - Kibana搜索:
model_name: "resnet50" AND latency_ms > 1000; - 发现所有慢请求的
input_shape均为[1,3,1024,1024](远超标准224x224); - 追溯源头:前端上传了未压缩的高清图,特征服务未做尺寸校验;
- 修复:在API网关层增加图片尺寸拦截规则。
没有结构化日志,这种问题只能靠猜。
4.3 追踪(Tracing):绘制一次预测的完整生命线
当服务链路涉及多个组件(如:API网关 → 特征服务 → Triton → 结果缓存),需用OpenTelemetry追踪单次请求。我们在KServe中启用Jaeger集成:
spec: predictor: triton: # 启用OTLP追踪 tracer: enabled: true endpoint: "http://jaeger-collector:4317"一次图像分类请求的Trace包含:
gateway.request(API网关接收)feature_service.get_features(特征服务调用)triton.infer(Triton模型推理,含GPU kernel耗时)cache.set_result(结果缓存写入)
通过Trace的span时间轴,能清晰看到:
- 若
triton.infer耗时长,但kernel_time短,说明是数据传输慢(网络/PCIe带宽); - 若
kernel_time长,说明模型本身计算密集,需优化模型或升级GPU。
我们曾用此方法发现:某次延迟飙升源于feature_service返回的特征向量维度错误(应为128维,实为127维),导致Triton在GPU上做非法内存访问,触发CUDA error,重试机制使延迟雪崩。追踪不是锦上添花,而是定位幽灵问题的唯一探针。
4.4 数据漂移监控:当模型“变笨”时,系统要先于业务感知
模型性能衰减往往悄无声息。我们不依赖离线评估,而是在服务层实时监控输入数据分布:
- 特征统计采集:Triton的
perf_analyzer可输出输入tensor的min/max/mean/std,我们将其作为指标暴露; - 漂移检测算法:对每个数值型特征,计算其7天滑动窗口的均值,当当前值偏离均值±3σ时触发告警;
- 类别型特征监控:统计
input_ids中各token出现频次,用JS散度(Jensen-Shannon Divergence)对比历史分布。
告警示例:
feature_token_freq_js_divergence{feature="user_age_bucket"}> 0.15 → 用户年龄分布发生显著偏移;input_tensor_mean{dim="0"}(代表R通道均值)从0.485突降至0.32 → 图像预处理管道异常,可能漏了归一化。
注意:漂移告警≠模型需重训。我们设置三级响应:
- 一级(轻微漂移):通知数据工程师核查数据源;
- 二级(中度漂移):触发离线评估,生成AUC变化报告;
- 三级(严重漂移):自动暂停该模型流量,切至备用模型。
这套机制让我们在一次营销活动导致用户画像突变时,提前2小时发现特征漂移,避免了线上效果下滑。
5. 常见问题与排查技巧实录:那些文档里不会写的血泪教训
5.1 “模型加载失败:Failed to load ‘resnet50’”——Triton的静默陷阱
现象:KServe Pod状态为Running,但kubectl logs显示Failed to load model 'resnet50',无更多错误。
排查路径:
- 进入Triton容器:
kubectl exec -it <triton-pod> -c kserve-container -- bash; - 手动运行Triton加载命令:
tritonserver --model-repository=/mnt/models --strict-model-config=false --log-verbose=1; - 关键线索在
--log-verbose=1输出中,常见原因:- CUDA版本不匹配:Triton镜像CUDA 11.8,但模型用CUDA 12.1编译 → 解决:统一使用Triton官方镜像,模型导出时指定
torch==1.13.1+cu117; - 缺少.so依赖:自定义OP需链接
libcudnn.so.8,但容器内只有libcudnn.so.8.8.0→ 解决:在Dockerfile中加软链ln -sf libcudnn.so.8.8.0 /usr/lib/x86_64-linux-gnu/libcudnn.so.8; - 权限问题:S3模型文件权限为
600,Triton容器以非root用户运行无法读取 → 解决:S3上传时设ACL=public-read,或在KServe中配置serviceAccountName赋予相应RBAC。
- CUDA版本不匹配:Triton镜像CUDA 11.8,但模型用CUDA 12.1编译 → 解决:统一使用Triton官方镜像,模型导出时指定
独家技巧:在InferenceService中临时添加env变量开启极致日志:
env: - name: TRITON_LOG_VERBOSE value: "1" - name: TRITON_LOG_INFO value: "1"5.2 “P99延迟忽高忽低,像心跳一样”——GPU上下文切换的幽灵
现象:Grafana上nv_inference_request_duration_us曲线呈规律性尖峰,间隔约10秒,峰值达500ms,但QPS稳定。
根因分析:Triton的dynamic_batching在等待max_queue_delay_microseconds(默认10ms)后强制提交batch,若此时GPU正被其他进程占用(如系统监控、日志收集),会导致kernel launch延迟。
解决方案:
- GPU独占:在
InferenceService中添加nvidia.com/gpu.product: "A10G",并配置节点亲和性,确保Triton Pod独占GPU; - 禁用GPU后台任务:在GPU节点上运行
nvidia-smi -r重置GPU,或禁用nvidia-dcgm服务; - 调整batch策略:将
max_queue_delay_microseconds从10000改为5000,牺牲少量吞吐换取延迟稳定性。
实测对比:调整后P99延迟从500ms±300ms收敛至120ms±20ms,抖动消除。
5.3 “新模型上线后,老模型流量没切干净”——金丝雀发布的失效时刻
现象:canaryTrafficPercent: 10配置后,新模型QPS为0,老模型仍承接100%流量。
排查步骤:
- 检查VirtualService:
kubectl get virtualservice resnet50-classifier-predictor -o yaml,确认http.routes中weight分配正确; - 检查KServe控制器日志:
kubectl logs -l control-plane=kserve-controller-manager -n kubeflow,搜索canary关键字; - 关键发现:KServe v0.12+要求
canary字段下的模型必须与predictor同名(即resnet50),否则忽略。我们曾将canary模型命名为resnet50-v2,导致配置无效。
修复:统一模型名为resnet50,通过storageUri区分版本路径,或升级KServe至v0.14+支持独立模型名。
5.4 “Prometheus抓不到指标:connection refused”——服务发现的隐形墙
现象:Triton Pod的/metrics端口(8002)在Pod内curl localhost:8002可访问,但Prometheus抓取失败。
真相:KServe默认未暴露8002端口给Service。需在InferenceService中显式声明:
spec: predictor: triton: # 添加端口映射 ports: - containerPort: 8002 name: metrics protocol: TCP并确保KServe的ServiceMonitor配置了targetPort: metrics。
避坑清单:
- ✅ 确认Triton容器内
netstat -tuln | grep 8002监听0.0.0.0:8002,非127.0.0.1:8002; - ✅ 检查K8s NetworkPolicy是否阻止了Prometheus Pod到Triton Pod的8002端口;
- ✅ 验证Prometheus配置中
scrape_interval不小于15s,避免高频抓取压垮Triton。
5.5 “模型服务突然503,但Pod状态正常”——KServe的健康检查盲区
现象:Triton PodRunning,但curl http://service/health/ready返回503。
深度排查:
- Triton的
/v2/health/ready端点检查的是模型加载状态,非GPU健康; - 运行
nvidia-smi发现GPU显存100%,但nvidia-smi dmon -s u显示utilization.gpu为0% → GPU被其他进程锁死; - 执行
fuser -v /dev/nvidia*发现dockerd进程占用GPU设备文件。
终极解法:
- 在GPU节点上配置
nvidia-container-toolkit的no-cgroups模式,避免Docker daemon抢占GPU; - 或在
InferenceService中添加securityContext强制容器使用hostPID: true,但这降低隔离性,仅作临时方案。
我的经验:线上GPU节点必须禁用所有非必要GPU进程(如nvidia-persistenced、dcgm-exporter),只留Triton和nvidia-smi。我们用Ansible定期巡检,发现异常进程自动kill。
6. 最后一点个人体会:模型服务不是终点,而是新协作的起点
做完Part 4,你手上握着的不再是一个静态的模型文件,而是一个有心跳、有脉搏、会呼吸的生产服务。但真正的挑战,往往在此之后才浮现。我见过太多团队,模型服务上线后,算法工程师就撤出,把运维甩给SRE,结果SRE看不懂config.pbtxt,算法看不懂Prometheus AlertManager的路由配置,出了问题互相扯皮。
我的建议是:把KServe的InferenceServiceYAML当作一份“契约”。它应该和模型代码一起,存放在同一个Git仓库,由算法和SRE共同Code Review。算法负责定义input/output、resource.requests、dynamic_batching参数;SRE负责审核tolerations、nodeSelector、ServiceMonitor配置。每次git push,都是一次跨职能的对齐。
另外,别忘了给业务方一个“自助服务台”。我们做了个简单Web页面,业务方输入样本数据,就能看到:
- 当前模型的预测结果、置信度、P99延迟;
- 近1小时错误率趋势;
- 数据漂移告警摘要。
这比发邮件问“模型还活着吗?”高效得多。
最后分享个小技巧:在Triton的config.pbtxt里加一行version_policy: "latest",然后每次模型更新只改S3里的文件,不用动YAML。这样算法同学就能自主发布,SRE只需管好底座。让流程适应人,而不是让人适应流程——这才是MLOps该有的温度。