新智元专栏
作者:王冠,Nauto (Playground Global) 首席机器学习工程师
【新智元导读】百度和 CoreOS 的联合开发的 PaddlePaddle Elastic Deep Learning(EDL)技术可以把一个机群的利用率提高到接近100%,而基于HPC的一般深度学习机群的使用率往往低于10%。本文作者调研了设计文档和源码,访问了EDL的主要设计者王益,并且在Playground的机群上部署了 EDL,以深度解读PaddlePaddle Elastic Deep Learning技术。
最近看到 Kubernetes 官方博客上发表特邀文章 http://blog.kubernetes.io/2017/12/paddle-paddle-fluid-elastic-learning.html,介绍了百度和 CoreOS 的联合开发的 PaddlePaddle Elastic Deep Learning(EDL)技术。文中试验表明这套技术可以把一个机群的利用率提高到接近100%。要知道基于HPC的一般深度学习机群的使用率往往低于10%。而机群成本是很高的。这样让一份投资收获多倍效益的技术可能让一家公司的计算投资每年减少数百万美元,很吸引在AI领域奋斗的团队。
可惜上文很简短。为了深入了解 PaddlePaddle EDL,我调研了设计文档和源码,访问了EDL的 主要设计者王益,并且在Playground的机群上部署了EDL。我把我的探索写成三篇文章
1. 深入解读 PaddlePaddle EDL
2. PaddlePaddle EDL 机群搭建
3. PaddlePaddle EDL 和无人驾驶技术
此文是第一篇。
一般云端的机器学习训练任务会包含几个master进程,一些参数服务(parameter server)进程 ,和比较多的训练进程(training process)。这些进程全部运行在云端机器集群里,有时需要和其它训练任务共享计算资源,经常还需要和其它的云端服务(比如web服务)共享资源。理想的状态是机器学习训练系统知道根据集群资源使用情况和各个任务的优先级动态地调整参数服务 进程和训练进程的个数,从而达到充分地利用集群CPU/GPU的目的。而这正是Elastic Deep Learning (EDL) 系统的设计目标。
EDL和HPA
Horizontal Pod Autoscaling (HPA)是Kubernetes提供的一种弹性调度机制。它的出发点是通过公平分配计算资源给某一个单一的计算任务中的各个Pod来实现分布式系统资源针对单一任务的最优化利用。在“训练深度学习模型”这个场景下,“某一个单一的计算任务”可能是训练一个识别图 像中物体的模型,而另一个“单一的训练任务”可能是训练一个语音识别系统。这两个训练任务可 能都需要部署在同一个集群里,部署时间有先后,对资源的需求也有不同。理想的情况是 autoscaling controller对每一个这样的训练任务所需的系统资源有一个全局的了解,然后按需分配 资源。可是HPA controller并没有对不同训练任务有那样一个全局了解。
另一方面,HPA的弹性调度是针对同种类型的计算任务(homogenous computing task)下的 Pods。但是深度学习系统里的training process和parameter server往往是在不同类型的Pods里 的。我们想要autoscale不同种类的Pods。 这些深度学习训练系统特有的需求导致使用Kubernetes的时候需要有特定的弹性调度解决方案, 而不能直接采用HPA。而这套特定的解决方案就是本文讨论的PaddlePaddle EDL。
PaddleEDL 的设计和实现
1.让Kubernetes支持定制的弹性调度机制
1)配置文件
Kubernetes本身就支持定制的资源管理机制。用户可以通过提交定制的resource declaration file 和controller file来实现对某种Pods的弹性调度。以下图为例,这个training_job.yaml保证了 controller会自动监管pservers,并且保证它们的数量在min-instance和max-instance之间。
在Kubernetes集群上,这个定制的资源可以通过kubectl create -f training_job.yaml命令获得。接下来,我们需要有个定制的trainingjobcontroller来调度这个资源。
定制的training job controller跑在一个Pod里,对集群资源有一个统一的了解,它通过Kubernetes API对集群资源起到监控和调度的作用。下图是training job controller配置文件的一个例子。
在Kubernetes集群上,这个定制的资源管理Pod可以通过kubectl create -f training_job_controller.yaml命令启动。
2)控制程序的实现
上面提到的定制化资源在Kubernetes里面目前有两种实现方式。一种是 Custom Resource Definition (CRD) ,由Kubernetes 1.7版本引入;另一种是 Third Party Resource (TRP) ,自Kuberenets 1.2版本引入,1.8版本中过时(deprecated),1.9版本中将不再支持。 PaddlePaddle项目现在用的是Kubernetes 1.6版本,所以实现的是TRP模式,今后将整合CRD模 式。
当前PaddlePaddle假设只有一个单独的training job controller在运行。(后续工作可能会考虑多个 controller运行的情况,比如根据某个leader selection机制来管理多个controller)
当前的training job controller依照下面的逻辑管理资源:
弹性调度算法
PaddlePaddle根据定制资源的配置文件(training_job.yaml)来判断某个job需不需要弹性调度, 而判断的标准是trainer和pserver的min-instance =/ max-instance。(当前PaddlePaddle只支持 trainer的弹性调度,还不支持pserver的弹性调度)
集群中GPU的调度
controller知道集群中一共有多少个GPU,当前有多少个闲置的GPU,并试图把闲置的GPU全部 分配给当前的训练任务。PaddlePaddle给需求GPU的训练任务定义一个“满足程度”的评分( fulfillment score),此评分的范围是[0,1]。PaddlePaddle会优先分配GPU资源给满足程度评分最低的训练任务。如果有分数相同的情况,则分别优先考虑GPU需求数,CPU需求数,内存需求数。如果有某个训练任务的GPU min-instance没有满足(除非cur-instance=min-instance),那么PaddlePaddle会把一个满足程度最高分的训练任务里的GPU资源拿出来分给它。如果满足程度分数最高的训练任务cur-instance=min-instance,则整个集群不再执行新的训练任务,新来的任务需等待。
集群中CPU的调度
CPU资源的分配和GPU思路相同。controller知道集群中一共有多少个CPU,内存,它们的负载情况;同时也知道训练任务对CPU的需求。同样的,CPU资源根据满足程度评分被按需分配。
具体的调度实现细节可参见https://github.com/PaddlePaddle/cloud/tree/develop/go/controller。
2.让PaddlePaddle支持容错
这里讨论PaddlePaddle的容错机制。原则上,在一个分布式训练任务里,如果master进程或者所 有的参数服务进程都死掉了,那么整个训练任务会被停掉,过一段时间被Kubernetes整个重启。 否则如果具体训练进程没有都死掉,则整个训练任务继续。我们来看看PaddlePaddle的错误恢复机制。
PaddlePaddle用etcd来记录训练进程的状态。etcd是高可靠性的分布式key-value存储,训练进程会定时把自身状态写进etcd,而这些信息将会在必要的时候用来恢复训练进程。具体过程如下图:
Master进程
当master进程被Kubernetes启动时,它进行如下操作:
1. 从etcd中取一个唯一的master lock,以此避免多个master实例存在
2. 查看etcd中是否存在任务队列。如果不存在,则新建一个任务队列;否则得到这个任务队列中的信息
3. 把自身的ip地址写进etcd中/master/addr 这个key中,便于后来的训练进程和自己通信
4. 开端口监听训练进程的任务需求,如果收到来自训练进程的任务请求,从任务队列中取任务分配之,并且更新任务队列。
如果master进程因为任何原因死掉了,Kubernetes会将它重启,从被重启到获取etcd的信息,获取训练进程的任务,这个过程一般是几分钟。
训练进程
当训练进程被Kubernetes启动时,它进行如下操作:
1. 查看etcd中包含参数服务前缀 /ps/ 获取当前参数服务进程的数量并等待,直到该数量达到配置文件中的要求
2. 从etcd的/master/addr key中获取master进程地址
3. 向master发起任务请求,根据任务开始训练程序
当训练进程死掉之后,Kubernetes会将它重启,新起来的进程会重复上述工作直到开始新的训练工作。
参数服务进程
当参数服务进程被Kubernetes启动时,它进行如下操作:
1. 从etcd /ps_desired中读取训练任务所需求的参数服务进程个数
2. 在etcd /ps/ (/ps/0, /ps/1, ...) 里找一个小于所需进程数里最大的还不存在的id,并
在etcd里创建这个entry,以此作为自身的id。(如下图所示)
3. 参数服务进程会从自身对应的etcd path中找到已有的训练结果参数并且将它读入
4. 参数服务进程开始接收来自训练进程的请求。
Paddle EDL 和 KubeFlow
开源社区中另一个和Kubernetes紧密结合的深度学习系统自然是和Kubernetes同样来自Google 的TensorFlow。近期,Google开源了KubeFlow项目,旨在利用Kubernetes调度Tensorflow,完成大规模训练任务。
从设计理念和实现思路来看,Paddle EDL和KubeFlow有非常多的相似之处。从开源的时间来看 ,二者也是同一时段,百度和谷歌的工程师对同一主题的几乎一样的理解。不过二者的确存在一些差别。
首先,KubeFlow目前只支持Tensorflow,而Paddle EDL目前只支持PaddlePaddle。而它们底层都依托于Kubernetes。Paddle EDL似乎对Kubernetes的整合更深入一些,比如利用可定制的资源分配方式,和自定义逻辑与Kubernetes API交互。而KubeFlow似乎直接使用Kubernetes一般性的 功能。因此,在弹性调度这个功能上,PaddlePaddle采取的是前文讨论的EDL方式,而 KubeFlow现在是HPA方式。这是二者最大的不同。
另外,虽然二者都支持一般的Kubernetes+docker环境,KubeFlow和Google在深度学习生态系 统中的其它开源项目一样,非常推崇在GCE上布署,深度整合Google云服务。而Paddle EDL并没有强调布署云的供应商服务。
由于Paddle EDL和KubeFlow都是刚刚开源的项目,更多的细节还在演化当中,我相信开源社区对它们的使用和理解会不断加深的。不过有一点可以肯定,Kubernetes和深度学习系统的结合将会越来越紧密,一个抽象层的,和Kubernetes API结合更紧密的,可调度不同后端训练系统(不 再绑定Tensorflow或者PaddlePaddle)的项目也许正在孕育中。
领取专属 10元无门槛券
私享最新 技术干货