Bert语义在360搜索中的探索实践-工程优化

Bert模型网络结构较深,参数量庞大,将Bert模型部署成在线服务在实时性和吞吐上面临巨大挑战。本文主要介绍360搜索将Bert模型部署成在线服务的过程当中碰到的一些困难以及作的工程方面的优化。

背景

在360搜索场景下对在线Bert服务的延迟和吞吐有极高的要求。通过前期的调研探索和试验,将Bert模型作成在线服务主要有如下3个挑战:算法

  1. 模型参数量巨大。12层Bert模型有超过1亿参数量,相比于其余语义模型计算量高不少。
  2. 推理时间长。经验证,12层Bert模型在CPU上延迟约为200ms,在GPU上未经优化的推理延迟为80ms,在搜索这个场景下如此性能是不可接受的。
  3. 推理计算量大,须要的资源多。通过压测验证,单个机房须要几百张GPU卡才能承接所有的线上流量,投入的成本远高于预期收益。

基于以上几个困难点,咱们前期调研了TF-Serving、OnnxRuntime、TorchJIT、TensorRT等几个热门的推理框架,在比较了是否支持量化、是否须要预处理、是否支持变长、稳定性和性能以及社区活跃度等几个维度后,最终选用了Nvidia开源的TensorRT。肯定了框架选型以后,咱们针对Bert在线服务作了几个不一样层面的优化。缓存

Bert在线服务优化

框架层面提供的优化

TensorRT推理框架自己提供的优化有:网络

  1. 层间融合和张量融合。本质是经过减小核函数调用次数来提升GPU利用率。
  2. Kernel自动调优。TensorRT会在目标GPU卡上选择最优的层和并行优化算法,保证最优性能。
  3. 多流执行。经过共享权重的方式并行处理多条任务流,优化显存。
  4. 动态申请Tensor显存。当Tensor使用时再真正申请显存,显著提升显存利用率。
  5. 模型量化。在保证精度的状况下大幅提高模型的吞吐,同时下降推理延迟。

知识蒸馏

12层Bert模型的线上延迟不能知足性能要求,咱们将其蒸馏至6层的轻量级小模型。作完知识蒸馏后,在下降计算量的同时也保证了预测效果,6层模型能够达到12层模型精度的99%。通过试验验证,Bert模型层数和在线服务的性能呈正相关的关系,6层模型TP99指标相对于12层模型性能提高1倍。架构

FP16量化

在Bert模型结构中,大部分Tensor都是fp32精度,可是在推理时不须要反向传播,此时能够下降精度,在保证模型效果的基础上大幅提升模型的吞吐。通过FP16量化以后,模型推理延迟变为原先1/3的同时,吞吐提高为原先的3倍,此时显存占用也为原先模型的1/2。可是相比较原先模型,fp16量化后的模型在万分位后有损失,在360搜索的场景下通过验证,量化后的模型对最终效果几乎无影响。权衡之下,量化后的收益远大于损失。框架

流水优化

image.png

上图中H2D和D2H分别表示从内存往显存中拷贝数据和从显存往内存中拷贝数据,Kernel表示正在执行核函数。线上请求推理时所作的三个动做为首先将请求数据由内存拷贝至显存,而后GPU发起核函数调用作推理计算,最后将计算结果由显存拷贝至内存。GPU真正执行计算的部分是执行核函数的部分(上图中蓝色部分),数据拷贝时GPU是空闲的(上图中白色部分),此时不管压测压力多大GPU都会有空闲时间,所以利用率不会压满。机器学习

解决上述问题的一个方法是增长一条Stream,使得两条Stream的核函数计算部分能够交替执行,增长GPU有效工做时间占比,GPU利用率能够压到98%以上。Stream能够理解为任务队列,H2D能够理解为一次任务,多增长一条Stream不会增长额外的显存占用,多条Stream是共享模型权重的。函数

运行架构

image.png

上图描述了一个占有2张GPU卡的单个Bert服务进程的运行架构。从左至右依次解释出现的名词,task表示待处理的预测请求,context用来存储这条请求的上下文信息,stream表示任务流,profile描述了模型输入的限制(好比限制输入的最大batch size),engine是TensorRT将原始模型编译优化后的模型。每张GPU卡上加载一个模型,每一个模型会有2条Stream共享模型权重对外提供预测服务。性能

每当Bert服务收到来自客户端的预测请求,这个请求将会被放入任务队列。上图线程池中的4个工做线程每当空闲时会从任务队列中取出一条预测任务,保存好上下文信息后便将请求数据经过Stream拷贝到显存,GPU调用核函数作完推理后再将结果经过Stream传回到内存,此时工做线程将结果存入指定位置后通知上层,一条完整的请求预测流程就完成了。学习

缓存优化

在搜索场景下,当天的搜索内容会有一部分热词出现,加上缓存能够有效减轻一部分计算量。在搜索系统加入请求级别的缓存以后,平均缓存命中率可达35%,极大地缓解了Bert在线服务的压力。测试

动态sequence length

最开始的在线服务是采用输入维度固定的方式,即输入shape的最后一个维度为离线统计出现过的最大sequence长度,通过线上小流量验证而且统计线上请求以后,发现线上请求长度超过70的sequence占比不到10%。因而咱们采起了动态sequence长度的优化方式,即采用一个请求batch中长度最长的sequence为输入长度,对其他的sequence作补零操做,通过这一优化线上性能提高7%。

Bert在线服务探索

作完上述优化后在测试以及小流量验证的过程当中,咱们也碰到了一些问题,分享给你们。

模型动态加载致使延迟升高

在搜索场景下,有一个多版本模型热加载的需求。开发完上线后观测到一个现象,在热加载新模型的时候,会出现TP99升高的现象,后来通过定位分析找到了缘由。

在Bert在线服务作预测的时候,会有一个将模型输入数据从内存拷贝到显存的操做。而Bert服务动态加载模型的时候,也会有一个将模型权重数据从内存拷贝到显存的动做,拷贝模型到显存的时候占据了PCI总线,这时候预测请求数据从内存拷贝到显存就会受到影响,从而TP99就会升高。模型权重拷贝持续约几秒的时间,此时TP95正常,经统计仅有几条请求会有延迟升高,对业务基本无影响。

精度震荡

在前期开发过程当中,咱们观测到相同的sequence输入模型,在不一样的batch size下返回的结果老是不尽相同,而是在某一固定的区间内震荡,例如返回的结果老是介于0.93-0.95之间且不固定。这个现象在TensorRT 7.1.3.4下稳定复现,后与Nvidia的同事沟通反馈,在7.2.2.3这个版本下已经修复。

显存占用

单个Bert模型仅占用几百MB的显存,可是上了多版本模型的功能后,Bert在线服务有可能加载5-8个模型,若是处理很差有可能会出现OOM的问题。目前咱们的处理手段是若是因显存不够而没法正常加载模型,仅仅会提示模型加载失败不会影响正常服务。加载一个新的模型的显存占用量是能够提早判断出来的,主要依据有3个:

  1. 模型自己的权重。模型自己的权重是须要占用显存的,占用显存的大小约等于模型在磁盘上的文件大小。
  2. 模型推理时须要的一些上下文信息。此部分显存占用分为两部分,一部分是保存上下文的持久化信息,包括输入输出数据占用的显存。另外一部分是推理时占用的中间信息,中间信息显存占用通常不会超过模型权重大小。
  3. CUDA运行时也耗费一些显存,但这些显存占用是固定的。

总结与展望

通过前期框架调研验证,模型优化,工程架构优化以及部署探索过程后,最终Bert在线服务在360搜索场景下正式上线了。目前通过优化后的6层模型单张T4卡每秒可计算1500条qt,线上高峰期TP99为13ms。工程方面Bert在线服务稳定性和性能获得了保障的基础上,业务效果上相较于baseline也取得了可观的收益。

咱们后续会持续探索推动Bert在360的应用落地,目前在搜索场景下工程方面还有一些亟需优化点:

  1. 目前Bert服务仍是物理机部署,存在升级扩容困难、容灾差以及资源浪费的问题,咱们正在推动Bert K8S化部署进程。
  2. 当前Bert的训练、蒸馏、数据和模型管理以及部署各个工做模块比较分散,咱们在作的一个工做是把这些模块慢慢集成到公司内部的机器学习平台中,作到模型训练,数据管理,模型管理,服务部署升级,AB实验的平台化和流程化,缩短上线周期,提升工做效率。
相关文章
相关标签/搜索