Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
BaiXuePrincess
Paddle
提交
212dcedc
P
Paddle
项目概览
BaiXuePrincess
/
Paddle
与 Fork 源项目一致
Fork自
PaddlePaddle / Paddle
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
P
Paddle
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录