尧图精选

从零构建AI工程链路:数据管道、模型训练到部署实战指南

🕒 发布时间:2026/10/1 6:06:30 📁 来源:尧图网络
1. 先说清楚AI工程不是“调库”而是整套交付能力1.1 我这个项目到底做了什么我给自己定了一个任务从零开始独立构建一条完整的AI工程链路而不是再像以前那样只跑通一个Jupyter Notebook就宣布“模型训练完了”。这个项目我命名为“ai-engineering-from-scratch”核心目标很直接把一份原始数据变成线上可用、可维护、可复现的AI服务。所以我没有把精力放在“造一个新模型”上而是把一整条管线拆成了六段数据获取与清洗、数据管道、模型训练、评估、服务化部署、上线后的监控。每一段都需要写真实可运行的代码不允许出现“这里手动调一下”的临时操作。做完之后我才敢说自己确实知道一个AI项目从0到1到底要跨多少坎。1.2 为什么坚持“从零开始”这条路有些人会觉得都已经有那么多现成框架了Bert下载即用Diffusion模型现成成堆为什么还要自己把环境、训练循环、部署脚本一个个搭起来这不是重复造轮子吗我的回答是如果只是把模型下载下来跑个推理那你学到的是“怎么使用工具”而不是“怎么解决工程问题”。我踩过很典型的坑。早年我用高层API训练模型训练过程一报错我只知道在搜索引擎里复制错误信息。后来我决定把训练循环自己写一遍才发现那些错误背后的逻辑其实非常简单梯度什么时候清零、状态字典怎么保存、学习率调度器每一步都在干什么。当你亲手写过一遍之后工具的封装对你来说就不再是黑盒。这就是我理解的“from scratch”不是从数学公式手写反向传播而是把工程链路上该自己掌握的部分全部老老实实掌握一遍。这个项目还帮助我理解了一个更重要的观点AI系统的难点往往不在模型而在模型周围的“管道”。数据漂移、标注噪声、推理延迟、服务重启后状态丢失这些问题每一个都比调优0.5个点准确率更影响线上效果。只有亲手搭建一遍才会对这些隐性成本有体感。1.3 适合谁看需要什么基础我把这套项目总结出来最希望给两类人参考。第一类是刚入门AI、想往工程方向转的开发者你已经有Python基础跑过一些教程但不确定下一步该学什么第二类是科研人员或算法工程师你平时工作主要在研究模型结构但突然被要求把模型部署上线需要快速补齐工程侧知识。基础要求不高会写Python会装conda环境对神经网络有最基础的概念。如果你连MNIST分类都没跑过建议先找一个基础教程跑通一次再回来看这篇内容。如果你已经有两年以上部署经验这篇内容是帮你做查漏补缺的很多坑你可能也踩过。2. 技术栈与关键选型从零开始不是一切重造2.1 基础运行环境Python、conda、CUDA的版本陷阱第一步要解决的问题是“环境”。我不建议直接在一台机器的全局Python环境里装包因为AI项目对版本极度敏感。我使用的是conda并专门创建了一个独立环境conda create -n ai-eng python3.10 conda activate ai-engPython版本我选3.10而不是最新的3.12原因是PyTorch和很多第三方库对最新Python的支持通常有滞后选成熟版本会少踩很多坑。装PyTorch之前先用nvidia-smi查看显卡驱动支持的CUDA版本再对应安装nvidia-smi这里有一个常见的认知误区nvidia-smi显示的CUDA版本是驱动支持的“最高版本”不代表你必须在机器里单独安装那个版本的CUDA。PyTorch安装包自带了它需要的CUDA运行库你只需要保证驱动足够新即可。所以我实际执行的是这样的安装命令pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121其中cu121表示CUDA 12.1。这个数字需要不大于nvidia-smi里显示的驱动版本对应的CUDA版本否则可能遇到no kernel image之类的错误。这类问题极其常见我后面会在踩坑实录里详细展开。2.2 深度学习框架为什么默认选PyTorch框架选型上我的结论很明确如果从零开始首选PyTorch。这不是说TensorFlow不好而是从工程生态和调试体验来讲PyTorch更适合个人项目和中小团队快速迭代。我整理了一个简单对比维度PyTorchTensorFlow / Keras调试体验动态图可以随时print张量、打断点静态图年代调试麻烦TF2改善但仍偏重模型生态HuggingFace、Timm等主流模型库优先支持Transformers等库有支持但节奏稍慢部署路径TorchScript、ONNX、TensorRT链路完整TF Serving、TFLite在服务端同样成熟上手曲线命令式编程思维更像写普通Python高层Keras简单但深入后概念偏多表格只是参考真正决定性的因素是你团队已有的技术栈。如果老系统都是TensorFlow没必要为了“新潮”重写。但如果是从零开始PyTorch会省掉很多“这个库怎么还不支持我的模型结构”的烦恼。2.3 工程侧组件我只选四件套训练模型只是工程链中的一环。我给我的项目配了四件基础组件Git做代码版本管理Docker做环境打包MLflow做实验追踪FastAPI做模型服务。没有上Kubernetes也没有上Airflow因为这些重型组件对于单机、中小数据量的项目过度了等业务规模真正扩大时再演进也不迟。文件结构我采用这样的组织方式ai-engineering-from-scratch/ ├── data/ ├── configs/ │ └── experiment.yaml ├── src/ │ ├── dataset.py │ ├── model.py │ ├── train.py │ ├── evaluate.py │ └── serve.py ├── scripts/ │ └── run_all.sh ├── Dockerfile ├── Makefile └── requirements.txt我特意把data目录放在项目根目录而不是随意散落这样在做数据版本管理时会更方便。整个项目用Git管理每个实验跑完都会在Git提交信息里记录“哪个配置、哪个指标”后期复盘时极大降低精神负担。MLflow是我比较推荐的实验追踪工具它可以自动记录每个训练任务的参数、指标和产物。用法也很简单只需要在训练脚本里加几行import mlflow mlflow.set_experiment(ai-engineering-from-scratch) with mlflow.start_run(): mlflow.log_params({lr: 1e-3, batch_size: 32}) mlflow.log_metrics({val_acc: 0.86}) mlflow.pytorch.log_model(model, model)有了这套东西你就再也不用像以前那样“用Excel记录实验结果”了。所有对比数据都在同一个地方非常直观。3. 实操路径拆解从数据到模型上线要过哪些关3.1 数据管道80%的工作量在这里很多初学者把大量时间花在调整网络结构上但我做这个项目的最大感受是数据管道才是真正吃时间的地方。你的模型再好喂进去的数据乱七八糟照样得不到可靠结果。我做的第一件事是把数据处理逻辑从Notebook里抽出来写成一个可以复用的dataset.py。不要小看这一步Notebook里的数据预处理代码是“一次性输出”只能手工执行而工程化代码需要支持反复运行、断点续跑、统一预处理逻辑被训练和推理两端共用。数据切分时我特别强调“先整体洗牌再切分”而且要做成函数而不是手动操作import pandas as pd from sklearn.model_selection import train_test_split df pd.read_parquet(data/raw.parquet) train_df, temp_df train_test_split(df, test_size0.3, random_state42) val_df, test_df train_test_split(temp_df, test_size0.5, random_state42)但这里有一个新手最容易踩的坑随机切分并不总是正确的。如果数据带有时间序列属性比如商品销量、用户行为日志随机切分会把未来数据泄漏进训练集导致模型在验证时看起来很好、线上却崩掉。正确的做法是按时序切分train_df df[df[timestamp] train_cutoff] val_df df[(df[timestamp] train_cutoff) (df[timestamp] val_cutoff)] test_df df[df[timestamp] val_cutoff]我对数据增强的处理原则是图像数据用随机裁剪、翻转、颜色抖动文本数据用回译、同义词替换要非常谨慎防止改变语义。具体用哪种增强取决于你的任务和数据模态灵活处理。3.2 训练脚本从玩具代码到可复现实验写训练脚本的时候我最看重的是“可复现性”。一个训练脚本跑出来的结果换了台机器、换了个时间再跑最好能拿到一致的结果。所以我在脚本开头固定了所有随机种子import random import numpy as np import torch def set_seed(seed: int 42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False注意cudnn.deterministic True会降低部分算子的速度但换来的是可复现性值得。训练循环我坚持自己写而不是直接套Trainer。好处是你能确切知道每一步发生了什么。我整理了一个最简可运行的训练核心model MyModel().to(device) optimizer torch.optim.AdamW(model.parameters(), lrcfg.learning_rate) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxcfg.epochs) grad_accum_steps 4 # 模拟更大batch size for epoch in range(cfg.epochs): model.train() for step, batch in enumerate(train_loader): inputs, labels batch[0].to(device), batch[1].to(device) outputs model(inputs) loss criterion(outputs, labels) loss loss / grad_accum_steps loss.backward() if (step 1) % grad_accum_steps 0: optimizer.step() optimizer.zero_grad()这里我做了一个容易被忽略但很重要的设计把总损失除以grad_accum_steps再反向传播。做梯度累积时如果不这么做每次反向传播都是完整的损失梯度累加后会变成原来好几倍的等效学习率导致优化过程不稳定。混合精度训练我也加了进来用PyTorch自带的自动混合精度就能让显存占用和训练速度都得到明显改善with torch.autocast(device_typecuda, dtypetorch.float16): outputs model(inputs) loss criterion(outputs, labels)再配合torch.cuda.amp.GradScaler做梯度缩放这套组合拳下来即使模型结构不变显存占用可以降低接近一半训练速度也能提升不少。我后来还尝试了bf16在部分显卡上效果更好不过对硬件有一定要求需要提前确认。3.3 评估到底看什么指标别只盯着准确率模型训练完很多人习惯只看accuracy。但对于很多业务场景准确率是一个非常坑的指标。比如一个二分类问题正样本只占5%模型只要全部预测为负类准确率就能到95%可这个模型毫无业务价值。我习惯至少看这几个东西精确率、召回率、F1、混淆矩阵、PR曲线。如果你的任务是排序类还要加上RecallK、NDCG等指标。看的时候不能只取一个数要把每个类别的指标都打出来from sklearn.metrics import classification_report, confusion_matrix y_pred predict(model, val_loader) print(classification_report(y_true, y_pred, digits4)) print(confusion_matrix(y_true, y_pred))更关键的一点是测试集只能用来做最终评估绝不能用来调参。我在这个项目里严格区分了验证集和测试集所有超参数调整、早停判断都基于验证集测试集只在全部流程确定后才跑一次作为最终效果报告。如果你做的是业务类AI项目我建议和业务方一起把“阈值”这件事聊清楚。模型输出的是0到1的概率到底取0.5还是0.3取决于你把“误报”和“漏报”哪个看得更重。比如做风险告警宁可多报一些让人工去过滤也不希望漏掉真实风险那阈值就往下调。这种决策不能只由算法工程师拍脑袋。3.4 部署与服务化FastAPI把模型包成接口模型训练结束真正的工程挑战才开始。我在这个项目里选择用FastAPI做推理服务因为它自带OpenAPI文档、异步支持代码量极小个人项目和中小团队用它非常合适。最小可运行的推理服务大概是这样的from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class PredictRequest(BaseModel): text: str app.post(/predict) def predict(req: PredictRequest): features preprocess(req.text) with torch.no_grad(): pred model(features) return {label: pred.argmax().item()}别看这段代码简单两个细节必须养成习惯第一推理阶段一定要包在torch.no_grad()里否则会多出大量计算图的显存占用第二模型要设置为model.eval()这会改变Dropout和BatchNorm的行为防止推理结果不稳定。上线前我给这个服务加了三样东西健康检查接口/healthz统一异常处理以及用gunicorn配合uvicorn的worker启动服务而不是直接用uvicorn单进程裸奔。单进程只能吃一个CPU核心一旦线上并发稍微上去就直接超时。多worker后吞吐量提升非常明显。Docker打包的时候我碰到的最大问题是镜像体积。早期镜像动不动就是5GB后来我把依赖层和代码层分开写利用构建缓存再选用轻量的python:3.10-slim作为基础镜像最终镜像缩小到1.5GB左右。针对GPU部署用nvidia/cuda:12.1-runtime-ubuntu22.04做基础镜像会更合适这里根据你的运行环境做取舍。4. 常见问题与排查技巧实录4.1 环境依赖的“地狱”时刻CUDA版本与wheel不匹配我最早跑这个项目时遇到过一个非常经典的错误模型一上GPU就崩溃报错是RuntimeError: CUDA error: no kernel image is available for execution on the device。这句话翻译过来就是当前PyTorch安装的CUDA内核与你的显卡驱动不兼容。排查思路很简单先看驱动支持什么nvidia-smi右上角有一个“CUDA Version”比如显示12.2这表示驱动支持最高12.2。那么你安装的PyTorch wheel如果是cu121或cu118都可以正常工作如果你装了cu123之类的更高版本就可能出现上面的问题。解决的最终命令是pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121如果你的机器压根没有NVIDIA显卡那就要走CPU方案pip install torch torchvision默认会装CPU版本也能跑通全流程只是训练速度慢很多。做原型验证还是够的。4.2 显存溢出不是只能调小batch sizeOOMOut of Memory是最常见的训练错误。很多人的第一反应是把batch size调小但有时候问题不在batch size本身而在于代码写得不够高效。我见到过的典型情况有几个输入序列里有超长样本导致batch内部padding非常严重实际计算量暴涨验证阶段忘了包torch.no_grad()同样会占用显存还有模型本身太大显存放不下。我的排查顺序是这样的先用watch -n 1 nvidia-smi观察显存变化看是哪一步把显存推爆的。如果显存是一点一点涨上去直到崩掉优先怀疑数据加载或缓存机制有问题如果是训练刚开始几十步直接爆掉说明模型层或单batch计算量太大了。然后我会尝试三件事开混合精度、开梯度检查点torch.utils.checkpoint用计算换显存、调整训练循环里没有必要的临时变量。这里有一个很容易忽略的点在循环里写loss loss.item()之前loss还挂在计算图里如果把它保留在一个Python list里方便后头画loss曲线那么整个计算图都无法释放显存会持续堆积。补救办法是loss_value loss.item() # 只提取标量不保留计算图这条习惯帮我排掉了无数隐性OOM。4.3 离线指标很好看线上效果却很烂这是我认为AI工程中最“玄学”但也最可解释的问题。模型在测试集上准确率90%上线之后用户反馈一团糟问题几乎都出在数据分布不一致上。最常见的原因有三个。第一验证集是随机切分的但线上数据是带时间属性的分布已经变了第二线上预处理和训练预处理不一致比如线上少做了一个标准化步骤第三验证集里的样本本身就挑选过不能代表真实业务分布。针对第一点我的做法是把线上真实请求记录下来定期抽一批作为“线上回流集”放入评估集。不需要人工标注很多几百条高质量样本就能暴露出很多问题。针对第二点我的做法是训练和服务共用同一个preprocess.py保证输入处理路径唯一而不是训练侧一套代码、服务端再复制一份自己改一改。我印象最深的一次排错模型在离线测试集上AUC很高上线后效果很差。后来发现服务端把文本特征做了某一种截断而训练时没有导致模型输入分布完全不同。把预处理统一后问题立刻消失模型一点没改。4.4 线上推理延迟太高批处理是性价比最高的优化点模型部署上线后你很快会发现单条请求的推理时间很难压下来因为模型前向传播的步骤是固定的单次调用开销非常大。可是如果把多条请求拼成一个batch一起算单位时间处理量能提升数倍。所以我给服务加了一个简单批处理队列请求先进入队列后端线程积累一定数量后合并成一个大batch交给模型。吞吐量提升非常明显而且工程改动没有想象中复杂核心就是“累积一段时间或累积到N条请求就执行一次”。另一个优化是模型导出。如果对推理延迟有更高要求可以把PyTorch模型转成ONNX格式再通过ONNX Runtime加载。纯PyTorch的话可以通过torch.jit.trace先做一层脚本化也能有一定加速。我的实测经验是ONNX导出后推理延迟通常能降低20%到40%取决于具体模型结构。对大模型来说收益更明显。5. 这套项目往后还能怎么扩展我的一些个人建议这个项目做完我最大的体会是AI工程师的核心竞争力不是会训练多先进的模型而是能把模型放进一个稳定的系统里让它持续产出价值。模型再强如果数据管道一跑就断、服务一重启就崩、实验结果无法复现业务方最终还是会失去耐心。我给自己的项目定了一个“扩展清单”如果你也想继续深入可以参考数据层面可以引入DVC做数据版本管理特征工程复杂的话可以引入特征存储任务数量和调度频率上来之后再学习Airflow也不迟。这些都是按需演进不是一上来就铺。最后分享一个我实际用起来非常顺手的小技巧把整套流程封装进Makefile或一个脚本里一键执行。我在scripts/run_all.sh里把“拉取数据、运行预处理、训练、评估、构建镜像、启动服务”全部串在了一起每次更新代码后只需要跑一次脚本就能完整检验整个链路有没有问题。这个习惯可以自然帮你建立工程化思维也会让你在团队里被同事觉得“靠谱”。如果你准备复现这套路径我建议不要一开始就追求大模型大任务找一个中等规模的数据集把端到端链路完整跑通再回头优化其中任何一环。这个顺序是我认为最划算的成长路线。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →