最近对于DMLC的宣传比较多。大部分宣传基本上都是从用户角度出发来做。今天想写一些东西,以我个人的观点来解释一下DMLC对于机器学习系统研究开发者意味着什么。
DMLC的起因
某一天我在和李沐闲聊的时候感叹目前c++的hacker各做各的。当时我们都在做分布式机器学习项目,中间涉及到的分布式数据读入,进程管理等都问题,于是我们似乎在两份目的相同的代码。我提问到:为什么我们需要花重复的力气去完成一样的事情,而不是合力来做,让大家把更多的精力花在研究的新的东西(对于机器学习算法和系统研究,这些东西是基础组成部分,属于要做就不得不啃的硬骨头)。于是DMLC就诞生了,后来迅速地得到了张峥带领的minerva团的响应,也得到了我们老大们的支持 。
为什么用C++(和python)
语言选择一直是比较容易引起争议的话题。没有绝对好的语言,只有适合的。 这里说的只是一些个人观点。
机器学习项目和常见项目不同之一是它的重点在于数值计算,许多时候对于内存的需求属于一次性,从训练开始到训练结束几乎没有内存的分配。就数值计算而言,C++的算符重载特性可以使得写矩阵向量数值几乎和写matlab没有什么区别。这小小的一点让java比起python和c++在复杂矩阵逻辑的代码可读性和编写效率上面大大降低了。C++的另外一个特性模板使得矩阵向量逻辑计算可以在编译时直接优化到和手写优化差不多的效果,这一点是所有其他语言比较难以做到的。拿深度学习为例,如果为了高效地完成所有的操作,几乎所有其它语言的代码都需要把基础的调用重定向到c++的代码。对于常见的BLAS操作者并不会带来很大的问题,但是深度学习的代码有很多定制需求,如activation,loss。这时候要么重新用c++编写这些部分,要么用多个已有的操作来组合出这个需求。前者工程更复杂,后者不一定高效。这个事情用模板库多写一个公式就不需要绕这么多弯路了。
机器学习的另外一个特点是对于资源的可控性需求。虽然我们说我们处在资源爆炸的时代,1T内存现在也非常常见。但是从架构上的原因越来越趋向于异构多层次架构。如在处理器往NUMA走之后会导致每个core的局部缓存很小但是很快速。GPU也存在类似的问题。因此为了最大的效率,编程模型往往就被打回了那个用“64K内存,把内存当作硬盘”小心翼翼的时代。在深度学习应用里面最长出现的问题是显存不够。另外一个非常常见的场景是外存计算。高效的机器学习对于资源的分配和控制有很大的需求。这个时候大家会发现往往采用其它语言来编写深度学习的程序的时候往往需要被写成c++style,绕过已有的语言的一些东西,反而不太直接了。
在程序编写模式上,因为这类程序比较合适程序设计竞赛style,通过一开始分配合理的内存,中间并不会产生大量的内存碎片,因此对于gc的需求也并没有那么大了。机器学习程序是程序设计竞赛经验比较有帮助的地方之一(当然需要加上一些软件设计的基础)。另外OpenMP让C++成为了最容易写多线程机器学习的语言,写多线程程序只要在for循环前面加入我要多线程的pragma就可以了。C++11带来的函数式编程和线程库等特性使得程序可以在编写简洁程度上面进一步提升。因为上面说的几个原因,原来c++的弱势在机器学习程序里面并没有得到体现,并且可以比较容易的发挥优势。这也使得它成为开发高性能机器学习程序的首选方案。
当然c++语言本身比较复杂,有时候不太方便c++直接用写数据处理流程的代码。这个时候我觉得python/R是更加适合这一角色的语言。比较幸运的是几乎所有的高级语言都和C有很好的接口,可以很容易把c++代码接入到pythonR等语言中。比如xgboost目前最大的用户基本来自R和python的社区。总结下来,C++的性能,开发可控性和python/R的灵活性使得它们成为机器学习项目相辅相成的两端。
Java是另外一个我们用得很多的语言。JVM类语言被大量的流行的分布式系统采用,例如hadoop和spark。我个人觉得java比起C++(C++11)来一个比较大的优点是序列化。可以在不停系统的情况下的把一段代码方便的发给其他机器执行。这个特性得益于下层的虚拟机jvm。但这也是双刃剑,有时候为了效率和资源控制需要绕开JVM去做一些事情,而这么做并没有C++来的那么直接。另外目前的JVM语言在前面提到的算符重载,简单多线程编程,异构编程等方面还存在一些缺憾,不过如算符重载等特性或许可以被未来的语言修改所弥补。
库,接口还是平台
大家一般都希望自己造一个完整的轮子和跑车。借用李沐的说法,这是希望可以把各个零件组合起来的一辆车,并且可以根据路况随时更换轮子。在这个意义上DMLC更加倾向于库而不是平台。每一个库都可以独立地被使用。我们关注最大的问题是每个库的可移植性,在合适的依赖于当前运行平台的一些功能。我们更加倾向于做好机器学习这一重要的部分,并且和各个已有的框架有更加深度的整合。传统的分布式系统架构因为平台设计的问题往往需要一类程序独占。这已经在YARN,Mesos里面得到了很好的解决。自我封闭的程序总归会存在一定的缺陷,再优秀的程序,也需要更加开放地和其他优秀的程序和平台进行整合来发挥最大的效率。在这一点上面我们对于YARN,MPI, SGE等方面的整合就体现了这一初衷。虽然平台的说法听起来更诱人,其实灵活可变的库来的更加强大,这也是我们努力的方向。
举一个例子来说明换轮子这个事情,xgboost依赖于allreduce接口。在YARN的平台下,rabit提供了allreduce的实现。那么假设出现了一个原生态支持更快的allreduce的平台(如更快的MPI)会怎么办呢?xgboost完全可以链接到原生态平台的实现上面去。在这里平台提供的实现并不是最关键的,最关键的问题在于依赖的接口本身。
DMLC属于谁
DMLC是开源项目,代码全部采用apache2.0协议。简单来说,版权属于所有的开发者和社区。这个协议对个人和公司都非常友好,欢迎大家来用和开发。
DMLC结构
成立最初想要解决的问题是减少分布式机器学习开发的成本以及新算法被大家接受测试的时间。经过过去一段时间的努力,这些基础库的部分都已经到达了一定的阶段。我们自己作为分布式机器学习的研究者,这些是我们用来搭建我们researchcodebase的一个非常好的基础。
从开发角度来看,dmlc目前分三层,dmlc-core提供所有的分布式数据读写和平台相关的job提交脚本,以及如线程预读,数据缓冲等机器学习中经常出现的通用模块。通信部分包含基于支持allreduce的rabit和异步通信的parameterserver。最上层是基于通信接口和dmlc-core的分布式机器学习库。另外还有mshadow,minerva等为代表的支持GPU计算的库用以支撑深度学习的快速开发。开发者可以根据自己的需求来选择自己需要的模块。其中每一个部分之间的依赖很多是接口关系。一般最常见的组合为基于dmlc-core和rabit的分布式BSP机器学习(xgboost)和基于dmlc-core和ps的异步机器学习程序。不论是哪一种组合,所有的程序都可以共享同一的数据格式和提交方式,并且在DMLC支持的各个平台下面运行。
对于机器学习研究者:使用dmlc-core基础库,和rabit或PS的通信接口进行开发。可以直接受益于数据线程预读,外存缓冲等模块,并且直接读写各类分布式文件系统。可以大大提供机器学习程序的开发效率和开发出来模块的通用性。一个比较简单的例子如xgboost,cxxnet, minerva依赖于dmlc之后都可以很容易地读写S3, HDFS,对于一些云端的应用很有帮助。因为依赖是以库的形式出现,编译出来的程序可以直接在单机上不借助任何框架运行,线程读写和分布式文件系统访问等功能对于单机程序的编写也会有所帮助。
对于系统和平台研究者:dmlc-core和通信接口的开发都存在很多系统的问题。系统研究者可以贡献基础库。对于新的系统开发,也可以实现和支持dmlc已有的通信的接口,可以直接运行dmlc的程序。这里存在一些接口标准化的问题。举个例子,假设一个新的平台支持了比较高效的PS或者allreduce接口。那么完全可以接入到目前的分布式机器学习程序中,不需要重新去实现机器学习程序了。
基础库和接口整合了大家多年机器学习优化的经验,使得一些常见的模式如线程预读,外存缓冲等方案可以直接作为基础库提供给用户,让大家用更少的代价写更高效的代码。因此我们也很希望它对于机器学习和系统研究者有一些助力。也非常期待更多的同学加入到其中来,互相帮助,让大家有精力去完成更加有挑战的东西。并且通过dmlc本身让研究成果更快地体现到真正的应用中去。