概述
雪球头条基于股票交流社区的场景,采用典型的Feeds流架构分发用户内容。面对变化频繁的股票市场,需要高效的实现用户与内容、用户与股票、用户与用户的连接,完成内容的及时有效分发。雪球算法团队开发了基于算法的智能内容分发系统,在Rank阶段采用Tensorflow框架,使用了业界前沿的Wide&Deep模型完成内容的排序。面对如此复杂的计算密集型业务,需要不断优化提升工程效率。
推荐系统团队优化了模型训练流程,缩短训练时间,提升模型迭代速度。同时基于Tensorflow的框架,打造一套自动化的模型系统,能够自动完成数据准备,模型训练和更新上线等,减少人为因素的介入,提高工作效率。
一、业务挑战
雪球头条属于典型的个性化推荐系统,依赖于大规模的数据仓库和机器学习的技术支持。雪球架构如下图所示,包括如下三层:
-
在线层:负责用户在线请求的处理,由推荐服务发起,依次调用召回服务,排序服务完成内容推荐。帖子服务和索引构建服务作为辅助系统完成相关功能。
-
离线层:包含两部分:
-
主要处理日志相关数据,用于业务分析,模型训练和用户画像的构建。此处的模型训练部分效率不高,逐渐成为业务瓶颈,是本章讨论的重点。
-
帖子相关部分的处理,依赖NLP技术完成帖子分类,Tag提取等的计算。
-
基础数据层:前后台的日志和用户发帖等的基础数据。
离线服务层的模型训练部分是推荐系统的难点。众所周知,我们采用的Wide&Deep模型相对复杂,需要密集的数据计算,这给工程团队带来不小的挑战。同时,由于股票市场变幻莫测,用户兴趣也随之频繁变化,需要不断更新调整排序模型。这里如何打造实时的模型系统,自动完成模型的训练和更新,也是摆在我们面前的问题。雪球推荐系统面临的业务挑战体现在:
-
离线训练方面,当前每日产生数据大小约有5T左右,训练数据达到10亿样本
-
线上吞吐量峰值达到400QPS,延迟P99小于600ms
-
随着业务蓬勃发展,数据量和计算量仍然急速增加
因此急需要提升模型训练速度,缩短训练时间,提升模型迭代速度,加快模型分发效率。在这个背景下,推荐系统团队尝试了如下的优化。
二、技术选型: GPU加速训练
相比CPU,GPU能够快速完成矩阵和浮点数的运算。主流GPU运算速度是CPU的30-100倍。在训练中,CPU和GPU的分工如下:CPU完成数据的准备,比如数据载入,特征抽取,数据打散等,GPU完成模型的计算。
GPU的编程需要预先安装Nvdia相关的驱动,并安装Tensorflow的GPU版本。当环境就绪后,就可以编写GPU程序了。编写Tensorflow的GPU代码比较简单,只需要为参数(var)和算子(op)指定设备名称即可。
在此之上,Tensorflow官方定制了常用的Estimator,并提供了默认的以轮询为策略的设备分配器*。用户只需要指定好会话(Session)的配置,即可应用起来GPU。如果用户打算根据自己的偏好定义Estimator,自由组织分配参数和算子,可以通过编写自定义的Estimator来实现,也是非常方便的。
1.GPU模型初试,尝试优化
第一版GPU模型上线后,和预期相差较远。主要体现在下面几点:
(1)训练速度过慢,仅有6000样本/秒。然而只用CPU的训练可以为8000样本/秒。
(2)CPU和GPU利用率偏低。在58核机器上,CPU使用率小于20%,GPU使用率仅有0-8%,而且呈明显的波峰波谷现象。
为了解决训练速度的问题,充分利用GPU,我们尝试了各种优化。终于提升到2.1w样本/秒,达到目前的训练需求。具体优化手段及思路如下。
首先我们分析下引入GPU后模型训练的方法。如前面所述,CPU负责了数据的输入和变换,GPU负责模型的计算。在一个STEP的计算中,分为三个阶段:
-
I/O :原始数据由系统IO从硬盘或其他持久性介质读入到内存
-
Map:经过变换(map)过程进行特征抽取,数据打散等操作转换为供Tensorflow使用的特征数据。同时还包括batch,shuffle等,此处的转换只能在CPU执行,是计算密集型操作。
-
Train :最后输入到GPU显存进行模型计算,完成训练过程。由于GPU运算速度极快,瓶颈是CPU与GPU交互部分,涉及到调度,内存显存双向拷贝等。
这里可以从准备数据和训练两个方面着手优化。准备数据方面是难点,放在后面讲。
2. 训练部分优化
训练部分依赖于模型的代码,主要是Estimator中对于参数和算子的定义。一般说来,要符合一个规则:尽量将复杂的浮点数计算(如矩阵运算)放在GPU操作,参数和相应的算子定义在同一块设备上,避免设备间的数据拷贝。
由于项目里使用了Tensorflow官方提供的Wide&Deep的Estimator,已经做了比较充分的优化,此处尝试的优化效果不明显。
3.准备数据部分的优化
3.1 并行
首先,最容易想到的是,并行。CPU的利用率比较低,多核未能充分利用,考虑通过多个线程进行数据准备。如下图所示:
为了充分使用到所有的CPU,这里启用了一组数据准备的线程池。1个批次的样本数据经过多个线程的处理后,交给GPU完成训练。经过并行的优化, 训练速度达到8000样本/s,获得约30%的提升,与纯CPU训练的速度基本持平。
然而,即使是并行化后,GPU的使用率也没有达到理想的状态。GPU使用率总是呈现偶尔的跳变的状态,总是间歇性的达到12%,然后归0。CPU的使用率也存在同样的间歇性现象,推测是计算模式的问题,训练速度还可以有进一步的提升。
3.2 数据预准备
接着上面的优化,分析计算机制上,在一个批次(batch)的计算处理上,CPU负责IO和特征抽取,准备好供训练的数据,然后交给GPU进行计算。在这个过程中,若设计不当,会造成CPU和GPU交替空闲的状态。即:CPU准备数据,GPU等待,然后GPU训练,CPU等待,如此往复。总的训练时间将是CPU时间和GPU时间的加和。
因此提出另一个优化点,CPU预准备数据。即CPU不间断地准备每一个batch数据,GPU并行进行计算。当CPU准备好一个batch数据,GPU训练,此时CPU继续准备下一批次的数据。(注:CPU向GPU拷贝数据速度极快,主流GPU可达到20G/s,此处的耗时可以忽略)
通过数据预准备这个优化点,训练速度提升到 9000样本/秒,约有10%的提升。
这里仅有的优化效果让人感觉到诧异,这个模式下,将训练的阶段并行起来,相当于减少了训练的时间,但是整体的速度并没有明显的提升。说明训练阶段的时间占比非常短(10%),同时说明GPU速度远大于CPU,提升训练速度的关键瓶颈仍然在准备数据部分。
同时我也看到,CPU的使用率会持续保持在较高的水平,但是GPU仍然存在间歇性的高峰现象。GPU仍然没有能够充分利用,计算模式仍有提升空间。
3.3 引入TFRecord
继续分析CPU占用很高,但GPU没有充分利用的问题。项目里的训练数据是与Hive原生的csv格式,从csv文件读取数据,到生成Tensorflow可用的数据格式。中间还有一些特征抽取的计算,而且对于项目里的样本反复的配置,特征抽取存在重复计算的现象。因此,这里引入Tensorflow原生的格式TFRecord,一种以key,value格式存储抽取后的特征的数据文件。在训练前,先将数据转化为TFRecord格式,并持久化到硬盘。之后的训练过程中,直接从硬盘中读取TFRecord文件,减少复杂的特征抽取计算。
TFRecord格式的引入,的确能减轻CPU负担,增大了准备数据阶段的吞吐量,GPU的使用率有了一定的提升。训练速度提升到1万样本/秒,约有10%的提升。但是这个效果与预期的并不一致,CPU使用率有了下降,然而最大的问题是,硬盘的使用率已经上涨到90%。这里真的是按下葫芦浮起瓢,另一个IO的问题等待我们去解决。
3.4 TFRecord的压缩
由于tfrecord是按照(key, value)格式存储的特征数据,文件比较大。在项目里对于100w条数据的大小约为4G。对于100M/s的机械硬盘来说,1秒钟最大处理2.5w条数据。当系统IO出现问题的时候,最容易想到的是数据压缩。这里我们的压缩办法从两个方面入手。
3.4.1 数据格式的压缩
TFRecord的默认存储结构可以如下图所示,每行一个样本,样本包括特征1-N,每个特征由key,value组织存储。如样本1,存储形式为list(kn, v(n,1))。这种存储方式下,对于每个样本都会重复存储key值(k1-kN),由于项目里的多数特征是单值float或者int的分类特征,尤其当key值较长时,这些key值占用的空间要占到60%以上。
优化:这里为了保持key值的可读性,没有压缩key的长度。而是采用了多样本合并存储的方案。项目里会以m为单位,合并存储m个样本。如下图所示。对于样本1-m,合并存储在一行TFRecord记录上,存储形式为list(kn, list(v(n,m)))。
m的取值是训练时超参数的batch_size的整除数,方便后续准备数据阶段的数据合并。同时,m是训练速度和空间的折中,m越大,空间压缩效果越好,但是会影响准备数据阶段的并行性,增大map阶段的耗时。m越小,并行性越好,map阶段耗时减少(这里未考虑调度的影响,看下一个优化点的讨论),但是空间压缩效果越差。一般说来,对于项目中batch_size=1000, m取值为100获取了一个较好的折中。
项目中,通过采用这种优化方法,数据大小减少56%,释放了硬盘的压力。同时,多样本合并,数据按照m为单位的存储,正好契合下一个优化点的需要。我们将在下一节进行说明。
3.4.2通用的压缩
通用的压缩会采用GZip等压缩方法对数据压缩,压缩比例比较明显,能获得额外60%的压缩效果。
通过引入压缩的TFRecord,彻底解决了CPU使用率过高,GPU使用率过低的问题。在这个优化的基础上,GPU使用率可提升到20%左右,样本的训练速度提升至1.6w样本/秒。硬盘使用率降低到15%以下。
3.5先batch再map
在尝试各种优化的方法时,有一个优化点效果非常明显:先batch后map。
先来看传统的做法,原始数据读入后,逐个进行map转换完成数据变换,然后通过batch操作形成一批批以batch_size为单位的训练样本。**优化的地方在于:我们将batch分为两个阶段,batch1和batch2,分别位于map前后。**其中batch1操作在map前,将原始数据按照batch_size1批量整合,然后送到map完成数据变换(此处的map函数需要修改以适合batch化的数据),最后batch2操作对数据再次整合。batch_size的选取公式如:
batch_size = batch_size1 * batch_size2
这种流程上的优化,会将样本的训练速度提升到2w样本/秒。
之所以会产生如此明显的效果,原因是采用了TFRecord后,map操作仅仅是训练数据的反序列化,map操作过于轻量,那么线程池的调度损耗就不能忽略:CPU的多数损耗在map线程池的等待和调度。因此,增加batch1操作,将批量的数据交给map线程去完成,增加map的操作厚度,相应的减少了线程调度对计算资源的损耗,最终提高了准备数据阶段的吞吐量。
与TFRecord的多样本合并存储方案契合的地方是,存储的TFRecord即为batch化的样本数据(上文中TFRecord压缩比例m即为本节中的batch_size1),因此省略了batch1过程,直接读取数据进行map即可。
3.6 优化总结
总结GPU训练的优化点:
优化点
训练速度(样本/秒)
GPU使用率
备注
无
6000
7-8%
多线程并行准备数据
8000
12%
数据预准备
9000
12%
引入TFRecord
1.6w
20%
压缩TFRecord
先batch后map
2.1w
30%
项目里Wide&Deep层数较少,GPU无法利用充分
3.7项目使用情况
目前项目中提供了单机4卡的GPU机器,可单机并行训练4个模型,速度达到8w样本/秒。训练阶段的速度提升,可在5小时内完成每日的训练任务,满足了目前每天更新模型的需求。
三、系统实践:Model_bus模型训练&预测系统
在提升了训练速度,满足每天更新模型的需求后,雪球算法团队研发了一套自动化的模型训练&更新&预测系统——Model_bus。该围绕当前模型训练的流程,打通了特征转换,模型训练,模型上线等各个环节,并配有WEB前端服务、异常告警等辅助功能,组成了一套完整的模型系统,提供于生产环境使用。
Model_bus系统分为三个主要模块:
(1)特征转换服务
特征转换服务由定时任务触发,从HDFS下载原始的训练数据,完成数据的特征抽取,生成压缩的TFRecord,上传到HDFS。由于训练数据间无依赖关系,此处的特征转换服务可分布式搭建,缩短转换时间。对于天级别的数据,此处耗时1-2小时。
特征转换服务上传HDFS完毕后,会发送消息到mq,通知模型训练服务开始训练。
(2)模型训练服务
模型训练服务由特征转换服务的消息触发,拉取TFRecord训练数据,依次完成模型训练、模型评估、AUC计算和上传模型等工作。对于天级别的数据,此处耗时5小时内。这里当模型评估不符合上线要求时,会停止后续流程,并触发异常告警。
当符合上线要求的模型上传至HDFS后,模型训练服务发送消息到消息队列,通知模型上线服务。
(3)模型上线服务
模型上线服务是基于Tensorflow-serving搭建了一套集模型加载&检测&上线的服务,由模型训练服务的消息触发,从HDFS拉取对应版本的模型,自动完成模型的更新和上线。当模型加载失败时,上线服务尝试重试,若仍然无法成功,将会异常告警,通知人工介入。
Model_bus的关键日志记录到Mysql,用户可以通过WEB前端界面查看模型状态、相关指标和上线情况。
总结与展望
在雪球头条Feeds流个性化推荐的场景下,雪球算法团队采用了Wide&Deep模型完成内容排序,通过GPU赋能,并完成一系列的优化,大大提升了模型训练速度。在此之上,搭建了一套模型上线系统Model_bus,自动完成模型的训练/更新/线上服务功能,并提供了web操作,异常告警等相关辅助模块。
接下来,雪球算法团队会集中精力打造更加实时的推荐系统,搭建实时数据仓库,推动在线学习的应用。在模型训练方面,研发基于Tensorflow的分布式训练,进一步提升计算能力。
招聘
随着业务不断发展,雪球推荐团队目前仍然有很多的业务需求和技术挑战。如果你对机器学习,推荐系统,算法或者后台开发有相关经验,希望你能一起来到这里添砖加瓦。为股民用户提供更加智慧更加有效的的内容分发系统。
岗位:推荐系统工程师/推荐算法工程师