
1. 从零搭建AI工程体系为什么我劝你别一上来就啃论文ai-engineering-from-scratch这个标题第一次看到的时候我以为是又一个教你调包的速成教程。点进去翻了翻才发现它讲的是一件更底层的事当你手里只有一台干净的机器、一个模糊的需求怎么一步步把AI能力真正落地成一个能跑、能维护、能扩展的工程系统。这件事听起来朴素但恰恰是绝大多数人卡住的地方。我见过太多人模型原理讲得头头是道Transformer的注意力公式能默写但真让他从零搭一个能对外服务的AI应用就懵了。数据怎么进来、特征怎么存、模型怎么版本化、推理服务怎么扛住并发、线上效果掉了怎么排查——这些在论文里一个字都不会提但在工程里全是命门。ai-engineering-from-scratch要解决的就是这个断层。这篇内容适合三类人一是刚转行做AI工程、有编程基础但没系统做过项目的人二是做算法研究、想把成果真正推上线的人三是带团队的技术负责人需要一套从零起步的工程框架来对齐认知。我会把整个从零搭建的思路、选型逻辑、实操步骤、踩过的坑尽量讲透。不堆术语讲人话能抄作业的地方直接给方案。2. 整体设计思路先想清楚工程两个字到底意味着什么2.1 为什么从零不等于从模型开始很多人对AI工程的理解是线性的先搞数据再训模型然后部署。这个顺序在学术场景下没问题但在工程场景下是反的。工程的第一性问题不是模型多准而是这个系统要解决谁的什么问题、在什么约束下运行。我举个具体的例子。假设你要做一个商品评论的情感分析服务。学术思路是找数据集、选模型、调参、看准确率。工程思路是谁调用这个服务调用频率多高延迟要求是100毫秒还是1秒评论是中文还是多语言要不要支持批量模型更新后旧结果要不要重算这些问题没想清楚你训出来的模型再准也可能根本用不上。所以ai-engineering-from-scratch的第一个设计原则是需求约束先行技术选型后置。先把边界画出来再决定用什么工具。这一步看起来慢实际上省的是后面反复返工的时间。2.2 分层架构把系统切成能独立演进的几块从零搭建最忌讳的是一锅烩。我习惯把整个AI工程系统切成四层每层职责单一层与层之间通过明确的接口通信。层级职责典型组件演进频率数据层采集、清洗、存储、版本管理对象存储、特征库、数据版本工具低模型层训练、评估、注册、版本化训练框架、实验追踪、模型仓库中服务层推理、批处理、缓存、限流推理引擎、API网关、队列高应用层业务逻辑、编排、监控业务服务、告警、看板高这么切的好处是当业务需求变化时你通常只需要动应用层和服务层数据层和模型层可以保持稳定。反过来当模型迭代时服务层和应用层几乎不用改。这就是独立演进的价值。我试过把四层揉在一起写前期开发确实快但到了第三个月改一个业务逻辑要重新跑一遍模型改一个模型要停整个服务那种痛苦会让你想把代码全删了重来。分层不是教条是用血泪换来的。2.3 选型背后的取舍逻辑从零搭建工具选型是最容易纠结的地方。我的原则是优先选生态成熟、社区活跃、能平滑替换的方案而不是选最新最酷的。拿推理服务来说可选的有原生框架自带的serving、通用推理服务器、自己用Web框架包一层。我的建议是如果团队没有专门的推理优化工程师直接用成熟的推理服务器把精力放在业务上。自己包一层看起来灵活但你要自己处理并发、显存管理、批处理调度这些坑深不见底。再比如数据版本管理小项目用文件命名约定就够了别一上来就上重型工具。我见过一个三人团队为了规范搭了一套完整的数据版本系统结果维护成本比业务本身还高。工具是为人服务的不是反过来。提示选型时问自己三个问题——这个工具出问题了我能自己修吗社区里遇到同样问题的人多吗换掉它的成本高吗三个都答不上来就再想想。3. 核心细节解析数据、模型、服务三块硬骨头怎么啃3.1 数据层脏活累活但决定上限数据层是AI工程里最不性感、但最决定成败的部分。模型再强喂进去的数据是垃圾出来的就是垃圾。从零搭建时数据层要解决四件事采集、清洗、存储、版本。采集环节关键是幂等和可追溯。同一条数据重复采集不能产生重复记录每条数据要能追溯到来源和时间。我通常会给每条记录加一个内容哈希作为唯一标识采集时先查哈希是否存在存在就跳过。这个简单的设计能省掉后面大量的去重工作。清洗环节别追求一步到位。我的做法是分阶段第一阶段只做最基础的格式校验和空值处理保证数据能进库第二阶段做业务规则清洗比如过滤掉明显无效的评论第三阶段才做高级的异常检测。为什么分阶段因为清洗规则本身会随着你对数据的理解而演进一次性写死后面改起来很痛苦。存储环节要区分原始数据和加工数据。原始数据只追加不修改加工数据可以重建。这样即使加工逻辑写错了也能从原始数据重新跑一遍不会造成不可逆的损失。版本管理这块小团队用日期描述的目录命名就够用比如2024-06-01_initial_clean。等数据量和协作复杂度上来了再考虑专门的版本工具。别为了版本而版本。3.2 模型层把实验变成可复现的工程模型层最大的陷阱是实验做了一堆但没人知道哪个实验对应哪个结果也没人能复现。从零搭建时必须把实验追踪和模型注册这两件事做扎实。实验追踪的核心是记录三样东西代码版本、数据版本、超参数。这三样确定了实验结果理论上就能复现。我习惯用配置文件管理超参数每次实验生成一个唯一的实验ID所有产出日志、指标、模型文件都挂在这个ID下。这样回头看任何一个结果都能顺藤摸瓜找到全部上下文。模型注册是另一个关键。训练出来的模型不能直接扔给服务层用要先注册——记录它的版本、指标、训练数据、评估结果通过审核后才能进入服务层。这个流程看起来繁琐但能避免线上跑的是哪个模型这种灵魂拷问。评估环节我要特别强调别只看单一指标。准确率高的模型可能在某个子群体上表现很差。我通常会做分层评估把测试集按关键维度切开看模型在每个子集上的表现。这个习惯帮我提前发现过好几次线上事故。3.3 服务层让模型真正可用的地方服务层是把模型变成产品的最后一公里。这里有几个必须处理的问题延迟、并发、批处理、降级。延迟方面要区分冷启动和热推理。冷启动是模型第一次加载或长时间没请求后的首次推理延迟可能是热推理的几十倍。解决办法是服务启动时预热或者保持一个最小常驻实例。我实测过一个中等规模的模型冷启动可能要好几秒热推理只要几十毫秒差距巨大。并发方面关键是批处理调度。单个请求推理效率低把多个请求攒成一批一起推理能大幅提升吞吐。但攒批会增加延迟所以要设一个最大等待时间比如10毫秒超过就立即推理。这个参数需要根据业务延迟要求调没有标准答案。降级方面必须有兜底策略。模型服务挂了怎么办返回缓存结果、返回默认值、还是直接报错这取决于业务。但无论如何不能让它无声无息地挂掉。我习惯在服务层加一个健康检查接口配合监控告警出问题第一时间知道。注意服务层的配置参数批大小、超时时间、重试次数一定要可配置不要写死在代码里。线上情况千变万化能不改代码就不改代码。4. 实操过程从空目录到能跑的系统4.1 环境准备与依赖管理从零开始第一步是把环境搭好。我的习惯是用容器隔离环境用锁文件固定依赖。容器方面基础镜像选官方的精简版别用带一堆预装工具的镜像那些你用不上还增加攻击面。Python项目我会用slim版本然后按需装依赖。依赖管理关键是锁文件。requirements.txt只写直接依赖requirements.lock记录所有依赖的精确版本。部署时用锁文件保证每次装出来的环境完全一致。我踩过的坑是开发环境用pip install随便装部署时用requirements.txt结果某个间接依赖升级了线上直接崩。锁文件能避免这个问题。# 生成锁文件 pip freeze requirements.lock # 部署时用锁文件 pip install -r requirements.lock目录结构我通常这么组织project/ ├── data/ # 数据相关 │ ├── raw/ # 原始数据 │ └── processed/ # 加工数据 ├── models/ # 模型相关 │ ├── training/ # 训练代码 │ └── registry/ # 模型仓库 ├── serving/ # 服务代码 ├── configs/ # 配置文件 ├── scripts/ # 运维脚本 └── tests/ # 测试这个结构的好处是职责清晰新人进来一眼能看懂东西在哪。4.2 数据管道的搭建数据管道我建议用脚本调度的方式别一上来就上重型工作流引擎。第一步写一个采集脚本负责从数据源拉数据、做基础校验、写入原始数据区。这个脚本要能重复执行而不产生重复数据。第二步写一个清洗脚本从原始数据区读数据做清洗写入加工数据区。清洗规则用配置文件管理方便调整。第三步写一个调度配置定时执行这两个脚本。简单的用cron就够复杂的用轻量调度工具。# 采集脚本的核心逻辑示意 import hashlib def collect(source): for record in source.fetch(): content_hash hashlib.md5(str(record).encode()).hexdigest() if not storage.exists(content_hash): storage.save(content_hash, record)这个哈希去重的逻辑简单但有效我用了好几年没出过问题。4.3 模型训练与注册流程训练流程我习惯做成配置驱动的。所有超参数、数据路径、输出路径都写在配置文件里训练脚本读配置执行。这样换一组参数就是换一个配置文件不用改代码。# configs/train_v1.yaml data: train_path: data/processed/train.csv val_path: data/processed/val.csv model: name: text_classifier hidden_size: 256 num_layers: 4 training: batch_size: 32 learning_rate: 0.001 epochs: 10 output: dir: models/registry/v1训练完成后脚本自动做评估把指标写进一个metrics.json和模型文件一起放到注册目录。然后有一个注册脚本检查指标是否达标达标就更新当前生产版本的指针。这个流程的关键是自动化。人工注册容易出错也容易忘记。自动化之后每次训练完就知道能不能上省心很多。4.4 推理服务的实现与调优推理服务我用成熟的推理服务器打底外面包一层业务逻辑。核心是三个接口健康检查、单条推理、批量推理。健康检查接口返回服务状态和当前加载的模型版本方便监控和排查。单条推理接口接收一条请求返回结果。内部要做输入校验、预处理、推理、后处理。批量推理接口接收多条请求内部做批处理调度提升吞吐。调优方面我实测下来最有效的三个手段预热、批处理、缓存。预热解决冷启动批处理提升吞吐缓存减少重复计算。缓存特别适合输入重复率高的场景比如热门商品的评论分析很多请求的输入是一样的缓存命中率能到30%以上。# 批处理调度的简化逻辑 import time class BatchScheduler: def __init__(self, max_batch_size32, max_wait_ms10): self.max_batch_size max_batch_size self.max_wait_ms max_wait_ms self.queue [] def add(self, request): self.queue.append((time.time(), request)) if len(self.queue) self.max_batch_size: return self.flush() return None def flush(self): # 取出队列中的请求批量推理 batch self.queue[:self.max_batch_size] self.queue self.queue[self.max_batch_size:] return self.infer_batch([r for _, r in batch])这个逻辑不复杂但效果显著。我测过一个场景单条推理QPS只有50加了批处理之后到了400多。5. 常见问题与排查技巧实录5.1 线上效果突然下降怎么查这是最让人头疼的问题。我的排查顺序是先看数据再看模型最后看服务。数据方面检查输入数据的分布有没有变化。比如评论分析服务如果突然涌入大量英文评论而模型只训过中文效果肯定掉。这种问题叫数据漂移是线上效果下降最常见的原因。模型方面确认线上跑的是哪个版本和预期是否一致。我遇到过好几次是部署脚本出错线上跑的是旧版本。服务方面检查有没有请求被截断、超时、或者预处理出错。这些都会导致结果异常。排查时一定要有对照。拿一批已知正确结果的样本走一遍线上流程看哪一步开始出问题。这个方法能快速定位问题环节。5.2 推理延迟忽高忽低延迟波动通常有三个原因批处理等待、资源竞争、冷启动。批处理等待导致的波动表现为延迟在某个范围内规律波动。解决办法是调整批处理参数或者对延迟敏感的业务单独走一条不走批处理的通道。资源竞争导致的波动表现为高峰期延迟明显升高。解决办法是限制并发、加资源、或者做请求排队。冷启动导致的波动表现为长时间没请求后第一次请求特别慢。解决办法是预热或保持常驻。我一般会先看延迟的分布图规律波动、高峰升高、偶发尖刺三种形态对应三种原因对症下药。5.3 模型更新后旧结果要不要重算这个问题没有标准答案取决于业务。我的判断原则是如果旧结果的错误会导致用户明显感知的问题就重算否则不重算。比如搜索排序模型更新后旧结果不重算用户可能看到不相关的推荐体验差那就重算。但如果是离线报表旧结果错一点无所谓就不重算省资源。重算的成本要提前评估。全量重算可能很贵可以考虑增量重算只重算最近一段时间的数据。5.4 常见问题速查表问题现象可能原因排查方法解决方向效果下降数据漂移对比输入分布重新训练或加规则效果下降模型版本错查服务加载版本修正部署流程延迟波动批处理等待看延迟分布调批处理参数延迟波动资源竞争看高峰期指标限流或扩容延迟尖刺冷启动看首次请求延迟预热或常驻服务报错输入异常看错误日志加输入校验内存泄漏缓存无上限看内存曲线加缓存淘汰策略提示排查问题时日志是第一手资料。日志要记录请求ID、输入摘要、处理耗时、结果摘要。有了这些大部分问题都能定位。6. 我踩过的坑和几条实在建议第一个坑是过早优化。刚开始搭系统时我花了两周做了一套复杂的缓存和批处理结果业务量根本没那么大这些优化完全用不上还增加了维护成本。后来我学乖了先跑通最小可用版本等真有性能问题了再优化。第二个坑是忽视监控。有次线上服务挂了两个小时我才知道因为没配告警。从那以后我把监控告警当成和业务代码同等重要的事。关键指标延迟、错误率、吞吐必须有告警阈值要合理太敏感会疲劳太迟钝会漏报。第三个坑是配置散落。早期我把配置写在代码各处改一个参数要翻好几个文件。后来统一到配置文件按环境区分改配置不用动代码清爽很多。几条实在建议先把最小闭环跑通再逐步加功能每个环节都要有日志和监控配置和代码分离能自动化的绝不手动定期回顾系统删掉用不上的东西。这个领域变化快但工程的基本原则变化慢。把数据、模型、服务这三块的基本功打扎实工具换了一茬又一茬你也不会慌。ai-engineering-from-scratch的价值不在于教你某个具体工具而在于帮你建立一套从零到一的工程思维。这套思维才是真正能带走的东西。