
我真正下决心做一次“ai-engineering-from-scratch”把整套AI工程链路从零开始完整走一遍是因为一次被生产事故狠狠教育了。那个项目的模型在离线测试里跑得漂漂亮亮上线一周后准确率直接对折连同事都开始怀疑我的测试集是不是调包调错了。排查到最后问题居然出在数据预处理和训练代码的版本不一致上——一个半年前就存在的隐蔽bug。当时我就想与其依赖各种封装好的框架和速成课不如亲手把整个AI工程链路从零搭一遍把所有环节的细节都摸透。于是有了这篇“ai-engineering-from-scratch”的完整记录。这篇内容适合三类人准备入行AI工程、想从算法岗转工程岗的研发同学已经在用现成AI能力、但想搞清楚底层链路的产品或后端开发者以及那些跑通过不少Demo、却始终觉得自己在“调包”而不是“做工程”的初学者。我会从工程视角而不是算法研究视角来拆解问题围绕数据、训练、评估、部署四个核心环节展开里面所有操作都是我自己真实跑过的路径踩过的坑也都会讲清楚来龙去脉。1. 从零开始之前AI工程到底在“工程”什么1.1 模型只是冰山一角很多人把AI工程等同于模型训练。实际上在一个投入生产的AI系统中真正的模型代码往往只占很小一部分。数据获取、清洗、标注、存储、特征工程、训练流水线、评估体系、上线部署、监控告警这些环节加起来才是工程的主体。用数据说话我曾经把一个文本分类服务拆开统计代码行数模型相关代码占比不到15%剩下的全是数据管道、接口、缓存、日志、回滚逻辑。所以从零开始第一步不是学最新的Transformer架构而是建立“系统思维”。你写的每一段代码最终都要服务于一个能稳定对外输出价值的系统而不是服务于一次实验。这也是“from scratch”和“调包”的本质区别调包只要知道接口签名做工程必须理解数据怎么流动、模型在哪一环失效、系统如何降级。1.2 把AI能力拆成可训练的技能清单从零开始最怕的是目标模糊。我建议把AI工程需要的能力拆成一张清单按顺序逐个击破数据技能采集、清洗、标注、增强、版本管理建模技能经典机器学习模型、深度学习模型、损失函数与优化器选择训练技能训练循环、分布式训练、断点续训、超参调优评估技能离线指标设计、线上指标映射、A/B测试部署技能模型导出、接口封装、容器化、GPU服务优化运维技能监控、告警、模型版本回滚、数据漂移检测我当时就是照着这张清单给自己定了一个“三个月从零搭出一个可用的文本分类服务”的目标。这样一个具体目标比单纯刷论文和教程有用得多。每完成一个环节就相当于在清单上打一个勾进度可见信心也稳得住。这个技能清单还有一个作用防止你过早陷入某一个细节。比如我见过很多人花了大量时间研究某个最新的模型结构结果数据管道一塌糊涂样本泄漏、标签错误模型再强也是白搭。从零开始做工程的正确顺序永远是先把骨架搭稳再考虑锦上添花。2. 最小闭环从裸机到一个能用的AI服务2.1 环境选型用最稳的而不是最火的动手第一步是搭环境。这里我吃过一次亏一开始直接用最新的Python版本和最前沿的训练框架结果第三方库兼容性连环报错光环境就折腾了两天。后来总结出一条经验选环境的第一标准是稳定兼容不是你拿到了多新的特性。我的建议组合操作系统用Ubuntu 20.04或22.04 LTS稳定且社区支持最全Python版本用3.8到3.11之间的长期支持版本不要刚发布新版本就在生产项目里冲锋训练框架选一个主流的稳定分支优先看它的LTS版本虚拟环境用conda或venv都行但整个项目内必须统一避免不同依赖互相污染配置好环境之后我强烈建议做一件事把依赖导出为可复现的文件连同版本号一起锁死。这一步看似简单实际操作时很多人会漏掉。等三个月后你想复现自己的实验结果就会感谢当时锁版本的决定。我当时用的是pip freeze重定向到requirements.txt同时把关键包的版本号记录在一个表里方便回溯。2.2 数据从哪里来别用干净数据集骗自己很多初学者喜欢用Kaggle上处理得干干净净的数据集来练手。如果你只是想熟悉模型代码这没问题但如果你要练的是AI工程请立刻换成“脏数据”。真实业务场景里的数据一定是字段缺失、格式混乱、标签有噪音、正负样本极不均衡。我从零搭建时选择了一个我熟悉的业务场景——企业内部工单分类。我拿到的原始数据是一堆CSV文件里面不仅有重复行还有大量空值、乱码和错误标签。我自己写了清洗脚本做了以下处理缺失字段区分“正常缺失”和“异常缺失”分别设计处理策略重复样本用文本hash去重同时保留一份去重前的备份标签噪音抽样100条人工复核统计标签错误率并建立修正规则类别分布统计每个类别的数量发现有一个类别占了将近70%明显不均衡这些都是工程里的真实问题。数据质量直接影响模型上限这句话说了无数遍但只有亲手处理过一批脏数据才能理解它的分量。我当时花在数据清洗上的时间占整个项目周期的大约40%而模型训练只占不到20%。这个比例一度让我怀疑自己是不是在浪费时间但后来事实证明数据清洗的每一分钟都是值得投入的。2.3 第一个可运行模型跑通不代表结束数据准备好之后我推荐先用一个最简单的基线模型跑通整个流程而不是直接上大模型。我的第一版是一个基于TF-IDF加逻辑回归的文本分类模型代码量很小训练也快几分钟就能跑完。它的意义在于把数据加载、模型训练、指标评估这条链路完整打通让每一环节都有真实的输入输出。我在这步踩过一个很典型的坑训练脚本能跑通但保存模型后重加载预测结果全是同一个类别。排查了半小时最终发现是因为保存模型时把词表也一起序列化了但加载时没有统一词表导致新的文本被映射到错误的id。这个问题的根子在于“训练与推理不一致”。从零搭建时一定要把模型和预处理逻辑绑定保存并且在加载后做一个冒烟测试用训练时的样本文本走一遍预测确认结果一致。跑通基线模型之后我才开始尝试复杂模型。这个过程让我深刻体会到一件事基线模型的价值不只是“交差”它为你后面所有复杂的改动提供了一个对照基准。后面无论换上什么模型都要拿它和基线对比如果提升不明显说明问题不在模型结构而在数据或特征。2.4 让模型开口说话最小化推理接口模型训练好了接下来要把它变成一个能对外提供服务的接口。这里不用上来就搭微服务先做一个最小可用的推理接口就够了。我用FastAPI写了一个简单的REST接口接受文本输入返回分类结果和置信度。这个环节有几个容易被忽略的点请求数据校验不能假设调用方一定会传合法格式要做字段校验和错误提示超时控制模型推理可能很慢接口必须设置超时防止请求堆积拖垮服务并发与排队如果同一模型同时来很多请求要么加锁排队要么上消息队列否则显存和内存会被打爆我当时的做法是先做同步处理加了一个简单的并发控制限制同时推理的请求数量。后来量大了才换成异步队列。这个演进思路就是从零搭建时应该有的节奏——先把最小闭环跑通再根据真实压力去优化。3. 训练、微调与部署三个最容易“翻车”的进阶环节有了上一节的最小闭环基础接下来就要进入真正拉开差距的三个进阶环节。这三个环节我都翻过车而且翻得非常彻底所以想用这一节把话说透。3.1 训练不收敛时先查数据再查模型第一次训练深度学习模型时我盯着屏幕上的损失曲线发现它一直不降。第一反应是调整模型结构加大学习率尝试各种优化器结果毫无改善。后来一个老前辈提醒我先检查数据再检查模型。我按他的建议排查发现第一个问题是标签从1开始编号但我忘了做偏移模型默认从0开始预测等于所有样本的预测结果天生错位。修正之后损失立刻开始下降。从那以后我养成了一套固定的排查顺序数据维度标签是否正确对齐、是否归一化、样本是否打乱模型输出维度输出维度是否等于类别数、最后一层激活函数是否合适数值稳定性有没有除以0、有没有溢出打印中间变量的张量形状和数值范围超参维度学习率是否过大批量大小是否小得离谱这个教训对“from scratch”尤其重要因为当你自己亲手写训练循环时到处都是出错的可能。框架封装的API帮你避免了很多低级错误但也让你缺失了排查的直觉。自己从零写一遍训练循环哪怕最后还是会换成封装好的库你至少知道报错信息背后的原因是什么。3.2 微调开源模型时我最后悔没早做的三件事有了一定基础后我把工单分类模型升级成了基于预训练模型的微调版本。这一步让我真正理解了“微调”是怎么回事。这里说三件我最后悔没早做的事。第一没提前冻结和检查tokenizer的输出。微调模型和基线模型最大的差别之一就是文本必须经过tokenizer处理成token id而这个环节一旦出错整个训练都是在乱学。我一开始直接用默认分词器处理中文文本结果很多专业术语被切得四分五裂。后来我专门配置了词典并在训练前打印一批样本的人工可读结果肉眼确认分词质量。第二没做训练前的“数据形状冒烟测试”。把数据送进模型之前先跑一个batch检查张量形状是否匹配、标签是否在同一设备上。这个冒烟测试能避免90%的枯燥debug时间。我后来每次写新模型都会先跑这一步。第三过早追求大模型。我最初试图直接微调一个很大的模型结果单卡显存不够训练速度极慢还没跑完一个epoch就OOM。后来我换成小一号的模型先跑通流程验证了效果之后再做模型规模扩展。这个顺序可以帮你快速定位瓶颈到底在算法层面还是在工程层面。3.3 部署推理时绕不开的延迟与显存账微调好的模型要真正发挥价值最后一步是部署上线。这里需要算两笔账一笔是延迟账一笔是显存账。延迟账用户从发出请求到拿到结果这个时间是多少如果模型推理要600毫秒而业务要求的P95要控制在300毫秒内你就不能简单做个同步调用。要么模型精简量化要么加缓存要么把模型拆成多个小模型并行推理。显存账模型参数量对应的显存占用不等于参数量乘以精度字节数。实际部署时中间激活值、CUDA上下文、推理框架的额外开销都要算进去。我当时部署一个中等规模模型参数占用大约2GB但实际启动后显存占用超过7GB。这个差距如果不提前估算生产环境很容易OOM。涉及量化时我的经验是优先做精度无损的优化把模型转成高效的推理格式往往能同时降低延迟和显存。如果还不够再考虑量化但必须注意精度回退。我当时用一个小型验证集对比量化前后的效果确保指标不掉再上线。4. 从零搭建必须知道的翻车现场与排查链路下面这三段都是我从零搭建过程中真实遇到、并且花了很长时间才解出来的问题。我之所以不直接给答案是想把整个排查链路完整还原出来因为思路比答案值钱得多。4.1 损失函数输出NaN的完整排查过程完整记录一次排查过程比看十篇博客都有用。我在训练过程中遭遇过一次损失突然变成NaN的情况训练到第1200步loss从2.1骤降到NaN整个模型直接废了。当时的排查链路是这样的第一步缩小范围。我把数据集从几万条缩小到200条把训练步数缩小到200步快速复现这个现象。结果200步内必定出现NaN说明问题不在随机性而是确定性bug。第二步打印中间值。我在训练循环里加了几行打印逻辑检查每一层的输出、梯度和损失值重点看哪一步开始出现“inf”或“NaN”。结果发现是某一层特征经过计算后数值爆炸从几百直接变成几亿。第三步定位根源。数值爆炸通常要么是学习率过大要么是数据范围过于极端。我先尝试把学习率从1e-4降到1e-6NaN推迟出现但仍在再把输入数据做标准化NaN消失。这说明问题正出在输入数据的数值范围上。第四步修复与验证。我给特征增加标准化处理并把归一化逻辑写进了预处理管道保证训练和推理一致。修复后重新跑完整数据loss稳定下降到收敛。这个案例里最有价值的经验是出问题时先缩小范围快速复现再逐层打印定位而不是迷茫地调整各种超参数。复现、定位、修复、验证四步走完bug才能被清清楚楚干掉。4.2 训练准确率100%但线上效果打骨折这个坑我开头提到过。现象是训练集准确率接近100%验证集也有95%以上一上线就崩。排查过程如下首先检查训练和推理的预处理一致性。我写了一个回归测试用同样的输入文本走训练时的预处理和走服务接口的预处理对比输出token id是否一致。结果发现服务端的文本清洗逻辑与训练脚本不完全一致比如做大小写归一化的顺序不同导致同一个句子最终进入模型的形式变了。接着检查采样逻辑。训练时我是从长时间段的数据里随机采样但线上流量集中在很短的时间窗口存在明显的时间分布偏移。模型见过的样本分布和真实样本分布不同效果自然打折。最后检查特征与标签定义。服务端调用方理解的“类别”和训练用的“标签”存在细微差别比如边界情况的归类不一致。修正统一口径之后正确率明显回升。这类问题的本质是“测试环境不等于生产环境”。处理方式就是建一套冒烟测试集——一组专门覆盖各种边界情况的样本每次部署前后都跑一遍输出一份报告。我从那以后坚持这个习惯它至今帮我拦下了好几次线上回归事故。4.3 数据泄漏藏在“清洗”里的隐形杀手数据泄漏是我觉得最隐蔽、最容易被忽略的一个问题。它的意思是训练阶段模型“看到”了本不应该看到的未来信息或测试集信息导致评估指标虚高。我做的一个模型就翻过这个车我用的特征里包含了一个“是否最近活跃”的字段这个字段是通过全量数据计算得到的但在真实预测时它依赖于未来时间窗口的数据。也就是说我的训练代码“偷看”了未来。评估时指标好看得离谱但真实场景根本没有这个字段的可用版本效果自然崩溃。后来我建立了一套数据版本管理机制每次生成训练特征都记录“当前可见时间点”的截止时间所有特征必须在推演时也能用相同逻辑从当前达到的数据计算出来。这一条被我写进了项目规范之后再也没有出现过同类泄漏。5. 最后还想说的几句实在话从零开始学AI工程最难的不是某个知识点而是整个过程中持续不断的小挫败。你会遇到环境装不上、数据对不上、模型不收敛、服务一上线就崩等各种问题。但正是这些挫败帮我把那些“看起来会”变成“真正会”。我个人最推荐的一条路线找一个你熟悉的业务场景做一个端到端的小项目从数据采集开始一直到接口上线。整个过程不要依赖一键部署的封装平台每一个环节都亲手做一遍。这个过程里你会踩到各种坑但每个坑都会让你对这个领域的理解上一个台阶。如果时间有限优先花在数据和评估上。这两个环节是AI工程里最“脏”但也最决定成败的地方。模型结构可以换、超参数可以调但如果数据和评估的口径不正所有努力都会变成空中楼阁。还有一个小技巧想分享给自己的项目写一份“工程日志”每踩一个坑就记录三样东西——现象、排查过程、最终原因。三个月后你会发现这份日志的价值远超任何一本教材。这基本上就是“from scratch”最好的副产品你不仅拿到了一个能用的系统还收获了一套属于自己的问题排查方法论。