
说说“ai-engineering-from-scratch”这件事。我见过太多人把AI工程理解成“调一下API”“跑通一个notebook”真正遇到数据垃圾、显存溢出、模型上线后效果飘忽这些事一下就懵了。“from scratch”这个路线说白了就是逼着你把AI系统的每一块骨头都亲手摸一遍数据怎么进来、怎么清洗、怎么划分模型怎么训练、怎么收敛、怎么保存上线之后怎么服务、怎么监控、怎么迭代。它能搞定的不是某个具体算法而是你对整套AI工程体系的掌控力。如果你已经会用框架跑通demo却总觉得换个场景就hold不住这篇文章就是给你准备的。1. 先聊清楚这条“with scratch”路线到底要解决什么问题1.1 为什么我不推荐你直接套框架很多刚入行的朋友习惯一上来就拉一个HuggingFace的模型再配一套现成的训练脚本数据塞进去loss下降皆大欢喜。这个流程本身没错但它掩盖了太多关键细节你的数据是不是干净的划分的时候有没有泄漏学习率为什么是这个值显存不够到底是代码问题还是数据问题模型上线之后预测分布变了怎么办这些问题的答案在现成框架里是找不到的。框架帮你把复杂的事情封装好了同时也把你和真实问题隔开了。我见过不少能熟练调用各种库的开发者一旦需要自己处理一个非标准格式的数据集或者把模型部署到低延迟场景就完全无从下手。原因很简单他们从来没有从零把一个系统搭起来过不知道每个环节内部到底发生了什么。“from scratch”这条路的价值就在这。它不是让你重新发明轮子而是让你把轮子拆开看一遍搞清楚它为什么是圆的、辐条怎么受力然后再把轮子装回去。这个过程会很慢很痛苦但走完之后你对AI工程的理解会是体系化的而不是点状的。1.2 路线整体设计把AI工程拆成四层我自己的习惯是把“AI工程”拆成四个大层每一层都对应一组可以独立验证的技能数据工程层数据获取、清洗、去重、标注校验、划分、数据管线构建。这一层决定了模型的天花板。很多人说“数据和特征决定了机器学习的上限”这句话放到工程体系里依然成立只是表现方式变成了数据质量不过关后面所有环节都在白费力气。模型与训练层模型选型、损失函数设计、优化器配置、训练循环实现、超参数调整、分布式训练、混合精度、断点续训。这一层是大多数人最关注的部分但也是最容易“照抄参数然后翻车”的部分。部署与推理层模型导出、服务化封装、推理优化量化、批处理、引擎选择、延迟与吞吐调优、灰度发布。这一层是“能跑demo”到“能上线”之间的天堑也是AI工程师和算法研究员最明显的分水岭。评估与迭代层离线评估指标设计、回归测试集建设、线上监控指标、bad case分析、模型更新流程。这一层决定了系统的长期健康度也是最容易被忽视、导致系统“越跑越歪”的根源。这样拆的好处非常明显每一层都能单独验证单独出问题单独查不用等到最后才发现整个系统崩了却不知道崩在哪。我自己在带项目的时候也严格要求每个阶段必须有可检查的产出物而不是一路闷头冲到部署。1.3 这条路线适合谁不适合谁说实话这条路不适合所有人。如果你只是想快速做出一个效果还行的模型解决眼前的任务直接用成熟的框架和AutoML工具效率更高没必要自讨苦吃。但如果你打算在AI工程方向长期深耕或者你所在的业务场景非常特殊数据格式怪异、延迟要求苛刻、领域知识复杂那这条路线几乎是必经之路。具体来说我觉得适合三类人。第一类是刚入行一到三年的开发者想从“会调用”进阶到“会构建”。第二类是算法工程师想补齐工程短板让模型不只停留在paper里。第三类是技术负责人需要评估AI项目可行性、设计技术方案没有亲手搭过整个链路的人很难做出靠谱的判断。不适合的人也很清楚只想要结果不想要过程的、时间非常紧急的、公司已经有成熟AI平台只想用平台的。这些情况下“from scratch”都是一种时间浪费。懂得判断什么时候该从零搭、什么时候该用现成方案本身就是工程能力的一部分。2. 数据工程最先要过的坎2.1 数据规模与质量的取舍多少样本才够这是被问得最多的问题但也是最没法用一个数字回答的问题。我问你几个反问题你做的是什么任务类别有多少数据本身的信噪比如何你对模型精度的期望值是多少这些条件全都不一样样本量需求天差地别。不过可以给出几个粗糙的经验坐标。对于文本分类这种相对简单的任务类别不太多几十类以内的情况下每类至少要有几百条样本整体到1万条级别才能看到一个相对稳定的指标曲线。再往下比如每类只有几十条那任何指标波动都可能是噪声先别急着调模型先想办法扩充数据。对于生成类任务或者开放域任务数据量需求则通常再高一到两个数量级。比样本数量更关键的是样本质量。我踩过最深的坑是数据里有大量重复和近似重复的样本。比如一个电商评论数据集里同一个用户反复发布几乎相同的评论直接把训练集里的重复率拉到30%模型训练出来之后在验证集上表现不错一上真实场景就垮——因为它实际上在背诵高频样本而不是理解语义模式。清洗之后重复率降到2%同样一个模型结构线上效果涨了十几个点。数据清洗不花一分钱GPU收益却往往比换模型大得多。还有一个容易忽略的点标签噪声。模型对标签一致性的敏感程度远高于你对数据“看起来差不多”的容忍度。几个人标注的口径如果不一致模型学到的东西就会混乱。有条件的话一定要做标注一致性抽检哪怕只是抽几十条人工复核都能发现不少问题。2.2 预处理与数据泄漏的血泪教训数据泄漏算是工程里最阴间的问题因为它不会让你报错只会让你的指标幽灵般虚高。最常见也最经典的时间序列任务里用了未来信息。我之前做过一个销量预测项目把整个时间段的特征都做了全局归一化结果模型训练指标漂亮得吓人到了线上预测完全失灵。原因就是归一化的均值和方差是从全量数据里算出来的包含了未来数据的信息。正确的做法是只用训练集的统计量并且滚动窗口划分数据保证验证集的每个时间点都晚于训练集的全部时间点。文本数据也有类似的坑。比如做文本分类数据里有文本长度这个统计特征而测试集构建时又刚好是长度分布的某个子集模型就可能学到“长文本属于某个类别”这种伪规律。更隐蔽的情况是你在预处理阶段对整个数据集做了同样的清洗或截断操作使得不同类别的数据形态产生了结构性差异模型捕捉到的根本不是语义信号而是这些人为差异。处理这类问题的核心思路只有一条数据划分必须发生在任何有监督信息的预处理之前。先把训练集、验证集、测试集按原始状态切好再去分别做清洗、归一化、特征工程。我遇到过很多次想图省事先统一清洗再切分的情况最后都因为奇奇怪怪的指标异常被迫返工。这个顺序问题值得你一开始就重视。2.3 数据管线的构建与加载速度训练过程中的数据加载往往是工程里最隐形也最拖后腿的部分。我见过不少人训练一个模型GPU利用率只有百分之三四十原因竟然是DataLoader在吭哧吭哧地读磁盘、解码图片数据喂不过来。GPU算得快数据跟不上训练时长直接翻倍这属于典型的“木桶短板”。PyTorch的DataLoader有几个常用参数值得认真调。num_workers一般建议设成CPU核心数或核心数的一半太少了数据加载不够快太多了进程切换开销反而增大。pin_memoryTrue在训练场景下通常能带来几个点的性能提升代价是内存占用多一些。shuffle一定要开除非你的数据本身就是随机顺序否则模型会学到batch之间的顺序模式。还有一个容易忽略的点预处理操作到底是放在数据加载时做还是提前离线做好。如果每个epoch都要对原始数据做同样的重活比如正则表达式清洗、复杂的文本解析那就应该把这些结果提前缓存线上加载时只做轻量的转换。我用过一个很粗暴但有效的方案把清洗后的数据序列化成二进制格式比如Arrow或pickle加载速度能提升五六倍训练迭代速度快了实验周期自然就短了。3. 模型与训练工程从基线到可收敛3.1 框架怎么选PyTorch为主为什么框架选型这个话题很容易变成粉丝论战但落到实际操作上我的结论很明确大部分场景选PyTorch特殊场景另说。原因不是PyTorch比TensorFlow或者JAX更“高级”而是它的调试体验和生态完整度更适合作工程迭代。PyTorch的命令式编程模型让断点调试变得非常自然。你可以在训练循环的任何位置插入打印、检查梯度、查看中间变量。这种可观测性在从零搭工程时至关重要因为你会遇到大量“不知道哪里出问题”的时刻一个能随时停下来看内部状态的框架能救你很多次。相比之下静态图框架在性能优化上有优势但从零开始排错的时候那层“编译-执行”的转换会让人非常头疼。生态方面PyTorch的覆盖面从模型库到部署工具链几乎全打通了模型训练用PyTorch导成ONNX再用Triton或ONNX Runtime做推理这条链路非常顺畅。还有一点招聘市场上熟悉PyTorch的候选人多团队协作和后续维护都更容易。框架只是工具选一个你能在项目周期里快速排错、社区资料充足的就是好选择。3.2 一组能用很久的训练参数基线从工程角度我强烈建议你准备一套“默认参数基线”所有新项目先跑这套基线再根据具体情况调整。这样能极大减少重复试错的成本。下面给我自己常用的一套以Transformer类模型微调场景为例优化器AdamWlr2e-5到5e-5起步小数据集从2e-5开始调weight_decay0.01warmup_ratio0.1即前10%的训练步数学习率线性上升batch_size32起步显存不够用梯度累积等效到32混合精度AMP开启torch.cuda.amp或者直接用autocast和GradScaler梯度裁剪max_grad_norm1.0训练轮数先从3个epoch开始观察验证集指标是否还在上升再决定是否延长为什么这几个参数是这种搭配AdamW本身已经能处理很多训练不稳定问题weight decay提供轻量正则化防止大模型在数据量不足时过拟合。Warmup是让模型在训练初期不要用太大的学习率猛冲先用小步幅适应参数空间再进入主要训练阶段。我见过太多人把warmup忽略掉结果loss直接起飞还以为是代码写错了。混合精度这块多说一句。AMP能显着减少显存占用并加快速度尤其是在TensorCore支持的GPU上。我第一次在项目里开AMP时非常担心精度损失实测下来fp16的前向和反向后再配合loss scaling绝大多数任务精度损失可以忽略不计。如果你的模型训练速度不够快先检查有没有开AMP这个改动通常比调任何超参都立竿见影。3.3 实验管理没有记录等于白练我犯过最蠢的错误之一是训练完一个模型觉得效果很好但记不清用了哪组超参数、哪份数据版本。等到要复现、要调优的时候只能靠回忆和猜。这种情况一次两次还好多了之后你根本无法判断实验间的差异到底是来自参数改动还是数据改动。后来我强制自己用实验管理工具不管项目规模大小。轻量方案可以用CSV文件记录每次运行的关键信息稍微正规一点可以用MLflow或者Weights Biases。我自己的偏好是WB它的交互界面方便快速对比多组实验的指标曲线团队协作时大家都能看到实验结果减少“各自为政”的混乱。实验管理不只是“记一下参数”那么简单核心是记录数据版本和代码版本。模型效果变好或变差你得能回答“是哪个改动导致的”。我的习惯是把数据集每次清洗后的版本打标签代码用git提交哈希关联训练脚本里自动记录这些信息。这样一来任何一次训练结果都能追溯到完整上下文。这花不了多少时间但能让你在项目后期省出大量的“考古”时间。4. 推理部署与线上闭环让模型真正干活4.1 服务化把模型变成一个接口训练出一个指标不错的模型这只是第一步。真正让模型创造价值的是把它封装成一个能响应外部请求的服务。很多初学工程的人会低估这一步的复杂度模型加载要多久并发请求来了怎么办输入数据格式怎么校验不支持某个输入时该返回什么我的经验是先从一个“能用”的版本开始别过度设计。最常见的方案是用FastAPI包一个HTTP接口加载模型到内存接收JSON格式的输入预处理后丢给模型推理再把结果包装成JSON返回。这个版本响应速度不会很快但足够让业务方先联调起来也让你对整体流程有直观认识。服务化过程中有一个很容易忽略的细节输入校验和异常处理必须做。线上请求的数据格式千奇百怪如果模型报错直接导致服务崩溃那运维事故就来了。在接口层把参数校验、超时控制、异常捕获做好比什么都重要。模型加载的时机也值得注意。懒加载服务启动后再加载模型会让第一个请求非常慢容易让调用方误以为服务挂了。我更推荐服务启动时就预加载模型哪怕让初始化时间多几秒钟换来线上请求的稳定。如果模型很大加载一次要几十秒甚至更久可以考虑常驻内存或预热机制这比每次冷启动要靠谱得多。4.2 推理优化量化、批处理与引擎选择部署上线之后你很快会遇到性能问题并发上来了响应时间变长GPU成本居高不下。这时候就该做推理优化了方向主要有四个模型压缩、推理引擎、动态批处理、缓存。量化是见效最快的压缩手段。把fp32模型转成INT8在精度损失可接受的范围内推理速度通常能提升两到三倍显存占用也大幅下降。实操时先用一个小验证集评估量化前后的指标差异尤其关注业务重点关注的指标比如分类任务的F1排序任务的NDCG如果损失在可接受范围内直接上量化版本。我见过量化后指标几乎不掉但速度快了两倍还多的案例也见过量化后直接崩坏的任务所以评估这步不能省。推理引擎方面ONNX Runtime和TensorRT是我最常用的两个。ONNX Runtime胜在通用性好支持的算子覆盖广导出后基本都能跑TensorRT在NVIDIA GPU上性能更极致但转换过程对模型结构的兼容性要求更高某些自定义算子会卡住你。工程上我的建议是先导ONNX用ONNX Runtime跑如果延迟还不达标再尝试TensorRT。别一开始就上最复杂的方案。我自己有一次为了追求极致性能在TensorRT上折腾了几天最后发现ONNX Runtime配合调整批处理策略已经满足了业务需求前期的折腾纯属过度优化。动态批处理是提升吞吐的利器。真实业务里请求是零散到达的如果一个一个推理GPU利用率很低。动态批处理把一小段时间内的多个请求攒在一起凑成一个batch统一推理整体吞吐能翻好几倍。实现的方式可以是在服务层做一个队列攒够数量或等待超时就触发一次推理。参数上我一般把最大batch设为8到16最大等待时间设为10到20毫秒。需要注意这两个值存在矛盾最大batch设大了容易让延迟升高等待时间设长了也伤害延迟具体取值要用实际的请求到达率去压测调整。换个好懂的类比训练阶段就像研究一道菜的菜谱跑通铁锅炒菜部署阶段则像开餐厅后厨要同时给很多桌出菜不能因为某桌客人点单就让其他人干等着。动态批处理、量化和推理引擎都是从“做出一道好菜”走向“稳定出很多道菜”的手段。4.3 评估与回归迭代不翻车的守门人模型上线不是终点而是一个新的开始。业务数据会变、用户行为会变模型的效果一定会随时间衰减。如果每次更新模型都是“凭感觉觉得效果变好了就上”那系统早晚会翻车。我的做法是维护一套“金标回归集”。这组数据是精心挑选的、覆盖业务关键场景的样本标注质量有保障不参与任何训练。每次要上线新模型前先在回归集上做完整评估与线上旧模型对比。如果新模型在回归集上主要指标没有下降、关键bad case有改善才允许上线。这个流程看似烦琐但能拦住绝大多数“局部优化、全局劣化”的情况。线上监控则是另一道防线。除了基础的请求量、错误率、延迟我更关注几个模型专属指标预测结果的类别分布漂移如果线上预测的类别比例和训练时候差很远说明数据分布变了、平均置信度变化置信度普遍下降往往意味着遇到了没见过的输入、空预测率和兜底分支触发率。这些指标一旦出现趋势性的异常就要立刻排查通常业务侧已经发生了变化。5. 常见问题与排查技巧实录5.1 训练不收敛的雷区训练不收敛或者收敛后效果差是新手遇到最多的现象。我的排查顺序是固定的先看数据再看训练配置最后才怀疑模型结构。数据层面最常见的是标签错误尤其人工标注的数据有时错误率高得惊人。我曾经接到一个“模型怎么调都不涨”的项目最后抽样检查训练数据发现标签错误率接近15%把明显标错的样本修了一批之后指标直接原地起飞。所以遇到不收敛我的第一件事永远是随机抽几百条训练样本人工看一遍核对文本内容和标签是否一致。训练配置层面优先级最高的是学习率。学习率太大loss会剧烈震荡甚至直接爆炸学习率太小loss下降很慢像蜗牛爬。排查时画loss曲线最直观震荡锯齿状多半是学习率偏高平缓下滑太慢可能需要加大学习率。其次检查是否开了梯度裁剪以及warmup比例是否合理。最后检查优化器里的weight decay是否设得过大这也会导致loss降不下去。模型结构的问题一般最后才怀疑因为随机初始化的模型在数据不太差的情况下总会有些学习信号完全不下降往往说明前面某一步已经出问题了。5.2 显存爆了的排查路径OOMOut of Memory是训练阶段最容易遇到的错误之一。很多人第一反应是调小batch size这确实是最直接的方案但有时候问题根源不在batch size。我先说一下排查顺序先看是不是数据加载阶段把整个数据集都塞进了内存或显存。有人为了方便把预处理后的数据一次性转成Tensor丢到GPU上几万条样本一上去就爆了。这个问题常见且隐蔽改成DataLoader按需加载就能解决。排除了数据问题之后再看模型本身。序列模型特别关注序列长度是否被无谓拉长图片模型关注输入分辨率是否过大。我这里有个经验先用最小的batch size跑一步试试如果batch1依然OOM那一定是模型或数据加载的问题而不是batch大小的问题。梯度检查点gradient checkpointing是另一招它对前向传播的中间结果做了“时间换空间”的取舍能大幅降低显存代价是训练变慢一些。最后还可以考虑混合精度它能同时减少显存占用和提升速度值得优先检查是否已经开启。5.3 数据管线慢得离谱GPU在等数据这是最浪费时间的故障之一。表现特征是GPU利用率上不去训练日志里每个step的耗时跳动很大数据加载的时间明显超过模型计算时间。排查方法也很直接在训练循环里分别统计数据加载耗时和模型计算耗时看谁占大头。数据加载慢的常见原因有这么几个。磁盘IO是瓶颈数据文件是大量小文件读取速度极慢解决办法是把小文件合并成大文件或者使用内存映射文件。预处理过重每个epoch都对原始文本做复杂解析解决办法是把清洗结果缓存成中间格式加载时只做轻量转换。num_workers设置不合理太高或太低都影响性能我通常从等于CPU核心数开始试做一次简单的profiling就能确定最佳值。还有一个细节是shuffle的位置。把shuffle放在预处理之前会导致每次epoch都要先花时间重新打散原始数据如果在加载器里用随机采样器则更高效。这种问题不致命但累积起来会让训练周期慢上不少。5.4 推理延迟忽高忽低模型上线后延迟抖动是部署环节最让人头疼的问题之一。P99延迟飙高但平均延迟看起来正常这种时候千万不能只盯着平均值看。抖动最常见的来源是动态批处理参数设置不当。最大等待时间设得太长会导致某些请求在队列里排队过久P99延迟恶化。解决办法是缩短等待时间增加最大batch大小让批处理更激进地“攒够数量就发车”而不是“干等时间到”。另一个来源是推理引擎的冷启动或预料之外的小批量请求。比如ONNX Runtime第一次推理时要做算子优化和内存分配所以服务上线后的前几个请求会特别慢。我的经验是在服务启动后做一次“预热推理”跑一两个假样本把一次性开销消化掉之后的延迟就会稳定下来。还有一个容易被忽略的原因模型推理时CPU和GPU之间的数据拷贝太频繁。当输入数据是文本、图像这种非Tensor格式时每次请求都要做序列化和反序列化这会增加不小的额外开销。解决办法是尽量复用Tensor缓冲区减少不必要的内存分配和拷贝。我实测过仅仅优化这部分P99延迟就能降低20%以上。5.5 高频问题排查速查表问题现象可能原因优先排查动作loss震荡不降学习率过大、warmup缺失调小学习率检查warmup比例loss下降极慢学习率过小、数据太稀疏适当调大学习率检查数据质量训练中OOMbatch过大、数据一次性载入减小batch检查数据加载方式GPU利用率低数据加载慢、num_workers不合理profiling加载耗时调workers数量验证集指标虚高数据泄漏检查划分时机和归一化方式线上效果与离线不符分布漂移、特征缺失检查线上特征覆盖率监控类别分布推理P99偏高批处理等待过长、冷启动调批处理参数做服务预热量化后效果暴跌量化敏感性高、评估集太小换量化方式用更大验证集评估6. 从“能跑”到“生产级”的进阶扩展6.1 生产级系统还需要什么按上面的路线全部走完你已经有了一条“从零到能跑”的完整链路数据有了、模型训了、接口上了、监控挂了。但距离一个真正可靠的生产级系统通常还差三个层面的东西。第一是特征与数据治理层。模型用到的大多数特征在业务系统中可能散落在各个数据源里需要一套统一的特征存储来管理和复用。离线训练用的特征和线上推理用的特征必须来自同一套逻辑并保持一致否则就会出现离线好、在线差的老问题。这个一致性问题在从零搭建时能靠“手动同步”糊弄过去规模一上来就不能这样干了。第二是模型与实验平台层。模型的版本管理、灰度发布、AB实验分流都需要平台化支持。上线一个新模型不能直接把旧的覆盖掉而是要能一键切换、一键回滚。我见过太多团队因为模型回滚困难线上出了事故还要紧急改代码这种体验非常痛苦。提前把模型注册和版本管理机制建好成本很低收益极高。第三是可观测性与告警体系。不仅是CPU、内存这些基础监控更要关注模型语义层面的健康状况预测分布漂移、关键字bad case的出现频率、请求内容的异常变化。单靠上午看一次dashboard远远不够设置合理的告警规则在问题发生的时候第一时间收到通知才能避免把小问题拖成大事故。这一步不需要特别重的工具一套定时任务加告警推送就能起步。6.2 我的三点实操体会从零搭建AI工程这条路我走了三遍了。第一遍用各种现成库拼拼凑凑勉强把流程跑通但内部原理一知半解第二遍开始动手写训练循环和推理服务遇到各种问题后回头查资料补基础才算真正入门第三遍才把数据、模型、部署、监控的耦合关系彻底理清能做到“换一个业务场景也不慌”。我的第一点体会是千万别追求第一次就搭出完美的系统。先用最笨的方式、最简单的方式把全链路跑通哪怕训练慢、代码丑、接口简陋都行。跑通一次之后你脑子里有了完整的整体图谱再回头优化每一层方向感会完全不同。很多人是在第一步“把链路跑通”就卡住了因为总觉得自己还没准备好、还要再学点东西才可以动手。实际上动手跑通本身就是最好的学习方式。第二点体会是建立“异常先怀疑基础设施”的排查直觉。大多数问题——训练不稳、推理变慢、指标异常——根源往往不在模型本身而在数据管线、资源分配、服务框架这些不起眼的地方。先排查基础设施再怀疑模型逻辑这条路径能节省大量时间。等你在数据管线和部署细节上栽过几次跟头之后这种直觉自然就建立了。第三点体会是文档记录的习惯越早建立越省心。做一个从零开始的工程你要做的决策比想象中多得多为什么选这个优化器、为什么这样划分数据、为什么推理用了ONNX Runtime而不是TensorRT。这些决策当时觉得理所当然三个月后回头看完全找不到理由。哪怕只是花五分钟在代码仓库的README里写一段“本项目的关键决策和原因”后续交接和自我回顾都会轻松很多。踩过几次坑之后你会慢慢建立起一种感觉——面对一个全新的AI项目脑子里像有一张地图上面标着从数据到模型到部署的每一站以及每一站容易翻车的点。这种感觉最珍贵它不是任何现成框架能教你的只能靠你自己一步步走下来。如果你正打算上手这条路线别犹豫直接开始第一版再简陋也没关系动手跑一次完整的链路比看十篇教程都管用。