装箱百万奖金,第六届全国工业互联网数据创新应用大赛火热报名中! 了解详情
写点什么

推荐、搜索、广告全场景预估框架实践与思考

  • 2022-10-08
    北京
  • 本文字数:5851 字

    阅读完需:约 19 分钟

推荐、搜索、广告全场景预估框架实践与思考

一、写在前面


在线实时预估推理已成为搜索、推荐、广告等领域内不可或缺的组成部分,被使用在推荐系统的级联排序的各个环节,比如召回/粗排/精排等凡是依赖模型输出结果的环节,对于 OPPO 来说也不例外,预估服务已经部署在 OPPO 的广告、游戏、内容推荐、搜索、用户增长,小布助手推荐在内的很多个场景,有超过千级规模的实例节点,某些场景单样本数千特征,某些场景粗排单次预估数万候选,某些场景使用百亿特征千亿参数的模型,是在线服务范畴一块不小的成本。

 

在 OPPO 这种多业务多场景以及大规模流量下,以具有成本效益的方式来进行模型在线实时预估,是具有挑战性的。我们可能面临以下几个挑战:第一个,预估能力是一个通用的需求,满足不同业务不同场景的诉求的同时,如何兼顾整体效率,避免重复开发?第二个,如何应对超大规模物料候选数量,大规模的特征和模型所带来的巨大算力成本消耗,我们做了那些优化?第三个,如何解决在线预估和训练的特征样本不一致的担忧?等等。接下来的内容将从预估框架核心功能角度出发,分享我们在建设推搜广全场景预估框架的一些经验和体会。



二、预估框架介绍


统一预估框架也就在此背景下产生,统一预估框架(代号 KIDDY)是一套由 OPPO 数智工程系统机器学习部自研的预估框架,该框架由 C++ 语言开发,经过不断迭代及优化,目前已支撑数智工程系统承接的几乎所有的智能推荐业务,预估框架的高性能以及面对各种业务场景所体现出来的可扩展及可伸缩性,都已经得到严格的生产环境验证。


简单来说,预估是一个围绕样本/特征/模型的计算逻辑集合,如图所示,输入可以是用户 ID +物料 ID 列表,输出是每个物料的预估结果,预估结果一般是浮点数或者浮点数集合,主要功能包括特征计算,模型计算,以及样本特征的 DUMP 管理。



三、特征计算实践


首先介绍预估框架的一个核心功能:特征计算框架,如图所示,特征计算框架负责完成样本补全和特征计算两个流程的工作,人为的分成这两个流程主要是为了将 IO 密集型操作和计算密集型操作分离,这么设计的好处后面章节再详细解释。



还是先回到上面提到的挑战,即 OPPO 的推搜场景的是非常多的,类似烟囱式每一个场景做一个特征计算模块对我们有限的人力是不现实的,面对这种现实和挑战,要求必须在复用或者低代码开发角度做些努力,首先引出特征计算框架的第一个特性。


可扩展算子模式。通过抽象通用能力及设计注册机制支持快速定制业务算子(注册的是算子类名及类构造函数),不同业务可以有特有的算子,也可以共享通用的算子能力,这样就相当于是面向全场景实现增量算子能力开发,这大大提升了我们的效率。算子能力支持全量注册,按需创建,也就是说对某个场景来说,特征计算框+特征算子集合的全局能力都可以提供给这个场景使用,不使用不会冗余创建浪费资源,甚至对于需要保密的策略,也可以做到策略算子代码层面的权限隔离。



接着介绍第二个特性,配置化计算逻辑,算子是程序是固化的,但算子的参数和使用是灵活的,特征配置文件(目前是以 schema_conf+feature_conf 两个文件的形式存在)描述一个完整的在线特征处理逻辑,即数据从哪里取,取什么,取到了怎么处理,输出什么,具体来说就是样本补全环节主要是 IO 密集型操作,用来从分布式存储中取出数据并组装成一个完整的原始样本,比如用户行为序列,物料画像等等数据,样本补全的输出的原始样本即图中所示的 sample-schemas,sample-schemas 作为特征计算的特征计算是纯计算的计算密集型操作,是对原始样本的进行各种组合和变换,不限于对数值类型的标准化,分桶,缩放,对时间的滑动窗口操作,对文本的分词,相似度处理等等不胜枚举,最终输出是 sample-features(稀疏和稠密特征)。通过配置化计算逻辑描述的方式有效的解耦了框架与算法策略逻辑,让工程与算法的工作就像偶尔有交叉的平行线,彼此依赖又独立前行。



以上两个特性主要是人力效率的一个解放,除了这之外,机器的效率也同样重要,在性能优化进阶上分享我们采用的三手段:第一个是预分配内存管理,这里我们同样的机制设计了 sample/slot/feature 三个层级的预分配对象池,三个对象池是独立的,可以灵活配置大小来应对不同的场景和机器规格,每一个对象池可以简单理解为都是一个二维数组,第一维是线程 ID,第二维是跟这个线程绑定的对象池列表,申请和释放都由这个线程自己完成,确保并行无锁情况下的线程安全,对于计算密集型的服务来说,高频内存申请及释放往往是性能杀手,预分配内存是性能优化的常规且必要手段。 



第二个是无冗余分离计算,在计算逻辑上,不同维度的特征分离计算,用户维度的公共特征只计算一次只保存一份,物料维度特征包括物料和交叉特征是正交的,可分批次并行计算,确保一次预估在不同维度的特征计算上没有冗余,在内存上也没有冗余存储,通过指针或引用指向公共特征即可。那么如何区分用户维度和物料维度的特征计算呢,其实在特征配置文件中其实就已经明确了这一点,就是只要依赖了物料维度 schema 的 slot 配置,就属于物料维度的,这类 slot 的计算规模必然要乘以物料候选规模,除此之外就是用户维度的,它对所有物料候选都是相同的,不需要重复计算。消除冗余计算是性能优化一个重要方向,其关键是如何识别冗余,一件事如果能不做肯定是性能最优的。 


第三个是自动化联动缓存,所谓联动缓存是想联动原始样本与抽取计算后的特征数据,因为人为的把在线特征工程分成了负责 IO 操作的样本补全环节和只负责纯计算的特征计算环节,这样特征抽取计算环节其实就具备了输入不变输出也不变的特点,幸运的是,目前很多推荐场景的物料画像更新频率是分钟级别,有些静态画像甚至是小时级别,我们在缓存物料画像的同时,也联动去缓存以它作为输入的特征抽取结果,用缓存代替直接计算。数据联动缓存这个特性上线某推荐场景后,对比线上原版本负载降低 40%,当然这个优化成果跟原始物料画像缓存时间直接相关,不同场景的负载收益不尽相同。这里提及性能优化的又一个常见方向,用缓存替代直接计算,前提当然读取缓存的开销要大大小于直接计算的开销。



四、模型计算管理


说完了特征计算框架,接下来说模型计算的部分,预估框架在 OPPO 也算伴随了模型从 LR/FM/GBDT/DNN 模型演进的过程,即便从深度神经网络模型角度,在内部组织架构更新和业务演进过程中,预估框架就曾采用过,原生 TensorFlow、PaddlePaddle,以及目前主要使用的 ONNX Runtime 作为推理方案,还是说回到效率,对于各种浅层模型也好,深度模型也罢,预估框架从设计之初就面向多方案的,推理环节采用统一的接口和数据格式,不同的推理方案对于预估框架来说也只是不同的算子或者插件,如果需要新增新的推理方案,对于框架来说只是新增算子和相关依赖,预估框架仍然是复用的。



预估框架支持推理分离架构,我们这里所指的分离架构是指预估框架本身集成了推理引擎,与此同时也可以走 RPC 调用推理服务集群,但是分离是有代价的,特征数据并不小,单请求甚至就有几百 K 到甚至到 M 级别,增加了网络延时不说,还有特征数据的反序列化开销。网络传输时延可以尽量同机房部署以及通过底层 RDMA 技术来缓解,但是特征数据是 sample/slot/feature 三层嵌套结构,假设一次预估有 200 候选,每个候选 1000 个 Slot,每个 slot 产出 10 个特征,那么一次预估的内存申请及释放就是百万级的,所以特征数据的反序列化开销非常不容小觑,进入推理集群推理接口计算之前,很多时间就已经消耗掉了,所以解决这个瓶颈,我们借鉴了零拷贝的思想,对于物料特征采用自定义格式的字节序列+内存数据强制类型转换的方案,将内存申请复杂度从 O(N*M*K) 直接降为 O(N),这个优化直接将推理计算之外 RPC 延时降低了 70%,效果非常显著,进一步降低了分离的代价。



既然分离有代价那为什么还要设计分离架构,主要是两个原因:首先就是机器利用率受限于最短板,类似于木桶理论,在某些场景的实际使用中,因为受限于 GPU 机器规格(通常 80 核+ 4 卡),CPU 率先达到瓶颈,导致 GPU 没有充分利用情况下整机就已经达到瓶颈,所以有必要将一部分 CPU 计算分离到另外的机器,来让 CPU 与 GPU 利用率更加均衡。其次就是也可以缓解对内存规格的高要求或者让内存有冗余可以支持更多的模型试验,同时分离架构也可以通过对模型分治来对后端模型服务集群的内存压力进行缓解(可选)。当然主要还是第一个原因,CPU 和 GPU 利用不均衡,GPU 无法充分利用,这个是最主要的原因。



补充一句,软件领域没有银弹,分离架构并不适用于所有场景,比如有些低复杂度或者低延时要求的模型推理场景,本地推理仍然在成本方面是更优的选择(比如粗排),具体情况具体分析,灵活运用架构所具有的能力才能做出最佳决策。

五、预估加速机制


对于预估来说,延时要求也是一个绕不开的话题,有些场景的延时要求是很高的,这就要求预估框架有通过计算资源换时间的加速能力,预估多阶段加速机制,主要是两个方面:首先是多核加速(蓝星所示):三个核心处理流程样本补全,特征计算,模型计算,支持候选维度拆分,三个处理流程有独立的线程池执行并行任务来加速,并行度可以通过参数灵活配置,其次是多机加速(黄星所示):多机加速有两种方式,第一个是上游调用预估服务的时候在预估请求层面支持基于物料候选维度的请求拆分,第二个是预估服务在调用推理集群可以进行基于物料维度的请求拆分。多级加速会存在用户维度计算冗余,要做好延时与成本的平衡。


六、在线离线一致性


下面我们讨论下如何解决在线预估和训练的特征样本不一致的担忧?这个是算法工程师非常关心的,这里就不陈述我们历史上的演进过程了,这里我们只介绍我们现在的在线离线一致性方案,方案有两个特性:


首先就是计算逻辑约束:线上预估和离线训练流程共用相同的特征抽取库(C++库),确保在线离线计算逻辑一致,确保原始样本一致,通过计算或者变换得到的特征也是一致的。


其次就是回流数据约束。在线预估时缓存全量候选的样本特征数据的快照,曝光回调时将曝光的候选样本特征 DUMP,其余淘汰,如果没有回调超过最大过期时间也会淘汰,一般就是几秒,确保预估时样本=训练时样本,达成在线 Serving 与离线 Training 是同一份样本数据的目标,回调数据约束这个机制目前也是目前 online-learning 方案的一个必要环节和组成部分。


目前支持缓存原始样本以及缓存计算后的特征两种阶段的数据进行 DUMP,原始样本数据 DUMP 方式(instance dump)在成本上更加友好一些,要留意离线使用时所采用的特征抽取库的版本是相同的,特征数据 DUMP 方式(feature dump)是对于目前是流式学习系统的必要环境和组成部分。


七、粗排针对性优化


其实某些场景级联排序环节中只有召回和精排,粗排并不是必须的,所以预估框架的主要是场景其实是精排,预估框架最初也是基于精排设计的,精排预估简单来说具有单样本特征多模型复杂的特点,如上文提及到的我们针对这些特点做了并行无锁对象池,无冗余分离计算,自动化联动缓存,多阶段加速机制,零拷贝数据传递,多模型特征共享等等优化实践。但是粗排具有跟精排场景不同的特点,尽管特征和模型都相对简单了,但是候选规模非常大,比精排高出一到二个量级,那么如何在现有框架基础上应对粗排这种超大候选规模的预估呢?



首先我们也是比较采用的业界通用的双塔模型的粗排架构,物料向量部分离线或者近线生产,来加速在线的预估速度,无论离线还是在线流程都是可以复用预估框架的能力,但是基础方案的万级候选规模下性能还是备受挑战的。那么超大候选规模(万级+)对预估框架意味着什么,用一个架构图来辅助说明,如图所示在表中爆炸图形及红叉图形的地方都面临候选规模爆炸带来的性能挑战,我们的优化也着眼于此图中所标识的这些点,最终效果非常突出,性能较双塔模型架构基础上再提升了 5 倍以上,用极小的开发成本补齐了短板。


具体优化点,逐一简述一下:


协议上,数据结构降维,因为对于双塔粗排来说,并不需要实时传递物料维度的画像信息,只需要物料 ID 就好了,所以可以将数据结构拍平为一维数组,预估结果回包也是同样道理,这个思路与分离架构特征数据重新编码思路类似,也是为了降低内存申请及释放的开销,不仅仅是对预估服务有益,更重要是的我们内部调用预估的是推荐引擎服务,是 JAVA 语言开发的,频繁组装带结构的对象也会造成很大的 GC 压力。


流程上,自适应精简计算流程,将物料样本补全和特征抽取计算流程裁剪掉,因为对于双塔粗排来说,是不需要物料维度特征抽取计算的,如果跟精排处理流程一样,势必造成冗余环节的处理。以上两个优化点不仅计算角度得到收益,同时内存收益也很大,前面提及过预分配内存池有候选维度的,内存代价挺大的。最后就是对推理输入处理的预分配内存也做了调整,使其更适应大规模候选的特点。



随着对效果要求的不断增高,目前粗排模型也有越来越复杂的趋势,比如目前某些场景的粗排模型已经从经典的双塔模型演进到双塔+MLP 模型,未来会进一步尝试引入物料实时特征,采用类似全连接 DNN 的模型,粗排越来越接近精排,这对已经做完的粗排性能优化的预估框架来说又会是一个新的挑战,我们除了继续去消除冗余计算,探索利用 SIMD 指令特性,低精度计算等性能优化方向之外,也在算力成本可控方面做些尝试。


这里提及的算力成本可控引申为两个方面,第一个是通过算力评估工具评估特征算力消耗指标(预估框架会统计每个特征槽位的计算时间,这个计算时间其实对应着算力消耗),作为工程优化以及算法特征选择上的参考,便于算法工程师做出最佳 ROI 的决策,深化算法与工程的协同设计。


第二个是通过在线自适应机制限定算力的上限,简单来说就是固定算力成本这个因子,在成本因子不变的前提下进行算法策略的优化,技术上采用延时和负载进行 PID 自适应控制,加上对复杂模型和简单模型切换,来达成算力成本可控的目标。这个机制理论上可以复用在召回/精排在内的级联排序的各个环节。



八、总结与展望


以上只是预估框架整体内容的一个缩影,介绍了特征抽取框架的设计,在线离线一致性约束机制,插件式模型管理等功能。预估框架是面向不同业务灵活伸缩,通用复用,增量扩展的框架,在性能优化上分享了预分配并行无锁对象池,数据结构降维,自动化联动缓存等技巧,这些都是我们在生产环境验证过的朴素且收益显著的方案,当然预估框架目前还有不少需要提高的空间,这个领域内的工作我们还会不断演进着,进一步挖掘冗余计算并消除,不断通过技术迭代,提供更好的工程架构产品以便更好的赋能给算法工程师等相关从业者,让他们的策略及想法以更有成本效益的方式落地,让工程的价值也得到更好的体现。

 

2022-10-08 18:456234

评论

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

聊聊多线程(一)线程的基础,linux集群架构师

Java 程序员 后端

聚焦WAVE SUMMIT 2021,大咖齐聚共研深度学习,关于线程池的五种实现方式

Java 程序员 后端

群晖DS218+做maven私服(nexus3),mysql性能调优

Java 程序员 后端

聊聊MyBatis的历史,mysql原理详解

Java 程序员 后端

腾讯T4架构师:刷3遍以下面试题,你也能从小公司成功跳到大厂

Java 程序员 后端

腾讯T8纯手写66个微服务架构设计模式,全部学会真的“变强

Java 程序员 后端

膜拜!华为内部都在强推的783页大数据处理系统:Hadoop源代码pdf

Java 程序员 后端

聊聊多线程(四)线程安全之AtomicInteger类,最全面试考点与面试技巧

Java 程序员 后端

脉脉上瞬间爆火的Java高级面试题被全网封杀,这套资源到底有什么魅力?

程序员 后端

脑筋急转弯:如何用两个栈实现一个队列?用两个队列实现一个栈

Java 程序员 后端

腾讯技术大牛带你玩转Spring全家桶,赠三本Spring实战篇电子文档

Java 程序员 后端

获12w+星标的神仙文档再度上榜,简直是一套活生生自学Java的福星

Java 程序员 后端

老大让我优化数据库,我上来就分库分表,他过来就是一jio

Java 程序员 后端

老板,运维和开发又打起来了!,你连基础的JVM运行时内存布局都忘了

Java 程序员 后端

自己搭建电商平台初期,原来“超卖,java书籍百度网盘

Java 程序员 后端

聊聊RabbitMQ RabbitMQ相关面试题,kafka入门教程步骤

Java 程序员 后端

肝完这份Linux网络编程笔记,感觉2年开发白干了,nginx反向代理原理转发过程

Java 程序员 后端

腾讯T4:结合我多年工作经验给程序员的几点忠告,别再埋头苦干了

Java 程序员 后端

腾讯、美团等六家大厂收到offer,浅谈大数据面试经历,2021Java面经

Java 程序员 后端

腾讯启动有史以来最大校招:苦逼程序猿,拿头发换了高质量生活

Java 程序员 后端

老夫面试两个月真实经历:面试了20家大厂之后,发现这样介绍项目经验

Java 程序员 后端

老生常谈:MySQL高可用,java泛型原理

Java 程序员 后端

老面试官问我:LRU 和 Innodb Buffer Pool 有什么关系?

Java 程序员 后端

聊聊MySQL主从复制的几种复制方式,上岸蚂蚁金服

Java 程序员 后端

架构实战营- 模块二作业

危险游戏

架构实战营

膜拜!京东T9大牛沉淀三年终于整理出了这份架构核心修炼之道

Java 程序员 后端

聚焦WAVE SUMMIT 2021,大咖齐聚共研深度学习(1),java在大数据技术中的运用

Java 程序员 后端

若依集成 WebSocket,linux学习步骤

Java 程序员 后端

美团二面:聊聊线程池设计与原理,由表及里趣味解析,linux操作系统实用教程课后答案

Java 程序员 后端

脑筋急转弯:如何用两个栈实现一个队列?用两个队列实现一个栈(1)

Java 程序员 后端

腾讯五面、快手三面已拿offer(Java岗位,linux基础教程第二版pdf

Java 程序员 后端

推荐、搜索、广告全场景预估框架实践与思考_技术管理_OPPO 魏刚_InfoQ精选文章