TensorFlow on Kubernetes性能瓶颈定位
当前性能问题描述
- 增加worker数,一定范围内能带来较好的性能提升,但是继续增加worker数时,训练性能提升不明显;
- 增加ps数,一定范围内能带来较好的性能提升,但是继续增加ps数时,训练性能提升不明显;
可能原因:
-
与ps和worker的分布情况强相关:
- 目前的调度策略,主要根据服务器的cpu和内存使用情况进行均衡调度,尽量使得集群中每台服务器的cpu和内存使用率相当。这种情况下,ps和worker的调度存在一定程度的随机性。
- 如果调度时,每台包含worker的服务器都有对应一个ps,那么训练性能会更高?如果有,性能提升多少呢?
-
K8S中的worker从HDFS集群中读取训练数据时存在IO瓶颈?可能网络上的或者是HDFS本身的配置,需要通过HDFS集群的监控来进一步排查。
下面,是针对第一种“可能原因:与ps和worker的分布情况强相关“ 设计的测试场景和用例:
场景1:将每个worker所在的服务器都有对应的ps。
测试用例
用例ID | 服务器数 | worker数 | ps数 | 说明 |
---|---|---|---|---|
1 | 1 | 10 | 1 | 一台服务器部署了10个worker和1个ps |
2 | 5 | 50 | 5 | 5台服务器分别部署了10个worker和1个p |
3 | 10 | 100 | 10 | 10台服务器分别部署了10个worker和1个p |
4 | 20 | 200 | 20 | 20台服务器分别部署了10个worker和1个p |
TensorFlow tasks调度设计图
调度实现
- 场景1的TensorFlow对象模板***scene1.jinja***
# scene1.jinja —— 对象模板 {%- set name = "##NAME##" -%} {%- set worker_replicas = ##WN## -%} {%- set ps_replicas = ##PN## -%} {%- set script = "##SCRIPT##" -%} {%- set case = "##CASE##" -%} {%- set port = 2222 -%} {%- set log_host_dir = "/var/log/tensorflow" -%} {%- set log_container_dir = "/var/log" -%} {%- set image = "registry.vivo.xyz:4443/bigdata_release/tensorflow1.3.0" -%} {%- set replicas = {"worker": worker_replicas, "ps": ps_replicas} -%} {%- macro worker_hosts() -%} {%- for i in range(worker_replicas) -%} {%- if not loop.first -%},{%- endif -%} {{ name }}-worker-{{ i }}:{{ port }} {%- endfor -%} {%- endmacro -%} {%- macro ps_hosts() -%} {%- for i in range(ps_replicas) -%} {%- if not loop.first -%},{%- endif -%} {{ name }}-ps-{{ i }}:{{ port }} {%- endfor -%} {%- endmacro -%} {%- for i in range( begin_index, end_index ) -%} {%- if task_type == "worker" %} --- kind: Service apiVersion: v1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: clusterIP: None selector: name: {{ name }} job: {{ task_type }} task: "{{ i }}" ports: - port: {{ port }} targetPort: 2222 --- kind: Job apiVersion: batch/v1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: template: metadata: labels: name: {{ name }} job: {{ task_type }} task: "{{ i }}" spec: imagePullSecrets: - name: harborsecret' affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "CASE" operator: In values: - "{{ case }}" - key: "INDEX" operator: In values: - "{{ i // 10 }}" - key: "SCENCE" operator: In values: - "1" containers: - name: {{ name }}-{{ task_type }}-{{ i }} image: {{ image }} resources: requests: memory: "4Gi" cpu: "300m" ports: - containerPort: 2222 command: ["/bin/sh", "-c", "export CLASSPATH=.:/usr/lib/jvm/java-1.8.0/lib/tools.jar:$(/usr/lib/hadoop-2.6.1/bin/hadoop classpath --glob); wget -r -nH -np --cut-dir=1 -R 'index.html*,*gif' {{ script }}; cd ./{{ name }}; sh ./run.sh {{ ps_hosts() }} {{ worker_hosts() }} {{ task_type }} {{ i }} {{ ps_replicas }} {{ worker_replicas }}"] restartPolicy: OnFailure {%- endif -%} {%- if task_type == "ps" -%} --- kind: Service apiVersion: v1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: clusterIP: None selector: name: {{ name }} job: {{ task_type }} task: "{{ i }}" ports: - port: {{ port }} targetPort: 2222 --- kind: Deployment apiVersion: extensions/v1beta1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: replicas: 1 template: metadata: labels: name: {{ name }} job: {{ task_type }} task: "{{ i }}" spec: imagePullSecrets: - name: harborsecret affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "CASE" operator: In values: - "{{ case }}" - key: "INDEX" operator: In values: - "{{ i }}" - key: "SCENCE" operator: In values: - "1" containers: - name: {{ name }}-{{ task_type }}-{{ i }} image: {{ image }} resources: requests: memory: "4Gi" cpu: "2" ports: - containerPort: 2222 command: ["/bin/sh", "-c","export CLASSPATH=.:/usr/lib/jvm/java-1.8.0/lib/tools.jar:$(/usr/lib/hadoop-2.6.1/bin/hadoop classpath --glob); wget -r -nH -np --cut-dir=1 -R 'index.html*,*gif' {{ script }}; cd ./{{ name }}; sh ./run.sh {{ ps_hosts() }} {{ worker_hosts() }} {{ task_type }} {{ i }} {{ ps_replicas }} {{ worker_replicas }}"] restartPolicy: Always {%- endif -%} {%- endfor -%}
- Label Nodes
选择对应的节点打上对应的Label。
kubectl label node $node_name SCENCE=1 CASE=? INDEX=?
测试结果
用例2的测试截图:
场景2:将所有ps和所有worker都强制进行物理隔离。
测试用例
用例ID | 服务器数 | worker数 | ps数 | 说明 |
---|---|---|---|---|
1 | 2 | 10 | 1 | 一台服务器部署10个worker,另外一台部署1个ps |
2 | 10 | 20 | 5 | 5台服务器分别部署10个worker,5台服务器分别部署1个ps |
3 | 20 | 50 | 10 | 10台服务器分别部署10个worker,10台服务器分别部署1个ps |
4 | 40 | 200 | 20 | 20台服务器分别部署10个worker,20台服务器分别部署1个ps |
TensorFlow tasks调度设计图
调度实现
- 场景2的TensorFlow对象模板***scene2.jinja***
# scene2.jinja —— 对象模板 {%- set name = "##NAME##" -%} {%- set worker_replicas = ##WN## -%} {%- set ps_replicas = ##PN## -%} {%- set script = "##SCRIPT##" -%} {%- set case = "##CASE##" -%} {%- set port = 2222 -%} {%- set log_host_dir = "/var/log/tensorflow" -%} {%- set log_container_dir = "/var/log" -%} {%- set image = "registry.vivo.xyz:4443/bigdata_release/tensorflow1.3.0" -%} {%- set replicas = {"worker": worker_replicas, "ps": ps_replicas} -%} {%- macro worker_hosts() -%} {%- for i in range(worker_replicas) -%} {%- if not loop.first -%},{%- endif -%} {{ name }}-worker-{{ i }}:{{ port }} {%- endfor -%} {%- endmacro -%} {%- macro ps_hosts() -%} {%- for i in range(ps_replicas) -%} {%- if not loop.first -%},{%- endif -%} {{ name }}-ps-{{ i }}:{{ port }} {%- endfor -%} {%- endmacro -%} {%- for i in range( begin_index, end_index ) -%} {%- if task_type == "worker" %} --- kind: Service apiVersion: v1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: clusterIP: None selector: name: {{ name }} job: {{ task_type }} task: "{{ i }}" ports: - port: {{ port }} targetPort: 2222 --- kind: Job apiVersion: batch/v1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: template: metadata: labels: name: {{ name }} job: {{ task_type }} task: "{{ i }}" spec: imagePullSecrets: - name: harborsecret' affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "CASE" operator: In values: - "{{ case }}" - key: "INDEX" operator: In values: - "{{ i // 10 }}" - key: "SCENCE" operator: In values: - "2" - key: "TYPE" operator: In values: - "worker" containers: - name: {{ name }}-{{ task_type }}-{{ i }} image: {{ image }} resources: requests: memory: "4Gi" cpu: "300m" ports: - containerPort: 2222 command: ["/bin/sh", "-c", "export CLASSPATH=.:/usr/lib/jvm/java-1.8.0/lib/tools.jar:$(/usr/lib/hadoop-2.6.1/bin/hadoop classpath --glob); wget -r -nH -np --cut-dir=1 -R 'index.html*,*gif' {{ script }}; cd ./{{ name }}; sh ./run.sh {{ ps_hosts() }} {{ worker_hosts() }} {{ task_type }} {{ i }} {{ ps_replicas }} {{ worker_replicas }}"] restartPolicy: OnFailure {%- endif -%} {%- if task_type == "ps" -%} --- kind: Service apiVersion: v1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: clusterIP: None selector: name: {{ name }} job: {{ task_type }} task: "{{ i }}" ports: - port: {{ port }} targetPort: 2222 --- kind: Deployment apiVersion: extensions/v1beta1 metadata: name: {{ name }}-{{ task_type }}-{{ i }} namespace: {{ name }} spec: replicas: 1 template: metadata: labels: name: {{ name }} job: {{ task_type }} task: "{{ i }}" spec: imagePullSecrets: - name: harborsecret affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "CASE" operator: In values: - "{{ case }}" - key: "INDEX" operator: In values: - "{{ i }}" - key: "SCENCE" operator: In values: - "2" - key: "TYPE" operator: In values: - "ps" containers: - name: {{ name }}-{{ task_type }}-{{ i }} image: {{ image }} resources: requests: memory: "4Gi" cpu: "2" ports: - containerPort: 2222 command: ["/bin/sh", "-c","export CLASSPATH=.:/usr/lib/jvm/java-1.8.0/lib/tools.jar:$(/usr/lib/hadoop-2.6.1/bin/hadoop classpath --glob); wget -r -nH -np --cut-dir=1 -R 'index.html*,*gif' {{ script }}; cd ./{{ name }}; sh ./run.sh {{ ps_hosts() }} {{ worker_hosts() }} {{ task_type }} {{ i }} {{ ps_replicas }} {{ worker_replicas }}"] restartPolicy: Always {%- endif -%} {%- endfor -%}
- Label Nodes
选择对应的节点打上对应的Label。
kubectl label node $node_name SCENCE=1 CASE=? INDEX=? TYPE=?
测试结果
用例2的测试截图:
测试结论及思考
对比两种不同场景下用例2(5个ps,50个worker)的监控数据,发现如下现象:
-
两种场景下,虽然创建了5个ps,但是实际上只有一个ps的负载比较高,其他的ps要么cpu usage在10%以下,要么甚至几乎为0。
-
两种场景下同样的worker number和ps number,整个tensorflow cluster消耗的cpu和内存差别很小。
测试结论
- 分布式tensorflow中,每个worker选择哪个ps作为自己的参数服务器跟我们如何强制分布ps和worker的布局无关,由分布式tensorflow内部自己控制(跟tf.train.replica_device_setter()设置的strategy有关)。
问题思考
-
为什么这个训练中,多个ps中只有一个ps在工作?是算法只有一个Big参数?如果是,那么默认按照Round-Robin策略只会使用一个ps,就能解释这个问题了。这需要算法的兄弟进行确认。
-
如果将Big参数拆分成众多Small参数,使用RR或LB或Partition策略之一,应该都能利用多个ps进行参数更新明显提升训练性能。
-
通过这次折腾,也不是一无所获,至少发现我们对于Distributed TensorFlow的内部工作原理还不甚了解,非常有必要深入到源码进行分析。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
kubernetes如何解决服务依赖呢?
在微服务的世界里,任何应用都需要注意,其所依赖的服务是会中断的。所以当应用发现某服务(如数据库)出现了故障,应该每隔一端时间去重试。而上层框架(如k8s)会检测到服务故障,并尝试恢复这个服务。 但在现实世界里,有些旧应用并没有处理这种情况,但我们还是希望能将他们也跑在微服务框架里,以期得到微服务的红利(例如应用故障重启),所以,需要定义服务依赖关系,从而保障旧应用启动时,它所依赖的服务已经ready。 解决方法是,微服务框架替应用等待其所依赖的服务(api, database, etc),当服务准备好时,框架才启动该应用。 如何知道Pod已经Ready kubernetes提供了Readiness Probe功能,用来探测Pod是否Ready。 Pod在Readiness Probe成功之前,不会接受任何流量;具体以Service来说,在Pod的Readiness Probe成功之前,Kubernetes不会将该Pod作为Serivce的Endpoint(注意,这是下面initContainer的基础)。Probe是kubernet API提供的功能,服务不需要做任何改变就可以支持。下...
- 下一篇
kubernetes代码阅读-apiserver之list-watch篇
apiserver的list-watch代码解读 list-watch,作为k8s系统中统一的异步消息传递方式,对系统的性能、数据一致性起到关键性的作用。今天我想从代码这边探究一下list-watch的实现方式。并看是否能在后面的工作中优化这个过程。 0. list-watch的需求 上图是一个典型的Pod创建过程,在这个过程中,每次当kubectl创建了ReplicaSet对象后,controller-manager都是通过list-watch这种方式得到了最新的ReplicaSet对象,并执行自己的逻辑来创建Pod对象。其他的几个组件,Scheduler/Kubelet也是一样,通过list-watch得知变化并进行处理。这是组件的处理端代码: c.NodeLister.Store, c.nodePopulator = framework.NewInformer( c.createNodeLW(), ...(1) &api.Node{}, ...(2) 0, ...(3) framework.ResourceEventHandlerFuncs{ ...(4) AddFunc...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8编译安装MySQL8.0.19
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2全家桶,快速入门学习开发网站教程