低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

使用 TensorFlow 训练 WDL 模型性能问题定位与调优

2020 年 2 月 27 日

使用TensorFlow训练WDL模型性能问题定位与调优

简介

TensorFlow 是 Google 研发的第二代人工智能学习系统,能够处理多种深度学习算法模型,以功能强大和高可扩展性而著称。TensorFlow 完全开源,所以很多公司都在使用,但是美团点评在使用分布式 TensorFlow 训练 WDL 模型时,发现训练速度很慢,难以满足业务需求。


经过对 TensorFlow 框架和 Hadoop 的分析定位,发现在数据输入、集群网络和计算内存分配等层面出现性能瓶颈。主要原因包括 TensorFlow 数据输入接口效率低、PS/Worker 算子分配策略不佳以及 Hadoop 参数配置不合理。我们在调整对 TensorFlow 接口调用、并且优化系统配置后,WDL 模型训练性能提高了 10 倍,分布式线性加速可达 32 个 Worker,基本满足了美团点评广告和推荐等业务的需求。


术语

  • TensorFlow - Google 发布的开源深度学习框架

  • OP - Operation 缩写,TensorFlow 算子

  • PS - Parameter Server 参数服务器

  • WDL - Wide & Deep Learning,Google 发布的用于推荐场景的深度学习算法模型

  • AFO - AI Framework on YARN 的简称 - 基于 YARN 开发的深度学习调度框架,支持 TensorFlow,MXNet 等深度学习框架


TensorFlow 分布式架构简介

为了解决海量参数的模型计算和参数更新问题,TensorFlow 支持分布式计算。和其他深度学习框架的做法类似,分布式 TensorFlow 也引入了参数服务器(Parameter Server,PS),用于保存和更新训练参数,而模型训练放在 Worker 节点完成。



TensorFlow 分布式架构


TensorFlow 支持图并行(in-graph)和数据并行(between-graph)模式,也支持同步更新和异步更新。因为 in-graph 只在一个节点输入并分发数据,严重影响并行训练速度,实际生产环境中一般使用 between-graph。


同步更新时,需要一个 Woker 节点为 Chief,来控制所有的 Worker 是否进入下一轮迭代,并且负责输出 checkpoint。异步更新时所有 Worker 都是对等的,迭代过程不受同步 barrier 控制,训练过程更快。


AFO 架构设计

TensorFlow 只是一个计算框架,没有集群资源管理和调度的功能,分布式训练也欠缺集群容错方面的能力。为了解决这些问题,我们在 YARN 基础上自研了 AFO 框架解决这个问题。


AFO 架构特点:


  • 高可扩展,PS、Worker 都是任务(Task),角色可配置

  • 基于状态机的容错设计

  • 提供了日志服务和 Tensorboard 服务,方便用户定位问题和模型调试



AFO 架构


AFO 模块说明


  • Application Master:用来管理整个 TensorFlow 集群的资源申请,对任务进行状态监控

  • AFO Child:TensorFlow 执行引擎,负责 PS、Worker 运行时管理和状态同步

  • History Server:管理 TensorFlow 训练生成的日志

  • AFO Client:用户客户端


WDL 模型

在推荐系统、CTR 预估场景中,训练的样本数据一般是查询、用户和上下文信息,系统返回一个排序好的候选列表。推荐系统面临的主要问题是,如何同时可以做到模型的记忆能力和泛化能力,WDL 提出的思想是结合线性模型(Wide,用于记忆)和深度神经网络(Deep,用于泛化)。


以论文中用于 Google Play Store 推荐系统的 WDL 模型为例,该模型输入用户访问应用商店的日志,用户和设备的信息,给应用 App 打分,输出一个用户“感兴趣”App 列表。



WDL 模型网络


其中,installed apps 和 impression apps 这类特征具有稀疏性(在海量大小的 App 空间中,用户感兴趣的只有很少一部分),对应模型“宽的部分”,适合使用线性模型;在模型“深的部分”,稀疏特征由于维度太高不适合神经网络处理,需要 embedding 降维转成稠密特征,再和其他稠密特征串联起来,输入到一个 3 层 ReLU 的深度网络。最后 Wide 和 Deep 的预估结果加权输入给一个 Logistic 损失函数(例如 Sigmoid)。


WDL 模型中包含对稀疏特征的 embedding 计算,在 TensorFlow 中对应的接口是 tf.embedding_lookup_sparse,但该接口所包含的 OP 无法使用 GPU 加速,只能在 CPU 上计算,因此 TensorFlow 在处理稀疏特征性能不佳。不仅如此,我们发现分布式 TensorFlow 在进行 embedding 计算时会引发大量的网络传输流量,严重影响训练性能。


性能瓶颈分析与调优

在使用 TensorFlow 训练 WDL 模型时,我们主要发现 3 个性能问题:


  1. 每轮训练时,输入数据环节耗时过多,超过 60%的时间用于读取数据。

  2. 训练时产生的网络流量高,占用大量集群网络带宽资源,难以实现分布式性能线性加速。

  3. Hadoop 的默认参数配置导致 glibc malloc 变慢,一个保护 malloc 内存池的内核自旋锁成为性能瓶颈。


TensorFlow 输入数据瓶颈

TensorFlow 支持以流水线(Pipeline)的方式输入训练数据。如下图所示,典型的输入数据流水线包含两个队列:Filename Queue 对一组文件做 shuffle,多个 Reader 线程从此队列中拿到文件名,读取训练数据,再经过 Decode 过程,将数据放入 Example Queue,以备训练线程从中读取数据。Pipeline 这种多线程、多队列的设计可以使训练线程和读数据线程并行。理想情况下,队列 Example Queue 总是充满数据的,训练线程完成一轮训练后可以立即读取下一批的数据。如果 Example Queue 总是处于“饥饿”状态,训练线程将不得不阻塞,等待 Reader 线程将 Example Queue 插入足够的数据。使用 TensorFlow Timeline 工具,可以直观地看到其中的 OP 调用过程。



TensorFlow 输入数据流水线


使用 Timeline,需要对 tf.Session.run()增加如下几行代码:


with tf.Session as sess:  ptions = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)  run_metadata = tf.RunMetadata()  _ = sess.run([train_op, global_step], options=run_options, run_metadata=run_metadata)  if global_step > 1000 && global_step < 1010:    from tensorflow.python.client import timeline    fetched_timeline = timeline.Timeline(run_metadata.step_stats)    chrome_trace = fetched_timeline.generate_chrome_trace_format()    with open('/tmp/timeline_01.json', 'w') as f:      f.write(chrome_trace)
复制代码


这样训练到 global step 在 1000 轮左右时,会将该轮训练的 Timeline 信息保存到 timeline_01.json 文件中,在 Chrome 浏览器的地址栏中输入 chrome://tracing,然后 load 该文件,可以看到图像化的 Profiling 结果。


业务模型的 Timeline 如图所示:



Timeline 显示数据输入是性能瓶颈


可以看到 QueueDequeueManyV2 这个 OP 耗时最久,约占整体时延的 60%以上。通过分析 TensorFlow 源码,我们判断有两方面的原因:


  • (1)Reader 线程是 Python 线程,受制于 Python 的全局解释锁(GIL),Reader 线程在训练时没有获得足够的调度执行;

  • (2)Reader 默认的接口函数 TFRecordReader.read 函数每次只读入一条数据,如果 Batch Size 比较大,读入一个 Batch 的数据需要频繁调用该接口,系统开销很大;


针对第一个问题,解决办法是使用TensorFlow Dataset接口,该接口不再使用 Python 线程读数据,而是用 C++线程实现,避免了 Python GIL 问题。针对第二个问题,社区提供了批量读数据接口 TFRecordReader.read_up_to,能够指定每次读数据的数量。我们设置每次读入 1000 条数据,使读数句接口被调用的频次从 10000 次降低到 10 次,每轮训练时延降低 2-3 倍。



优化数据输入使性能提升 2-3 倍


可以看到经过调优后,QueueDequeueManyV2 耗时只有十几毫秒,每轮训练时延从原来的 800 多毫秒降低至不到 300 毫秒。


集群网络瓶颈

虽然使用了 Mellanox 的 25G 网卡,但是在 WDL 训练过程中,我们观察到 Worker 上的上行和下行网络流量抖动剧烈,幅度 2-10Gbps,这是由于打满了 PS 网络带宽导致丢包。因为分布式训练参数都是保存和更新都是在 PS 上的,参数过多,加之模型网络较浅,计算很快,很容易形成多个 Worker 打一个 PS 的情况,导致 PS 的网络接口带宽被打满。


在推荐业务的 WDL 模型中,embedding 张量的参数规模是千万级,TensorFlow 的 tf.embedding_lookup_sparse 接口包含了几个 OP,默认是分别摆放在 PS 和 Worker 上的。如图所示,颜色代表设备,embedding lookup 需要在不同设备之前传输整个 embedding 变量,这意味着每轮 Embedding 的迭代更新需要将海量的参数在 PS 和 Worker 之间来回传输。



embedding_lookup_sparse 的 OP 拓扑图


有效降低网络流量的方法是尽量让参数更新在一个设备上完成,即


with tf.device(PS):  do embedding computing
复制代码


社区提供了一个接口方法正是按照这个思想实现的:embedding_lookup_sparse_with_distributed_aggregation接口,该接口可以将 embedding 计算的所使用的 OP 都放在变量所在的 PS 上,计算后转成稠密张量再传送到 Worker 上继续网络模型的计算。


从下图可以看到,embedding 计算所涉及的 OP 都是在 PS 上,测试 Worker 的上行和下行网络流量也稳定在 2-3Gpbs 这一正常数值。



embedding\_lookup\_sparse\_with\_distributed_aggregation 的 OP 拓扑图


PS 上的 UniqueOP 性能瓶颈

在使用分布式 TensorFlow 跑广告推荐的 WDL 算法时,发现一个奇怪的现象:WDL 算法在 AFO 上的性能只有手动分布式的 1/4。手动分布式是指:不依赖 YARN 调度,用命令行方式在集群上分别启动 PS 和 Worker 作业。


使用 Perf 诊断 PS 进程热点,发现 PS 多线程在竞争一个内核自旋锁,PS 整体上有 30%-50%的 CPU 时间耗在 malloc 的在内核的 spin_lock 上。



Perf 诊断 PS 计算瓶颈


进一步查看 PS 进程栈,发现竞争内核自旋锁来自于 malloc 相关的系统调用。WDL 的 embedding_lookup_sparse 会使用 UniqueOp 算子,TensorFlow 支持 OP 多线程,UniqueOp 计算时会开多线程,线程执行时会调用 glibc 的 malloc 申请内存。


经测试排查,发现 Hadoop 有一项默认的环境变量配置:


export MALLOC_ARENA_MAX="4"
复制代码


该配置意思是限制进程所能使用的 glibc 内存池个数为 4 个。这意味着当进程开启多线程调用 malloc 时,最多从 4 个内存池中竞争申请,这限制了调用 malloc 的线程并行执行数量最多为 4 个。


翻查 Hadoop 社区相关讨论,当初增加这一配置的主要原因是:glibc 的升级带来多线程 ARENA 的特性,可以提高 malloc 的并发性能,但同时也增加进程的虚拟内存(即 top 结果中的 VIRT)。YARN 管理进程树的虚拟内存和物理内存使用量,超过限制的进程树将被杀死。将 MALLOC_ARENA_MAX 的默认设置改为 4 之后,可以不至于 VIRT 增加很多,而且一般作业性能没有明显影响。


但这个默认配置对于 WDL 深度学习作业影响很大,我们去掉了这个环境配置,malloc 并发性能极大提升。经过测试,WDL 模型的平均训练时间性能减少至原来的 1/4。


调优结果

注意:以下测试都去掉了 Hadoop MALLOC_ARENA_MAX 的默认配置


我们在 AFO 上针对业务的 WDL 模型做了性能调优前后的比对测试,测试环境参数如下:


模型:推荐广告模型WDLOS:CentOS 7.1CPU: Xeon E5 2.2G, 40 CoresGPU:Nvidia P40磁盘: Local Rotational Disk网卡:Mellanox 25G(未使用RoCE)TensorFlow版本:Release 1.4CUDA/cuDNN: 8.0/5.1
复制代码



分布式线性加速效果


可以看到调优后,训练性能提高 2-3 倍,性能可以达到 32 个 GPU 线性加速。这意味着如果使用同样的资源,业务训练时间会更快,或者说在一定的性能要求下,资源节省更多。如果考虑优化 MALLOC_ARENA_MAX 的因素,调优后的训练性能提升约为 10 倍左右。


总结

我们使用 TensorFlow 训练 WDL 模型发现一些系统上的性能瓶颈点,通过针对性的调优不仅可以大大加速训练过程,而且可以提高 GPU、带宽等资源的利用率。在深入挖掘系统热点瓶颈的过程中,我们也加深了对业务算法模型、TensorFlow 框架的理解,具有技术储备的意义,有助于我们后续进一步优化深度学习平台性能,更好地为业务提供工程技术支持。


作者简介

  • 郑坤,美团点评技术专家,2015 年加入美团点评,负责深度学习平台、Docker 平台的研发工作。


2020 年 2 月 27 日 10:49275

评论

发布
暂无评论
发现更多内容

会用Docker的人都别装了,这多简单呐

MySQL从删库到跑路

MySQL Docker Linux yum redhat

训练营第四周作业 1

仲夏

极客大学架构师训练营

架构师作业 - 第四周 - 一个典型的大型互联网应用系统使用了哪些技术方案和手段

袭望

理解分布式一致性:Paxos协议之Basic Paxos

程序那些事

raft PAXOS raft协议 paxos协议 Basic paxos

week04总结

追风

架构师一期

架构师训练营第 1 期 - 第四周总结

Todd-Lee

极客大学架构师训练营

第四周-系统架构-总结

刘希文

WEEK4 一个典型的大型互联网应用系统使用了哪些技术方案和手段,主要解决什么问题?请列举描述

陈勇

理解分布式一致性:Raft协议

程序那些事

分布式 raft 分布式一致性 raft协议

大型互联网应用面对的挑战及应对方案和手段

张荣召

深入理解JVM垃圾回收算法 - 复制算法

WANDEFOUR

深入理解JVM GC复制算法 Cheney

Paxos 的变种(一):Multi-Paxos 是如何劝退大家去选择 Raft 的

多颗糖

分布式 架构师 分布式架构 分布式一致性

区块链行业发展的“忧与愁”

CECBC区块链专委会

区块链 互联网

架构模式

张荣召

“链”接技术与应用:区块链的新命题,大命题

CECBC区块链专委会

区块链 数字货币

spring-boot笔记

solike

训练营第四周作业 2

仲夏

极客大学架构师训练营

DDIA读书笔记(1)可靠性,可扩展性,可维护性

莫黎

读书笔记

架构师训练营第四周作业

xs-geek

极客大学架构师训练营

架构师训练营第 1 期 - 第四周作业提交

Todd-Lee

极客大学架构师训练营

架构师训练营-week04-作业1

lucian

极客大学架构师训练营

为什么说 Python 内置函数并不是万能的?

Python猫

Python

区块链助力政府建设高效政务能力

CECBC区块链专委会

区块链 政务

架构师训练营—第四周学习总结

Geek_shu1988

系统架构:系统技术挑战与方案

张荣召

互联网架构演化

张荣召

维基百科技术架构

张荣召

LeetCode题解:22. 括号生成,递归生成同时过滤,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

WEEK4 学习总结

陈勇

极客大学架构师训练营

架构师训练营第四周 -- 学习总结

张荣召

一个典型的大型互联网应用系统使用了哪些技术方案和手段,主要解决什么问题?

A p7+

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

使用TensorFlow训练WDL模型性能问题定位与调优-InfoQ