乐趣区

关于大数据:蚂蚁Alluxio在蚂蚁集团大规模训练中的应用

本期内容咱们邀请到了来自蚂蚁团体的开发工程师陈传迎老师,给大家分享 Alluxio 在蚂蚁团体是如何反对大规模模型训练的。

首先是对于引入 Alluxio 的背景:
为什么要引入 Alluxio?
Alluxio 到底解决了什么问题?

带着这些问题,咱们疾速 get 陈老师分享的核心内容:
第一局部:稳定性建设

稳定性建设次要从两块进行:worker register follower 和 master 迁徙。

【价值】能够把整个集群做 FO 的工夫管制在 30 秒以内,如果再配合一些其余机制,比方 client 端有一些元数据缓存机制,就能够达到一种用户无感知的条件下进行 FO。

第二部份:性能优化

性能优化次要进行了 follower read only 的过程。

【价值】单个集群的吞吐曾经造成了三倍以上晋升,整个性能也会晋升上来,能够反对更大并发的模型训练任务。

第三局部:规模晋升

规模晋升次要是横向扩大。

【价值】模型训练汇合越来越大,能够把这种模型训练引入进来,对外提供反对。

以上仅为大咖演讲概览,残缺内容点击视频观看:

附件:大咖分享文字版残缺内容可见下文

背景介绍

首先是咱们为什么要引入 Alluxio,其实咱们面临的问题和业界基本上是雷同的:
√ 第一个是存储 IO 的性能问题,目前 gpu 的模型训练速度越来越快,势必会对底层存储造成肯定的压力,如果底层存储难以反对目前 gpu 的训练速度,就会重大制约模型训练的效率。
√ 第二个是单机存储容量问题,目前咱们的模型汇合越来越大,那么势必会造成单机无奈寄存的问题。那么对于这种大模型训练,咱们是如何反对的?
√ 第三个是网络提早问题,目前咱们有很多存储解决方案,但都没方法把一个高吞吐、高并发以及低延时的性能交融到一起,而 Alluxio 为咱们提供了一套解决方案,Alluxio 比拟小型化,随搭随用,能够和计算机型部署在同一个机房,这样能够把网络延时、性能损耗降到最低,次要出于这个起因咱们决定把 Alluxio 引入蚂蚁团体。

以下是分享的核心内容:总共分为 3 个局部,也就是 Alluxio 引入蚂蚁团体之后,咱们次要从以下三个方面进行了性能优化:第一局部是稳定性建设、第二局部是性能优化、第三局部是规模晋升。

稳定性建设

首先介绍为什么要做稳定性的建设,如果咱们的资源是受 k8s 调度的,而后咱们频繁的做资源重启或者迁徙,那么咱们就须要面临集群频繁的做 FO,FO 的性能会间接反映到用户的体验上,如果咱们的 FO 工夫两分钟不可用,那么用户可能就会看到有大量的报错,如果几个小时不可用,那用户的模型训练可能就会间接 kill 掉,所以稳定性建设是至关重要的,咱们做的优化次要是从两块进行:一个是 worker register follower,另外一个是 master 迁徙。

Worker Register Follower

先介绍下这个问题的背景:上图是咱们 Alluxio 运行的稳固状态,由 master 进行元数据服务,而后外部通过 raft 的进行元数据一致性的同步,通过 primary 对外提供元数据的服务,而后通过 worker 节点对外提供 data 数据的服务,这两者之间是通过 worker 注册 primary 进行一个发现,也就是 worker 节点的发现,这样就能够保障在稳固状态下运行。那如果这时候对 primary 进行了重启,就须要做一次 FO 的迁徙,也就是接下来这个过程,比方这时候对 primary 进行了重启,那么外部的 standby 就须要通过 raft 进行从新选举,选举进去之前,其实 primary 的元数据和 worker 是断联的,断连的状态下就须要进行 raft 的一致性选举,进行一次故障的转移,接下来如果这台机器选举进去一个新的 primary,这个时候 work 就须要从新进行一次发现,发现之后注册到 primary 外面,这时新的 primary 就对外提供元数据的服务,而 worker 对外提供 data 数据的服务,这样就实现了一次故障的转移,那么问题点就产生在故障产生在做 FO 的时候,worker 发现新的 primary 后须要从新进行一次注册,这个局部次要面临三个问题:

第一个就是首个 worker 注册前集群是不可用的,因为刚开始首个 worker 复原了新的 primary 领导能力,如果这个时候没有 worker,其实整个 primary 是没有 data 节点的,也就是只能拜访元数据而不能拜访 data 数据。

第二个是所有 worker 注册过程中,冷数据对性能的影响。如果首个 worker 注册进来了,这时就能够对外提供服务,因为有 data 节点了,而在陆续的注册的过程当中如果首个节点注册进来了,而后后续的节点在注册的过程当中,用户拜访 worker2 的缓存 block 的时候,worker2 处于一种 miss 的状态,这时候 data 数据是失落的,会从现存的 worker 中选举进去到底层去读文件,把文件读进来后从新对外提供服务,然而读的过程当中,比如说 worker1 去 ufs 外面读的时候,这就牵扯了一个预热的过程,会把性能拖慢,这就是注册当中的问题。

第三个是 worker 注册实现之后的数据冗余清理问题。注册实现之后,其实还有一个问题就是在注册的过程当中一直有大量数据进行了从新预热,worker 全副注册之后,注册过程中从新缓存的这部分数据就会造成冗余,那就须要进行预先清理,依照这个重大等级其实就是第一个 worker 注册前,这个集群不可用,如果 worker 规格比拟小,可能注册的工夫 2 - 5 分钟,这 2 - 5 分钟这个集群可能就不可用,那用户看到的就是大量报错,如果 worker 规格比拟大,例如一个磁盘有几 tb 的体量,齐全注册上来须要几个小时。那这几个小时整个集群就不可对外提供服务,这样在用户看来这个集群是不稳固的,所以这个局部是必须要进行优化的。

咱们目前的优化计划是:把所有的 worker 向所有的 master 进行注册,提前进行注册,只有 worker 起来了 那就向所有的 master 从新注册一遍,而后两头通过这种实时的心跳放弃 worker 状态的更新。那么这个优化到底产生了怎么成果?能够看下图:

这个时候如果 primary 被重启了,外部通过 raft 进行选举,选举进去的这个新的 primary 对外提供服务,primary 的选举须要经验几局部:第一局部就是 primary 被重启之后,raft 进行自发现,自发现之后两者之间进行从新选举,选举进去之后这个新的 primary 通过 catch up 后就能够对外提供服务了,就不须要从新去获取 worker 进行一个 register,所以这就能够把工夫齐全节省下来,只须要三步:自发现、选举、catch up。

这个计划的效率十分高,只须要 30 秒以内就能够实现,这就大大缩短了 FO 的工夫。另一个层面来说,这里也有一些负面的影响,次要是其中一个 master 如果进行了重启,那么对外来说这个 primary 是能够提供失常服务的,而后这个 standby 重启的话,在对外提供服务的同时,worker 又须要从新注册这个 block 的元数据信息,这个 block 元数据信息其实流量是十分大的,这时会对以后的 worker 有肯定影响,而且对局部注册上来的 master 性能也有影响,如果这个时候集群的负载不是很重的话,是齐全能够疏忽的,所以做了这样的优化。

Master 的迁徙问题

如图所示,其实刚开始是由这三者 master 对外提供服务,这三者达到一个稳固的状态,而后 worker 注册到 primary 对外提供服务,这个时候如果对机器做了一些腾挪,比方 standby3 把 standby1 替换掉,而后 standby4 把 standby2 替换掉,而后新的 primary 把老的 primary 替换掉,这个时候新的这个 master 的集群节点就是由这三者组成:standby3、standby4、新的 primary,依照失常的流程来说,这个 worker 是须要跟以后这个新的集群进行建联的,维持一个失常的心跳,而后对外提供服务,然而这时候并没有,次要起因就是 worker 辨认的 master 信息其实是一开始由 configer 进行动态注入的,在初始化的时候就曾经写进去了,而且后盾是动态治理的,没有动静的更新,所以永远都不能辨认这三个节点,辨认的永远是三个老节点,相当于是说这种场景间接把整个集群搞挂了,对外没有 data 节点就不可提供服务了,复原伎俩次要是须要手动把这三个新节点注册到 configer 当中,从新把这个 worker 重启一遍,而后进行辨认,如果这个时候集群规模比拟大,worker 节点数量比拟多,那这时的运维老本就会十分大,这是咱们面临的 master 迁徙问题,接下来看一下怎么应答这种稳定性:

咱们的解决方案是在 primary 和 worker 之间维持了一个主心跳,如果 master 节点变更了就会通过主心跳同步以后的 worker,实现实时更新 master 节点,比方 standby3 把 standby1 替换掉了,这个时候 primary 会把以后的这三个节点:primary、standby2、standby3 通过主心跳同步过去给以后的 worker,这个时候 worker 就是最新的,如果再把 standby4、standby2 替换,这时候又会把这三者之间的状态同步过去,让他放弃是最新的,如果接下来把新的 primary 加进来,就把这四者之间同步过去,重启之后进行选举,选举进去之后 这就是新的 primary,因为 worker 节点最初的一步是存着这四个节点,在这四个节点当中便当寻找以后的 leader,而后就能够辨认新的 primary,再把这三个新的 master 同步过去 这样就达到一个平安的迭代过程,这样的状况下再受资源调度腾挪的时候,就能够稳固的腾挪上来。

以上两局部就是稳定性建设的内容。

性能优化

性能优化咱们次要进行了 follower read only 的过程,首先给大家介绍一下背景,如图所示:

这个是以后 Alluxio 的整体框架,首先 client 端从 leader 拿取到元数据,依据元数据去拜访失常的 worker,leader 和 standby 之间通过 raft 进行与元数据一致性的同步,leader 进行元数据的同步只能通过 leader 发动而后同步到 standby,所以说他是有先后顺序的。而 standby 不能通过发动新的信息同步到 leader,这是一个违反数据一致性准则的问题。

另一部分就是以后的这个 standby 通过后面的 worker register follower 的优化之后,其实 standby 和 worker 之间也是有肯定分割的,而且数据都会收集上来,这样就是 standby 在数据的完整性上曾经具备了 leader 的属性,也就是数据基本上和 leader 是保持一致的。

而这一部分如果再把它作为 backup,即作为一种稳定性备份的话,其实就是一种资源的节约,想利用起来但又不能突破 raft 数据一致性的规定,这种状况下咱们就尝试是不是能够提供只读服务,因为只读服务不须要更新 raft 的 journal entry,对一致性没有任何的影响,这样 standby 的性能就能够充分利用起来,所以说这里想了一些优化的计划,而且还牵扯了一个业务场景,就是如果咱们的场景实用于模型训练或者文件的 cache 减速的,那只有第一次预热的时候数据才会有写入,前面是只读的,针对大量只读场景利用 standby 对整个集群的性能取胜是十分可观的。

上面是具体的优化计划,如图所示:

次要是针对后面进行的总结,所有的 worker 向所有的 standby 进行注册,这时候 standby 的数据和 primary 的数据基本上是统一的,另一部分还是 primary 和 worker 之间保护的主心跳,这个时候如果 client 端再发动只读申请的时候,就会随机散列到以后所有的 master 上由他们进行解决,解决实现之后返回 client 端,对于写的申请还是会发放到 primary 下来。而后在不突破 raft 一致性的前提下,又能够把只读的性能晋升,这个机器扩大进去,依照失常推理来说,只读性能可能达到三倍以上的扩大,通过 follower read 理论测验下来成果也是比拟显著的。这是咱们引入 Alluxio 之后对性能的优化。

规模晋升

规模晋升次要是横向扩大,首先看一下这个问题的背景:如图所示:

还是 Alluxio 的框架,master 外面次要蕴含了很多构件元素,第一个就是 block master,第二个是 file master,另外还有 raft 和 snapshot,这个局部的次要影响因素就是在这四个方面:
√ Bblock master,如果咱们是大规模集群创立下,block master 面临的瓶颈就是内存,它会强占掉大量 master 的内存,次要是保留的 worker 的 block 信息;
√ File master,次要是保留了 inode 信息,如果是大规模场景下,对本地存储的压力是十分大的;
√ Raft 面临的同步效率问题;
√ snapshot 的效率,如果 snapshot 的效率跟不上,能够发现后盾会积压十分多 journal entry,这对性能晋升也有肯定影响;

做了一些测试之后,在大规模场景下,其实机器规格不是很大的话, 也就反对 3 - 6 个亿这样的规模,如果想反对 10 亿甚至上百亿这样的规模,全副靠扩充存储机器的规格是不事实的,因为模型训练的规模能够有限增长,然而机器的规格不能够有限裁减,那么针对这个问题咱们是如何优化的呢?

这个优化咱们次要借鉴了 Redis 的实现计划,就是能够在底层对元数据进行分片,而后由多个 cluster 集群对外提供服务,这样做的一个益处就是对外能够提供一个整体,当然也能够采取不同的优化策略,比方多个集群齐全由用户本人去掌控, 把不同的数据调配到每一个集群上,但这样对用户的应用压力就会比拟大。先来介绍一下这个框架,首先咱们把这个元数据进行一个分片,比方用户拿到的整体数据规模汇合比拟大,单集群放不下了,这时候会把大规模的数据汇合进行一个分片,把元数据进行一些哈希(Hash)映射,把肯定 hash 的值映射到其中某一个 shard 上,这样 cluster 这个小集群就只须要去缓存对应局部 key 对应的文件,这样就能够在集群下面有目标性的进行抉择。

那么接下来其余的数据就会留给其余 cluster,把全量的 hash 调配到一个设定的集群规模上,这样就能够通过几个 shard 把整个大的模型训练文件数量 cache 下来,对外提供大规模的模型训练,而后咱们的前端是减少了 proxy,proxy 其实外部是保护一张 hash 映射表的,用户过去的申请其实是通过 proxy 进行 hash 的映射查找,而后调配到固定的某一个集群上进行解决,比方过去的一个文件申请通过计算它的 hash 映射能够断定 hash 映射路由到 cluster1 下面去,这样其实就能够由 cluster1 负责,其余 key 的映射调配到其余 cluster 上,把数据打散,这样的益处有很多方面:

√ 第一个就是元数据承载能力变大了;
√ 第二个就把申请的压力调配到多个集群下来,整体的 qps 能力、集群的吞吐能力都会失去相应的晋升;
√ 第三个就是通过这种计划,实践上能够扩大出很多的 cluster 集群,如果单个集群反对的规模是 3 - 6 个亿,那三个集群反对的规模就是 9 -18 亿,如果扩大的更多,对百亿这种规模也能够提供一种反对的解决方案。

以上是咱们对模型进行的一些优化。整个的框架包含稳定性的建设、性能的优化和规模的晋升。
√ 在稳固建设方面:咱们能够把整个集群做 FO 的工夫管制在 30 秒以内,如果再配合一些其余机制,比方 client 端有一些元数据缓存机制,就能够达到一种用户无感知的条件下进行 FO,这种成果其实也是用户最想要的,在他们无感知的状况下,底层做的任何货色都能够复原,他们的业务训练也不会中断,也不会有感到任何的谬误,所以这种形式对用户来说是比拟敌对的。
√ 在性能优化方面:单个集群的吞吐曾经造成了三倍以上晋升,整个性能也会晋升上来,能够反对更大并发的模型训练任务。
√ 在模型规模晋升方面:模型训练汇合越来越大,能够把这种模型训练引入进来,对外提供反对。在 Alluxio 引入蚂蚁适配这些优化之后,目前运行下来对各个方向业务的反对成果都是比拟显著的。另外目前咱们跟开源社区也有很多的单干,社区也给咱们提供很多帮忙,比方在一些比拟焦急的问题上,能够给咱们提供一些解决方案和帮忙,在此咱们表示感谢!想要理解更多对于 Alluxio 的干货文章、热门流动、专家分享,可点击进入【Alluxio 智库】:

退出移动版