ARTICLE DETAIL

资讯详情

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

AI工程从零到上线:系统思维、模型部署与监控实战

AI工程从零到上线:系统思维、模型部署与监控实战 我把自己两年多来围绕 AI Engineering 攒下的笔记整理成了一个项目名字就叫 ai-engineering-from-scratch。起因很实际团队里能在 Jupyter Notebook 里调出漂亮 AUC 的人不少但能把模型稳定送上线、出问题能十分钟内定位的人掰着手指头数得过来。我自己就是从后者被骂出来的。这个项目不是算法课也不是某个框架的官方文档搬运它是一条从零开始搭建 AI 工程能力的完整路线环境和依赖怎么管、数据怎么进管道、模型怎么训练和记录、服务怎么上线、上线之后看什么指标以及每一步踩过的坑。它围绕一个可以真实复现的案例展开——用公开的 MovieLens 评分数据构建一个推荐服务从原始 CSV 一直做到带监控的线上 API。如果你正在从算法或开发岗位转向 AI 工程方向或者学完理论之后总觉得缺了落地那一环这份笔记大概率对你有用。1. 为什么从零开始比从框架开始更适合入门AI工程1.1 算法工程师与AI工程师差在系统思维我的第一份相关工作是在算法组。那时候的工作节奏很单一拿到一个业务问题找特征、调模型、看离线指标周会上汇报 AUC 涨了 0.3 个点大家都挺开心。直到有一次模型上线业务方反馈转化率反而掉了我拿着离线实验记录去对线才发现问题根本不在模型训练数据里的用户活跃度特征是在凌晨批处理算出来的而线上请求发生在白天两边的特征分布对不上。那一刻我意识到AI 工程师要管的不是模型本身而是模型周围的一整圈系统——数据是否准时、特征是否一致、服务是否可用、指标是否可信。这就是 AI Engineering 和纯算法工作的分水岭。算法工程师可以只对模型负责AI 工程师必须对整个系统的闭环负责。模型只是这个闭环里的一环而且往往不是最脆弱的那一环。最脆弱的是数据管道、特征逻辑、部署配置和监控告警。我把这个观点放到项目最前面的理由很简单如果一开始就奔着训练一个高精度模型去很容易重复我走过的弯路——模型很强系统很脆。1.2 复盘结论亲手搭一遍最小闭环比套用现成平台更划算ai-engineering-from-scratch 这个项目最核心的决策是拒绝一开始就上现成的机器学习平台或 AutoML 工具。原因不是这些工具不好而是它们帮你隐藏了大量必须自己理解的东西。用 AutoML 跑出一个高分模型很容易但线上延迟超了、特征缺失了、数据漂移了平台给你的报错会像天书一样。类比一下会用一体机的人很多但真到了电脑出故障还是得靠那些自己动手装过机的人。从零搭一遍最小系统不是为了重复造轮子而是为了获得排查问题的底感。所以项目里我给自己定了一条规矩每个环节都用最朴素的工具先跑通。数据管道先用 Python 脚本模型服务先用 FastAPI实验记录先用 MLflow数据库先用 PostgreSQL。整套东西加起来一台带 GPU 的开发机就能撑起来成本很低但每个环节的问题都会真实暴露在你面前。先痛苦后顺畅这是从零开始最大的回报。2. 起步阶段先搭能力地图我选定的环境与最小技术栈2.1 环境层Python版本、Docker、GPU驱动的不一致问题环境是绝大多数从零开始的开发者第一个崩溃点。我曾经在同一台机器上装过三个 Python 版本系统 Python、Anaconda Python还有某个项目自带的 Python结果 pip 安装的包互相污染一个项目升级依赖把另一个项目搞挂。后来我强制自己使用 pyenv 管理 Python 版本所有项目都声明.python-version文件配合 poetry 管理依赖pyproject.toml里锁定直接依赖poetry.lock锁定完整依赖树。这一步花了我一个周末但之后再也没有因为换环境浪费过一整天。GPU 环境更麻烦。CUDA 驱动、cuDNN、PyTorch 三者的版本必须匹配很多人在这里反复试错。我的建议是别直接装最新版先查一下框架官方对 CUDA 版本的支持矩阵然后固化成 Docker 镜像。下面是我在项目里用过的启动命令片段# 基于官方PyTorch镜像构建避免宿主机驱动与容器库版本错位 docker build -t ai-from-scratch:0.1 -f docker/Dockerfile . docker run --gpus all -it --rm -v $(pwd):/workspace ai-from-scratch:0.1 bash这个做法的核心收益是项目的可复现性不再取决于某台机器的状态而取决于一个镜像的 tag。任何人任何时候拉取同一个镜像得到的运行环境一致这是 AI 工程底座的第一个习惯也是后面所有实验可复现的前提。2.2 应用层FastAPI、MLflow、PostgreSQL组成的底座技术栈我只选了三个东西并且每一个都有明确理由。FastAPI 负责把模型封装成 HTTP 服务选它是因为自带基于 Pydantic 的请求校验和 OpenAPI 文档写一个推荐接口只需要几十行代码后续加限流、加监控也不别扭。MLflow 负责实验跟踪和模型注册它可以记录每次训练的超参数、指标和模型文件比自己在 Excel 里记实验日志可靠太多。PostgreSQL 负责存三类数据原始特征快照、模型版本元数据、线上请求日志。这三者组合起来基本覆盖了一个小型 AI 系统的全部需要。不要一上来就上 Kubernetes、上 Feature Store、上完整的 MLOps 平台。我见过不少团队模型还没跑通先花两周搭平台最后平台比模型还难维护。最小技术栈的原则是单机能跑、三小时能学会、出问题能自己修。等流量和复杂度真的上来了再按需替换。项目里我把这套栈的选择过程也写进了 README方便后来者理解每个组件解决什么问题。2.3 数据层我先用脚本加版本管理而不是上重型平台数据管道是另一个容易过度设计的地方。对于从零开始的项目我建议先用脚本加版本管理而不是一上来就上 Airflow 或 Feature Store。我的处理方式所有原始数据放到data/raw目录清洗脚本放到src/data目录每次清洗都输出一个带时间戳和内容哈希的 dataset 快照。内容哈希可以用简单的 md5 实现但要在记录里写明数据来自哪个版本。伪代码类似这样import hashlib import pandas as pd df pd.read_csv(data/raw/movielens/ratings.csv) # 清洗逻辑... snapshot_hash hashlib.md5(df.to_csv().encode()).hexdigest()[:8] df.to_parquet(fdata/processed/ratings_{snapshot_hash}.parquet)这个看起来很朴素的方案解决了 AI 工程里最隐蔽的问题数据版本不可追溯。当你发现某个特征在线上异常能快速确认这个特征是用哪份数据、哪个清洗脚本、什么时候生成的比任何花哨平台都管用。等管道数量超过十个、调度依赖复杂到脚本维护不了再考虑换平台也不迟。3. 端到端案例用MovieLens数据把一个推荐服务跑上线3.1 数据清洗评分数据里的缺失、偏置和时间陷阱项目采用的公开数据是 MovieLens 的评分记录虽然已经做过基本清洗但仍有很多现实问题评分时间戳分布不均、部分用户只有一条评分、热门电影占据大量样本。我用它来模拟真实业务数据里的偏置。清洗时做了三件事。第一把时间戳转成可读时间并拆出星期、小时等特征因为后续要验证时间型特征漂移。第二筛掉评分记录少于 5 条的用户避免冷启动噪音影响早期模型判断同时单独留存一份冷启动测试集后面专门验证服务策略。第三按用户 ID 做分层切分而不是随机切分这样能防止同一个用户的数据同时出现在训练集和验证集里造成虚假的高指标。这一步的教训是数据清洗不只是去掉脏数据它直接决定了你能发现什么问题。如果你不做分层切分模型上线后大概率会在新用户身上翻车而你离线验证时根本看不出来。我在项目里专门写了一个脚本对比两种切分方式的指标差这个对比结果比模型本身更能说明工程思维的重要性。3.2 训练阶段每一步都要记录否则等于白做训练部分我选择了一个中等规模的协同过滤模型加一点特征工程没有追求最先进的深度学习结构因为重点是工程闭环。但每轮训练我都会用 MLflow 记录四类信息代码版本也就是 git commit 的完整 hash数据版本也就是清洗后数据集的哈希超参数比如 Embedding 维度和学习率离线指标比如精确率、召回率、覆盖率以及随机种子。记录这些信息意味着任何一次实验结果都可以被完整重现。代码示例很简单import mlflow with mlflow.start_run(): mlflow.log_param(embedding_dim, 64) mlflow.log_param(lr, 1e-3) mlflow.log_metric(precision10, 0.312) mlflow.log_artifact(model.pt)很多人觉得这是在增加负担但实际排查问题时你才知道能回到过去有多重要。我线上出过一次事故最后定位到是某个特征计算脚本改了逻辑如果当时没有记录数据版本我不可能在一个下午内确认影响范围可能要回滚整个服务。3.3 服务化用FastAPI把模型包成可观测的API模型训练完真正的 AI 工程才刚开始。我把模型文件、特征处理逻辑、预测函数打包成一个 FastAPI 应用。这里有一个关键点特征处理逻辑必须和训练时完全一致否则上线就是事故。所以我把特征处理抽成了独立模块训练和推理共用同一个函数而不是各写一份。下面是服务端点的简化示例from fastapi import FastAPI from schemas import RecommendRequest from features import build_features app FastAPI() app.post(/recommend) def recommend(req: RecommendRequest): user_features build_features(req.user_id, req.context) items model.recommend(req.user_id, top_kreq.top_k) return {user_id: req.user_id, items: items}除了业务接口我还加了两个工程必备的东西健康检查端点和基础限流。健康检查是为了让后续的负载均衡和服务发现能判断实例存活性限流是为了防止某个突发流量把模型服务打挂。这些不写在算法教程里但却是 AI 工程上线前必须考虑的。3.4 上线第一周我在监控面板上看到的三类异常服务上线后我搭了一个最简单的监控面板记录请求量、P95 延迟、错误率、推荐覆盖率以及推荐列表里物品的平均热度。第一周就发现三类问题。第一P95 延迟在晚上八点到十点明显升高原因是晚高峰请求量大加上缓存命中率下降热门电影推荐列表在高峰时段几乎全部要实时计算。解决方式是把 Top-N 热门推荐结果做定时缓存并且给实时计算加了一个超时熔断超时直接返回缓存结果。第二错误率里有一批 422 校验错误查日志后发现是部分客户端没有传 context 上下文但我把它设成了必填字段。改成可空字段并给出默认特征后错误率马上降下来。第三也是最值得记录的推荐覆盖率很高但新用户被推荐的内容几乎全是热门电影。业务上这不算 bug但它暴露了离线指标好看不代表产品效果好的问题。我把这个问题记录为一个实验待办后来通过给新用户单独走探索策略解决了。监控的价值不在于面板好看而在于它能强迫你回答什么叫系统正常。AI 工程里最怕的不是指标差而是没人知道指标什么时候开始差的。4. 工程化必须较真的四个细节可复现、评估、监控与成本4.1 可复现性锁定依赖、数据与随机种子可复现性是 AI 工程和算法实验之间最明显的分水岭。算法阶段你重跑一遍代码指标差一点可能无所谓工程阶段指标差一点你会怀疑代码、数据、环境、随机数四个层面排查成本翻倍。我在项目里做了一次彻底的可复现性改造把依赖升级为带哈希锁定的 poetry.lock把整个训练环境固化进 Docker 镜像数据快照记录 hash同时固定了随机种子并为任何使用随机数的模块显式传入 seed。做完这些之后我每周跑一次同样的训练脚本指标波动控制在千分之一以内。千分之一这个数字很重要它意味着我能区分代码改动带来的真实变化和环境噪音带来的随机波动。没有这个基线每次实验对比都是在猜。4.2 线下评估不等于线上表现影子模式怎么救了我离线指标与线上表现的差距几乎每个做过 AI 工程的人都会遇到。差距来自很多方面离线数据是历史数据线上特征可能是实时的离线评估里采样分布和被服务用户分布不同推荐系统还会改变用户行为形成反馈循环。我踩过一次印象很深的坑离线 AUC 提升了 0.5%觉得很稳结果线上点击率纹丝不动。后来用影子模式验证才知道那个 AUC 提升来自一小撮极活跃用户而这部分用户早已被收敛偏好无论如何推荐都不会增加点击。影子模式的做法是让新模型在后台跟随线上流量做预测但预测结果不直接上线而是记录到日志里隔天用真实的用户行为对比新旧模型的选择差异。这样可以在不影响线上体验的前提下评估模型改进。实现成本不高一个日志表加一个离线对比脚本而已但它是连接离线实验和线上表现的桥梁。从零开始的项目我强烈建议一上来就预留影子模式的日志字段后面做实验会非常省事。4.3 成本思维学会给每一行代码估算开销AI 工程最后拼的是成本。模型在离线评估里效果好不代表它在预算内能服务好。我给自己定了一个习惯每个模型上线前先估算单次推理的算力开销。计算公式不复杂每秒请求数乘以单次推理耗时就是所需并发算力单次推理耗时又由模型结构、输入长度、批处理大小决定。一个 Embedding 维度过大的协同过滤模型单次推理要 30 毫秒QPS 50 时就需要每秒 1.5 秒的计算量这在 CPU 上根本扛不住必须上 GPU 或者裁剪模型。我还会在代码里记录每一个模型的推理耗时和显存占用作为模型注册时的元数据。时间久了积累出来的成本台账可以直接回答业务方最常见的两个问题这个推荐位还能不能加更多候选和我们能不能多上几个模型做实时重排。工程思维不是省到极致而是每一分算力花得明明白白。5. 从零到一阶段踩过的坑以及我的排查链路5.1 坑一换台机器分数就变原生依赖不一致项目做到第三周我把代码从开发机同步到另一台带卡的服务器上跑同一个训练脚本离线指标莫名掉了两个百分点。我第一反应是数据没同步对比了目录里的 hash发现数据一致第二反应是代码版本不同git log 确认是同一个 commit最后怀疑依赖不一致一对比锁定文件才发现一台机器上 numpy 是 1.24另一台是 2.0而某个库的底层算法在 numpy 2.0 下行为发生了变化。这种依赖漂移在 AI 工程里特别隐蔽因为它不影响程序报错只影响数值结果。我的排查链路是这样的先固定代码与数据排除最显眼的变量再对比关键依赖版本特别关注 numpy、pandas、scikit-learn 这类基础库最后用 Docker 把训练环境固化。现在我在新机器上跑训练之前会先跑一个项目自带的 smoke_test 脚本里面内置了一个小额数据训练输出指标必须落在预期区间。这个脚本五分钟就能跑完却是整个项目最值得的投资之一。5.2 坑二训练时正常、服务化后反常预处理链路不一致另一个让我印象深刻的坑发生在服务化后的第二天。离线测试推荐结果看着很正常但通过 API 调用返回的结果里部分用户的 item 列表和离线完全对不上。一开始我认为是模型加载问题重新加载了很多次都一样。后来我打印了 API 请求进入服务后的中间特征发现服务器端对用户历史的编码方式和训练时不一致训练时用户 ID 做了全局编号映射服务端加载模型时却把编号重置了导致同一个用户预测出来完全不同的结果。定位到根因后我把用户编号映射表、特征处理函数、模型权重打包成同一个版本发布并且要求任何线上服务只能加载与模型同版本的 artifact。排查这个坑花了我大半天但它让我养成了一个习惯凡是在线服务涉及的预处理逻辑一律复用训练时的同一份代码绝不单独重写。很多看起来诡异的线上问题根源都是训练和推理各写了一套逻辑。提示训练和推理共用一份特征处理代码是 AI 工程里性价比最高的约定。5.3 坑三线上指标平稳业务方却说效果变差了最诡异的坑出现在上线一个月后。技术指标一切正常P95 延迟稳定、错误率几乎为零、推荐覆盖率也没变化但业务方反馈转化率在持续下滑。我第一反应是业务方自己的统计口径问题沟通之后发现对方看的是推荐位点击后到下单的转化而这个指标在我的监控面板里根本没有。补上这个指标后确实看到连续三周的缓慢下滑。进一步排查发现不是模型变差了而是用户行为分布变了随着时间推移新用户比例上升而训练数据的历史分布里老用户占比高。模型对老用户拟合得很好对新用户却几乎是在盲猜。这就是数据漂移的一种——人群分布漂移。解决办法是每周对线上特征分布跑一次分布偏移检验超过阈值就自动触发重训练同时把冷启动用户单独建一个探索策略不让模型直接背锅。这个坑给我的教训很直接监控不是技术指标的陈列而是业务指标的翻译。技术指标再稳翻译不成业务语言就等于没有监控。6. 这个项目走到现在我的真实体会和下一步打算6.1 坚持记录的好处这个仓库本身就是最大的产出ai-engineering-from-scratch 这个项目走到今天给我最大的收获反而不是技术能力而是记录的习惯。每做一次实验我都会写一小段复盘当时想解决什么问题、用了什么方案、结果如何、踩了什么坑。这些记录平时看不出价值但三个月后回看它们就是为什么当初这么选的最佳答案。项目代码本身可能会过时技术栈也会被替换但那些关于决策原因的记录会在你面对相似问题时直接节省大量试错时间。我现在的做法是每次改动都先更新 README 里的决策日志再写代码。这个顺序反过来代码就很容易变成没人能解释的黑盒。对从零开始的人来说养成记录决策的习惯比学会某个框架更重要。6.2 后续扩展从传统推荐走向LLM应用工程底座没有变项目下一步的方向是把这套最小闭环迁到 LLM 应用场景里。很多人以为从传统推荐到 LLM 应用是换一套技术栈其实不是。你依然要管数据管道、实验追踪、服务化、监控和成本。LLM 的 API 调用延迟更高、成本更贵所以成本思维和影子模式反而更重要模型输出不稳定所以可复现性和评估指标会更难设计但那一整套工程习惯完全没有变。这也是我建议所有想追大模型热点的人先把传统 AI 工程闭环走一遍的原因磨刀不误砍柴工。如果说最后还能分享一点个人体会那就是从零开始这四个字听起来慢实际却是最快的路径。直接套模板、抄配置能让你今天跑通一个 demo但解决不了明天线上出现的任何问题。亲手把环境、数据、训练、部署、监控走一遍你就拥有了一个可以随时复现、随时扩展、随时排查的底座这才是 AI Engineering 真正值钱的地方。
返回列表