1. 这不是“部署说明书”,而是一份AI训练师的现场作业手记
你搜“AI模型部署”时,页面上堆满“5分钟部署ChatGPT本地版”“Ollama一键安装教程”“免费AI模型合集”,点开一看,全是复制粘贴的命令行截图、没头没尾的config.toml片段、以及一句轻飘飘的“搞定!”。但真实世界里,我上周刚帮一家做工业质检的客户上线一个YOLOv8+ResNet双路推理服务——他们产线每秒产生237帧图像,模型在RTX 4090上推理延迟必须压到82ms以内,否则整条流水线就得停。结果呢?第一次部署后,GPU显存占用飙到98%,API响应时间跳到310ms,产线报警灯直接亮红。最后发现,问题出在PyTorch DataLoader的num_workers参数设成了8,而服务器只有4核CPU,线程争抢把I/O拖垮了。这种坑,不会写在任何官方文档里,也不会出现在“免费模型下载站”的README中。
这本《AI训练师图解_10_管理和部署_应用训练好的AI模型》,核心就干一件事:把“模型部署”从玄学拉回地面。它不讲大道理,不堆术语,只记录一个合格AI训练师在现场会怎么做、为什么这么做、踩过哪些坑、怎么绕过去。关键词里的“AI训练师”不是头衔,是角色——你得懂模型结构,也得会调Linux内核参数;你得看懂loss曲线,也得会查NVIDIA-smi输出里的memory fragmentation;你得和算法工程师聊attention head,也得和运维同事对齐Docker镜像的base OS版本。所谓“管理和部署”,本质是在算力、延迟、稳定性、可维护性四股力量撕扯中,找到那个能持续跑下去的平衡点。适合三类人:刚跑通第一个ResNet的在校生(别急着发论文,先让你的模型在测试机上稳住72小时);带团队落地AI项目的Tech Lead(你签的SLA里写的“99.95%可用性”,背后是几十个配置项的取舍);还有正在评估是否该自建推理集群的CTO(别只算GPU卡多少钱,算算你招一个能搞定CUDA 12.1 + Triton + Prometheus监控栈的全栈AI工程师,年薪多少)。
我见过太多项目死在“训练完成=项目成功”这个幻觉里。模型在Jupyter里acc 98.7%,一放到生产环境,第一天就OOM kill;本地测QPS 1200,上K8s后掉到200,排查三天发现是Service Mesh的sidecar注入导致gRPC header超长被截断;客户说“我们要用ChatGPT”,结果交付时发现他们内网连不上OpenAI API,临时切本地LLM,但RAG检索延迟从300ms涨到2.3秒,用户投诉率翻倍。这些都不是技术不行,是部署环节的系统性思维缺失。所以这本图解,每一章都按真实项目节奏展开:从模型交付物清单怎么写(别再只扔个.pth文件)、到推理服务容器化打包的17个检查点、再到灰度发布时如何设计AB测试分流策略——所有内容,都来自我经手的47个落地项目现场笔记,删掉了所有“理论上可行”的废话,只留“实测有效”的动作。
2. 部署不是终点,而是AI生命周期管理的起点
2.1 “部署”这个词本身,就是个危险的误导
翻开任何AI教材,“模型部署”章节永远排在最后,像给毕业典礼发证书。但现实是:部署不是训练的句号,而是AI服务生命周期的第一个逗号。一个训练好的模型,从产出到真正产生业务价值,要经历至少五个不可跳过的阶段,每个阶段都有明确的交付物和验收标准:
交付阶段:算法团队把模型交给你,不是丢个文件夹就走。必须确认:模型权重格式(.pt/.onnx/.gguf)、输入输出schema定义(JSON Schema or Protobuf)、预处理/后处理代码(独立于训练框架)、硬件依赖清单(CUDA版本、cuDNN、TensorRT)、以及最关键的——性能基线报告(在指定硬件上,batch_size=1/8/16时的latency、throughput、显存占用)。我见过最离谱的一次,算法同事邮件写着“模型已训练好”,附件里只有一个Jupyter Notebook,里面混着数据加载、训练、评估代码,没有一行推理逻辑。这种交付物,连编译都做不到,更别说部署。
封装阶段:把模型变成可执行的服务单元。这里的核心矛盾是抽象层级的选择。用Flask写个HTTP接口?简单,但并发扛不住;用Triton Inference Server?性能强,但学习成本高,且对模型格式有硬性要求(比如ONNX/TensorRT)。我的经验是:小团队或POC项目,用FastAPI+Uvicorn足够,但必须手动实现异步批处理(async batching),否则QPS上不去;中大型服务,必须上Triton,哪怕多花两周学习——它内置的模型热更新、动态批处理、GPU多实例(MIG)支持,是稳定性的基石。去年帮某银行部署风控模型,初期用Flask,日均请求量破50万后,凌晨三点频繁出现ConnectionResetError,切到Triton后,错误率归零,且通过配置
dynamic_batching,同等硬件下QPS提升3.2倍。集成阶段:服务不是孤岛。它要接入API网关(做鉴权、限流)、日志系统(ELK or Loki)、指标监控(Prometheus+Grafana)、链路追踪(Jaeger)。这里最容易被忽视的是健康检查端点的设计。很多人只加个
/healthz返回200,但真正的健康检查必须包含:模型加载状态(model.is_loaded)、GPU显存余量(nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits)、以及一次轻量级推理(用预置的test input跑通全流程)。否则K8s liveness probe可能误判服务存活,流量导进去却卡死。发布阶段:绝对不能“一刀切”。必须有灰度策略:按流量比例(1%/5%/20%...)、按用户ID哈希、或按业务标签(如新注册用户优先)。我们给电商客户做推荐模型升级,采用“按用户城市等级灰度”:先放量给三线以下城市(流量占比12%,但投诉敏感度低),观察72小时无异常后,再扩至二线城市。期间发现新模型对“县域特产”类目召回率下降17%,立刻回滚,避免影响核心GMV。
运维阶段:这才是真功夫。模型会退化(data drift)、硬件会老化(GPU显存泄漏)、依赖库会更新(PyTorch 2.3升级后某些op行为变更)。必须建立模型性能看板:横轴是时间,纵轴是关键指标(p95 latency、error rate、GPU util%),并叠加告警阈值线。当latency连续5分钟>120ms,自动触发告警;当error rate突增300%,自动保存当前模型快照并通知负责人。这套机制,让我们的SaaS客户平均MTTR(平均修复时间)从17小时降到2.3小时。
提示:别信“一键部署”神话。所有自动化脚本(Ansible/Terraform)的前提,是你亲手在裸机上跑通过一遍全流程。我坚持让新人第一周必须用纯bash写一个模型服务启动脚本,包括环境变量设置、进程守护、日志轮转——这不是复古,是建立对系统底层的肌肉记忆。
2.2 模型交付物清单:一份让算法和工程不再互相甩锅的契约
很多部署失败,根源在交付物模糊。算法工程师觉得“模型跑通了”,工程团队觉得“API没文档”。解决办法,是制定一份双方签字的《模型交付物清单》(Model Delivery Checklist),共12项,缺一不可:
| 序号 | 交付项 | 具体要求 | 验收方式 | 常见陷阱 |
|---|---|---|---|---|
| 1 | 模型权重文件 | 格式明确(.pt/.onnx/.gguf),附MD5校验码 | md5sum model.pt对比 | 算法用torch.save(model.state_dict()),但工程需要torch.save(model),加载时报错 |
| 2 | 输入输出Schema | JSON Schema定义,含字段名、类型、约束(如"image_url": {"type": "string", "format": "uri"}) | 用jsonschema.validate()验证样例数据 | 只给Python dict示例,没Schema,导致前端传参格式错误 |
| 3 | 预处理代码 | 独立模块(preprocess.py),接受原始输入,返回模型所需tensor | 手动运行python preprocess.py --input test.jpg | 预处理逻辑写在训练脚本里,耦合严重,无法复用 |
| 4 | 后处理代码 | 独立模块(postprocess.py),接受模型输出,返回业务可读结果 | 运行python postprocess.py --input output.npy | 后处理用PIL.Image.show(),部署时GUI报错 |
| 5 | 硬件依赖清单 | 明确CUDA/cuDNN/TensorRT版本,及最低GPU显存要求 | nvidia-smi+nvcc --version对照 | 写“需GPU”,但没标显存,客户用RTX 3060(12GB)跑不动16GB显存需求的模型 |
| 6 | 性能基线报告 | 在目标硬件上,不同batch_size下的latency(ms)、throughput(req/s)、显存占用(MB) | time python benchmark.py --batch_size 1 | 只测batch_size=1,线上实际用batch_size=8,性能差3倍 |
| 7 | 测试数据集 | 至少100条真实样本(非合成数据),覆盖正常/边界/异常场景 | 用测试集跑通端到端流程 | 只给1条测试图,无法验证泛化性 |
| 8 | 错误码定义 | HTTP状态码+业务错误码(如40001: image_format_not_supported) | 查看API返回的error_code字段 | 只写“返回JSON”,没定义具体code |
| 9 | 日志规范 | 关键路径打点要求(如[INF] model_load_start,[ERR] inference_timeout) | grep日志文件验证打点 | 日志全用print,无法被ELK采集 |
| 10 | 安全要求 | 是否需HTTPS、JWT鉴权、输入长度限制 | 尝试未授权访问,验证拦截 | 忽略输入校验,导致SQL注入或XXE漏洞 |
| 11 | 资源限制建议 | CPU/GPU/内存限制值(Docker run --cpus=4 --gpus=all --memory=8g) | docker stats观察资源使用 | 不设限制,容器吃光宿主机资源 |
| 12 | 回滚方案 | 上一版模型权重+配置备份路径,及一键回滚脚本 | 执行回滚脚本,验证服务恢复 | 没备份旧版,出问题只能重训 |
这份清单,不是形式主义。去年帮医疗影像公司部署肺结节检测模型,算法团队漏了第6项(性能基线),我们按常规配置部署后,发现推理延迟达420ms,远超临床要求的<200ms。追查发现,他们基线测试用的是Tesla V100,而客户采购的是A10,架构差异导致TensorRT优化失效。补测后,调整--fp16和--opt_level=5参数,延迟降至187ms。清单的价值,在于把隐性知识显性化,把“我以为你知道”变成“白纸黑字你签了字”。
2.3 为什么Ollama不是银弹?本地模型部署的真实水位线
热搜词里“免费AI模型Ollama UI”“Ollama部署无限制模型”刷屏,但作为天天和Ollama打交道的人,我必须说:Ollama是极佳的本地开发玩具,但离生产级部署有三道深沟。它解决了“快速跑通”的问题,却回避了“长期稳住”的挑战。
第一道沟:资源隔离与多租户。Ollama默认所有模型共享同一GPU上下文。当你同时加载llama3:8b和phi3:3.8b,它们会竞争显存和计算单元。生产环境要求严格隔离——A部门的客服模型不能因为B部门的报表分析模型OOM而挂掉。Ollama原生不支持,你得自己用cgroups或NVIDIA MPS(Multi-Process Service)硬隔离,而MPS配置复杂,且不兼容所有驱动版本。我们给某政务云平台做方案时,最终放弃Ollama,改用Kubernetes+Triton,每个模型跑在独立Pod里,通过nvidia.com/gpu: 1资源请求强制隔离。
第二道沟:模型热更新与滚动发布。Ollama的ollama pull是全量替换,期间服务中断。生产环境要求零停机升级。Triton通过model_repository目录管理,新增模型版本只需放入子目录,调用tritonserver --model-repository=/models即可热加载,旧版本仍可服务,直到流量完全切走。我们为金融客户做风控模型迭代,每次升级都用此方式,全年服务可用性99.992%。
第三道沟:可观测性深度不足。Ollama的ollama list和ollama ps只显示基础状态。生产环境需要:每毫秒的GPU利用率曲线、每个请求的端到端耗时分解(网络+排队+推理+序列化)、模型层内部各op的耗时占比。Triton原生集成Prometheus metrics,暴露nv_gpu_utilization、inference_request_success等200+指标,配合Grafana可构建完整看板。而Ollama需自行埋点,工作量巨大。
但这不是否定Ollama。它的价值在于降低本地验证门槛。我的标准工作流是:
- 算法用Ollama快速验证新模型效果(
ollama run llama3:8b); - 工程用Triton封装,生成ONNX/TensorRT引擎;
- Ollama仅作为CI/CD中的“冒烟测试”环节——每次PR合并,自动
ollama run跑10条测试用例,通过才触发Triton构建。
这样,既享受Ollama的敏捷,又守住生产的底线。记住:工具没有好坏,只有是否匹配阶段。把玩具当生产系统用,是最大的风险。
3. 实操核心:从模型文件到稳定API的七步炼金术
3.1 第一步:模型格式转换——不是“能跑就行”,而是“跑得最优”
拿到算法给的.pt文件,别急着扔进Flask。先问三个问题:
- 这个模型在推理时,是否用了
torch.compile()或torch.jit.script()? - 输入tensor是否已做
contiguous()? - 是否启用了
torch.backends.cudnn.benchmark = True?
如果答案是否定的,直接部署等于埋雷。我的标准转换流程(以PyTorch模型为例):
1. 导出为TorchScript(首选)
import torch # 加载训练好的模型 model = torch.load("model.pt") model.eval() # 创建示例输入(必须match实际业务shape) example_input = torch.randn(1, 3, 224, 224).cuda() # batch=1, ch=3, h=224, w=224 # 导出为TorchScript traced_model = torch.jit.trace(model, example_input) traced_model.save("model.ts")为什么选TorchScript?它比ONNX更贴近PyTorch原生,保留更多op语义,且支持torch.compile优化。实测在A10 GPU上,TorchScript比原始.pt快1.8倍。
2. 进阶:转ONNX + TensorRT优化
如果追求极致性能,必须走TensorRT。但ONNX导出有坑:
torch.nn.functional.interpolate在ONNX中不支持align_corners=True,需提前替换为torch.nn.Upsample;- 动态shape(如NLP的变长序列)需用
dynamic_axes参数明确定义。
正确导出示例:
torch.onnx.export( model, example_input, "model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={ "input": {0: "batch_size", 2: "height", 3: "width"}, "output": {0: "batch_size"} }, opset_version=17 )然后用TensorRT Builder优化:
trtexec --onnx=model.onnx \ --saveEngine=model.engine \ --fp16 \ --workspace=2048 \ --minShapes=input:1x3x224x224 \ --optShapes=input:8x3x224x224 \ --maxShapes=input:16x3x224x224关键参数解释:
--fp16:启用半精度,速度提升约2倍,精度损失<0.5%;--workspace=2048:分配2GB显存用于优化,太小会失败;--min/opt/maxShapes:定义动态batch范围,让TRT生成最优kernel。
注意:不要迷信“自动优化”。TRT的
--best模式耗时数小时,且不一定最优。我的经验是:先用--fp16+--workspace=1024快速生成,再用trtexec --loadEngine=model.engine --dumpProfile分析瓶颈,针对性调整--minShapes。
3.2 第二步:服务封装——FastAPI还是Triton?选型决策树
面对选择,别凭感觉。用这张决策树判断:
模型是否需GPU加速? ├─ 否 → Flask/FastAPI(CPU推理足够) └─ 是 → ├─ 是否需支持多模型/多版本/热更新? │ ├─ 否 → FastAPI + PyTorch (简单场景) │ └─ 是 → Triton Inference Server (生产首选) └─ 是否需极致性能(<10ms延迟)? ├─ 否 → FastAPI + TorchScript (平衡点) └─ 是 → Triton + TensorRT (唯一选择)FastAPI封装实操(适合中小规模):
核心是解决两个痛点:并发瓶颈和内存泄漏。
- 并发:用
concurrent.futures.ThreadPoolExecutor替代默认async,因PyTorch CUDA操作非完全异步:
from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor(max_workers=4) # 根据CPU核数设 @app.post("/predict") async def predict(request: Request): data = await request.json() # 提交到线程池,避免阻塞event loop future = executor.submit(inference_func, data) result = await asyncio.wrap_future(future) return {"result": result}- 内存:PyTorch默认不释放GPU缓存,需手动
torch.cuda.empty_cache(),但别在每次推理后调用(开销大)。我的做法:每100次请求后执行一次,并监控torch.cuda.memory_allocated(),超阈值(如80%)立即清理。
Triton部署实操(生产级):
目录结构是灵魂:
models/ ├── my_model/ │ ├── 1/ # 版本号 │ │ ├── model.py # 自定义backend(可选) │ │ └── config.pbtxt # 关键!定义输入输出、instance数等 │ └── config.pbtxt # 模型级配置config.pbtxt必须包含:
name: "my_model" platform: "pytorch_libtorch" # 或 "tensorrt_plan" max_batch_size: 16 input [ { name: "INPUT__0" data_type: TYPE_FP32 dims: [3, 224, 224] } ] output [ { name: "OUTPUT__0" data_type: TYPE_FP32 dims: [1000] } ] instance_group [ { count: 2 # 启动2个GPU instance,提升吞吐 gpus: [0] # 绑定到GPU 0 } ]关键技巧:count: 2不是开2个进程,而是TRT在单GPU上创建2个推理上下文,充分利用GPU SM(Streaming Multiprocessor)。实测在A10上,count=2比count=1吞吐提升1.7倍。
3.3 第三步:容器化打包——Dockerfile里的17个生死检查点
一个能上生产的Dockerfile,绝不是FROM pytorch:2.1-cuda11.8然后COPY . /app。以下是必须检查的17个点(按构建顺序):
- Base镜像选择:不用
pytorch:latest,用pytorch:2.1.0-cuda11.8-cudnn8-runtime——runtime版比devel版小40%,且不含编译工具,攻击面更小。 - CUDA版本锁定:
RUN apt-get install -y cuda-toolkit-11-8,而非cuda-toolkit,避免系统升级时CUDA突变。 - Python版本固定:
ENV PYTHONUNBUFFERED=1+RUN python3.10 -m pip install --upgrade pip,防止pip版本不一致。 - 依赖最小化:
pip install -r requirements.txt --no-cache-dir,禁用缓存减少镜像体积。 - 模型文件分离:
COPY models/ /app/models/,但权重文件用.dockerignore排除,构建时挂载或从私有Registry拉取。 - 非root用户:
RUN groupadd -g 1001 -r app && useradd -S -u 1001 -r -g app app,安全基线。 - 工作目录权限:
RUN chown -R app:app /app && chmod -R 755 /app。 - 环境变量声明:
ENV MODEL_PATH="/app/models" TORCH_HOME="/tmp/torch",避免硬编码。 - Health Check:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD curl -f http://localhost:8000/healthz || exit 1。 - 启动脚本健壮性:
ENTRYPOINT ["./entrypoint.sh"],脚本内包含trap 'kill $(jobs -p) 2>/dev/null' EXIT防僵尸进程。 - 日志重定向:
CMD ["gunicorn", "--access-logfile=-", "--error-logfile=-", "app:app"],确保日志输出到stdout。 - GPU支持声明:
LABEL com.nvidia.volumes.needed="nvidia_driver",供K8s识别。 - 镜像扫描:
docker scan --accept-license my-app:latest,检查CVE。 - 多阶段构建:构建阶段装
gcc编译依赖,最终镜像只COPY编译产物,体积减半。 - Layer缓存优化:
COPY requirements.txt .放在COPY . .之前,利用Docker layer cache。 - 安全加固:
RUN apk add --no-cache curl && rm -rf /var/cache/apk/*,清理包管理缓存。 - 镜像签名:
cosign sign --key cosign.key my-app:latest,确保供应链安全。
漏掉任意一点,都可能在上线后爆发。例如第9点(Health Check),某次客户集群升级K8s 1.25,旧版probe超时时间从30s缩为10s,未设--start-period导致服务反复重启。容器化不是打包,是构建可信赖的运行时契约。
3.4 第四步:K8s部署——YAML文件里藏着的十个魔鬼细节
Triton服务跑在K8s上,YAML不是模板填充,是精密手术。以下是十个常被忽略的魔鬼细节:
Resource Requests/Limits:
resources: requests: nvidia.com/gpu: 1 # 必须!否则调度器不知需GPU memory: 4Gi cpu: "2" limits: nvidia.com/gpu: 1 memory: 8Gi # 设limit防OOM kill cpu: "4"陷阱:只设
requests不设limits,OOM时K8s会杀进程;nvidia.com/gpu必须与节点label匹配(kubectl get nodes -o wide查nvidia.com/gpu.product)。Anti-Affinity:
affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: ["triton"] topologyKey: "kubernetes.io/hostname" # 同节点不调度多个pod避免单节点GPU过载。
Init Container预热:
initContainers: - name: model-preload image: nvidia/cuda:11.8.0-runtime-ubuntu20.04 command: ['sh', '-c'] args: ['echo "Loading model..." && nvidia-smi && sleep 30'] # 触发GPU驱动加载解决首次请求慢(cold start)问题。
Liveness/Readiness Probe:
livenessProbe: httpGet: path: /v2/health/live port: 8000 initialDelaySeconds: 60 # 给TRT warmup时间 periodSeconds: 30 readinessProbe: httpGet: path: /v2/health/ready port: 8000 initialDelaySeconds: 30 periodSeconds: 10TRT启动需时间,
initialDelaySeconds必须大于warmup时间。Volume Mount for Models:
volumeMounts: - name: model-storage mountPath: /models volumes: - name: model-storage persistentVolumeClaim: claimName: triton-model-pvc模型文件必须用PVC,避免节点重启丢失。
Security Context:
securityContext: runAsUser: 1001 runAsGroup: 1001 fsGroup: 1001 seccompProfile: type: RuntimeDefault强制非root运行。
Node Selector for GPU Type:
nodeSelector: nvidia.com/gpu.product: A10确保调度到指定GPU型号。
Tolerations for GPU Taints:
tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule"允许调度到GPU节点。
Service Type:
kind: Service spec: type: ClusterIP # 内部服务用ClusterIP,对外用LoadBalancer ports: - port: 8000 targetPort: 8000 protocol: TCP外部访问需额外Ingress或LoadBalancer。
Horizontal Pod Autoscaler (HPA):
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: triton-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: triton-server minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: External external: metric: name: triton_inference_requests_per_sec target: type: Value value: 500CPU指标不够准,必须加外部指标(如Prometheus抓取的
triton_inference_requests_total)。
实操心得:每次K8s部署前,必做三件事:1)
kubectl describe pod查Events,确认GPU调度成功;2)kubectl exec -it <pod> -- nvidia-smi,验证GPU可见;3)curl http://localhost:8000/v2/health/ready,确认TRT服务就绪。跳过任一环,上线即事故。
3.5 第五步:监控告警——不只是看GPU显存,而是读懂模型心跳
生产环境监控,不能只盯着nvidia-smi。必须构建三层监控体系:
基础设施层(Infrastructure):
- GPU Utilization (
DCGM_FI_DEV_GPU_UTIL):持续>95%需扩容; - GPU Memory Used (
DCGM_FI_DEV_MEM_COPY_UTIL):显存泄漏的早期信号; - Node Load Average:CPU过载会拖慢数据加载。
服务层(Service):
- HTTP 5xx Error Rate:>0.1%立即告警;
- P95 Latency:超过SLA阈值(如200ms)持续5分钟告警;
- Request Throughput:突降50%可能意味着上游故障。
模型层(Model):
- Input Data Drift:用KS检验对比线上输入分布 vs 训练集分布,p-value < 0.05触发告警;
- Prediction Confidence Drop:分类模型输出softmax最大值均值,连续下降10%提示模型退化;
- Class Imbalance Shift:某类别预测占比突增300%,可能数据污染。
用Prometheus+Grafana实现:
- 数据源:TRT暴露的
/metrics端点 + 自定义Exporter(抓取模型层指标); - 告警规则:
ALERT TritonLatencyHighIF histogram_quantile(0.95, rate(triton_inference_latency_us_bucket[1h])) > 200000FOR 5mLABELS {severity="critical"}ANNOTATIONS {summary="Triton p95 latency > 200ms"}
关键经验:监控不是“看大盘”,而是“设哨兵”。我们给每个模型部署一个“哨兵服务”,每5分钟用真实业务数据调用一次,记录latency和output,单独建Dashboard。当哨兵数据异常,比主服务告警早3-8分钟——这是黄金响应时间。
3.6 第六步:灰度发布——用流量切分代替“赌一把”
上线新模型,绝不“全量发布”。标准灰度流程:
准备阶段:
- 新模型部署在独立Deployment(
triton-server-v2),Service指向新旧两个Endpoint; - 配置Istio VirtualService,定义流量路由:
trafficPolicy: loadBalancer: simple: ROUND_ROBIN http: - route: - destination: host: triton-server-v1.default.svc.cluster.local weight: 90 - destination: host: triton-server-v2.default.svc.cluster.local weight: 10
- 新模型部署在独立Deployment(
验证阶段:
- 监控新模型的
triton_inference_requests_total和triton_inference_errors_total; - 抽样对比新旧模型输出:用1000条线上请求,计算输出差异率(如分类标签不同占比);
- 检查新模型资源消耗(GPU util, memory),是否显著高于旧版。
- 监控新模型的
渐进阶段:
- 每2小时增加10%流量,直至100%;
- 每次增量后,人工抽检100条结果,确认业务逻辑正确(如推荐列表相关性)。
熔断机制:
- 当新模型
error_rate > 1%或latency_p95 > 150% of baseline,自动回滚:kubectl patch virtualservice triton-vs -p '{"spec":{"http":[{"route":[{"destination":{"host":"triton-server-v1.default.svc.cluster.local"},"weight":100}]}]}}'
- 当新模型
去年做电商搜索模型升级,灰度到50%时,发现新模型对“iPhone 15”类查询召回率下降,但对“安卓手机”提升。立刻暂停,定位到新训练数据中iOS设备占比失衡。灰度不是流程,是决策缓冲带。每一次流量切换,都是用数据投票。
3.7 第七步:回滚与复盘——事故不是终点,而是知识沉淀的起点
无论多严谨,事故总会发生。关键在回滚速度和复盘深度。
回滚SOP:
- Step 1:
kubectl rollout undo deployment/triton-server-v2(5秒内完成); - Step 2:验证旧版Service健康:
curl http://triton-old/v2/health/ready; - Step 3:检查日志确认无残留错误;
- Step 4:通知业务方,提供影响时段和请求数统计。
**复盘会议(Blameless Postmortem