外观
模型部署的常见陷阱,是一批「症状看起来千奇百怪、根因翻来覆去就那几样」的经典事故模式。 为什么值得一篇专门的合集?因为部署事故有极强的规律性:团队踩的坑 80% 是同样的十来个,而每个坑从「线上出问题」到「定位根因」平均要消耗数小时到数天——提前知道这些模式,等于给排查装上「预置的怀疑列表」。这篇按「症状 → 原因 → 解法」结构收录十个最经典的坑与反模式,每个坑标注关联页面,最后给出部署前 10 条自检清单。配套的完整动手流程见 从零部署一个模型。
阅读方式
先对症状,再查根因。 线上出问题时翻到对应条目,按「解法」里的排查路径走。读一遍 + 踩一遍,比背十遍有用。
坑① 训练/推理预处理不一致
- 症状:离线验证集 AUC 0.92,上线后准确率断崖下跌 10-20 个点;排查代码、环境都「没区别」。
- 原因:训练侧和推理侧对输入的处理不一致——常见三类:归一化参数不同(训练用
mean=[0.5,0.5,0.5],推理手写x/255忘减均值);图像通道顺序(训练 RGB,推理 BGR);文本 tokenizer 版本不同(训练用tokenizers旧版,推理换了新版)。模型对输入分布的假设被悄悄破坏。 - 解法:
- 预处理代码只写一份:定义在
preprocess.py,训练加载和推理加载同一个函数(见 从零部署一个模型 的做法); - 归一化参数随模型走:导出时写进
meta.json,推理类读取,杜绝「两处硬编码」; - 上线前做端到端一致性测试:同一张图,训练代码的输出 vs 服务接口的输出,
np.allclose(atol=1e-4)。
- 预处理代码只写一份:定义在
为什么「代码看着一样」还是不一样
最隐蔽的变体是顺序不同:训练先归一化再 resize,推理先 resize 再归一化——数值完全不等。比对用「数值级」断言,别用肉眼。
坑② 数值精度差异:引擎 FP16 与训练 FP32
- 症状:用 TensorRT/Triton 加速后,个别请求结果错误、分数异常,整体精度略降但偶尔「离谱」。
- 原因:推理引擎为了提速默认用 FP16(半精度),而训练是 FP32。FP16 的动态范围比 FP32 窄(最大 ~65504),归一化分布偏大或数值剧烈变化的模型(尤其 RNN、attention 的 softmax 前 logits)会溢出/掉精度。
- 解法:
- 引擎推理输出 vs 原框架输出做数值比对,设置允许误差(FP16 通常允许 1e-2 量级差异,差异到 1e0 就是问题);
- 关键层(如 softmax 前的 logits)保留 FP32 计算;
- 换引擎必须重新过一遍离线评估,FP16 的精度损失不会报错,只会悄悄劣化。格式与引擎差异见 模型格式与转换。
坑③ 量化后精度崩塌未验证
- 症状:模型「变快了 2 倍」,但线上转化率掉了 15%,回看才发现 INT8 模型从来没跑过验证集。
- 原因:量化流程跑了,验证环节没跑——或是校准数据用错了(随机噪声/单类别),或是量化了本就敏感的层。INT8 把激活值压到 256 级,分布一错,误差放大成系统性偏差。
- 解法:
坑④ 显存泄漏与 OOM
- 症状:服务跑 3 天后开始报 OOM、显存不足;重启后正常,几天后复发;P99 随时间缓慢爬升。
- 原因:四类高发源——① 长连接/请求对象未释放(请求体、图片 bytes 持有引用);② 推理引擎缓存无界增长(TensorRT 的 workspace、ONNX Runtime 的 arena 配置不当);③ 每请求创建会话(
InferenceSession每请求 new 一个);④ 批处理队列无限积压。 - 解法:
坑⑤ worker 数与模型重复加载
- 症状:4 worker 的容器,显存/内存是预期的 4 倍;一台 16GB 卡只够跑 2 个 worker;模型加载变慢拖长启动。
- 原因:进程模型 worker(如
uvicorn --workers 4)每个进程独立加载一份模型。模型 3GB 时,4 worker 就是 12GB。新手常误以为 worker 共享模型内存。 - 解法:
- 先算账:
worker 数 × 模型体积≤ 内存/显存的 70%; - 模型小、QPS 低 →
workers=2甚至 1 + 异步足够; - 模型大 → 用 shared memory / 单进程多线程架构,或把模型加载与请求进程分离(如 Triton 的模型并发实例由引擎管理,见 NVIDIA Triton 多模型服务);
- 启动时做模型预热(加载后先跑 1-2 个 dummy 请求),否则第一个真实请求延迟爆表。
- 先算账:
坑⑥ 无监控裸奔:上线不带指标
- 症状:上线即裸奔;两周后业务说「效果变差了」,你没有任何数据能回答「从哪天开始、差多少、哪类流量最差」。
- 原因:上线前只做了功能验证,没埋指标、没接监控。模型系统还有一个特殊放大器:数据漂移——输入分布变了,模型效果自然退化,没有监控就永远发现不了(监控与可观测性 讲了漂移为何必须盯)。
- 解法:
- 上线即带黄金指标:RPS、P99、错误率、饱和度(GPU 利用率/排队数);
- 指标上线的验收标准是「up == 1 且有人看」,不是「metrics 端点能访问」;
- 漂移检测:输入分布监控(均值/方差/类别分布),阈值触发告警;
- 完整落地配置见 可观测性落地。
坑⑦ 一次性全量发布:无灰度
- 症状:新模型全量上线的第二天,客服工单爆了;想回滚,发现回滚命令没准备、旧镜像被覆盖了。
- 原因:把「离线验证通过」当成了「上线安全的充分条件」。模型效果依赖线上数据分布,离线集再大也是抽样;全量发布等于把「验证」甩给了真实用户。
- 解法:默认金丝雀:5%→20%→50%→100% 逐档观察,自动回滚条件提前配置并演练。三层回滚(模型/特征/代码)版本配套。完整流程见 发布策略:灰度与回滚。
坑⑧ 把在线推理当批处理用(或反之)
- 症状(两个方向):
- 在线方向:把 batch 预测接口当单条用——每次请求都送 batch=256 的输入,GPU 计算 256 条只返回 1 条,延迟和成本爆炸;
- 批处理方向:用在线服务逐个处理 100 万条离线数据,跑了 3 天,把服务打挂。
- 原因:混淆了两种工作负载的形态。在线推理(online)是低延迟、高并发、逐条响应;批处理(batch)是高吞吐、可排队、整体完成。两者的资源策略、超时设置、调度方式完全不同。
- 解法:
坑⑨ 缓存污染与一致性
- 症状:A/B 或灰度期间新旧版本效果对比失真;用户反馈「我上一秒看到的结果,刷新后不一样还自相矛盾」。
- 原因:缓存键没带模型版本。新旧模型共用同一个缓存键时,先请求的版本决定后请求的内容——灰度期间的「效果差异」可能只是缓存命中差异,不是模型差异。
- 解法:
- 缓存键纳入
model_version、feature_version; - 灰度期间对易变缓存做隔离(新版本旁路缓存或用独立缓存前缀);
- 明确缓存内容与 TTL:结果缓存、特征缓存、tokenizer 缓存各自的失效策略;
- 一致性相关的完整讨论见 模型服务 的状态管理。
- 缓存键纳入
坑⑩ 压测数据与线上不符:热身不足、缓存虚高
- 症状:压测说能扛 1000 QPS、P99 50ms,上线第一天真实流量 300 QPS 就 P99 500ms;或被问「压测数据怎么来的」答不上来。
- 原因:三类典型——① 热身不足:刚启动就压,JIT/缓存未热,数字虚低或虚高;② 缓存虚高:压测请求全命中缓存(重复参数),真实流量零命中;③ 压测机成瓶颈:压测机先打满,测出的不是服务能力而是压测机能力。
- 解法:
- 正式记录前预热 30-60 秒;稳态场景压 ≥ 10 分钟;
- 压测请求参数随机化,单独测「无缓存命中」的真实路径;
- 记录环境四件套(压测机规格、worker 数、并发、时长),数字可复现——规范见 压测与容量规划。
部署前 10 条自检清单
- [ ] ① 预处理代码单份定义,归一化参数随模型走,一致性测试通过;
- [ ] ② 换引擎后输出已与原框架数值比对,误差在预期范围;
- [ ] ③ 量化后已验证集评估,掉点在业务阈值内;
- [ ] ④ 内存/显存曲线 10 分钟压测平稳,无增长趋势;
- [ ] ⑤ worker 数 × 模型体积算过账,且启动有模型预热;
- [ ] ⑥ 黄金指标已埋点,
up == 1,监控有人看; - [ ] ⑦ 发布走灰度,自动回滚条件配置并演练过;
- [ ] ⑧ 在线/批处理负载分离,在线服务有超时与限流;
- [ ] ⑨ 缓存键含版本,灰度期缓存隔离;
- [ ] ⑩ 压测报告含环境、预热时长、参数随机化说明,结论可信。
检查清单
- [ ] 能说出自己服务最容易踩的三个坑及对应预案;
- [ ] 事故复盘按「症状→原因→解法」记录,沉淀进团队 wiki;
- [ ] 每个坑的关联页面已收藏/熟读,需要时 10 分钟内查到解法;
- [ ] 自检清单挂在发布流程里(PR 模板/发布单勾选项)。
延伸阅读
- 从零部署一个模型 —— 本合集每个坑在最小闭环里的正确做法
- 发布策略:灰度与回滚 —— 坑⑦ 的完整解法
- 可观测性落地 —— 坑⑥ 的完整解法
- 模型优化实战 —— 坑③ 的完整解法
- 压测与容量规划 —— 坑⑩ 的完整解法
- 模型格式与转换 —— 坑② 的格式与精度背景
参考资料
- Google SRE 手册(生产事故复盘文化):https://sre.google/sre-book/postmortem-culture/
- 十二要素应用(进程模型与配置):https://12factor.net/
- NVIDIA Triton 动态批处理文档:https://github.com/triton-inference-server/server/blob/main/docs/user_guide/dynamic_batching.md
- PyTorch 内存管理与泄漏排查:https://pytorch.org/docs/stable/notes/faq.html