ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI工程化实战:从数据管道到推理服务的完整落地指南

AI工程化实战:从数据管道到推理服务的完整落地指南 作为一个在传统后端写了几年代码的人我第一次听到 ai-engineering 这个词时以为它只是机器学习模型训练的代名词把模型精度调上去就万事大吉。直到我自己动手把一个训练好的模型真正接进业务系统才意识到事情完全不是这样。模型在 notebook 里跑得再好到了线上也会因为特征对不上、依赖缺失、GPU 空转、数据漂移而一夜崩盘。这篇内容就是围绕我发起的 ai-engineering-from-scratch 项目的完整复盘从零开始搭一套能跑、能迭代、能扛住线上流量的 AI 工程链路。它不是一个算法教程而是一份工程化实战记录适合那些已经能训练模型、却还不清楚模型之后该怎么活的开发者读一读。1. 一次失败上线让我重新定义什么叫AI工程1.1 从调好模型到养好模型的转变我的项目起点非常狼狈。当时团队里有个文本分类模型离线测试 F1 有 0.92上线后第二天直接掉到 0.81业务方跑来质问是不是模型训练出了问题。我第一反应是检查训练代码折腾了半天发现模型参数和训练流程都没问题。后来排查了一整天才找到真正的病根线上服务的请求特征里有一个字段在离线训练时用的是标准化之后的数值而线上服务把原始未标准化数据直接送进了模型。训练和推理的特征处理逻辑不一致这是一个极其低级但极其常见的 AI 工程事故。那次之后我开始意识到AI 落地最大的阻碍不是模型精度而是模型之外的那一圈工程链路。模型只是 AI 系统里的一个组件数据管道、特征存储、实验追踪、推理服务、监控告警、反馈回炉这些环节环环相扣任何一个掉链子模型精度再高也白搭。这就是我把项目命名为 ai-engineering-from-scratch 的原因——我想要的不是一篇篇算法论文而是一套从数据到部署的完整工程能力。1.2 为什么照抄教程的三层结构会卡住网上大量教程都把 AI 应用分成三层数据层、模型层、应用层听起来清晰动手就懵。因为真实场景里的每一层都有无数细节教程不会告诉你。数据层不是读个 CSV而是要考虑数据从哪个业务系统来、多久同步一次、Schema 变化了怎么办、脏数据要不要拦截模型层不是训练一个模型而是实验怎么记录、代码和数据集版本怎么对齐、GPU 资源怎么分配、换了一个环境能不能复现应用层也不是写个 Flask 接口而是模型何时加载、并发多大、超时怎么处理、模型更新怎么灰度、线上特征和训练特征如何保持一百个一致。我按照传统三层架构去做卡在数据和模型之间长达两周。后来我把抽象层换成数据管线、模型生命周期、推理服务、反馈闭环这四个横向切面整个系统才逐渐清晰。这也是 ai-engineering-from-scratch 最重要的一个设计决策用工程生命周期取代算法分层来组织项目结构。维度传统教程侧重实际工程侧重数据清洗、特征选择血缘、版本、一致性、延迟模型网络结构、训练技巧实验记录、可复现性、部署方式系统调用流程可用性、可观测性、成本1.3 AI 工程的基本盘数据、训练、推理、反馈在我最终形成的框架里任何 AI 工程都绕不开四个基本盘。第一是数据入口。模型吃进去的东西决定了模型的天花板。这里的关键不是用了多少数据而是数据的获取是否稳定、Schema 是否可控、训练数据和线上数据是否同源。第二是训练与实验。这里的关键不是调参而是每跑一次实验都能清楚知道数据集、代码、超参数分别是什么且能一键复现。第三是推理服务模型要以接口、批处理或流式的方式稳定对外提供能力支撑不同的调用方。第四是反馈闭环生产和监控数据要能回流进训练集让模型可以持续升级而不是上线即终点。这四个基本盘听上去不复杂但每个盘子里都塞满了工程细节。后面几节我就按这个框架把 ai-engineering-from-scratch 从零到跑通的实际过程拆开来讲包括每一步的选型逻辑、代码结构、踩坑经历。你会发现我给出的不是一个最优解而是一个普通人能落地的横切面方案。2. 地基工程数据管线和特征存储先解决吃进去的东西2.1 第一版数据管道Airflow 分区表项目刚开始数据团队给我的数据是每日凌晨导出的若干 CSV 文件存放在共享存储里。模型训练和数据调研阶段这样没问题但一旦进入持续迭代人工下载、手动预处理的路子就走不通了。我第一版选择的是 Apache Airflow 加 Postgres 分区表原因很朴素团队里没人真正上手过复杂的流式计算Airflow 是因为它做任务编排最成熟文档多出错也容易搜到答案。我的 DAG 设计得很简单清晰每天 0 点触发一个任务链检查源文件是否到达、做完整性校验、清洗转换、写入按日期分区的目标表、最后在表里打上数据日期标签。这样做有一个立竿见影的好处训练和回测只需要指定WHERE dt 2024-05-12就能拿到完全一致的数据切片再也不用在目录里翻找哪个文件是昨天、哪个文件是新版。对于从零起步的 AI 工程来说数据隔离能力比数据量更重要它能让你永远知道自己在用什么数据训练。2.2 特征一致性的坑训练和推理用同一份特征逻辑我在第一节里提到的最初事故就是典型的特征一致性问题。那时候训练代码里写了一个函数做标准化线上推理代码里又写了一个差不多的函数看起来没问题结果一上线就露馅。这个坑几乎每个 AI 工程都会踩一遍最佳实践是特征处理逻辑只写一次训练和推理共用同一份代码。在 ai-engineering-from-scratch 里我把所有特征变换做成了一个独立 Python 包features/里面每一个处理函数都有明确的输入输出定义和单元测试。离线训练时直接 import 这个包来构造训练集线上服务启动时同样加载这个包里的函数处理请求特征。这样虽然多了一个依赖但换来的是训练特征 线上特征的硬保证。你还可以用更严格的手段校验把线上请求的原始特征记录一部分离线用相同函数重新处理对比结果差异。一旦有偏差报警立刻响。2.3 什么时候需要真正的特征存储AI 工程里常听到特征存储这个词但很多人一上来就上 FeatStore 这类系统结果维护成本比自己写特征逻辑还高。我在项目里的判断标准很简单如果特征被多个模型共用或者线上推理要求特征延迟低于几十毫秒才值得引入专门的特征存储如果只是单模型消费自己的离线特征一张分区表完全够用。后来我的项目发展到三个模型共享一批用户画像特征每次重复从原始表计算耗时太长才把公用的画像特征抽出来单独固化形成了自己的小型特征层。这个阶段的特征是批处理产出的延迟在秒级我依然没有上重型系统只是用每日定时任务把它们写入一张宽表并创建索引。你会发现AI 工程选型最重要的是够用而不是高级。2.4 小步快跑的批处理路线总结数据这部分的经验我走的是非常务实的批处理路线。没有一开始就搞 Kafka、Flink 实时流而是用 Airflow 定时的批处理把每日的数据先结清。这样做有巨大好处出了问题可以追溯到一个确定的时间点重跑任务成本低数据的可回放性极强。对于大多数非实时交互推荐类业务小时级甚至天级的数据更新完全能够满足需求完全不必为了技术含量而背上实时架构的运维负担。3. 模型生命周期实验管理、版本化、可复现训练3.1 从文件名记录到MLflow实验管理最开始做模型实验我依赖的是一套极其原始的方法代码目录里放model_v1.py、model_v2_final.py、model_v2_final_real.py这样的文件名模型权重按日期命名存盘。一个月之后我根本记不清xxx_final到底对应哪组超参数、哪份数据集。这个问题在 AI 工程里几乎是致命的因为如果实验结果不可追溯那每一次调参都是在沙滩上盖楼。我随后引入 MLflow把每个实验的启动参数、代码版本、训练指标、产物地址全部自动记录。每次训练完打开 UI 就能看到哪一条 run 用的是什么学习率、哪一批数据、最终精度是多少。这样不仅自己能复盘团队协作时也让每个人提交的实验变成可查询对象。哪怕模型的最终指标没有提升一次实验也产生了有效信息而不是一句试了一下效果不行。3.2 数据、代码、模型三位一体的版本化有了实验管理之后另一个更隐蔽的问题浮出水面给定的模型文件能不能精确还原出它的训练数据和代码MLflow 能记录 git commit id但这还远远不够。因为数据本身也是持续变化的今天和昨天同一个dt2024-05-12分区里的内容可能因为上游修复任务而被重刷过。为此我做了几件事第一数据目录使用不可变分区写入后的分区不再允许修改如果要修复数据错误新建一个新分区并废弃旧分区第二训练任务启动前把当前数据集的元信息行数、特征列、哈希值写入实验标签第三模型文件名带上数据批次信息和 git commit 前缀。这样一来从模型反查数据、代码、超参数变成了一条稳定的血缘链路。别看这些手段土它们的核心目标不变——可复现性是 AI 工程里最重要的工程纪律。3.3 训练结果对比不要相信感觉要看指标我自己在实验对比上也栽过跟头。有一段时间我用肉眼比较两组训练曲线觉得某个版本看起来收敛更快直接把这个模型部署了结果 A/B 测试效果反而不如旧版本。后来我意识到人类大脑对看起来不错的曲线非常敏感但对统计显著性一无所知。正确的做法是每次实验都固化一组离线评估指标包括准确率、召回率、F1、AUC以及针对线上环境的模拟测试集。同时对于多个候选模型设置严格的对照规则——同一批数据、同一个随机种子、同样的评估代码只改变一个实验变量。最好的情况是把这个流程写成 CI每次提交训练代码自动评估把结果集中呈现在一个表里做比较。现在回头看这套纪律的价值远远超过任何一种模型调参技巧。4. 推理服务化把模型从notebook搬到生产的最短路径4.1 单体批推理 vs 在线API服务模型训练完之后面临第一个路线选择以批处理方式还是在线 API 方式对外提供服务。我对两者的判断标准是业务方的时延要求。如果是给运营同学出日报、给推荐系统生成候选集批处理完全够用而且实现简单挂了重跑就行。但如果是用户在页面发起请求的实时场景就必须走在线 API。我的项目刚好两种都需要所以做成了两层。离线批量任务每晚用调度框架统一跑完把结果写回结果表在线服务则部署一个轻量 API加载模型并实时处理请求。即使项目里多个模型共用同一套基础设施这条分工逻辑也一直没变。避免过度设计是这时最需要注意的事情很多新手会执着于把一切做成微服务但对小团队来说一个稳定的批次服务加上一个 API 服务已经能解决 90% 的需求。4.2 一条从PyTorch到Triton/ONNX的最小路径在部署环节我一开始直接使用 PyTorch 原生torchserve或写 Flask 接口加载模型。本地测试表现尚可但在 GPU 并发请求上来后性能瓶颈十分明显。后来我选择了 ONNX 模型格式结合 Triton Inference Server这条路径并不是最优但组合很稳妥把训练好的 PyTorch 模型导出为 ONNX再交给 Triton 做服务化。导出这一步有非常多细节。先说动态维度如果模型输入的 batch size 不变还好一旦变化必须在 ONNX 导出时声明动态轴否则请求稍多尺寸一变就报错。然后是算子兼容性某些层在转换时会出现不支持的问题这时可以用opset_version调整或者用 ONNX Simplifier 化简一下。我把所有转换脚本放到deploy/目录并写好 README确保任何人 clone 仓库后可以按顺序跑通。拿一个简单的文本分类模型举例导出核心代码大致是import torch import torch.nn as nn model BertClassifier.load_from_checkpoint(checkpoint.ckpt) model.eval() dummy_input torch.zeros(1, 128, dtypetorch.long) torch.onnx.export( model, dummy_input, model.onnx, input_names[input_ids], output_names[logits], dynamic_axes{input_ids: {0: batch}, logits: {0: batch}}, opset_version17, )导出之后再用 Triton 的 Python backend 或 ONNX Runtime backend 加载。我实际选择了 ONNX Runtime 后端因为可以免去自定义前后处理的复杂配置。Triton 对动态 batch、并发请求有很好的优化我们的接口从单并发 20ms 提升到并发 200 时 P95 稳定在 35ms 左右性能上完全满足要求。4.3 后端的依赖地狱与GPU资源分配部署推理服务时AI 工程特有的麻烦事是依赖冲突。模型训练时用的 PyTorch 版本、CUDA 版本、Python 包版本和线上服务环境经常对不上。训练环境里torch2.0.1cuda117线上 GPU 驱动可能只支持到 CUDA 11.8模型里用的某个 tokenizer 库线上机器没有安装服务直接 crash。我的解决方案是 docker 化加严格锁版本。把训练环境、导出工具、推理服务分别打成独立镜像在每个镜像里锁死 Python 依赖版本。推理服务镜像只装必要的库比如torch这类重组件如果不需要就完全不装从而最小化攻击面和依赖风险。GPU 资源配置方面我的经验是用 Triton 的模型实例管理为不同模型分配独立实例并设置显存限制和max_batch_size避免一个模型吃满显存导致其他模型排队失败。4.4 可靠性限流、降级、缓存模型接口上线之后你要直面所有服务都要面对的问题流量激增、依赖故障、超时雪崩。AI 推理服务有一个额外风险单次推理计算量大如果不加控制上游一个突发请求就能把 GPU 打满后续所有请求全部排队延迟线性上升。我做了四层防护。第一层网关层限流对单 IP 或单用户的请求数做并发限制第二层超时控制每个推理请求设置严格的超时时间宁可返回降级结果也不让请求无限堆积第三层结果缓存对相同特征输入在短时间内复用上一次推理结果这部分对业务量提升非常显著第四层降级策略当模型服务异常时走一个规则版本的兜底逻辑让主流程不至于因为 AI 服务中断而整体瘫痪。这四层防护做完之后我的服务连续运行几个月没有出现过事故和之前那种本地能跑就行的心态完全不一样。5. 上线后的另一半工程可观测性与反馈闭环5.1 日志不只是打印而是trace血缘模型上线只是工程的开始。最开始我的服务日志只有简单的请求参数和推理结果出了问题时根本无从下手。AI 工程的可观测性和传统服务有区别除了请求延迟、错误率这些常规指标你还要关心模型输出的分布、输入特征分布以及样本的抽样策略。我在每个请求里都加入了 trace_id从网关开始贯穿到推理结束。每个请求会记录以下内容请求原始特征、预处理后的特征、模型输出的完整 logits、最终阈值判定结果、响应延迟。这些日志除了排障更大的作用是给后续的离线分析和模型迭代提供线上真实数据。日志不是打印给人看的而是数据的第二次采集入口。5.2 数据漂移监控指标、报警和阈值模型上线后最容易被忽视的问题就是数据漂移即线上数据分布和训练数据分布逐渐分家。之前提到的 F1 从 0.92 掉到 0.81 的事故本质就是漂移没有及时被发现。我在这块引入了两层监控特征漂移和预测漂移。特征漂移用 PSI 和 KS 检验来做。我每隔一小时对线上请求的每个特征做一次分布统计拿它和训练集的基准分布做比较当一个特征的 PSI 超过 0.2 就触发告警。预测漂移则跟踪模型输出的平均概率、置信度分布如果模型今天输出的平均概率比昨天低了 0.1那往往意味着业务环境变了模型已经不再适用。告警阈值不是拍脑袋设定的而是根据历史正常波动的 P95 值算出来的宁可先宽松再收紧也不要一上来就被报警轰炸到麻木。5.3 用户反馈回炉重训的循环最后AI 工程和传统软件开发最大的一点不同是它可以也应该持续演进。模型上线后我会把线上请求及其最终业务结果用户是否点击、工单是否解决、内容是否通过周期性地回流到训练数据池里。每两周做一次增量训练然后通过离线评估和线上的 A/B 小流量验证决定是否替换线上模型。这个闭环说起来简单实施时最关键的是标注质量和回流链路。不能把原始请求直接丢进训练集必须经过清洗、去重、标注确认。我在 Airflow 里专门设计了一个反馈清洗任务每天晚上把当日用户反馈数据与线上日志 join 起来做合法性和一致性校验再写入标注队列。人工标注完成后模型自动进入重训流程。这样模型会随着时间推移越来越贴合真实的业务情况而不是永远停留在离线那几万条样本上。6. 复盘从零到一最难的不是算法而是工程纪律6.1 一份自检清单项目进入稳定期后我整理出了一份自检清单每次上线新模型或者改动数据管线时都会逐项过一遍分享出来供你参考[ ] 训练特征逻辑与线上服务是否共用同一份代码有没有单测保障[ ] 当前实验的数据集、代码版本、超参数、模型产物是否都能追踪[ ] 推理服务是否有限流、超时、缓存、降级四件套[ ] 模型输出的分布和关键特征分布是否有监控告警[ ] 线上真实样本是否按照策略回流到标注和训练链路[ ] 如果负责这段工程的同事请了一周假另一个人能否从 README 和文档顺利接手从最后一条你会看出来AI 工程最怕的就是个人英雄主义。真正稳定的系统不是靠某一个大佬的惊人操作而是靠一整套纪律和文档把每个环节都钉在轨道上。6.2 如果重来我会先做什么如果让我重新开始一次 ai-engineering-from-scratch我不会再先调入参和模型结构而是先把数据管线和实验记录两件事做扎实。这两个环节是所有上层智能的基础基础不牢后面的每一步都是空中楼阁。我也不会为了追求实时架构而上流式计算批处理先把业务跑通用最容易维护的系统积累起第一批真实数据和模型经验再决定要不要进化。6.3 最终要过的那道坎我在这个项目里最大的体会是AI 工程是一个逐步叠加纪律的过程不存在一步到位的银弹。以为上了某个平台就可以高枕无忧是大忌。你需要不断地在数据一致性、可复现性、服务稳定性和迭代效率之间做取舍每条路线的取舍理由都值得写进文档因为这些决策构成了系统的灵魂。到现在ai-engineering-from-scratch 已经跑过了数据管道、实验管理、推理服务和监控闭环四个完整阶段。我能明显感到从零搭建这套体系让整个团队对 AI 项目的信心增加了不止一个档次——大家不再担心模型上线就失控因为每一个环节都有相应的护栏。如果你也正处在模型能跑但是不敢上线的阶段希望这篇内容能给你指一条可以下脚的路。先别急着追最新的算法把你手头那个模型的工程半径缩小一点把数据管线和可复现性先立起来剩下的路会自己慢慢铺开。
返回列表