找回密码
 会员注册
查看: 37|回复: 0

微信NLP算法微服务治理

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64454
发表于 2024-9-21 01:10:33 | 显示全部楼层 |阅读模式
导读 本文主题为微信NLP算法微服务治理,将分享模型微服务带来的挑战,以及应对这些挑战的解决方案。主要内容包括:1.概述2.微服务面临的管理问题3.微服务面临的性能问题4.微服务面临的调度问题分享嘉宾|冯佳宜腾讯高级工程师编辑整理|尹川集美大学出品社区|DataFun01概述马斯克收购了推特,但对其技术表示不满。认为主页速度过慢是因为有1000多个RPC。先不评价马斯克所说的原因是否正确,但可以看出,互联网上为用户提供的一个完整的服务,背后会有大量的微服务调用。以微信读书推荐为例,分为召回和排序两个阶段。请求到达后,会先从用户特征微服务拉取特征,把特征组合在一起进行特征筛选,然后调用召回相关的微服务,这一流程还需要乘以一个N,因为我们是多路召回,会有很多类似的召回流程在同时运行。下面的是排序阶段,从多个特征微服务中拉取相关特征,组合后多次调用排序模型服务。获得最终结果后,一方面将最终结果返回给调用方,另一方面还要将流程的一些日志发送给日志系统留档。读书推荐只是微信读书整个APP中非常小的一部分,由此可见,即便是一个比较小的服务后面也会有大量的微服务调用。管中窥豹,可以意料到整个微信读书的系统会有巨量的微服务调用。大量的微服务带来了什么问题?根据日常工作的总结,主要是有以上三方面的挑战:①管理方面:主要是围绕如何高效地管理、开发以及部署大量的算法微服务。②性能方面:要尽量提升微服务,特别是算法微服务的性能。③调度方面:如何在多个同类算法微服务之间实现高效合理的负载均衡。02微服务所面临的管理问题1. 开发和部署:CI/CD系统提供自动打包和部署第一点是我们提供了一些自动打包和部署的流水线,减轻算法同学开发算法微服务的压力,现在算法同学只需要写一个Python函数,流水线会自动拉取预先写好的一系列微服务模板,并将算法同学开发的函数填入,快速搭建微服务。2. 扩缩容:任务积压感知自动扩缩容第二点是关于微服务的自动扩缩容,我们采取的是任务积压感知的方案。我们会主动去探测某一类任务积压或空闲的程度,当积压超过某一阈值后就会自动触发扩容操作;当空闲达到某一阈值后,也会去触发缩减微服务的进程数。3. 微服务组织:图灵完备DAG/DSL/自动压测/自动部署第三点是如何把大量的微服务组织在一起,来构造出完整的上层服务。我们的上层服务是用DAG去表示的,DAG的每一个节点代表一个对微服务的调用,每一条边代表服务间数据的传递。针对DAG,还专门开发了DSL(领域特定语言),更好地描述和构造DAG。并且我们围绕DSL开发了一系列基于网页的工具,可以直接在浏览器里进行上层服务的可视化构建、压测和部署。4. 性能监控:Trace系统第四点性能监控,当上层服务出现问题时要去定位问题,我们构建了一套自己的Trace系统。针对每一个外来请求,都有一整套的追踪,可以查看请求在每一个微服务的耗时,从而发现系统的性能瓶颈。03微服务所面临的性能问题一般来说,算法的性能耗时都在深度学习模型上,优化算法微服务的性能很大一部分着力点就在优化深度学习模型infer性能。可以选择专用的infer框架,或尝试深度学习编译器,Kernel优化等等方法,对于这些方案,我们认为并不是完全有必要。在很多情况下,我们直接用Python脚本上线,一样可以达到比肩C++的性能。不是完全有必要的原因在于,这些方案确实能带来比较好的性能,但是性能好不是服务唯一的要求。有一个很著名的二八定律,以人与资源来描述,就是20%的人会产生80%的资源,换句话说,20%的人会提供80%的贡献。对于微服务来说,也是适用的。我们可以把微服务分为两类,首先,成熟稳定的服务,数量不多,可能只占有20%,但是承担了80%的流量。另一类是一些实验性的或者还在开发迭代中的服务,数量很多,占了80%,但是承担的流量却只占用的20%,很重要的一点是,经常会有变更和迭代,因此对快速开发和上线也会有比较强的需求。前面提到的方法,比如Infer框架,Kernel优化等,不可避免的需要额外消耗开发成本。成熟稳定的服务还是很适合这类方法,因为变更比较少,做一次优化能持续使用很久。另一方面,这些服务承担的流量很大,可能一点点的性能提升,就能带来巨大的影响,所以值得去投入成本。但这些方法对于实验性服务就不那么合适了,因为实验性服务会频繁更新,我们无法对每一个新模型都去做新的优化。针对实验性服务,我们针对GPU混合部署场景,自研了Python解释器——PyInter。实现了不用修改任何代码,直接用Python脚本上线,同时可以获得接近甚至超过C++的性能。我们以Huggingface的bert-base为标准,上图的横轴是并发进程数,表示我们部署的模型副本的数量,可以看出我们的PyInter在模型副本数较多的情况下QPS甚至超越了onnxruntime。通过上图,可以看到PyInter在模型副本数较多的情况下相对于多进程和ONNXRuntime降低了差不多80%的显存占用,而且大家注意,不管模型的副本数是多少,PyInter的显存占用数是维持不变的。我们回到之前比较基础的问题:Python真的慢吗?没错,Python是真的慢,但是Python做科学计算并不慢,因为真正做计算的地方并非Python,而是调用MKL或者cuBLAS这种专用的计算库。那么Python的性能瓶颈主要在哪呢?主要在于多线程下的GIL(GlobalInterpreterLock),导致多线程下同一时间只能有一个线程处于工作状态。这种形式的多线程对于IO密集型任务可能是有帮助的,但对于模型部署这种计算密集型的任务来说是毫无意义的。那是不是换成多进程,就能解决问题呢?其实不是,多进程确实可以解决GIL的问题,但也会带来其它新的问题。首先,多进程之间很难共享CUDAContext/model,会造成很大的显存浪费,这样的话,在一张显卡上部署不了几个模型。第二个是GPU的问题,GPU在同一时间只能执行一个进程的任务,并且GPU在多个进程间频繁切换也会消耗时间。对于Python场景下,比较理想的模式如下图所示:通过多线程部署,并且去掉GIL的影响,这也正是PyInter的主要设计思路,将多个模型的副本放到多个线程中去执行,同时为每个Python任务创建一个单独的互相隔离的Python解释器,这样多个任务的GIL就不会互相干扰了。这样做集合了多进程和多线程的优点,一方面GIL互相独立,另一方面本质上还是单进程多线程的模式,所以显存对象可以共享,也不存在GPU的进程切换开销。PyInter实现的关键是进程内动态库的隔离,解释器的隔离,本质上是动态库的隔离,这里自研了动态库加载器,类似dlopen,但支持“隔离”和“共享”两种动态库加载方式。以“隔离”方式加载动态库,会把动态库加载到不同的虚拟空间,不同的虚拟空间互相之间看不到。以“共享”方式加载动态库,那么动态库可以在进程中任何地方看到和使用,包括各个虚拟空间内部。以“隔离”方式加载Python解释器相关的库,再以“共享”方式加载cuda相关的库,这样就实现了在隔离解释器的同时共享显存资源。04微服务所面临的调度问题多个微服务起到同等的重要程度以及同样的作用,那么如何在多个微服务之间实现动态的负载均衡。动态负载均衡很重要,但几乎不可能做到完美。为什么动态负载均衡很重要?原因有以下几点:(1)机器硬件差异(CPU/GPU);(2)Request长度差异(翻译2个字/翻译200个字);(3)Random负载均衡下,长尾效应明显:①P99/P50差异可达10倍;②P999/P50差异可达20倍。(4)对微服务来说,长尾才是决定整体速度的关键。处理一个请求的耗时,变化比较大,算力区别、请求长度等都会影响耗时。微服务数量增多,总会有一些微服务命中长尾部分,会影响整个系统的响应时间。为什么动态负载均衡难以完美?方案一:所有机器跑一遍Benchmark。这种方案不“动态”,无法应对Request长度的差异。并且也不存在一个完美的Benchmark能反应性能,对于不同模型来说不同机器的反应都会不同。方案二:实时获取每一台机器的状态,把任务发给负载最轻的。这一方案比较直观,但问题在于在分布式系统中没有真正的“实时”,信息从一台机器传递到另一台机器一定会花费时间,而在这一时间中,机器状态就可以发生了改变。比如在某一瞬间,某一台Worker机器是最空闲的,多台负责任务分发的Master机器都感知到了,于是都把任务分配给这台最空闲的Worker,这台最空闲的Worker瞬间变成了最忙的,这就是负载均衡中著名的潮汐效应。方案三:维护一个全局唯一的任务队列,所有负责任务分发的Master都把任务发送到队列中,所有Worker都从队列中取任务。这一方案中,任务队列本身就可能成为一个单点瓶颈,难以横向扩展。动态负载均衡难以完美的根本原因是信息的传递需要时间,当一个状态被观测到后,这个状态一定已经“过去”了。Youtube上有一个视频,推荐给大家,“LoadBalancingisImpossible”https://www.youtube.com/watch?v=kpvbOzHUakA。关于动态负载均衡算法,Powerof2Choices算法是随机选择两个worker,将任务分配给更空闲的那个。这个算法是我们目前使用的动态均衡算法的基础。但是Powerof2Choices算法存在两大问题:首先,每次分配任务之前都需要去查询下Worker的空闲状态,多了一次RTT;另外,有可能随机选择的两个worker刚好都很忙。为了解决这些问题,我们进行了改进。改进后的算法是Joint-Idle-Queue。我们在Master机器上增加了两个部件,Idle-Queue和Amnesia。Idle-Queue用来记录目前有哪些Worker处于空闲状态。Amnesia记录在最近一段时间内有哪些Worker给自己发送过心跳包,如果某个Worker长期没有发送过心跳包,那么Amnesia就会逐渐将其遗忘掉。每一个Worker周期性上报自己是否空闲,空闲的Worker选择一个Master上报自己的IdIeness,并且报告自己可以处理的数量。Worker在选择Master时也是用到Powerof2Choices算法,对其他的Master,Worker上报心跳包。有新的任务到达时,Master从Idle-Queue里随机pick两个,选择历史latency更低的。如果Idle-Queue是空的,就会去看Amnesia。从Amnesia中随机pick两个,选择历史latency更低的。在实际的效果上,采用该算法,可以把P99/P50压缩到1.5倍,相比Random算法有10倍的提升。05总结在模型服务化的实践中,我们遇到了三个方面的挑战:首先是对于大量的微服务,如何进行管理,如何优化开发、上线和部署的流程,我们的解决方案是尽量自动化,抽取重复流程,将其做成自动化流水线和程序;第二是模型性能优化方面,如何让深度学习模型微服务运行得更加高效,我们的解决方案是从模型的实际需求出发,对于比较稳定、流量较大的服务进行定制化的优化,对于实验型的服务采用PyInter,直接用Python脚本上线服务,也能达到C++的性能;第三是任务调度问题,如何实现动态负载均衡,我们的解决方案是在Powerof2Choices的基础上,开发了JIQ算法,大幅缓解了服务耗时的长尾问题。今天的分享就到这里,谢谢大家。分享嘉宾INTRODUCTION冯佳宜腾讯高级工程师本科毕业于浙江大学,硕士毕业于中国科学院。是开源深度学习框架PaddlePaddle的主要开发者之一。目前在腾讯微信团队从事深度学习模型的线上服务系统的开发与搭建。▌2023数据智能创新与实践大会4大体系,专业解构数据智能16个主题论坛,覆盖当下热点与趋势70+演讲,兼具创新与最佳实践1000+专业观众,内行人的技术盛会欢迎大家,扫码加好友,限时领取7折购票优惠(立减¥1500)👇▌关于DataFun👇点击阅读原文直达线下大会官网
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-28 08:20 , Processed in 0.427595 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表