新一轮科技革命和产业变革迅猛发展,人工智能等新技术方兴未艾,随着大模型等新技术被深入应用到企业的多个业务场景,不仅企业的数据成为了企业发展的新型重要生产要素,软件技术的创新应用作为新质生产力的重要组成部分,也在承担着更为重要的历史使命,在加速自身技术变革的同时,也赋能千行百业,为中国经济的新一轮发展助力。
12月13-14日,由中科软科技股份有限公司举办的年度技术盛会——“2024软件技术大会”在北京朗丽兹西山花园酒店成功召开。作业帮基础架构负责人董晓聪受邀出席并以《云原生助力AIGC发展》为主题进行演讲。董晓聪从回顾作业帮云原生建设的全貌开始展开,再讲到AIGC时代下,云原生如何助力时代发展,给与会者带来了一场极具价值的技术盛宴。
以下是经过整理后的演讲内容:
大家好,很高兴参加本次技术峰会。先做下自我介绍,我是董晓聪,现在在作业帮担任基础架构负责人,负责作业帮的架构研发、运维、DBA、安全相关的工作,同时也是腾讯云TVP、阿里云MVP。
今天和大家分享的主题是云原生助力AIGC的发展。本次分享会分成三个部分,第一部分来讲下CPU时代的云原生建设,一方面来看下云原生建设的全貌和作业帮的建设历程,另一方面我们在这个过程也总结了一些经验,为接下来AIGC时代的建设提供了助力。第二部分来讲下云原生如何助力AIGC,从2022年底OpenAI推出ChatGPT以来,国内外的很多公司都在AIGC这个领域持续发力。作业帮也不例外。这中间除了在算法上面临一系列挑战外,基础设施也是一样的。基础架构在这方面进行了一系列的建设,下面也会详细展开来讲。最后就是对未来的展望,除了云原生有更多助力AIGC的点外,AIGC的发展也会反哺云原生,达成协同进化的效果。
先来看第一部分,CPU时代的云原生建设。
作业帮成立于2015年,是一家致力于用科技手段助力普惠教育的公司。作业帮的主要技术现状可以归纳为两点,规模化和复杂化。规模化是指作业帮线上的服务数量比较多,有数千个。这么多的服务对应了数万的服务实例,而这么多的服务实例又是跑在数十万计算核心之上,整个规模比较大。
复杂化是指作业帮的技术栈比较多元。早期业务以PHP为主,但java、C++、python在一些场景也有广泛的应用。后来随着技术的演进,Golang、Nodejs的使用也越来越多。当前Golang已成为使用最多的编程语言。作业帮从创建之初就base在云计算之上,后来也由于一系列原因最终选择了一套多云的架构。
云原生本质上是用基础设施去接管业务当中的大量非功能逻辑。在我看来,云原生是企业解决自身质量、安全、成本、效率的最短路径。
云原生架构的全貌是怎么样的?大的架构可以分成资源层和应用层。资源层,包含计算存储网络这些IaaS实体。计算的话就是各种各样的CPU、GPU。存储的话包含块存储、文件存储、对象存储等。网络包含的实体就会更多。
应用层就是各种编程语言写的服务模块。我们前面提到的各种问题其中很大一块是因为资源层和应用层耦合导致的。以docker+k8s为代表的容器技术通过容器镜像、作业编排、作业调度、资源管理实现底层资源对上层应用的透明。上层应用想要跑得更快,还需要一层微服务治理体系。微服务治理体系以服务的通信为基础,包括服务的注册发现、服务感知、流量管控等。
面对这么庞大的一个体系,19年底我们对业务进行容器化改造,对PHP、Golang、Java、Python等语言制定标准的镜像、编排模版,以及在框架层面做了一系列的功,以辅助业务的容器化改造,最终通过一年半的时间完成95%服务的容器化改造。
我们在21年开始对作业帮APP的核心系统、检索系统进行容器化改造。检索系统是一个有状态的系统,对稳定性和性能要求极为苛刻,我们通过使用fluid+jindoRuntime完成其容器化改造。再之后我们还对核心系统RTC完成了容器化改造。在改造的过程中我们发现k8s原生的调度器并不智能,一方面是因为其数据源较为单一,仅为request和limit,无法获取更多实时的指标;另一方面调度策略也偏简单。所以我们基于k8s插件机制做了自定义调度策略,通过策略的完善,实现了整体负载的提升。
作业帮和其它互联网业务一样,也有明显的波峰波谷。我们通过自定义调度将大数据离线任何在波谷时调度到在线集群,大大提升了集群的平均使用率。有了容器化和自定义调度器这两个基础,我们可做的事情就更多了。向下我们开始大规模的使用serverless。其实serverless的应用过程也不是一蹴而就的,也是一个逐步灰度的过程,我们也经历了从定时任务、普通web应用,再到核心系统的过程。当前作业帮的检索系统和RTC系统有30%-90%是跑在serverless之上。
在服务治理这块做的事情比较多,这里就挑两个来讲下。我们通过自研service mesh,大幅度优化了性能,进而实现了90%的服务覆盖。当前我们不仅拦截了入向流量,也拦截了出向流量,不仅拦截了RPC流量,也拦截了数据存储流量,给业务带来质量、安全能力的大幅度提升。在服务观测领域,我们自研了日志系统,较开源方案有较大的成本优化,约为ELK方案1/10-1/20的成本。然后基于这些能力之上,我们建成了一套多云的架构。
最终取得的收益也是很明显的,在质量方面,服务的SLA从99.95%提升到99.99%,之所以能有这样的提升,主要得益于观测的覆盖以及故障自愈能力。
观测方面,大家都知道覆盖度越高,对问题分型和定位的帮助就越大,但是困扰覆盖提升的主要因素就是成本。作业帮在这块做了大量技术优化,不仅是自研系统,trace中还使用Clickhoust替换ES,metric进行了超时数据的降准处理等等。最终使得成本在一个合理范围,实现100%的覆盖。
故障自愈方面,k8s的NPD可以自动摘出疑似故障的机器。Readyness接口也可以实现应用层面的故障摘流。以及在数据库层面作业帮也实现了自动扩容等自愈手段。
成本方面,得益于容器技术,这几年我们实现累计单价70%的下降。以及每一核的CPU有更高的利用率。
效率方面,我们通过容器、service mesh等云原生技术可以很好的落地。举个例子,处于安全的要求,我们不希望研发知道数据库的账号、密码,以及在服务中任意的访问外部服务。现在通过service mesh的流量管控可以很好的实现,运维效率也得到大幅度的提升。我记得在20年初的时候有活动,流量每天都在增加,当时还没有完成容器化改造,只能每天夜里运维初始化机器,研发部署程序,QA验收效果,效率比较低,以及质量也不高。而现在这个操作只需要数分钟就可以完成。再有就是机型更换的速度,我记得在21年我们更换容器的一款主力机型,从intel到AMD,仅需要两周就完成了。云机房的迁移速度也得到大幅度提升。作业帮数千个服务及对应的数据存储,我们用三个月就可以完成迁移。以及整个过程中基本不需要业务研发参与,仅做好功能的验收即可。最后是安全方面,得益于云原生技术,主机、网络、数据安全方面的能力也得到大幅度的提升。
下面讲下第二部分,云原生助力AIGC。
进入AIGC时代后,我们遇到的挑战发生了很大的变化。首先是资源分布的问题。之前我们可以要求云厂商在北京的机房供给充足的CPU机器,但AIGC时代则不是。尤其是在23年的时候,整个GPU资源的争抢十分严重,进而导致资源极其的分散,机房数量也大大增加。第二个是推理的成本太高了,从等同的成本来看,1000核CPU可以服务数百、数千个用户。但是1张A800的卡只能支持个位数的QPS。需要持续的进行技术优化,把推理成本降下去,才能使得业务ROI合适。
大模型早期缺少相关的Devops规范,很多时候是在机器上手动操作。这块也继续完善,继续一套完备的Devops体系。大模型时代,算力、算法、数据是企业的核心资产,大模型文件更是这些的结晶,我们需要对模型文件做好安全加固。最后一个是一个小点,但对研发效率的影响很大。不同于CPU代码,大模型文件动辄百G起,分发耗时较长。再叠加上第一个因素,全国各地的分布式机房,很有可能发布一次,需要数个小时的同步时间。面对这么多的问题,作业帮基础架构主要进行了这三方面的建设,统一资源调度、DevSecOps、异构算力网络。
先来进行统一资源调度的介绍,对于业务研发而言,希望有独立的资源池。这样可以最大程度保障服务稳定,避免其他业务线的干扰。但这个对企业而言却不是最优解,不管是资源供给还是资源利用的方面,只有把各方放到一个统一的资源池,才能实现资源利用的最大化。但这个需要有一个资源切分以及强隔离的技术,有了这个才能保证共享场景下各服务的稳定性。对CPU而言,这个技术就是cgroup+namespace,但GPU是缺乏的。只有有了资源共享的技术,才能进一步优化调度策略,不管是分时复用、在离线混部、使用弹性资源等,最终实现资源使用率的提升。
这一切的关键还在于GPU共享的技术,我们先来看大模型程序是如何运行的。最上面的是我们大模型的应用程序,下面是我们使用的各种推理框架,如TRT、Vllm等,这些框架都是基于cuda生态的。整个cuda生态不仅包含cuda类库,还有上层的runtime API、底层的driver API。Cuda层再之下就是内核驱动和硬件层。很多GPU虚拟化的技术会选择在cuda进行代理,通过对任务的调度实现类似cgroup的时间片逻辑。
举个例子,一张卡上跑着A、B两个服务,分别使用0.8卡和0.2卡的资源。那么A、B两个服务在1s内就分别被分配了800ms和200ms的任务运行时间。如果B服务在这个周期的任务运行时间超了,比如到210ms,那么在下一个周期就进行惩罚,只允许运行190ms。通过这种方式,可以在一个相对长的周期内有较好的切分效果。但cuda这一层任务的粒度偏大,有些任务运行时间较长。假设B服务有一个任务需要300ms才能运行完。那在一秒内就超过了限制,会选择一些惩罚措施,如下一秒扣100ms,只让B服务运行100ms,毛刺效果就会特别剧烈。那么该怎么来解,让我们再往下看下,内核这一层任务粒度更小,一般在几毫秒。在这块进行代理可以实现更为精准的时间片逻辑。各家云厂商均实现了类似效果,作业帮通过和各家云厂商合作,就实现了精准的GPU共享能力。
有了服务隔离能力后,下一个就是自定义调度能力。自定义调度有两块基础,决策数据获取和基础调度策略。决策数据这块,只通过request limit这些静态指标远远不够,我们通过promethues做实时指标采集,不光有node、GPU卡的各种监控信息,还通过service mesh获取服务运行信息,有了这些数据就可以进行更精准地调度。调度主要有两种策略,堆叠和平铺。平铺是将多个pod打散部署到多台node多张卡上,主要适用于单个服务的多个实例部署,避免单卡故障后对服务影响过大。堆叠是将多个pod尽量部署到一台node甚至一张卡上,主要是针对不同服务的pod实例,用于提升资源利用率。
有了这些基础能力后我们来看下具体的应用场景。首先是在线服务的调度,所有的业务都基于一个资源池,做各种HPA的策略。HPA策略包含手动、auto、Cron等方式。由于GPU资源很难有像CPU那样的Buffer池,更多是针对不同业务的使用时间来做cronHPA的策略。通过不同业务之间的复用,有千卡级别的资源节省。虽然做了不同业务的分时复用,但整个在线集群还是有明显的波峰波谷,这时就需要在波谷时把离线任务调度到在线集群。
AIGC的离线任务主要有各种离线推理任务、多模态的训练任务。不同离线任务对资源的使用情况是不一样的,比如一些训练任务对整台node CPU、GPU资源使用比较机制,再调度前就需要先探测集群内资源情况,若没有足够的资源,就会发起重调度,已实现全局最优的堆叠效果。对于使用资源不多的离线推理任务,通过GPU共享技术和在线业务复用GPU卡资源,当离线任务完成计算后,需要将服务从node上清退,来释放占用的显存、磁盘资源。
作业帮当前有众多GPU在离线混部。作业帮也在积极使用弹性的GPU资源。Serverless有两种技术选型,函数计算和虚拟节点。从业务易用性角度出发,作业帮选择了虚拟节点方案,对业务服务而言调度到普通节点还是虚拟节点,效果都是一样的。Log Trace Metric等观测信息也完成了对齐。当前作业帮弹性资源刚开始和云厂商探索,约有百卡级别的资源使用。通过这一系列操作,大大降低了GPU推理的成本。
下面我讲下DevSecOps,聊这个之前首先要明确一个问题,大模型服务的模型文件算什么?是代码环境,还是数据。这个问题直接决定我们选择什么样的分发方案。代码环境,顾名思义,像研发用Golang、PHP等编程语言写的代码、代码依赖的配置、基础镜像等。走的是镜像分发的模式;数据对应的存储方式就比较多样,既可存在专门的数据存储组件,MySQL、Redis,也可以使用共享文件系统NFS。作业帮检索系统的容器化也是将数据卸载到JindoRuntime这样的分布式缓存系统。这套方案在大模型训练中也广泛应用。那么大模型推理该选择什么样的方案呢,从表面上看数据文件规模一般较大,模型文件是符合这个特征的。但是从本质来看,模型文件不会随业务规模扩大而扩大,以及需要较强的版本管理能力,基于此我们认定模型文件为环境的一部分,应该选择镜像分发的流程。
在整个DevOps流程中我们应该做什么呢,从代码编写到编译、测试、定版、发布以及线上运维这个完整周期中。整个过程中CI/CD部分比较复杂,后面会详细来说。这里先介绍下其他部分。
编码这块,基础架构提供了cg平台,可一键生成项目,其中包含基础镜像、推理框架、云原生套件。云原生套件包括标准日志输出、readyness接口、配套的service mesh等。
测试这块,GPU作为比较昂贵的资源,不会在测试环境预留,但生产环境和测试环境有网络安全域隔离。这里就需要使用DMZ做授信的打通,进而实现安全严格性和业务灵活性的平衡。
监控这块,大家会发现GPU的监控锯齿状明显,不像CPU监控比较平滑。服务正常时和异常时差异不大,作业帮通过ServiceMesh可以更有效观察服务的状态。
下面来讲下CI/CD的流程。在CPU时代,本地完成代码的编写后提交到远端的Git Server。Git Server绑定了钩子,提交会触发CI Server对commit编译为镜像,然后再推送到权威镜像仓库harbor当中。这样就完成CI流程。当研发点击部署按钮时触发k8s集群部署。K8s集群会先从docker-registry中拉取镜像,若docker-registry中不存在,再从其上联的harbor中拉取,最终完成部署操作。
大模型文件纳入DevOps的第一挑战是文件太大,无法上传到Git。这块使用Git LFS,Git大文件存储技术来解决。模型文件直接从源头——训练机器上传。Git提交时分成两段提交,模型文件提交到lfs系统,指针文件提交到Git,两者之间有映射关系。完成提交后也是一样的镜像编译过程。部署如果再等到研发手动点击,就会将等待时间进一步拉长。所以这块做的第一个优化是当镜像上传到harbor后k8s集群就会运行一个job来提前拉取镜像,做到镜像文件的预热。
前面提到过作业帮的推理集群分布在多个云多个region,这块给镜像分发带来了挑战。多集群之间分发很自然会想到P2P技术,这个可极大提升传输效率。但该方案有一定复杂性,问题排查难度偏高。所以这块集群分发我们使用的是对象存储的DTS,简单可靠,也可以在较短时间内完成传输。最后我们在镜像传输的环节也做了切分,加速整个环境。经过上述的优化,百G文件的分发我们可以从6个小时,缩短80%,降低到45分钟左右,大幅提升了研发效率。
但这一切还没有结束,我们还要在DevOps整个过程中做到端到端的加密,实现真正的DevSecOps。这块我们借鉴之前治理敏感数据的KMS方案,选择对大模型文件也进行加密处理。
具体做法是在训练机器的pre commit阶段添加钩子,提交前先对模型文件进行切分,切分成核心文件和非核心部分,核心文件一般几十MB,然后对核心文件远程获取密钥进行加密,再将这加密的核心文件和未加密的非核心文件一起提交Git,再经过编译和部署这些阶段,最终模型文件都落到k8s集群中。
此时大模型服务是无法读取此类文件的,有三种解法。第一种,是对所有的大模型工程进行修改,在代码逻辑中实现解密和合并这些逻辑,但这样的影响面过大。我们一开始选择的是第二个方案,在大模型程序运行前,先执行init程序,将加密的核心文件解密,再和非核心文件合并成原始的模型文件,这样大模型程序就可以正常识别了。该方案虽然解决了问题,但存在几个弊端。一是整个流程涉及多个文件操作,耗时较长,约30分钟。另一个是这样模型文件就会在磁盘中落地,违背了我们端到端加密的原则。所以我们优化到第三个方案,我们hook了系统文件读取函数。当读取到大模型文件时,我们在这一次函数调用中先读取加密的核心文件解密后输出,再读取非核心文件进行输出。整个操作都是内存中,既安全可靠,耗时也缩短到11分钟。至此完成了大模型DevSecOps的体系。
我们来看异构算力网络。在CPU时代,作业帮出于对稳定性的极致追求,最终建成了一套多云的架构。多云架构有众多技术选型,如主备多云,只把另一个云做成存储的备份。又或者业务切分多云,将不同的业务单云部署到不同的云厂商。面对这么多选型,作业帮最终选择了多活多云的方案。多活方案是稳定性和成本的最优解之一,但复杂性也是最高的。整套架构的流程是用户的流量通过DNS/DoH按照一定比例指到各个云的流量入口-网关,网关再转发到具体的微服务,再往下就是微服务之间的相互调用。
为了能够实现单云闭环,要在每个云厂商部署所有的服务,以及服务与服务之间使用svc的调用方式。但这个仍不能100%防止墒增,且在一些特殊情况下仍需要进行跨云通信。比如某种资源只在一个云独有,又比如某个服务在单个云出现异常,需要紧急止损。所以需要一套既保证隔离的严格性又有一定灵活性的方案,就是如此的矛盾。
作业帮经过设计,最终达成了这样的效果。我们在CPE设备上设置ACL,从网络层实现了强割裂,拒止不同云应用服务的流量,做到严格性。但通过东西向网关这样的基础组件,允许授权的服务进行跨云通信,以此就做到了灵活性。当单边云机房出现故障时,我们只要把流量切到另一边,就可以快速止损。整套架构是比较理想的。
但到了GPU时代就发生了变化,我们的CPU服务还是部署在北京的不同云机房,但GPU服务散落在全国各地,很多机房也没有专线内网直通。业务不得不选择使用公网域名通信的方式,被迫把东西向通信变成了南北向通信。原有的注册发现机制也被打破了,机房概念对业务不再透明。业务需要知道下游的GPU资源具体分布在哪个机房,有多少容量,然后进行调用。如果下游出现资源的变化,GPU云机房发生变化了,上游业务还需要配合进行调整。以及走公网的情况下,安全和性能等方面也往往存在问题。安全方面,一旦业务的鉴权做的不好,就会出现大模型服务被刷的风险。性能方面,连接池维护不好的情况下,每次都要重新建立TCP连接、TLS连接,耗时被大幅度增加了。
基础架构发现这个问题后,对东西向网关进行了升级,变成了全新的集群间mesh-k8s mesh方案。要达成的目标是重新让部署信息对业务透明,这样才能解除耦合。业务服务访问大模型服务仍使用svc域名的方式,具体实现方案是在集群CoreDNS这块做文章,对缺省的服务,解析到k8s mesh。K8s mesh知道跨集群的注册发现信息,然后会在不同集群间寻到最合适的流量路径。对于有大模型服务的云机房,其入向流量也是先由k8s mesh承接。因为流量的两头都是我们可控的服务,我们可以自签加密方式,解决了安全性。又通过自定义词典的方式,大幅度压缩跨集群的数据传输包,最多可实现50%的压缩率,大大提升了传输性能。
整套方案的基础框架就完成了,在过程中也出现了新的变化,比如今年云厂商在我们原有的北京region又可以提供一部分L20、H20。对于大模型服务能闭环在一个机房肯定是更理想的方案。但由于大模型服务在当前机房不再缺省,走CoreDNS的方案就走不通了。这块我们基于service mesh拦截出向流量,在这块重新定义了流量路由规则,优先分发到本机房内的服务,如果无法承接再走k8s mesh的跨集群分发。
以上就是基础架构当前做的主要建设了,关于未来展望我是这么想的。云原生能助力AIGC的方面还远远没有挖掘透彻,随着业务推理规模的进行提升,会有更多新的技术。在性能优化这个方面,前一段时间看到月之暗面的mooncake分离式架构,以及针对kv cache的store方案。后面作业帮也会深入探索下。
另一方面,我们来聊下AIGC对云原生的反哺作用。不知道今天有多少同学是做基础架构的,有多少同学是做业务研发的。不知道大家有没有发现这么一个趋势,随着云原生的深入,业务研发越来越聚焦到业务,越来越多的非功能逻辑由基础设施来承接。分工越来越明确。但在基础架构人员不变的情况下,单个人力支撑的基础服务也越来越多。这种情况下很容易触发人力不足支撑变差的恶性循环。此前我们使用了一些偏管理偏协同的方案,为业务线的架构师赋能等等,收效一般。而大模型的出现给了我们解决问题新的思路。不管是AIops 问题的根因分析,还是智能客服 能自动解答一些咨询问题。希望能再通过半年左右的完善,达到一个更理想的状态,也希望明年可以给大家来汇报我们新的进展。
以上就是我今天分享的全部内容,谢谢大家。