从数据湖到模型服务:构建企业级MLOps平台的架构与实践

从数据湖到模型服务:构建企业级MLOps平台的架构与实践 1. 项目概述从“炼丹”到“工厂化生产”的跨越在机器学习领域摸爬滚打了十几年我见过太多团队从最初的“小作坊式”实验到最终将模型部署上线所经历的阵痛。一个典型的场景是数据科学家在本地Jupyter Notebook里精心调教出一个准确率99%的模型兴奋地交给工程团队结果发现数据源不一致、环境依赖缺失、计算资源不足上线过程变成了长达数月的“填坑”之旅。这种割裂本质上是实验环境与生产环境、数据管理与计算资源、算法迭代与系统运维之间的巨大鸿沟。我们内部称之为“ACAI”的平台正是为了解决这一系列痛点而生的。ACAI并非某个开源项目的缩写而是我们团队对“端到端机器学习系统”愿景的概括——自动化、协同化、敏捷化、智能化。它的核心目标是构建一个覆盖从原始数据接入到模型服务上线的完整流水线让数据科学家能像在笔记本上一样自由探索同时让工程团队能获得稳定、可控、可观测的生产级系统。这次分享的就是我们基于数据湖架构与自动资源调度两大基石将这一理念落地的完整实践。无论你是正被MLOps问题困扰的算法工程师还是负责搭建基础架构的平台研发亦或是希望提升团队协作效率的技术负责人相信这些踩过的坑和总结出的模式都能给你带来直接的参考价值。2. 核心架构设计数据湖与自动调度的双引擎驱动一个稳健的端到端机器学习系统绝不能是各种开源工具的简单堆砌。它的顶层设计必须回答几个关键问题数据如何统一管理并高效供给计算资源如何弹性适配多变的训练任务工作流如何编排并确保可复现性ACAI平台的设计正是围绕这些核心问题展开的。2.1 数据湖不是“数据沼泽”而是“活水之源”提到数据湖很多人会联想到HDFS上堆积如山的、缺乏管理的原始数据文件即所谓的“数据沼泽”。我们对于数据湖的理解截然不同它是一个有明确入湖标准、元数据管理、统一访问接口的“活水”系统。为什么选择数据湖而非传统数据仓库机器学习的数据需求非常特殊数据格式多样除了结构化的表格数据还有大量的图像、文本、音频等非结构化数据。数据版本化需求模型的训练和评估必须绑定特定版本的数据快照以确保实验的可复现性。探索性分析频繁数据科学家需要快速抽样、关联查询不同来源的数据进行特征探索。传统数仓严格的Schema-on-Write模式写入时定义格式和批处理ETL流程无法满足这种敏捷、多变的需求。而数据湖的Schema-on-Read模式读取时解析格式和对象存储的灵活性正好契合。我们的数据湖核心组件与设计存储层我们选择了MinIO作为对象存储服务部署在Kubernetes集群内。选择它而非直接使用云厂商对象存储如S3的原因一是出于混合云环境下数据主权和网络延迟的考虑二是MinIO与S3 API完全兼容未来迁移成本极低。所有原始数据、处理后的特征、模型文件都作为对象存储在这里。元数据与目录服务这是避免“数据沼泽”的关键。我们引入了Apache Iceberg作为表格式。Iceberg在对象存储之上提供了一层抽象带来了如时间旅行可查询历史某个时刻的数据快照、隐藏分区分区逻辑对查询引擎透明优化分区策略无需重写数据、ACID事务等数仓才有的能力。数据科学家通过SQL或DataFrame API查询Iceberg表时感受到的是类似数据库的体验完全无需关心底层文件是如何组织的。统一数据访问层我们开发了一个轻量的“数据服务”封装了对Iceberg表、以及直接对象文件的访问。该服务提供Python SDK支持# 示例获取指定版本的特征数据集 from acai.data import Dataset ds Dataset(nameuser_behavior_features, versionv20240501) df ds.to_pandas() # 自动处理格式转换、采样等这一层隔离了底层存储的复杂性也为后续的数据血缘追踪、访问审计打下了基础。注意数据湖的建设最容易犯的错误是“只建不治”。我们强制要求所有入湖的数据必须附带基本的元数据数据来源、负责人、业务含义描述并通过定期扫描和治理规则如识别长期未访问的“冷数据”来保持湖水的“清澈”。2.2 自动资源调度让计算力“随需而动”机器学习任务尤其是训练任务对计算资源的需求是“脉冲式”且差异巨大的。一个小型推荐模型的精调可能只需要单GPU几小时而一个百亿参数大模型的预训练则需要数百张GPU卡持续数周。如何高效、公平、低成本地满足这种需求我们基于Kubernetes打造了自动资源调度系统。为什么是KubernetesK8s已成为容器编排的事实标准其声明式API、强大的调度器kube-scheduler和资源模型CPU/Memory/GPU为构建机器学习平台提供了绝佳的基础。但我们并没有直接使用原生的K8s因为它在机器学习场景下存在不足队列管理与公平共享原生K8s缺乏任务队列概念容易发生资源被单个大任务“霸占”。GPU等异构资源细粒度调度比如共享GPU、GPU拓扑感知NVLink、不同型号GPU混布等。任务生命周期管理机器学习任务有复杂的依赖关系如数据准备-训练-评估需要工作流编排。我们的调度系统架构 我们在K8s之上引入了KubeRay和Volcano两个关键组件。KubeRay用于管理和调度Ray集群。Ray是一个流行的分布式计算框架特别适合机器学习中的超参搜索、模型训练等任务。KubeRay Operator让我们能够像声明Pod一样声明一个RayCluster并自动管理其生命周期。当用户提交一个Ray任务时调度器会自动按需创建或扩容Ray工作节点。Volcano这是一个专为批量计算、AI、大数据任务设计的K8s批量调度器。它弥补了原生调度器的关键短板队列Queue我们根据业务优先级和团队划分了多个队列如high-priority,research,batch。任务提交到指定队列。公平共享与抢占Volcano的调度策略可以确保高优先级队列的任务能快速获得资源并在资源紧张时优雅地抢占低优先级任务的资源通过驱逐Pod并后续重试。任务组PodGroup将一个分布式训练任务的所有Pod如1个Chief 8个Worker视为一个整体进行调度要么全部成功调度要么都不调度避免了“死锁”问题。资源调度策略示例 我们定义了一个TrainingJob的CRD自定义资源用户提交的YAML如下apiVersion: batch.acai.io/v1 kind: TrainingJob metadata: name: bert-finetune spec: queue: high-priority priority: 100 taskGroups: - name: trainer replicas: 4 # 需要4个Worker template: spec: containers: - name: trainer image: pytorch:2.0-cuda11.8 resources: limits: cpu: 8 memory: 32Gi nvidia.com/gpu: 1 # 申请1张GPU command: [python, train.py] policies: minResources: # 最小资源需求不满足则不调度 cpu: 4 memory: 16Gi nvidia.com/gpu: 1当这个任务提交后Volcano调度器会检查high-priority队列的配额和集群资源如果满足minResources则尝试将4个Pod作为一个整体进行调度。如果当前资源不足任务会在队列中等待直到有足够资源或被更高优先级任务抢占。2.3 端到端流水线编排连接数据与计算的“管道”有了“水源”数据湖和“动力”调度器还需要“管道”将它们按正确顺序连接起来这就是流水线编排。我们评估了Airflow、Kubeflow Pipelines和Argo Workflows最终选择了Argo Workflows。选型考量云原生原生Argo Workflows以K8s CRD方式定义工作流每个步骤就是一个Pod与我们的基础设施哲学完全一致集成度最高。动态与灵活性支持通过Python DSL定义复杂依赖、循环、条件分支并且能在工作流运行中动态生成后续步骤非常适合机器学习中基于上游输出结果决定下游操作如根据评估指标决定是否部署的场景。可视化与调试提供清晰的可视化界面能直观看到每个步骤的状态、日志和输入输出排障效率高。一个典型的模型训练流水线在Argo中定义如下简化apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: ml-pipeline- spec: entrypoint: main-pipeline templates: - name: main-pipeline steps: - - name: get-data template:>from feast import Entity, FeatureView, ValueType from feast.infra.offline_stores.contrib.iceberg_offline_store.iceberg_source import IcebergSource # 定义实体例如用户 user Entity(nameuser, value_typeValueType.INT64) # 定义数据源指向Iceberg表 user_stats_source IcebergSource( tableiceberg_catalog.db.user_aggregates, timestamp_fieldevent_timestamp, ) # 定义特征视图 user_stats_fv FeatureView( nameuser_statistical_features, entities[user], ttltimedelta(days7), # 特征有效期 sourceuser_stats_source, onlineTrue, # 同时发布到在线存储 features[ Field(nameavg_order_value_7d, dtypeFloat32), Field(namelogin_count_24h, dtypeInt64), ] )定义完成后执行feast apply这个特征视图的元数据就被注册到了Feast的中心注册表。接下来可以通过两种方式获取特征离线训练Feast会根据特征定义自动从Iceberg数据湖中拉取对应时间段的特征数据生成训练样本。在线推理Feast提供了一个低延迟的在线特征服务。我们使用Redis作为在线存储。当推理服务需要某个用户的特征时只需向Feast Serving发送一个包含user_id和特征名的请求就能在毫秒级内获得最新的特征值。实操心得特征回溯这是特征平台最复杂的部分。Feast通过timestamp_field和ttl配置能够正确地进行时间点对齐确保训练时用的特征一定是模型“当时”能看到的数据避免未来信息泄露。在线-离线一致性保证离线训练时计算的特征与在线推理时服务的特征完全一致是特征平台的命脉。我们通过将特征计算逻辑封装成统一的特征转换器Python函数或SQL模板并确保离线的Spark批处理作业和在线的实时计算如Flink都调用同一套转换逻辑来实现。3.2 实验追踪不只是记录指标更是复现的基石没有完善的实验追踪机器学习团队很快就会陷入混乱。我们要求所有训练任务都必须将实验数据记录到MLflow中。但我们的用法不止于记录。深度集成与自动化记录 我们在训练镜像中预置了MLflow的Python客户端并通过环境变量自动注入实验名称、运行ID等信息。在训练脚本中只需简单几行import mlflow mlflow.set_tracking_uri(http://mlflow-server:5000) mlflow.set_experiment(experiment_name) with mlflow.start_run(): # 自动记录代码版本Git Commit mlflow.log_params({learning_rate: 0.001, batch_size: 128}) # 记录指标 for epoch in range(epochs): train_loss ... mlflow.log_metric(train_loss, train_loss, stepepoch) # 记录模型文件自动上传到后端存储 mlflow.pytorch.log_model(pytorch_model, model) # 记录关键制品如混淆矩阵图片 mlflow.log_artifact(confusion_matrix.png)更重要的是我们通过平台自动记录了训练任务的环境上下文使用的Docker镜像ID、启动命令、分配的GPU型号和数量、任务队列优先级等。这些信息与MLflow的运行记录关联使得任何一个实验都能被精准复现。模型注册与版本管理 训练出的模型通过MLflow的Model Registry进行管理。模型从Staging到Production的晋升需要与CI/CD流程和评估结果联动。我们设置了一个规则只有当模型在预留的测试集上达到特定指标且通过代码评审后才能被标记为Production。部署系统会监听Model Registry一旦有模型被标记为Production就会自动触发部署流水线。3.3 模型部署与服务化从文件到API的最后一公里模型部署是另一个容易“踩坑”的地方。我们的目标是实现一键部署、自动扩缩容、灰度发布和全面监控。服务化模式 我们采用KServe现更名为InferenceService作为模型服务的标准。它将模型封装成一个标准的Kubernetes Service并自动管理其生命周期。对于PyTorch/TensorFlow模型KServe提供了开箱即用的预测器对于自定义模型可以通过实现一个简单的Pythonpredict函数来创建自定义服务。一个典型的KServe InferenceService定义apiVersion: serving.kserve.io/v1beta1 kind: InferenceService metadata: name: sentiment-analysis annotations: # 指定自动扩缩容策略 autoscaling.knative.dev/target: 80 spec: predictor: model: modelFormat: name: pytorch storageUri: s3://acai-model-registry/prod/sentiment/v2 resources: requests: cpu: 2 memory: 4Gi nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1 minReplicas: 2 maxReplicas: 10这个配置定义了一个情感分析服务它从数据湖S3兼容加载模型默认启动2个副本每个副本分配1张GPU并会根据请求并发量在2到10个副本之间自动伸缩当平均CPU利用率达到80%时触发。灰度发布与流量管理 我们利用KServe的Canary Rollout功能实现灰度发布。当新版本模型v3准备上线时可以这样配置spec: predictor: canaryTrafficPercent: 10 # 10%流量导到新版本 model: storageUri: s3://.../sentiment/v3 containers: ... traffic: 90 # 90%流量仍由旧版本v2服务平台会创建两套Pod并通过Service Mesh我们使用Istio精确地将10%的线上流量路由到v3版本其余90%到v2版本。同时平台会收集两个版本的实时预测指标延迟、成功率、业务指标并在监控面板上对比展示。只有确认v3版本各项指标稳定优于或持平v2后才会逐步扩大流量比例直至完全切换。4. 平台运维与稳定性保障实战一个再先进的平台如果不稳定、不可靠也无法获得用户的信任。在ACAI平台的运维中我们重点关注监控、成本优化和灾难恢复。4.1 全方位监控与可观测性体系监控分为四个层次基础设施层、平台服务层、任务运行层、模型服务层。基础设施层使用Prometheus监控K8s集群、节点CPU、内存、磁盘、网络、GPU使用DCGM Exporter获取GPU利用率、显存、温度等详细指标。平台服务层监控所有核心组件的状态如MLflow Server、Feast Serving、Redis、MinIO的可用性、请求延迟和错误率。任务运行层这是最复杂的一层。我们为每个TrainingJob和Workflow实例都生成了唯一的追踪ID。通过Fluentd收集所有任务容器的日志并发送到Elasticsearch。在Grafana中我们可以通过追踪ID一站式查看某个训练任务在所有环节数据准备、训练、评估的日志、资源消耗曲线和产出指标。模型服务层KServe集成了Prometheus指标导出。我们监控每个模型服务的QPS、预测延迟P50, P90, P99、错误率。同时通过一个旁路服务对模型的预测结果进行抽样并计算与离线评估时一致的业务指标如AUC进行线上-线下指标一致性监控这是发现数据漂移和概念漂移的关键。一个关键的告警规则示例# Prometheus告警规则检测模型预测延迟飙升 - alert: ModelInferenceHighLatency expr: histogram_quantile(0.99, rate(kserve_predictor_request_duration_seconds_bucket{service_namesentiment-analysis}[5m])) 1 for: 2m labels: severity: warning annotations: summary: 高延迟预警服务 {{ $labels.service_name }} P99延迟超过1秒 description: 当前P99延迟为 {{ $value }} 秒。4.2 成本控制与资源优化策略在云原生环境下资源闲置是最大的成本浪费。我们采取了多项措施弹性资源池对于训练任务队列我们与云厂商合作在K8s集群中配置了弹性节点池。当队列中有任务排队时自动伸缩组CA会自动扩容出包含GPU的节点当节点空闲一段时间后又会自动缩容。这为处理突发的大规模训练任务提供了可能同时避免了长期保有昂贵GPU实例的成本。任务优先级与抢占如前所述通过Volcano的队列和优先级机制确保高价值任务如线上模型迭代能快速获得资源必要时抢占低优先级任务如内部研究性实验。Spot实例利用对于容错性高、可中断的训练任务如超参搜索我们将其调度到由云厂商Spot实例抢占式实例组成的节点池中成本可以降低60%-70%。任务被中断时Volcano调度器会将其重新加入队列等待资源。资源配额与预算为每个团队或项目设置资源配额CPU/内存/GPU小时数并在平台界面中展示实时消耗和预算进度培养团队的成本意识。4.3 灾难恢复与数据备份方案平台的高可用建立在各个组件的冗余设计上。对于有状态服务我们制定了详细的备份策略MinIO数据湖启用纠删码存储模式并提供跨可用区的数据冗余。同时对关键的Iceberg元数据表进行定期快照并备份到另一个区域的对象存储中。MLflow后端存储MLflow的元数据存储在PostgreSQL中我们配置了主从复制。模型文件存储在MinIO中同样享受纠删码保护。特征存储在线层Redis采用Redis Cluster模式并定期将快照备份到MinIO。由于在线特征主要用于加速推理即使完全丢失也可以通过离线特征管道在几分钟内重建当然会伴随短暂的性能下降这定义了我们的恢复点目标。我们的恢复演练每季度进行一次模拟数据库故障或区域中断验证从备份中恢复核心服务的能力。5. 平台落地过程中的挑战与应对实录搭建平台的过程绝非一帆风顺。以下是几个最具代表性的挑战及我们的解决方案。5.1 挑战一数据湖性能瓶颈与查询优化问题初期数据科学家抱怨从Iceberg表查询数据特别是关联多张大表速度很慢有时甚至超时影响了探索效率。根因分析小文件问题上游数据接入任务频繁写入产生了海量小文件导致元数据膨胀查询时Listing操作耗时极长。缺乏数据组织数据按日期分区后没有进一步排序或分桶查询时需要扫描大量不必要的数据。计算引擎选择直接使用Presto/Trino进行交互式查询对于复杂的多表JOIN和UDF操作性能不佳。解决方案实施文件压缩任务我们开发了一个定时的Spark作业监控Iceberg表的小文件数量当超过阈值时自动触发rewrite_data_files操作将小文件合并成大文件。优化数据布局在数据入湖时根据常见的查询模式如经常按user_id过滤在分区的基础上增加了Z-Order排序。例如对(event_date, user_id)两列进行Z-Order排序可以使得按user_id查询时的数据局部性大大提升。-- 在数据写入后执行优化 CALL iceberg.system.rewrite_data_files( table db.table, strategy sort, sort_order event_date, user_id )引入查询加速层对于核心的特征表我们使用DuckDB作为加速引擎。将Iceberg表中高频访问的数据子集如最近30天同步到DuckDB的内存或本地SSD存储中。数据科学家在探索时平台会自动将查询路由到DuckDB获得亚秒级的响应对于全量历史分析则回退到Trino。这本质上是一个自动化的“热数据缓存”策略。5.2 挑战二GPU资源争抢与隔离问题问题多个团队共享GPU集群时经常发生资源争抢。更棘手的是有用户提交的任务不规范占满GPU显存却利用率极低导致其他任务无法调度。解决方案推行“资源声明”规范我们强制要求所有TrainingJob必须准确声明resources.limits。平台提供了资源预估工具帮助用户分析其训练脚本的峰值显存和GPU利用率给出建议值。实施GPU共享与隔离我们部署了NVIDIA MIGMulti-Instance GPU技术将物理A100 GPU划分为多个独立的“实例”如7个5GB的实例。对于中小型训练任务可以调度到某个MIG实例上实现硬隔离避免相互干扰。同时对于推理服务我们使用NVIDIA Triton Inference Server它支持动态批处理和并发模型执行能显著提升单个GPU的利用率。引入“资源超售”与回收机制对于低优先级的开发调试任务我们允许其声明较少的资源如0.5张GPU底层通过GPU时间片共享的技术实现超售。同时平台监控所有运行中任务的GPU实际利用率对于长时间如30分钟利用率低于20%的任务会自动发送警告并在一定时间后强制挂起释放资源给排队任务。5.3 挑战三模型部署后的漂移与衰减问题模型上线时AUC很高但几周后线上效果逐渐下降而离线评估指标却依然良好。根因分析这是经典的模型漂移问题包括数据漂移输入数据的分布发生变化和概念漂移输入与输出的关系发生变化。解决方案建立模型性能的持续监控与闭环反馈系统。实时数据统计监控在特征服务Feast Serving和模型服务KServe的入口处我们嵌入了轻量的统计模块实时计算并上报关键特征的分布如均值、方差、分位数与训练期的特征分布进行对比如计算PSI群体稳定性指标超过阈值即告警。预测结果抽样与标注我们搭建了一个轻量的数据标注流水线定期对线上模型的预测结果进行抽样并发送给人工或规则系统进行标注。将这批带有真实标签的新数据作为“监控数据集”的一部分定期计算模型当前的线上AUC等指标。自动重训练触发平台监控上述指标。当PSI超过阈值或监控数据集的AUC下降超过预定范围时会自动触发一个“模型诊断”工作流。该工作流会使用最新数据重新评估现有模型并可能自动创建新的训练任务使用包含新数据的数据集进行增量训练或全量重训练。新训练出的模型会进入标准的评估和灰度发布流程。这个过程将模型从“静态部署物”变成了一个能够自我感知、自我调整的动态系统真正实现了MLOps的闭环。6. 总结与展望平台化思维的价值回顾ACAI平台的构建历程最大的收获不是技术组件的堆叠而是平台化思维对团队研发模式的深刻改变。数据科学家不再需要关心集群运维可以更专注于算法创新工程团队的发布从“月”缩短到“天”模型迭代的速度和可靠性都得到了数量级的提升。平台本身也在不断进化。我们正在探索的方向包括Serverless Feature Engineering让特征计算任务也像函数一样按需运行、更智能的Hyperparameter Optimization与调度器深度结合动态分配资源给最有希望的参数组合、以及大语言模型LLM的全流程支持从提示词管理、微调、评估到服务化部署。构建这样一个平台绝非一日之功建议从最痛的痛点开始以一个具体的业务场景切入先搭建最小可用的闭环再逐步扩展功能和规模。记住工具是为人服务的最好的平台是让用户几乎感觉不到其存在却能自由驰骋的那个。