机器学习部署不是终点,而是另一段旅程的起点

代码轻食主义
2025-06-19 13:48
阅读 736

作为一名有五年工作经验的人工智能工程师,我走过不少弯路,也踩过不少坑。如果说模型训练是“炼丹”的过程,那模型部署就是把这枚丹药真正送上战场。

从刚开始接触Keras搭个本地服务就当生产环境,到后来亲手搭建起支持高并发、低延迟的微服务系统,中间经历了太多血泪教训。今天我就想以第一人称视角,跟大家聊聊我在真实项目中如何一步步完成机器学习系统的部署落地,包括遇到的挑战、踩过的坑、总结的经验。


背景介绍:一次真实的业务需求

数据科学流程-1

背景介绍:一次真实的业务需求

事情还得从两年前讲起。当时公司要做一个电商推荐系统升级,核心目标是提升点击率CTR和转化率。我们团队决定从规则系统过渡到基于机器学习的推荐排序模型。

数据方面,我们有一年的用户行为日志,包括点击、加购、下单等事件;商品特征、用户画像也在数据仓库中有存储。最终要部署的是一个二分类模型,用来预测用户对某个商品的点击概率。

我们的线上服务用Java写成,对外提供RESTful接口,而模型这边我是用PyTorch写的,这就面临了第一个问题:

模型怎么在生产环境中跑起来?要不要改语言?能不能扛住访问压力?


遇到的挑战:模型上线不是训练完就完事了

遇到的挑战:模型上线不是训练完就完事了

挑战一:服务响应慢得像蜗牛爬

最初我直接在Flask里加载模型做预测,然后通过Gunicorn跑了几个worker,本地测试没问题,但一旦放到服务器上,QPS(每秒请求数)稍微上来一点,RT(响应时间)就开始飙升。

原因分析:

  • Flask本身性能有限
  • 每次预测都在主线程里做数据处理和推理,没有异步
  • 没有考虑请求队列、负载均衡

挑战二:模型更新麻烦,每次上线都要停机

因为当时模型更新流程完全手动,需要登录服务器替换模型文件,重启服务。这不仅影响线上体验,还容易出错。

挑战三:模型效果不稳定,上线后突然掉点

训练时AUC是0.79,上线后变成了0.71。检查之后发现,特征预处理不一致。训练阶段用的是Spark处理的数据,线上用Pandas单独实现的逻辑,有些地方没对齐。

这个问题让我意识到:

模型表现的好坏,不仅取决于模型本身,更在于整个链路是否准确无误地还原训练环境。


解决方案:构建稳定可靠的机器学习部署系统

解决方案:构建稳定可靠的机器学习部署系统

为了解决这些问题,我逐步搭建了一个端到端的ML部署架构,核心组件包括以下几个模块:

  1. 特征服务(Feature Store):统一离线与在线特征计算逻辑
  2. 模型服务(Model Serving):使用FastAPI + TorchScript + gRPC
  3. 模型版本管理(Model Registry):配合CI/CD流程自动发布
  4. 监控系统(Monitoring):实时跟踪服务健康、模型偏差

接下来我会逐一展开。


技术选型与架构设计思路

特征一致性:构建轻量级Feature Store

为了避免线上线下特征不一致的问题,我决定抽象出一层“特征服务”。这部分并不是完整的Feature Store平台,而是将核心特征逻辑封装成Python包,供训练与线上共同调用。

举个例子,用户的最近点击次数、平均停留时长这些统计特征,在训练时是从Parquet文件读取的,在线上则是从Redis中获取历史数据,然后统一调用feature_utils.py中的函数进行处理。

# feature_utils.py

def calc_avg_stay_time(click_events):
    if not click_events:
        return 0.0
    return sum(e['stay_seconds'] for e in click_events) / len(click_events)

这样确保训练与推断过程中使用的特征逻辑完全一致。


推理服务优化:采用TorchScript + FastAPI + Gunicorn + Uvicorn

为了兼顾易用性和性能,我采用了如下组合:

  • 使用PyTorch的torch.jit.script()导出模型,得到.pt格式模型文件
  • 使用FastAPI构建HTTP服务(替代Flask)
  • 多Worker运行(Gunicorn + Uvicorn)

代码片段如下:

import torch
from fastapi import FastAPI

model = torch.jit.load("ctr_model.pt")
app = FastAPI()

@app.post("/predict")
async def predict(features: FeatureInput):
    with torch.no_grad():
        output = model(torch.tensor(features.data))
    return {"score": float(output.item())}

为什么选择FastAPI?

  • 自带类型提示,便于参数校验
  • 支持异步处理
  • 内置Swagger文档,方便调试

模型热加载:支持不停机更新

为了让模型更新不影响线上服务,我借鉴了TensorFlow Serving的设计思路,实现了简单的模型热加载机制。

基本做法是在服务启动时加载多个模型版本,并通过配置切换当前活跃的版本。这样新模型准备好后,可以通过一个POST接口触发切换。

class ModelService:
    def __init__(self):
        self.models = {}
        self.active_version = None
    
    def load_model(self, version, path):
        self.models[version] = torch.jit.load(path)
    
    def set_active_version(self, version):
        if version in self.models:
            self.active_version = version
    
    def predict(self, features):
        model = self.models[self.active_version]
        return model(torch.tensor(features))

实际踩过的坑与解决经验

坑一:模型过大导致冷启动慢

第一次部署时,模型文件大小超过5GB,每次加载要等十几秒,严重影响自动化流程。

解决办法:

  • 对模型进行剪枝(prune)和量化(quantize)
  • 使用混合精度训练
  • 将输入层压缩至较小维度,减少参数数量

最终模型体积缩小到了不到600MB,加载速度明显改善。

坑二:线上推理超时多

一开始我们设定单个预测接口最大响应时间是200ms,但在高峰期经常出现超时。

排查发现:

  • 模型推理占用了大量CPU资源,尤其是批量转换tensor的过程
  • 一些请求中包含异常大尺寸的特征向量

改进措施:

  • 启用批处理(Batching),将多个请求合并处理(参考TorchServe)
  • 对输入特征进行标准化校验,限制特征维度范围
  • 使用C++编写特征预处理部分(后续才做的)

坑三:模型偏移检测缺失,性能下降未被及时发现

一段时间后,模型效果开始变差,但没人注意到。直到BI报告说CTR下降了,才发现特征分布发生了变化。

于是我们引入了两个关键指标监控:

  • 特征分布偏移:对关键特征做漂移检测(KL散度/KS检验)
  • 模型预测分布变化:观察预测分数整体分布的变化趋势

监控这块我们使用了Prometheus + Grafana,搭配自定义埋点脚本。


上线后的效果与收益

部署完成后,我们的推荐系统取得了以下成果:

指标 上线前 上线后
CTR 2.3% 3.8%
转化率 0.7% 1.1%
平均响应时间 180ms 90ms
QPS容量 ~300 ~1200

同时,研发侧的效率也提高了:

  • 模型上线时间从小时级缩短到分钟级
  • 所有变更都纳入Git版本控制和CI/CD流程
  • 线上模型状态可实时追踪,具备故障回滚能力

经验总结与建议

数据科学流程-2

经过这一整套实践,我想分享几条经验给正在或即将部署模型的朋友:

✅ 模型部署不是“写个接口”那么简单

它涉及到方方面面:特征工程的一致性、服务稳定性、性能优化、版本控制、监控告警……可以说,模型只是冰山一角,水下才是真正的工程挑战。

✅ 不要把所有希望寄托在一个框架上

虽然现在有很多工具(如TF Serving、ONNX Runtime、TorchServe)可以简化部署,但它们不一定适用于你当前的场景。理解底层原理更重要,这样才能因地制宜地做调整。

✅ 先跑通再优化,避免过度设计

不要一开始就追求高性能、分布式、弹性伸缩,先保证整个链路能走通,再逐步完善细节。比如可以先跑单节点服务,再考虑横向扩展。

✅ 监控一定要提前做好

很多问题刚发生时信号很轻微,等影响用户体验了再查就已经晚了。提前搭建好监控体系,才能第一时间发现问题。

✅ 学会从失败中学东西

我曾经花了一整天排查一个模型性能下降的问题,结果发现是忘了归一化。事后复盘发现:这种错误完全可以靠单元测试+回归验证集提前暴露。


最后想说几句

机器学习从来不是一个孤立的模块,它是融入整个工程体系的一部分。作为一个AI工程师,除了懂得建模,还要理解数据流向、服务交互、运维管理,甚至有时候要写点前端接口(笑)。

在这个不断演进的技术世界里,部署方式也会随着业务发展和技术进步而不断变化。无论是走向Kubernetes编排、服务网格、还是LLM带来的新范式,我们都应该保持开放的心态去拥抱变化。

如果你现在正站在部署前线,希望这篇文章能帮你少踩几个坑。如果你已经经历过这些,欢迎留言交流你的心得——咱们一起成长!


作者简介:
一位热爱AI工程化的开发者,喜欢用最简单的方式讲清楚复杂的事。目前专注于推荐系统、大规模机器学习系统的设计与落地。欢迎关注我的公众号【AI工程手记】,我们一起聊点真技术。

评论 0

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