1. 先搞清楚:AI工程到底在解决什么问题
在开始写代码之前,我想先把一个概念掰开揉碎讲清楚——AI工程,指的到底是什么?
很多朋友一听说"ai-engineering from scratch",第一反应是"我要从零学会训练神经网络"。这个理解对了一半。AI工程的重点不在AI,而在工程。它要解决的从来不是"怎么把模型精度从92%提到93%",而是"怎么让一个AI系统稳定、高效、可持续地跑在生产环境里"。从零开始,不是从神经网络的反向传播开始学,而是从"怎么把一个模型想法变成一条可靠的生产链路"开始学。
我自己见过太多这样的例子:算法工程师在Notebook里跑通了模型,精度漂亮,demo惊艳,但一提到上线就卡壳——数据管道没人维护、模型文件不知道放哪、线上环境的依赖跟开发环境不一致、推理延迟超标。这些都是AI工程要回答的问题。
那这个内容适合谁?三类人。第一类是想转行做AI工程/MLOps的开发者,需要一张完整的地图;第二类是算法工程师,想补上工程一侧的手艺,不再写"能跑就行"的脚本;第三类是技术负责人,想从零搭一套AI交付体系,但不确定从哪下手。这三类人读这篇文章,应该都能找到对应的切入点。
1.1 从"能跑的代码"到"能用的系统"
我经常打一个比方:在Notebook里训练模型,相当于在厨房里炒一盘菜;搭建AI工程,相当于开一家餐厅。炒菜只需要关心火候和味道,开餐厅要操心的事情就多了——供应链、排风系统、员工排班、食品安全、客户的等待时间、菜单迭代节奏……
对应到AI工程上,就是这五件事:
- 数据层:训练数据从哪里来、如何校验、如何版本化、如何应对线上分布的漂移。
- 实验层:模型训练过程如何复现,参数、代码、数据、环境如何一一对应锁定。
- 模型层:训练好的模型如何评估、如何打包、如何做版本管理。
- 服务层:模型怎么部署成接口,吞吐量、延迟、成本怎么权衡。
- 运维层:上线后怎么监控、怎么告警、模型效果衰减了怎么迭代。
这五层合起来才叫AI工程。任何一个环节脱节,整个系统就会像一条拧起来的链条,总有地方卡住。"从零开始"的价值就在这里——每一层你都亲手搭过、踩过坑,你才知道系统真正脆弱的地方在哪里。
1.2 为什么至少要从零完整走一遍
我见过不少朋友先学MLflow,再学Kubeflow,docker、kubernetes、prometheus挨个啃,学了大半年,还是觉得AI工程是个模糊的概念。原因很简单——工具是分散的,每一件单独学都懂,但串不起来。
所以我强烈建议,至少要从零完整走一个最小闭环:用一个真实的小数据集,从原始数据清洗开始,到特征工程、训练、评估、打包成镜像、部署成接口、写监控、做自动告警,一个环节都不跳。这个过程走完,你再看MLflow的文档,理解就完全不一样了。因为你知道它解决的是哪一环的什么问题。
这就好比学开车。光看仪表盘说明、背交通法规,永远学不会。必须上车,从启动、换挡、踩刹车开始,磕磕绊绊走几趟,你才真正理解每个部件是做什么的。AI工程也是一样,从零走一遍的"笨功夫",是后面提速的基础。
2. 从零搭AI工程需要掌握的四层基本功
既然要from scratch,就得先说清楚基本功的范围线。我的建议是,不用贪多,把下面四层打扎实,AI工程的主干就有了。每层我都附上最低限度的学习重点和判断标准。
2.1 语言与代码基本功
首先是代码能力,这是所有工程能力的根。AI工程的主要语言还是Python,这不完全因为Python写模型方便,更因为它的生态足够厚——数据处理、Web服务、运维脚本,一行Python都能找到成熟方案。需要重点过关的包括:
- 类型标注(type hints)和Pydantic之类的数据校验工具,这部分是代码可维护性的基础。
- 装饰器、上下文管理器、生成器这几个Python高级特性,写数据处理管道的时候会频繁用到。
- 单元测试(pytest)基础,至少能在重构的时候兜底。
我还建议掌握一点Go或Rust的基础,不为了写业务,而是为了将来需要压榨性能时,知道性能瓶颈在哪里、怎么跟底层工具协作。这个不强求,属于加分项。
我见过很多从算法转工程的朋友,最大的绊脚石就是代码组织能力。模型代码几百行全挤在一个脚本里,全局变量满天飞,连参数都写死在文件里。这样的代码,模型精度再高,到了工程阶段都要推倒重来。所以我的建议是,开始学AI工程之前,先老老实实把一个Python项目用清晰的结构重写一遍,模块拆分、依赖注入、异常处理,这些基础不牢,后面所有环节都会很痛苦。
2.2 数据工程基本功
AI工程里绕不开数据。一个残酷的事实是:大部分AI项目的时间消耗不在模型调试,而在数据准备。我自己的比例大概是七成时间在处理数据,三成时间在训练和调优。所以数据工程的基本功不能少:
- SQL是基本功中的基本功。别觉得简单,窗口函数、复杂Join、子查询的优化,这些必须在不看文档的时候也能熟练写出来。
- Pandas/Polars的数据操作。Polars这几年发展很快,处理大数据集内存效率更高,我建议两个都掌握。
- 数据质量校验。写数据管道的人必须有一种"怀疑一切"的心态——空值、重复值、脏数据、类型错乱,每个环节都要校验。这一块后面实操部分我会详细展开。
这里有个容易被忽视但很重要的点:数据版本管理。Git管理代码,DVC(Data Version Control)这类工具管理数据。没有数据版本管理,你就无法回溯"这个模型是用哪份数据训出来的",这在排查线上问题时是致命的。
2.3 机器学习与深度学习基础
AI工程毕竟还是围着模型转,机器学习基础不能缺。最低限度包括:
- 监督学习的核心概念:过拟合、欠拟合、偏差方差权衡、交叉验证、正则化。
- 常用模型的原理与适用范围:线性模型、树模型、深度神经网络。不用会手推反向传播,但要知道每种模型的先验假设和适用场景。
- 评估指标的选择:分类问题看AUC还是F1?回归问题看RMSE还是MAE?排序问题看NDCG还是MAP?指标选错,整个项目方向就错了。
- 深度学习的工程细节:学习率调度、Batch Size的影响、Dropout/BN的作用、损失函数的设计。
这一层的核心目标不是让你发明新模型,而是让你能读懂模型的行为、判断模型的输出是否合理。在工程链路里,你更多是模型的使用者和Debugger,而不是模型的发明者。
2.4 工程化基本功(DevOps与系统设计)
这是从"懂机器学习"到"能构建AI工程"最关键的一层跳跃。很多算法背景的同学对这一层最陌生,但恰恰是AI工程最核心的竞争力。要掌握的东西包括:
- Linux操作与Shell脚本,这是线上环境的基础。
- Docker容器化。必须能把训练环境、推理环境打包成镜像,让开发、测试、生产环境保持一致。
- CI/CD概念与实操,用好GitHub Actions或GitLab CI这类工具,实现代码改动到自动测试、自动构建、自动部署的流水线。
- Kubernetes基础。知道Pod、Service、Deployment这些概念就够了,初期不建议深挖,但要知道它能解决什么。
- 监控体系基础。日志、指标、告警是三个支柱。比如Prometheus采集指标、Grafana做可视化,这是AI工程里监控模型效果的底座。
我在带团队时经常发现一个现象:纯算法背景的同事,对CI/CD这件事天然有抗拒感,觉得"训练脚本为什么要自动化测试、自动部署"。但恰恰是这些看似跟AI无关的工程设施,决定了整个系统的交付质量和效率。没有自动化流水线,每次模型更新都是一次手工搬运模型文件、手工改配置的危险操作,迟早出事故。
2.5 一个实用主义的工具选型建议
工具选型很容易陷入"什么火用什么"的误区。我个人的原则是:选那些社区活跃、文档清晰、不绑定特定云计算厂商的工具。给你一套我自己用下来比较顺手也推荐给初学者的组合:
| 环节 | 推荐工具 | 推荐理由 |
|---|---|---|
| 代码仓库 | GitHub / GitLab | 生态完善,GitHub Actions内置CI |
| 数据版本管理 | DVC | 开源、与Git搭配自然 |
| 实验追踪 | MLflow | 开源主流,追踪参数/指标/模型一条龙 |
| 编排管道 | Airflow / Prefect | 做数据管道和训练管道的调度 |
| 容器化 | Docker | 事实标准,没有悬念 |
| 编排平台 | Docker Compose(起步)+ Kubernetes(后期) | 循序渐进 |
| 模型服务 | FastAPI + MLflow模型服务 或 Triton | 先FastAPI起手,性能瓶颈再上Triton |
| 监控 | Prometheus + Grafana | 观测届的经典组合 |
这套组合的最大优点是:全部开源、社区案例多、遇到问题Google一下基本都有答案。初期不要追求大而全的MLOps平台,从轻量组合开始,理解每一环的原理之后,再逐步引入更重的组件。
3. 核心环节拆解:数据、训练、部署、监控
这一节是正文的正文。我用一个完整项目的口吻,带你看AI工程里每个核心环节,我实际是怎么做的,每一步后面的"为什么"是什么。
3.1 数据工程:AI项目的地基工程
数据环节的坏消息是它不够性感,好消息是只要你把这块做扎实,项目就成功了一半。我在项目中落地数据工程时,会严格走这四步。
第一步:数据探查(EDA)
拿到数据后,我通常不会急着写清洗代码。先把数据往DataFrame里一载,快速过一遍以下几项:
- 维度(行数、列数)、各列数据类型
- 空值比例、缺省值分布
- 数值列的分布(均值、分位数、标准差、最大最小值)
- 类别列的取值频次
- 目标变量的分布(是否需要做样本均衡)
- 时间字段的范围与粒度
这一步看起来很基础,但它的价值在于尽早暴露问题。有一次我在做一个用户行为预测的项目,EDA时发现某个关键字段的统计口径在不同月份发生了变化,如果不发现,后面训练的模型会学到假规律。这就是EDA的意义——花一天的时间,省掉后面一个月返工的代价。
第二步:数据清洗
清洗这一步,我给自己定了几条规矩:
- 空值处理必须基于业务理解来定策略,不能一刀切。用户年龄为空,可能是用户未填写,也可能是系统没采集到,两种情况的处理方式完全不同。前者可以填充默认值,后者可能需要从别的表关联补齐。
- 异常值处理要谨慎。3倍标准差之外的"异常值",不一定是要剔除的脏数据,有可能代表着非常典型的业务场景,比如大客户的超大订单。剔除前必须问一句:这个值有没有可能是真实业务产生的。
- 清洗策略必须可复现、可审计。所有的清洗逻辑应该写成函数,而不是在Notebook里手改单元格。否则训练数据和生产数据可能因为清洗逻辑不同而出现分布偏差,这在业界叫"数据偏差"问题。
我建议把清洗逻辑做成一个独立的Python包,每个清洗函数有对应的单测。这不是小题大做,想想看:如果你的清洗逻辑改了,线上推理时用的是旧逻辑还是新逻辑?如果没管好,模型训练时的输入分布和线上推理时的输入分布不一致,性能会莫名其妙地劣化,排查起来特别痛苦。
第三步:特征工程
特征工程是持续迭代的过程。我的习惯是,先做一套能跑通端到端链路的"瘦身版"特征,让模型和部署流程先转起来,后续再迭代特征。这样做的好处很明显:你能在早期就发现整条链路的瓶颈在哪里,而不是在特征工程堆了几天后,发现部署环节根本不支持这些特征。
特征工程里有一个工程侧的坑:特征的线上逻辑和离线逻辑必须保持一致。比如"用户最近7天下单总金额"这个特征,离线看是5块,线上用同一个公式跑出来却是3块,这种"训练/推理特征不一致"是AI工程里最隐蔽也最致命的问题之一。解决办法是,把特征计算逻辑封装成唯一一份代码,离线在线共用,而不是各写一套。
第四步:数据与管道验证
最后一个环节是经常被忽略的验证。在数据管道构建完成后,我会设置一套"数据质量门禁":
- 每天/每次管道运行后,自动检查关键字段的空值率、分布漂移指标(比如PSI)。
- 一旦超过阈值,立刻告警,而不是等模型上线后效果变差了才回头查数据。
- 在模型中记录并持续追踪"训练数据分布 vs 线上数据分布"的差距。
不要以为这些都是大厂才需要的。哪怕你的项目很小,长期维护一个AI系统的过程无非就是处理各种数据分布的变化,数据质量门禁是刚需。
3.2 训练管线:让实验可复现、可追溯
很多人理解的训练就是跑个脚本,但这个环节一旦上升到工程层面,核心目标就变成三个词:可复现、可比较、可恢复。
可复现,意味着任何一次实验结果,都应该能回溯到:用了哪份数据(版本)、哪些代码(Git提交)、哪些超参数(配置)、什么环境(镜像)。缺少任何一项,实验结果都无法追溯。为了让可复现真正落地,我通常这么做:
- 把超参数写进配置文件(YAML),而不是写死在代码里。
- 用MLflow记录每次运行的参数、代码版本、数据版本、指标、模型产物。
- 把运行环境固化成Docker镜像,镜像tag对应到代码版本。
可比较,意味着每一次实验都记录统一的评估指标算分。同一个任务,不同模型、不同超参数组产生的结果要能在同一个维度上对比。我自己的习惯是,在MLflow里设一套统一的评估组件,对每个模型运行同一套逻辑,产出同一组指标。这样表格拉出来,哪个模型更好一目了然。这比在微信上发来发去Excel文件高效一百倍。
可恢复,指的是训练失败时能从断点继续,而不是从头再来。深度学习训练动辄几小时到几天,中途断了从头跑非常浪费时间。PyTorch的torch.save/torch.load加一个断点恢复机制,或者直接用PyTorch Lightning的ModelCheckpoint,都是成熟的做法。
关于训练本身,我再说几个实操细节:
- 随机种子要固定。模型初始化的随机性、数据随机打乱的随机性,都要设置种子,否则同一份代码跑两遍,结果不一样,你都不知道该信哪个。
- 混合精度训练(AMP)是标配。现代GPU都有Tensor Core,用FP16训练,速度翻倍,显存占用减半。只要不是对精度极度敏感的场景,建议无脑开。
- 梯度累积是模拟更大Batch Size的小技巧。显存不够用的时候,把多个Batch的梯度攒够一起更新,效果接近大Batch训练。
- 早停法(Early Stopping),别让训练无止境地跑。监控验证集指标,连续若干epoch没有提升就停掉。
我看到不少团队只训练个模型,就把训练脚本扔在那边,训练过程完全没有记录。这等于在黑暗里走夜路,模型效果好了都不知道怎么好的,模型效果差了也不知道差在哪里。这一点如果你记不住其他内容,也请一定把这一条记住。
3.3 模型部署:从模型文件到稳定服务
模型训练完,只是万里长征走了一半。真正的挑战在于:怎么让这个模型稳定地对外提供预测服务。
部署方式的选择。初期我建议先用最直接的方式:用FastAPI把模型包一层HTTP服务。这个方案简单、直观、代码量少,适合业务量不大、延迟要求不苛刻的场景。之后如果并发量上来了,可以切换到Triton Inference Server,它支持动态批处理(Dynamic Batching)、多模型管理、GPU显存优化,性能提升显著。
模型打包的两种形态要分清楚:
- 训练态:PyTorch的.pt文件、TensorFlow的.pb文件等,只适合在训练环境里加载。
- 服务态:标准化的、自包含的、带有输入输出Schema的模型格式,比如ONNX、TorchScript。
服务态模型的好处是:不依赖训练框架的版本、优化了推理性能、可以在线做模型版本切换。我见过太多线上事故,都是因为生产环境重新装了个训练框架,版本跟训练时不匹配,模型加载直接报错。所以我的建议是:从第一天起就规划模型导出。
部署场景的输入输出要设计好。我见过一个很典型的错误:模型输入是图片,训练时用的是预处理后的张量,部署时直接传原始图片字节,结果报错半天找不到原因。这里的关键是——输入输出Schema必须在模型打包时锁定。我建议用Protobuf或JSON Schema定义好请求和响应的结构,同时把预处理逻辑也包含在模型服务里,或者至少提供与训练时完全一致的预处理代码版本。训练和推理环境对输入数据的处理方式不一致,这是部署环节最大的坑,没有之一。
模型服务上线前,压测必须做。很多团队部署完模型,小流量跑通了就宣告上线。但线上真实流量一上来,各种问题就都暴露了:内存泄漏、GPU显存溢出、超时重试风暴。我通常的压测流程是:先确定SLO(比如P99延迟小于100ms,成功率99.9%以上),然后用Locust或WRK做压测,观察延迟、吞吐量、内存、显存四项指标,找到系统的真实容量上限,再据此配置自动扩缩容。
3.4 监控与迭代:模型上线只是开始
上线之后,AI工程的工作还有一半。模型不是说训完就一劳永逸了,真实世界的数据分布永远在变。我经常说的是:模型效果衰减,不是意外,是常态。
所以监控体系必须从第一天就搭建。我一般分两个层面做监控:
系统层监控:服务器资源、容器状态、API延迟、错误率。这层用Prometheus加Grafana就能解决,任何一个做后端工程的人都熟悉这套。关键是告警要配置好——不是所有指标都要告警,盯住P99延迟、错误率、GPU利用率这几个核心指标即可。
模型层监控:这个层面更有AI工程特色。核心包括三个指标:
- 输入分布漂移:线上请求的输入特征分布,跟训练集分布做对比,用PSI或KL散度衡量。分布漂移大了,说明线上环境变了,模型可能要重新训练。
- 预测分布漂移:模型预测结果的分布是否发生偏移。比如一个风控模型,线上预测的风险评分整体上抬,这可能意味着客群变了,或者出现了新的风险模式。
- 反馈延迟问题(Label Feedback):很多模型的真实标签需要等一段时间才能拿到(比如"用户是否下单"要等几天才知道)。这个反馈链路的时间窗口,决定了模型迭代的节奏。
做模型监控时有一个很扎心的点:很多情况下你并没有"真实标签"来直接判断模型是不是变差了,你只能靠代理指标(Proxy Metric)来间接观测。比如内容推荐模型,你没有即时反馈告诉你"这个推荐是不是真的好",只能通过用户的次留、点击率等代理指标来推断。所以,设计监控指标时必须想清楚:这个指标到底在反映什么,它在什么情况下可能会误导你。
关于迭代,我遵循一个简单原则:先定位问题源头,再动手改模型。模型效果下降,先去看是数据分布变了,还是上游数据处理逻辑变了,还是某个特征源挂了。很多时候问题根本不在模型本身。把排查顺序定下来,能节省大量无效训练的时间。
4. 实操:从零构建一个完整的AI工程示例
这一节,我拿一个非常落地的场景完整走一遍链路。场景:电商平台商品评论的情感倾向分类。数据用公开的评论数据集(比如IMDB电影评论,或者直接自己爬点商品评论),模型用一个轻量的预训练语言模型做微调。目标不是做出最好的模型,而是带你走完一整条AI工程闭环。
4.1 项目初始化与数据准备
我建议所有AI工程都先建立好目录结构。下面是一份我常用的结构,参照它来组织你的项目:
. ├── configs/ # 配置文件(YAML),模型参数、数据配置 ├── data/ # 数据文件(用DVC管理,不进Git) │ ├── raw/ # 原始数据 │ ├── processed/ # 清洗后的数据 │ └── features/ # 特征数据 ├── src/ # 源代码包 │ ├── data/ # 数据加载、清洗、特征工程 │ ├── models/ # 模型定义、训练逻辑 │ ├── evaluate/ # 评估指标与评估流程 │ └── serve/ # 推理服务代码 ├── tests/ # 单元测试与集成测试 ├── scripts/ # 一次性脚本与工具脚本 ├── Dockerfile # 镜像构建定义 ├── docker-compose.yml # 本地一键起服务 └── README.md目录不用照抄,但思路值得借鉴:代码按职责分离,配置与代码分离,数据与代码分离。这套结构的好处是,无论项目规模怎么增长,新人都能在十分钟内找到自己要改的文件。
数据准备环节,我做三件事:
- 把原始数据放到
data/raw/,用DVC做版本管理。 - 写清洗脚本,把评论文本去重、去空白、统一大小写、按需要做简单分词。清洗脚本的每个函数都配单测。
- 做一次快速EDA,检查标签分布、文本长度分布,把结果存成报告。如果标签非常不均衡(比如好评占90%),要提前决定是不是要做采样或者修改评估方式。
4.2 训练实验与模型追踪
这个项目的模型我用一个轻量级的预训练模型,比如distilbert,在消费级显卡上也能微调。训练代码的结构建议是:
- 配置文件(configs/train.yaml):
model: name: distilbert-base-uncased max_length: 512 data: batch_size: 16 train_file: data/processed/train.csv valid_file: data/processed/valid.csv training: epochs: 5 learning_rate: 2e-5 seed: 42 mixed_precision: true gradient_accumulation_steps: 4所有超参数都进配置文件,不写死在代码里。
- MLflow追踪,在训练脚本里,我就这么接入:
import mlflow with mlflow.start_run(): # 记录参数 mlflow.log_params(config["training"]) # 训练逻辑 ... # 记录指标 mlflow.log_metric("val_accuracy", val_acc) mlflow.log_metric("val_f1", val_f1) # 保存模型 mlflow.pytorch.log_model(model, "distilbert_sentiment")- 评估。情感分类这种二分类任务,我至少看准确率、F1、AUC三个指标。如果标签不均衡,F1是主要参考。每次实验跑完,MLflow会自动把所有实验结果汇总在同一个UI里,方便横向比较。
这里我想强调一个细节:训练之前先跑一个过拟合小实验。取几十条训练样本,把模型训几个epoch,看能不能做到接近满分。这一步是验证代码链路通不通的最快方式。如果这么点数据都过拟合成不了,那一定是训练逻辑、数据加载、模型定义中哪个环节有bug。先排除bug再上全量数据,能省下大量无效等待时间。
4.3 模型服务化与Docker部署
训练好的模型,我通常导出为ONNX格式,这样最终的推理过程可以脱离PyTorch训练框架,部署更轻量,速度也更快。
import torch from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained("distilbert_sentiment") dummy_input = torch.randint(0, 30522, (1, 512)) # token ids 模拟输入 torch.onnx.export( model, dummy_input, "model.onnx", input_names=["input_ids", "attention_mask"], dynamic_axes={"input_ids": {0: "batch_size"}, "attention_mask": {0: "batch_size"}}, )导出之后,推理服务我用FastAPI来写。简化起见,核心就是这样一个结构:
from fastapi import FastAPI import onnxruntime as ort app = FastAPI() ort_session = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"]) @app.post("/predict") def predict(text: str): inputs = tokenize(text) logits = ort_session.run(None, inputs)[0] label = "positive" if logits[0][1] > logits[0][0] else "negative" return {"label": label, "confidence": float(...)}写完之后,最重要的就是Docker化,保证所有环境依赖与训练阶段一致。Dockerfile大致是这个样子:
FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]然后docker build -t sentiment-service . && docker run -p 8000:8000 sentiment-service,服务起来后,用一行curl验证:
curl -X POST http://localhost:8000/predict -H "Content-Type: application/json" -d '{"text": "This product is amazing!"}'接着我强烈建议补上一个健康检查端点(/healthz),Kubernetes或Docker Compose的健康检查都靠它来判定服务是否正常运行。返回的不仅是一个纯状态200,我还会顺带把模型是否加载成功、显存剩余情况一起返回,这样排障时能少打好几个命令。
4.4 CI/CD与自动化流水线
到这步,一个"能跑"的AI服务已经有了。但我们要的是"能长期维护、可自动交付"的AI工程,所以要上CI/CD。
我用GitHub Actions做自动化的三件事:
- 代码检查与单元测试:每次push代码,自动跑
pytest。 - 构建镜像与推送:打上Git commit hash作为镜像tag,推送到容器仓库。
- 部署:如果commit打上了特定tag,自动把新镜像部署到测试环境;测试环境验证通过后,再手动触发部署到生产。
流水线文件大致长这样:
name: ci on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 - run: pip install -r requirements.txt - run: pytest build: needs: test runs-on: ubuntu-latest steps: - ... - run: docker build -t my-registry/sentiment:${{ github.sha }} - run: docker push my-registry/sentiment:${{ github.sha }}盖上流水线以后,模型更新的发布流程从"手工复制模型文件、手工改服务器配置"变成了"合并代码、打tag、自动部署"。有一次线上模型效果衰减,我们要紧急回滚到旧版本,因为有这个流水线,一条命令就完成了镜像回滚。这种时刻才真正体会到工程化的价值。
4.5 灰度发布与模型监控落地
生产环境我从来不做模型的全量一刀切替换。标准做法是灰度发布:新模型先接5%的流量,观察一段时间,对比新旧两版模型的业务指标与系统指标。没问题再逐步加大流量,直到全量。
在代码层面,我通常在推理服务里加一个简单的流量路由逻辑:按照请求ID的hash值,决定命中哪个模型版本。初期可以做得简朴一点,不用引入复杂的Feature Store或在线推理架构。关键是养成"永不全量直接切"的习惯。
监控方面,我在这个项目里落地了三个组件:
- Prometheus负责采集指标:请求量、P99延迟、错误率。
- Grafana做可视化面板:API延迟曲线、模型预测结果分布。
- 自研的分布漂移检测:每天定时把当天请求的输入特征做一次统计,与训练集分布做对比,计算PSI。PSI一旦超过0.2就触发告警。
这一套监控体系,让我在一个小项目上也能做到"模型效果变差之前先发现异常信号"。有一次我们监测到某个商品的评论长度分布突然异常,一查发现是上游文本采集接口改了参数导致空文本比例增加。如果不做这些监控,这种问题可能要等几个星期后用户投诉才暴露。
5. 常见问题与排查技巧实录
最后这部分,我把自己亲手踩过的坑和帮别人排过的雷整理成一份速查表,按场景分类。这些问题的共性是:初次搭建AI工程时极其常见,但官方文档通常不会写。
5.1 环境依赖的坑
| 现象 | 根因 | 解法 |
|---|---|---|
| 本地能跑,容器里加载模型失败 | 镜像里的依赖版本与训练环境不一致 | 用pip freeze锁定全量版本并写入requirements.txt;或基于训练环境导出的镜像做推理镜像 |
| CUDA/GPU不可用,但明明装了NVIDIA驱动 | Docker镜像缺少cuda运行时或没有配置--gpus all | 在Dockerfile里基于nvidia/cuda基础镜像构建;运行时加--gpus all或用NVIDIA Container Toolkit |
| Python原生依赖冲突,装这个包坏了那个包 | 环境被污染 | 坚决使用虚拟环境(venv/conda);锁版本;每换环境就重新pip install -r |
依赖问题是AI工程里最大的隐形杀手,没有之一。最容易踩的是"window上能跑,Linux上跑不了"这种环境差异。我特别建议大家尽早放弃Windows当生产环境,开发机可以用Windows,但Docker容器和训练服务器统一Linux,能少掉一半莫名奇妙的bug。
5.2 模型复现性的坑
| 现象 | 根因 | 解法 |
|---|---|---|
| 同一份代码两次训练,指标不一样 | 随机种子没固定,或GPU运算不确定性 | 设置torch/numpy/random的种子;固定DataLoader的worker种子;接受GPU微小非确定性,但保证最佳结果可复现 |
| MLflow里看不到之前的实验记录 | 实验追踪服务没启动,或模型日志目录不一致 | 统一MLflow的tracking_uri;养成每次训练前明确experiment_name的习惯 |
| 训练完的模型换台机器推理效果明显变差 | 预处理逻辑没有跟着模型一起打包 | 预处理代码和tokenizer配置一并冻结进服务镜像;或使用ONNX这类自带Input Schema的格式 |
复现性问题的排查思路其实很简单——逐层锁定变量。数据是变量吗?代码是变量吗?环境是变量吗?把三个变量逐步固定下来,复现一定可以做到。怕的是每次都稀里糊涂地改好几个变量一起跑,那出了问题根本无法定位。
5.3 部署性能的坑
| 现象 | 根因 | 解法 |
|---|---|---|
| 接口P99延迟很高,但CPU利用率不高 | 没有开启批处理,或预处理环节太慢 | 用Triton等支持Dynamic Batching的框架;预处理逻辑里把Tokenizer批量化,不要一条条做 |
| GPU显存OOM,服务直接崩溃 | 并发请求过多,或单个请求输入过长 | 做长度截断;在模型服务外加队列与限流;设置显存预留上限并在OOM前返回错误而非崩溃 |
| 服务莫名超时,但日志没有异常 | 线程/协程阻塞,或依赖的存储超时导致整个请求链路卡死 | 给所有外部调用设置超时时间;用异步方式组织I/O;在网关层设置请求超时兜底 |
| 模型明明效果好,线上表现差得离谱 | 线上输入分布与训练时分布不一致 | 加分布监控;在预处理处做输入合法性校验;如果是漂移,优先做定向数据采集与补充训练 |
我印象最深的一次线上事故,是模型服务在高峰期P99延迟从100ms飙升到5秒。查了半天发现不是模型推理变慢了,而是服务里一个负责加载停用词表的模块每次都去读远程文件系统,远程盘抖动直接拖垮了整个链路。最后把词表加载到本地缓存,问题秒解。这个案例想说明的是:AI工程里的性能问题,很多时候出在那些看起来跟"AI"毫无关系的工程细节上。
6. 最后一个建议:从小闭环开始
写到这里,该说的主干都已经说透了。从数据工程、训练追踪、模型部署、CI/CD到监控告警,每一步单独拿出来都能写一篇长文,但把一个最小闭环完整走通,比把某一个环节钻研得特别深更重要。
我个人体会最深的一点是:AI工程的能力不是"学"出来的,是"趟"出来的。你亲手把一个模型放到容器里、亲手配置Prometheus告警、亲手回滚一次糟糕的发布,这些经验的价值远超过读十篇架构文档。从零开始的含义,就是别绕过那些看似麻烦的步骤——恰恰是那些步骤让你真正理解系统。
最后分享一个小技巧:给自己设定"最小目标"——两周内,把一个你自己选的小模型走通完整的工程链路,哪怕数据只有几千条、服务只用Docker Compose在本机跑。这两周里你会遇到各种报错,但每一个报错,都是你从零搭建AI工程能力的一块砖。这条路走完,你再看任何MLOps平台、AI工程框架,思路都会完全打开。