
1. 从零手搓AI工程为什么我不建议你直接调包很多人一上来就想搞个大模型应用第一反应是找现成的API或者开源框架三行代码跑通一个对话机器人然后觉得自己已经入门AI工程了。我刚开始也这么干过结果踩了一堆坑才发现这种“调包式开发”只能让你停留在Demo层面一旦遇到性能瓶颈、数据漂移、推理延迟或者成本失控你连问题出在哪都找不到。ai-engineering-from-scratch这个方向核心不是让你从零训练一个GPT而是让你从工程视角理解AI系统的每一层——数据怎么流、模型怎么加载、推理怎么调度、服务怎么部署、监控怎么做。它解决的是“只会调API不会做系统”的断层问题。适合谁看如果你已经会写Python用过PyTorch或TensorFlow跑过几个Demo但一到生产环境就抓瞎那这篇内容就是给你写的。如果你是完全零基础也没关系我会把每个环节的“为什么”讲清楚你跟着走一遍至少能建立起完整的工程认知。我自己的经历是最早做AI项目时模型在Jupyter Notebook里准确率95%一上线就崩。后来花了三个月把整个链路拆开重写才发现问题根本不在模型而在数据预处理和批处理逻辑。从那以后我就明白AI工程的核心不是模型本身而是围绕模型的那套工程体系。2. 整体架构设计从数据到服务的五层拆解2.1 为什么是五层而不是三层市面上很多教程把AI系统分成“数据层、模型层、应用层”三层听起来很清晰但实际落地时你会发现模型层和应用层之间还有一大块空白——推理服务、批处理调度、缓存策略、版本管理这些既不属于模型训练也不属于前端应用。所以我习惯把它拆成五层数据层、特征层、模型层、推理层、服务层。这么拆的好处是每一层的职责边界清晰出问题时能快速定位。比如线上响应变慢你可以先看服务层的QPS和延迟再看推理层的批处理队列然后看模型层的加载耗时最后追溯到特征层的计算瓶颈。如果只有三层你很容易把推理层的问题误判成模型层的问题然后去优化模型结构结果白费功夫。2.2 各层的核心职责与选型逻辑数据层负责原始数据的采集、清洗、存储。这里的关键是数据版本化我见过太多团队因为数据更新导致模型效果回退最后连哪版数据对应哪版模型都说不清。我的做法是用DVC或者LakeFS做数据版本管理每次训练都记录数据哈希。特征层负责把原始数据转成模型能吃的特征。很多人喜欢在训练脚本里直接做特征工程但这样会导致训练和推理的特征计算逻辑不一致也就是常说的训练-服务偏差。正确的做法是把特征计算逻辑抽成独立的模块训练和推理共用同一套代码。模型层就是训练和调参。这里我不展开讲算法重点说工程上的事模型版本管理和实验追踪。MLflow或者Weights Biases是标配每次训练的超参、指标、模型文件都要记录否则你调了三个月最后不知道哪组参数最好。推理层是模型上线后的执行引擎。这里要解决的是批处理、并发、硬件加速。比如你用ONNX Runtime还是TensorRT用动态批处理还是静态批处理这些选择直接影响吞吐量和延迟。服务层是对外的API和监控。FastAPI或者Triton Inference Server是常见选择关键是要有健康检查、指标暴露、日志采集。我习惯用Prometheus加Grafana做监控能看到每个请求的延迟分布和错误率。2.3 一个典型的目录结构ai-engineering-from-scratch/ ├── data/ │ ├── raw/ # 原始数据只读 │ ├── processed/ # 清洗后的数据 │ └── features/ # 特征存储 ├── src/ │ ├── data/ # 数据加载和清洗脚本 │ ├── features/ # 特征计算模块 │ ├── models/ # 模型定义和训练 │ ├── inference/ # 推理引擎封装 │ └── service/ # API服务 ├── configs/ # 配置文件 ├── tests/ # 单元测试和集成测试 ├── notebooks/ # 探索性分析 └── deploy/ # 部署脚本和Dockerfile这个结构的好处是每个目录对应一层新人进来能快速找到代码。我试过把特征计算放在src/features/下训练和推理都import同一个模块训练-服务偏差的问题直接消失了。3. 核心细节解析数据管道与特征工程3.1 数据清洗的四个必做步骤原始数据往往很脏直接拿来训练就是给自己挖坑。我一般会做四件事去重、缺失值处理、异常值检测、格式统一。去重不是简单的drop_duplicates()而是要根据业务逻辑判断。比如用户行为日志同一个用户同一秒点了两次按钮可能是误触也可能是真实行为你得看业务场景决定是否合并。缺失值处理要看缺失比例。如果某字段缺失超过30%我一般直接丢掉这个字段因为填充引入的噪声可能比信息还多。如果缺失在5%以内可以用均值、中位数或者模型预测填充。这里有个坑填充逻辑必须和推理时一致否则线上会出现训练时没见过的缺失模式。异常值检测我常用IQR或者Z-score但要注意有些异常值是真实信号比如欺诈检测里的异常交易你不能直接删掉。这时候应该保留异常值但在特征里加一个is_outlier标记。格式统一包括时间格式、编码格式、单位统一。我踩过最大的坑是时间戳训练数据用的是UTC推理时传的是本地时间差了8小时模型效果直接崩了。后来我在数据层强制所有时间戳转成UTC并在特征层加了一个hour_of_day特征问题才解决。3.2 特征工程的三个原则原则一训练和推理共用代码。这是铁律。我见过太多团队训练用Pandas推理用NumPy重写一遍结果特征计算逻辑有细微差异线上效果差一大截。我的做法是把特征计算写成纯函数输入是原始数据字典输出是特征向量训练和推理都调这个函数。原则二特征要可解释。不要搞一堆黑盒特征出了问题你都不知道怎么排查。每个特征都要有明确的业务含义和计算逻辑最好在代码里写清楚注释。原则三特征要版本化。特征定义变了模型必须重新训练。我习惯在特征模块里加一个FEATURE_VERSION常量每次修改特征逻辑就递增版本号训练时记录版本号推理时校验版本号不一致就拒绝服务。3.3 一个可复用的特征计算模块import numpy as np import pandas as pd FEATURE_VERSION 1.0.0 def compute_features(raw_data: dict) - dict: 输入原始数据字典输出特征字典。 训练和推理共用此函数。 features {} # 数值特征归一化 features[age_normalized] (raw_data[age] - 30) / 10 # 类别特征独热编码 features[gender_male] 1 if raw_data[gender] male else 0 features[gender_female] 1 if raw_data[gender] female else 0 # 时间特征从UTC时间戳提取 dt pd.to_datetime(raw_data[timestamp], units, utcTrue) features[hour_of_day] dt.hour features[day_of_week] dt.dayofweek # 交互特征 features[age_hour_interaction] features[age_normalized] * features[hour_of_day] return features这个模块的好处是训练时你可以用DataFrame.apply批量计算推理时直接传单个字典逻辑完全一致。我实测下来训练-服务偏差从原来的15%降到了2%以内。4. 模型训练与推理优化从Notebook到生产4.1 训练脚本的工程化改造Notebook适合探索但不适合生产。我一般会把训练脚本改造成命令行工具用argparse或者click传参用logging记录日志用MLflow追踪实验。import argparse import mlflow import torch from src.data.loader import load_data from src.features.compute import compute_features from src.models.net import MyModel def train(config): mlflow.set_experiment(my-experiment) with mlflow.start_run(): # 记录超参 mlflow.log_params(config) # 加载数据 raw_data load_data(config[data_path]) features compute_features(raw_data) # 训练模型 model MyModel(config) model.fit(features) # 记录指标 mlflow.log_metric(accuracy, model.accuracy) # 保存模型 mlflow.pytorch.log_model(model, model) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--data_path, typestr, requiredTrue) parser.add_argument(--lr, typefloat, default0.001) parser.add_argument(--batch_size, typeint, default32) args parser.parse_args() train(vars(args))这样改造后你可以用python train.py --data_path ./data/raw --lr 0.0001跑实验所有结果自动记录到MLflow再也不用翻Notebook找参数了。4.2 推理优化的三个方向方向一模型量化。把FP32转成FP16或者INT8模型体积缩小一半到四分之三推理速度提升1.5到3倍。我一般用ONNX Runtime的量化工具实测下来INT8量化后准确率只掉0.5%但延迟从50ms降到了18ms。方向二动态批处理。单个请求推理很浪费GPU动态批处理可以把多个请求攒在一起推理。Triton Inference Server自带这个功能配置好max_batch_size和batch_timeout就行。我一般设max_batch_size32batch_timeout10ms吞吐量能提升5倍以上。方向三缓存。对于重复的请求直接返回缓存结果。比如推荐系统里同一个用户短时间内多次请求推荐结果可以缓存几秒钟。我用Redis做缓存命中率大概30%P99延迟降了40%。4.3 一个完整的推理服务示例from fastapi import FastAPI from pydantic import BaseModel import onnxruntime as ort import numpy as np from src.features.compute import compute_features, FEATURE_VERSION app FastAPI() session ort.InferenceSession(model.onnx) class Request(BaseModel): age: int gender: str timestamp: int class Response(BaseModel): prediction: float feature_version: str app.post(/predict, response_modelResponse) def predict(req: Request): raw_data req.dict() features compute_features(raw_data) # 转成模型输入格式 input_array np.array([list(features.values())], dtypenp.float32) # 推理 outputs session.run(None, {input: input_array}) prediction float(outputs[0][0]) return Response(predictionprediction, feature_versionFEATURE_VERSION) app.get(/health) def health(): return {status: ok}这个服务用FastAPI做API层ONNX Runtime做推理引擎特征计算复用训练时的模块。我实测下来单机QPS能到200以上P99延迟在30ms以内。5. 常见问题与排查技巧实录5.1 训练-服务偏差怎么排查这是最常见的问题表现是离线指标很好线上效果很差。排查步骤检查特征版本训练和推理的FEATURE_VERSION是否一致。对比特征分布抽样一批线上请求用训练时的特征计算逻辑重新算一遍对比分布是否一致。检查数据预处理训练时的缺失值填充、异常值处理逻辑是否在推理时也执行了。检查时间戳训练和推理的时间戳时区是否一致。我踩过最大的坑是时间戳时区问题训练数据是UTC推理时传的是本地时间差了8小时hour_of_day特征完全错了。后来在数据层强制转UTC问题解决。5.2 推理延迟突然飙升怎么办可能原因和排查方法现象可能原因排查方法P99延迟飙升P50正常个别请求特征计算慢检查特征计算是否有慢查询所有请求延迟都高GPU显存不足或批处理队列积压检查GPU利用率和批处理队列长度延迟周期性波动缓存过期或数据加载慢检查缓存命中率和数据加载耗时延迟逐渐升高内存泄漏检查服务内存占用是否持续增长我遇到过一次延迟周期性飙升最后发现是缓存过期时间设得太短每5分钟缓存集体失效导致大量请求穿透到推理引擎。后来把过期时间改成随机分布问题解决。5.3 模型效果突然回退怎么排查检查数据版本是否用了新数据训练但新数据有质量问题。检查特征版本特征逻辑是否变了但模型没重新训练。检查模型版本线上加载的模型是否是最新版本。检查输入分布线上请求的输入分布是否发生了漂移。我一般会在服务层加一个输入分布监控用KS检验或者PSI指标一旦分布漂移超过阈值就告警。这样能在效果回退之前发现问题。5.4 常见问题速查表问题可能原因解决方案训练时准确率高线上低训练-服务偏差共用特征计算代码版本化特征推理延迟高模型太大或批处理不当量化模型开启动态批处理服务崩溃内存泄漏或OOM加内存监控限制批处理大小效果逐渐变差数据漂移加输入分布监控定期重新训练模型加载慢模型文件太大量化模型用ONNX格式并发上不去同步推理阻塞用异步推理或增加工作进程6. 实操心得与避坑指南6.1 我踩过的五个坑坑一在Notebook里做特征工程。训练时用Pandas推理时用NumPy重写逻辑不一致线上效果差。后来把特征计算抽成独立模块训练和推理共用问题解决。坑二不记录实验参数。调了三个月最后不知道哪组参数最好。后来用MLflow追踪每次实验自动记录超参和指标再也不用翻Notebook了。坑三模型文件不版本化。线上加载的模型和训练时的模型不一致效果回退。后来用MLflow的模型注册功能每次训练自动生成版本号线上按版本号加载。坑四不做输入分布监控。数据漂移了都不知道效果慢慢变差。后来加了PSI监控一旦分布漂移超过阈值就告警提前发现问题。坑五推理服务不做健康检查。服务挂了都不知道用户请求全部失败。后来加了/health接口配合Kubernetes的liveness probe服务挂了自动重启。6.2 三个提升效率的技巧技巧一用配置文件管理超参。不要硬编码在代码里用YAML或者JSON管理方便切换实验。技巧二用Docker封装环境。训练和推理环境一致避免“在我机器上能跑”的问题。技巧三用Makefile管理常用命令。比如make train、make serve、make test减少重复输入。6.3 后续扩展方向这套框架搭好后你可以往几个方向扩展一是加A/B测试对比不同模型的效果二是加自动重训练数据漂移时自动触发训练三是加模型解释性用SHAP或者LIME解释预测结果四是加联邦学习在多个节点上联合训练。我个人在实际操作中的体会是AI工程最难的不是模型本身而是围绕模型的那套工程体系。你把数据管道、特征工程、推理服务、监控告警这几块搭好模型换一个又一个系统依然稳定。反过来如果工程体系没搭好再好的模型也上不了线。最后再分享一个小技巧每次上线新模型前先用历史数据做一次回放测试对比新旧模型的效果确认没问题再切流量。这个习惯帮我避免了好几次线上事故。