什么是PaddlePaddle
PaddlePaddle是一个易用的、高效的、灵活的和可扩展的深度学习平台,最初由百度开发,目的是将深度学习应用于百度自2014年之后的产品。
使用PaddlePaddle所支持的15个百度产品已经创造了50多项创新成果,其范围从搜索引擎、在线广告,到问答和系统安全。
在2016年9月,百度开源了PaddlePaddle,这马上就吸引了许多百度之外的贡献者。
为什么要在 Kubernetes上运行PaddlePaddle
PaddlePaddle的旨在做成轻薄独立的计算架构。用户可以在Hadoop、Spark、Mesos、Kubernetes及其他框架之上运行它。我们对Kubernetes浓厚的兴趣产出自它的灵活性、效率及其丰富的功能。
我们在各种百度产品中应用PaddlePaddle的过程中,发现PaddlePaddle主要用于两个方面:研究和产品。研究数据不经常改动,关注点是快速实验去达成预期的科学测量。产品数据通常来自于由Web服务产生的日志消息,经常会变化。
成功的深度学习项目既包括研究也包括数据处理管道,有许多需要进行调整的参数。许多工程师同时投身于项目的不同部件。
为确保项目易于管理并有效地利用硬件资源,我们希望在同一架构平台上运行项目的所有部件。
平台应该提供:
容错性。它应该把管道的每一阶段抽象为服务,它由许多处理构成,通过冗余提供高吞吐率和健壮性。
自动扩展。在白天,通常会有许多活动的用户,平台应该扩展在线服务。而到了晚上,平台则应该释放一些资源进行深度学习实验。
任务打包和隔离。它应该能够把需要GPU的PaddlePaddle训练过程、需要大内存的后端服务以及需要磁盘IO的CephFS过程分配到同一节点上以充分利用其硬件。
我们想要的是一个在同一集群中运行深度学习系统、Web服务器(比如Nginx)、日志收集器(比如fluentd)、分布式队列服务(比如Kafka)、日志合并工具和其他用Storm、Spark和
Hadoop MapReduce写成的数据处理器的平台。我们希望在同一集群中运行所有的任务(在线和离线)、生产和实验,所以我们应该充分利用该集群,因为不同类型的任务需要不同的硬件资源。
因为虚拟机带来的日常费用与我们的效率和利用率的目标是矛盾的,所以我们基于解决方案选择容器。
鉴于我们基于解决方案对不同容器的研究,Kubernetes最适合我们的需求。
在Kubernetes上进行分布式训练
PaddlePaddle天生就支持分布式训练。在PaddlePaddle集群中有两个职责:参数服务器和训练者。每个参数服务器过程维护一个公共模型的碎片。每个训练者有它自己本地的模型拷贝,并用自己的本地数据去更新这个模型。在训练过程期间,训练者发送模型更新到服务参数服务器,参数服务器负责收集这些更新,以便训练者能够用全局模型同步他们的本地拷贝。
图1:分割为两个碎片的模型。由两个参数服务器负责管理。
有些其他方式是使用一组参数服务器去共同持有一个非常巨大的模型,该模型处于多台主机上的CPU内存空间中。但实际上,我们通常不会有这么大的模型,因为受GPU内存所限处理这么大的模型应该效率极低。在我们的配置中,多台参数服务器大多是为了快速通信。假设与所有训练者一起工作的只有一台参数服务器,那么该参数服务器就必须得从所有训练者中收集渐变情况,于是就成了一个瓶颈。我们的经验表明,包含有同样数量的训练者和参数服务器是一项实验性的有效配置。而我们通常会在同一节点上运行一对训练者和参数服务器。在如下Kubernetes任务配置中,我们启动一个运行在N个Pod的任务,每个Pod上有一个参数服务器和一个训练者进程。
yaml apiVersion: batch/v1 kind: Job metadata: name: PaddlePaddle-cluster-job spec: parallelism: 3 completions: 3 template: metadata: name: PaddlePaddle-cluster-job spec: volumes: - name: jobpath hostPath: path: /home/admin/efs containers: - name: trainer image: your_repo/paddle:mypaddle command: ["bin/bash", "-c", "/root/start.sh"] env: - name: JOB_NAME value: paddle-cluster-job - name: JOB_PATH value: /home/jobpath - name: JOB_NAMESPACE value: default volumeMounts: - name: jobpath mountPath: /home/jobpath restartPolicy: Never |
我们可以看到这个配置中的parallelism、completions都设置为了3。所以该任务将同时启动3个PaddlePaddle
pod,而且该任务将在所有三个pod结束时完成。
图2:在两个节点上运行的一个pod的任务B和三个pod的任务A。
每个pod的入口点是start.sh。它从一个存储服务中下载数据,以便训练者能从pod本地磁盘空间中快速读取。在下载完成之后,它运行一段Python脚本start_paddle.py,它启动了一个参数服务器,等到所有pod的参数服务器都准备就绪后,再启动该pod上的训练者进程。
这个等待是必须的,因为每个训练者需要与所有参数服务器对话,如图1所示。KubernetesAPI 使训练者可以检查pod的状态,所以Python脚本应该等到所有参数服务器的状态都变为“运行中(running)”后再启动训练员进程。
一般,从数据碎片到pod/训练员的映射是静态的。如果我们打算去运行N个训练者,就需要把数据分割为N个碎片,然后把每个碎片静态指定给每个训练者。我们再次依赖Kubernetes
API把pod整理成列表放到任务中,把pod/训练者从1到N进行编号,那么第i个训练者就可以读到第i个数据碎片了。
训练的数据通常在分布式文件系统上提供。实际上我们使用的是我们企业预置型集群上的CephFS和AWS上的Amazon
Elastic File System。如果你有兴趣构建运行分布式PaddlePaddle训练任务的Kubernetes集群,请阅读这个教程。
接下来的工作
我们正致力于让使用Kubernetes的PaddlePaddle更平稳地运行。
你可能注意到了,目前训练者调度完全依赖于基于静态分割映射的Kubernetes。这种方式易于上手,但可能会导致一些效率问题。首先,缓慢或停滞的训练者会阻碍整个任务。在初始部署之后没有可控的抢占或重新调度。第二,这种资源分配是静态的。所以如果Kubernetes具有的可用资源超出我们的预料,那么就不得不去手工修改资源需求。这是一项枯燥乏味的工作,与我们效率和利用率的目标是不一致的。
为解决上述问题,我们将增加一个懂得Kubernetes API的PaddlePaddle主机,它能动态增加、移除资源能力,并以更加动态的方式把碎片分派给训练者。该PaddlePaddle主机把etcd作为动态映射碎片到训练者的容错性存储。因此,即使该主机崩溃了,映射也不会丢失。Kubernetes可以重启该主机,任务仍将保持运行。
另一个可能的改进是更好的PaddlePaddle任务配置。我们所提倡的具有同等数量的训练者和参数服务的经验主要是从特定目标集群的运用中收集而来的。这个策略在我们只运行PaddlePaddle任务的集群上有明显的效果。然而,在运行许多种任务的多用途集群上,这个策略可能就未必合适了。
PaddlePaddle训练者能利用多个GPU去加快计算。但GPU在Kubernetes中还不属于第一类资源。我们必须半手动地管理GPU。我们将很愿意与Kubernetes社区共同去改进GPU的支持,确保PaddlePaddle在Kubernetes上以最佳状态运行。
你可以:
下载Kubernetes
在 GitHub 上参与Kubernetes项目
在Stack Overflow 上提问或回答问题
在 Slack 上与社区取得联系
在推特上 @Kubernetesio 咨询最新更新的事宜
编辑按:这篇文章由百度深度学习团队和CoreOS etcd团队联合发布,经作者授权,由InfoQ翻译为中文版予以发布。 |