ARTICLE DETAIL

资讯详情

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

从零搭建AI工程体系:数据、实验、部署与监控全攻略

从零搭建AI工程体系:数据、实验、部署与监控全攻略 做AI项目这几年我最大的感受是真正难的从来不是模型本身而是模型之外的整个工程链路。很多人一提到ai-engineering第一反应是我该用什么框架训练模型但真正做过几个项目之后你会发现数据怎么管、实验怎么记录、模型怎么部署、上线之后怎么监控这些无聊的环节才决定一个AI项目能不能活下来。这篇内容我想从一个工程视角把我自己从零搭建AI工程体系的全过程、踩过的坑、以及沉淀下来的方法完整写出来。不管你是刚入行的算法工程师还是被公司赶鸭子上架负责AI落地的后端开发这篇文章应该都能给你提供一条可以照着走的路线图。1. 内容整体设计与思路拆解1.1 AI工程与算法研究根本是两种游戏先讲一个我踩过的大坑。早几年我做推荐系统的时候把80%的精力都花在调模型上——换网络结构、调学习率、加正则线下的AUC确实一点一点在涨。结果上线之后线上效果纹丝不动甚至有时候还倒退了。那时候我百思不得其解后来才发现问题跟模型半毛钱关系都没有训练时用的特征管道和线上服务的特征管道是两套代码一个用pandas处理一个用Java重写了一遍数值对不上、字段有缺失、时间窗口计算逻辑不一致。从那以后我意识到一个问题**算法工程师和AI工程师是两种完全不同的角色。**算法研究追求的是在给定数据集上把指标做到最高而AI工程追求的是让一个AI系统在真实环境里稳定、可复现、可维护地运行。前者像在实验室里培养皿中做实验后者像在工厂里搭建一条完整的生产线。这篇博文要讲的就是后者——一条AI生产线的完整搭建过程。我把整个体系拆成了六大模块每个模块解决一个核心痛点模块解决的痛点产出物工程脚手架项目结构混乱同事之间代码无法协作标准化目录统一命令入口实验管理实验结果无法复现参数改了什么都记不住实验记录配置文件版本锁定数据版本化数据一换全链路结果对不上数据快照变更记录评估体系只看准确率模型上线即翻车多维度离线指标线上监控服务化部署训练代码直接当服务用性能惨不忍睹独立的推理服务性能优化监控告警线上模型效果衰减没人知道效果监控数据漂移检测你没看错模型训练本身不在这六大模块里。我的观点是训练脚本本身没啥好工程化的真正决定一个AI项目生死的是训练之外的那一圈基础设施。1.2 从零开始到底是什么意思我在项目标题里写了from scratch但这不意味着让你自己去实现一个Transformer或者手写梯度下降。我从不鼓励这种意义上的从零——那是造轮子不是工程。我这里说的from scratch指的是在没有任何现成AI基础设施的前提下从一个空目录开始一砖一瓦地把一套能支撑AI项目全生命周期的工程体系搭起来。包括目录结构、依赖管理、配置规范、数据约定、实验追踪、评估脚本、部署模板、监控方案。这套东西不依赖特定的云平台或机器学习平台用纯开源工具和普通的Git仓库就能起步。为什么要强调这一点因为很多团队一开始就陷入平台崇拜——觉得上了某大厂的机器学习平台就万事大吉了。结果平台是上了但团队对底层发生了什么一无所知数据存在哪、模型怎么发布的、实验怎么对比的全都变成了黑盒。等平台到期或者业务迁移的时候整个项目就瘫了。我自己更倾向于先用最少、最透明的工具把流程打通等跑通了再按需引入重型平台。这套思路也贯穿这篇文章的始终。2. 核心细节解析与实操要点2.1 目录结构一个清爽的仓库应该长什么样一个AI项目仓库最忌讳的就是算法工程师式布局——所有脚本平铺在根目录文件夹命名随意什么final_final_v2、test3_backup这种文件到处都是。这种仓库别说协作自己隔两周回来看都得找半天。我的项目骨架一般长这样project-root/ ├── Makefile # 统一命令入口 ├── pyproject.toml # Python依赖和项目元数据 ├── configs/ # 所有实验配置 │ ├── baseline.yaml │ └── production.yaml ├── src/ # 项目核心代码 │ ├── __init__.py │ ├── data/ # 数据加载和预处理 │ ├── features/ # 特征工程 │ ├── models/ # 模型定义 │ ├── train.py # 训练入口 │ ├── evaluate.py # 评估入口 │ └── predict.py # 推理入口 ├── tests/ # 单元测试 ├── scripts/ # 运维脚本不是核心逻辑 │ ├── download_data.sh │ └── deploy.sh ├── data/ # 数据目录不入Git用DVC或软链 │ ├── raw/ │ └── processed/ ├── outputs/ # 实验输出不入Git │ └── exp_001/ └── experiments/ # 实验笔记和结论记录 ├── exp_001.md └── exp_002.md有几个设计点我要专门说一下**第一src和scripts严格分离。**src里的代码是会被import的是系统的核心逻辑scripts里的脚本是运维操作是给人在终端敲的。如果运维逻辑混进核心代码比如在train.py里写死了一堆shell调用那这套系统基本上就没法测了。**第二configs单独成目录不跟代码混在一起。**配置是实验的身份证应该被版本管理。每次实验跑之前先把配置文件固化下来实验结束后这个文件就不能再改。很多人习惯把超参数硬编码在训练脚本里这种做法的致命问题是你没法快速回溯这个结果到底是用什么参数跑出来的。**第三outputs和experiments分开。**outputs放的是机器产物——模型权重、评估结果、日志文件乱七八糟但不用人读experiments放的是人类可读的实验笔记——你改了哪个特征、为什么改、结论是什么。前者可以随便清后者必须永久保留。2.2 依赖管理的三个原则Python项目的依赖管理被称为泥潭不是没有道理的。AI项目尤其严重因为牵扯到CUDA版本、PyTorch/TensorFlow这种巨型依赖、以及一堆底层库的互相打架。我现在的处理原则很简单**用虚拟环境不要用全局Python。**这年头还有人在全局环境里直接pip install我只能说他还没经历过把开发机搞崩了然后重装系统的毒打。我一般用uv管理虚拟环境因为它的建环境速度比conda快一个数量级而且生成的uv.lock能锁住传递依赖的精确版本。锁版本但不是锁死所有东西。pyproject.toml里写明直接依赖的版本范围比如pytorch2.0,3.0uv.lock或poetry.lock里锁定传递依赖的精确版本。前者保证升级灵活性后者保证可复现性。**依赖分环境。**训练环境、开发环境、部署环境的依赖交集比你想的小。训练要pytorch和cuda-toolkit部署只要torchCPU版就行加一个Web框架。我把这拆成project.toml核心依赖、project.toml的dev分组、deploy分组部署镜像里不装任何不在deploy分组里的包。关于CUDA还有一个非常实际的坑你的开发机上可能装的是CUDA 12.x但线上服务器的驱动只支持CUDA 11.8。你要是直接pip install torch装了个默认版本训练没问题部署的时候全傻眼。我的经验是提前确认线下和线上的CUDA版本一致如果做不到一致至少保证你的镜像是自包含的把CUDA runtime打进镜像里。2.3 Makefile让所有操作一键执行我见过太多AI项目README里写的命令五花八门——有人用python train.py有人用bash run.sh还有人flask直接裸奔跑在8000端口。这种项目新手拿到手上手成本极高。我的解决方案是用Makefile把所有重复命令统一起来.PHONY: setup train evaluate serve test setup: uv sync train: uv run python src/train.py --config configs/baseline.yaml evaluate: uv run python src/evaluate.py --config configs/baseline.yaml serve: uv run python src/serve.py test: uv run pytest tests/新人拿到项目只需要一条make setup就能把环境拉起来。我自己也省事——不用每次敲一长串带各种flag的命令。命令入口统一之后团队协作的门槛瞬间降了一大截。3. 实操过程与核心环节实现3.1 实验管理实操从记不住到一切可追溯实验管理是我认为整个AI工程体系里性价比最高的一个环节——投入不大但能拯救你的无数个深夜。没有实验管理的项目是这样的你在train.py里改了三个超参数跑出一个不错的结果你很高兴。跑了几个别的配置之后发现还是之前那个最好但你回去找的时候发现train.py已经被你改得面目全非想恢复之前的配置你只能靠记忆。而人类的记忆在周五晚上六点是完全不可靠的。我的实验管理方案不需要任何重型平台只需要三件套配置文件 目录 Git标签。每次训练之前我先在configs/下建立一份实验配置# configs/exp_023_lr_tuning.yaml experiment: name: exp_023 description: 调整学习率从1e-4到5e-5观察收敛速度 tags: [lr_tuning, baseline_v3] data: version: 2024-06-01 batch_size: 128 model: hidden_size: 512 num_layers: 3 dropout: 0.1 training: learning_rate: 5e-5 epochs: 30 seed: 42 early_stopping: true然后在训练脚本里硬性规定一条读取配置文件之前先记录Git commit hash。这样任何一份实验结果都能回溯到那一行代码。训练输出统一放到outputs/exp_023/下里面至少包含outputs/exp_023/ ├── config.yaml # 拷贝一份配置到输出目录防丢 ├── metrics.json # 所有指标集中记录 ├── model.pt # 模型权重 ├── predictions.csv # 预测结果如果有必要 └── train.log # 训练日志这一步做完了这个结果怎么来的这个问题就永远有了答案。3.2 数据版本化实操给数据上身份证很多AI项目复现不了问题不在代码而在于数据变了。今天早上同事往共享目录里加了一批新数据你的实验明天就跑出了完全不同的结果而你还以为是自己改的代码起作用了。我的数据管理方案看项目规模来定。小项目数据10GB用最简单的方案——数据目录跟着实验走data/ ├── 2024-06-01/ │ ├── train.parquet │ └── test.parquet └── 2024-07-01/ ├── train.parquet └── test.parquet每次训练时把数据目录作为参数传到配置里data: path: data/2024-06-01这样的好处是你永远知道实验用的是哪一批数据。我还以为数据没变这个AI项目里最常见的幻觉直接就被治好了。如果你的数据用到的字段比较多我建议你在数据目录里放一个schema.yaml把字段名、类型、含义、允许为空与否写清楚。这个文件在特征对齐的时候价值极大。数据量再大一些或者多人协作频繁我会引入dvcData Version Control来做数据的Git化管理。DVC的核心机制很简单数据本体放在自己的存储里云存储/共享磁盘DVC在Git仓库里只存一份.dvc文件的指针。dvc add时DVC会对数据算哈希并生成指针文件git commit提交指针dvc push把数据推到远端。别人拿到项目后dvc pull一下就能把数据拉回来。这个方案看着多了一步操作但它解决了数据换版本后代码能自动感知这个问题——Git仓库里的指针变了你make train时输入的data明确标注这次用的是哪个版本的数据。不用再因为数据是全量还是增量昨天那份跑得好的数据是哪个文件夹去问同事了。我把数据版本化的常见方案做了个对比供你选型参考方案适用规模优点缺点手工加日期文件夹10GB零成本直观迁移麻烦难以回溯DVC10GB~1TB支持Git流程可回溯对存储要求高需要学习云平台数据集管理1TB自带版本、血缘绑定平台迁移困难3.3 评估体系实操别让一个指标骗了你评估是最容易被轻视的环节。很多项目面试时说准确率95%上线之后用户根本不买账。原因在于指标建立在了错误的假设上。一个让我印象深刻的例子做文本分类的时候模型在测试集上的F1值是0.92看起来很漂亮。但上线之后发现模型几乎把所有的投诉类文本都分错了。后来一查训练数据里投诉类别只占2%模型学会了只要不确定就预测成多数类整体指标很好看但关键类别烂到了家。从那次以后我的评估体系强制包含以下几个维度分层的指标明细不只报告整体指标还要按类别、按分组计算指标。表格要列出每类的precision、recall、F1尤其关注样本量少的关键类别。误差分析记录分析预测错误的样本归纳出模型在哪类输入上最容易犯错。这一步看起来费时间但它是给产品经理讲明白模型能做什么不能做什么的最佳材料。严格划分训练/验证/测试集测试集是模型在整个开发周期中绝对不能碰的数据。一旦你把测试集的信息泄漏到训练里哪怕是隐式的比如你对测试集上的badcase做了特征工程你的评估结果就开始失真了。关于评估的具体实现几个点要注意如果你的模型是分类模型我建议你在评估脚本里输出混淆矩阵的四类数值配合分层指标一起看。如果是回归模型除了MAE/RMSE也可以把预测值与真实值的散点图存下来观察是否在某个区间出现系统性偏差。这些工作花了时间但能提前避掉上线后一半的锅。4. 常见问题与排查技巧实录这一节我把自己在多个AI项目里踩过的最典型的坑汇总成一张表再挑几个最要命的展开讲。4.1 高频问题速查表问题根因排查思路训练时没问题部署后P99延迟爆炸模型没有预热或推理时显存分配不稳定部署服务启动时先预跑一次推理实验结果隔天跑不出同样数字没有锁随机种子或变了数据固定seed用同一份数据快照线上效果比离线差一大截特征不一致 / 数据分布漂移逐特征对比线上和离线分布服务OOM没有限制请求缓冲 / 特征处理无界增长为数据加载和缓存过程增加上限训练和推理的指标对不上评估代码和线上流程存在差异尽量复用同一个推理模块4.2 实验不可复现先别骂GPU昨晚明明跑出85%准确率今天同样的代码怎么只有80%——这个问题我趟过太多次了。排查顺序基本已经固定**第一步查随机种子固定了没有。**如果没固定seed结果差几个点完全正常。我的习惯是把seed放到配置文件里默认固定为42不到必须用随机性的地步不改。**第二步确认数据是否过期。**你在data/目录下又补了一批数据或者共享目录里被同事动过。这也是为什么要做数据版本化——日期词语直接写进配置路径之后这件事基本就杜绝了。**第三步确认代码是Git里那一版。**很多时候你以为的同样的代码其实已经被改过了。训练脚本里记录Git commit hash不是可选的是必须的。我用过一段时间的办法是在训练启动时先执行一个git rev-parse HEAD并把结果写到输出文件的第一行后来发现这种方式在CI/CD环境下最稳定。4.3 P99延迟高不是换台好机器就能解决模型服务上线后最常被问的指标是P99延迟——100个请求里最慢的那一个耗时。把一个自动生成文本的模型部署成服务时P95/P99延迟往往比P50高出一个数量级。常规解决路径是加预热。模型是懒加载的第一个请求要临时把参数载入显存自然慢。服务启动后在健康检查里加一个隐藏的预热请求让模型提前跑一次能把P99直接砍掉一半。限制batch size。如果第一个请求要处理的文本是10K字而配置里batch size是1一切都好说。但请求长度如果不受限制当单次请求特别长时P99就会失控。解决方法是给请求队列设置长度上限或者把长文本切成多个分片并行处理。测显存占用曲线。跑一个连续的benchmark画一下每小时显存占用。如果缓存在持续增长说明你的服务有进程内存泄漏不是换个GPU型号能解决的。4.4 线上效果不对的排查清单线上效果与离线评估不一致我复现流程基本如下先确认线上特征与训练时特征是否完全一致。这一点我会对照schema文件逐一检查。最常见的问题是离线特征管道和线上特征管道用了两套实现比如离线用Polars online用Spark一个字段的取值逻辑差了一点点最后对结果的影响可能就是几个点的F1。再看数据的分布漂移。最简单的做法是把线上最近两周的请求数据拉出来跟训练集做一个单特征分布对比。比如用户年龄字段训练集是20~40岁为主线上全是20岁以下那模型翻车几乎是一种必然。最后才去看模型本身。模型权重有没有被线上配置覆盖、有没有加载错误版本这些都排在特征和分布的问题后面。特征一致性这块我的经验是训练和推理阶段尽量复用同一段特征代码而不是各写一遍。维护两套特征逻辑是AI工程里最大的隐性负债之一短期内看省了事长期看一定会在线上暴露成事故。5. 总结与个人体会这篇文章从一个工程骨架的视角把我从零搭建AI工程体系的过程写了一遍从项目结构、依赖管理、实验追踪、数据版本化一直讲到了评估、部署和监控。写到这里我想起一件旧事有一回我做一个线上客服机器人离上线还有两天突然发现模型对用户的地址类问题回答全都有偏差。我们排查了两天才找到原因——训练数据里地址字段的格式和线上用户输入的口语化地址差别太大模型压根没有见过这种输入。当时如果早一点把数据分布漂移检测做起来这个问题可能在数据进模型的头一天就被发现了完全不用等挨到上线前。所以回到开头那句话真正难的不是模型而是模型之外的工程链路。从零开始搭建AI工程体系并不会让你的模型变聪明但能让你的项目从在本地跑通一次变成稳定地在线上运行一年。它的价值不是立竿见影的而是体现在你被线上问题撞击的每一个深夜——到那时候你才有底气说这个问题我早就有预案了。
返回列表