Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
PaddlePaddle
Paddle
提交
212dcedc
P
Paddle
项目概览
PaddlePaddle
/
Paddle
大约 1 年 前同步成功
通知
2299
Star
20931
Fork
5422
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
1423
列表
看板
标记
里程碑
合并请求
543
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
P
Paddle
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
1,423
Issue
1,423
列表
看板
标记
里程碑
合并请求
543
合并请求
543
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
212dcedc
编写于
11月 17, 2016
作者:
C
chenguoyan01
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
rewrite distributed_training_on_k8s.md
上级
23b6dfd0
变更
6
显示空白变更内容
内联
并排
Showing
6 changed file
with
482 addition
and
289 deletion
+482
-289
doc_cn/build_and_install/distributed_training_on_kubernetes.md
...n/build_and_install/distributed_training_on_kubernetes.md
+0
-289
doc_cn/cluster/k8s/Dockerfile
doc_cn/cluster/k8s/Dockerfile
+7
-0
doc_cn/cluster/k8s/distributed_training_on_kubernetes.md
doc_cn/cluster/k8s/distributed_training_on_kubernetes.md
+254
-0
doc_cn/cluster/k8s/job.yaml
doc_cn/cluster/k8s/job.yaml
+43
-0
doc_cn/cluster/k8s/start.sh
doc_cn/cluster/k8s/start.sh
+19
-0
doc_cn/cluster/k8s/start_paddle.py
doc_cn/cluster/k8s/start_paddle.py
+159
-0
未找到文件。
doc_cn/build_and_install/distributed_training_on_kubernetes.md
已删除
100644 → 0
浏览文件 @
23b6dfd0
# Paddle on Kubernetes:分布式训练
前一篇文章介绍了如何在Kubernetes集群上启动一个单机Paddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上启动分布式Paddle训练作业。关于Paddle的分布式集群训练,可以参考
[
Cluster Training
](
https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md
)
, 本文在此基础上,利用Kubernetes快速构建Paddle集群,进行分布式训练任务。
## 制作镜像
Paddle的集群训练需要有一个Paddle集群来实现,在本文中,我们使用Kubernetes来快速创建一个Paddle集群。我们使用
`paddledev/paddle:cpu-demo-latest`
镜像作为Paddle集群节点的运行环境,里面包含了 Paddle 运行所需要的相关依赖,同时,为了能将训练任务及配置统一分发到各个节点,需要使用到
`sshd`
以便使用
`fabric`
来操作。镜像的 Dockerfile 如下:
```
FROM paddledev/paddle:cpu-demo-latest
RUN apt-get update
RUN apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:root' | chpasswd
RUN sed -ri 's/^PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
```
使用
`docker build`
构建镜像:
```
docker build -t mypaddle:paddle_demo_ssh .
```
## 准备工作空间
工作空间
[
Job Workspace
](
https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md#prepare-job-workspace
)
, 即一个包含了依赖库,训练,测试数据,模型配置文件的目录。参考
[
Cluster Training
](
https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md
)
中的例子,我们也是用
`demo/recommendation`
作为本文的训练任务。此demo可直接从
[
Github Paddle源码
](
https://github.com/baidu/Paddle/tree/develop/demo/recommendation
)
中获取。
### 准备训练数据
在Paddle源码中,找到
`demo/recommendation`
文件夹,即为我们的Workspace, 在本文的环境中,路径为
`/home/work/paddle-demo/Paddle/demo/recommendation`
```
[root@paddle-k8s-node0 recommendation]# tree
.
├── common_utils.py
├── data
│ ├── config_generator.py
│ ├── config.json
│ ├── meta_config.json
│ ├── meta_generator.py
│ ├── ml_data.sh
│ └── split.py
├── dataprovider.py
├── evaluate.sh
├── prediction.py
├── preprocess.sh
├── requirements.txt
├── run.sh
└── trainer_config.py
1 directory, 14 files
```
运行
`data/ml_data.sh`
脚本,下载数据,然后运行
`preprocess.sh`
脚本进行预处理。
```
[root@paddle-k8s-node0 recommendation]# data/ml_data.sh
++ dirname data/ml_data.sh
+ cd data
+ wget http://files.grouplens.org/datasets/movielens/ml-1m.zip
--2016-11-04 10:14:49-- http://files.grouplens.org/datasets/movielens/ml-1m.zip
Resolving files.grouplens.org (files.grouplens.org)... 128.101.34.146
Connecting to files.grouplens.org (files.grouplens.org)|128.101.34.146|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5917549 (5.6M) [application/zip]
Saving to: ‘ml-1m.zip’
100%[==========================>] 5,917,549 50.6KB/s in 2m 29s
2016-11-04 10:17:20 (38.8 KB/s) - ‘ml-1m.zip’ saved [5917549/5917549]
+ unzip ml-1m.zip
Archive: ml-1m.zip
creating: ml-1m/
inflating: ml-1m/movies.dat
inflating: ml-1m/ratings.dat
inflating: ml-1m/README
inflating: ml-1m/users.dat
+ rm ml-1m.zip
[root@paddle-k8s-node0 recommendation]# ./preprocess.sh
generate meta config file
generate meta file
split train/test file
shuffle train file
```
### 修改集群训练配置
参考
[
Cluster Training
](
https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md
)
中的介绍,我们使用
`paddle/scripts/cluster_train/`
中的文件来作为分布式训练任务的配置和启动脚本。在
`run.sh`
文件中,填入我们的workspace和训练配置文件路径。
```
#!/bin/sh
python paddle.py
\
--job_dispatch_package
=
"/home/work/paddle-demo/Paddle/demo/recommendation"
\
--dot_period
=
10
\
--ports_num_for_sparse
=
2
\
--log_period
=
50
\
--num_passes
=
10
\
--trainer_count
=
4
\
--saving_period
=
1
\
--local
=
0
\
--config
=
/home/work/paddle-demo/Paddle/demo/recommendation/trainer_config.py
\
--save_dir
=
./output
\
--use_gpu
=
0
```
## 创建Paddle集群
创建Paddle集训需要编写创建Kubernetes资源的yaml文件,首先,创建一个Service,便于我们通过此Service来查找其对应的Paddle节点。
```
apiVersion: v1
kind: Service
metadata:
name: cluster-demo
spec:
selector:
app: cluster-demo
ports:
- name: default
protocol: TCP
port: 7164
targetPort: 7164
```
为了创建多个Paddle节点,我们使用Kubernetes ReplicationController资源来控制Paddle集群中的节点数量,Paddle节点之间需要开放相关的端口来互相通信。下面的例子中,我们开放了每个Paddle节点的7164-7167端口,例如,一个包含4个节点的Paddle集群的yaml文件如下:
```
apiVersion: v1
kind: ReplicationController
metadata:
name: cluster-demo
spec:
replicas: 4
selector:
app: cluster-demo
template:
metadata:
name: cluster-demo
labels:
app: cluster-demo
spec:
containers:
- name: cluster-demo
image: mypaddle:paddle_demo_ssh
ports:
- containerPort: 7164
- containerPort: 7165
- containerPort: 7166
- containerPort: 7167
```
然后我们可以通过
`kubectl`
工具来查看所创建的资源信息。
首先查看我们创建的Paddle Service,然后根据Service,查看所创建的Paddle节点的IP地址。
```
[root@paddle-k8s-node0 cluster_train]# kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cluster-demo 11.1.1.77 <none> 7164/TCP 6h
[root@paddle-k8s-node0 cluster_train]# kubectl get -o json endpoints cluster-demo | grep ip
"ip": "192.168.129.79",
"ip": "192.168.129.80",
"ip": "192.168.223.157",
"ip": "192.168.223.158",
```
## 开始集群训练
我们需要在
`paddle/scripts/cluster_train/conf.py`
文件中指定各个节点的IP地址以及开放的端口。根据上文创建的信息,
`conf.py`
文件修改如下:
```
HOSTS = [
"root@192.168.129.79",
"root@192.168.129.80",
"root@192.168.223.157",
"root@192.168.223.158"
]
'''
workspace configuration
'''
#root dir for workspace, can be set as any director with real user account
ROOT_DIR = "/home/paddle"
'''
network configuration
'''
#pserver nics
PADDLE_NIC = "eth0"
#pserver port
PADDLE_PORT = 7164
#pserver ports num
PADDLE_PORTS_NUM = 2
#pserver sparse ports num
PADDLE_PORTS_NUM_FOR_SPARSE = 2
#environments setting for all processes in cluster job
LD_LIBRARY_PATH="/usr/local/cuda/lib64:/usr/lib64"
```
然后使用
`run.sh`
脚本开始训练,启动的打印如下:
```
[root@paddle-k8s-node0 cluster_train]# ./run.sh
[root@192.168.129.79] Executing task 'job_create_workspace'
......
[root@192.168.129.80] Executing task 'job_create_workspace'
......
[root@192.168.223.157] Executing task 'job_create_workspace'
......
[root@192.168.223.158] Executing task 'job_create_workspace'
......
[root@192.168.129.79] run: echo 0 > /home/paddle/JOB20161104171630/nodefile
[root@192.168.129.80] Executing task 'set_nodefile'
[root@192.168.129.80] run: echo 1 > /home/paddle/JOB20161104171630/nodefile
[root@192.168.223.157] Executing task 'set_nodefile'
[root@192.168.223.157] run: echo 2 > /home/paddle/JOB20161104171630/nodefile
[root@192.168.223.158] Executing task 'set_nodefile'
[root@192.168.223.158] run: echo 3 > /home/paddle/JOB20161104171630/nodefile
```
可以看到192.168.129.79,192.168.129.80,192.168.223.157,192.168.223.158分别为Paddle集群的Node 0-3.
我们可以进入其中一个Paddle节点查看训练的日志。
```
root@cluster-demo-fwwi5:/home/paddle/JOB20161104171700/log# less paddle_trainer.INFO
Log file created at: 2016/11/04 09:17:20
Running on machine: cluster-demo-fwwi5
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
I1104 09:17:20.346797 108 Util.cpp:155] commandline: /usr/local/bin/../opt/paddle/bin/paddle
_trainer --num_gradient_servers=4 --nics=eth0 --port=7164 --ports_num=2 --comment=paddle_proce
ss_by_paddle --pservers=192.168.129.79,192.168.129.80,192.168.223.157,192.168.223.158 --ports_
num_for_sparse=2 --config=./trainer_config.py --trainer_count=4 --use_gpu=0 --num_passes=10 --
save_dir=./output --log_period=50 --dot_period=10 --saving_period=1 --local=0 --trainer_id=1
root@cluster-demo-fwwi5:/home/paddle/JOB20161104171700/log# tailf paddle_trainer.INFO
......
I1104 09:17:37.376471 150 ThreadLocal.cpp:37] thread use undeterministic rand seed:151
I1104 09:18:54.159624 108 TrainerInternal.cpp:163] Batch=50 samples=80000 AvgCost=4.03478 CurrentCost=4.03478 Eval: CurrentEval:
I1104 09:20:10.207902 108 TrainerInternal.cpp:163] Batch=100 samples=160000 AvgCost=3.75806 CurrentCost=3.48134 Eval: CurrentEval:
I1104 09:21:26.493571 108 TrainerInternal.cpp:163] Batch=150 samples=240000 AvgCost=3.64512 CurrentCost=3.41923 Eval: CurrentEval:
```
最后,我们可以在Paddle集群的node0(192.168.129.79)上查看训练的输出结果。
```
[root@paddle-k8s-node0 ~]# ssh root@192.168.129.79
......
root@cluster-demo-r65g0:/home/paddle/JOB20161104171700/output/pass-00000# ll
total 14876
drwxr-xr-x. 2 root root 4096 Nov 4 09:40 ./
drwxr-xr-x. 3 root root 23 Nov 4 09:40 ../
-rw-r--r--. 1 root root 4046864 Nov 4 09:40 ___embedding_0__.w0
-rw-r--r--. 1 root root 100368 Nov 4 09:40 ___embedding_1__.w0
-rw-r--r--. 1 root root 6184976 Nov 4 09:40 ___embedding_2__.w0
-rw-r--r--. 1 root root 2064 Nov 4 09:40 ___embedding_3__.w0
-rw-r--r--. 1 root root 7184 Nov 4 09:40 ___embedding_4__.w0
-rw-r--r--. 1 root root 21520 Nov 4 09:40 ___embedding_5__.w0
-rw-r--r--. 1 root root 262160 Nov 4 09:40 ___fc_layer_0__.w0
-rw-r--r--. 1 root root 1040 Nov 4 09:40 ___fc_layer_0__.wbias
......
......
-rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w0
-rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w1
-rw-r--r--. 1 root root 262160 Nov 4 09:40 _movie_fusion.w2
-rw-r--r--. 1 root root 1040 Nov 4 09:40 _movie_fusion.wbias
-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w0
-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w1
-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w2
-rw-r--r--. 1 root root 262160 Nov 4 09:40 _user_fusion.w3
-rw-r--r--. 1 root root 1040 Nov 4 09:40 _user_fusion.wbias
-rw-r--r--. 1 root root 169 Nov 4 09:40 done
-rw-r--r--. 1 root root 17 Nov 4 09:40 path.txt
-rw-r--r--. 1 root root 3495 Nov 4 09:40 trainer_config.py
```
\ No newline at end of file
doc_cn/cluster/k8s/Dockerfile
0 → 100644
浏览文件 @
212dcedc
FROM
paddledev/paddle:cpu-latest
MAINTAINER
zjsxzong89@gmail.com
COPY
start.sh /root/
COPY
start_paddle.py /root/
CMD
["bash"," -c","/root/start.sh"]
\ No newline at end of file
doc_cn/cluster/k8s/distributed_training_on_kubernetes.md
0 → 100644
浏览文件 @
212dcedc
# Paddle on Kubernetes:分布式训练
前一篇文章介绍了如何在Kubernetes集群上启动一个单机Paddle训练作业 (Job)。在这篇文章里,我们介绍如何在Kubernetes集群上进行分布式Paddle训练作业。关于Paddle的分布式训练,可以参考
[
Cluster Training
](
https://github.com/baidu/Paddle/blob/develop/doc/cluster/opensource/cluster_train.md
)
, 本文利用Kubernetes的调度功能与容器编排能力,快速构建Paddle容器集群,进行分布式训练任务。
## Kubernetes 基本概念
在介绍分布式训练之前,需要对Kubernetes(k8s)有一个基本的认识,下面先简要介绍一下本文用到的几个k8s概念。
### Node
[
`Node`
](
http://kubernetes.io/docs/admin/node/
)
表示一个k8s集群中的一个工作节点,这个节点可以是物理机或者虚拟机,k8s集群就是由
`node`
节点与
`master`
节点组成的。每个node都安装有Docker,在本文的例子中,
`Paadle`
容器就在node上运行。
### Pod
一个
[
`Pod`
](
http://kubernetes.io/docs/user-guide/pods/
)
是一组(一个或多个)容器,pod是k8s的最小调度单元,一个pod中的所有容器会被调度到同一个node上。Pod中的容器共享NET,PID,IPC,UTS等Linux namespace,它们使用同一个IP地址,可以通过
`localhost`
互相通信。不同pod之间可以通过IP地址访问。
### Job
[
`Job`
](
http://kubernetes.io/docs/user-guide/jobs/
)
可以翻译为作业,每个job可以设定pod成功完成的次数,一次作业会创建多个pod,当成功完成的pod个数达到预设值时,就表示job成功结束了。
### Volume
[
`Volume`
](
http://kubernetes.io/docs/user-guide/volumes/
)
存储卷,是pod内的容器都可以访问的共享目录,也是容器与node之间共享文件的方式,因为容器内的文件都是暂时存在的,当容器因为各种原因被销毁时,其内部的文件也会随之消失。通过volume,就可以将这些文件持久化存储。k8s支持多种volume,例如
`hostPath(宿主机目录)`
,
`gcePersistentDisk`
,
`awsElasticBlockStore`
等。
### Namespace
[
`Namespaces`
](
http://kubernetes.io/docs/user-guide/volumes/
)
命名空间,在k8s中创建的所有资源对象(例如上文的pod,job)等都属于一个命名空间,在同一个命名空间中,资源对象的名字是唯一的,不同空间的资源名可以重复,命名空间主要用来为不同的用户提供相对隔离的环境。本文只使用了
`default`
默认命名空间,读者可以不关心此概念。
## 整体方案
### 前提条件
首先,我们需要拥有一个k8s集群,在这个集群中所有node与pod都可以互相通信。关于k8s集群搭建,可以参考
[
官方文档
](
http://kubernetes.io/docs/getting-started-guides/kubeadm/
)
,在以后的文章中我们也会介绍AWS上搭建的方案。在本文的环境中,k8s集群中所有node都挂载了一个
`mfs`
(分布式文件系统)共享目录,我们通过这个目录来存放训练文件与最终输出的模型。在训练之前,用户将配置与训练数据切分好放在mfs目录中,训练时,程序从此目录拷贝文件到容器内进行训练,将结果保存到此目录里。
### 使用 `Job`
我们使用k8s中的job这个概念来代表一次分布式训练。
`Job`
表示一次性作业,在作业完成后,k8s会销毁job产生的容器并且释放相关资源。
在k8s中,可以通过编写一个
`yaml`
文件,来描述这个job,在这个文件中,主要包含了一些配置信息,例如Paddle节点的个数,
`paddle pserver`
开放的端口个数与端口号,
`paddle`
使用的网卡设备等,这些信息通过环境变量的形式传递给容器内的程序使用。
在一次分布式训练中,用户确定好本次训练需要的Paddle节点个数,将切分好的训练数据与配置文件上传到
`mfs`
共享目录中。然后编写这次训练的
`job yaml`
文件,提交给k8s集群创建并开始作业。
### 创建`Paddle`节点
当k8s master收到
`job yaml`
文件后,会解析相关字段,创建出多个pod(个数为Paddle节点数),k8s会把这些pod调度到集群的node上运行。一个
`pod`
就代表一个
`Paddle`
节点,当pod被成功分配到一台物理/虚拟机上后,k8s会启动pod内的容器,这个容器会根据
`job yaml`
文件中的环境变量,启动
`paddle pserver`
与
`paddle train`
进程。
### 启动训练
在容器启动后,会通过脚本来启动这次分布式训练,我们知道
`paddle train`
进程启动时需要知道其他节点的IP地址以及本节点的
`trainer_id`
,由于
`Paddle`
本身不提供类似服务发现的功能,所以在本文的启动脚本中,每个节点会根据
`job name`
向
`k8s apiserver`
查询这个
`job`
对应的所有
`pod`
信息(k8s默认会在每个容器的环境变量中写入
`apiserver`
的地址)。
根据这些pod信息,就可以通过某种方式,为每个pod分配一个唯一的
`trainer_id`
。本文把所有pod的IP地址进行排序,将顺序作为每个
`Paddle`
节点的
`trainer_id`
。启动脚本的工作流程大致如下:
1.
查询
`k8s apiserver`
获取pod信息,根据IP分配
`trainer_id`
1.
从
`mfs`
共享目录中拷贝训练文件到容器内
1.
根据环境变量,解析出
`paddle pserver`
与
`paddle train`
的启动参数,启动进程
1.
训练时,
`Paddle`
会自动将结果保存在
`trainer_id`
为0的节点上,将输出路径设置为
`mfs`
目录,保存输出的文件
## 搭建过程
根据前文的描述,要在已有的k8s集群上进行
`Paddle`
的分布式训练,主要分为以下几个步骤:
1.
制作
`Paddle`
镜像
1.
将训练文件与切分好的数据上传到共享存储
1.
编写本次训练的
`job yaml`
文件,创建
`k8s job`
1.
训练结束后查看输出结果
下面就根据这几个步骤分别介绍。
### 制作镜像
`Paddle`
镜像需要提供
`paddle pserver`
与
`paddle train`
进程的运行环境,用这个镜像创建的容器需要有以下两个功能:
-
拷贝训练文件到容器内
-
生成
`paddle pserver`
与
`paddle train`
进程的启动参数,并且启动训练
因为官方镜像
`paddledev/paddle:cpu-latest`
内已经包含
`Paddle`
的执行程序但是还没上述功能,所以我们可以在这个基础上,添加启动脚本,制作新镜像来完成以上的工作。镜像的
`Dockerfile`
如下:
```
Dockerfile
FROM
paddledev/paddle:cpu-latest
MAINTAINER
zjsxzong89@gmail.com
COPY
start.sh /root/
COPY
start_paddle.py /root/
CMD
["bash"," -c","/root/start.sh"]
```
[
`start.sh`
](
start.sh
)
文件拷贝训练文件到容器内,然后执行
[
`start_paddle.py`
](
start_paddle.py
)
脚本启动训练,前文提到的获取其他节点IP地址,分配
`trainer_id`
等都在
`start_paddle.py`
脚本中完成。
使用
`docker build`
构建镜像:
```
bash
docker build
-t
registry.baidu.com/public/paddle:mypaddle .
```
然后将构建成功的镜像上传到镜像仓库,注意本文中使用的
`registry.baidu.com`
是一个私有仓库,读者可以根据自己的情况部署私有仓库或者使用
`Docker hub`
。
```
bash
docker push registry.baidu.com/public/paddle:mypaddle
```
### 上传训练文件
本文使用
`Paddle`
官方的
`recommendation demo`
作为这次训练的内容,我们将训练文件与数据放在一个
`job name`
命名的目录中,上传到
`mfs`
共享存储。完成后
`mfs`
上的文件内容大致如下:
```
bash
[
root@paddle-k8s-node0 mfs]# tree
-d
.
└── paddle-cluster-job
├── data
│ ├── 0
│ │
│ ├── 1
│ │
│ └── 2
├── output
└── recommendation
```
目录中
`paddle-cluster-job`
是本次训练对应的
`job name`
,本次训练要求有3个
`Paddle`
节点,在
`paddle-cluster-job/data`
目录中存放切分好的数据,文件夹
`0,1,2`
分别代表3个节点的
`trainer_id`
。
`recommendation`
文件夹内存放训练文件,
`output`
文件夹存放训练结果与日志。
### 创建`job`
`k8s`
可以通过
`yaml`
文件来创建相关对象,然后可以使用命令行工具创建
`job`
。
`job yaml`
文件描述了这次训练使用的Docker镜像,需要启动的节点个数以及
`paddle pserver`
与
`paddle train`
进程启动的必要参数,也描述了容器需要使用的存储卷挂载的情况。
`yaml`
文件中各个字段的具体含义,可以查看
[
`k8s官方文档`
](
http://kubernetes.io/docs/api-reference/batch/v1/definitions/#_v1_job
)
。例如,本次训练的
`yaml`
文件可以写成:
```
yaml
apiVersion
:
batch/v1
kind
:
Job
metadata
:
name
:
paddle-cluster-job
spec
:
parallelism
:
3
completions
:
3
template
:
metadata
:
name
:
paddle-cluster-job
spec
:
volumes
:
-
name
:
jobpath
hostPath
:
path
:
/home/work/mfs
containers
:
-
name
:
trainer
image
:
registry.baidu.com/public/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
-
name
:
TRAIN_CONFIG_DIR
value
:
recommendation
-
name
:
CONF_PADDLE_NIC
value
:
eth0
-
name
:
CONF_PADDLE_PORT
value
:
"
7164"
-
name
:
CONF_PADDLE_PORTS_NUM
value
:
"
2"
-
name
:
CONF_PADDLE_PORTS_NUM_SPARSE
value
:
"
2"
-
name
:
CONF_PADDLE_GRADIENT_NUM
value
:
"
3"
volumeMounts
:
-
name
:
jobpath
mountPath
:
/home/jobpath
restartPolicy
:
Never
```
文件中,
`metadata`
下的
`name`
表示这个
`job`
的名字。
`parallelism,completions`
字段表示这个
`job`
会同时开启3个
`Paddle`
节点,成功训练且退出的
`pod`
数目为3时,这个
`job`
才算成功结束。然后申明一个存储卷
`jobpath`
,代表宿主机目录
`/home/work/mfs`
,在对容器的描述
`containers`
字段中,将此目录挂载为容器的
`/home/jobpath`
目录,这样容器的
`/home/jobpath`
目录就成为了共享存储,放在这个目录里的文件其实是保存到了
`mfs`
上。
`env`
字段表示容器的环境变量,我们将
`paddle`
运行的一些参数通过这种方式传递到容器内。
`JOB_PATH`
表示共享存储挂载的路径,
`JOB_NAME`
表示job名字,
`TRAIN_CONFIG_DIR`
表示本次训练文件所在目录,这三个变量组合就可以找到本次训练需要的文件路径。
`CONF_PADDLE_NIC`
表示
`paddle pserver`
进程需要的
`--nics`
参数,即网卡名
`CONF_PADDLE_PORT`
表示
`paddle pserver`
的
`--port`
参数,
`CONF_PADDLE_PORTS_NUM`
则表示稠密更新的端口数量,也就是
`--ports_num`
参数。
`CONF_PADDLE_PORTS_NUM_SPARSE`
表示稀疏更新的端口数量,也就是
`--ports_num_for_sparse`
参数。
`CONF_PADDLE_GRADIENT_NUM`
表示训练节点数量,即
`--num_gradient_servers`
参数
编写完
`yaml`
文件后,可以使用k8s的命令行工具创建
`job`
.
```
bash
kubectl create
-f
job.yaml
```
创建成功后,k8s就会创建3个
`pod`
作为
`Paddle`
节点然后拉取镜像,启动容器开始训练。
### 查看输出
在训练过程中,可以在共享存储上查看输出的日志和模型,例如
`output`
目录下就存放了输出结果。注意
`node_0`
,
`node_1`
,
`node_2`
这几个目录表示
`Paddle`
节点与
`trainer_id`
,并不是k8s中的
`node`
概念。
```
bash
[
root@paddle-k8s-node0 output]# tree
-d
.
├── node_0
│ ├── server.log
│ └── train.log
├── node_1
│ ├── server.log
│ └── train.log
├── node_2
......
├── pass-00002
│ ├──
done
│ ├── ___embedding_0__.w0
│ ├── ___embedding_1__.w0
......
```
我们可以通过日志查看容器训练的情况,例如:
```
bash
[
root@paddle-k8s-node0 node_0]#
cat
train.log
I1116 09:10:17.123121 50 Util.cpp:155] commandline:
/usr/local/bin/../opt/paddle/bin/paddle_trainer
--nics
=
eth0
--port
=
7164
--ports_num
=
2
--comment
=
paddle_process_by_paddle
--pservers
=
192.168.129.66,192.168.223.143,192.168.129.71
--ports_num_for_sparse
=
2
--config
=
./trainer_config.py
--trainer_count
=
4
--num_passes
=
10
--use_gpu
=
0
--log_period
=
50
--dot_period
=
10
--saving_period
=
1
--local
=
0
--trainer_id
=
0
--save_dir
=
/home/jobpath/paddle-cluster-job/output
I1116 09:10:17.123440 50 Util.cpp:130] Calling runInitFunctions
I1116 09:10:17.123764 50 Util.cpp:143] Call runInitFunctions
done
.
[
WARNING 2016-11-16 09:10:17,227 default_decorators.py:40] please use keyword arguments
in
paddle config.
[
INFO 2016-11-16 09:10:17,239 networks.py:1282] The input order is
[
movie_id, title, genres, user_id, gender, age, occupation, rating]
[
INFO 2016-11-16 09:10:17,239 networks.py:1289] The output order is
[
__regression_cost_0__]
I1116 09:10:17.392917 50 Trainer.cpp:170] trainer mode: Normal
I1116 09:10:17.613910 50 PyDataProvider2.cpp:257] loading dataprovider dataprovider::process
I1116 09:10:17.680917 50 PyDataProvider2.cpp:257] loading dataprovider dataprovider::process
I1116 09:10:17.681543 50 GradientMachine.cpp:134] Initing parameters..
I1116 09:10:18.012390 50 GradientMachine.cpp:141] Init parameters
done
.
I1116 09:10:18.018641 50 ParameterClient2.cpp:122] pserver 0 192.168.129.66:7164
I1116 09:10:18.018950 50 ParameterClient2.cpp:122] pserver 1 192.168.129.66:7165
I1116 09:10:18.019069 50 ParameterClient2.cpp:122] pserver 2 192.168.223.143:7164
I1116 09:10:18.019492 50 ParameterClient2.cpp:122] pserver 3 192.168.223.143:7165
I1116 09:10:18.019716 50 ParameterClient2.cpp:122] pserver 4 192.168.129.71:7164
I1116 09:10:18.019836 50 ParameterClient2.cpp:122] pserver 5 192.168.129.71:7165
```
\ No newline at end of file
doc_cn/cluster/k8s/job.yaml
0 → 100644
浏览文件 @
212dcedc
apiVersion
:
batch/v1
kind
:
Job
metadata
:
name
:
paddle-cluster-job
spec
:
parallelism
:
3
completions
:
3
template
:
metadata
:
name
:
paddle-cluster-job
spec
:
volumes
:
-
name
:
jobpath
hostPath
:
path
:
/home/work/paddle_output
containers
:
-
name
:
trainer
image
:
registry.baidu.com/public/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
-
name
:
TRAIN_CONFIG_DIR
value
:
recommendation
-
name
:
CONF_PADDLE_NIC
value
:
eth0
-
name
:
CONF_PADDLE_PORT
value
:
"
7164"
-
name
:
CONF_PADDLE_PORTS_NUM
value
:
"
2"
-
name
:
CONF_PADDLE_PORTS_NUM_SPARSE
value
:
"
2"
-
name
:
CONF_PADDLE_GRADIENT_NUM
value
:
"
3"
volumeMounts
:
-
name
:
jobpath
mountPath
:
/home/jobpath
restartPolicy
:
Never
\ No newline at end of file
doc_cn/cluster/k8s/start.sh
0 → 100755
浏览文件 @
212dcedc
#!/bin/sh
set
-eu
jobconfig
=
${
JOB_PATH
}
"/"
${
JOB_NAME
}
"/"
${
TRAIN_CONFIG_DIR
}
cd
/root
cp
-rf
$jobconfig
.
cd
$TRAIN_CONFIG_DIR
python /root/start_paddle.py
\
--dot_period
=
10
\
--ports_num_for_sparse
=
$CONF_PADDLE_PORTS_NUM
\
--log_period
=
50
\
--num_passes
=
10
\
--trainer_count
=
4
\
--saving_period
=
1
\
--local
=
0
\
--config
=
./trainer_config.py
\
--use_gpu
=
0
doc_cn/cluster/k8s/start_paddle.py
0 → 100755
浏览文件 @
212dcedc
#!/usr/bin/python
# Copyright (c) 2016 Baidu, Inc. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import
requests
import
time
import
socket
import
os
import
argparse
# configuration for cluster
API
=
"/api/v1/namespaces/"
JOBSELECTOR
=
"labelSelector=job-name="
JOB_PATH
=
os
.
getenv
(
"JOB_PATH"
)
+
"/"
+
os
.
getenv
(
"JOB_NAME"
)
JOB_PATH_DATA
=
JOB_PATH
+
"/data"
JOB_PATH_OUTPUT
=
JOB_PATH
+
"/output"
JOBNAME
=
os
.
getenv
(
"JOB_NAME"
)
NAMESPACE
=
os
.
getenv
(
"JOB_NAMESPACE"
)
PADDLE_NIC
=
os
.
getenv
(
"CONF_PADDLE_NIC"
)
PADDLE_PORT
=
os
.
getenv
(
"CONF_PADDLE_PORT"
)
PADDLE_PORTS_NUM
=
os
.
getenv
(
"CONF_PADDLE_PORTS_NUM"
)
PADDLE_PORTS_NUM_SPARSE
=
os
.
getenv
(
"CONF_PADDLE_PORTS_NUM_SPARSE"
)
PADDLE_SERVER_NUM
=
os
.
getenv
(
"CONF_PADDLE_GRADIENT_NUM"
)
def
refine_unknown_args
(
cmd_args
):
'''
refine unknown parameters to handle some special parameters
'''
new_args
=
[]
for
arg
in
cmd_args
:
if
arg
.
startswith
(
"--"
)
and
arg
.
find
(
"="
)
!=
-
1
:
equal_pos
=
arg
.
find
(
"="
)
# find first = pos
arglist
=
list
(
arg
)
arglist
[
equal_pos
]
=
" "
arg
=
""
.
join
(
arglist
)
arg
=
arg
.
lstrip
(
"-"
)
new_args
+=
arg
.
split
(
" "
)
elif
arg
.
startswith
(
"--"
)
and
arg
.
find
(
"="
)
==
-
1
:
arg
=
arg
.
lstrip
(
"-"
)
new_args
.
append
(
arg
)
else
:
new_args
.
append
(
arg
)
return
new_args
def
isPodAllRunning
(
podlist
):
'''
check all pod is running
'''
require
=
len
(
podlist
[
"items"
])
running
=
0
for
pod
in
podlist
[
"items"
]:
if
pod
[
"status"
][
"phase"
]
==
"Running"
:
running
+=
1
if
require
==
running
:
return
True
return
False
def
getPodList
():
'''
get all container status of the job
'''
apiserver
=
"https://"
+
\
os
.
getenv
(
"KUBERNETES_SERVICE_HOST"
)
+
":"
+
\
os
.
getenv
(
"KUBERNETES_SERVICE_PORT_HTTPS"
)
pod
=
API
+
NAMESPACE
+
"/pods?"
job
=
JOBNAME
return
requests
.
get
(
apiserver
+
pod
+
JOBSELECTOR
+
job
,
verify
=
False
).
json
()
def
getIdMap
(
podlist
):
'''
generate tainer_id by ip
'''
ips
=
[]
for
pod
in
podlist
[
"items"
]:
ips
.
append
(
pod
[
"status"
][
"podIP"
])
ips
.
sort
()
idMap
=
{}
for
i
in
range
(
len
(
ips
)):
idMap
[
ips
[
i
]]
=
i
return
idMap
def
startPaddle
(
idMap
=
{},
train_args_dict
=
None
):
'''
start paddle pserver and trainer
'''
program
=
'paddle train'
args
=
" --nics="
+
PADDLE_NIC
args
+=
" --port="
+
str
(
PADDLE_PORT
)
args
+=
" --ports_num="
+
str
(
PADDLE_PORTS_NUM
)
args
+=
" --comment="
+
"paddle_process_by_paddle"
ip_string
=
""
for
ip
in
idMap
.
keys
():
ip_string
+=
(
ip
+
","
)
ip_string
=
ip_string
.
rstrip
(
","
)
args
+=
" --pservers="
+
ip_string
args_ext
=
""
for
key
,
value
in
train_args_dict
.
items
():
args_ext
+=
(
' --'
+
key
+
'='
+
value
)
localIP
=
socket
.
gethostbyname
(
socket
.
gethostname
())
trainerId
=
idMap
[
localIP
]
args
+=
" "
+
args_ext
+
" --trainer_id="
+
\
str
(
trainerId
)
+
" --save_dir="
+
JOB_PATH_OUTPUT
logDir
=
JOB_PATH_OUTPUT
+
"/node_"
+
str
(
trainerId
)
if
not
os
.
path
.
exists
(
JOB_PATH_OUTPUT
):
os
.
makedirs
(
JOB_PATH_OUTPUT
)
os
.
mkdir
(
logDir
)
copyCommand
=
'cp -rf '
+
JOB_PATH_DATA
+
\
"/"
+
str
(
trainerId
)
+
" ./data"
os
.
system
(
copyCommand
)
startPserver
=
'nohup paddle pserver'
+
\
" --port="
+
str
(
PADDLE_PORT
)
+
\
" --ports_num="
+
str
(
PADDLE_PORTS_NUM
)
+
\
" --ports_num_for_sparse="
+
str
(
PADDLE_PORTS_NUM_SPARSE
)
+
\
" --nics="
+
PADDLE_NIC
+
\
" --comment="
+
"paddle_process_by_paddle"
+
\
" --num_gradient_servers="
+
str
(
PADDLE_SERVER_NUM
)
+
\
" > "
+
logDir
+
"/server.log 2>&1 &"
print
startPserver
os
.
system
(
startPserver
)
# wait until pservers completely start
time
.
sleep
(
10
)
startTrainer
=
program
+
args
+
" > "
+
\
logDir
+
"/train.log 2>&1 < /dev/null"
print
startTrainer
os
.
system
(
startTrainer
)
if
__name__
==
'__main__'
:
parser
=
argparse
.
ArgumentParser
(
prog
=
"start_paddle.py"
,
description
=
'simple tool for k8s'
)
args
,
train_args_list
=
parser
.
parse_known_args
()
train_args
=
refine_unknown_args
(
train_args_list
)
train_args_dict
=
dict
(
zip
(
train_args
[:
-
1
:
2
],
train_args
[
1
::
2
]))
podlist
=
getPodList
()
# need to wait until all pods are running
while
not
isPodAllRunning
(
podlist
):
time
.
sleep
(
10
)
podlist
=
getPodList
()
idMap
=
getIdMap
(
podlist
)
startPaddle
(
idMap
,
train_args_dict
)
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录