ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零搭建AI工程体系:特征工程、模型服务与监控的工程化实践

从零搭建AI工程体系:特征工程、模型服务与监控的工程化实践 1. 从零搭建AI工程体系为什么我劝你别一上来就搞模型ai-engineering-from-scratch这个标题第一次看到的时候我以为是又一个教你调包的教程。点进去翻了翻发现它讲的是从最底层开始把AI工程化这件事从头捋一遍——不是从pip install transformers开始而是从数据怎么进来、特征怎么存、模型怎么上线、推理怎么加速、监控怎么做这一整条链路。这个方向其实特别对。我见过太多团队模型训得漂漂亮亮F1刷到0.95结果一上线就崩QPS扛不住、延迟飙到几秒、特征对不上、模型版本回滚要半天。问题从来不在模型本身而在工程。AI工程化这件事说白了就是把实验室里的能跑变成生产环境里的稳跑中间隔着的不是算法是工程。这篇内容适合谁看如果你是刚入行的算法工程师只会写notebook不会写服务那这篇能帮你补齐工程短板如果你是后端工程师被拉来做AI平台但不懂模型那套东西这篇能帮你理解AI系统的特殊性如果你是技术负责人正在规划团队的AI基础设施这篇能帮你理清哪些环节必须自建、哪些可以买。我不打算讲太玄的东西就按一个真实项目从零到一的顺序把每个环节的关键决策和踩坑点摊开说。2. 整体架构设计先想清楚数据怎么流再想模型怎么放2.1 为什么从零反而比用现成更难很多人觉得从零搭建就是不用框架、不用云服务什么都自己写。这是个误解。真正的from scratch是指你理解每一层的职责边界知道什么时候该用现成的、什么时候必须自己控制。比如特征存储你可以用Redis凑合也可以用Feast还可以自己写一套。选哪个不取决于哪个高级取决于你的特征更新频率、一致性要求和团队维护能力。我自己的经验是从零搭建AI工程体系最难的不是写代码是做减法。市面上工具太多了每个都号称能解决问题但你把十个工具拼在一起维护成本是指数级上升的。所以第一步不是选工具是画数据流图。从数据源到最终预测结果中间经过哪些环节每个环节的输入输出是什么延迟要求是多少一致性要求是什么。这张图画清楚了工具选型自然就出来了。2.2 分层架构的取舍逻辑一个典型的AI工程体系我会分成五层数据层、特征层、训练层、服务层、监控层。这个分法不是教科书上的标准答案是我在实际项目里觉得职责最清晰的切法。数据层负责原始数据的采集、清洗、存储。这里的关键决策是批处理和流处理怎么分工。我的建议是能用批处理解决的绝不上流处理。流处理虽然听起来酷但运维复杂度高一个量级。只有那些对时效性要求极高的场景比如实时风控、实时推荐才值得上流处理。特征层是AI系统区别于普通后端系统的地方。普通后端系统的状态就是数据库里的记录AI系统的状态是特征向量。特征层要解决的核心问题是训练和推理的一致性——训练时用的特征和线上推理时用的特征必须是同一套计算逻辑否则就会出现训练服务偏差。这个问题我在三个项目里都遇到过后面会详细讲怎么解。训练层相对成熟但要注意的是训练环境的可复现性。你今天用这个版本的PyTorch训了一个模型三个月后想复现发现依赖升级了、数据变了、随机种子没固定那就抓瞎了。所以训练层的第一要务不是跑得快是跑得可复现。服务层是模型上线的地方。这里的关键指标是延迟、吞吐和资源利用率。一个模型能不能上线不取决于它的准确率取决于它在P99延迟约束下能不能扛住峰值QPS。监控层是最容易被忽视的。很多人觉得模型上线就完事了其实上线才是开始。数据漂移、概念漂移、特征异常、预测分布偏移这些问题不监控就发现不了等业务方找上门来就晚了。2.3 一个最小可行架构的参考如果你现在要从零开始搭我建议先搭一个最小可行版本跑通全链路再逐步优化。最小版本可以长这样层级最小方案后续演进方向数据层定时脚本拉数据到对象存储引入调度系统、数据质量检查特征层Python函数统一计算抽成特征平台、加缓存训练层单机脚本配置文件分布式训练、实验管理服务层FastAPI包一层模型服务器、动态批处理监控层日志简单统计漂移检测、自动告警这个表的意思是不要一上来就搞大而全。先把链路跑通哪怕每个环节都很粗糙。跑通之后你才知道瓶颈在哪才知道该优化什么。我见过太多团队花三个月搭了一套完美的平台结果业务需求变了平台白搭。3. 特征工程与数据管道训练推理一致性是命门3.1 特征计算的双轨制问题特征工程里最经典的坑就是训练和推理用了两套代码。训练的时候用Pandas做特征推理的时候用Java重写一遍两边逻辑稍微对不齐线上效果就掉。这个问题有个专门的名字叫训练服务偏差Training-Serving Skew。我踩过最惨的一次是训练时对缺失值填了0推理时忘了填直接传了null进去模型输出全是NaN。排查了一整天最后发现是两行代码不一致。从那以后我就定了个规矩特征计算逻辑只能有一份代码训练和推理都调这一份。具体怎么做如果你的技术栈是Python可以把特征计算逻辑抽成一个独立的包训练脚本import它推理服务也import它。如果推理服务是Java或Go那就把特征计算做成一个独立的服务训练和推理都通过RPC调它。后者的好处是语言无关坏处是多了网络开销。对于延迟敏感的场景可以在推理服务里嵌入一个轻量级的特征计算库但逻辑必须从同一份定义生成。3.2 特征存储的选型与实操特征存储Feature Store这个概念被炒得很热但不是所有团队都需要。我的判断标准是如果你有超过三个模型共用同一批特征或者特征更新频率高于每天一次那就值得上特征存储。否则用Redis或数据库凑合就行。如果决定上特征存储Feast是个不错的起点。它的核心概念是feature view把特征定义、数据源、实体绑定在一起。下面是一个简化的例子from feast import FeatureView, Field, FileSource from feast.types import Float32, Int64 driver_source FileSource(pathdata/driver_stats.parquet) driver_stats_fv FeatureView( namedriver_hourly_stats, entities[driver_id], ttltimedelta(hours24), schema[ Field(nameconv_rate, dtypeFloat32), Field(nameavg_daily_trips, dtypeInt64), ], sourcedriver_source, )定义好之后训练时用get_historical_features拿历史特征推理时用get_online_features拿实时特征两边逻辑自动一致。这个设计的好处是把一致性保证下沉到了框架层不依赖工程师的自觉。但Feast也不是银弹。它的在线存储默认用SQLite生产环境要换成Redis或DynamoDB。离线存储用Parquet或BigQuery。这些配置项不少第一次搭要花点时间。我的建议是先在一个小项目上试跑通了再推广。3.3 数据质量检查的实操要点数据管道里另一个容易被忽视的环节是数据质量检查。原始数据出问题后面全白搭。我一般会在数据入口做三层检查第一层是schema检查字段类型、字段数量、必填字段是否齐全。这层用Great Expectations或Pydantic都能做。第二层是分布检查关键字段的均值、方差、分位数是否在预期范围内。第三层是业务规则检查比如年龄不能为负、订单金额不能超过某个上限。注意数据质量检查的阈值不要设得太死。我见过团队把阈值设成均值波动不超过1%结果每次大促都被告警淹没。阈值应该基于历史数据的分布来定留出合理的波动空间。检查发现问题之后怎么办我的做法是分级处理。schema错误直接阻断管道分布异常发告警但继续跑业务规则违反记录到单独的表里人工审核。这样既不会因为小问题阻断整个流程也不会让大问题溜过去。4. 模型训练与版本管理可复现比跑得快重要4.1 训练环境的可复现性设计训练环境可复现这件事说起来简单做起来烦。你需要固定四样东西代码版本、依赖版本、数据版本、随机种子。代码版本用Git commit hash依赖版本用lock文件数据版本用快照或哈希随机种子在每个涉及随机性的地方都设上。我一般会在训练脚本开头加这么一段import random import numpy as np import torch def set_seed(seed42): 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注意最后两行deterministicTrue会让cuDNN用确定性算法benchmarkFalse会关掉自动调优。这两个设置会牺牲一点性能但换来的是可复现性。如果你的训练任务对时间不敏感建议打开。如果对时间敏感至少要在实验阶段打开确认结果稳定后再关掉跑最终版本。数据版本这块小数据可以直接存快照大数据用DVC或LakeFS做版本管理。DVC的好处是和Git集成得好dvc add之后数据文件的哈希会记录在Git里切换commit就能切换数据版本。LakeFS更重一些适合数据量特别大的场景。4.2 实验管理与模型注册实验管理工具我用过MLflow、Weights Biases和TensorBoard。MLflow胜在开源、可自托管适合对数据安全要求高的团队。WB体验好、功能全但数据要传到云端。TensorBoard最轻量但只管可视化不管实验追踪。如果从零搭建我建议先用MLflow。它的核心概念是experiment和run每个run记录一组参数、指标和artifact。下面是一个典型用法import mlflow mlflow.set_experiment(my_model) with mlflow.start_run(): mlflow.log_param(learning_rate, 0.001) mlflow.log_param(batch_size, 64) for epoch in range(epochs): train_loss train_one_epoch() mlflow.log_metric(train_loss, train_loss, stepepoch) mlflow.pytorch.log_model(model, model)跑完之后在MLflow UI里就能看到所有实验的对比。这个工具最大的价值不是可视化是让每个模型都有迹可循。三个月后你回头看能知道当时用了什么参数、数据是什么版本、指标是多少。模型注册是实验管理的下一步。当一个模型被验证有效、准备上线时把它注册到模型注册表里打上版本号标记为production或staging。这样服务层加载模型时只需要指定模型名和stage不用关心具体的文件路径。MLflow Model Registry和SageMaker Model Registry都提供这个能力。4.3 训练管道的编排训练管道编排工具Airflow、Kubeflow Pipelines、Metaflow各有拥趸。我的选择逻辑是如果团队已经有Airflow在跑数据管道那就用Airflow跑训练别引入新工具。如果是从零开始Kubeflow Pipelines对K8s原生支持好Metaflow对Python工程师友好。不管用哪个训练管道要解决的核心问题是依赖管理和失败重试。一个典型的训练管道包括数据拉取、特征计算、数据校验、模型训练、模型评估、模型注册。每一步都可能失败失败后要能重试重试要能从上一步的产物继续而不是从头再来。Airflow里用TaskFlow API可以比较优雅地表达这种依赖from airflow.decorators import dag, task dag(scheduledaily) def training_pipeline(): task def fetch_data(): return data_path task def compute_features(data_path): return feature_path task def train_model(feature_path): return model_path data fetch_data() features compute_features(data) model train_model(features) training_pipeline()这个写法比传统的Operator方式简洁很多而且任务之间的数据传递是自动的。但要注意Airflow的任务之间传递大数据不合适应该传路径而不是传数据本身。5. 模型服务与推理优化延迟和吞吐的平衡术5.1 服务框架的选型对比模型服务框架常见的有TorchServe、Triton Inference Server、TF Serving、BentoML还有直接用FastAPI自己包的。选哪个取决于你的模型类型和性能要求。框架优势劣势适用场景FastAPI自包灵活、可控性能优化要自己做小规模、快速上线TorchServePyTorch原生只支持PyTorch纯PyTorch团队Triton多框架、性能强配置复杂多框架、高性能要求BentoML开发体验好生态相对小快速迭代的团队我自己的经验是如果QPS在100以下FastAPI自包完全够用而且最灵活。如果QPS上千或者要跑多个模型Triton是更好的选择。Triton的动态批处理Dynamic Batching功能特别实用能把多个小请求合并成一个大batchGPU利用率能提升好几倍。5.2 动态批处理的参数计算动态批处理的核心参数是max_batch_size和max_queue_delay_microseconds。前者控制最大batch大小后者控制等待时间。这两个参数怎么定要看你的延迟预算和QPS。假设你的P99延迟要求是100ms模型单次推理耗时20msbatch_size1那么留给排队的时间最多80ms。如果QPS是500平均每2ms来一个请求那么等待10ms能攒到5个请求。所以max_queue_delay_microseconds可以设成10000max_batch_size设成8或16。但这是理想情况。实际中请求不是均匀到达的有峰值有低谷。所以参数要留余量而且要在压测中验证。我一般会做一组压测把不同参数组合下的P99延迟和吞吐画成曲线选那个在延迟约束下吞吐最高的点。提示动态批处理对GPU模型效果明显对CPU模型效果有限。因为CPU模型的瓶颈往往不在计算而在内存带宽或IO。如果你的模型跑在CPU上优先优化的是模型本身比如量化、剪枝而不是批处理。5.3 模型量化的实操与坑模型量化是推理优化的另一个大招。FP32转FP16能省一半显存速度提升30%到50%精度损失通常很小。INT8量化更激进能省四分之三显存速度提升2到4倍但精度损失要看模型和校准数据。PyTorch的量化分动态量化和静态量化。动态量化最简单一行代码model_quantized torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtypetorch.qint8 )但动态量化只对Linear层有效而且推理时还是要动态计算量化参数加速有限。静态量化需要校准数据精度更好加速更明显但流程复杂model.eval() model.qconfig torch.quantization.get_default_qconfig(fbgemm) model_prepared torch.quantization.prepare(model) # 用校准数据跑一遍 for data in calibration_loader: model_prepared(data) model_quantized torch.quantization.convert(model_prepared)校准数据的选择很关键。要用真实分布的数据不能随便拿几条凑数。我一般会从验证集里随机抽100到500条覆盖各种边界情况。校准完之后一定要在测试集上验证精度如果掉点超过1%就要考虑混合量化或者放弃量化。5.4 服务层的弹性与降级模型服务上线之后要考虑弹性伸缩和降级策略。弹性伸缩用K8s的HPA就行但要注意指标的选择。CPU利用率对模型服务来说往往不是好指标因为GPU模型的CPU利用率可能一直很低。更好的指标是QPS或队列长度。降级策略是很多人忽略的。当流量突增、模型服务扛不住时要有兜底方案。最简单的降级是返回缓存结果或默认结果。复杂一点的可以准备一个轻量级模型主模型扛不住时切到轻量模型。再复杂一点可以做请求采样只对部分请求做推理其余走规则。我一般会在服务层加一个开关通过配置中心控制。出问题时运维不用改代码改个配置就能降级。这个开关平时是关的但要有而且要定期演练确保真出问题时能生效。6. 监控、漂移检测与持续迭代上线只是开始6.1 监控指标的四个层次AI系统的监控比普通后端系统复杂因为除了系统指标还要监控数据指标和模型指标。我一般分四层第一层是基础设施指标CPU、内存、GPU利用率、网络IO。这层用Prometheus加Node Exporter就能覆盖。第二层是服务指标QPS、延迟分布、错误率、超时率。这层用Prometheus加应用埋点。第三层是数据指标输入特征的分布、缺失率、异常值比例。第四层是模型指标预测分布、置信度分布、业务指标如点击率、转化率。这四层里前两层是标配后两层是AI系统特有的。很多团队只做前两层结果模型效果掉了都不知道为什么。我的建议是至少要把第三层做起来因为数据问题是最常见的。6.2 数据漂移与概念漂移的检测数据漂移Data Drift是指输入特征的分布发生了变化。概念漂移Concept Drift是指输入和输出之间的关系发生了变化。两者的检测方法不同。数据漂移检测常用PSIPopulation Stability Index或KS检验。PSI的计算方式是def calculate_psi(expected, actual, buckets10): def scale_range(input, min_val, max_val): input np.clip(input, min_val, max_val) return (input - min_val) / (max_val - min_val) breakpoints np.arange(0, buckets 1) / buckets * 100 breakpoints np.percentile(expected, breakpoints) expected_percents np.histogram(expected, breakpoints)[0] / len(expected) actual_percents np.histogram(actual, breakpoints)[0] / len(actual) def sub_psi(e_perc, a_perc): if a_perc 0: a_perc 0.0001 if e_perc 0: e_perc 0.0001 return (e_perc - a_perc) * np.log(e_perc / a_perc) psi_value sum(sub_psi(expected_percents[i], actual_percents[i]) for i in range(len(expected_percents))) return psi_valuePSI小于0.1说明分布稳定0.1到0.25说明有轻微漂移大于0.25说明有显著漂移。这个阈值不是绝对的要根据业务容忍度调整。概念漂移检测更复杂因为它需要标签。如果标签能及时拿到可以监控模型指标的变化。如果标签延迟很大可以用代理指标比如预测分布的熵、置信度的均值。这些指标变化不一定意味着概念漂移但值得警惕。6.3 模型迭代的触发机制模型什么时候该重新训练常见的有三种触发方式定时触发、指标触发、事件触发。定时触发最简单每周或每月重训一次。适合数据分布变化慢的场景。指标触发是当漂移指标超过阈值时触发重训。事件触发是当业务发生重大变化时手动触发比如大促、政策调整。我一般会组合使用。定时触发作为兜底保证模型不会太旧。指标触发作为主要方式及时响应分布变化。事件触发作为补充应对突发情况。重训之后不能直接上线要走评估流程。评估包括离线指标和在线指标。离线指标看准确率、召回率、AUC这些。在线指标看A/B测试的结果。我一般会先跑一周的A/B测试新模型流量占10%观察业务指标有没有提升。确认没问题再逐步放量。6.4 常见问题速查表问题现象可能原因排查方向解决方案线上效果远差于离线训练服务偏差对比训练和推理的特征值统一特征计算逻辑延迟突然升高模型变大或流量突增查看模型大小和QPS曲线量化模型或扩容预测分布偏移数据漂移计算PSI和KS重新训练模型部分请求超时长尾请求或资源竞争查看延迟分布加超时限制或隔离资源模型加载失败版本不兼容或文件损坏检查模型文件和依赖版本回滚到上一版本GPU利用率低batch太小或IO瓶颈查看batch size和IO等待调大batch或优化数据加载这张表是我自己排查问题时总结的不一定全面但覆盖了大部分常见情况。排查的核心思路是先定位问题在哪一层再深入具体原因。不要一上来就怀疑模型大部分问题出在数据和工程上。7. 一些踩坑之后的个人体会从零搭建AI工程体系这件事我做过三次每次都有新的教训。第一次是低估了特征一致性的重要性上线后效果掉了一半排查了两周才发现是特征计算逻辑不一致。第二次是低估了监控的价值模型跑了三个月效果慢慢变差但没人发现直到业务方投诉。第三次是低估了可复现性的成本想复现半年前的一个实验发现依赖升级了、数据变了折腾了一周也没完全复现。如果让我给正在从零搭建的团队一个建议我会说先把链路跑通再优化每个环节。不要一上来就追求完美架构不要一上来就引入一堆工具。跑通之后你会发现问题在哪那时候再针对性优化效率高得多。还有一个体会是AI工程化这件事工程能力比算法能力更重要。我见过算法很强但工程很弱的团队模型训得很好但上不了线。也见过算法一般但工程很强的团队模型效果不是最好但业务价值很大。从零搭建AI工程体系核心不是把模型做到极致是把整个系统做到可靠、可维护、可迭代。最后分享一个小技巧在服务层加一个影子模式新模型上线前先让它跑在影子流量上不返回结果但记录预测。对比影子预测和主模型预测的差异如果差异在可接受范围内再切流量。这个做法能大幅降低上线风险我现在的项目都会加这个。
返回列表