TensorFlow on Kubernetes性能瓶颈定位

简介: 当前性能问题描述 增加worker数,一定范围内能带来较好的性能提升,但是继续增加worker数时,训练性能提升不明显; 增加ps数,一定范围内能带来较好的性能提升,但是继续增加ps数时,训练性能提升不明显; 可能原因: 与ps和worker的分布情况强相关: 目前的调度策略,主要根据服务器的cpu和内存使用情况进行均衡调度,尽量使得集群中每台服务器的cpu和内存使用率相当。

当前性能问题描述

  1. 增加worker数,一定范围内能带来较好的性能提升,但是继续增加worker数时,训练性能提升不明显;
  2. 增加ps数,一定范围内能带来较好的性能提升,但是继续增加ps数时,训练性能提升不明显;

可能原因:

  1. 与ps和worker的分布情况强相关:

    • 目前的调度策略,主要根据服务器的cpu和内存使用情况进 行均衡调度,尽量使得集群中每台服务器的cpu和内存使用率相当。这种情况下,ps和worker的调度存在一定程度的随机性。
    • 如果调度时,每台包含worker的服务器都有对应一个ps,那么训练性能会更高?如果有,性能提升多少呢?
  2. 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的内部工作原理还不甚了解,非常有必要深入到源码进行分析。

本文转自掘金-TensorFlow on Kubernetes性能瓶颈定位
相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
11月前
|
缓存 Kubernetes 数据库
【kubernetes】Service: 将外部服务定位为 Service
【kubernetes】Service: 将外部服务定位为 Service
74 0
|
存储 Kubernetes 监控
使用 Kubernetes 监控定位 Pod 状态异常根因 | 学习笔记
快速学习 使用 Kubernetes 监控定位 Pod 状态异常根因
687 0
使用 Kubernetes 监控定位 Pod 状态异常根因 | 学习笔记
|
SQL 弹性计算 监控
如何使用 Kubernetes 监控定位慢调用 | 学习笔记
快速学习 如何使用 Kubernetes 监控定位慢调用
217 0
如何使用 Kubernetes 监控定位慢调用 | 学习笔记
|
SQL 弹性计算 Kubernetes
如何使用 Kubernetes 监测定位慢调用
本次课程主要分为三大部分,首先将介绍慢调用的危害以及常见的原因;其次介绍慢调用的分析方法以及最佳实践;最后将通过几个案例来去演示一下慢调用的分析过程。
如何使用 Kubernetes 监测定位慢调用
|
消息中间件 自然语言处理 监控
在阿里巴巴,我们如何先于用户发现和定位 Kubernetes 集群问题?
本文整理自阿里云高级研发工程师彭南光(光南) 在 KubeCon China 2021 大会的演讲实录,分享了阿里巴巴是如何通过自研通用链路探测+定向巡检工具 KubeProbe 应对大规模集群的稳定性挑战的。关于阿里云云原生团队在本次 KubeCon 上分享的全部内容沉淀于电子书《云原生与云未来的新可能》当中,可点击文末“阅读原文”下载。
在阿里巴巴,我们如何先于用户发现和定位 Kubernetes 集群问题?
|
存储 弹性计算 Kubernetes
尝鲜阿里云容器服务Kubernetes 1.16,共享TensorFlow实验室《二》--共享GPU的弹性
上一篇文章《尝鲜阿里云容器服务Kubernetes 1.16,共享TensorFlow实验室》我们讲述了如何通过CGPU的方案来实现CGPU资源的共享和隔离。 本文介绍基于CGPU资源的弹性能力。 ps:下面的说明是基于上一篇文章的环境来进行的描述,环境的搭建请参考上一篇文章。 ## 配置弹性伸缩组 1. 在“集群列表”中目标集群的“更多”的下拉菜单中选中“自动伸缩” ![001.j
622 0
尝鲜阿里云容器服务Kubernetes 1.16,共享TensorFlow实验室《二》--共享GPU的弹性
|
机器学习/深度学习 Kubernetes 网络协议
尝鲜阿里云容器服务Kubernetes 1.16,共享TensorFlow实验室
尝鲜阿里云容器服务Kubernetes 1.16,拥抱GPU新姿势-v4 简介 TensorFLow是深度学习和机器学习最流行的开源框架,它最初是由Google研究团队开发的并致力于解决深度神经网络的机器学习研究,从2015年开源到现在得到了广泛的应用。
5394 0
|
存储 Kubernetes 网络协议
Kubernetes 应用故障的一些定位方法
常备工作 准备一个工具镜像 其中包含 nslookup, ping, curl, 甚至是 ab、siege 等常用工具以及一个顺手的 Shell。一言不合就可以用静态 Pod 的方式将其运行到 Kubernetes 之中进行内部诊断。
1118 0
|
Kubernetes TensorFlow 算法框架/工具
如何在Kubernetes上玩转TensorFlow ?
前言 Tensorflow作为深度学习领域逐渐成熟的项目,以其支持多种开发语言,支持多种异构平台,提供强大的算法模型,被越来越多的开发者使用。但在使用的过程中,尤其是GPU集群的时候,我们或多或少将面临以下问题: 资源隔离。
2114 0
|
机器学习/深度学习 算法 TensorFlow
深度学习框架TensorFlow在Kubernetes上的实践
什么是深度学习? 深度学习这个名词听了很多次,它到底是什么东西,它背后的技术其实起源于神经网络。神经网络最早受到人类大脑工作原理的启发,我们知道人的大脑是很复杂的结构,它可以被分为很多区域,比如听觉中心、视觉中心,我在读研究中心的时候,做视频有计算机视觉研究室,做语言有语言所,语音有语音所,不同的功能在学科划分中已经分开了,这个和我们人类对大脑理解多多少少有一些关系。
2085 0