从分布式计算到分布式训练

对计算机来讲,所谓的计算,不过是将存储在各个地方的数据通过数据总线进行传输,然后经过算术逻辑单元执行一系列预设好的规则,最终再将输出写入到某个位置。

在计算能力有限、存储成本偏高的情况下,就需要利用好计算机的资源,让它的计算能力发挥出最大的价值,所以在编程初期用指令直接操作硬件,例如汇编语言中常见的操纵寄存器,本质上都是为了减少数据传输的时间,充分利用CPU的计算能力,避免因为数据的长时间传输导致CPU进行过长的等待。

分布式计算的到来

随着科技的发展,“数据存储”领域有了质和量的双向发展,除了稳定性、安全性的提升外,容量也呈指数级增长。因此可以在单机上直接构建整套服务,类似LAMP类似的这种一键搭建服务器的套装软件有了更多的应用场景。

然而随着业务的发展,另一个问题逐渐显现出来:虽然磁盘容量增加了,但是机器的访问速度并没有变快。

什么意思呢?举个例子:虽然20年前一个盘最大的存储空间只有100MB,但是读取完整磁盘只需要1分钟。如今虽然磁盘容量可以轻易的变成1TB、1PB,然而读取完整个盘的数据需要数小时之上。

这背后的问题在于技术发展的限制:磁头在磁道上移动速度的增速远远低于磁盘容量的增长。用通俗的话来说就是,仓库的面积已经从10平米扩展到100平米甚至到1000平米了,但是一个搬运工一天搬运货物的速度并没有显著的提升,所以虽然仓库的容量越来越大,但是搬完整个仓库的货物需要的时间却越来越多。

不过好在我们还有另一个好消息:带宽逐渐变得廉价。相比20年前,GB带宽的光纤已经非常普遍,网络能够实现一秒传输,数据量已经远远超过了整块盘的容量。于是一个大胆的想法被提出来了:既然读取完一个盘的数据需要几个小时,那把数据分成N份,分别放在不同的机器上并行读取,是不是一秒钟就读取完了?

采用网络并行的方式进行读取,将瓶颈从磁头移动转移到了网络,而要增加一条高速带宽,已经不需要付出多么大的代价。

还是仓库的例子,既然一个搬运工速度这么慢,搬完1000平米仓库需要1000分钟,那么我用1000个搬运工搬1000平米是不是1分钟就完了?这个时候影响搬运工的,仅仅是大门的大小,需要同时容纳1000个搬运工进出而已,但是开个大门似乎成本并不高,大不了把四面的墙都拆了做成门嘛。

MR一代

一个优秀的思想被提出来后,总会有许多追随者尝试将其落地,Google率先丢出了三大论文:BigTable、GFS、MapReduce,从理论上讲述了在分布式下如何做到数据的存储、计算,甚至提出了可以在分布式下做结构化的检索。

三大论文开启了分布式计算的时代,然而对于工程界来说,仅有三篇论文并不足以解决生产上的问题,Google并没有将内部实现的内容进行开源,于是另一帮团队:Yahoo,自行根据论文进行实现,而后将其贡献给Apache,逐渐发展成时至今日依旧如日中天的:HDFS、Mapreduce、HBase。

其中尤为重要的分布式计算模型:MapReuce,我们常称为第一代MR,也就是:MRV1。

上图是MRV1的主要架构图,我们可以看到,在MRV1里面,主要分为两个部分:运行环境和编程模型,所谓的运行环境,指的是用来进行分布式任务调度、资源分配等任务运行过程中涉及到的信息,而编程模型,则指的是提供给开发人员进行开发的接口。

对于MRV1来说,它的运行结构图如下所示:

可以看到,在MRV1里面,当我们的一个任务被提交上去之后,由统一的调度器进行任务的监控、分发,以及资源的申请、回收控制等操作。

MRV1有着明显的两个阶段:Map和Reduce,Map阶段主要负责处理输入,每一个Map任务对应一个分片的数据,而后将数据送入到一个特有的数据结构:环形缓冲区。所谓的环形缓冲区,是用来记录数据和索引的一个区域。当环形缓冲区快要溢出的时候,数据将会被落地到磁盘。在数据输入完成后,将会调用用户自己实现的map函数,而后通过与jobtracker的通信,保持着联系,然后分别进入到reduce的阶段,renduce阶段会汇集所有的数据,这个动作在广义上会被很多人称为:shuffle。实际上shuffle并不是reduce才发生的,对于MR来说,从数据从HDFS上加载开始,shuffle就已经开始了,一直伴随到reduce结束。

MRV1类似于工厂生产辣椒酱,很多工人负责把流水线送到自己身边的辣椒切碎,这个就是Map操作,所有工人切碎的辣椒汇集在一起做成辣椒酱,这个就是Reduce操作。也许某个工人把辣椒切成块的速度赶不上流水线送给他辣椒的速度,那么他就需要把辣椒从流水线拿下来放在他的自己的某个地方存着慢慢切,这个动作就相当于shuffle操作。因为最后汇总会等到所有的人都把辣椒切成块之后再处理,所以如果有一个人没有完成,就需要等待,这个时候就发生了我们常说的,数据倾斜。

MR二代

MRV1是统一管理资源的,类似于一家公司的所有决策都需要通过CEO来发出指令,所有人都听命于CEO,每个人做什么事全都是CEO一一安排,所以如果CEO忙不过来了,或者有事联系不上了,整个组织就成了无头苍蝇、完蛋了。

因此对于MRV1来说,虽然它实现了一个并行计算模型,但是其暴露出来的问题也显而易见:

  • 固化的两阶段模式,限制了迭代任务的进行
  • 多次数据落地,整个运行时间大大延长
  • 所有任务由统一的jobtracker调度,存在单点故障。
  • 对资源的控制不到位,没有明确的任务优先级
  • 资源利用不合理,例如在V1里面,资源分为map solt和reduce solt,导致运行map的时候,reduce的solt全部闲置
  • 安全控制

在这些问题逐渐暴露出来后,有很多补救的措施逐渐出现,例如Tez就是一个非常好的例子,它通过接管MRV1的输入和输出,减少其落地到磁盘的动作,目前Tez已经是Hive的内置计算模型。

但是这些补救框架,并不能从根本上解决MRV1的问题,于是第二代MR被研究出来,也就是MRV2,那么对于MRV2来说,它是怎么做的呢?既然一个公司全靠CEO去安排任务和进行管理有风险,那么我们就把公司的所有人分成N个小团队,每个团队有自己的Lead负责进行工作安排,CEO干什么呢?CEO只负责把要做的事情丢给小团队的Lead,小团队的Lead自己去安排手下的人干活。

大多数时候我们对MRV2这个名字并不熟悉,但是我们一定熟悉一个名字:Yarn。Yarn就是MRV2下最核心的功能。

通过上面的图我们发现,对于MRV2来说,它的资源的申请、控制、回收,不再由统一的jobtracker(前面举例中的CEO)来调度了。在MRV2里面,它产生了几个新的概念:

  • Resource Manager:负责统一管理所有资源
  • Application Master:负责一个任务的监控、资源分配、回收等工作(前面例子中的小团队Lead)
  • Node Manager:各个节点的资源监控

这里面并没有提到Yarn,因为Yarn并不是一个技术,而是一个概念,代表V2里面整个任务调度和资源管理系统。我们合并起来统一称为:Yarn。

我们可以对比一下MRV1和MRV2的机构图:

在MRV2里面,依旧分为两个部分:运行环境和编程模型。然而不一样的地方在于,每一个应用程序需要实现自己的Application Master,也就是资源管理系统。Resource Manager进行一次统一的资源分配,由Application Master自己去决定怎么把资源分给每一个Task,在实际开发中,我们发现自己似乎并没有写过资源分配相关的代码,MR的代码依旧可以运行,那是因为MRV2里面,默认提供了MR的Application Master,在MRV2里面,API也发生了变化,而为了兼容MRV1,分别存在两套API。

同时由于MRV2的超高思想,将整个资源调度独立出来,这带来一个好处,那就是Yarn不单单能调度MR计算引擎,还能调度其他计算引擎,例如Spark。虽然目前有Mesos,但是大多数情况下我们还是会选择采用Yarn去作为资源调度器。

Spark分布式计算模型

看起来似乎MRV2向前迈进了一大步,解决了不少问题,然而对于MRV2来说,依然存在它无法跨越的问题。首先为了兼容MR计算模型,它依然保留着两阶段计算的模型,因为对迭代计算基本乏力。MR模型就像一个工厂流水线要生产辣椒酱,要先把辣椒切碎,然后再汇集起来做成辣椒酱,固定的2步操作,如果想在切碎之前再做点啥,或者做成辣椒酱之后再贴个标签啥的,MR模型就支撑不了,因此“需要任意灵活的进行迭代”这一需求就出来了,这个就是Spark的特点。

同时,MR的核心思想是:运行在廉价服务器上,挪数据,所以对于实时计算,MVR2基本抓瞎。

在这些问题之上,Spark诞生。Spark的思想比较简单:挪计算不挪数据。既然要挪计算,那怎么去描述这个计算呢?于是通过RDD封装一个针对数据对应关系记录,在这个封装之上来记录计算。所以在Spark里面,操作分为两类:Action和Transformation。

为什么会有这两类操作?我们可以想一下,如果数据被分散在100个阶段,我们需要做的是查询某个字段大于0的数据,那么这个计算根本不用把数据汇集在一起,统一过滤,分别在不同节点进行过滤就行了。

而如果我们的操作是统计共有多少条数据,则需要将数据汇总,所以对于Spark来说,Action才真正会触发“挪数据”这个动作,Transformation只是做了一个标记转换。我们对Spark的各种调优,大部分时间也是在尽量减少Action的操作。由于在Spark里面,RDD是只读的,所以每一次操作,都会产生一个新的RDD,因此可以形成一系列的RDD依赖,我们也叫RDD链。

模型训练

模型训练更多的偏向于AI领域,在AI领域有两个明显的分支:概率论和神经网络。在计算能力欠缺的时候,概率论模型是最为普遍的做法,但是近年来发展起来的计算能力,让深度神经网络模型逐渐的展现出风采,很多框架都表明自己就是一个深度学习框架。

模型训练本质上是对数据特征的提取,训练本身和大数据没有必然的关系,但是却相辅相成,数据量越大,提取的特征越多,模型训练出来的效果自然越好,然而数据量越大,对计算的要求就越高,也正因为如此,对模型的探索始终是在小数据、抽样领域进行尝试。

那么什么是特征呢?举个例子,我们如果想要预测一个人能活多少岁,最简单的办法就是返回已知去世的人的平均年龄,无论是谁都返回这个值,要做这样的系统当然没有问题。但是仔细观察就会发现,男性能活多少岁和女性似乎不一样,那么我们可以简单的修改一下,在预测之前先判断一下性别,如果是男的就返回男的平均,女的则返回女性的平均。在这里我们已经无形的用了性别这个特征,是因为我们认为性别对结果是有影响的,而训练就需要找出无数个这样的特征。

然而目前对于大数据的处理能力,似乎已经发展到了一个非常好的阶段,至少在分布式计算上,理论上是可以通过水平扩展无限的增加计算能力。

可是模型的训练和应用在工程中的发展一直不是那么顺利,大约总结起来有如下几个原因:

  • 门槛较高,首先需要有比较专业的背景知识,同时还需要具备较强的编程能力,方能将其应用于工程之上。
  • 对于模型训练来说,没有大数据量的支持,生产上的效果始终差强人意,而数据量增大,如何去处理数据又成了另外一个领域的问题,能够同时处理好两方面的问题,人员较少。
  • 在实际工程中,我们获取到的数据集,往往不是训练模型直接能用的,要达到能够直接用于训练模型,还需要非常多的额外处理,这些代价甚至会高于模型训练本身,因此让模型训练这件事的成本变高。
  • 部分使用者,往往并没有达到模型训练的程度,例如连基本的数据平台都不存在,茫然的使用模型,导致效果不如预期,而将结果归结于模型本身的好坏之上。

虽然模型训练的发展过程中有诸多问题,但是依旧能够看到其在向前发展,目前来说,基于GPU的训练,已经成了所有做模型训练的人的标配,Google甚至研发了自己的GPU:TPU。而很多芯片研发公司,也在致力于研究开发出专门用于模型训练的芯片。

对于模型训练来说,目前一般会有两种做法:

  • 单机模型训练
  • 分布式模型训练

单机模型训练

所谓的单机训练,其实就是在一台机器上训练了,对于单机模型训练来讲,瓶颈主要在于提升单机的性能配置,例如不停的提高单个GPU的计算能力。而对于数据来说,大部分都是利用本地数据,虽然我们可以读取分布式文件系统的数据,但是实际上还是经过了shuffle操作,将数据读取到本地,而模型的训练,都是全程单机训练,我们可以通过各种优化算法,例如奇异值分解等手段,来降低计算成本。

分布式模型训练

对于单机训练来说,单个GPU,始终会陷入瓶颈,所以对于模型训练,也有人开始尝试,是否可以分布式训练?

模型的分布式,相对于其他分布式计算会困难许多,首先模型依赖于数据,而模型本身的计算又要依赖于GPU,那么要如何将数据和计算能力结合?

对于目前来讲,模型的分布式一般会有以下几种做法:

  • 数据分布式训练
  • 模型分布式训练
  • 混合训练

上面的图片比较形象的描述了几种不同的训练方式,首先对于数据分布式来说,每一个节点都有一个完整模型的副本,而对于模型分布式来说,模型的计算会被分散到不同的节点上,例如Tensorflow就通过图形化的表达方法,将计算描述为一个图,然后再判断图中的哪些计算可以并行运行,分别拆分到不同的节点上进行训练,从而达到分布式训练的效果。在混合训练中,模型训练会被分散,同时数据也会分散,无论是哪种分布式训练,最终都会涉及一个操作:模型的归一。在目前来说,有不同的做法,可以将模型最终归一,例如集成算法就是逻辑上实现了模型的归一。

结尾

对于大数据和人工智能来讲,现在仅仅是萌芽时期,后面还有大量的工作要做,而模型的训练无论是单机还是分布式,都还没有达到真正稳定的生产批量效果,这些挑战,不仅仅来自于技术的实现,同时也来自于业务的配合,如何利用现有的技术能力,将其推广到业务上解决问题,才是重点需要关注的地方。

文/ThoughtWorks白发川

1 4 收藏 评论

关于作者:ThoughtWorks

ThoughtWorks是一家全球IT咨询公司,追求卓越软件质量,致力于科技驱动商业变革。擅长构建定制化软件产品,帮助客户快速将概念转化为价值。同时为客户提供用户体验设计、技术战略咨询、组织转型等咨询服务。 个人主页 · 我的文章 · 72 ·   

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部