把模型塞进外卖App要踩多少坑

笑看风云
2026-08-13 23:41
阅读 799

我在美团外卖做了四年Java后端,主要跟订单链路和高并发打交道。这两年算法团队的深度学习模型一个接一个往线上推,后端跟着折腾了不少部署方案。最近准备跳槽,刷面经发现机器学习部署被问得越来越频繁,索性把踩过的坑整理出来。

从一次线上事故说起

去年夏天,算法组推了一个新的配送时长预估模型,双塔结构,线上QPS峰值约两万。他们用Flask包了个HTTP服务,单机压测还行,一上生产直接被打爆。日志里全是timeout,订单详情页配送时间显示不出来。

排查下来问题很典型:Flask默认同步阻塞,模型推理那几百毫秒把worker线程全占住,请求排队排到天荒地老。后来紧急加了gunicorn多worker配置,又上了模型结果缓存,才勉强扛住。

模型部署不是起个HTTP服务就完事,得考虑并发模型、超时控制、降级策略、版本管理一堆东西。

模型服务化的基本盘

别用Flask直接裸奔。我们统一迁移到了FastAPI加uvicorn的组合,异步支持好,性能强不少。几个关键点得抠死。

模型加载要放在进程启动时,别每次请求都加载。正确姿势是用startup事件:

from fastapi import FastAPI
import torch

app = FastAPI()
model = None

@app.on_event("startup")
async def load_model():
    global model
    model = torch.load("/data/models/eta_model_v3.2.pt")
    model.eval()

推理必须上线程池隔离。PyTorch推理是CPU密集型的,在async函数里直接调用会阻塞整个事件循环。我们用asyncio.to_thread把推理扔到线程池,配合uvicorn多worker模式,单机QPS从几百提到两三千。

版本管理和灰度发布

模型迭代比后端代码快多了,算法组一周能出好几个实验版本。我们搭了个模型版本管理服务,模型文件统一放对象存储,线上通过配置中心指定加载版本。

灰度踩过一个坑:模型输出格式变了,新版本返回字典,老版本返回数组,灰度时下游解析直接炸。后来定了规矩:模型接口的输入输出schema必须向后兼容,不兼容的走新接口。灰度策略用用户ID哈希分流,出问题随时回滚,比按百分比随机靠谱。

模型压缩和推理优化

配送时长预估对时延极度敏感。原始模型几百兆,单次推理CPU上要四五百毫秒,肯定不行。我们试了几条路:

优化手段 推理耗时(CPU) 模型体积 线上效果
原始PyTorch 480ms 620MB 不可用
ONNX Runtime 210ms 580MB 时延仍偏高
ONNX + INT8量化 95ms 160MB 精度损失0.3%,可接受
TorchScript + 多线程 180ms 600MB 部署复杂,放弃

最后选了ONNX加INT8量化,推理耗时压到一百毫秒以内,精度掉得不多。量化不是万能的,带LayerNorm的Transformer量化完精度能掉两三个点,得先在小数据集上验证。

监控和降级

模型服务最怕挂了之后影响主链路。我们的做法:所有模型调用都包一层超时和熔断,超时时间设得比P99略高,配送时长预估设300毫秒,超时返回兜底值,比如历史均值。用户感知不明显,但系统不会雪崩。

监控维度除常规的QPS、错误率、时延,还要加模型特有指标:推理耗时分布、输入数据分布漂移、空值率。有一次某模型输入字段缺失率从1%涨到15%,一查是上游改了字段名,模型还在读老字段,等于拿空值推理,输出全偏了。没有输入监控根本发现不了。

最后说几句

机器学习部署说到底是个工程问题,不是算法问题。模型再牛,服务写得烂、监控不到位、降级没有,上线就是定时炸弹。后端同学别觉得这是算法组的事,真出问题第一个被叫起来的还是我们。

评论 0

最热最新
暂无评论
笑看风云Lv.1
0
影响力
0
文章
0
粉丝