暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

基于云原生打造分布式机器学习平台(分布式训练篇)

rivernjl 2022-08-17
1658

接上一章: 基于云原生打造分布式机器学习平台(介绍篇)


分布式训练在机器学习中是一个不可缺少的部分模型训练大都伴随着大量的数据需要进行计算单机的资源往往是有限的利用多机资源分布式进模型训练是加速模型训练的一个重要的手段


自定义开发环境-秒级创建Jupyter开发环境提升工作效率

 

(Jupyter界面图)


为什么需要Jupter+自定义容器环境


1: 提供易用的IDE工具辅助开发提高开发效率(在很多的开发测试环境依赖于linux等机器环境很多情况下的同学要么通过linux无图形化界面进行开发或者本地开发然后再上传代码配置等这样的开发效率非常低下)


2: 隔离用户空间减少开发过程中相互影响


3: 隔离环境满足不同开发需求对不同环境的要求(如一些库对GCC版本要求较低有些库要求高他们之间相互影响)


4: 对环境做镜像达到秒级创建新环境相比起在实体机器上重建环境少则一天多则可能一周都搞不定一个复杂的环境比如机器迁移故障等等环境重建比如工作交接新同学入职开发环境搭建只要一键化就可以做到


  我们通过docker将用户的开发环境进行隔离针对于不同种类型的环境构建出一个基于jupter基础镜象的开发镜像环境这样使用者就可以通过平台选择一个自已相适应用开发镜像一键创建出一个容器环境用户只需要通过WEB页面就可以打开Jupyter进行代码开发了这样即能做到环境的隔离也能做到用户之间开发空间的隔离每个用户都可以创建自已的jupyter容器大家开发上互不干扰各种环境的依赖之间也是互不干扰如果机器迁移或者机房迁移用户只需要一键重建环境就可以了




对于前台用户只需选择镜像填写机器要求比如CPU\GPU\内存\磁盘信息后就能创建一个环境对于后台来说要解决的问题如下:


 1: 根据jupter的镜像创建一个pod

 2: 构建jupyter启动脚本在容器启动时将jupyter的进程启动起来

3: pod启动后用户需要能够访问到这个pod中的jupyter所以需要构建一套网络访问的服务或者叫CRD,最后将让upyter的访问地址能够在办公网络进行访问


首先我们来看一下整个机器学习平台的网络访问结构



从外部网络需要访问到k8s内部的服务需要通过外部的负载均衡负载到k8s的一些结点上这些结点绑定着一个静态的端口Nodeport),通过这个端口能将请求通过kube-proxy转发到对应的istio-ingressgateway最后由istio配置的网关及VirtualService将流量转到对应的service,最终通过service后就通访问到容器中Jupter的服务了

 

Gateway的配置是静态的平台需为了保证每个用户创建的每个jupyter pod都能独立进行访问所以需要针对每个jupyter pod 动态创建service\VirtualService进行绑定最终达到可以动态创建jupter的效果


分布式存储-分布式训练的基石

在分布式训练过程中训练的容器次源是由K8S进行调度分配置工作容器被分布在集群中的哪一台机器使用者是预先不知道的这样我们就需要有一种介质来存储训练过程中所需要的代码配置数据等等以便于在训练过程中任何一个容器都可以访问它



在系统框架中已经介绍过了平台采用的是ceph做为平台的分布式存储,同时与rook进行集成部署在k8sceph包含了包括对象存储、块设备、文件系统显然这三种模式中文件系统存储便适合平台的使用方式主要有如下几个原因


1: ceph文件系统能通过系统内核的方式进行挂载使用者能像使用本地文件系统一样访问分布式文件系统,对于使用者来说无感知使用成本几乎为0对于那种以前都是单机模式开发的程序迁移成本会大大降低


2: 文件通过操作系统内核挂载后期如果更换文件系统对于整个平台及平台的用户是无感知的系统扩展方便


平台按分类在分布式文件目录下创建子目录同时按分类创建静态存储卷比如用户空间存储目录/xxx/xx2/user,会在k8s上创建一个PVPVCwoker容器创建时将容器下的目录mount到这个pvc


mount的目录主要分成几种模式一种是用户级别的目录这个目录下的文件只有用户  自已可以访问还有一种目录是项目组共享目录这个目录是同属一个项目组下的用户才可以访问另一种目录是全局共享目录这个目如下的数据是所有用户都可以访问每个运行的任务都会规属到个人项目组这样每个运行的task的容器在创建时都会将当前task所归属的项目组用户所属的目录挂载到运行容器中去

 


  解决了存储的问题后我们就能在任何容器中像访问本地文件一样访问分布式文件系统上相同文件了这样我们写一份代码我们不用关心容器在创建在哪台实体机器上都可以进行访问了


分布式训练-为百G以上级别数据进行模型训练护航


分布式训练基础知识介绍

  本文所说的训练,指的是利用训练数据通过计算梯度下降的方式迭代地去优化神经网络参数,并最终输出网络模型的过程。在单次模型训练迭代中,会有如下操作:

  

 首先利用数据对模型进行前向的计算。所谓的前向计算,就是将模型上一层的输出作为下一层的输入,并计算下一层的输出,从输入层一直算到输出层为止。其次会根据目标函数,我们将反向计算模型中每个参数的导数,并且结合学习率来更新模型的参数。


而并行梯度下降的基本思想便是:多个处理器分别利用自己的数据来计算梯度,最后通过聚合或其他方式来实现并行计算梯度下降以加速模型训练过程。比如两个处理器分别处理一半数据计算梯度 g_1, g_2,然后把两个梯度结果进行聚合更新,这样就实现了并行梯度下降。

训练并行机制

  模型训练并行机制有三种但是我们最常见的方式有2数据并行与模型并行其中目前工业界中基本的训练框架实现都是基于数据并行的方式。


 分布式训练最大的优势就是可以利用集群多机的资源并行的进行计算每一台机器承载着整个计算的一部分也就是说一份大体量的工作由一堆人来做每个人同时做其中的一小块事情目前最常见的并行计算方式有2

 

模型并行集运行的集群中每台机器上计算着相同的数据但是每台机器上运行模型中的不同计算部分

数据并行所有机器上的模型是相同的但是是需要训练的数据按机器进行拆分每台机器计算数据中的一部分计算完后再将结果进行合并

目前工业界最主流运用最广泛的模式是数据并行计算


模型并行计算的实现架构


Parameter server 模式

 


PS架构下所有的参数信息都存放在参数服务器中参数服务(PS)在集群中可以是多台Worker机器为工作结点Worker结点首先从PS上获取参数信息然后根据训练数据计算梯度值计算完成后将计算的梯度更新到PSPS获取Worker过来的梯度值后对梯度求平均最后返回给到Worker


Allreduce 模式

 


AllReduce模式是所有的机器上都具有相同的模型参数信息每台机器计算一部分数据得到一个梯度值然后执行allreduce操作使得所有node结点都得到其它结点上的所有梯度值最终更新本地的梯度值AllReduce每轮迭代都需要同步所有参数对于网络来说是一个大的冲击后来在2017年百度在tensorflow 上实现了基于 Ring-Allreduce 的深度学习分布式训练Rring-AllReduce大大减少了网络的压力


参数服务器适合的是高纬稀疏模型训练,它利用的是维度稀疏的特点,每次 pull or push 只更新有效的值。但是深度学习模型是典型的dense场景,embedding做的就是把稀疏变成稠密。所以这种 pull or push 的不太适合。而 网络通信上更优化的 all-reduce 适合中等规模的深度学习。又比如由于推荐搜索领域模型的 Embedding 层规模庞大以及训练数据样本长度不固定等原因,导致容易出现显存不足和卡间同步时间耗费等问题,所以 all-reduce 架构很少被用于搜索推荐领域


分布式模型训练

 

上面介绍完分布式训练的一些基础知识后我们来看平台是如何与这些框架结合进行模型训练在机器学习平台上主要选取如下2种模式来支持深度学习模型的分布式训练:


基于RingAllReduce分布式训练

Horovod主要是基于RingAllReduce的架构来进行分布式训练Horovod 支持TF/Pytorch/MXNet等训练框架进行模型训练,在图像音视频文本等等分布式训练场景下使用非常广泛对原框架(TF/Pytorch等等)的入侵很小使用起来简单方便对原代码做很小的改动就能进行分布式训练



选择了使Horovod进行训练后需要有一套机制来组成RingAllreduce通讯结构可以看下图这时我们需要有一套机制去创建容器同时让他们组成一个环境环形的通讯结构

 


我们首先来看一下Horovod的运行示例如果是在实体机上执行的话只需要设置分布式下多台机器的SSH免登录然后在其中一台机器上执行下面的代码整个分布式就能正常的运行起来了



但是在K8S上我们的容器是动态创建的IP地址是动态变化的也不好去动态设置这几台容器间的SSH(我个人是没有试过如果在容器上设置多台机器的SSH免登录同时能在多台机器间去通过SSH执行脚本)执行完成或者异常后还需要对这一批容器进行回收等等操作这时我们就需要一套这样的机制来实现上面说的这些功能这时KubeFlowMPI-Operator就能派上用场了


MPI-Operator

 

MPI-Operator根据用户定义的CRD文件生成一个ConfigMap

 


我们可以看到这个ConfigMap里边主要是生成了三部分我们现在主要关注的是hostfilekubexec.sh,MPI-Operator会创建2种角色的容器: launcherworker,launcher在所有的worker容器启动后调用horovodrun命令,在上面官方广档中默认是通过SSH方式向集群中的其它容器发出执行远程命令launcherMPI-Operator会设置launcher的环境变量OMPI_MCA_plm_rsh_agent


 

这样最终在执行过程中会在launcher执行kubeexec.shworker发起命令执行用户脚本同时MPI-Operator还管理运行过程当中成功与异常时容器的退出等等这样在机器学习平台侧则需要构建MPI-OperatorCRD


1: 构建文件挂载信息将分布式存储挂载到Horovod的容器中去以保证在任何容器中能访问到训练脚本代码和配置训练数据等等

2构建资源调度规则如结点分配规则信息如如果有申请到GPU的资源那则设置worker容器都分布到GPU的结点上去如果只需要CPU资源则设置worker分配到CPU的结点上去同时会按照平台的资源隔离策略如资源有按照分组进行隔离测将worker分布到当前分组所在的资源结点上去运行

3设置pod之间的亲和策略比如是GPU机器的话尽量将容器分布到相同的结点上减少中间的一些网络损耗


平台要解决的问题是通过上面一个简单的配置就能实现复杂的分布式训练过程


 

开始提交训练任务运行分布式任务


CPU任务执行 


GPU任务执行


提交后的效果如上平台会设置将launcher 尽量调度到cpu机器如果没有CPU机器则调度到GPU的机器同时只分配到CPU的资源


基于PS架构的分布式模型训练

虽然基于RingAllReduce的模式在训练的性能方面会比PS架构要好很多但是上面我也有提到过在推荐广告搜索等这种超大规模场景及需要做在线实时训练场景下PS架构是很适应的所以在机器学习平台对这种分布式训练场景的支持是非常有必要的


     

     Multi-machine multi-GPU distributed parallel training.



PS架构下所有的训练参数信息保存在参数服务上参数服务是集群进行部署的这样的话在超大规模参数下单机的内存资源是无法满足训练的要求的特别像是在一些广告场景中大量的embedding造成参数规模很大


PS架构的实现是基于KubeflowTF Operator来实现的,TFPS训练模式下



从上面的图我们可以看到整个训练过程中会创建如下几种角色:


PS: 所有的参数存储的地方

Worker: 根据训练参数计算出梯度值然后把梯度传递给PS之后拿到了PS返回的最新参数并更新到本地并进行多轮的迭代计算

Chief一般来说可以用来单独保存模型代码执行点(比如执行构建Graph)日志记录等等比如下下部分代码只会在Chief上运行



这样我们就可以推断出TF Operator需要做的事情如下

1:创建PS\WORKER\CHIEF等角色的容器

2:根据这些角色创建的PODIP信息创建TF_CONFIG

3:在创建容器时候设置TF_CONFIG为容器的环境变量

4:在容器启动时执行用户脚本


TF-OPRATOR 的整个处理流程并不是太复杂对于机器学习平台来说就是创建TF Operator 对应的CRD


1: 构建文件挂载信息将分布式存储挂载到Horovod的容器中去以保证在任何容器中能访问到训练脚本代码和配置训练数据等等

2设置PSchief容器的信息2种容器只需要分配到CPU的机器上即可对于worker容器根据用户设置如果需要cpu则设置cpu资源信息如果需要GPU则设置GPU资源需求

4设置Node结点亲和度信息如当前项目组下有资源测将当前任务的容器设置要调到到当前分组的资源结点下

3设置pod之间的亲和策略


对于使用者来说只需要通过如下简单配置加上代码中的训练脚本配合就能就行分布式的训练了对于资源的创建回收网络的管理都交由平台来管理用户只需要关注自已的训练逻辑就可以了

 


资源调度

在分布式计算下我们需要申请大批量的机器进行训练但是在大部的场景情况无论是MPI+Horovod 或者是TF PS架构下都是需要等容器创建完后整个训练过程才会开始



如上图有一部分的worker申请到了机器了但是另外几个worker申请不到机器还一直处于Pending状态



这里我们查看luncher的状态还一直处理Init状态等待所有的worker Ok了后才开始作业这些如果一直申请不到机器已经起来的worker的资源就一直占用并浪费掉了特别是GPU的资源所以我们就需要一套资源调度框架来处理这些事情原官方有kube-batch但是kube-batch已经很多年不更新了对于目前很多的计算框架或者一些组件会有不兼容volcano是目录最火而且比较完善的调度框架


Volcanoschedulercontrollermanageradmissionvcctl组成:


Scheduler Volcano scheduler通过一系列的actionplugin调度Job,并为它找到一个最适合的节点。与Kubernetes default-scheduler相比,Volcano与众不同的 地方是它支持针对Job的多种调度算法。


Controllermanager Volcano controllermanager管理CRD资源的生命周期。它主要由Queue ControllerManager PodGroupControllerManager VCJob ControllerManager构成。


Admission Volcano admission负责对CRD API资源进行校验。

Vcctl Volcano vcctlVolcano的命令行客户端工具。


未完待续


文章转载自rivernjl,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论