2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

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:002316

评论

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

我不是怕表错态,而是怕我会不自觉地捍卫它

池建强

个人成长

ZGC都出来了,你还不懂G1?

大白给小白讲故事

G1 JVM

死磕Java并发编程(4):happens-before是什么?JMM最最核心的概念,看完你就懂了

Seven七哥

Java Java并发 happens-before JMM

二叉树先序中序后序的非递归实现

Kenn

算法

曾国藩的人生“六戒”

霍太稳@极客邦科技

身心健康 个人成长 心理学

不知不觉,写了10000字了

小天同学

写作 个人感想 思辨

二叉树的先序中序后序递归实现

Kenn

算法 递归

哪儿有真实靠谱的数据,说谎话必须负责的那种?| IPO招股说明书(1)

赵新龙

阿里巴巴 IPO 旷视科技 数据

“IPO上市扒层皮”,以阿里巴巴为例看看公开了什么 | 如何读IPO招股书(3-a)

赵新龙

阿里巴巴 IPO 招股说明书

迷茫时,想想能为这个世界做些什么就好了

霍太稳@极客邦科技

身心健康 个人成长 团队协作

祝这些不要脸的王八蛋同行家里着火

二爷

vSphere 7融合Kubernetes,构建现代化应用的平台

亨利笔记

Kubernetes 容器 云原生 k8s vSphere

判断链表是否有环

Kenn

算法 链表 双指针 Brent

Golang 真的好用吗?

极客时间

编程语言 Go 语言

Harbor和Dragonfly双剑合璧 打造容器镜像运维新模式

亨利笔记

容器 k8s Harbor dragonfly 镜像

回"疫"录(4):见证历史

小天同学

疫情 回忆录 现实纪录 纪实

“WHY-HOW-WHAT”这个被誉为伟大的领袖如何激励行动的黄金圈法则,非常值得大家学一学!

数列科技杨德华

思维方式

如何读IPO招股说明书(2)到哪儿下载招股书?

赵新龙

IPO 上市 招股说明书

批注MYSQL开发规范,助你了解其背后的“道”

三石

数据库规范 规范背后的原理 白话规范

如何避免把中台变成外包团队

松花皮蛋me

数据中台

程序员陪娃漫画系列——吃饭

孙苏勇

程序员 生活 陪伴 漫画

演讲的秘诀

伯薇

个人成长 演讲 追求极致 完美主义

JCJC错别字检测JS接口新增CORS跨域支持

田春峰-JCJC错别字检测

OpenCV 在 Android 上的应用

fengzhizi715

android OpenCV 计算机视觉

浅谈行业软件

孙苏勇

软件 思考 转型

Nginx学习

陈雷雷

nginx

“IPO上市扒层皮”,以阿里巴巴为例看看公开了什么 | 如何读IPO招股书(3-b)

赵新龙

阿里巴巴 IPO 招股说明书

我们是时候降低对完全自动驾驶的期望了

赵钰莹

自动驾驶 AI

像产品设计一样思考、像程序运行一样执行

水色

运维 Harbor 镜像仓库的法宝:Operator

亨利笔记

Kubernetes 容器 k8s Harbor operator

“消灭你,与你无关”——阿里巴巴的风险 | 旧文重发

赵新龙

阿里巴巴 风险 蒋凡 IPO

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