经常有人问我同一个问题:想做AI,是不是先把深度学习模型啃透、把Transformer源码读一遍,就能成为一名AI工程师?
我自己做这个ai-engineering-from-scratch项目之前,也是这么想的。但真正把一个模型从想法推到能稳定服务用户的系统之后,我才意识到:模型训练只是AI工程这条链路上最显眼的一环,而真正决定一个AI项目能不能落地、能不能长期稳定运行的,反而是大量看不见的工程细节。
如果你正打算进入AI领域,或者已经在做算法岗但总觉得自己的项目差点意思,那么这篇文章可能适合你。我会从零开始,把AI工程到底是什么、需要打通哪些能力、实际项目里哪些环节最容易翻车,按我自己的经验完整梳理一遍。里面不会有花哨的大模型炫技,更多的是数据管线、实验管理、部署监控这些“脏活累活”的真实解法。
1. AI工程是什么、不是什么——先分清“调参侠”和“工程师”
1.1 一个扎心的类比:会炒一道菜不等于会开一家餐厅
很多教程教你学AI,本质上是教你“炒一道菜”:给你一份清洗好的公开数据集,跑通一个现成的模型脚本,然后调两个超参数让准确率提升两个点。这确实能让你学会“做一道菜”,但它和真正的AI工程之间差着十万八千里。
真实世界的AI项目更像开一家餐厅。你要考虑食材从哪来、质量怎么控制、后厨流程怎么设计、高峰期怎么接单、客人投诉怎么办、厨房着火怎么止损。模型训练只是“炒菜”这个动作本身,而AI工程关心的是“这家餐厅怎么持续赚钱、怎么规模化经营”。这个类比我在好几次技术分享里用过,每次都能看到听众点头——因为做过真实项目的人都有同样的体感:模型跑通的那一刻,项目其实才刚开了个头。
所以AI工程的定义,我更倾向于这样表达:AI工程是用系统化、可重复、可维护的方式,把机器学习模型(或者说AI能力)从一个idea变成稳定运行的产品能力的整套方法论。它涵盖了数据工程、实验管理、模型服务化、监控告警、持续迭代这一整条链路。
1.2 AI工程的四层骨架:数据、模型、部署、迭代
如果让我把这套东西拆成四个层级,大致是这样的:
| 层级 | 核心内容 | 常见工具/职责 |
|---|---|---|
| 数据层 | 数据采集、清洗、标注、版本管理、质量监控 | DuckDB, DVC, LabelStudio, Great Expectations |
| 模型层 | 实验追踪、配置管理、训练调优、评估验证 | MLflow, W&B, Optuna, PyTorch Lightning |
| 部署层 | 模型服务化、性能优化、上线回滚、资源调度 | FastAPI, Triton, ONNX, Kubernetes, Ray Serve |
| 迭代层 | 线上监控、数据漂移检测、反馈闭环、持续再训练 | Prometheus, Grafana, Evidently AI, 定期重训pipeline |
这四层不是串联关系,而是互相咬合的。数据层质量差,模型层再好也白搭;部署层不稳定,模型效果再好也传不到用户手里。AI工程师的日常工作,就是在这四层之间不断往返,找到瓶颈并解决它。
为什么这两年AI工程的这个词越来越热?一个重要的原因是:模型本身的获取门槛在急速降低。开源社区里预训练模型越来越多,像HuggingFace上随便一个开源模型,效果可能比你从零训练的模型还好。这导致“算法能力”的稀缺性在下降,而“把算法稳定地变成产品能力”的工程能力,变成了真正的稀缺资源。这个趋势在过去一年里体现得格外明显,岗位JD里的关键词也从“调参、炼丹”慢慢变成了“训练加速、推理优化、数据管线、监控系统”。
1.3 从ai-engineering-from-scratch开始,我打算打通什么
说来惭愧,我最初想做这个项目,动机其实很简单:我发现自己能跑通教程里的模型,但一旦要自己独立做一个稍微完整点的AI项目,就卡住了。数据不知道该去哪弄、实验记录随手丢、模型训练到一半进程崩了没有断点续训、部署上线之后模型测了线上数据效果崩盘……这些问题每个单看都不算难,但堆在一起,就会让你觉得寸步难行。
所以我给自己定了一个从零开始的项目目标:不依赖任何现成的“保姆级教程”,自己动手把一条完整的AI工程链路搭建起来,每一个环节都用真实的工具和真实的数据去跑通。这个过程里踩过的坑、总结的解法,就是我在这篇长文里想分享给你的核心。
2. 从零起步的个人能力拆解:先打通“能跑”到“能交付”的能力链路
如果你也是从零开始,建议先别急着买一堆深度学习的书啃,而是先对照一下自己缺哪块能力。我用一个简单的清单梳理过AI工程需要的基础功底,你可以自测一下。
2.1 软件工程基础:AI工程的地基
说实话,很多算法岗的同学软件工程基础是偏弱的。我以前也这样,写训练脚本是脚本式思维:把所有东西堆在一个Jupyter Notebook里,从上往下跑通就算完事。但这样做出来的东西,连你自己隔两周来看都会看不懂,更别说别人接手维护了。
AI工程对软件工程的依赖体现在四个具体能力上:
- Git版本控制:不只是把代码传到GitHub,而是要会用分支管理实验代码、用tag标记可发布的版本、能回滚出问题的改动。我遇到过不止一次,实验效果好但代码改动记录一团乱,最后根本没法复现。
- 单元测试与数据验证:给数据清洗函数、特征工程函数写测试。很多人觉得这是浪费时间,直到有一天发现某个特征在特定数据分布下会变成NaN,导致线上模型悄无声息地退化了好几天。
- 模块化与代码组织:把数据加载、预处理、模型定义、训练逻辑、评估逻辑拆成独立模块。别小看这一步,它直接决定了你迭代实验的快乐程度。
- CI/CD的思维:不一定要一上来就搭完整的持续集成,但至少要有“改动代码后自动跑一遍测试和评估”的意识。我后来用GitHub Actions搭了一个很轻量的pipeline,每次push代码自动跑测试和一个小规模冒烟训练,帮我在早期拦截了大量低级错误。
2.2 数据处理能力:真正的分水岭
我观察过一个很有意思的现象:新手做AI项目,时间分配往往是80%花在模型上、20%花在数据上;而有经验的AI工程师,这个比例恰好是倒过来的。数据处理能力,才是从“模型玩家”到“AI工程师”的真正分水岭。
这里的数据处理不只是会用Pandas读文件,而是要具备体系化的能力:
- 能写SQL/使用DuckDB处理GB甚至TB级别的表格数据,别什么都加载进内存用Pandas硬干;
- 能自己写爬虫或利用公开API构建数据集,明白什么样的数据源有版权风险、什么样的数据有偏倚隐患;
- 能做数据质量分析,用Great Expectations或ydata-profiling这类工具快速发现字段缺失、分布异常、类型错乱等问题;
- 能做离线数据验证,在pipeline里自动卡住“脏数据流入训练”这条路径。
2.3 机器学习基础:够用就好,但要结构化
我不是叫你绕开机器学习理论。线性回归、逻辑回归、决策树、梯度下降、损失函数、过拟合与正则化,这些核心概念必须扎实掌握。但我也建议你不要陷入“把所有数学推导搞明白才肯动手”的误区。更好的方式是在实践中建立直觉,再回头补理论。
比如你训练一个模型,出现严重过拟合,这时候你需要的不只是知道“dropout、正则化、数据增强”这些名词,而是要能判断先上哪个方案性价比最高:通常先加数据增强和早停,再看要不要调结构。这种决策能力比死记硬背公式重要得多。
深度学习方面,理解神经网络的基本原理、常见的激活函数、优化器(SGD/Adam)、归一化层的优缺点、Transformer的注意力机制大致计算逻辑,基本够用了。现在的框架把这些都封装得很好,工程上更关键的是知道“用哪个”“为什么用”“切了之后有什么影响”。
2.4 工程基础设施认知:容器、GPU、命令行
这一块是很多非科班同学最大的盲区。我在项目里吃过亏,训练脚本在自己电脑上跑得好好的,一挪到服务器上就各种起不来。后来才发现是环境依赖不一致的锅。
所以建议你至少掌握:
- Docker的基础使用:能写Dockerfile把训练环境固化成镜像;懂得镜像和容器的区别;能在容器里跑训练并且把结果目录挂载出来。
- 命令行效率:能熟练使用rsync同步数据、用tmux/screen挂后台任务、用nvidia-smi查GPU状态、用htop看资源占用。
- GPU的基本认识:知道显存(VRAM)和内存(RAM)的区别、什么样的操作会让显存暴涨、如何通过降低batch size或梯度检查点来控制显存占用。
我当时是按这个顺序把能力一点点补齐的,每一步都对应到项目里真实要解决的问题,所以学起来不会觉得抽象。这个路线不一定适合所有人,但如果你也是从纯业务或纯软件背景转过来,可以参考一下。
3. 从零搭建可复现的AI开发环境:不解决环境污染,后面全是噩梦
3.1 为什么环境问题会在AI项目里被无限放大
普通的后端项目,环境不一致最多就是服务挂了、报个依赖错误,你花半小时修一下就行。但AI项目里,环境之痛会被无限放大,因为你的实验效果受太多因素影响:Python版本、CUDA版本、PyTorch版本、各个依赖库之间的微妙交互、甚至cuDNN的版本差异,都可能导致同一份代码跑出完全不一样的结果。
我经历过一次特别崩溃的事:在A服务器上训练出的模型AUC是0.78,在B服务器上复现同样的训练,AUC变成了0.74。排查了三天,最后发现是两台机器上CUDA的版本不同,导致PyTorch调用了不同的卷积算法,结果数值稳定性产生了偏差。从那以后,我对自己只有一个要求:环境必须固化,任何一次实验都要有完整的可复现环境记录。
3.2 我最终选择的工具链与理由
我从一开始试过直接用conda管理环境完事,但后来发现只依赖conda是不够的。经过几次折腾,我目前比较稳定的组合是这样的:
- conda(或mamba):管理Python版本和部分二进制依赖,比如CUDA toolkit、cudnn。它的优势是能解决非Python包的依赖问题,比纯pip省心很多。
- Docker:把整个conda环境打包成镜像,保证本地和服务器、训练机和推理机完全一致。这是我的最终防线。
- Poetry或pip-tools:管理Python依赖的版本锁定。我偏向用Poetry,因为它能把依赖分组(主依赖、开发依赖)并且生成lock文件。
- 预提交钩子(pre-commit):在代码提交前自动跑格式化、lint检查,防止“别人接手你代码时想骂人”的情况。
这里有个实操细节值得说一下:conda环境虽然方便,但如果你直接用conda install安装了一堆包,环境文件environment.yml里记录的版本信息往往不够精确,很容易在换一台机器时复现出不同结果。我的做法是尽量用conda做基础环境,再用poetry管理项目的Python依赖并锁定精确版本,最后通过Docker做整体固化。这样三层下来,复现概率已经高得多了。
3.3 从裸机到可运行的项目模板,一次走通
分享一个我后来固定下来的项目目录结构,给新手做个参考:
my-ai-project/ ├── .github/ │ └── workflows/ # CI流水线,跑测试和冒烟训练 ├── data/ │ ├── raw/ # 原始数据,只读不写 │ ├── processed/ # 清洗后的数据 │ └── external/ # 外部引入的公开数据 ├── src/ │ ├── data/ # 数据加载、清洗、特征工程的代码 │ ├── models/ # 模型定义 │ ├── train/ # 训练逻辑 │ ├── evaluate/ # 评估逻辑 │ └── serve/ # 推理部署代码 ├── tests/ # 单元测试和集成测试 ├── configs/ # 所有实验配置文件(YAML格式) ├── scripts/ # 启动训练、评估、部署的脚本 ├── notebooks/ # 探索式分析,和主代码分离 ├── Dockerfile ├── Makefile # 常用的命令入口 └── pyproject.toml这个结构看起来简单,但每条规则背后都是踩过坑的。比如data/raw只读不写这条,我见过太多人把原始数据处理到一半覆盖掉了,等要用的时候才发现已经找不回原始数据。再比如notebooks和src分离,是为了避免“代码只在notebook里能跑,一旦抽象成模块就各种报错”的经典悲剧。
3.4 环境搭建最容易踩的三个坑
第一个坑是Python版本和系统包冲突。Ubuntu自带的Python和conda里的Python共存,路径混乱,导致命令行里敲python启动的其实是旧版本。解决办法是尽量用conda的base环境自带的python,或者显式用conda run -n env_name python xxx.py来执行脚本。
第二个坑是CUDA和GPU驱动的错配。装PyTorch的时候,很多人直接pip install torch,结果装的是CPU版本,GPU根本用不上。你应该先看自己机器的CUDA驱动支持什么版本,再到PyTorch官网选择对应的安装命令。这里教大家一句心法:驱动版本是老大,CUDA Toolkit版本是老二,PyTorch编译时带的CUDA版本是老三,三者关系必须兼容向下。
第三个坑是依赖雪崩。很多数据科学库互相之间有复杂的版本依赖关系,装A库会把B库升级,结果B库的新版本和C库不兼容。锁版本这一步真的不能省。
提示:如果你电脑一般、只有CPU可以跑,前期照样能完成大部分AI工程链路的学习。真正需要GPU的场景主要在大规模训练和推理性能测试,前期完全可以先在小数据上把流程跑通,再找云GPU机器做正式训练。
4. 数据管线:80%工作量最容易翻车的环节
4.1 数据的来源问题:别盲目迷信公开数据集
很多教程项目的数据集是现成的、清洗过的、甚至标签都给你标好了。这导致了一个严重的认知偏差:新人以为做AI最耗时间的不是搞数据,而是调模型。
但真实项目里,数据永远是最大变量。我接手过的项目里,数据情况五花八门:有的数据散落在各个业务系统的数据库里,需要你写SQL提取;有的数据是PDF和图片,需要做OCR抽取;有的数据没有任何标签,需要从零设计标注方案;还有的数据有标签但标签质量极差,需要你用规则清洗和人工复核相结合来提升质量。
我的建议是:做ai-engineering-from-scratch项目时,不要用一个“已经被处理得干干净净”的数据集起步,那样你会错过整个AI工程里最有价值的一段训练。自己找一个相对冷门但真实的主题,从数据采集开始做。采集的渠道可以是公开API(比如某些开放数据平台)、网站页面的爬取(注意遵守robots协议和版权规则),甚至可以是现实世界里的实物数据(比如用摄像头采集某个场景的图片)。
4.2 数据清洗的七条实用经验
数据清洗没有银弹,但有几个方向是通用的,我整理了七条经验:
- 先看轮廓再动手:不要上来就写清洗代码。先用ydata-profiling生成一份数据报告,整体看一遍字段缺失率、值分布、类型推断,心里有数之后再动手。
- 缺失值处理要有依据:均值填充、中位数填充、预测填充各有适用场景,但前提是你要理解这个字段的业务含义。根本原因是用户没填还是系统没记录,应对方式完全不同。
- 异常值先标记不急着删:异常值可能是噪声,也可能是极其重要的信号(比如欺诈检测里的极端金额)。先打标记,建模时再决定是用截断、分箱还是单独建模。
- 字符串字段统一规范:性别字段里“男”、“male”、“M”、“男性”同时存在,这种情况我见了太多次。写一个归一化函数一劳永逸。
- 采样逻辑要记录:从千万条数据里采样了十万条,怎么采的?随机还是分层?这个信息必须在代码注释里写清楚,否则实验结论的可推广性根本没法判断。
- 保存清洗版本历史:清洗代码每次改动后,对processed数据生成新版本,不要在同一份数据上原地改来改去。
- 每次加载数据后做断言:比如“id没有重复”、“价格字段没有负数”、“日期字段都能解析成功”,这些简单断言能在第一时间发现上游数据异常。
4.3 数据版本管理:为什么你上个月的结果永远复现不出来
你有没有遇到过这种情况:模型效果明明很好,结果你今天重新拉数据想复现一下,发现效果对不上了。你以为是自己模型代码有bug,排查了半天,最后发现是训练数据被更新过、或者清洗逻辑被改过——数据变了你却毫不知情。
这个问题靠Git是解决不了的,因为Git是为文本代码设计的,而数据往往是大文件、二进制文件,而且数据变更的记录经常和代码变更不同步。数据版本管理的核心思路是:每次训练实验都要记录当时用的数据版本、代码版本、配置参数、环境信息,四个缺一不可。
工具层面我用的是DVC(Data Version Control)。DVC的设计思路很有启发性:它不直接把数据存进Git,而是把数据文件保存在本地的缓存/云存储里,在仓库里维护一个很小的元数据文件(类似一个指针)。你把元数据文件提交到Git,就相当于在Git里记录下了某一时刻数据的版本信息。别人从Git拉代码后,通过dvc pull就能把对应版本的数据拉到本地。
操作上大致是这样的:
# 开始跟踪数据目录 dvc init dvc add data/raw # 把dvc文件提交到git git add data/raw.dvc .dvc/config git commit -m "feat: 添加raw数据初始版本" # 推送实际数据到远程存储(比如S3、MinIO) dvc remote add -d myremote s3://my-bucket/dvc dvc push之后每次数据更新,流程都是:更新数据目录 →dvc add→git commit。这样Git仓库里就留下了一条清晰的数据版本演进线,而且可以随时dvc checkout回到任意历史版本。
4.4 数据质量自动化验证:别等到训练完才发现是脏数据害了你
我们经常说“垃圾进、垃圾出”,但实际工作中,数据质量的检查往往是滞后的——你已经花了一整天训练模型,回来一看效果不对,才回头去查数据,发现某个字段的值全乱了。如果能把这个检查放在训练之前自动化跑掉,一整天的时间不就省下来了吗?
这就是数据验证pipeline的价值。我用的工具是Great Expectations(现在叫GX),核心概念是:你对数据做出“断言”,然后让工具自动检查数据是否满足断言。比如:
- 订单金额字段不能为负;
- 用户ID的缺失率不能超过5%;
- 日期字段必须全部能被解析为合法日期;
- 分类特征的取值集合必须落在历史允许范围内。
在训练pipeline的早期加这么一步验证,数据一旦异常,pipeline就自动fail并告警,而不是带着脏数据继续往下跑。这个习惯,是我做这个项目最大的收获之一。
5. 训练实验管理:从“玄学调参”到“可复现的实验体系”
5.1 实验记录的痛是每个人都要经历的
如果一个AI项目你只跑过五六次实验,可能觉得实验管理没必要。但我敢说,只要实验次数超过二三十次,你一定会经历这些时刻:你明明记得某个实验效果很好,却想不起来当时改了哪个参数;你把lr=0.001改成了lr=0.0005,结果曲线好像好了不少,但你只改了一个参数吗?还是中间不小心动了别的设置?
这些问题的根源都一样:实验和实验之间的差异没有结构化记录。我一度靠Excel表格和命名文件夹来记录实验,但很快就坚持不下去了——你没法保证自己每次都能记得手动更新表格,而且命名的文件夹根本承载不了环境信息、数据版本、代码版本这种复杂元数据。
5.2 用MLflow建立一套最低限度的实验管理体系
我最终选择的方案是MLflow,关键是它上手非常轻,却能把实验的关键信息都管起来。MLflow的四大部分里,我用得最多的是Tracking(实验追踪)和Model Registry(模型注册)。Tracking的用法简单说就是这样:
import mlflow # 设置追踪地址 mlflow.set_tracking_uri("http://localhost:5000") # 开始一轮实验 with mlflow.start_run(run_name="bert_base_finetune_v3"): # 记录参数 mlflow.log_param("learning_rate", 0.00002) mlflow.log_param("batch_size", 32) mlflow.log_param("model_name", "bert-base-chinese") # 训练过程... # 记录指标 mlflow.log_metric("val_loss", 0.413) mlflow.log_metric("val_acc", 0.871) # 记录模型和代码版本 mlflow.log_artifact("best_model.pt") mlflow.log_artifact("config.yaml")这就是最低限度的实验管理体系了。比这个更重要的是习惯:每次训练脚本启动时自动开启一个run,所有参数、指标、产物全部交给MLflow记录,不需要你手动去记。跑完之后,在MLflow的Web UI上,就能看到所有实验的变化趋势,横向对比一目了然。
5.3 配置管理:论“写死参数”的危害
我见过太多的训练脚本,各种超参数直接写死在代码里。这么做最直接的问题是:你为了测一个新参数,就得复制一份脚本临时改,改乱了也不奇怪。而且时间一长,代码里遍布各种注释掉的参数组合,根本分不清哪些是有效的。
正确做法是把配置和代码分离。我习惯用YAML配置文件加一个简单读取函数来实现。每次实验就是一份独立的YAML配置文件,代码本身保持通用:
# configs/exp_bert_03.yaml model: name: bert-base-chinese max_length: 128 data: train_path: data/processed/train_v03.parquet test_path: data/processed/test_v03.parquet label_column: label drop_duplicates: true training: learning_rate: 2e-5 batch_size: 32 epochs: 5 seed: 42 early_stopping_patience: 2 evaluation: metrics: ["accuracy", "f1", "precision", "recall"]然后在训练代码里做一件事:把YAML配置文件和训练结果一起交给MLflow记录。训练脚本启动时接收一个参数指定用哪份配置:
python src/train.py --config configs/exp_bert_03.yaml这样,每份配置本身就是一次实验的存档,结合MLflow的Tracking,任何一次实验都能完整回溯:数据版本是什么、配置是什么、代码commit是什么、跑出来的指标是什么。做一个可复现的AI项目,这套体系就是底气。
5.4 种子管理与随机性控制
不知道你有没有遇到过这种情况:同一份代码、同样的参数,在同样的环境里跑两遍,效果居然不一样,有时差别还挺大。这里面有两个主要来源:初始化参数随机性和数据加载顺序的随机性。
把控制随机性的代码固定下来是个好习惯,不要只在代码里写一句random.seed(42)就完事。一份更完整的种子固定应该长这样:
import random import numpy as np import torch def set_seed(seed: int): 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注意,PyTorch里有个torch.use_deterministic_algorithms(True)的选项,开了之后很多算子会被强制使用确定性算法,但可能带来性能损失。实操里我的建议是:实验对比阶段必须开确定性,线上性能调优阶段可以酌情关闭。不同框架和版本下,seed固定的效果不完全一样,所以同一实验尽量在相同环境中运行多遍取均值,才能得到更可靠的结论。
5.5 训练可观测性与断点续训
长训练是AI工程的另外一种考验。一个模型训练十几个小时是家常便饭,中间一旦进程崩溃,如果没有断点续训能力,前面的算力就全部白费了。所以训练代码里至少要有两块保命措施:
- 定期保存checkpoint:不只保存最后的模型权重,还要保存优化器状态、epoch数、学习率调度器的状态。因为恢复训练如果只恢复权重不恢复优化器状态,效果会大打折扣。
- 日志统计要结构化:训练损失、验证指标、学习率、显存占用、数据加载耗时,这些指标定期写入本地日志和MLflow。训练出问题时,你可能需要从日志里反推是最先崩在哪一步。
我用过的比较轻量的方案是配合PyTorch Lightning的ModelCheckpoint回调来做checkpoint保存,关键参数是save_top_k(只保存最优的几个)和monitor(监控哪个验证指标来决定是否更新最优模型)。这样磁盘也不会被一堆checkpoint占满。
6. 部署与监控:模型上线,才是工程的真正开始
6.1 部署画像:你的模型到底需要哪种上线方式
很多第一次接触部署的人容易陷入一个误区:以为模型部署就是把模型存下来,然后用Flask包一层HTTP接口就完事。但实际工程里,部署方案需要根据使用场景来定。我一般会画一个“部署画像”,确认几个关键问题:
- 延迟要求:是毫秒级(在线推荐、审核接口),还是秒级(批量处理),还是根本没有实时性要求(离线批量预测)?
- 吞吐要求:每秒多少个请求?每天多少条数据要跑?
- 成本要求:算力预算是多少?这个量级需要多少张GPU?
- 弹性要求:流量是平稳的还是所有脉冲式的?需要自动扩容吗?
根据这几个变量,你会得到不同的部署姿势。最轻量的方案是直接用FastAPI包一个HTTP服务,加载模型进内存做推理。复杂一点的方案是用Triton Inference Server做多模型管理和GPU并发优化,再用Kubernetes做容器编排和自动伸缩。对于从零开始的项目,我的建议是:先服务化跑通,再考虑优化。
6.2 推理性能优化:从“能跑”到“扛得住”
第一次用FastAPI把BERT模型包成服务,我测了一下延迟:单条文本推理要120毫秒。看起来不多,但如果接口的QPS要求是50,那单机单进程是扛不住的。这里要用到一个很重要的指标概念:P95延迟和吞吐量之间的关系,而不是只盯着单条延迟。
推理优化的常规手段,从低成本到高成本大概是这样一个顺序:
- 批量推理:GPU的算力是并行的,单条输入进去算和32条输入一起算,时间差不多。所以在线服务里把并发请求攒起来、凑成一个batch再推入模型,是性价比最高的优化。
- 模型轻量化:蒸馏、剪枝、量化。把BERT从12层压缩到6层,或者把权重从FP32量化到INT8,推理速度和显存占用都会有质的改善。
- 推理框架升级:PyTorch自带的eager模式换成
TorchScript或ONNX Runtime,再配合Triton这种专门为推理优化的引擎,延迟能再降一截。 - 缓存:如果业务里大量文本是重复或相似的(比如固定客服话术),用一层Redis缓存能挡掉很多重复计算。
这里有个很关键的实操口径:优化的时候先用量化工具看看瓶颈到底在哪,再用profile定位瓶颈在模型计算、数据预处理还是网络传输,切忌对着一个你认为的瓶颈盲目优化。
6.3 模型漂移:你上线时的效果,不代表一个季度后的效果
这是新手上线模型时最容易忽略的地方。模型在离线测试集上效果不错,上线当天也表现良好,但两三个月后,线上效果肉眼可见地变差。你去看模型代码没变、数据pipeline也没变,那问题出在哪?
大概率是数据漂移。用户的分布永远在变,训练时候的数据分布和线上新进来的数据分布逐渐产生偏差。这种偏差一般分两种:一种是数据漂移(Data Drift),比如特征的取值范围逐渐变化了;另一种是概念漂移(Concept Drift),比如用户对这个功能的认知和使用习惯变了,导致特征和标签之间的关系本身变了。
应对数据漂移,我的策略是“三件套”:
- 测量:对线上的输入特征做分布统计,和训练集的分布做对比。常用的方法是计算PSI(Population Stability Index),或者用Evidently这类开源库直接出报告。
- 告警:把漂移指标接到Prometheus或Grafana上,设置阈值,一旦超过就触发告警。这一步能让问题在影响扩大之前被发现。
- 再训练:建立触发式再训练的pipeline,告警触发后用最新的线上数据重新训练和评估,通过后自动替换线上模型。
这一整套闭环,才是真正意义上的AI工程。模型上线不是终点,而是进入了一个持续运维的新阶段。
7. 一个从0到1的完整案例:把前面所有环节串成一条真实链路
聊了这么多抽象的方法论,我来分享一个从零开始做的真实小项目——多类别文本意图分类系统。规模不大,但覆盖了从数据到部署的完整链路,很适合拿来当参考模板。
7.1 项目目标和数据构建
目标是把用户反馈文本自动划分成几个类别,比如“咨询”、“投诉”、“建议”、“其他”。开源的标注数据不好找,我最后的方案是:收集一批公开的用户反馈样本,自己设计标注规范,标注了3000条文本来做训练和评估。
这个过程的经验是:**标注规范和标注工具要从一开始就设计好,否则返工的成本极高。**我用Label Studio搭了一套内部标注环境,两个人分别标同一批样本,再计算标注一致性(Cohen‘s Kappa)。第一次标出来的Kappa只有0.61,说明标准不清晰,然后改标准、重新校准、继续标注,最后稳定在了0.83,才开始训练模型。
数据预处理阶段,我先用ycdata-profiling做了一遍质量检查,发现不少文本文档有重复内容,需要去重;还有大量全角/半角符号混乱,统一做了归一化。预处理后的数据用DVC做了版本管理,这份标签版本和数据版本都留了备份。
7.2 训练与实验管理:什么模型适合这种任务
考虑到数据量只有3000条,直接上大模型不是一个特别明智的起点。我实际测试了三种方案:TF-IDF + 逻辑回归作为基线、TextCNN、中文BERT微调。
因为前面搭好了实验追踪体系,三组对比非常快。结果也很典型:在小数据量的场景下,TF-IDF加逻辑回归的准确率已经能到0.79,BERT经过微调能到0.87左右,但训练和推理成本高出一个数量级。最后结合业务场景(推理延迟要在50ms以内、标注数据会持续增加),选择的是微调一个distilbert的中文版本,兼顾效果和速度。
这里想特别提醒一个细节:训练集和验证集的划分方式要符合真实使用场景。如果你的数据里有大量相似文本,随机划分会把相似样本同时分到训练集和验证集,导致验证指标虚高。我在这个项目里做了文本相似度去重之后再划分,验证集的指标一下子就真实了不少。
7.3 部署上线与监控
线上服务我用FastAPI包的接口,Docker打包部署,推理的时候做了两条优化:一是把样本攒成batch再送进GPU推理,二是对重复的文本查询加了一层Redis缓存。上线后单次请求P95延迟从140毫秒降到了28毫秒,QPS从个位数提升到接近40。
漂移监控用的Evidently,对线上输入文本的句子长度、关键词分布、类别概率分布做了持续追踪。上线后的第二周,我收到了第一条漂移告警:文本长度分布出现了明显变化,大量短文本涌进来。排查后确认是业务方上线了新的入口,用户反馈形态变了。好在数据采集和再训练pipeline已经打通,用最新数据增量微调了一版模型,重新部署后效果指标恢复到了正常水平。
这整个流程走下来最大的感受是:这个项目的模型部分可能三天就能跑通,但数据管线、实验体系、部署监控这几个环节,才是真正花了八成的精力,也是真正撑起整个项目的东西。
8. 复盘与扩展:做完整条链路之后,我总结的三条核心经验
8.1 经验一:数据在先、评估在中、模型在后
这是我做完这个项目后最深的感悟。很多项目失败的根源只有一个:把太多精力押在了模型选择上,却忽略了一个事实——数据质量决定了模型效果的上限,而评估方法决定了你能不能诚实判断模型的真实水平。
所以我的工作顺序已经固定为:先花精力把数据做扎实,再把评估方案设计好(用什么指标、如何划分、线上如何观测),最后才开始选模型和调参。这个顺序看着简单,但真正做到位,能帮你避开绝大多数的返工。
8.2 经验二:没有一个工具能解决所有问题,串联起来才有力量
很多人问我,做AI工程是不是学会某个工具就够了?我会列这样一个最小工具集让他自己去理解:
- 环境固化:conda + Docker + Poetry
- 数据处理:Python + DuckDB + pandas + Great Expectations
- 实验管理:MLflow + YAML配置 + seed控制
- 部署监控:FastAPI + Docker + Prometheus/Grafana + Evidently
这个清单看起来很长,但每个工具解决的都是一个特定的问题。你不需要一开始就全部用上,可以从环境固化开始,这块成本最低、收益却非常直接;然后加实验管理;再然后处理数据验证。链路是一点一点串起来的,不是一口气搭完的。
8.3 经验三:写文档和写代码一样重要
这不是一句空话。我重新打开自己两周前写的代码,都要花时间才能回忆起当时的思路,更别提没有文档的项目,别人接手得有多痛苦。我现在的习惯是:每个模块的顶部写清楚用途和关键设计取舍,README里记录项目的整体架构和数据流向,每个实验在MLflow上都有完整的描述。
成本很低的事,但绝大多数人都懒得多写两行字。等你真正需要回溯一个三个月前的模型决策时,你就会感谢过去的自己。
如果你现在正好也在做自己的AI项目,我希望这篇长文能帮你少走一些弯路。从零开始搭完一条完整链路,确实比单纯跑通一个教程模型要难十倍,但收获也完全不同——你会真正理解AI工程的每一个环节为什么存在,以及它们如何共同支撑起一个稳定可靠的AI产品。下一步我会在这个项目里尝试加入完整的CI/CD流程和更细粒度的监控告警体系,让它更接近一个可以直接交付的生产级项目。你有在项目里遇到什么卡住的地方,也欢迎在评论区聊聊,说不定你踩过的坑正好也是我下一步要补的课。