从测开转后端三年,我踩过的机器学习部署坑比代码还多

@胡思宇
2025-12-16 22:11
阅读 989

大家好,我是老王(不是那个卖瓜的),坐标杭州未来科技城,白天在某大厂写后端,晚上在出租屋里折腾模型。三年前我还是个测试开发,天天和 CI/CD、接口自动化打交道,结果一不小心被算法组拉去“支援”,从此走上了一条不归路——既要写 API,又要搞模型部署,偶尔还得帮产品解释为什么“AI 不能直接预测用户明天会不会分手”。

上周五凌晨两点,我又一次坐在电脑前,盯着 Prometheus 上飙升的 CPU 使用率,心里默默问候产品经理全家。事情起因很简单:业务方想上线一个用户流失预警模型,要求实时推理、低延迟、高可用。听起来人畜无害对吧?但当你发现这个模型是用 PyTorch 写的,依赖一堆 torch==1.12.0+cu113 这种玄学版本,而生产环境只认 Python 3.8 + CPU 的时候……你就知道什么叫“理想很丰满,现实要重装系统”了。


部署不是 dump 模型就完事了

很多算法同学(没有冒犯的意思)觉得:“我把 .pt 文件给你,你 load 一下不就行了?” 哥,那是 Jupyter Notebook 里!生产环境可不吃这套。

我们一开始也天真地用了 Flask + TorchScript 的方案,本地跑得飞起,一上 K8s 就 OOM。为啥?因为 TorchScript 虽然能序列化模型,但推理时的内存占用和训练时差不多,而且没法做 batch 推理优化。更惨的是,有次半夜报警,日志里全是:

RuntimeError: CUDA out of memory. Tried to allocate 2.00 GiB...

问题是……我们的 Pod 根本没挂 GPU!后来才发现是模型里残留了 .cuda() 调用,算法同事说“本地调试方便”,结果线上直接炸穿。


架构设计:别让算法绑架后端

痛定思痛,我们开始重新思考整个部署链路。核心原则就一条:算法负责效果,后端负责稳定。具体拆解成几个关键点:

  1. 模型必须与服务解耦
    别再把模型塞进主应用了!我们用独立的 inference service,通过 gRPC 对接后端。这样模型升级不用动主站代码,运维也敢重启了(以前改个模型参数,后端兄弟手都在抖)。

  2. 输入输出标准化
    算法给的 feature 工程代码五花八门:有的用 pandas,有的用 numpy,还有的直接传 JSON 字符串。我们强制要求:所有输入必须是 Protobuf 定义的结构体,输出也一样。这样既能做 schema 校验,又能避免“字段突然少了一个”的灵异事件。

  3. 资源隔离与弹性伸缩
    模型推理是 CPU 密集型,而我们的主站是 I/O 密集型。混部署?达咩!我们单独开了 inference 的 deployment,HPA 基于 requests_per_second 自动扩缩容。双11期间流量涨了5倍,自动扩容到20个副本,老板看了直呼内行。


折腾新技术?先过稳定性这关

说到新技术,去年我们团队一度想上 TensorRT 加速,据说能提速3倍。结果折腾一周,发现我们的模型用了 torch.nn.functional.interpolate,TensorRT 不支持动态 shape……最后还是乖乖回退到 ONNX + ONNX Runtime。

ONNX 真香警告

  • 支持跨框架(PyTorch/TensorFlow 通吃)
  • 可以做常量折叠、算子融合等图优化
  • ONNX Runtime 提供 CPU/GPU 统一接口,还能开 quantization

贴一段我们导出 ONNX 的脚本(带血泪注释):

import torch
import onnxruntime as ort

# ⚠️ 必须用 eval 模式!不然 BatchNorm 会崩
model.eval()

# ⚠️ 输入必须是固定 shape!动态 axis 要显式声明
dummy_input = torch.randn(1, 128, dtype=torch.float32)

torch.onnx.export(
    model,
    dummy_input,
    "churn_model.onnx",
    export_params=True,
    opset_version=13,  # 别用最新版!14 以上有些算子 runtime 不支持
    do_constant_folding=True,
    input_names=["input"],
    output_names=["output"],
    dynamic_axes={
        "input": {0: "batch_size"},  # 批量推理的关键
        "output": {0: "batch_size"}
    }
)

# 验证 ONNX 模型是否和 PyTorch 一致
ort_session = ort.InferenceSession("churn_model.onnx")
ort_inputs = {"input": dummy_input.numpy()}
ort_outs = ort_session.run(None, ort_inputs)
print("ONNX 输出:", ort_outs[0][:5])

上线后,P99 延迟从 320ms 降到 95ms,内存占用砍半。运维小哥终于不用半夜被叫起来扩容了(他请我喝了杯瑞幸,值了)。


区块链?别笑,真有用上场景

你可能会问:标题里的 区块链 是来凑数的?还真不是。

我们有个 B2B 场景:给合作银行提供风控模型。银行要求模型推理过程可审计、不可篡改。这时候,传统日志根本不够用——谁知道你是不是偷偷换了模型权重?

于是我们搞了个骚操作:每次推理请求的输入、输出、模型版本、时间戳,都生成一个 hash,写入私有区块链(Hyperledger Fabric)。虽然性能有损耗(加了 15ms 延迟),但满足了合规要求。产品经理拿着这个去跟客户吹“AI+区块链双驱动”,当场签了百万合同。

冷知识:区块链在这里不是为了去中心化,而是为了可信存证。别被 buzzword 吓到,技术选型永远服务于业务。


综合对比:几种部署方案实测数据

为了选型,我们横向测了主流方案(4核8G Pod,batch_size=32):

方案 P99 延迟 (ms) 内存 (MB) 吞吐 (QPS) 稳定性 上手难度
Flask + PyTorch 320 1200 45 ⭐⭐
FastAPI + TorchServe 210 950 70 ⭐⭐⭐ ⭐⭐
ONNX Runtime 95 600 180 ⭐⭐⭐⭐ ⭐⭐
Triton Inference Server 80 550 210 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐

结论很清晰:中小团队闭眼选 ONNX Runtime。Triton 虽强,但运维成本高(要管 model repository、metrics、GPU pooling),适合阿里/字节这种有专职 infra 团队的。


最后一点真心话

从测开转开发这三年,我最大的感悟是:机器学习部署的本质,不是炫技,而是工程妥协的艺术

算法追求 AUC 0.01 的提升,而我们要保证 99.99% 的可用性;产品经理想要“明天就上线”,而我们知道光是模型压测就得跑三天。

但正是这些撕扯,让工作变得有意思。上周模型成功扛住双11流量高峰后,我凌晨三点走出公司,西湖边的空气都是甜的——虽然第二天还得改需求。

如果你也在杭州,搞后端或者 MLOps,欢迎约 coffee。顺便帮我看看有没有跳槽机会?网易和阿里的 HC 还放吗?(狗头保命)

彩蛋:我们开源了内部用的模型部署脚手架,包含 ONNX 导出、gRPC 服务、Prometheus metrics,GitHub 搜 ml-deploy-kit 就能找到。Star 了记得回来夸我!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝