天池 DeepRec CTR 模型性能优化大赛 - 夺冠技术分享

news2024/9/24 13:15:33

作者:niceperf 团队 (李扬, 郭琳)

大家好,我们是 niceperf 团队,在天池 DeepRec CTR 模型性能优化大赛中,很荣幸取得了冠军的成绩 (Top 1/3802)。这篇文章复盘一下我们的参赛经验,希望对大家有所启发。

1.背景介绍

我们团队包括两名成员:李扬、郭琳,现就职于国内知名互联网公司,担任广告算法工程师。本次比赛的赛题,是在给定的深度学习框架 DeepRec 下,优化 WDL、DeepFM、DLRM、DIN、DIEN、MMoE 六大经典模型的单机 CPU 训练速度。

赛题具有一定的挑战性,我们在日常工作中经常使用的训练性能优化手段主要是分布式训练和数据IO 优化,而本次比赛限定了是单机条件,而且在数据 IO 方面的性能提升空间很有限。这就要求我们结合模型结构与训练框架,做出更细致深入的优化。

好在赛题涉及的技术栈与我们是较为契合的,比赛初期短暂适应后就可以上手优化了。首先,DeepRec的底层框架是 TensorFlow 1.15,我们在日常工作中已对其核心代码非常熟悉;此外,比赛所涉及的模型,团队成员在实际业务中已使用多年,能够结合对模型结构的理解,更快定位训练性能瓶颈所在之处,便于合理分配比赛精力,在优势方向重点发力。

2.优化内容

在做优化之前,要先确定优化的切入点,先把 low-hanging fruit 拿到,再做更深入的优化,有利于比赛的推进。我们逐模型分析了单步耗时,统计结果如下图所示:

image.png
从数据来看,DeepFM 耗时如此之高是不符合预期的,因此我们将该模型作为切入点,展开后续的优化。

(1) IndicatorColumn 算子优选 (DeepFM)

DeepFM 的单步训练timeline 如下图所示:

image.png

对上图数据做 op 粒度的下钻分析后发现,OneHot 为耗时头部算子,而该算子来自于 IndicatorColumn,这里可能存在较大优化空间。

图片

进一步,我们精读了 IndicatorColumn 的源码。为了兼容定长 & 非定长的特征,其输入类型为 SparseTensor,输出类型为 Tensor,为该特征的 multi-hot 表达。具体处理过程分为三步:

Step 1:调用sparse_tensor_to_dense,将 SparseTensor 转为 Tensor;Step 2:调用 one_hot,得到每个子特征的 one-hot 表达;Step 3:调用reduce_sum,将属于同样本的子特征 one-hot 求和,得到 multi-hot 表达。

针对部分高度稀疏的特征,sparse_tensor_to_dense 带来了额外的 padding,引入了无效计算。针对这一问题,我们创建了子类IndicatorColumnV2,改变原有的 multi-hot 生成方式,采用 scatter_nd 代替 sparse_tensor_to_dense + one_hot + reduce_sum。代码示意如下:

tf.scatter_nd(indices=multi-hot 非零值坐标,updates=全 1 向量,shape=原输入 sparse tensor 的 dense shape)

优化后单步训练耗时从 500 ms 降低至 75 ms,前后性能对比如下两表所示,其中红框中的 ConcatV2 算子是用来将多个特征向量拼接送入后续 MLP 使用的。

图片

图片

ConcatV2 算子是 “并行特征处理” 与 “串行MLP 多层计算” 的分界点,它的耗时排名从第 9 名变为了第 1 名,可见特征处理阶段的 op 并行效率得到大幅提升。这一提升一方面来自于 IndicatorColumn 本身的性能优化,另一方面来自于有限硬件资源下,优化后的IndicatorColumn 让出了更多线程供其他算子使用,从而带来了更显著的性能提升。

(2) RNN cell 算子融合 (DIEN)

针对 DeepFM 的优化告一段落,我们将关注点转向单步耗时第二高的 DIEN。其整体结构如下图所示:

图片

在 DIEN 中有两个 RNN 层,即 GRU 与 VecAttGRU,分别对应上图中两个红框内的结构。由于 RNN 采用循环结构,所以很自然的想法是针对单步 cell 做优化,提升单步执行性能从而带动整体性能提升。

比赛中我们分别针对 GRU cell 与 VecAttGRU cell 编写了前向计算与梯度计算的 c++ 算子,以替代原有的多个算子构成的子图,DIEN 整体耗时降低 ~67.96 秒。以 VecAttGRUCell 为例,前向 op、梯度 op 执行逻辑如下图所示:

图片

(3) Attention layer 算子优选 (DIN & DIEN)

DIN 与 DIEN 中都用到了 Attention layer,在它们的原始实现中,存在大量无效 padding。这是因为不同用户的历史行为序列长度不一,当多个样本 batching 在一起时,容易出现部分样本序列短、部分样本序列长的现象。

在原始实现中,会将各个样本的 query padding 到统一长度,与 facts 交互后生成三维张量送入后续 MLP 计算。由于引入了无效 padding,导致 MLP 计算过程存在额外计算量,效率较低。

图片

优化方法就是通过组合合适的算子,去除 padding 部分,具体过程如下图所示。

图片

对比两张图的红框部分,进入 MLP 的张量 shape 从 [B,T,4C] 变为[N,4C]。训练过程中 BT >= N 恒成立,通常一个 batch 内的序列长度不均衡,BT/N 越大,性能收益越大,最终 DIN 与 DIEN 合计耗时降低约 141.38 秒。

(4) 序列特征解析算子融合

在模型输入特征解析环节,存在诸多琐碎的小算子构成的子图,比如求序列均值特征的子图,典型案例如 history_price,原始数据为数值序列通过某个分隔符拼接构成的字符串,构建模型输入时需要将该字符串 split 后转成数值再通过多个步骤求均值,本次比赛中我们编写了两个 c++ 算子 (SparseSequenceLength、StringSplitToNumberAndMean)替换掉了整个繁杂的过程。其他被替换的还有:求序列特征 hash 分桶的子图、序列特征截断的子图,累积耗时降低约 88.47 秒。

图片

(5) 工作流调度优化

对工作流的优化也是我们的一项重点工作。

第一个优化点是 “异步检查点”,思路是将 checkpoint (即检查点) 的保存过程异步化,减少对模型训练过程的干扰。具体的优化方法是开发了 AsynchronousCheckpointSaverHook 替代原有的CheckpointSaverHook,开辟新的线程专门用来保存 checkpoint,合计耗时降低约 31.7 秒。

图片

另一项优化是多线程配置超参优化。如:针对 elm 数据集的相关模型开启 smart stage;stage prefetch 使用独立线程池,算子并行度 intra_op 与 inter_op 均设为 8 (主线程池的配置也一致);stage prefetch threads 设为 1,capacity 设为 2;dataset map threads 设为 1 等等。

配置相关的超参优化暂无放之四海而皆准的规则,是我们结合给定的硬件资源、数据集,在多个模型整体效果上的粗调结果,调参思路是尝试降低数据 parsing 占用的线程资源,观察是否有性能提升。最终多个版本优化后,整体耗时降低约 33.4 秒。

3.优化效果与总结

各优化点的效果汇总如下图所示,整体训练耗时降低 36.65%  (2063.25 秒 → 1307.17 秒)。

图片

主要优化方法分为算子优选与算子融合。两者在思路上是一致的,都是选择性能更好的算子 (组合) 来替换原来的算子 (组合),在本文中的区别只在于是否开发了新的 macro op 替换原有的子图。

一些其他的优化措施包括:异步检查点、多线程超参优化,也对性能提升有较大帮助。此外,其他尝试过但没有取得全局稳定收益的措施还有很多,本文不做详尽探讨。

最后感谢主办方提供的这次比赛机会,过程中主办方技术团队与我们多次沟通协作,不断升级比赛评测系统,使评测结果更加稳定,为我们提供了卓越的竞技环境。通过参加 DeepRec CTR 模型性能优化大赛,对深度学习框架性能优化加深了理解,期待DeepRec 框架继续推出更多性能优化新特性。

DeepRec开源地址: https://github.com/alibaba/DeepRec

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/400273.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KDZD5035系列电缆试验油杯

一、概述 武汉凯迪正大总结十多年的局放试验经验,开发生产了KDZD5035系列电缆试验油杯终端,具有使用方便,性能可靠,本身局放量小等优点,与早期落地式油杯相比,可为用户节约大量的试验成本。 KDZD5520交流…

单片机学习笔记之点阵(8x8)

心血来潮,想捡一下丢了很久的单片机,纪录一下单片机学习简单的点阵显示,及踩到的䟘,找到吃灰很久的普中科技开发板(非广告,为毕设学习买的)。 1. 使用工具 使用开发板: 普中科技开发…

Hive---自定义函数

Hive自定义函数 文章目录Hive自定义函数定义自定义函数步骤创建一个Maven工程,导入依赖创建自定义函数类在 hive 的命令行窗口创建函数创建临时函数创建永久函数UDF打成 jar 包上传到服务器/opt/soft/hive312/lib/目录下将 jar 包添加到 hive 的 classpath建临时函数…

python数据类型与数据结构

目录 一、数据类型 1.1变量与常量 1.1.1变量 1.1.2常量 1.2字符串类型 1.3整数与浮点数 1.4List列表 1.5 元组tuple 1.6字典dict 二、字符串格式化 三、数据输入和类型转换 四、简单列表习题练习 一、数据类型 变量类型: 整数int(4字节&#x…

IR-825 Biotin,IR 825 Biotin,IR825 Biotin,IR-825可以进行修饰生物素基团

IR825 Biotin,IR 825 Biotin,IR-825 Biotin | 生物素IR825荧光染料, 荧光染料IR825生物素,IR-825近红外染料 | CAS:N/A | 纯度:95%1.IR825 Biotin试剂信息:CAS:N/A外观:固…

字符串的使用

数组字符串转换 joinToString列表转成字符串 val str list.joinToString(",")split(“,”)字符串转成列表 val list1 str.split(",")subString()字符串截取 substring(0,2)这个只含开头不含结尾,索引从0开始 substring(2)这个表示截掉前两…

Genymotion模拟器安装

1.本节引言 如果你符合下述三种情况的话,你可以考虑安装一个Genymotion Android模拟器: 没有真机调试,只能用模拟器 嫌SDK内置的AVD启动速度,运行速度慢 电脑配置还可以,最好4G内存以上 如果你满足上述三种情况的话,那么装个比真机还快的Genymotion吧! 官方给出的介绍:…

Go爬虫学习笔记

N002.02 Go分布式爬虫实战 开篇 学习三阶段 入门,照猫画虎底层,了解方方面面,深入阅读源码和书籍借助开源组件来进行复杂设计,窥探各个组件赋能业务 分布式系统: 扩展性一致性可用性高并发微服务 爬虫&#xff1…

Java8使用Lambda表达式(流式)快速实现List转map 、分组、过滤等操作

利用java8新特性,可以用简洁高效的代码来实现一些数据处理。1 数据准备1.1 定义1个Fruit对象package com.wkf.workrecord.work;import org.junit.Test;import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;/*** author wuKeFan* date …

Framework源码面试——Handler与事件传递机制面试集合

Handler面试题 Handler的作用: 当我们需要在子线程处理耗时的操作(例如访问网络,数据库的操作),而当耗时的操作完成后,需要更新UI,这就需要使用Handler来处理,因为子线程不能做更新…

软件回归测试是什么?

一、软件回归测试是什么? 软件回归测试作为软件生命周期的一个组成部分,在整个软件测试过程中占有很大的工作量比重,软件开发的各个阶段都会进行多次回归测试。回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其…

Boosting三巨头:XGBoost、LightGBM和CatBoost(发展、原理、区别和联系,附代码和案例)

❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…

Binder ——binder的jni注册和binder驱动

环境:Android 11源码Android 11 内核源码源码阅读器 sublime textbinder的jni方法注册zygote启动1-1、启动zygote进程zygote是由init进程通过解析init.zygote.rc文件而创建的,zygote所对应的可执行程序是app_process,所对应的源文件是app_mai…

因果推断12--dragonnet论文和代码学习

目录 论文 dragonnet 1介绍 2 Dragonnet 3定向正则化 4相关工作 5实验 6讨论 NN-Based的模型 dragonnet 如何更新参数 dragonnet的损失函数 CausalML Dragonnet类 论文代码 论文 dragonnet Adapting Neural Networks for the Estimation of Treatment Effects 应…

二叉搜索树的实现

什么是二叉搜索树1.若它的左子树不为空,那么左子树上所有节点都小于根节点2.若它的右子树不为空,那么右子树上所有节点都小于根节点3.它的左右子树也分别是二叉搜索树4.使用中序遍历结果是从小到大定义节点,使用静态内部类static class TreeN…

http组成及状态及参数传递

http组成及状态及参数传递 早期的网页都是通过后端渲染来完成的:服务器端渲染(SSR,server side render): 客户端发出请求 -> 服务端接收请求并返回相应HTML文档 -> 页面刷新,客户端加载新的HTML文档&…

7综合项目 旅游网 【7.精选分类】

精选旅游人气旅游→收藏次数最高最新旅游→日期最新主题旅游→主题关键字相同在首页将精选的内容动态展示的实现分析首页中的精选包含“人气旅游”、“最新旅游”、“主题旅游”三个部分index.html//页面加载完成,发送ajax请求根据点击不同分类展示不同内容人气旅游→收藏次数最…

分享17个提升开发效率的工具“轮子”

本文是向大家介绍平时在开发中经常用到的小工具,它能够极大得提升我们的开发效率,能够解决平时开发中遇到的问题。前言在java的庞大体系中,其实有很多不错的小工具,也就是我们平常说的“轮子“。如果在我们的日常工作当中&#xf…

数据结构课程设计:高铁信息管理系统(C++实现)

目录 简介实验输出实验要求代码运行环境结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金,有幸在竞赛中拿过一些国奖…

嵌入式Linux驱动开发(一)chrdevbase虚拟字符设备

Linux下三大驱动:字符设备,块设备,网络设备。一个硬件可以从属于不同的设备分类。 0. Linux应用程序对驱动程序的调用流程 驱动加载成功后会在/dev目录下生成一个文件,对该文件的操作就是对设备的操作。当我们在用户态调用一个函…