
标题里带“from scratch”是很要命的。见过太多人一上来就追求最前沿的大模型微调方案结果连自己的数据长什么样都没搞明白也有同学简历里写了三四个AI项目一问到“模型怎么上线的”就沉默。我叫它“AI工程”不是因为它听起来比“算法工程师”高级而是因为它的本质正在于此让模型从想法走到可用走完一条完整的、可重复的、不靠运气的路。这篇文章就是围绕“ai-engineering-from-scratch”这个话题展开的。我会从AI工程的能力地图讲起聊清楚工程化和“调参实验”之间的边界然后逐步拆开环境搭建、数据工程、训练评估、部署监控这几个核心环节中间穿插具体参数、命令、排查思路以及我个人踩坑后的经验。适合谁看一种是已经在做算法或后端开发但觉得AI项目“跑通就完了”之后没下文的人另一种是想转行AI工程方向、希望系统梳理知识体系的同学。文章不会从线性代数重新讲起但会解释很多课堂不会告诉你的事情。1. AI工程到底是什么以及为什么值得从零盘一遍1.1 科学与工程的分界线学术界关心“这个方法能不能work”工程界关心“这个方法能不能一直work并且work得可控”。在AI语境里区别尤其明显。你可能在一个Notebook里跑通了一个效果不错的分类模型准确率98%于是你觉得任务完成了。但工程会追问这98%是在哪一批测试数据上得到的这批数据和线上真实分布一致吗如果明天数据分布变了模型还在跑吗重训一次需要多长时间谁触发重训模型推理延迟能不能扛住线上流量如果有人误用了这个模型本地复现不出你的结果怎么办这些问题单个看起来都不难难的是它们之间的耦合。数据、代码、模型、配置、监控任何一环出问题都会让整个系统毫无征兆地退化。AI工程就是把这些环节当成一个完整的生命周期来管理而不是每次临时救火。这也是为什么“from scratch”值得做——不是从零重复造轮子而是从零理解每一个轮子为什么需要存在、在什么条件下会转不动。1.2 端到端全链路应该长什么样我习惯把AI工程拆成六个环节需求与指标定义、数据与特征管线、模型训练与评估、部署与服务化、监控与反馈闭环、迭代与治理。很多人把注意力全放在第三个环节但实际上前三者往往是瓶颈。举个具体的例子需要做一个垃圾评论识别系统。需求阶段要定的不是“用BERT还是用CNN”而是“误杀率允许多少”和“漏过率允许多少”。这两个指标直接决定了数据标注的倾向、模型阈值的选择、以及后期人审的工作量。数据工程师根据这两个指标去设计采集规则、清洗策略、标签体系。训练工程师拿到的应该是一个有明确分布说明、版本可回溯的数据集而不是一堆就躺在共享目录里的CSV。模型上线后监控系统要回答的也不只是“模型还活着吗”而是“模型在最近一周的预测置信度分布是否发生了变化”、“新增样本里有没有训练阶段没见过的新话术模式”。这一环如果缺失前面所有的努力都会在某个凌晨突然失效而你没有任何可用的日志去定位。整个链路串起来才是AI工程的全貌。2. 从零搭建一套靠谱的AI工程环境2.1 Python环境、依赖管理和容器化的口味差异AI项目几乎没有不依赖一堆第三方库的PyTorch、NumPy、pandas、scikit-learn再加上各类工具链。问题从来不是“安装一个库”而是“让所有人在任何机器上都能复现同一个环境”。我见过很多团队的环境管理是用了一个requirements.txt写死了几个大版本然后靠“在我机器上能跑”来混日子。直到某天别人clone代码装完依赖训练出来的指标少了两三个点排查了两天才发现是NumPy版本差异导致随机数序列变了。聊聊我现在的标配做法。Python版本管理用pyenv项目环境管理用venv团队统一用Docker固定可复现环境。关键点在于pyenv负责“装不同版本的Python”venv负责“隔离依赖”docker负责“镜像分发”。三者职责不同不要混用。如果项目里要用GPU训练Docker里还得注意NVIDIA容器工具包nvidia-container-toolkit的配置否则容器里看不到卡。具体的启动命令大概长这样docker build -t ai-train-env:latest . docker run --gpus all -v $(pwd):/workspace -it ai-train-env:latest bash第一次写Dockerfile时容易把镜像体积搞得巨大什么依赖都往里面塞。我的习惯是分阶段安装基础镜像固定CUDA版本然后单独安装训练依赖再单独装部署推理依赖。这样最终镜像体积能压缩30%到50%对后续分发很有利。2.2 依赖锁定与精确复现的细节requirements.txt写版本号时大多数人写的是“numpy1.20”但你不知道1.20到1.26之间某个版本会改变某些函数的默认行为。严谨做法是锁定精确版本最好再加哈希校验。pip可以用pip freeze生成当前环境的精确版本清单但freeze的输出不一定适合作为上层依赖。更可靠的做法是分两个文件一个requirements.in放顶层直接依赖一个requirements.txt放锁定后的完整依赖树。配合pip-tools或poetry这类工具做约束解析。Poetry的lock文件机制我个人比较喜欢它能同时维护项目依赖和开发依赖。需要说明的是Docker镜像本身也不是完全一劳永逸的。镜像构建过程中会从PyPI等源拉包如果没有把下载源替换成公司内部镜像源每次构建的时机不同拉到的包版本可能也不同。所以我会把构建依赖源的步骤写进Dockerfile同时在CI里固定基础镜像的digest避免“基础镜像悄悄变了”这种隐性复现问题。2.3 环境之外的项目骨架与配置管理环境搭好只是第一步代码组织同样影响工程效率。AI项目的代码结构我推荐按功能域划分- configs/ # yaml配置文件环境无关 - data/ # 数据脚本与数据说明 - features/ # 特征工程代码 - models/ # 模型定义与训练入口 - evaluations/ # 评估脚本 - deployments/ # 部署与推理服务 - tests/ # 单元测试与数据校验配置管理有个核心原则代码和环境分离配置和代码分离。环境相关的东西比如数据路径、API密钥永远不要写进代码里而是通过环境变量或单独的config文件注入。训练超参数习惯上放yaml文件里这样能方便做多组实验对比也能把每次实验的完整参数记录到日志中。另外要提一下随机种子。很多项目复现不齐未必是代码bug而是随机种子没有完全固定。PyTorch里需要同时设置torch.manual_seed、numpy.random.seed还要关掉cuDNN的确定性模式开关torch.backends.cudnn.deterministic True。但要注意开确定性模式会降低训练速度所以只在需要精确复现时才开。这个取舍工程上要心里有数。3. 数据工程AI工程里最容易被低估的环节3.1 数据版本管理比代码版本管理更麻烦代码可以用Git管理数据呢数据集动不动几十GB甚至TB不可能直接塞进Git仓库。但训练结果的可复现性恰恰又极度依赖“当时用的到底是哪一批数据”。现在通行的做法是用专门的数据版本管理工具DVC是其中典型。它不把真实数据存进Git而是记录数据文件的哈希值以及在远程存储上的位置。训练时DVC会根据配置还原到对应版本的数据文件。听起来很顺理成章但实际操作中有个坑DVC的远程存储需要在团队里提前配置好可能是对象存储也可能是NFS或NAS。如果远程存储的访问权限不一致或者网络带宽不一致队友拉数据时的体验会非常割裂。所以我在项目启动时第一件事就是把远程存储通道路通并且把“拉取数据的命令”写进项目README当作日常操作而不是一次性部署任务。3.2 数据清洗和基础质量校验的工程化很多人的清洗逻辑是一堆脚本跑完一次就不再维护。工程化的做法是把清洗阶段提炼成可复用的管道步骤每一步都带校验逻辑。我记得有一次接手一批用户行为日志清洗流程里有一行代码是“过滤掉时长小于1秒的样本”但没写为什么。结果后来业务方说短时长样本在某个频道里恰恰是重要信号。因为没有记录过滤规则的产生背景后续团队不敢动这行代码模型效果一直上不去直到追溯到源头才解决。所以不管清洗规则多简单都要在代码注释里写清楚“为什么过滤”“依据是什么”“阈值怎么定出来的”。数据质量校验可以靠Great Expectations这类工具来保持活文档它对数据集的列名、数据类型、缺失值比例、值域范围等做断言。训练前跑一遍线上数据异常时预警能提前很多。哪怕团队初期没有这类工具至少也要在读取数据后加assert断言把“数据不符合预期”暴露在训练前而不是训练出一个奇怪模型之后才反过来怀疑数据。3.3 数据漂移与线上分布对齐训练数据和线上真实数据分布不一致是AI工程里最隐蔽的坑。写训练代码时拿到的往往是历史样本线上服务时用户行为可能已经变了。如果只关注训练集上指标好看上线之后翻车是大概率事件。所以成熟的AI工程一定会做数据漂移检测。做法一般分两步统计层面用PSI群体稳定性指数或KL散度对比特征分布采样层面用“属性相似度”判断当前线上的样本是否落在训练分布的高密度区域。这些指标不是模型上线后才开始计算而是在数据管线里定期输出和模型的业务指标一起看板展示。我见过一个团队他们的模型线上A/B测试没通过但离线评测很好。后来排查发现原因是线上请求的特征填充率比训练时低了8个百分点某些特征大量为空。这就是典型的数据对齐问题和模型本身的算法能力无关。4. 模型训练与评估从“跑通”到“可控”4.1 训练脚本的正确姿态初学者的训练代码往往是把Notebook里的一段顺序执行搬成一个py文件变量名沿用data1、data2参数硬编码在头部。这能跑但只支持“一次性跑”。工程化训练脚本的关键在于以配置文件驱动以命令行覆盖以日志和实验记录为最终结果。我用Hydra或配置文件管理超参数训练脚本本身不包含任何固定数值。必要的时候命令行参数可以覆盖配置文件中的值方便做快速消融实验。训练循环里检查点保存也是门学问。不要每次epoch都保存完整模型那样磁盘很快就不够用了也不要只在最终epoch保存因为可能最后几步loss飙高。正确做法是监控验证集指标在指标最优时保存检查点同时保留最近N个检查点以便回滚。实际开发时我用PyTorch Lightning的ModelCheckpoint回调设置monitor参数监控验证集指标mode设为max或min。4.2 评估指标应该跟着业务走“准确率96%”这类指标在工程评审中几乎没有信息量。业务关心的是误杀率、召回率、覆盖率、时延成本和运营人工成本。模型评估必须回到需求阶段定的指标上。我建议团队的评估模板里至少包含三类指标核心性能指标准确率、精确率、召回率、F1等按业务分群分别看不要只看总平均。鲁棒性指标在不同时间段、不同用户群体、不同渠道上的表现差异。工程指标推理延迟、显存占用、单次推理成本。有一个点容易被忽略阈值的选择。很多库的默认预测阈值是0.5模型输出的概率分数达到0.5就判正。但业务场景往往要求不同的误杀和漏过权衡。这时候要做阈值扫描选择在业务损失函数意义下的最优阈值而不是简单接受默认值。Sklearn有precision_recall_curve可以辅助做这件事。我做过一个文本分类项目业务要求误杀率不超过1%一开始阈值设为0.5时误杀率是3%。通过扫描阈值找到0.82附近时误杀率降到0.8%但召回率从75%掉到了62%。这时不是简单地选阈值而是要和业务方沟通清楚可能的动作包括调整模型结构、增加特征、或者接受更低的召回。这就是评估指标服务于业务决策的真实过程。4.3 训练调试中的“玄学”与基本法loss不降、loss炸了、验证集指标和训练集差距过大……这些问题是每个AI工程师日常都会遇到的。我的排查顺序一般固定为先看数据再看实现最后才怀疑超参。先看数据是指训练集里是否存在标签错误、特征泄漏、异常分布。特征泄漏是最难查的典型例子是时序预测里把未来信息当特征用了。再看实现是指损失函数公式方向、梯度是否被正确传播、数据预处理是否和推理时一致、模型保存和加载时是否有状态丢失。最后才考虑超参比如学习率过大导致loss震荡、batch size过小导致梯度噪声大。有一个经验技巧值得分享先从一个小数据集上把模型跑过拟合。如果你在几十条样本上连过拟合都做不到那大概率代码哪里有bug。能过拟合说明模型有足够容量学习和记忆数据然后再逐步扩大数据量观察泛化表现。这条路径比直接上全量数据盲调高效得多。5. 部署与服务化模型从研发到生产的临门一脚5.1 模型服务化选型与适配训练完的模型是要给人用的无论是实时API还是批量离线计算都要走服务化。最简单的方案是用Flask/FastAPI包一个HTTP接口把模型加载进内存做推理。对原型验证来说够用但对生产环境还要考虑并发、超时、排队、抢锁、批处理等问题。对于工业级场景我一般按需求分三层选型轻量且依赖简单的模型直接用ONNX Runtime或者TensorRT做推理加速不必引入额外的推理框架。中等规模模型TensorFlow Serving或TorchServe能自动管理多模型版本和多worker。大模型或生成式模型还需要考虑KV cache、连续批处理等能力这时考虑vLLM、Triton这类推理服务框架更合适。选型时要评估一个细节模型输出的内容和中间过程是否需要注册到外部系统。很多推荐类或风控类的线上推理还需要保留每个请求的特征快照和决策日志。这些不是模型推理框架原生支持的需要服务层自治。5.2 推理性能优化与资源评估在线服务的优化核心是延迟和吞吐。延迟指单个请求从发出到收到响应的时间吞吐指单位时间能处理的请求数。两者往往有冲突通过动态批处理可以提升吞吐但单个请求会等积累更多样本一起推理延迟会变大。对于GPU推理我习惯先做一次显存占用量化估算。比如一个BERT-base模型参数量约110MFP32权重占440MB加上激活值、优化器状态和CUDA context实际峰值占用会远超这个数。如果不够就换FP16或者做模型量化。工程上要反复权衡成本和效果不要盲目追求用大模型。实际生产里请求排队打满显存的情况很常见。我会在服务入口加信号量或并发队列控制防止流量冲击时把服务打挂同时为每个模型部署配置“最大batch size”和“最大队列长度”两个参数两者都不能取太大。这几个参数是运维事故中救人的关键。5.3 线上监控与模型回退机制模型部署上线只是一个起点。监控要做的第一件事是存活探活第二件事是质量监控。存活探活容易理解周期性地发一个心跳请求确认服务还能响应。质量监控复杂很多需要区分数据漂移、概念漂移和业务指标异常。数据漂移线上输入特征的分布与训练集差异变大。概念漂移输入分布没变但输入到输出的映射关系发生了变化老模型不再适用。比如用户兴趣整体迁移同样特征对应的点击概率完全不同。监控方案会提前把阈值定好比如PSI在0.1以内正常0.1到0.25需要关注超过0.25就要触发告警。告警不是直接通知人就行了还要伴随一个预案到底是继续观察、切回上一个稳定版本、还是触发自动重训。如果没有预案告警只是把问题从“没人知道”变成“知道了但不知道怎么处理”。6. 常见问题排查与避坑经验速查6.1 典型问题排查对照症状可能原因排查思路训练loss不降学习率过大/过小数据标签噪声高特征没有归一化先在小数据上试过拟合检查loss曲线的下降趋势训练损失降验证损失升过拟合训练集数据分布不一致增加正则化、数据增强、检查验证集的构成推理延迟波动大动态批处理策略激进、网络带宽抖动、GPU共享压测定位瓶颈分别测试CPU与GPU耗时线上线下性能差异大特征线上填充率低、pipeline处理不一致diff特征值对比线上请求日志与训练数据模型偶发返回空结果输入长度超限、服务并发竞争、超时切分异常在服务层加超时与异常捕获记录完整请求与响应镜像构建时GPU驱动不匹配CUDA基础镜像和宿主机驱动不兼容用nvidia-smi查驱动对应的CUDA版本匹配基础镜像复现不出历史训练结果数千随机种子未固定、依赖版本漂移、数据版本错乱确认训练哈希、代码提交号和配置文件的完整性这个对照表是我多年项目运维的沉淀但真正上手排查时不要贪图快速靠经验定论。按“先数据、再代码、后超参”的顺序来大多数问题都能在半小时内定位到方向。6.2 工程效率相关的非技术坑AI工程里有一类坑和技术无关但破坏力极大——团队协作方式。代码评审时大家大概率会关注模型结构和指标却很少人review数据管道脚本、标签定义文档、配置变更记录。我后来养成一个习惯每次提PR时必须附上“数据影响说明”和“配置变更说明”。不需要长篇大论三五行讲清楚“加了什么过滤条件”“哪个阈值变了”“为什么变”就行。这几行说明救过我好多次让后续接手的人能快速理解改动的意图。另外实验记录也要工程化。不要只记录“改了个模型”“把学习率从1e-4调到3e-5”这种描述没有索引价值。理想的做法是每次实验自动记录git提交哈希、配置文件全文、数据版本、环境依赖快照、训练日志关键指标、模型产物路径。现在MLflow这类工具可以直接省掉这些工作最好从第一个实验就用起来不要等实验多了再补。6.3 从“项目能跑”到“系统可靠”的经验补全在AI项目里我学到的最朴素的经验是把不确定性显式化不要掩盖在代码里。数据漂移检测不是“锦上添花”而是“计时炸弹的拆除工具”随机种子的管理不是“仪式感”而是“可复现工程的底线”线上监控不是“运维部门的事”而是“模型质量的一部分”。从零搭建AI工程最大的难点不是任何单个技术而是如何让每个环节都保持“可解释、可重复、可调试”的状态。模型可以是复黑盒但工程链路不能是黑盒。结尾关于“从零开始”的最后一句话做AI工程越久我越觉得“from scratch”真正的含义不是从零发明轮子而是从零建立一套属于自己的判断标准。别人推荐的框架、模板和最佳实践都可以抄但遇到“这个模型的阈值到底定多少”“这周的漂移指标升了要不要干预”“这个数据质量问题是不是该阻塞版本发布”这类问题时唯有你自己对整条链路的理解才能给出负责任的答案。如果你正准备开始一个AI项目我的建议是先不要着急写模型花一个周末把环境、数据版本和配置管理搭好然后用一个最小模型把从训练到部署的完整流程走一遍。这个过程可能会很枯燥但它会帮你省掉后面无数个“为什么我的模型烂得莫名其妙”的夜晚。踩过几次坑之后你自然会发现所谓的AI工程不是某个炫酷的工具而是一种把不确定事物管理起来的习惯。