写点什么

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

  • 2021-03-10
  • 本文字数:3141 字

    阅读完需:约 10 分钟

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 搜索的场景下经过验证,量化后的模型对最终效果几乎无影响。权衡之下,量化后的收益远大于损失。

流水优化

开发完在线服务后压测过程中,观察到一种现象:无论压测请求压到多高,GPU 利用率会达到一个瓶颈,维持在 80%左右。此时继续增大压力,GPU 利用率依然没有升高,延迟反而不断增大。



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


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

运行架构



上图描述了一个占有 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 实验的平台化和流程化,缩短上线周期,提高工作效率。


本文转载自:360 技术(ID:qihoo_tech)

原文链接:Bert语义在360搜索中的探索实践-工程优化

2021-03-10 13:001996

评论

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

剑指Offer——企业级项目中分层的含义与依据及多态的优势

No Silver Bullet

封装 offer 继承 多态 2月月更

[Python公开课]零基础玩转Python基础篇----第二节:Python的语法基础

是Dream呀

2月月更

为什么start方法不能重复调用?而run方法却可以?

王磊

Java 面试

虎啸春来!丰树电子与中联重科签署战略合作协议

联营汇聚

DevOps进阶(三)走近 DevOps 工程师

No Silver Bullet

DevOps 敏捷 jenkins 2月月更

拥有CI/CD的所有益处,却更绿色

龙智—DevSecOps解决方案

静态代码分析 静态代码分析工具 SAST工具 静态分析安全测试工具

《MySQL入门很轻松》第4章:数据表中能存放的数据类型

乌龟哥哥

数据库 2月月更

给面试加点硬菜:延迟任务场景,该如何提高吞吐量和时效性!

小傅哥

小傅哥 架构设计 Redis 6.0 任务系统

甜言蜜语生成器、定时问候邮件机…开源程序员为这个情人节付出太多

腾源会

开源

流量录制与回放在vivo的落地实践

vivo互联网技术

测试工具 回归测试 流量回放

还没有表白神器?情人节来喽,快为心爱的她送上一份专属的礼物吧~

是Dream呀

Python 2月月更

美团动态线程池实践思路,开源了

yanhom

Java 线程池 动态调整线程池参数 动态线程池 美团线程池

最佳实践 | 如何避免一行错误代码造成的血案?

龙智—DevSecOps解决方案

代码质量 静态代码分析 电信公司解决方案 代码检查器

web前端培训: JavaScript 中初始值如何填充数组

@零度

JavaScript 前端开发

专访 OpenKruise 负责人:现在的云原生应用自动化发展到什么程度了?

阿里巴巴云原生

阿里云 开源 云原生

平安保险基于 SPI 机制的 RocketMQ 定制化应用

阿里巴巴云原生

阿里云 云原生 消息队列 开源云工具

Linux系统编程-进程间通信(mmap内存映射)

DS小龙哥

2月月更 mmap内存映射

敏捷环境中的DevSecOps

龙智—DevSecOps解决方案

敏捷 DevSecOps 敏捷环境 DevSecOps和敏捷

Kotlin语法手册(三)

寻找生命中的美好

android kotlin 安卓

DGIOT 工业物联网开源平台简介

dgiot

物联网 2月月更 2月日更 dgiot dgiot物联网

教程直播第8期|一文详解 OceanBase 社区版生态工具 ODP & OCP

OceanBase 数据库

数据库 分布式 直播 OceanBase 开源

企业为什么要做应用多活?

阿里巴巴云原生

阿里云 云原生 容灾

Go 语言Web开发很简单:使用模板将视图与逻辑分离

宇宙之一粟

Go 语言 Web应用开发 2月月更

智汇华云|ArStack 热迁移背后的黑魔法

华云数据

智汇华云 | 通过iscsi为容器提供存储

华云数据

微信朋友圈高性能架构方案

Geek_36cc7c

详细讲解mybatis的执行流程

编程江湖

mybatis

花灯照 人笑颜|OceanBase祝大家工作生活都和元宵一样甜

OceanBase 数据库

数据库 分布式 开发者 OceanBase 开源 元宵

人人都是 Serverless 架构师 | 现代化 Web 应用开发实战

阿里巴巴云原生

阿里云 Serverless 云原生

计算机网络面试知识点

yuexin_tech

面试 计算机网络

大数据培训:在 flink 中使用 hive udf的原因分析

@零度

flink 大数据开发

Bert语义在360搜索中的探索实践-工程优化_语言 & 开发_360技术_InfoQ精选文章