本学期工作内容主要分为四个阶段:
第一阶段:调研
第一阶段主要集中在调研工作上。
第一周
了解了MLOps的概念和原理;
对比了目前MLOps主流平台(MLFlow,KubeFlow等)的基本架构、优劣和特点等;
简要概括,就是:
- KubeFlow是一个基于k8s的ML workflow平台,核心是容器编排系统,为机器学习的开发、生产、及部署提供了一整套完整的解决方案;
- MLFlow是一个Python程序包,核心是实验的跟踪记录和模型的版本控制;
第二周
主要调研了:
- KubeFlow是如何实现ML的工作全流程的
- KubeFlow的内部工具,如Notebook,Pipeline,Katib(AutoML)等;
- KubeFlow的重要外部工具,如Elyra,Kserve等;
简要概括,就是:
将ML的工作流程划分为实验阶段和生产阶段:
-
在实验阶段,通过Notebook编写代码;借助Katib(AutoML)、Training Operators等模型训练并超参调优;最终构建Pipeline编排出一整套工作流程;
-
在生产阶段,通过编排好的Pipeline自动完成数据处理、模型训练及调参、模型存储及上传的全过程;后期还可以通过Metadata,TensorBoard等对模型结果可视化来观察模型的表现。
第三周
进一步了解KubeFlow的两大核心组件:Pipeline和Training Operators;
主要调研了:
- KubeFLow Pipeline(KFP)原生方法的工作逻辑和编排步骤
- KFP组件的构建和操作的主要方法;
- 阅读Training Operators(主要是TFjob Operator)的源码分析博客了解工作机制和存在问题
简要概括,就是:
-
KubeFlow Pipeline的各组件都工作在相互独立的容器环境内,但可以通过为各组件挂载相同PV卷实现文件存储的共享;
-
KFP组件的构建方法有三种:基于Python函数,容器化、自定义容器组件
-
为了在k8s上拉起多个训练进程,需要在k8s上创建多个Job,因此需要为不同Job分别编写对应的Yaml文件。而这些Yaml文件结构一致,大部分内容相同;因此可以把变化的部分抽象出来作为参数,在运行前动态渲染并生成;这就是TFJob的工作机制;
-
虽然TFJob已经对分布式训练做了很好的支持;但还未能很好地实现负载均衡;
第二阶段:学习
第二阶段主要为之后的具体实验做准备。
因为之前没有这方面代码经验,甚至不了解Docker是干嘛用的;因此第二阶段在努力补齐这方面知识。
第一周
学习了Docker的基础概念和Docker的基本操作方法(DockerFile的编写,容器的构建、拉取和推送,容器与外界的互联互通等)。
第二周、第三周
学习了K8s的工作机制和基础概念;Yaml、Pod、Job、ConfigMap等的基本操作方法;并简单了解了Deployment、Service、Ingress、PV、PVC等基础知识。
第四周
利用实验室的三台服务器搭建了多节点的k8s集群,并尝试基于该集群部署KubeFlow平台。
第三阶段:实践
第三阶段的工作主要是基于KubeFlow平台,逐步跑通Pytroch Mnist任务的Pipeline;并在该过程中,完成对KubeFLow各项功能组件的调研和实践;形成对KubeFlow的一个全面理解和认识。
第一周
阅读了KFP文档,并完成quickStart示例。
第二周
针对Pytroch Mnist任务,构建了由单个组件组成的Pipeline。
主要工作:
- 写了份模型训练的代码,并与Mnist数据集、requirements.txt等一同打包入镜像;
- 使用容器化方法构建组件,搭建Pipeline,创建Experiments并提交至KFP后端执行;
第三周
构建一个完整的Pipeline。
- 将流程拆分为三个步骤:
- 数据下载与预处理
- 模型训练
- 模型上传
针对各步骤构建组件。
- 了解并掌握MinIO的基础用法,将MinIO作为之后数据集存储,模型存储的对象存储服务器。
- 将Mnist数据集存放在MinIO上,由第一个组件下载数据集至本地并对数据预处理。
- 第二个组件使用下载好的数据对模型训练并保存。
- 第三个组件将模型从本地上传至MinIO。
- 动态创建PV卷,并挂载在所有组件下;即,所有组件的容器执行环境虽然是相互独立的,但都工作在同一个文件存储环境上,实现了各组件文件的共享互通。
第四周
使用Elyra替代KFP的原生方法构建完整Pipeline,并比对二者的不同和Elyra的不足。
简要总结,如下:
组件的构建方法对比
- KFP原生方法主要通过容器化的方法(即将主代码、代码运行环境、代码的文件依赖打包成镜像,构建KFP组件);
- 而Elyra可以直接将py或ipynb文件(notebook)作为节点,在节点属性中选择合适的镜像(如python:latest等)作为该代码的运行环境即可;还可以在节点属性中编辑该节点的文件依赖;因此在开发阶段,Elyra更简便、更便于代码修改、迭代。
Pipeline各组件的文件存储共享方法对比
- KFP原生方法主要通过为各组件挂载动态创建的PV卷实现组件的文件存储共享;
- 而Elyra则会在指定MinIO中动态创建bucket(存储桶)作为Pipeline各节点的文件存储共享;只要在节点属性定义该组件的output Files,Elyra就会将该组件容器内的文件输出至bucket中永久存储,供其他节点读写。
但Elyra由于外网等问题,提交Pipeline后一直报错,该问题将在下周解决。
第五周
主要使用了KubeFLow Kaitb(AutoML),并提出了构建Elyra私有镜像的想法及建议。
使用KubeFlow Katib
通过Katib,将流程中的模型训练模块升级为对模型的训练及超参调优。
分别使用了三种方法:
- 原生方法:将模型的训练代码构建为镜像,编写并部署katib的Yaml文件
- Katib Python SDK:使用Python SDK编写Yaml文件(便于复用Yaml中相同的部分,抽象出Yaml中变化的参数),并跟踪查看Katib当前的实验数据;
- Tune API:基于Python SDK的进一步高层封装:自动将我们的训练代码包装成镜像,并构建相应的Yaml文件部署。
Elyra私有镜像的构建
通过分析Log信息和部分源码,发现Elyra在跑Pipelines时,每次启动节点容器前,都要在github上下载多个文件(如requirements.txt),并pip install 需要的包;
与拓麦交流后,最后将这几份文件放入了本地MinIO,修改了Elyra的文件下载路径;修改了pip源;重新构建了ELyra的私有镜像。
同时,在编辑Elyra的节点属性:该节点的运行环境镜像时,全部改用本地镜像库中的镜像。
Elyra的网络问题得以解决。
第六周
主要使用了Training Operator(Pytorch Job),Artifacts(模型结果可视化);并基于ELyra自定义组件的功能,探讨了未来实现低代码开发的可能解决方案。
使用Training Operator(Pytorch Job)
分别使用了三种方法:即原生方法(Yaml部署)、Python SDK、高级API,在KubeFlow上部署了模型的分布式训练。体验与Katib相近。
使用Artifacts(可视化)
分别使用了两种方法:
- 编写指定元数据的Json文件
- 使用可视化API
但Artifacts有大问题:
- 第一种方法:繁琐;首先通过csv文件保存可视化数据,再将该csv上传至s3等云端;然后在Json的source属性中指定该csv的url路径。
- 第二种方法:渲染不稳定;
- Artifacts可视化的类型仅有:表格、ROC曲线、混淆矩阵(confusion_matrix)。
因此,不建议使用;可以选择TensorBoard等工具替代。
Elyra自定义组件带来的启发
Elyra不仅支持直接py、ipynb文件直接作为节点;事实上,将kubeflow的组件、管道编译为yaml文件保存并载入到Elyra后,也可以作为节点操作。
有三种方式载入组件/管道节点:指定组件Yaml文件的本地路径;指定组件Yaml文件的url路径;指定存放有一个或多个Yaml文件的目录的本地路径。
因此,我们可以针对ML工作全流程,总结出可复用的部分,抽象出需要自定义的部分作为组件参数,制作出一套完整的通用组件;使低代码开发成为可能。
第四阶段:SageMaker
在Amazon SageMaker复刻了使用KubeFlow的全过程,了解了SageMaker的Pipeline构建逻辑,并比对了二者的异同和优劣。
复刻使用KubeFlow的全过程
- 分布式训练
- 超参调优
- Pipeline编排
SageMaker的Pipeline构建逻辑
KubeFlow内部提供的开发组件仅有:Notebook,Training Operator,Katib;Kserve作为模型部署工具,仍作为外部组件需要额外安装。
因此,KubeFlow的Pipeline流程与SageMaker Pipeline相比,往往更简单: 数据预处理 —— 模型训练及调参 —— 模型存储
SageMaker则提供了一套更为完备的工具链,它的Pipeline流程主要为:
- 数据下载与预处理
- 模型的超参调优及训练
- 选取调参结果最优的模型,在测试集上评估该模型(并生成实验数据,便于在下一步数据可视化)
- 若评估结果满足设定的条件,注册该模型(若配置了评估文件,在表中可以查看到模型参数、可视化结果等)
- (可选) 部署、测试模型
SageMaker与KubeFlow在编排Pipeline时的异同
SageMaker编排Pipeline
在编排Pipeline前,需要创建Session会话,动态获取s3 bucket(存储桶),用于Pipeline各组件的的文件输入输出。
其实它的编排逻辑也很清晰,在构建每个组件时,需要定义的最重要的三个部分:
inputs;outputs;执行文件。
其中,inputs和outputs需要定义它的source和destination,即这个组件的输入和输出"从哪来,到哪去":它需要从bucket中上加载inputs到容器内;执行完成后,再将输出从容器放入至bucket中;
执行文件主要分为两种情况:
- 对于数据预处理、模型评估等组件,通常将代码上传至bucket中,由组件加载uri;
- 对于模型训练、调参组件,需要将模型训练代码包装成镜像,并在Estimator中写明它的image uri(可以是Amazon的ECR或者dockerhub)
这使组件标准化的同时,也不免造成了开发时的繁琐。
Elyra编排Pipeline
相比之下,Kubeflow的Elyra就方便很多:它使pipeline下的所有节点都工作在一个动态创建的Minio bucket上;如果你希望某个组件的输出可以被其他组件获取,只要在该节点属性的output files写明它的容器内路径,Elyra将其永久保存至bucket内。
SageMaker的优势
- 训练资源的弹性伸缩和动态分配;
- 可视化的Json文件易于编写;
- 可以在模型注册表内查看模型像相关信息,勾选多个模型比对实验结果;
- 模型易于部署;
- 在Pipeline的UI界面上点击节点的输出信息,如超参调优节点,可以直接看到当前调参实验进度和结果;
- 提供Condition Step(即Pipeline级别的if else),实现了条件触发。
SageMaker的不足
运行实例的限制
SageMaker在构建数据预处理,模型训练,模型评估等组件时,需要指定该组件的运算实例的类型;
但有两种限制:
- 某些运算实例不可用于该组件的任务;
- 某些运算实例在AWS账号上限制了可用资源数量;
模型训练需要构建镜像
SageMaker模型训练的核心类为Estimator,在创建时Estimator要填image_uri;这意味着我们写完模型训练代码,还得把它封装成iamge提交;不便于开发时训练代码的迭代。
当然,Amazon的部分API,如Pytorch类,还是基于Estimator做了一定的封装:通过entry_point指定代码文件和选择pytorch运行环境镜像,能自动帮我们打包成image并提交。