
去年我在一次内部技术分享会上问了个问题在场自认为在做AI工程的人有多少举手的人不少。我又追问了一句项目里你们花时间最多的是哪个环节回答五花八门最多的是“调prompt”少数人说是“处理数据”几乎没人提到“监控线上效果”。这个场景我印象很深因为它戳中了一个普遍误区——很多人对AI工程的认知还停留在“训练或调用一个模型”的层面。我自己也经历过这个阶段。刚开始做相关项目时以为能用现成框架跑通一个开源模型就算入门AI工程了。后来踩了无数坑才明白从“模型能跑”到“系统能稳定交付价值”中间隔着数据、评估、部署、监控、迭代一整条完整链路。这篇文章想把这几年从零开始做AI工程的经验整理成一份可参考的路线图给正在入门或准备转型AI工程方向的朋友帮大家绕开那些我走过的弯路。1. 你以为的AI工程和实际做的AI工程差距比想象中更大先给“AI工程”一个更准确的定位。很多人第一反应是“训练模型”这其实只是其中一环。我更倾向于把它定义为以模型为核心、以系统交付为目标的软件工程实践。换句话说模型只是这个系统里的一个组件真正的难点在于让整个系统在真实环境中稳定、可控、可持续地运转。这里有一个经常被忽略的对比。做学术实验时数据集是固定的目标是刷高离线指标而生产环境下的AI工程数据会不断变化模型要持续迭代服务要保证稳定性任何一个环节出问题整个系统都可能不可用。我见过不少效果优秀的模型上了线就歇菜核心原因恰恰是只关注了模型本身没关注链路里的其他环节。1.1 一个AI工程项目的典型生命周期一个真实落地的AI工程项目基本绕不开这几个阶段需求定义、数据工程、模型选型与训练、评估验证、部署上线、监控回归、持续迭代。需求定义容易被忽略但它决定了后面所有工作的方向。比如业务方说“帮我做一个工单自动分类”你得先搞清楚分类到几级类目错误成本有多高对延迟的容忍度是多少这些不明确后续所有技术选型都可能跑偏。数据工程是很多项目的隐形主力我在实际项目里见过的最极端情况是整个团队80%的时间都花在数据清洗和标注上。模型训练反而是那个“看起来最忙、其实周期最短”的环节。拿一个文本分类项目举例。需求是“自动判断用户反馈是抱怨还是表扬”听起来简单但数据里掺杂大量广告、无意义字符、中英文混杂清洗规则可能比模型结构还复杂。这种时候你就明白AI工程考验的不是模型技巧而是系统工程能力。1.2 数据环节为什么是AI工程的重灾区数据质量决定了一个模型的上限这句话不是鸡汤是客观规律。模型的结构只是逼近这个上限的手段。我做过一个通俗的类比你把数据想象成学生用的教材模型是学生教材错误百出学生再聪明也学不到正确答案。生产环境的数据问题通常是这样的数据源不止一个字段格式不统一有空值有异常值历史数据和实时数据分布不一致同名实异或者同实异名标签的标注标准模糊不同人标的结果对不上。每一个问题都会直接传导到模型效果和线上稳定性上。很多团队在模型上反复调参却不愿意花时间把数据管线做扎实这是典型的抓错重点。1.3 服务环节模型只是系统的一部分到了部署环节问题就更多了。推理延迟、吞吐量、成本、弹性伸缩、灰度发布、版本回滚每一件都跟算法能力无关但每一件都会决定模型能不能真正被业务用起来。我见过一个案例模型精度很好但单次推理要800毫秒业务方说超过300毫秒用户就会流失于是整个方案只能推倒重来。懂一点服务端知识、懂一点部署流程、懂一点性能分析在AI工程里不是加分项而是必修课。这也是为什么我坚持认为AI工程本质上是系统工程模型能力只是其中的一个必要不充分条件。2. 为什么“from scratch”值得做以及哪些模块真的需要手写说回“from scratch”这件事。很多人会问现在开源工具这么成熟为什么还要从零开始我最早接触预训练模型时也用现成的Trainer几行代码就跑通了微调当时还觉得挺轻松。但后来一遇到问题就傻眼日志报错看不懂训练过程没法自定义想加个特殊的采样逻辑都不知道从哪里下手。后来我下决心把训练循环、数据加载、评估脚本都自己写了一遍整个人的状态完全变了。不是说现成工具不好而是你必须先“手写一次”来理解机制之后用现成工具时才真正知道它在帮你干什么出了问题也知道去哪排查。2.1 手写一次到底能换来什么我想把手写的价值总结成三点。第一是全局理解。当你自己写过一遍训练循环你就知道loss.backward()做了什么梯度在哪个环节更新学习率调度器是怎么影响参数更新的。这些细节在调参或排查问题时非常关键。第二是可排查性。别人的代码始终是个黑盒你自己的代码虽然简陋但每一行都清楚压测、debug、加日志都更顺手。第三是可定制性。业务需求千奇百怪总有一些现成框架覆盖不到的场景这时候你能改底层就能比别人多一条路。2.2 值得手写的模块清单根据我自己的经验这几个模块值得至少完整手写一次模块建议做法手写理由数据清洗与预处理管线用纯Python/pandas手写完整流程理解每个字段处理的意义方便排查线上特征不一致训练循环用PyTorch手写forward/backward/optimizer step掌握底层机制后续换框架或自定义操作时不慌Tokenizer构建自己写一次词表构建和编码过程理解文本变成ID的过程避免被封装工具坑推理API服务用FastAPI/Flask手写一个模型服务理解部署链路知道模型的输入输出如何暴露成接口评估脚本自己写指标计算和错误分析避免用错指标知道每个指标到底在度量什么2.3 哪些东西不该自己造轮子“from scratch”不等于排斥一切现成工具。分布式训练框架、高性能推理引擎、向量数据库、监控可视化系统这些没必要自己造直接站在巨人的肩膀上就好。我自己的判断标准很简单凡是半衰期短、跟业务强相关的部分值得手写凡是半衰期长、通用性极强的部分直接用现成的。比如数据预处理跟业务强相关几乎每个项目都不一样手写才有针对性而分布式训练框架是通用基础设施你花三个月写出来的大概率不如DeepSpeed这类成熟方案稳定。把精力花在刀刃上这才是“from scratch”的真正意义。3. 一条真实可复制的AI工程链路从数据到上线的四段推进前面讲了不少理念接下来给一条可以落地的实操链路。我用“短文本工单自动打标”这个场景来贯穿整个流程因为它的数据形态和模型结构都比较简单方便理解全链路又不至于被领域知识干扰。3.1 数据准备把原始数据变成训练集工单系统里导出的原始数据通常长这样一条JSON或者一行CSV包含用户ID、提交时间、工单内容、人工处理结果等字段。首先做字段筛选把真正需要的文本和标签字段拿出来然后去重。工单场景里同一用户会重复提交相似内容按文本内容的哈希值去重是最常见的方式。import pandas as pd def build_train_data(raw_path: str) - pd.DataFrame: df pd.read_json(raw_path, linesTrue) df df.drop_duplicates(subset[content_hash]) # 文本去重 df df.dropna(subset[text, label]) # 去除缺失值 df df[df[text].str.strip().str.len() 2] # 去掉无信息短内容 return df[[text, label]]清洗完数据之后紧接着是标注环节。标注规范非常关键拿“其他”这个默认类别来说不定义清楚就会成为垃圾箱模型学到的全是“不确定就分到其他”。我给团队定的规矩是每条数据至少两个人标标签不一致的交给仲裁人最后统计标注一致性。数据最终要保存版本信息包括数据来源、清洗规则、标注规范、生成时间方便后面回溯。这些看起来繁琐但是排查线上问题时的救命稻草。3.2 训练与微调从baseline到可用模型数据准备好开始训练。我的习惯是先选一个中等规模的预训练模型当baseline比如中文场景用bert-base-chinese不要一上来就上大模型。小模型训练快方便快速验证数据是否存在问题等数据链路跑通了再考虑换更大模型提升效果。训练脚本的关键结构是这样几个部分自定义Dataset负责加载和编码数据模型用预训练权重初始化优化器用AdamW学习率调度器用warmup加线性衰减保存策略是每轮验证后保留最优checkpoint。from torch.utils.data import Dataset, DataLoader from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) model AutoModelForSequenceClassification.from_pretrained( bert-base-chinese, num_labelslen(label_set) ) for epoch in range(num_epochs): preds, labels [], [] for batch in train_loader: outputs model(**batch) loss outputs.loss loss.backward() optimizer.step() scheduler.step() optimizer.zero_grad()几个实际经验供参考batch size受显存限制可以先从8开始能加就加到32一般不要一开始就追求大batch除非配合梯度累积学习率用2e-5到5e-5之间的范围迭代次数2到3个epoch基本够了再多就容易过拟合。训练过程中不能只看loss曲线我还会定期取一批bad case看验证模型是在“理解”数据还是在“硬背”答案。3.3 评估体系不能只盯着准确率工单分类场景里类别往往不均衡“网络问题”和“账户问题”可能各占40%剩下的20%分散在五六个类别里。这种情况下只看准确率没有意义——模型把所有样本都预测成“网络问题”准确率也能有40%。所以要分开看precision、recall和F1特别是每个类别单独算。我见过很多团队在评估环节犯一个严重错误数据划分太随意没有按时间线或按用户维度拆分导致训练集和测试集之间存在信息交叉评估分数虚高。这一点我后面会专门讲踩坑过程这里先给结论划分数据集时至少要保持一个原则——训练数据里出现过的用户不应该出现在测试集里否则模型在“见人下菜碟”。评估环节还必须包含bad case分析。每次跑完验证集我会挑出二三十个预测错的样本逐个看是标注错、文本歧义还是模型理解不到位。这一步对改进数据和模型结构的指导价值比调任何超参数都大。3.4 部署把模型包装成一个稳定服务模型训练完到了部署环节。最轻量的方案是FastAPI把模型包装成HTTP服务加载预训练权重到内存接收文本输入返回预测结果。from fastapi import FastAPI from pydantic import BaseModel class RequestItem(BaseModel): text: str app FastAPI() app.post(/predict) def predict(item: RequestItem): inputs tokenizer(item.text, truncationTrue, max_length128, return_tensorspt) logits model(**inputs).logits label int(logits.argmax(-1)) return {label: label, prob: float(logits.softmax(-1)[0][label])}服务上线前要做的几件事加健康检查接口给容器设置合理的内存和显存上限用压测确认并发能力把模型输出和输入文本的日志打全。发布时先灰度给10%流量观察一段时间稳定了再全量放开。这些事没有一个是算法层面的难度但每一个都直接影响线上可靠性。4. 三个我栽过跟头的地方每个都值得写进避坑手册这部分我想讲几个自己真实踩过的坑重点不是结果而是当时的排查思路和修复过程。希望读者以后遇到类似问题能快速定位不用绕我走过的弯。4.1 数据泄漏评估分数很美上线就露馅第一个坑发生在一个评论审核模型上。离线评估F1刷到0.95所有人都挺乐观结果上线后线上效果差得离谱误杀率比预期高好几倍。当时的排查链路是这样的先怀疑预处理不一致把训练和线上代码对比了一遍没发现问题再怀疑模型过拟合但验证集的视觉效果又确实好最后逐条抽查验证集样本发现测试集里混了很多标注噪声很重的数据而且这些数据在构造时是从同一个时间段的语料里随机切分的同一个用户的多条评论被同时分到了训练集和测试集。模型等于见过“同一个人的说话习惯”自然能猜到答案。修复方案是重写数据划分逻辑按用户ID维度切分确保同一个用户的全部数据只出现在训练集或只出现在测试集。更保险的做法是加入时间维度用前面时间段的数据训练用后面时间段的数据评估模拟真实上线后的分布变化。这次之后我养成一个习惯每次构造数据集时做一次泄漏自检确认训练集和测试集之间没有用户、文本哈希、以及目标字段本身的交叉。4.2 显存OOMbatch size不是越大越好第二个坑是训练启动时直接爆显存。当时给模型加了一组CRF头做序列标注满心欢喜跑起来结果一行日志没看到就OOM了。排查过程比较简单但值得记录。我先把batch size降到1跑一遍发现能运行说明模型本身不算太占显存。然后按2、4、8倍增batch size测试同时用nvidia-smi实时观察显存占用找到临界点。最后发现不是模型太大而是backward时保存的中间激活值占了大量显存序列长度稍微长一点就直接溢出。我当时用的是“梯度累积混合精度”的组合方案。梯度累积的意思是模拟大的batch size但不增大单次反向传播的显存压力——攒几个小batch的梯度再更新一次参数。混合精度则是用FP16做大部分运算减少显存占用。简单列一下当时的处理逻辑先确定单次可承受的最大batch size比如8想使用等效的32的batch效果就设梯度累积步数为4同时开启AMP混合精度显存压力明显下降。这里想多说一句不要盲目调大batch size我曾经以为模型效果差是batch太小后来发现过大的batch有时反而让模型更不容易跳出局部最优收敛效果未必更好。选择batch size的核心标准是“这个batch下loss下降正常、梯度更新稳定”。4.3 训练时和线上推理的预处理不一致第三个坑的特征是“离线效果正常线上服务返回结果却莫名其妙”。当时做一个文本分类服务训练时的预处理流程是先统一转小写、再过滤URL和邮箱、最后做分词线上服务这边因为复用了另一段代码里面省略了URL过滤这一步。模型训练时压根没见过带URL的文本线上一遇到就乱预测。排查链路很有意思。我先在线上随机抽取了几十条推理日志把输入文本和模型实际接收的编码结果一起打印出来和训练样本的编码结果做对比发现同一句话经过线上和训练两个流程后得到的token序列有明显差异。修复方案是把预处理逻辑抽成一个公共函数库训练和线上服务都从同一个包里导入不能让两边各写各的。更绝的是我后来加了一个自动化检查每次上线前随机选100条真实日志样本分别跑训练预处理和线上预处理对比编码结果是否一致不一致就拦截发布。这个思路后来帮我拦下了至少两次低级的发布事故。5. 给同样想从零开始的人学习路线与我的个人体会最后这部分写给那些真的打算从零开始走AI工程这条路的人。我可以给出我自己验证过的路线和一些工具选型建议用不着面面俱到但一定保证你走下来不会慌。5.1 建议的学习路线按我现在的经验看比较顺畅的顺序应该是这样先打牢Python和数据处理基础然后手写一个简单的MLP在MNIST上跑通分类理解梯度下降到底在做什么。接着学习Transformer结构不用读太多源码但要把attention的计算过程自己推导一遍。之后再接触预训练模型微调这时你对底层已经有感觉用huggingface的封装工具会非常顺手。之后学实验管理用MLflow或wandb把每次实验的记录留下来。接着就是部署先把FastAPI的模型服务跑通再学容器化和基本压测。最后可以进阶到性能优化比如推理加速、量化、分布式训练。这条路线不追求快但每一步都稳形成的能力不会因为工具换代而清零。5.2 我的常用工具清单场景工具说明数据处理pandas、SQL快速清洗和探查别一上来就上大数据框架实验记录MLflow记录参数、指标、模型产物简单易上手训练框架PyTorch生态成熟资料多遇到问题容易搜到答案预训练模型transformers加载和微调预训练模型的标准工具模型服务FastAPI写接口轻量性能足够大多数场景监控与日志Prometheus Grafana虚拟机指标和简单业务指标足够用容器部署Docker统一运行环境方便迁移和隔离依赖5.3 我的一些实在话最后分享几点个人体会。第一不要等到所有东西都准备好了才开始AI工程接触的链路很长边做边补是常态。第二一定要养成记录的习惯每次实验的参数、数据集版本、踩过的坑都记下来三个月后你会感谢自己。第三遇到问题先怀疑数据再怀疑代码最后才怀疑模型——按这个顺序排查大概率能省下半天时间。我自己也就着“from scratch”的方式把很多模块重新实现了一遍整个过程谈不上轻松但带来的收益非常直接现在无论是看开源项目源码还是排查线上问题都更有底气。希望这篇内容也能给你一些参考让你在从零开始的道路上少踩一些无谓的坑。