嘉宾 | 黄彬
整理 | 涂承烨
日前,在51CTO主办的AISummit 全球人工智能技术大会上,网易云音乐算法平台研发专家黄彬带来了主题演讲《网易云音乐在线预估系统的实践与思考》,从技术研发的视角分享了如何建设一套高性能、易用,且功能丰富的一个预估系统的相关实践与思考。
现将演讲内容整理如下,希望对诸君有所启发。
首先,我们来看一下整个预估系统的一个架构,如下图所示:
系统整体架构
中间的Predict Server,是预估系统的核心组件,包括查询组件、特征处理组件,和模型计算组件。左侧的监控系统用于线网服务的监控,确保系统网络的畅通。右侧的PushServer用于模型推送,把最新的模型推送进线上预估系统进行预测。
目标是建设一套高性能、易用,且功能丰富的一个预估系统。
如何提升计算性能?我们常见的计算性能问题有哪些,我从三个方面进行阐述。
在通用方案里,我们的特征计算和模型计算是分进程部署的,这样就会导致有大量的特征存在跨服务、跨语言的传递,会带来多次编解码和内存拷贝,导致会存在比较大的性能开销。
我们知道在模型更新时,会有大块类型的申请和释放。然而在一些通用方案里,它不会自带模型预热的方案,这样就会导致模型更新的过程中有比较高的耗时抖动,无法支持模型的实时化更新。
一般的框架使用的是同步机制,并发度不够,CPU利用率比较低,无法满足高并发的计算需求。
那么,我们在预估系统里面是如何解决这些性能瓶颈呢?
我们为什么要做这样一个事情呢?因为在传统方案里,我们都知道特征处理和模型计算分进程部署,这样就会带来比较多的特定的跨网络传输,序列化、反序化,还有频繁的内存申请和释放。尤其是在推荐场景的特征量特别大时,这样就会带来比较明显的性能开销。下图中,靠上方的流程图就展示了通用方案里具体的情况。
无缝集成计器学习库
为了解决上述问题,我们就在预估系统里面将高性能计算学习框架集成到预估系统内部,这样的好处就是我们能够确保特征处理和模型计算能够同进程部署,能够以指针的形式去实现对特征的操作,避免序列化、反序列化以及网络传输的开销,从而在特征计算以及特征处理这一块带来比较好的计算性能提升,这就是无缝集成机器学习带来的好处。
首先,整个系统采用全异步的架构设计。异步架构带来的好处就是外部调用是无堵塞等待的,所以异步机制可以确保在CPU高负载的情况下,例如在60%到70%的情况下,依旧保持线网服务的耗时稳定性。
其次,访存优化。访存优化主要是基于服务器的NUMA架构,我们采用了绑核运行的方式。通过这种方式能够去解决之前NUMA架构存在的远端内存访问的问题,从而提升了我们服务的计算性能。
第三,并行计算。我们对计算任务进行分片的处理,采用多线程并发的方式做计算,这样就能够较大程度降低服务的时耗,提升资源的利用率。
架构设计考量
以上就是我们在预估系统的系统架构考量上的实践。
多级缓存,主要应用在特征查询阶段和初级阶段。我们封装的缓存机制,一方面能够降低查询的外部调用,另一方面也能够减少特征抽取导致反复无效的计算。
通过缓存的方式,可以极大程度的提升查询和抽取的效率。尤其在查询阶段,我们根据特征的重要程度以及根据特征的量级,我们封装了多种组件,例如同步查询、异步查询以及特征批量导入等组件。
第一种是同步查询,主要适用于一些比较重要的特征,当然同步查询的性能没有那么高效。
第二种是异步查询,主要针对一些“艾特维度”的特征,这些特征有可能重要程度不是那么高,那么就可以采用这种异步查询的方式。
第三种是特征批量导入,主要适用于特征规模不是特别大的特征数据。我们将这些特征批量导入到进程内部,就可以实现特征的本地化查询,性能是非常高效的。
多级缓存
介绍完缓存机制之后,我们来看一下模型计算的行为优化。模型计算,我们主要从模型输入优化、模型加载优化以及内核优化,这三个方面进行优化。
在模型输入这一块,大家都知道TF Servering采用的是Example的输入。Example输入会存在Example的构造、Example的序列化反序化以及模型内部调用Parse Example的情况,这样就会存在比较明显的耗时。
在下图中,我们看【优化前】的截图展示了模型计算优化前的数据统计情况。我们可以看到,有一个比较长的Parse Example解析耗时,并且在Parse Example解析完之前,其他op是没有办法执行并行调度的。为了解决模型树的性能问题,我们在预估系统里封装了高性能的模型输入方案。通过新的方案,我们能够实现特征输入零拷贝,从而减少这种Example的构造耗时以及解析耗时。
在下图中,我们看【优化后】的截图展示了模型计算优化之后的数据统计情况,我们可以看到,已经没有了Parse Example解析耗时,就只剩下Example的解析耗时。
模型计算优化
介绍完模型输入优化,我们来看一下模型加载的优化。Tensorflow的模型加载是懒加载的模式,模型加载到内部之后,它并不会进行模型预热,而是要等到线网正式请求来了之后才会进行模型预热,这样就会导致模型加载之后会有比较严重的耗时抖动。
为了解决这个问题,我们在预估系统内部实现了自动的模型预热功能,并且实现了新旧模型的热切换,还实现了旧模型的异步卸载和内存释放。这样通过这些模型加载的一些优化手段,实现模型的分钟级更新能力。
接下来,我们看一下模型内核的优化。目前我们主要是对Tensorflow内核做了一些内核同步优化,以及我们会根据模型去调整op间和op内的一些线程池等等。
以上就是我们在模型计算方面的一些性能优化的尝试。
通过介绍上面的性能优化方案之后,我们来看一下最终的性能优化成果。
性能优化成果
这里我们使用预估系统和通用方案的系统做了一个对比。我们可以看到预估系统在CPU使用达到80%的情况下,整个服务的计算耗时以及超时率都非常稳定,非常低。通过对比,我们可以得出新方案(预估系统)在计算处理这一块,性能是有提升两倍,对CPU的榨取能力更强,服务耗时更低。
得益于我们对系统的优化,我们可以为业务算法提供更多模型的复杂度计算以及更多候选集的计算。
上图中举了一个例子,候选集从之前的300个候选集扩充到1000个候选集,同时我们增加了模型计算复杂度以及使用了一些比较复杂的特征,分别在多个业务里带来了比较好的效果提升。
以上就是预估系统在性能优化上以及性能优化成果的介绍。
系统采用分层的架构设计。我们将整个预估系统分成三层,分为底层架构层、中间模板层和上层结构层。
底层架构层主要提供异步机制、任务队列、并发调度、网络通信等。
中间模板层主要提供模型计算相关的组件,包括查询管理、缓存管理、模型加载管理以及模型计算管理。
上层接口层主要提供Highlevel的接口,业务仅需实现此层接口,大量减少代码开发。
通过系统的分层架构设计,不同业务之间完全可以复用底层和中间层的代码,开发只需要关注最上层的少量代码开发就可以。同时,我们也在进一步的思考,有没有什么办法能够进一步减少上层接口层的代码开发?下面我们来详细介绍一下。
通过基于动态pb技术实现特征查询和特征解析形成的通用化方案的封装,能够实现仅通过XML配置表名、查询KEY、缓存时间、查询依赖等就能实现特征查询、解析、缓存全流程。
如下图所示,我们通过少量的几行配置,就能够实现复杂的一个查询逻辑。同时,通过查询封装提升了查询的效率。
特征计算可以说是整个预估系统里面,代码开发复杂度最高的一个模块,那么什么是特征计算呢?
特征计算包括离线过程和在线过程。离线过程其实就是离线样本,通过处理得到离线训练平台需要的一些格式,例如TF Recocd的格式。在线过程,主要是对在线的请求做一些特征计算,通过处理得到在线预测平台需要的一些格式。离线过程和在线过程,其实对特征处理的计算逻辑是完全一样的。但是因为离线过程和在线过程的计算平台不一样,使用的语言不一样,就需要开发多套代码来实现特征计算,所以存在以下三个问题。
一致性难以保证的根本原因是离线训练和在线预测对特征处理逻辑难以统一。一方面会影响到算法的效果,另外一方面会导致在开发过程中带来比较高昂的一次性校验成本。
如果要新加一个特征,就需要涉及到离线过程和在线过程的多套代码的开发,导致开发效率非常的低。
复用难,主要原因是框架缺乏对复用能力的支持,导致不同业务之间想要做到特征计算的复用,变得非常难。
以上就是特征计算框架存在的一些问题。
为了解决这些问题,我们将按照如下四点思路来逐步解决。
首先,我们提出算子的概念,将特征计算抽象成算子的封装。其次,算子封装之后,我们建立一个算子库,通过算子库能够提供业务之间算子的复用能力。然后,我们基于算子,定义特征计算描述语言DSL。通过这种描述语言,我们能够完成特征计算的配置化表达。最后,就是前面介绍的,因为在线过程和离线过程存在多套逻辑,会导致逻辑不一致的问题,我们就需要解决特征一次性的问题。
以上四点,就是我们如何对特征计算框架进行封装的思路。
为了实现算子抽象,首先必须实现数据协议的统一。我们利用动态pb的技术,根据特征的原数据信息,将任意的一个特征按照统一的数据进行处理,这样就为我们的算子封装提供了数据基础。接下来,我们对特征处理的过程进行抽样封装,将特征计算过程抽象成解析、计算、组装、异常处理几个过程,并且统一计算过程API,从而实现了算子抽象。
有了算子的抽象之后,我们就可以建立算子库。算子库分为平台通用算子库和业务自定义算子库。平台通用算子库主要是实现公司级的复用。业务自定义算子库主要是针对业务的一些自定义场景及特征,实现组内的复用。我们通过算子的封装以及算子库的建设,实现特征计算的多场景复用,提升开发效率。
特征计算的配置化表达,是指定义特征计算表达的配置化语言叫DSL。通过配置化语言,我们能够实现算子的多层嵌套表达,能够实现四则运算等等。下图中的第一幅截图展示了配置化语言的具体语法情况。
我们通过特征计算的配置化语言能够带来什么好处呢?
第一,我们能够通过配置化完成整个特征计算,从而达到开发效率的提升。
第二,我们能够通过发布特征计算配置化表达,实现特征计算的热更新。
第三,训练和预测使用同一份特征计算的配置,从而实现线上线下一致性。
这就是特征计算表达带来的好处。
前面说到,特征计算分为离线过程和在线过程。因为离线和在线的多平台原因,导致逻辑计算的不一致。为了解决这个问题,我们在特征计算框架里,实现了特征计算框架的跨平台运行能力。核心逻辑采用C++开发,对外暴露的是C++接口以及Java接口。在打包构建的过程中,能够一键实现C++的so库以及jar包,从而确保特征计算能够运行在线计算的C++平台以及离线的Spark平台或者说Flink平台,并且用特征计算表达,可以确保特征计算实现线上线下逻辑的一致性。
上面介绍的是特征计算的具体情况。下面我们来看一下特征计算目前已经取得的一些成果。
我们现在已经沉淀了120家的算子,通过特征计算的DSL语言能够实现配置化,完成整个特征计算。通过我们提供的跨平台运行能力,实现了线上线下逻辑不一致的问题。
下图中的截图展示了通过少量的配置,就可以实现整个特征计算的过程,较大程度提高了特征计算的开发性效率。
上述介绍了我们在开发效率提升的一个探索。总得来说,我们通过系统的分层设计,提高代码的复用度,以及通过对查询、对抽取、对模型计算的封装,能够实现配置化的开发流程。
模型计算同样也是采用封装的形式。通过配置化表达的形式,实现模型的加载、模型的输入构造、模型的计算等等,使用几行配置,实现整个模型计算的表达过程。
下面我们来看一下模型实时化的落地案例。
我们为什么要做这样的模型实时化项目?
主要原因是传统的推荐系统是天级别更新用户推荐结果的系统,它的实时性非常差,无法满足这种实时性要求比较高的场景,例如我们的直播场景,或者说其他一些实时性要求比较高的场景。
还有一个原因是传统的样本生产方式,是存在特征穿越的问题。什么是特征穿越呢?下图中展示了特征穿越产生的根本原因,是因为我们在做样本拼接的过程中,我们采用的是“T-1”时刻的模型预估结构,和“T”时刻的特征进行拼接,这样就会出现特征穿越的问题。特征穿越会非常大程度的影响线网推荐的效果。为了解决实时性的问题,以及为了解决样本穿越的问题,我们就在预估系统里去落地这样一个模型实时化的方案。
模型实时化方案从三个维度进行阐述。
样本实时生成。我们基于在线预估系统,将预估系统的特征实时落地到Kafka,通过RACE ID的形式关联,这样我们就能够确保样本实现秒级落盘,并且能够解决特征穿越的问题。
模型增量训练。有了样本的秒级落盘之后,我们就可以修改训练模块,实现模型的增量训练,就能实现模型的分钟级更新。
预估系统实时化。有了模型的分钟级导出之后,我们通过模型推送服务Push Server,将最新的模型推送到线上预估系统,能够使得现场预估系统使用最新的模型进行预测。
模型实时化方案总得来说,就是要实现样本的秒级落盘,实现模型的分钟级训练和分钟级线上更新。
我们现在的模型实时化方案,已经在多个场景进行了落地。通过模型实时化方案,在业务上有比较好的效果提升。
上图中,主要展示了模型实时化方案的具体实验数据。我们可以看到增量训练,它的训练周期越短越好。通过具体的数据,我们可知周期为15分钟的效果远远大于2小时、10小时、一天的。现在的模型实时化方案已经有一套规范化的接入流程,能够批量为业务带来比较好的效果提升。
上述介绍了预估系统如何提升计算性能,如何提升开发效率,以及如何通过工程的手段带来项目算法提升三个方面的探索和尝试。
整个预估系统的平台价值,或者说整个预估系统的平台目的,可以概括成三个字,就是“快、好、省”。
“快”就是前面介绍的应用性建设。我们希望通过持续的应用性建设,使得业务的迭代能够更加高效。
“好”就是希望通过工程手段,例如通过模型实时化方案,以及通过特征计算的线上线下逻辑一致性方案,能够为业务带来比较好的效果提升。
“省”就是使用预估系统的更高性能,能够更加节省计算资源,以及节省计算成本。
未来,我们会朝这三个目标持续努力。
以上就是我对云音乐预估系统的介绍,我的分享到此为止就结束了,谢谢各位!
以上是网易云音乐算法平台研发专家黄彬:网易云音乐在线预估系统的实践与思考的详细内容。更多信息请关注PHP中文网其他相关文章!