尧图精选

从零搭建AI工程体系:数据管道、模型训练与推理服务部署实战

🕒 发布时间:2026/10/1 4:49:57 📁 来源:尧图网络
1. 从零搭建AI工程体系为什么我劝你别一上来就调包ai-engineering-from-scratch这个标题第一次看到的时候我愣了一下。市面上讲AI的教程铺天盖地但绝大多数都是教你import torch然后跑个预训练模型或者调个API接口就完事。真正从零开始、把AI工程当作一门系统工程来拆解的内容少得可怜。我自己在这个坑里摸爬滚打了几年带过几个从零起步的团队也见过太多人卡在能跑通demo但上不了线的阶段。所以看到这个标题我特别有感触——它说的不是从零学AI算法而是从零搭建AI工程体系。这两个东西的差别比很多人想象的大得多。AI工程这个词核心不在AI而在工程。算法是研究员的事工程是把算法变成能稳定运行、能持续迭代、能扛住真实流量的一套系统。你需要的不是推导反向传播而是知道数据怎么流转、模型怎么版本管理、推理服务怎么部署、监控怎么做、出问题怎么回滚。这些东西调包是学不会的。这篇文章适合谁看如果你是刚转行想做AI工程的同学或者你已经会训模型但不知道怎么把它变成产品又或者你是个后端工程师想补齐AI这块的工程能力那接下来的内容应该对你有用。我会按照一个真实项目从零搭建的顺序把每个环节的思路、选型理由、实操细节和踩过的坑都摊开讲。不堆砌名词不搞玄学就是一套能落地的东西。2. 整体架构设计先想清楚数据怎么流再想模型怎么跑2.1 为什么架构要从数据流倒推很多人做AI项目第一反应是我要用什么模型。这个思路是反的。正确的顺序是先搞清楚数据从哪来、经过哪些处理、最终以什么形式喂给模型、模型的输出又流向哪里。把这条链路画清楚模型选型自然就出来了。我习惯把AI工程体系分成五层数据层、特征层、训练层、推理层、应用层。每一层之间有明确的接口和契约。这样做的好处是任何一层出问题你可以快速定位任何一层要替换只要接口不变其他层不用动。举个具体的例子。假设你要做一个商品评论的情感分析系统。数据层负责从各个渠道收集评论、去重、清洗特征层负责分词、向量化、构造训练样本训练层负责模型训练、评估、版本管理推理层负责把训练好的模型包装成服务应用层负责对接业务系统把情感分返回给调用方。这个分层看起来简单但真正落地的时候很多人会把特征处理和推理逻辑混在一起导致训练和推理不一致——这是AI工程里最经典的坑之一。训练的时候用了一套分词逻辑推理的时候用了另一套结果模型效果大打折扣。分层就是为了避免这种问题。2.2 技术选型的几个关键决策点从零搭建选型是最容易纠结的地方。我列几个核心决策点以及我自己的选择逻辑。编程语言Python几乎是默认选项因为AI生态最全。但如果你的推理服务对延迟要求极高可以考虑用C或者Rust重写推理部分Python只做训练和编排。我一般建议先用Python把整个链路跑通有性能瓶颈再针对性优化不要过早优化。深度学习框架PyTorch现在是主流动态图调试方便社区活跃。TensorFlow在工业部署上曾经有优势但这两年差距在缩小。我的建议是除非团队已经有TensorFlow的积累否则直接上PyTorch。模型服务框架这是很多人忽略的一环。你训练好的模型不能直接扔给业务方用需要一个服务框架来管理。常见的选择有TorchServe、Triton Inference Server、FastAPI自己写。小项目用FastAPI就够了灵活大项目或者多模型场景Triton更合适它支持动态批处理、多框架、GPU共享。数据版本管理这是从零搭建时最容易漏掉的。数据变了模型效果就变了但你如果不知道数据什么时候变的排查问题就是灾难。DVCData Version Control是个不错的选择它能把数据和代码版本关联起来。实验管理MLflow或者Weights Biases。记录每次实验的超参数、指标、模型文件。没有这个你做实验就是上次那个效果好的配置是啥来着。下面这张表是我总结的选型对照供参考环节小团队/快速验证中大型/生产环境框架PyTorchPyTorch ONNX服务FastAPITriton / TorchServe数据版本手动记录DVC / LakeFS实验管理表格记录MLflow / WB监控日志Prometheus Grafana2.3 目录结构从第一天就规范起来从零搭建最容易犯的错是文件乱放。今天写个train.py明天写个test.py后天数据文件扔在根目录。一个月后你自己都找不到东西。我推荐一个经过实战检验的目录结构project/ ├── configs/ # 配置文件 ├── data/ # 数据目录raw/processed ├── src/ │ ├── data/ # 数据处理代码 │ ├── features/ # 特征工程 │ ├── models/ # 模型定义 │ ├── training/ # 训练逻辑 │ ├── inference/ # 推理逻辑 │ └── utils/ # 工具函数 ├── experiments/ # 实验记录 ├── notebooks/ # 探索性分析 ├── tests/ # 测试 ├── docker/ # 容器化 └── scripts/ # 运维脚本这个结构的关键在于src下面按功能模块划分而不是按文件类型划分。很多人喜欢搞all_models.py、all_utils.py最后文件几千行改一处怕影响另一处。按功能分每个模块职责单一好维护。注意notebooks目录只用来做探索性分析不要在里面写生产代码。我见过太多项目最后notebook变成了核心逻辑没法测试、没法复用、没法版本管理。3. 数据管道搭建AI工程里最脏最累但最重要的活3.1 数据采集与清洗的实操要点数据是AI工程的根基。模型再牛数据烂结果就是垃圾。从零搭建数据管道这块我建议你花至少40%的时间。采集环节核心要考虑的是数据源稳定性和采集频率。如果是内部数据库直接写SQL定时拉取就行。如果是外部API要注意限流和重试机制。如果是爬虫注意合规性和反爬策略。我一般会写一个采集层把不同来源的数据统一成相同的格式打上来源标签和时间戳。清洗环节常见的问题包括重复数据、缺失值、异常值、格式不一致、编码问题。这里我分享几个实操技巧。去重不要只用完全匹配。比如两条评论这个产品很好用和这个产品很好用完全匹配去不掉但语义上重复。可以用SimHash或者MinHash做近似去重。缺失值不要无脑填充。先分析缺失模式是随机缺失还是系统性缺失。系统性缺失往往意味着数据采集有问题填了反而引入偏差。编码问题在国内项目里特别常见。GBK、UTF-8、GB18030混着来读文件的时候一定要显式指定编码并且加异常处理。我一般会写一个safe_read函数尝试多种编码记录哪种成功了。def safe_read(filepath): encodings [utf-8, gbk, gb18030, latin-1] for enc in encodings: try: with open(filepath, r, encodingenc) as f: return f.read(), enc except UnicodeDecodeError: continue raise ValueError(f无法解码文件: {filepath})这个函数看起来简单但能帮你省掉大量排查编码问题的时间。3.2 特征工程训练和推理必须用同一套逻辑特征工程是AI工程里最容易出不一致问题的地方。训练的时候你用pandas做了一堆处理推理的时候线上服务没有pandas或者版本不一样结果就不一致。我的做法是把特征处理逻辑封装成一个独立的模块训练和推理都调用它。这个模块的输入是原始数据输出是模型需要的特征。它不依赖任何训练特有的东西比如全局统计量。但有些特征确实需要全局统计量比如归一化的均值方差、TF-IDF的词汇表。这些叫有状态特征。处理方式是训练时计算并保存这些状态推理时加载保存的状态。保存的格式可以是JSON、pickle或者专门的feature store。class FeatureProcessor: def __init__(self): self.mean None self.std None self.vocab None def fit(self, data): self.mean data.mean() self.std data.std() # 构建词汇表等 def transform(self, data): return (data - self.mean) / self.std def save(self, path): # 保存状态 pass def load(self, path): # 加载状态 pass这个模式叫fit-transformsklearn里很常见但很多人自己写代码的时候就忘了。记住任何在训练集上计算的统计量都必须保存下来供推理使用。3.3 数据版本管理与可复现性数据版本管理是很多从零搭建的项目忽略的。你改了数据清洗逻辑重新跑了一遍模型效果变了。但你不知道是逻辑改对了还是数据本身变了。没有版本管理这个问题无解。DVC的思路是数据文件本身不纳入git而是用一个小的元文件记录数据的哈希值。数据存在本地或者对象存储里。这样git里只有代码和元文件数据可以很大。# 初始化 dvc init # 添加数据 dvc add data/raw/reviews.csv # 这会生成 reviews.csv.dvc 文件纳入git git add data/raw/reviews.csv.dvc data/raw/.gitignore git commit -m add raw data每次数据变化dvc add会更新哈希值你就能在git历史里看到数据什么时候变的。配合dvc checkout可以切换到任意历史版本的数据。提示DVC适合中小规模数据。如果数据量到TB级考虑LakeFS或者Delta Lake。但核心思想是一样的数据和代码分开管理用版本号关联。4. 模型训练与实验管理别让实验变成玄学4.1 训练脚本的工程化改造从零搭建训练脚本最容易写成一次性代码。跑完就扔下次要用再改改。这样做的后果是你永远不知道哪个版本效果好也没法复现。我的做法是训练脚本必须支持配置驱动。所有超参数、数据路径、模型结构参数都从配置文件读不硬编码。配置文件用YAML或者JSON方便版本管理和对比。# configs/train_config.yaml data: train_path: data/processed/train.csv val_path: data/processed/val.csv batch_size: 32 model: name: bert-base-chinese num_labels: 2 dropout: 0.1 training: epochs: 5 lr: 2e-5 warmup_steps: 100 seed: 42然后训练脚本用argparse或者hydra加载配置。hydra的好处是支持配置组合和命令行覆盖做实验特别方便。import hydra from omegaconf import DictConfig hydra.main(config_pathconfigs, config_nametrain_config) def train(cfg: DictConfig): # 所有参数从cfg读 set_seed(cfg.training.seed) model build_model(cfg.model) # ...这样做的好处是每次实验的配置可以保存下来和模型文件放在一起。复现的时候直接加载配置就行。4.2 实验追踪把每次尝试都记录下来实验追踪的核心是回答三个问题我试过什么、效果如何、哪个最好。没有实验追踪你做实验就是碰运气。MLflow是我常用的工具。它记录每次运行的参数、指标、模型文件还提供一个UI界面方便对比。import mlflow mlflow.set_experiment(sentiment-analysis) with mlflow.start_run(): mlflow.log_params({ lr: 2e-5, batch_size: 32, epochs: 5 }) for epoch in range(epochs): train_loss train_one_epoch() val_acc evaluate() mlflow.log_metrics({ train_loss: train_loss, val_acc: val_acc }, stepepoch) mlflow.pytorch.log_model(model, model)跑完实验打开MLflow UI所有实验一目了然。哪个学习率效果好哪个batch size更稳直接对比。我还会在实验记录里加一个notes字段记录这次实验的动机和观察。比如尝试降低学习率发现收敛更稳但速度慢。这些文字记录比数字更有价值因为数字只告诉你结果文字告诉你为什么。4.3 模型评估与选择别只看准确率模型评估这块很多人只看准确率。但准确率在类别不平衡的时候会骗人。比如99%的样本是正类你全预测正类也有99%准确率但模型其实啥也没学到。我一般会看这几个指标精确率、召回率、F1、AUC。具体看哪个取决于业务场景。如果是垃圾邮件过滤你更关心精确率因为误杀正常邮件代价高。如果是疾病筛查你更关心召回率因为漏诊代价高。除了指标还要看混淆矩阵和错误样本。混淆矩阵告诉你模型在哪些类别上容易混。错误样本告诉你模型具体错在哪有时候能发现数据标注的问题。from sklearn.metrics import classification_report, confusion_matrix print(classification_report(y_true, y_pred)) print(confusion_matrix(y_true, y_pred))我还会做一个错误分析把预测错的样本拿出来看。经常发现有些样本本身标注就是错的或者有歧义。这些样本清理掉模型效果能提升不少。注意测试集一定要留到最后用不要用来调参。调参用验证集。测试集只用一次用来报告最终效果。我见过太多人反复用测试集调参最后报告的效果虚高上线就崩。5. 推理服务部署从模型文件到线上服务5.1 推理服务的核心需求训练好的模型是个文件业务方没法直接用。你需要把它包装成一个服务接收请求、返回结果。这个服务要满足几个核心需求低延迟、高并发、稳定、可监控。低延迟意味着单次推理要快。影响因素包括模型大小、批处理策略、硬件。高并发意味着同时处理多个请求。这需要服务框架支持异步或者多线程。稳定意味着不能动不动就崩。需要异常处理、限流、熔断。可监控意味着出问题能快速定位。需要日志、指标、链路追踪。5.2 FastAPI快速搭建推理服务小项目或者快速验证FastAPI是最快的方式。它异步支持好代码简洁自动生成API文档。from fastapi import FastAPI from pydantic import BaseModel import torch app FastAPI() class Request(BaseModel): text: str class Response(BaseModel): label: str score: float model None tokenizer None app.on_event(startup) def load_model(): global model, tokenizer model torch.load(model.pt) model.eval() tokenizer load_tokenizer() app.post(/predict, response_modelResponse) async def predict(req: Request): inputs tokenizer(req.text, return_tensorspt) with torch.no_grad(): outputs model(**inputs) probs torch.softmax(outputs.logits, dim-1) score, pred torch.max(probs, dim-1) return Response( labelid2label[pred.item()], scorescore.item() )这个服务跑起来访问/docs就有自动生成的API文档。但要注意FastAPI默认是单进程的生产环境要用gunicorn或者uvicorn多worker启动。gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000-w 4是4个worker进程根据CPU核数调整。每个worker会加载一份模型所以内存要够。5.3 动态批处理提升吞吐的关键单条推理效率低因为GPU利用率上不去。动态批处理是把短时间内到达的多个请求合并成一个batch一起推理然后拆分结果返回。这样能大幅提升吞吐。Triton Inference Server原生支持动态批处理。配置里设置max_batch_size和preferred_batch_size它会自动攒批。# config.pbtxt name: sentiment_model platform: pytorch_libtorch max_batch_size: 32 dynamic_batching { preferred_batch_size: [8, 16, 32] max_queue_delay_microseconds: 5000 }max_queue_delay_microseconds是最大等待时间。设太小攒不到批设太大延迟高。5000微秒5毫秒是个经验值根据你的延迟要求调整。如果不用Triton自己实现动态批处理也可以但复杂度不低。核心是一个队列加一个后台线程线程从队列取请求攒批推理完把结果放回各自的future。5.4 模型版本管理与灰度发布模型上线不是一次性的。你会不断迭代新模型要替换旧模型。直接替换风险大万一新模型有问题全量用户受影响。所以需要灰度发布。灰度发布的思路是新模型先接一小部分流量观察指标。没问题再逐步扩大比例直到全量。出问题立即回滚。实现方式有几种。简单的是在服务层做根据请求ID哈希决定用哪个模型。复杂的是用服务网格比如Istio做流量切分。import hashlib def select_model(request_id): hash_val int(hashlib.md5(request_id.encode()).hexdigest(), 16) if hash_val % 100 10: # 10%流量走新模型 return new_model return old_model模型文件要版本化存储比如model_v1.pt、model_v2.pt。服务启动时加载指定版本回滚就是改配置重启。提示灰度发布一定要有自动回滚机制。监控新模型的错误率、延迟超过阈值自动切回旧模型。人工发现往往来不及。6. 监控与运维上线只是开始6.1 监控什么从系统指标到模型指标监控分两个层面系统层面和模型层面。系统层面看CPU、内存、GPU利用率、请求延迟、错误率、QPS。这些用Prometheus采集Grafana展示。FastAPI可以集成prometheus-fastapi-instrumentator自动暴露指标。from prometheus_fastapi_instrumentator import Instrumentator Instrumentator().instrument(app).expose(app)模型层面看预测分布、置信度分布、输入数据分布。这些指标能帮你发现模型退化。比如预测分布突然偏向某一类可能是输入数据变了或者模型有问题。数据漂移是模型退化的常见原因。训练时的数据分布和线上数据分布不一致模型效果就会下降。监控输入特征的均值、方差、分位数和训练时对比能提前发现漂移。6.2 日志与链路追踪日志要结构化方便检索。用JSON格式包含时间戳、请求ID、输入、输出、耗时、模型版本。import logging import json logger logging.getLogger(__name__) def log_prediction(request_id, text, label, score, latency, model_version): logger.info(json.dumps({ request_id: request_id, text: text, label: label, score: score, latency_ms: latency, model_version: model_version, timestamp: time.time() }))链路追踪用OpenTelemetry能追踪一个请求经过的所有服务方便定位瓶颈。6.3 常见线上问题与排查思路线上问题排查我总结了一个速查表现象可能原因排查方向延迟突然升高流量突增、模型加载慢、GPU争用看QPS、GPU利用率、批处理队列长度错误率升高输入格式异常、模型OOM、依赖服务挂看错误日志、内存使用、依赖健康检查预测结果异常数据漂移、模型版本错误、特征处理bug对比输入分布、检查模型版本、验证特征逻辑服务无响应死锁、线程池满、GC停顿看线程栈、GC日志、连接数排查的核心思路是先定位层面再深入细节。是系统问题还是模型问题是输入问题还是输出问题是偶发还是必现把范围缩小再逐个排除。我踩过的一个坑是模型服务跑了一段时间后延迟逐渐升高。查了半天发现是日志文件没轮转磁盘写满了导致写日志阻塞。后来加了logrotate问题解决。这种问题监控系统指标就能提前发现所以监控一定要做全。7. 从零搭建的几条经验之谈7.1 先跑通再优化别过度设计从零搭建最大的诱惑是过度设计。一开始就想着微服务、Kubernetes、特征平台结果三个月过去了模型还没跑起来。我的建议是先用最简单的方式把整个链路跑通。一个Python脚本做数据处理一个脚本做训练一个FastAPI做推理。跑通了再根据瓶颈逐步优化。哪里慢优化哪里哪里不稳加固哪里。7.2 测试是AI工程的保险绳AI项目测试比传统软件难因为输出不是确定的。但正因为难才更要测。我一般会写这几类测试数据测试检查数据格式、范围、分布是否符合预期特征测试给定输入特征输出是否确定模型测试模型加载、推理是否正常输出形状是否正确服务测试API接口是否正常异常输入是否被正确处理def test_feature_processor(): processor FeatureProcessor() processor.fit(train_data) result processor.transform(test_data) assert result.shape expected_shape assert not np.isnan(result).any()这些测试跑起来改代码的时候心里有底。7.3 文档和注释给三个月后的自己看AI工程项目人员流动快今天你写的代码三个月后可能是别人维护也可能是你自己维护但忘了细节。所以文档和注释特别重要。我要求每个模块有一个README说明这个模块干什么、输入输出是什么、依赖什么。每个函数有docstring说明参数、返回值、异常。配置文件有注释说明每个参数的含义和推荐值。这些看起来是小事但能省掉大量沟通成本。我见过太多项目因为没文档新人上手要花几周老人离职后代码没人敢动。7.4 持续学习AI工程变化快AI工程这个领域工具和最佳实践变化很快。今天流行的框架明年可能就过时了。所以保持学习很重要。我的习惯是每个月花点时间看看新出的工具和论文但不盲目追新。新工具先在小项目上试验证了再引入生产。生产环境稳定优先不为了用新技术而用新技术。最后分享一个我自己的体会从零搭建AI工程体系最难的不是技术而是坚持工程规范。技术问题都有解但规范问题靠的是自律。数据要版本管理、实验要记录、代码要测试、服务要监控这些事做一次不难难的是每次都做。但正是这些坚持决定了你的项目是玩具还是产品。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →