做AI工程这一年多,我最大的感受是:真正难的不是跑通一个模型,而是把模型变成一套能持续迭代、能扛住业务压力的工程体系。网上铺天盖地都是“提示词调优”“微调实战”,但很少有人聊清楚从零开始搭建AI工程能力的完整路径。这个“ai-engineering-from-scratch”项目,就是我当时从零搭建的一套个人AI工程体系,今天把整个思路、踩过的坑、沉淀下来的方法论一次性整理出来。
如果你是一个刚接手AI项目的开发者,或者想系统构建AI工程能力但不知道该从哪里下手,这篇文章会比较对胃口。我会避开那些玄乎的概念,直接用我实际做过的事情来讲:怎么搭基础设施、怎么管数据、怎么让模型训练不失控、怎么部署上线之后还能睡得着觉。这套东西不挑框架、不挑团队规模,小到个人项目大到几十人的团队,底层逻辑都是相通的。
1. 从零开始的底层逻辑:先想清楚你要建什么
1.1 “从零”不等于“造轮子”
很多人听到from scratch,第一反应是啥都自己写。我一开始也差点走偏,觉得AI工程就得自己实现Transformer、自己写分布式训练框架,结果折腾两周连数据加载都没搞定。后来才想明白,这个“零”说的是你没有现成的AI工程化能力沉淀,而不是说你要从数学原理重新推导一遍。
我最终定的原则很简单:模型算法能用现成的就用现成的,工程基建能上开源的就上开源的,自己只做两件事——把流程串起来、把坑填平。比如模型结构直接用开源实现,但是训练流程、数据校验、评估体系、部署方案这些一定要自己搭,因为它们是业务独有的,买不来也抄不来。
这个判断背后的逻辑是:AI项目的风险等级是不一样的。算法效果不行,你还能调参、换模型;但工程基建塌了,你连实验都跑不起来。所以从零搭建的时候,优先级一定是先保证实验能快速跑起来,再逐步丰富工程细节。
1.2 拆解AI工程化的四个核心模块
我花了大概一周时间,把整个AI工程体系拆成了四个相互独立又紧密咬合的模块:
| 模块 | 解决什么问题 | 核心产出 |
|---|---|---|
| 数据管道 | 数据从哪来、怎么洗干净、怎么版本化 | 可复现的数据集 |
| 实验管理 | 模型怎么训练、参数怎么记录 | 可对比的实验记录 |
| 评估体系 | 模型好不好、能不能上线 | 量化指标+人工抽检 |
| 部署运维 | 模型怎么出去、怎么监控 | 稳定可用的服务 |
这个拆分方式最核心的价值在于:每个模块都能独立演进。数据管道升级不会影响部署,评估体系调整不用重新训练模型。这跟软件工程里的模块化思想一脉相承,只是把模块边界划在了AI特有的位置。
我当时犯过的一个错误是试图把四个模块一次性全建好再跑实验,结果连第一个模型都没训出来。后来改成“先建最小可用闭环,再逐模块加固”的节奏,才真正跑起来。所谓最小可用闭环,就是能完成一次“数据进→模型出→指标看”的完整流程,哪怕很粗糙。
1.3 技术选型的一个核心标准:团队能接住
技术选型这件事,我在这个项目里踩的坑最多。不是工具不好用,而是选了团队驾驭不了的工具。我最开始选了某个特别强大的工作流引擎,功能应有尽有,但团队里没人真正用过,出了问题查文档都要查半天。
选型的核心标准后来变成了四个字:团队能接住。我会问自己几个问题:
- 如果核心维护者请假了,剩下的人能撑两周吗?
- 这个工具出了问题,社区里能搜到答案吗?
- 它的学习曲线,团队需要投入多少时间才能上手?
基于这个标准,我最终选了Python生态为主的技术栈:PyTorch做模型训练,MLflow做实验管理,Docker打包部署,Airflow做数据管道编排。不是它们最好,而是它们在国内社区足够活跃、上手资料足够多,出了问题能找到人问。
2. 数据管道的工程化:AI项目的隐形地基
2.1 数据收集阶段就要想清楚的事
数据是AI项目的地基,但绝大多数人把数据当成“拿来就能用”的现成资源。我见过太多项目,模型训练到一半发现训练集和验证集有重叠,或者数据分布跟线上完全不一致,最后只能推倒重来。
我做数据收集时坚持几条规则:
- 每个数据集都要有明确的来源记录:爬下来的、买来的、业务导出的,分开存放并标注清楚
- 原始数据绝对不允许被修改:任何清洗操作都生成新版本,原始文件只读
- 记录每一条数据的入库时间:这能帮你后期排查数据新鲜度带来的问题
这个习惯帮我避免了一次大事故。有一次模型效果突然下降,排查了半天发现是有个上游数据源的字段含义变了,历史数据和新数据混在一起训练,模型学到的分布整个乱掉。因为保留了完整的来源记录,我才能快速定位到是哪个数据源出了问题、影响范围有多大。
2.2 数据清洗的“三大纪律”与“一项注意”
数据清洗是整个数据管道里最花时间的环节,一般能占到总工作量的一半以上。我总结了一套自己的“三大纪律”:
- 重复数据的处理必须显式化:不是简单drop_duplicates,而是记录有多少重复、为什么重复、去重后保留哪条
- 缺失值的填充必须场景化:数值型特征用中位数还是均值填充,文本字段用空字符串还是特殊标记,都要根据业务场景决定
- 异常值的处理必须可解释:超过3个标准差就删掉?这不是好习惯,要先把异常值单独提出来看看到底是数据错误还是业务本身的极端情况
一项特别容易被忽略的注意点是清洗规则的版本管理。我见过太多团队,数据清洗脚本一改,整个数据集就变了,但没人能说清楚变了哪里。后来我把清洗规则也纳入git管理,每次改动都走代码评审,这看起来麻烦,但能避免那种“不知道数据怎么变成现在这样”的恐慌。
数据清洗的原则用一句话总结就是:宁可慢一点,也要让每一步操作都有据可查。因为AI项目里数据的问题是链式传导的,底层脏数据到了模型层面会被放大成灾难。
2.3 数据版本化:训练可复现的基石
模型训练最怕的一件事是:同一个脚本,换了个数据集版本,结果完全变了。如果没有数据版本化,你根本无法回答“这个模型是用哪份数据训出来的”这个问题。
我用的方案是DVC(Data Version Control),它在git之上加了数据文件版本管理的能力。具体操作很简单:
# 初始化DVC dvc init # 把数据目录纳入版本管理 dvc add data/raw_dataset.csv # 关联git提交 git add data/raw_dataset.csv.dvc git commit -m "add raw dataset v1.0"这样每次训练实验,我都能精确记录用的是哪个版本的数据。配合MLflow记录的超参数和代码commit哈希,就能实现一次完整的实验复现:
- 代码:git commit哈希
- 数据:dvc的md5值
- 参数:MLflow记录的超参数
- 环境:requirements.txt加上Docker镜像tag
只有这四个要素齐全,才称得上“可复现的实验”。我当时是把这个要求写进了团队的实验规范里,任何一次正式实验都必须记录这四项信息,缺一不可。
2.4 数据质量校验的自动化关卡
数据管道里最容易被人忽视的是质量校验环节。我一开始也是手动看数据、手动跑统计,效率低而且容易漏。后来设计了三个自动化的质量检查关卡,嵌在管道里:
关卡一:格式校验。检查字段类型、值域范围、枚举值合法性。比如年龄字段不能为负数、性别字段只能是预设的枚举值、时间字段必须能解析。
关卡二:分布校验。检查当前批次数据和历史数据的分布差异。我用的核心指标是PSI(Population Stability Index),设定一个阈值,超过就报警。这能帮你提前发现上游数据源的变化,而不是等模型线上效果掉了才发现。
关卡三:完整性校验。检查必填字段的空值率、表之间的关联完整性。核心表之间外键对不上的问题,往往在训练阶段才暴露,到时候排查成本极高。
这三个关卡用Python写成一个check模块,每天自动跑一遍,有问题就发告警。这套机制上线之后,我的数据质量事故率至少降了七八成。
3. 模型训练的工程化:把训练变成可控流程
3.1 训练脚本规范化的三个层次
模型训练看起来是AI工程师最核心的工作,但实际上大部分时间都在处理工程问题。我踩过的最大坑是训练脚本写了一堆if-else,参数散落在各处,换一个配置就得改代码。
后来我把训练脚本规范化成三个层次:
- 配置层:所有可调参数(学习率、batch size、模型结构选择等)都放在一个yaml配置文件里,代码里不允许硬编码
- 逻辑层:模型构建、数据加载、训练循环的核心代码,尽量保持与具体配置无关
- 入口层:只负责读取配置、组装模块、启动训练,代码量控制在几百行以内
这样做的好处是,跑实验变成了“改配置文件+执行命令”的动作,而不是每次都要改代码。我的标准命令大概是这样的:
python train.py --config configs/bert_base_v1.yaml这样一个命令就能复现一次完整训练。配合MLflow,每次跑完自动记录配置、指标、产物,整个训练过程的透明度大大提高。
3.2 实验管理的正确姿势:不只是记个参数
实验管理工具我用的是MLflow,但它的价值不在于“记录参数”这个动作,而在于实验之间的对比和筛选。我每次实验都会在MLflow里记录这几个维度的信息:
- 数据集版本和文件哈希
- 训练配置的完整内容
- 训练过程的loss曲线指标(记录每个step的loss值,而不是只记最终值)
- 评估阶段的每个指标
- 产出的模型文件路径
有了这些,我就能随时回答“这个线上模型是怎么训出来的”这个问题。有一次线上模型出了预测异常,我通过MLflow找到当时的训练配置和数据版本,竟然完整复现了训练过程,逐步定位到是某个特征在当时的处理方式有问题。没有这套记录,这种问题几乎没法排查。
3.3 训练资源管理:单机到分布式的平滑过渡
很多团队的起点是单卡训练,但随着数据量增长,单卡很快就会撑不住。我自己经历的这个过程,总结出来几条经验:
- 单卡训练时就把代码写好:用DataLoader、用分布式训练框架的API,哪怕你暂时只用单卡,也别写死单卡逻辑。PyTorch的
DistributedDataParallel加上torchrun启动,改动量很小
# 单卡启动 python train.py --config configs/base.yaml # 多卡启动 torchrun --nproc_per_node=8 train.py --config configs/base.yaml先搞定数据加载,再做模型并行:分布式训练最容易出问题的不是梯度同步,而是数据加载成为瓶颈。DataLoader的
num_workers和prefetch_factor要反复测,找到甜点值显存不够先别急着上模型并行:梯度累积(gradient accumulation)、混合精度(AMP)往往能解决80%的显存问题,虽然代码上多几行,但比上分布式简单太多
混合精度我几乎每个训练任务都开,在A100上能节省接近一半的显存,速度也能提升一倍左右。当然要留意loss是否出现异常波动,个别场景下FP16的精度损失会影响收敛。
3.4 训练过程的“执行细节”与防呆设计
训练过程跑几天甚至几周,中途断了是家常便饭。我早期没有做断点续训,跑了一个三天的训练任务,第二天晚上机器重启,所有进度清零,心态直接崩了。从那之后,我把三个“防呆机制”作为训练代码的标配:
- 定期保存checkpoint:每N步保存一次,包含模型权重、优化器状态、当前step数
- 启动时自动检测断点:如果存在checkpoint,从最新状态恢复训练
- 训练日志全量落盘:loss、学习率、显存占用等指标都写入日志文件,便于事后分析
还有一个容易踩的坑是随机种子的问题。如果你追求可复现,必须在数据加载、模型初始化、数据增强等环节统一设置随机种子。但要注意,PyTorch、NumPy、Python自带random三者是互不相关的,需要分别设置:
import random import numpy as np import torch random.seed(42) np.random.seed(42) torch.manual_seed(42) torch.cuda.manual_seed_all(42)不设置好随机种子,你就无法判断两个实验的效果差异到底是来自代码改动还是随机波动,这会让实验对比失去意义。
4. 评估体系:模型能不能上线的唯一判据
4.1 离线评估指标的“三重校验”
模型训练完不等于能用,评估是上线前的守门员。我的评估体系分三层:
第一层:核心业务指标。准确率、召回率、F1这些经典指标要看,但更重要的是和你业务直接挂钩的指标。比如做搜索,就看搜索点击率;做风控,就看坏账率。这些指标才真正反映业务价值。
第二层:鲁棒性指标。模型在分布外数据上的表现如何?我每次都会预留一部分“难例”数据做测试,比如刻意收集的边界case。如果你的模型在这些数据上表现拉胯,上线后大概率会出问题。
第三层:分群指标。模型在不同用户群体、不同时间段的差异表现如何?整体指标好看,不代表每个群体都好。曾有一个模型整体准确率95%,但在某个低频品类上准确率只有60%,上线后那部分用户大量投诉。
我用一句话来总结离线评估:宁可多测三轮,不可少测一轮。评估环节省的时间,一定会在线上以事故的形式加倍还回来。
4.2 测试集划分的常见陷阱
测试集划分看着简单,实际操作中有不少坑。第一个坑是数据泄漏。如果训练集和测试集有重叠(比如文本匹配任务,同义改写后的同一个问题同时出现在两边),模型指标会虚高到你不敢相信。我判断数据泄漏的方法很朴素:训练集拟合到接近100%太正常,但测试集也接近100%就有问题了。
第二个坑是时间序列的切分方式。很多业务数据有时序特性,直接随机切分测试集,相当于让模型预习了未来数据。我遇到这种情况,会按照时间顺序切分,用前70%的数据训练,后30%按时间窗口做验证。
第三个坑是数据分布漂移。训练数据和测试数据都来自同一时期,线上数据却是几个月后的,分布可能已经变了。我的做法是定期补充新的线上数据进测试集,保持测试集的新鲜度。
4.3 人工评估:机器指标不可替代的补充
纯靠指标来决定模型上线,会漏掉很多机器指标“看不到”的问题。我经历过最典型的一次:模型A的BLEU分数比模型B高不少,明显更“好”,但拿给业务方一看,他们觉得A的输出虽然语法更通顺,但总是丢失关键实体信息,B虽然语句有点生涩,但实体信息完整。业务上B更好用。
从那以后,我建立了“双轨评估”机制:机器指标跑完,必须再做一轮人工抽检评估。抽检比例不用高,但每次上线前必须做,而且抽检人要换着来,避免评估人自己训练的印象偏差。抽检结果和机器指标一起,作为上线决策的依据。
人工抽检测评最重要的是建好一个评估准则清单,让不同的人打分时标准统一。比如生成任务,就要明确“语句通顺”和“关键信息完整”哪个优先级更高。否则每个评估人按自己的感觉打分,结果没法综合判断。
4.4 从离线到线上:上线前的最后一公里验证
模型从离线到线上的跨度,比想象中大得多。我习惯在上线前加一道“影子验证”环节:让新模型和线上模型同时跑,但新模型的预测结果只记录不生效。跑一段时间,对比新旧模型的预测分布,差距太大就说明有什么东西离线没发现。
影子验证能帮你抓到三类问题:
- 特征一致性:离线训练用的特征工程逻辑,线上跑的时候是不是一样?这类bug很隐蔽,很多都是“离线处理了缺失值,线上没处理”这种细微差异
- 性能和延迟:新模型在线上数据量下的推理延迟能不能扛住,这在离线压测时往往测不准确
- 边界case:真实线上输入总有一些离线数据里没见过的极端情况
5. 部署与运维:模型落地才算真正完成
5.1 模型部署方案的选型路径
模型部署有好几条技术路线,我按适用场景排了个序:
- 简单业务、低并发:直接封装成Flask/FastAPI服务,模型推理用简单的predict函数,代码量极少
- 中等并发、延迟敏感:用专门的推理框架比如Triton Inference Server,支持模型动态批处理,吞吐量能提升好几倍
- 超高并发、资源受限:考虑模型量化(INT8/INT4)加上模型蒸馏,或者直接用优化后的轻量模型结构
大多数团队的起点是Flask/FastAPI,这没有问题,但要注意别让部署成为性能瓶颈。我第一次部署就是图省事直接用Flask,QPS到了50就撑不住了,后来换了Triton做动态批处理,同样一台机器QPS翻了将近5倍,而且延迟还更稳定。
5.2 推理服务的性能优化三板斧
部署之后,性能优化的优先级是:
第一板斧:动态批处理。单个请求做一次推理太亏了,GPU的计算能力没有充分用起来。Triton这类框架支持把多个请求攒到一起推理,吞吐量大幅提升。
第二板斧:推理框架的预热。模型首次加载之后,GPU缓存还没有“热”起来,前几次请求会特别慢。部署上线之后,要跑一个预热脚本,造一些假请求先跑几遍,把CUDA相关的kernel编译好、显存分配好,再对外提供服务。
第三板斧:特征计算的预计算。很多特征在离线就可以算好存起来,线上直接查表,而不是每次请求都算一遍。这个优化看似不起眼,但能省掉网络请求模型最耗时的一部分。
优化性能时要理解一个核心矛盾:延迟和吞吐是此消彼长的。动态批处理提升吞吐,但单条请求排队等待的时间会更长。所以你要先明确业务对延迟的上限,比如不能超过200ms,再在这个约束下最大化吞吐。
5.3 监控告警:线上模型“生病”时你要第一时间知道
模型部署上线,监控是续命的关键。我搭的监控体系分三层:
- 服务层监控:QPS、延迟、错误率、GPU利用率。这些指标告诉你服务还活着、还扛得住
- 数据层监控:输入特征的分布变化、缺失率变化、新值出现频率。这些指标告诉你线上数据是不是已经跟训练数据不一样了
- 输出层监控:预测结果分布、置信度变化、业务方反馈的异常case。这些指标告诉你模型是不是已经在“乱说话”
输出层监控最容易被忽略,但恰恰最重要。有一次我们的模型线上跑着,服务层指标一直很健康,直到业务方反馈说推荐结果越来越偏,我才发现模型已经因为数据漂移输出质量下降了两周。
这层监控我会每天自动跑一个“线上数据分布 vs 训练数据分布”的对比报告,任何关键特征的PSI超过阈值就告警。警报不要太多,要能真正拉起人的注意力。我见过一些团队的告警一天响几十次,最后大家直接屏蔽了,等于没有监控。
5.4 模型更新与回滚:快速迭代的最后一道保险
模型迭代快,意味着你必须随时具备“说换就换、说回滚就回滚”的能力。我的模型服务设计原则是模型文件与代码分离,模型文件放在对象存储里,部署时拉取指定版本。
# 拉取指定版本的模型 python deploy.py --model-version 20240515_v3更新流程用蓝绿部署:准备好新版本模型的服务实例,验证通过后切换流量,旧版本保留一段时间再下线。一旦发现新版本有问题,一键切回旧版本。
我还建议给每次模型更新做一份变更记录:改了什么、为什么改、加了什么数据、效果提升了多少。我经历过上线一个新模型后效果反而变差的情况,当时就是因为变更记录不完整,排查了好久才把问题定位到一个特征处理方式的改动上。
6. 全流程复盘:一个从0到1的真实项目周期
6.1 项目实战时间线与里程碑
我用一个实际项目来串一下整套流程。项目背景是做一个商品评论的智能分类系统,把评论自动分成“质量”“物流”“服务”“价格”几个类别。
项目从零开始,整体时间线:
- 第1周:明确业务目标、确定分类体系、收集初始数据(约2万条标注样本)
- 第2周:搭建数据管道,完成清洗和版本化,划分训练集/验证集/测试集
- 第3周:训练基线模型(先用简单的TextCNN),跑通最小闭环
- 第4-5周:切换BERT类预训练模型,调参优化,指标从F1 0.82提升到0.91
- 第6周:影子验证、线上部署、监控上线
这个项目给我最大的启示是:真正的时间分布是数据准备30%,实验迭代40%,部署上线30%。模型训练本身可能只占很小一部分。认清这个分布,才不会被“AI项目就是训练模型”的错觉误导。
6.2 从0到1的“最小可用闭环”策略
第一个基线模型跑通之前,我用的策略一直是“最小可用闭环”:
- 用少量数据(甚至几百条)先训练一个最简单的模型
- 让它跑完整个流程:数据处理→训练→评估→保存
- 看每个环节是否能正常工作、接口是否通畅
- 确认闭环没问题了,再逐步加数据、换模型、调参数
这个策略能让你尽早暴露工程问题。如果一上来就上几万条数据+BERT大模型,数据管道和训练脚本任何一个环节报错,你都分不清问题是出在数据、代码还是框架。小闭环跑通后,后续的工作就变成了“流水线优化”,而不是“救火”。
6.3 复盘总结:AI工程化最关键的三个认知
走完整个项目,我沉淀了三个核心认知:
第一个认知:工程化思维比算法能力更稀缺。绝大多数AI项目的问题不是“模型不够好”,而是“模型根本没法稳定复现和上线”。能训练一个高精度模型的人很多,能把它变成稳定服务的人少很多。
第二个认知:可观测性决定上限。你能看到多少信息,决定了你能解决多少问题。数据版本、实验记录、线上监控,这些东西都是可观测性的组成部分。它们不会直接提升模型精度,但能极大降低排查问题的成本。
第三个认知:流程规范要写进代码,而不是写在文档里。文档写得再全,大家也不一定看。但如果你把规范固化到代码里,比如训练脚本自动记录数据版本和参数、评估脚本强制跑完三重校验,那规范就能真正被执行。
7. 新手避坑指南与工具清单
7.1 新手最容易犯的五个错误
从我带新人的经验看,AI工程化入门阶段最容易犯的五个错误:
- 数据集没有版本管理就开训。训完一个模型后想复现,发现数据早就被改过了,实验全部白费
- 训练脚本把所有配置写死在代码里。换个batch size都要改代码,改完之后还可能引入bug
- 只盯着模型指标,忽略数据质量。模型效果不好,第一反应是换模型,其实多半是数据有问题
- 评估只用单一指标。只看准确率,不看分群指标和鲁棒性,上线后才会暴露问题
- 跳过影子验证直接上线上。离线评估通过就以为万事大吉,实际上一堆兼容性bug在上线后爆发
7.2 值得收藏的工具链清单
工具不在多,够用就好。这是我自己用下来觉得性价比最高的工具链:
| 功能模块 | 推荐工具 | 选择理由 |
|---|---|---|
| 代码管理 | Git | 没什么好说的,标配 |
| 数据版本管理 | DVC | 支持大文件,与git无缝配合 |
| 实验管理 | MLflow | 跟踪代码、参数、指标,开源且易部署 |
| 工作流编排 | Airflow | 生态成熟,调度能力稳定 |
| 模型训练 | PyTorch | 灵活性高,社区庞大 |
| 模型部署 | Triton / FastAPI | 性能优先选Triton,轻量优先选FastAPI |
| 监控告警 | Prometheus + Grafana | 开箱即用,指标可视化能力强 |
工具选型的最重要标准仍然是“团队能接住”。不要因为某个工具在技术圈火就直接引入,先想清楚团队是否有能力维护。
提示:整套AI工程体系不是一天建成的,先从最小闭环开始,一步步加东西。所有工具都可以后续再替换,但可复现实验的习惯必须从第一天就养成。
7.3 我最后想分享的几个细节心得
根据我个人的实操经验,有几个小细节对工程质量的影响特别大,但很少被人提及。
第一个是日志规范。训练日志、部署日志、监控日志都要有统一格式,包含时间戳、级别、模块名、关键上下文。排查问题的时候,一份结构化日志能省掉一大半时间。我第一次排查线上模型异常,就是因为日志格式统一、能过滤出模型预测前后那几秒的系统状态,才快速定位到是输入数据的编码问题。
第二个是模型产物的组织结构。不要把所有模型文件堆在一个目录里,按“项目/日期/版本”组织,文件名里带上关键参数(比如模型结构、训练数据版本、f1分数)。养成这个习惯后,找某个阶段的历史模型会变得非常容易。
第三个是与业务方的沟通节奏。AI工程化不能闷头自己搞,最好每隔几天就跟业务方同步一次当前效果和下一步计划。我踩过最大的坑是闷头调了两周模型,自认为效果不错,结果业务方说他们的需求优先级已经变了。早沟通、多沟通,比什么都重要。
这套从零到一的AI工程体系,我是真刀真枪在项目里跑了一年多才沉淀出来的。如果你正准备开始搭建自己的AI工程能力,别急着搞复杂架构,先把数据版本、实验记录、评估流程、部署监控这条主干跑通,再慢慢添砖加瓦。等你哪一天线上模型出了问题能一眼定位到根因,或者新模型说上线就上线说回滚就回滚,你就会明白这一整套工程化投入的全部价值。