ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零搭建AI工程体系:从环境准备到推理服务部署的完整实操指南

从零搭建AI工程体系:从环境准备到推理服务部署的完整实操指南 1. 从零搭建AI工程体系为什么我劝你别一上来就啃论文ai-engineering-from-scratch这个标题第一次看到的时候我以为是又一个教你怎么调包的教程合集。点进去翻了翻发现它想做的事情比调包大得多——它试图回答一个很具体的问题一个没有机器学习背景的普通开发者怎么一步步把AI能力真正落到工程项目里而不是停留在跑通一个demo的层面。我自己带过几个从传统后端转AI方向的同学也见过太多人卡在同一个地方Python会写API会调但一旦要自己搭一套能用的AI工程链路就不知道从哪下手了。不是模型不会训而是整个工程化的思路没建立起来。数据怎么管、推理怎么部署、效果怎么评估、线上怎么监控这些东西没有任何一门课会从头到尾串起来讲。这个项目标题里的from scratch我理解有两层意思。一层是从零开始学另一层是从底层开始搭。这两件事其实是同一件事的两面——你不从底层搭一遍就永远不知道那些封装好的工具到底帮你做了什么出了问题也不知道去哪找。这篇文章我打算按我自己实际走过的路径来拆从环境准备到最小可用系统再到效果评估和上线部署把每个环节的关键决策和踩过的坑都摊开讲。适合两类人看一类是刚转AI方向的工程师另一类是已经在做AI但总觉得根基不牢、想系统补一遍的开发者。不需要你有深度学习基础但需要你能写Python、懂基本的命令行操作。2. 整体设计思路为什么我不建议从模型训练开始2.1 先搞清楚AI工程和AI研究的边界很多人一提到AI工程第一反应就是训模型。这个认知偏差是最大的坑。AI研究和AI工程是两个完全不同的工种前者关心的是能不能做出一个新东西后者关心的是这个东西能不能稳定、低成本、可维护地跑在线上。我见过太多团队花三个月训了一个模型效果比开源模型好两个点然后发现部署成本是开源方案的十倍推理延迟高到业务方无法接受最后项目黄了。问题不出在模型上出在工程判断上。所以from scratch的第一步不是搭训练环境而是搞清楚你的工程边界在哪。具体来说你需要先回答几个问题你的任务是什么类型分类、生成、检索、还是多模态你的数据量级和更新频率是多少你的推理延迟要求是毫秒级还是秒级你的预算是自建还是调API这几个问题的答案会直接决定你后面所有的技术选型。比如延迟要求毫秒级那基本只能考虑小模型加量化加本地部署如果延迟要求秒级且预算有限调API可能是更理性的选择。2.2 最小可用系统的设计原则我自己的习惯是任何AI项目都先搭一个最小可用系统英文叫Minimum Viable System。注意不是Minimum Viable Product是System。区别在于MVP关注的是功能能不能跑通MVS关注的是整条链路能不能闭环。一个AI工程的最小可用系统应该包含四个部分数据入口、推理服务、效果评估、日志监控。这四个部分缺一个你的系统就是不可维护的。数据入口负责把原始数据变成模型能吃的格式推理服务负责把输入变成输出效果评估负责告诉你输出好不好日志监控负责在出问题的时候让你知道哪里出了问题。很多人只做中间那一步前后都不管结果就是系统上线即失控。我建议的顺序是先把推理服务搭起来用最简单的方案跑通然后加日志确保每一步都有记录再加评估用一个小规模的人工标注集做基准最后才优化数据入口。这个顺序的好处是你每一步都有反馈不会闷头搭了半个月发现方向错了。2.3 技术选型的三个核心考量技术选型这块我踩过的坑最多总结下来就三个考量维度可控性、成本、迭代速度。可控性指的是你对整个链路的掌控程度。用开源框架自己搭可控性最高但工作量大用云服务可控性低但省事。我的建议是核心链路自己搭边缘能力用云服务。比如推理服务自己搭但数据标注可以用现成的工具。成本这块要算总账不能只看显性成本。自己搭推理服务服务器成本是显性的但维护成本、调优成本、故障处理成本都是隐性的。调API单价是显性的但数据隐私风险和供应商锁定风险是隐性的。我一般会算一个三年期的总拥有成本这样比较才有意义。迭代速度是最容易被忽略的。AI项目的特点是需求变化快今天要分类明天可能要加生成。如果你的架构不支持快速迭代每次改需求都要重构那项目基本没法推进。所以架构设计上要留好扩展点把模型层和业务层解耦换模型不影响业务逻辑。3. 核心细节解析从环境到推理服务的实操要点3.1 环境准备别小看这一步环境准备看起来简单实际上是新手翻车最多的地方。我见过太多人卡在装依赖上一卡就是两三天热情直接磨没了。我的建议是不管你是Windows还是Mac都先用Docker把环境隔离起来。不是为了炫技是为了可复现。AI项目的依赖冲突特别严重不同库对同一个底层库的版本要求经常打架用虚拟环境都不一定能解决Docker是最稳的方案。基础镜像我一般选python:3.11-slim比完整版小很多构建快。然后按需装依赖不要一上来就pip install一堆。核心依赖就几个numpy做数值计算torch或transformers做模型推理fastapi做服务框架uvicorn做ASGI服务器pydantic做数据校验。这里有个细节torch的安装要选对版本。CPU版和GPU版是两个不同的包源装错了要么跑不了GPU要么装了一堆用不上的CUDA库。如果你只是做推理CPU版通常够用除非你的模型特别大或者QPS特别高。提示装torch之前先确认你的CUDA版本用nvidia-smi看。然后去PyTorch官网查对应的安装命令不要凭记忆写。还有一个坑是pip的源。默认源在国内访问很慢换成国内镜像源能快十倍。这个不多说搜一下就有。3.2 数据入口把脏活累活做在前面数据入口这块我的经验是花多少时间都值得。因为数据质量直接决定模型效果上限而数据处理的代码往往是最脏最乱的。一个标准的数据入口应该做四件事加载、清洗、转换、缓存。加载就是从各种来源把数据读进来可能是CSV、JSON、数据库、或者API。清洗是去掉明显有问题的数据比如空值、重复、格式错误的。转换是把数据变成模型需要的格式比如文本要tokenize图片要resize。缓存是把处理好的数据存下来避免每次训练都重新处理。我重点说清洗和缓存。清洗这块很多人只做最基本的去重和去空但实际数据里的问题远不止这些。比如文本数据里的编码问题、HTML标签残留、特殊字符图片数据里的损坏文件、尺寸异常、色彩空间不一致。这些问题不处理训练出来的模型会有各种奇怪的行为。缓存这块我强烈建议用Parquet格式而不是CSV。Parquet是列式存储读取速度快很多而且自带压缩文件体积小。对于大规模数据读取速度的差异可能是分钟级和秒级的区别。import pandas as pd # 读取原始数据 df pd.read_csv(raw_data.csv) # 基础清洗 df df.dropna(subset[text]) df df.drop_duplicates(subset[text]) df[text] df[text].str.strip() # 过滤过短文本 df df[df[text].str.len() 10] # 存为Parquet df.to_parquet(clean_data.parquet, indexFalse)这段代码看起来简单但每一步都有讲究。dropna的subset参数指定只检查text列避免因为其他列的空值误删数据。drop_duplicates的subset同理。strip去掉首尾空白避免因为空格导致的重复。过滤过短文本是因为太短的文本通常没有训练价值。3.3 推理服务从脚本到服务的跨越把模型跑起来和把模型做成服务是两件完全不同的事。前者只需要一个脚本后者需要考虑并发、错误处理、资源管理、版本控制。我推荐用FastAPI做推理服务原因是它异步支持好、自动生成文档、类型校验强。下面是一个最小可用的推理服务框架。from fastapi import FastAPI, HTTPException from pydantic import BaseModel from transformers import pipeline import logging app FastAPI() logger logging.getLogger(__name__) # 全局加载模型避免每次请求都加载 model pipeline(text-classification, modelyour-model-path) class PredictRequest(BaseModel): text: str class PredictResponse(BaseModel): label: str score: float app.post(/predict, response_modelPredictResponse) async def predict(req: PredictRequest): if not req.text or len(req.text) 512: raise HTTPException(status_code400, detailInvalid input length) try: result model(req.text)[0] return PredictResponse(labelresult[label], scoreresult[score]) except Exception as e: logger.exception(Prediction failed) raise HTTPException(status_code500, detailInternal error)这个框架有几个关键点。模型在全局加载一次不是每次请求都加载这是性能的关键。输入校验放在最前面避免无效请求进入模型。异常处理要记日志不然线上出问题你什么都不知道。启动命令用uvicorn加workers参数控制并发进程数。一般设成CPU核数加一但具体要看模型大小和内存。模型大的话workers多了会OOM。uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2注意workers不是越多越好。每个worker都会加载一份模型到内存模型1G的话4个worker就是4G内存。先算内存再定workers。3.4 效果评估没有评估就没有优化效果评估是AI工程里最容易被糊弄的环节。很多人就是跑几个例子看看觉得差不多就上线了。这种做法在小规模场景下可能没事一旦规模上去问题就会集中爆发。我的做法是建一个固定的评估集规模不用大几百条就够但必须覆盖所有重要的场景。每次模型更新都跑一遍评估集看指标变化。指标的选择要看任务类型分类任务看准确率和F1生成任务看BLEU和ROUGE检索任务看召回率和MRR。评估集的建设有个技巧从线上真实数据里采样而不是自己造。自己造的数据往往过于理想化覆盖不到真实场景里的边界情况。采样的时候要注意类别平衡避免某一类样本过多导致指标虚高。评估的频率也要控制。不是每次改代码都跑全量评估那样太慢。我的做法是分两级快速评估用一个小集子几十条每次提交都跑全量评估用完整集每天跑一次或者发版前跑。4. 实操过程从零搭一个文本分类系统的完整记录4.1 项目初始化和目录结构我以一个文本分类任务为例完整走一遍从零搭建的过程。这个任务是把用户评论分成正面和负面两类是最经典的AI工程入门任务。目录结构我习惯这样组织project/ ├── data/ │ ├── raw/ │ └── processed/ ├── src/ │ ├── data/ │ │ └── prepare.py │ ├── model/ │ │ └── train.py │ ├── service/ │ │ └── main.py │ └── eval/ │ └── evaluate.py ├── tests/ ├── Dockerfile ├── requirements.txt └── README.md这个结构的好处是职责清晰。data放数据src放代码service放服务eval放评估。每个模块独立改一个不影响其他。requirements.txt要锁版本不要用大于等于号。AI库的版本兼容性很脆弱今天能跑的代码明天可能就跑不了。锁版本是保证可复现的基础。fastapi0.104.1 uvicorn0.24.0 transformers4.35.0 torch2.1.0 pandas2.1.3 scikit-learn1.3.24.2 数据准备和基线模型数据准备我前面讲过这里补充一个细节划分训练集和验证集的时候要用分层采样保证两边的类别比例一致。不然验证集的指标会失真。from sklearn.model_selection import train_test_split train_df, val_df train_test_split( df, test_size0.2, stratifydf[label], random_state42 )stratify参数就是分层采样random_state固定随机种子保证可复现。基线模型我建议先用最简单的方案比如TF-IDF加逻辑回归。别一上来就上BERT先跑通链路再说。基线模型的价值在于给你一个参照系后面换复杂模型的时候你能知道提升到底有多少。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline baseline Pipeline([ (tfidf, TfidfVectorizer(max_features10000)), (clf, LogisticRegression(max_iter1000)) ]) baseline.fit(train_df[text], train_df[label])这个基线在大多数文本分类任务上能到80%左右的准确率作为起点足够了。4.3 升级到预训练模型基线跑通之后再升级到预训练模型。这一步的关键是选对模型和调对参数。模型选择上中文任务用bert-base-chinese英文任务用distilbert-base-uncased。distilbert是bert的蒸馏版效果差一点点但速度快一倍工程上更划算。训练参数里最重要的是学习率和batch size。学习率一般设2e-5到5e-5batch size设16或32。这两个参数要一起调学习率大batch size也要大不然训练不稳定。from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments tokenizer AutoTokenizer.from_pretrained(distilbert-base-uncased) model AutoModelForSequenceClassification.from_pretrained( distilbert-base-uncased, num_labels2 ) def tokenize(batch): return tokenizer(batch[text], paddingTrue, truncationTrue, max_length128) train_enc train_df.apply(tokenize, axis1)max_length设128是个经验值覆盖大多数评论长度。设太大浪费计算设太小截断信息。可以先统计一下数据里文本长度的分布取95分位数作为max_length。训练过程用Trainer封装省去手写训练循环的麻烦。但要注意Trainer默认的评估指标是loss你需要自己定义compute_metrics函数来算准确率和F1。4.4 服务化部署和压测模型训好之后导出成可以部署的格式。我一般用torch.save保存整个模型或者用ONNX导出做推理加速。服务化就是前面讲的FastAPI框架把模型加载和服务逻辑接起来。这里补充一个细节模型加载要放在启动事件里不要放在模块顶层。因为模块顶层加载会在import的时候就执行测试的时候很麻烦。app.on_event(startup) async def load_model(): global model, tokenizer model AutoModelForSequenceClassification.from_pretrained(./model) tokenizer AutoTokenizer.from_pretrained(./model)部署完之后一定要压测。压测工具用locust或者wrk看QPS和P99延迟。QPS决定你的服务能扛多少流量P99延迟决定用户体验。一般P99延迟要控制在200ms以内超过这个值用户能明显感觉到卡。压测的时候要注意第一次请求会触发模型预热延迟特别高。所以压测要跑够长时间把预热阶段排除掉再看稳定值。5. 常见问题与排查技巧实录5.1 模型效果不达预期怎么排查模型效果不好是最常见的问题但原因可能有很多种。我一般按这个顺序排查数据、模型、训练、评估。先看数据。数据量够不够标注质量怎么样类别是否平衡我遇到过一个案例模型准确率死活上不去最后发现是标注数据里有30%的标签是错的。数据问题不解决换什么模型都没用。再看模型。模型选对了吗对于文本分类bert类模型通常够用对于序列标注需要token-level的模型对于生成任务需要seq2seq的模型。模型选错了怎么调都白搭。然后看训练。学习率是不是太大或太小训练轮数够不够有没有过拟合看训练loss和验证loss的曲线如果训练loss一直降但验证loss开始升就是过拟合了需要加正则或者减模型复杂度。最后看评估。评估集有没有问题指标选对了吗我见过有人用准确率评估不平衡数据结果模型全预测多数类准确率还有90%但实际完全没用。不平衡数据要用F1或者AUC。5.2 推理服务性能优化推理服务的性能问题通常出在三个地方模型加载、请求处理、资源竞争。模型加载的问题前面讲过要全局加载一次。但还有一个坑是模型没有预热。第一次推理会触发各种初始化延迟可能是正常值的十倍。解决办法是在服务启动后主动跑几次推理做预热。请求处理的问题主要是同步阻塞。如果推理是CPU密集型的用async反而会拖慢因为async适合IO密集型。这种情况应该用多进程而不是多线程Python的GIL会限制多线程的CPU并行。资源竞争的问题在多worker场景下常见。多个worker同时读写同一个文件或者抢同一个GPU会导致性能下降甚至报错。解决办法是每个worker独立资源或者用队列做串行化。提示GPU推理的时候batch size不是越大越好。太大的batch会导致显存溢出而且延迟会增加。要找到吞吐量和延迟的平衡点。5.3 线上效果和离线效果不一致这个问题特别隐蔽但特别常见。离线评估准确率90%上线之后用户反馈一堆错误。原因通常是数据分布不一致。离线评估用的是历史数据线上遇到的是新数据。如果新数据的分布和历史数据不一样模型效果就会下降。这种情况叫数据漂移是AI系统特有的问题。解决办法有两个。一个是持续监控线上数据的分布发现漂移就重新训练。另一个是在训练数据里加入更多样的样本提高模型的泛化能力。监控数据分布可以用一些统计指标比如文本长度的分布、词频的分布、预测结果的分布。这些指标的变化能提前预警数据漂移。5.4 常见问题速查表问题现象可能原因排查方法解决方案模型准确率低数据质量问题抽样检查标注重新标注或清洗训练loss不下降学习率太小看loss曲线调大学习率验证loss上升过拟合对比训练验证曲线加正则或减复杂度推理延迟高模型未预热看首次请求延迟启动时预热服务OOMworkers太多看内存占用减少workers或换小模型线上效果差数据漂移对比线上线下分布重新训练或加样本并发上不去GIL限制看CPU利用率改用多进程预测结果不稳定输入未校验检查异常输入加输入校验这张表是我自己踩坑总结的基本上覆盖了80%的常见问题。遇到问题先查表能省很多时间。6. 我个人的一些实操心得6.1 关于工具选择工具选择上我的原则是能用简单的就不用复杂的。很多人喜欢追新工具今天LangChain明天LlamaIndex结果项目里一堆依赖维护成本极高。实际上很多场景用不上这些框架直接调API或者用transformers就够了。框架的价值在于降低重复劳动但如果你的场景本身就不复杂框架反而增加理解成本。我见过有人用LangChain搭一个简单的问答系统代码量比直接调API多了三倍调试还特别麻烦。6.2 关于迭代节奏AI项目的迭代节奏和传统软件不一样。传统软件改代码就行AI项目改代码只是一部分还要改数据、改模型、改参数。所以迭代周期通常更长要有心理准备。我的建议是把迭代拆小每次只改一个变量。比如这次只改数据下次只改模型这样能清楚地知道每个改动的影响。一次改多个变量效果好了不知道是哪个起的作用效果差了也不知道是哪个搞的鬼。6.3 关于文档和记录AI项目特别需要记录因为实验太多了。今天试了这个参数明天试了那个模型不记录的话一周后就忘了。我习惯用表格记录每次实验的配置和结果包括数据版本、模型版本、超参数、评估指标。这个记录习惯看起来麻烦但长期看省时间。当你需要复现某个结果或者对比不同方案的时候有记录和没记录是天壤之别。6.4 关于上线策略AI系统上线一定要有回滚方案。模型效果不好或者服务出问题的时候要能快速切回旧版本。我一般用A/B测试的方式上线先放10%的流量到新模型观察一段时间没问题再逐步放大。灰度发布的过程中要重点看几个指标预测延迟、错误率、业务指标。预测延迟和错误率是技术指标业务指标是效果指标。三个都正常才能全量。最后分享一个小技巧给模型输出加一个置信度阈值。低于阈值的预测不直接返回而是走人工审核或者返回默认值。这样能过滤掉大部分低质量预测提升用户体验。阈值定多少要看业务容忍度一般从0.8开始试。
返回列表