1. 我为什么要把AI工程这条路从头走一遍
这两年做业务系统的时候,我发现一个很有意思的现象:大家的笔记本里都跑过模型,Kaggle 上也刷过几个像样的分数,可真要让自己负责的 AI 功能在生产环境里稳定跑三个月,大多数人心里是没底的。我自己就是这批人之一——训练过分类模型,调过参数,也读过不少深度学习的文章,但真正开始从零构建一套 AI 工程体系时,才发现"能训练模型"和"能交付一个AI系统"之间,隔着整整一个工程化的鸿沟。
正好手头有一个真实需求:团队要做一套基于大模型的内容理解与检索服务,需要把非结构化文本自动分类、抽取关键信息,再接入到已有的推荐流程里。需求听起来不复杂,但涉及数据采集、清洗、标注、模型选型、微调、在线推理、监控评估一整条链路。我没打算买现成的外部API一接了事,因为我清楚后面会有大量定制化需求和数据隐私约束,必须有一套自己能掌控的体系。于是决定从零开始,把AI工程这条路完整走一遍。
这篇文章不是什么"30天上手AI工程"的速成指南,我也不打算给一份包治百病的框架清单。我更想做的,是把从零开始搭建这套系统时踩过的坑、反复验证过的方案、以及最后沉淀下来的方法论,原原本本讲清楚。如果你跟我一样,有工程底子但对AI落地全流程缺一块完整拼图,或者正在犹豫要不要从一个具体项目入手去啃AI工程,这篇文章应该能给你一些实质性的参考。
先说一个核心认知:AI工程和机器学习研究是两码事。研究关注的是模型在基准数据集上的精度提升,而工程关注的是一个带有模型的软件系统能否持续、稳定、可预期地产生业务价值。这意味着数据管线、模型生命周期、推理基础设施、评估反馈机制,每一项都是硬功夫,缺一环整条链就断。我从零开始的过程,本质上就是把这几块地基一块一块夯实的过程。
2. 数据,而不是模型,才是我从零起步的第一道坎
2.1 数据获取的"脏",远比教程里演示的脏得多
我一开始犯的错误,是把精力过度集中在模型选择上,想着"只要用个好模型,效果自然就出来了"。结果第一版数据到手,仅仅做了简单的去重和空值删除就丢给模型训练,随后一测,分类结果的波动大得离谱。同一个样本今天预测为A类,改天重训就变成了B类。后来花了整整两周排查,才发现问题根本不在于模型,而在于数据本身存在系统性偏差。
举个例子。数据里有个"用户来源渠道"字段,因为采集脚本上线时间不同,前半年的数据里这个字段几乎全为空,后半年才慢慢补齐。我最初做特征工程时图省事,把空值统一填成了"未知",结果模型学到的规律完全混乱——它学到的是"来源为未知的样本大概率是负样本",而实际上是"前半年采集的样本恰好负样本居多"。这种隐藏的偏差,任何花哨的模型结构都救不回来。
从那以后我养成一个习惯:任何数据进入管线之前,必须做三类检查。第一是完整性检查,逐字段统计缺失率、空值率,而不是只看总体行数;第二是一致性检查,同一个枚举值在不同时期的数据里是否用了同一套编码;第三是分布检查,按时间维度切分数据观察各字段的分布变化,任何明显跳变都要能解释原因。这三步看着简单,但对后续模型的稳定性有决定性影响。
2.2 给数据建立版本和血缘,代码能回滚数据凭什么不能
工程上对代码的版本管理已经做得很成熟了,git、分支、回滚都是基本操作。可一落到数据上,很多团队的态度就变得相当随意:一个CSV文件改来改去,放好几个副本,谁也不确定线上模型用的到底是哪个版本。
我在这个项目里吃过大亏。第一次微调实验跑出了不错的效果,参数、训练脚本全都提交到了代码仓库,想着怎么都能复现了。结果两周后再跑一次相同的训练,效果明显变差。一查才发现,供训练用的数据文件已经被另一个同事用最新导出覆盖了,而新导出的数据里包含了大量没清洗干净的重复样本。代码没变,数据变了,实验结果自然不可复现。
后来我搭了一套轻量级的数据版本管理方案,原则就是"用管理代码的方式来管理数据"。每个数据集版本有一个独立标识,记录它的来源、清洗脚本版本、生成时间、样本量、各字段的统计摘要。处理脚本统一入仓,从原始数据到最终训练集之间的每一步都有记录,保证任何一次实验都能回溯到确切的输入数据。这套方案不复杂,但价值极高——它让实验结果变得可解释、可复现,也让团队协作时有了共同的"数据事实基础"。
2.3 一份可以拿去用的数据质量检查清单
这里分享一个通用的检查清单,是我在多次返工之后总结出来的。每次拿到新数据,按这个顺序过一遍,能挡掉大多数隐患:
- 重复样本排查:不是只看整体去重,而要看"关键业务字段"组合是否重复。比如文本分类任务中,正文完全一致但标签不同的样本,往往标注错误率远高于均值。
- 标签质量抽检:随机抽200条标注样本人工复核,计算标注一致率。如果一致率低于90%,先解决标注规范问题,再进行训练。
- 时间分布切面:按天、按周统计样本量和标签分布,找出异常的时间窗口,回溯该窗口内数据采集或标注过程是否出现变动。
- 特征穿越检查:确认是否存在"用未来信息预测过去目标"的情况。例如某个特征字段只有在结果发生后才会填充,那它就不应该出现在训练特征里。
- 极端值与异常值:对数值型字段绘制分布图,对文本字段统计长度分布,定位那些明显偏离正常范围的样本,确认它们是有意义的极端还是采集错误。
这套检查看起来琐碎,但每一个条目背后都对应过一次实打实的线上事故。数据工程没有那么多炫技的空间,多数时候拼的就是谁更认真、更系统。
3. 模型选择与微调:不是越大越好,而是匹配任务
3.1 基础模型选型,我衡量的五个维度
模型选型是这个项目里最纠结的部分。市面上可选的开源模型不少,参数规模从几亿到几百亿都有,每个都有自己的优势和适用场景。我最初的想法很朴素:模型越大效果越好,直接上最大的。好在动手前冷静算了一笔账,才避免掉进性能陷阱。
我最终用的是五个维度的综合评估法,而不是单看榜单分数。第一是任务适配度,模型在目标任务上的零样本或少样本能力如何;第二是推理成本,考虑单位时间内的调用量和单次输入长度,估算单台服务器能支撑的QPS;第三是生态成熟度,模型是否有完善的推理优化工具链、量化方案、社区踩坑记录;第四是微调资源需求,全参数微调需要多大显存,低秩微调的稳定性如何;第五是迭代速度,模型版本更新频率,以及是否容易在私有数据上快速迭代。
这五个维度做成一张简单的评分表,几个候选模型一对比,答案就很清楚了。比如对一个文本分类任务,参数量少一个量级的模型,经过合适的微调后效果可能只差两三个百分点,但推理成本能省出一大半,运维压力也小很多。在业务场景里,"够用且可控"往往比"最先进"更有价值。
3.2 什么时候全量微调,什么时候低秩适配足够
微调策略的选择,核心取决于三件事:目标任务与基础模型原始任务的差距、私有数据量的大小、以及可用的计算资源。
我在项目中同时遇到了两种典型场景。场景一是文本分类,任务语义相对通用,只是需要适配特定领域的词汇和表达习惯,数据量有几千条高质量标注样本。这种情况下用低秩适配微调就足够了——只训练一小部分参数,既快又稳,效果已经能到92%的F1分数。场景二是信息抽取,需要模型按照复杂的模板输出结构化字段,通用模型几乎做不对,这时低秩适配明显不够,需要全量微调或者带更多任务头的方式才能学会输出格式的约束。
实操当中的一个重要心得是:微调之前一定要先评估"基础模型本身的能力上限"。方法很简单,拿几百条代表性样本做零样本测试,看看基础模型能到什么水平。如果零样本已经接近目标,说明任务和模型能力之间差距小,微调只是锦上添花;如果零样本惨不忍睹,那要审视的可能是任务定义本身,或者是否需要收集更多数据、换更大的模型,而不是急着上微调。很多人的误区恰恰在这里——数据不够、任务又难,硬微调,结果模型什么都没学好,还把基础能力给带偏了。
3.3 微调训练崩溃现场:我在复现实验时遇到的问题
微调阶段我遇到过几次值得记录的崩溃现场,都是很容易被忽略的工程细节。
第一次是学习率设置不合理。照搬了一个知名开源仓库的默认参数,学习率设得偏高,训练过程看起来收敛得很快,但在验证集上提前过拟合,效果反而比基础模型还差。后来把学习率降到原来的五分之一,同时加上线性衰减和适当的预热轮数,情况才稳定下来。这个经验说明一个事情:微调时"训得上"和"训得好"是两码事,只看loss有没有下降是远远不够的。
第二次是数据集划分的随机性问题。因为数据做了增强,样本间存在大量近似副本,我按单纯随机方式切分训练集和验证集时,同一个原始样本的增强版本被同时分到了两边,导致验证集分数虚高。每当模型在验证集上表现很好,我就怀疑是这个问题在捣乱。解决办法是先把原始样本去重,再对原始样本ID做分组划分,保证任何一组增强数据不会跨训练集和验证集出现。
第三次是精度溢出问题。在GPU上训练时模型参数是FP16格式,部分参数更新后的梯度太小被舍入归零,表现在训练曲线上就是loss到一个平台再也下不去。排查过程中发现浅层网络和嵌入层的梯度尤其严重。最终靠混合精度训练的配置调整,在关键层保留FP32精度,以及使用梯度累积配合更大的有效批次,解决了这个隐性崩溃。这类问题的排查逻辑很值得记下来:训练异常时,先看数据和划分是否有问题,再看学习率和精度设置,最后才怀疑模型结构本身。
4. 把模型包成工程:推理服务和系统化架构
4.1 在线推理与离线批算,选错会让你怀疑人生
模型训练好之后,紧接着就是"怎么用"的问题。我最初想着统一做一个在线推理服务,所有请求实时计算。但实际压测后发现,一部分业务场景根本不需要实时响应——比如内容库的离线批量分析,跑一次可以接受几分钟;而另一部分用户触发的实时检索请求,延迟要求又很苛刻。混在一个服务里,两边都别扭。
后来我做了拆分。离线场景走批处理管道:定时任务从消息队列拉取待分析内容,批量调用模型推理,结果写入结果表,供下游检索和筛选使用。在线场景走独立推理服务:模型常驻GPU显存,请求进来直接在内存中做向量化和前向计算,通过接口同步返回结果。两类场景的输入长度、并发模式、SLA要求完全不同,拆开后互相不干扰,资源利用率反而更高了。
这里有一个内存管理的细节:GPU显存是稀缺资源,一个模型放上去就占了几个GB。一开始我在在线服务里同时加载了两个模型,一个负责向量化,一个负责分类,结果显存告警不断。后来把两个模型合并成一条流水线,输入文本先过小模型做分类,命中特定类别再过向量化模型,既省显存又降低了平均延迟。对于刚起步的小团队而言,用软件架构去压缩硬件需求,比无脑堆GPU实在得多。
4.2 在线服务压测:从线程池炸掉到队列削峰填谷
在线推理服务上线前的压测,我踩了一个印象深刻的坑:并发一高,服务直接停止响应。初版实现很直接,每个请求分配一个线程去执行模型推理。看起来没什么问题,可模型推理是CPU密集操作,线程一旦多了,上下文切换开销飙升,再加上Python的全局解释器锁限制,整个服务的吞吐量上不去,反而在高峰期请求排队越排越多,最后内存也跟着涨爆。
解决方案是放弃"每请求一线程"模型,改成有界线程池加消息队列的组合。请求进来先入队,后台固定数量的工作线程从队列里取任务执行推理,队满时直接返回"服务繁忙"的提示让客户端退避重试。这样一个简单的改造,让服务的峰值吞吐量提升了两倍多,长尾延迟也稳定下来。压测结果告诉我一个做AI服务的基本道理:推理任务依赖的是周期性工作线程的模式,和普通Web服务还是有差异的,不能用常规思维直接套。
另一个重要调整是批处理推理。我在服务里对短文本请求做攒批合并——把同时涌进来的多个请求拼成一个批次同时推理,利用GPU或向量化指令的并行能力,摊薄单次推理的开销。攒批的关键是等待时间可控,我用了2毫秒的时间窗口作为上限,也就是请求最多等2毫秒凑个批次,这样在吞吐和单请求延迟之间取一个平衡点。实测下来,吞吐提升明显,而延迟增加几乎感觉不到。
4.3 高可用设计基础:不要让你的模型成为单点
AI服务上线后常常被当成一个"特殊"服务,好像模型推理天然就应该偶尔出问题。但对我来说,只要它对业务承诺了可用性,就必须按基础设施的标准来做。
我做的最基本一件事是冗余部署。在线推理服务至少保持两个实例,前置负载均衡,任何一台机器挂掉,流量能自动切换。模型文件全部存放在对象存储中,每次发布新版本模型时,先在一个灰度实例上加载并跑冒烟测试,确认输出格式和预期一致,再逐步切流量。这个过程看起来占用人手,但它能挡住大多数"模型效果很好但服务端返回报错"的隐性事故。
还有一项容易被忽略的设计是超时与熔断。下游请求模型服务时,如果模型服务因为显存不足或者推理异常导致响应过慢,上游一定要有超时控制,不能无限等下去。我在模型服务里加了两级超时:单次推理超时和队列等待超时,任何一个超时都快速失败并返回兜底结果。兜底策略从简——返回之前缓存的结果,或者返回一个默认分类,保证业务流程能走下去,而不是整体卡死。经历过一次队列积压导致的雪崩之后,我才真正明白:兜底方案不是为准确率设计的,而是为可用性设计的。
5. 评估机制,AI工程里最值得先建的体系
5.1 离线评估:不能只盯整体准确率
模型训练时,大家习惯盯着整体准确率、F1这些宏观指标。可一旦面向真实业务,这样的评估粒度远远不够。我在这套系统里构建了一套多维度离线评估的方法,核心思路是:把模型能力拆到业务关心的每一个切面上去验证。
首先是分群评估。不同的文本来源、不同的内容长度、不同的语言风格,模型表现可能天差地别。我把验证集按这些维度切成子集,分别计算指标。结果发现模型在长文本上的F1比短文本低了近十个点,这是只看整体指标完全发现不了的问题。定位到薄弱点之后,我针对性地补充了这个子集的训练数据,整体效果提升明显。
其次是困难集和黄金集的建设。从历史数据中人工挑选一批最难判断、最容易错的样本组成"困难集",每次迭代模型都要在这个集合上跑一遍,确保新模型不会在这些关键样本上"开倒车"。另选一批标注质量最高的样本组成"黄金集",作为每次发布模型的冒烟验证数据集。这两套小数据的价值非常大,它们像测试用例一样守护着模型效果的底线,防止"某个指标涨了但实际体验倒退"的隐性回归。
5.2 线上监控与反馈闭环:离线指标再漂亮也只是代理指标
离线评估做得再充分,也替代不了线上真实环境的检验。我在系统上线第一天就发现,线上真实数据的分布和训练集差异比预想的大得多——用户发送的文本里夹杂着大量广告内容、特殊字符、多语言混合,这些在干净的训练集里几乎没有出现过。模型虽然是同一个,线上表现却和离线评估对不上。
所以我建了三层监控。第一层是系统层监控,跟踪推理服务的响应时间、错误率、GPU利用率和队列深度,任何基础设施问题都能第一时间报警。第二层是数据层监控,统计线上输入样本的关键分布指标——平均长度、Top类别占比、拒识率等,一旦某个指标发生明显漂移,立即触发告警,因为大概率是数据来源或者用户行为变了。第三层是业务层监控,跟踪下游应用的核心指标,例如推荐系统的点击率、搜索的转化率,虽然这些指标受多种因素影响,但长期趋势异常往往能回溯到模型问题。
更关键的是反馈闭环。我给系统加了一条弱标签通道:线上预测结果在用户使用过程中自然产生隐式反馈,比如用户是否点击了某个推荐内容。这些反馈汇总到样本池里,经过筛选和人工抽检,按周期回流到训练集,形成"数据—训练—上线—反馈—再训练"的持续循环。没有这个闭环,模型就是一个静态的棋子,用久了必然和现实脱节。这是一条慢功夫,但它是AI系统和普通规则系统的根本区别所在。
5.3 评估可信度,比评估本身更值得被认真对待
这里要特别说一个容易被忽略的问题——评估的数据本身也可能不可信。
有一次我在优化模型后,离线评估显示效果大幅提升,团队差点就直接上线了。幸好我做了一项额外检查:读取了评估样本的标注时间和标注人ID,发现新增训练数据里有相当比例来自一个经验不足的新标注员,标注噪声明显偏高。模型其实没有提升那么大的空间,是评估集里的标注噪声恰好被模型"迎合"了。从那以后,我坚持定期对标注数据进行质量复核,同时保证训练集和评估集的标注标准完全一致,不能让评估集成为"注过水的标尺"。
另一个经验是,每次做模型版本对比时,要用完全相同的评估代码和评估数据。听起来是基本功,但在有多人协作的团队里,评估脚本分散在不同机器、不同分支上,版本错位的概率非常高。我现在把所有评估流程固化成统一的命令行工具,输入模型路径和数据集路径,输出标准化的评估报告,杜绝手工拼指标。可复现、可追溯、口径统一,这三点是评估可信度的底线。
6. 从零开始的重建清单:我的优先级排序
如果让我重新开始这个项目,我会把资源和精力按下面的优先级分配,每一步都尽量缩短"从开始到看到有效反馈"的周期:
- 先用两周时间打通最小闭环:收集一批数据,选一个基础模型,直接做零样本测试,搭建最基础的评价脚本。关键不是效果多好,而是让数据、模型、评估三个环节先跑起来,形成可迭代的骨架。
- 数据质量优先于模型技巧:花四成精力在数据清洗、版本管理、血缘追踪和标签质量控制上。数据稳了,后续所有模型迭代才有可信的基础。
- 模型选型基于成本模型和任务偏差,而不是排行榜:明确自己是要做分类、抽取还是检索,先小规模验证效果,再决定是否放大模型规模。
- 微调从低秩适配起步,稳定优先:只有确认低秩适配的效果达不到要求,再考虑全量微调。每个实验记录数据版本、参数配置和完整评估结果。
- 推理服务设计考虑并发模式、攒批策略和兜底机制:先求稳定可用,再优化吞吐,最后才谈更多花哨的特性。
- 评估与监控体系与上线同步搭建:离线评估、困难集、线上漂移监控缺一不可。没有评估体系的模型上线,等于在无人驾驶状态下开高速。
按照这个顺序走下来的最大好处是:每一步都有清晰的可验证产出,遇到问题可以快速定位是哪一环出了问题,而不是在整条链路上盲目碰运气。
我自己的体会是,从零开始做AI工程,真正难的从来不是某一个单独环节,而是把数据、模型、推理、评估串成一条环环相扣的链路,在每个环节都用工程化的标准去要求它。模型可以换、框架可以变,但数据质量意识、可复现意识、评估意识这几条主线不能丢。这也是我认为AI工程和普通软件开发最相通的地方——它首先是一门工程,其次才是炫酷的模型。
最后分享一个我后来一直沿用的小习惯:每次迭代一个新的模型版本,我都会在提交说明里同时附上一份"一句话效果预期"。线上跑了一周之后,用真实数据去验证这个预期是否兑现。这种把预期写下来、再回头对照的做法,帮我避免了很多"凭感觉上线、凭感觉迭代"的低效循环。AI工程的本质,就是不断用系统的方法去逼近真实世界里的有效结果,而不是靠灵感和运气。