“AI Engineering from Scratch”,这个标题看着就很硬核。它不是那种教你调个 API、套个 LangChain 模板的速成课,而是把 AI 应用的工程底座一块块拆给你看。最近“ai-engineering”相关的讨论越来越多,很多同学从模型 API 调用入手,最后都卡在工程化落地上:推理性能怎么优化、数据管道怎么搭、评估怎么做、上线之后怎么监控。如果你正处在这个阶段,或者想从一开始就建立完整的 AI 工程认知,这篇文章的内容应该对你有用。我会从全局设计、核心模块拆解、一条可落地的实操路径到常见问题排障,完整走一遍。
1. 全局设计:先搞清楚“从零开始”到底要搭什么
很多人在看到“from scratch”的时候,第一反应是“我要从线性代数开始推导 Transformer”,或者“我要自己写一个深度学习框架”。这种理解不能说错,但太狭隘了。在真实的工作环境中,“from scratch”的含义更接近:在不依赖全套托管服务的前提下,自己动手搭建一个能跑、能维护、能迭代的 AI 应用系统级工程。
也就是说,这个“零”的起点不是“不懂数学”,而是“没有现成的 AI 基础设施”。要解决的问题是:当你没有现成的 ML 平台、没有现成的特征存储、没有现成的模型服务框架时,你该如何用最基础的工具,组合出一个生产可用的 AI 系统。
1.1 核心需求解析:不只是训练模型,而是交付系统
我们把标题拆开看,AI Engineering 包含两大层面:
- AI 层面:涉及模型选型、数据处理、训练(或微调)、评估这些和算法强相关的环节。
- Engineering 层面:涉及系统架构、服务部署、性能优化、监控告警、CI/CD、成本控制这些和软件工程强相关的环节。
很多人以为 AI 工程化就是“训练一个模型然后封装成 API”,但实际落地时你会发现,训练只占整个工程量的一小部分。我见过太多团队花两个月调模型,最后上线时才发现推理延迟压不下来、数据分布一变模型就崩、监控面板上全是报警却不知道先处理哪个。这些问题都不是“模型”层面的问题,而是“工程”层面的问题。
1.2 设计原则:以终为始,反推架构
我在自己动手搭建这套系统时,遵循了一个核心原则:从“上线形态”反推技术选型。
先问自己几个问题:
- 这个系统的调用方式是什么?在线 API、离线批量,还是嵌入到现有服务里?
- 预期的 QPS(每秒请求数)和延迟要求是多少?
- 数据更新频率是小时级、天级还是实时?
- 团队里有几个人维护?大家的技能栈是什么?
这四个问题的答案直接决定了架构的复杂度。比如你现在只是个人项目或小团队,就别一开始就上 Kubernetes,一个 Docker Compose 就能解决很多问题。但如果你知道半年后要支持多租户高并发,那服务层的设计从一开始就要考虑水平扩展。
这种“以终为始”的思路贯穿整个 from scratch 过程。它帮你避免两个极端:一个是堆砌大而全的组件导致过度设计,另一个是图省事全部用 Notepad 写脚本导致后期完全没法维护。
2. 核心模块拆解:一个最小可用 AI 系统的完整构成
一个可投入生产的 AI 工程系统,从上到下大致包含:数据层、训练层、模型服务层、评估与监控层、运维层。每一层都有自己独立的技术栈和坑点。
2.1 数据层:特征管道与数据版本管理的底层逻辑
数据处理往往是被新手最轻视的环节。很多人用 Pandas 处理完一份 CSV 就开始训练,完全不考虑数据质量、特征一致性和版本回溯问题。但真实生产环境里,数据和模型是同步演进的,如果数据和模型没有做版本绑定,三个月后你连“这个模型当时是用什么数据训出来的”都无法复现。
从工程角度看,数据层必须解决:数据获取(批量导入还是实时流)、数据校验(格式、分布、缺失率)、特征加工(标准化、归一化、编码)、数据版本管理。
我的建议从一开始就引入你的数据版本控制工具。它的思路和 Git 类似,但针对的是数据集和特征文件。每一次训练任务都记录下用到的数据版本,这样模型文件和数据集就建立了关联关系,出问题时能精确回溯。
特征管道我推荐用标准化流程加工作流调度工具。很多团队觉得 Airflow 太重,确实它适合那种有复杂依赖的周期性任务。如果你只是每天定时拉数据、清洗、落库,直接用一个简单的 Python 脚本配合调度器调度就够了。关键不在于工具多强大,而在于任务有明确的状态管理、日志输出和失败重试。
2.2 模型服务层的技术选型与性能优化
模型训练完成后,如何把这个模型变成可以被外部系统调用的服务,是整个工程链路里最影响体验的一环。
先说选型。如果你训练的是深度学习模型(PyTorch 生态),目前最常见的服务化方案就是用专门的生产级推理服务器把模型导出为对应格式,然后加载并提供接口。如果你用的是机器学习库(Sklearn / XGBoost),可以直接用工具对模型先做序列化保存,再用网络框架包一层 HTTP 接口。
我在初版架构时踩过一个坑:模型文件用默认方式保存后,直接加载到服务进程里做推理。结果请求一多,GIL 锁导致 CPU 无法多线程并行,性能完全发挥不出来。后来我改用独立推理服务进程,才把 CPU 利用率和吞吐拉上去。
推理性能优化有几个关键手段,按优先级排序:
- 模型量化:把 FP32 权重压缩到 FP16 或 INT8,推理速度能提升 2 到 4 倍,显存占用也大幅降低。
- 批处理:把并发的请求攒到一个小队列里,凑够一批再跑一次前向计算。对于 GPU 推理尤其有效,吞吐能提升一个量级。
- 缓存:对相同输入直接返回缓存结果。这个在 LLM 应用里尤其常见,因为很多 Prompt 场景下的问题本质上是重复的。
2.3 评估与监控:没有度量就没有优化
模型上线后的评估逻辑和离线训练时是完全不同的。离线评估你可以慢慢算准确率、召回率,但线上系统需要的是:实时监控数据漂移、推理延迟、异常输入占比,并且能自动或半自动地触发模型重训。
评估体系我建议分层设计:
- 模型质量层:算指标,例如准确率、均方误差、F1 值,对应的是“模型本身好不好用”。
- 系统性能层:算延迟百分位(P50/P95/P99)、吞吐量、错误率,对应的是“服务稳不稳定”。
- 业务指标层:算点击率、转化率、留存率,对应的是“AI 到底给业务创造了什么价值”。
这三个层级的监控缺一不可。只做第一层,模型调得再好线上也可能表现不佳;只做第三层,出了故障你也无法定位是模型问题还是系统问题。
监控落地的技术栈,我推荐从最朴素的方案起步:服务日志统一收集到集中式日志系统里,配合现成的仪表盘工具做可视化。先别急着上复杂的 APM 工具,把日志打全、打好,比什么监控工具都强。
3. 实操路径:从零到一搭建一个可运行的 AI 推理服务
这一节我直接给出一条经过验证的从零到一实操路径。我会以一个文本分类服务的搭建为例,完整展示从数据处理到服务上线的过程,并给出可复制的设计思路。
3.1 第一周:搭数据管道,固定数据版本
我习惯先搭数据管道,而不是先写模型代码。因为模型训练可以反复迭代,但数据管道一旦铺下去,后面所有环节都会依赖它。
首先是原始数据收集。用一个脚本从数据源拉取原始文件,落到一个原始数据目录里,按日期归档(例如data/raw/20240101/)。这一步的作用是保留最原始的信息,为后续的数据清洗留退路。
然后是清洗与加工。写一个独立的 Python 脚本,针对这份数据处理以下事项:缺失值填充、标签编码、文本截断、训练集和测试集划分。脚本的输出在数据集文件夹下,生成全新的数据集文件。保存时注意带上版本号,例如train_v001.parquet。
为什么要用 Parquet 而不是 CSV?因为 CSV 没有 Schema 概念,几千列的时候读写效率很差,而且大文件加载很占内存。Parquet 是列式存储,读取指定列会快很多,而且自带压缩,磁盘占用也更小。这个选择在数据量大了之后差距非常明显。
最后,初始化数据版本管理工具,把原始数据和加工后的数据都纳入版本管理。记录之后,你就具备了回溯能力。
3.2 第二周:模型训练与实验记录
数据处理完了就进入到模型训练阶段。平时做实验时,很多人都有一个坏习惯:模型参数、训练日志、指标结果全部散落在不同的文件夹里,过两周就忘了哪个文件对应哪个结果。要解决这个问题,我建议引入实验跟踪工具(如 MLflow、Weights & Biases 等,任选其一即可)。
不用把所有实验都记录得特别详细,但至少要固定这样几项:数据版本、代码版本、超参数、最终指标、模型产物地址。
这五个信息完整记录后,你的每次训练就有了“指纹”,任何一次实验结果都能追溯到完整的上下文。即使你只是一个人维护这套系统,这个习惯也能让你在三个月后快速定位到“当时那个最好的模型是哪个”。
训练代码本身我会固定成一个标准的模式:读取数据、预处理、定义模型、交叉验证、评估、保存模型产物。不要在一个 Notebook 里跑完整流程,因为 Notebook 天然难以复用和维护。
3.3 第三周:服务化封装与性能压测
模型训完后,开始把它封装成一个 API 服务。
我做服务化的标准结构是四层:
- 接口层:负责接收 HTTP 请求、参数校验、返回格式化。
- 业务逻辑层:负责调用模型进行预测,以及一些前后处理逻辑。
- 模型加载层:负责加载模型权重、初始化推理引擎。
- 基础设施层:负责日志、监控指标暴露、健康检查(即对外提供探活接口)。
如果用 Python 生态,最简单的方案就是用一些轻量级 Web 框架写一个应用文件,把模型加载写到全局初始化阶段,然后对外暴露一个预测接口和一个健康检查接口。
为什么模型加载必须放全局初始化?因为模型加载是一个 IO 密集且耗时的操作。如果每个请求进来都重新加载一次模型,服务基本没法用。全局初始化只在进程启动时执行一次,之后所有请求都复用已经驻留内存的模型实例。
服务写完之后,立刻做压测。很多人上线前不压测,上线后一看 P99 延迟 5 秒才发现问题。我在压测时常用的工具是并发测试工具,先以 1 并发起步,逐渐增加压力,记录不同并发下的延迟分布和错误率。压测的重点不是看最大吞吐,而是看瓶颈出现的拐点——在那个拐点之前的负载区间才是你能安全承诺的容量范围。
如果压测发现性能不足,优先检查:模型推理有没有做批处理?有没有做量化?服务启动了几个 Worker?数据库有没有成为瓶颈?
3.4 第四周:监控告警与迭代闭环
服务稳定跑起来之后,立刻补监控和告警。我个人经验是:没有告警的服务等同于裸奔。
监控部署至少包含三条线:
- 服务日志:记录每天全量请求和响应,存留足够周期。
- 业务指标日志:把预测结果分布、置信度分布记下来,用于识别线上数据漂移。
- 系统指标:CPU、内存、GPU 利用率、请求耗时、错误码。
这些指标记录好之后,在可视化面板里配置几个核心面板:请求量趋势、延迟趋势、错误率趋势、资源使用率。
告警规则我建议从最简单的开始:延迟超过阈值、错误率超过百分之一、突然零请求。先保证“出事知道”,再慢慢优化“怎么提前发现出事”。
迭代闭环的核心是:定期从线上日志里抽取新增样本,沉淀为新训练数据,重新训练后灰度上线。这个闭环一旦转起来,模型才真正开始“越用越准”。
4. 遇到的几个典型故障与排查思路
这条路走下来,踩过的坑远比顺利的时刻多。这里挑几个典型的故障复盘一下,希望能帮你少走弯路。
4.1 推理延迟高居不下,问题出在数据预处理
一次模型服务压测时,我注意到单次推理延迟只有 20 毫秒,但接口整体 P95 延迟却高达 800 毫秒。一开始怀疑是网络框架的问题,排查很久才发现瓶颈出在预处理函数里:文本转 ID 的操作循环里包含冗余的对象创建和重复计算,每次请求都要执行一遍。
排查方式其实很直接:在代码里分阶段打点计时,把“预处理耗时”“模型推理耗时”“后处理耗时”分别记录下来,一看就明白时间花在哪了。
从那之后,我把所有不依赖输入数据的预处理步骤全部抽到启动阶段完成,并把依赖输入的部分向量化重写。优化后整体 P99 延迟降到了 100 毫秒以内。
4.2 线上模型效果变差,不是模型退化了,是数据变了
跑了一段时间后,业务方反馈模型准确率明显下降。检查模型文件没变,代码也没改动,但效果就是变差了。后来对比了线上预测结果分布和训练集的标注分布,发现输入数据的文本风格发生了明显的偏移,导致模型输出置信度整体偏低。
这就触发了我前面提到的业务指标监控的价值:只盯着系统性能指标是不够的,业务指标(预测分布)的漂移才是模型失效的前兆。解决办法是收集最近一段时间的线上数据,补充标注后增量训练,重新发布。
4.3 缓存失效风暴:加缓存后出现大面积错误
为了降低重复请求的延迟,我引入了一个简单的内存缓存模块,结果上线后反而出现大量超时错误。查了半天发现是缓存并发写的问题:高并发场景下多个线程同时写入同一个 Key 的缓存,导致数据竞争,部分线程拿到半初始化状态的数据。
这个问题的教训是:任何中间件级的优化,都要先想想并发安全。后来我换成了线程安全的缓存方案,并且增加了缓存击穿保护(单飞模式,即同一时间只有一个请求去查源数据,其他请求等待结果),问题才彻底解决。
4.4 新手上路最容易忽略的五个问题
结合我自己带人的经验,给刚入门的同学五个提醒:
- 固定随机种子:不固定随机种子,你的实验结果就永远无法复现,也就谈不上迭代了。数据处理、模型初始化、训练过程都要固定。
- 统一 Python 环境:用虚拟环境固定依赖版本,不然今天能跑明天崩,多半是某个依赖被升级了。
- 日志不要只打一句“成功”:要打清楚“哪个环节成功、耗时多久、输入输出的关键信息是什么”才能定位问题。日志就是服务的黑匣子,越详细越好。
- 别急着追求并行:先确认单线程逻辑正确,再去做多线程多进程。并行引入了复杂度,先把复杂度本身搞定再说。
- 模型文件和代码要一起走版本管理:模型和代码脱节会让生产环境完全不可复现。在机器学习平台类工具出现之前,我们用的办法是在代码仓库的 Release 里附带模型文件的哈希值,靠这个把代码和模型绑定在一起。
5. 从“能跑”到“好用”的关键横跳
前四周的搭建能让你有一个“能跑”的系统,但要达到“好用”的程度,还有一些工程习惯上的关键转变。
5.1 从手动运行到自动化流水线
最初你可能习惯手动跑脚本——手动处理数据、手动跑训练、手动启动服务。这在 Demo 阶段没什么问题,但一旦进入长期迭代,就必须把这些操作全部自动化。
用工作流调度工具把“数据更新 -> 模型训练 -> 评估 -> 部署”串成一条流水线。任何一个环节失败时,流水线自动重试或告警,而不是依赖人工盯着屏幕。
这一步是我认为 from scratch 过程中价值最高的一步。自动化的意义不只是省人力,它让整个系统的迭代节奏从“有灵感才更新”变成“按节奏持续进化”。
5.2 从无测试到关键链路测试
AI 系统的测试不像传统软件那样容易覆盖。但至少有几个关键链路必须测试:数据处理函数的输入输出是否符合预期、模型推理服务的接口是否正确、模型在新数据上的表现是否触发下降预警。
我在项目里会在代码提交前跑一个轻量级的冒烟测试(Smoke Test),确保最基本的流程没有中断。更进一步的话可以引入数据质量测试,每次数据管道更新时自动计算数据分布指标,和上一个版本对比,如果偏移超过阈值就阻断后续流程。
这个机制能极大避免“数据早就导错了,跑到训练完才发现”的尴尬局面。越早发现问题,修复成本越低。
5.3 从独享到共享的模型仓库
项目推进到多模型并存阶段时,一定要建立统一的模型仓库。所有模型产物统一命名规则、统一打包格式、统一上传到同一个存储位置,并附带元信息(训练时间、数据版本、指标、作者)。
这个习惯初期看不出效果,但在模型超过五个之后,你会发现“找模型”“对比模型”“回滚版本”这些操作变得异常顺手。这其实就是一个轻量级的模型管理方案,远期再平滑过渡到正式模型管理平台也不难。
6. 后续可以怎么继续扩展这套系统
这套系统稳定运行之后,还有很多方向可以继续演进。
一个方向是从分析型模型走向生成式模型。当你开始接入大语言模型时,前面搭的这套工程底座基本可以平移过去。你需要额外补充的是:Prompt 版本管理、上下文管理、答案评估(大模型打分或者人工反馈闭环)以及成本控制(Token 用量追踪)。
另一个方向是引入更高级的部署形态。从 Docker Compose 过渡到容器集群管理平台,再到服务网格,每一步的动机都应该来自真实的瓶颈,而不是追逐技术潮流。我在生产环境里见过太多过度工程化的案例——一个日请求量只有几千的服务,搞了三套中间件和四套监控系统。这种复杂度只会拖垮迭代速度。
还有一块容易被忽视的是安全加固。模型服务上线公网后,接口鉴权、限流、敏感信息过滤这些都得补上。尤其是模型可能输出敏感内容,必须要有内容审核机制兜底。这部分能力虽然是后加的,但应该在架构设计时预留位置,不然等到需要时再接会非常痛苦。
最后分享一点个人体会:AI Engineering from Scratch 这个项目,真正难的不是某一块硬核技术,而是把模型、数据、服务、监控、自动化这些环节像齿轮一样咬合在一起的能力。如果你也正在走这条路,建议不要一上来就追求复杂架构,先把最小闭环跑通,再一层层加厚。把一个简单的系统打磨到极致,比搭建一个复杂但处处都是坑的系统有价值得多。
如果你有具体场景想聊的,欢迎在评论区说说你现在卡在哪一步,是数据处理、模型服务化还是线上监控,我们可以就具体环节再展开细聊。