线上推理*敏*感*词*能效率提升一倍-爱奇艺深度学习平台
优采云 发布时间: 2021-07-17 21:39线上推理*敏*感*词*能效率提升一倍-爱奇艺深度学习平台
写在前面
在CTR(Click Through Rate)点击率估算的推荐算法场景中,TensorFlow Feature Column在实践中被广泛使用。这一方面为模型特征处理带来了便利,另一方面也带来了在线推理服务的一些性能问题。为了优化推荐服务的性能,提高在线服务的效率,爱奇艺深度学习平台团队在实践中总结了一些性能优化方法。
经过这些优化,推荐业务的在线推理服务的性能效率可以提高一倍以上,p99延迟可以降低50%以上。
背景介绍
Feature Column 是 TensorFlow 提供的用于处理结构化数据的工具。它是将样本特征映射到训练模型特征的桥梁。提供多种特征处理方式,使算法人员可以方便地将各种原创特征转化为模型输入进行模型实验。
如上图所示,所有的Feature Columns都派生自FeatureColumn类,并继承了CategoricalColumn、DenseColumn和SequenceDenseColumn三个子类,分别对应稀疏特征、密集特征和序列密集特征。算法人员可以根据样本特征的类型找到对应的界面直接适配。
此外,Feature Column 和 TF Estimator 接口很好地集成在一起。通过定义相应的特征输入,可以直接在预定义的Estimator模型中使用。 TF Estimator在推荐算法中的使用非常普遍,尤其是它封装了分布式训练的功能。
下图是使用Feature Column处理特征并进入Estimator DNN Classifier的例子:
虽然Feature Column使用起来非常方便,模型代码写起来也比较快,但是在爱奇艺推荐业务的线上服务实施过程中,一些性能问题逐渐凸显。下面将一一介绍我们在实践中遇到的。一些问题以及如何优化。
整数特征哈希优化
推荐模型通常将ID特征散列到一定数量的bucket中,然后转换成Embedding作为神经网络的输入,比如视频ID特征、用户ID特征、产品ID特征。示例如下:
在`categorical_column_with_hash_bucket`的文档[2]中说:对于String类型的输入,`output_id = Hash(input_feature_string)%bucket_size`会执行hash运算,对于integer类型的输入会先转换为String 类型,然后执行相同的哈希操作。通过查看源代码[3]你可以看到这个逻辑:
在推荐业务中,通常这样的ID都经过了某种方式的hash,形成了一个64位的整数特征并放入样本中,所以需要进行将整数转换为String的操作。 ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ????????????????????????????????????????????????但是在TensorFlow的Timeline中,可以看到TF里面的`as_string`函数对应的`AsString` OP其实是一个比较耗时的操作。经过分析对比,发现`AsString` OP的耗时操作通常是后续的hash操作3次以上,如下图所示:
进一步分析`AsString` OP里面的代码,我们可以发现这个OP还涉及到内存分配和复制操作,所以可以理解为比纯hash计算要慢。
团队自然考虑去掉相关操作进行优化,所以专门写了一个散列整数的函数来优化。示例代码如下:
经过这种类型的hash方法,对原来耗时的类型转换操作进行了彻底的优化。这里需要注意的是,新添加的hash函数对应的新OP也需要添加到TF Serving中。
定长特征转换优化
定长特征是指使用`tf.io.FixedLenFeature`接口解析出来的特征,比如用户的性别、年龄等,这类特征的长度通常是定长的,固定为1或多维。此类特征通过接口`tf.io.parse_example`解析成Dense Tensor,然后由Feature Column进行处理,然后进入模型的输入层。常见代码示例如下:
以上代码为例,分析TensorFlow内部的Tensor转换逻辑。如下图所示,两个样本user_names分别是bob和wanda,将样本解析成形状为2的Dense Tensor,然后通过`categorical_column_with_vocabulary_list`进行转换,查找词汇分别转换为0和2,然后由 `indicator_column` 转换成一个热编码的 Dense 输入。
从上面的示例处理来看,没有问题,再看Feature Column代码里面的转换处理逻辑:
如上图所示,在代码中,Vocabulary Categorical Column会先去除一些非法值,然后将输入的Dense Tensor转换为Sparse Tensor。在 Indicator Column 中,Tensor 将再次从 Sparse 转换为 Dense,最后转换为 One Hot Tensor required。
我们先想一想以上两种转换操作的目的。一方面是去除样本数据中的一些异常值。另一方面,这个处理实际上考虑了输入是 Sparse Tensor 的情况。如果输入是 Spare Tensor,则直接进行 Vocabulary 词汇查找,然后转换为 Dense Tensor。这种转换虽然达到了代码复用的效果,但是在性能上有一定的损失。如果能直接将原创 Input Tensor 转换为 One Hot Tensor,则可以省去两个转换过程,而 Sparse Tensor 和 Dense Tensor 之间的转换实际上是一个非常耗时的操作。
回到固定长度特征的原创性质。对于这种类型的定长特征,如果在样本处理过程中没有值,则填充为默认值,并保证在生成样本时不会出现。有空值或-1,所以离群值的处理其实可以省略。最后,优化后的内部转换逻辑如下图所示,省去了Sparse Tensor和Dense Tensor之间的两次转换。
除了上面的 Vocabulary Categorical Column 之外,还有其他类似的 Feature Columns 也有同样的问题。因此,针对这些特性,平台开发了一套优化的Feature Column接口供业务使用,性能优化效果还不错。
用户特征去重优化
推荐算法模型有一个很典型的特征,就是模型会收录用户端特征和物品端特征进行推荐,比如视频特征、产品特征等。服务,它会向用户推荐多个视频或产品,模型会返回这些多个视频或产品的评分,然后根据评分的大小推荐给用户。由于是针对单个用户的推荐,此时用户的特征会根据推荐项目的数量重复多次,然后发送给模型。下图为典型推荐算法排序模型在线推理*敏*感*词*:
图中所示的模型输入有 3 个 User 特征和 3 个 Item 特征。假设用户在做推荐,用户的3个特征分别对应u1、u2、u3。这时候我们需要对两个不同的item进行推荐评分请求,即一个请求中有两个item,这两个item分别是I1和I2,这两个item分别具有三个特征,I1对应I11, I12, I13 , 等等,这就构成了一个batch size为2的推理请求。 从图中可以看出,因为向同一个用户推荐了两个不同的item,所以Item端的特性是不同的,但是用户的特征重复两次。
上例仅以2个Item为例,但实际在线服务的推理请求会带来100个以上的Item,所以用户的特征会重复100次以上,重复的用户特征不仅增加了传输带宽,但也增加了特征处理时的计算量。因此,企业希望解决这个问题。
这个问题的起源始于 TensorFlow 模型训练代码。 TensorFlow 训练过程中的每个样本都是用户在某个 Item 上的行为,然后经过 shuffle 和 batch 后进入训练模型。这时候batch中的数据必须收录多个用户行为样本,这和在线推理服务的输入数据格式完全不同。
如何解决这个问题?最简单的想法是,如果在线服务上只发送一个用户配置文件怎么办?快速尝试可以判断当特征数据进入模型输入层时 concat 失败。这是因为Item特征的batch size有多个,而user features的batch size只有1。示例如下:
为了解决concat失败的问题,从模型的角度,可以考虑在进入输入层之前,将用户特征恢复到与Item特征相同的batch size,如下图。
显然从数学模型上这是可行的,下一步就是如何在TensorFlow代码中实现这个想法。这里需要注意的是,复制操作只能在在线服务模型中进行,不能在训练好的模型中进行。
目前TF Estimator接口在推荐算法的应用中比较常见,Estimator接口提供了很好的模型区分方法。通过判断ModeKeys为`tf.estimator.ModeKeys.PREDICT`为在线服务模型,ModeKeys为`tf.estimator.ModeKeys.TRAIN`时为训练模型,示例代码如下:
在实际模型中,需要区分User和Item的特征列,分别传入。这是对原创模型代码的一个比较大的改动。通过判断Item特征的长度可以得到batch size。再重复一遍。
在实际发布过程中,团队经历了两个阶段。第一阶段是只修改算法模型代码。处理用户特征时,只取第一维,但实际发送的推理请求仍会多次重复用户特征;在第二阶段,发送的推荐请求被优化为只发送用户特征的副本。此时模型代码不需要修改,已经自动适配了。
如上图所示,在第一阶段,多次重复输入用户特征。在模型中,对用户特征只取第一维,然后进行特征处理。示例代码如下:
上述模型代码可以适应推理请求同时发送重复的用户特征,也可以只发送一个用户特征。因此,第二阶段不需要修改模型代码,只需要优化发送推理请求的引擎端代码即可。
经过这样的优化,在线推理服务不需要重复发送用户特征,既节省了带宽,又减少了序列化消耗。批量只对用户特征进行一次Feature Column转换,然后进行复制操作。复制时间远小于转换时间。
其实这里还可以做进一步的优化。复制操作推迟到第一层神经网络的矩阵乘法,可以减少第一层矩阵乘法的部分计算成本。如果用户特征占比比较高,优化效果会更明显。
总结
本文介绍了爱奇艺深度学习平台实践中总结的一些TensorFlow Feature Column优化。经过这些优化,在线推理服务的性能效率提高了一倍以上,p99延迟降低了50%以上。并且相较于操作熔断器、模型图修改等优化,这些优化在商业实践中更容易实现。
最后还是要肯定TensorFlow Feature Column给推荐算法带来的特征处理的便利性。它抽象了整个特征处理。只要算法稍微适应样本特征,就可以快速迭代和实验。 .
参考资料
1.
2.
3.
本文转载自:爱奇艺技术产品团队(ID:iQIYI-TP)
原文链接:双重推理性能,TensorFlow Feature Column 性能优化实践