[深度学习大讲堂]从NNVM看2016年深度学习框架发展趋势

本文为微信公众号[深度学习大讲堂]特约稿,转载请注明出处

虚拟框架杀入

从发现问题到解决问题

半年前的这时候,暑假,我在SIAT MMLAB实习。前端

看着同事一下子跑Torch,一下子跑MXNet,一下子跑Theano。算法

SIAT的服务器通常是不给sudo权限的,我看着同事挣扎在编译这一坨框架的海洋中,开始思考:编程

是否能够写一个框架:后端

import xx.tensorflow as tensorflow
import xx.mxnet as mxnet
import xx.theano as theano
import xx.caffe as caffe

这样,利用工厂模式只编译执行部件的作法,只需编译惟一的后端便可,框架的不一样仅仅在于前端脚本的不一样。设计模式

Caffe2Keras的作法彷佛是这样,但Keras自己是基于Theano的编译后端,而咱们的更但愿Theano都不用编译。服务器

当我9月份拍出一个能跑cifar10的大概原型的时候:微信

import dragon.vm.caffe as caffe
import dragon.vm.theano as theano
import dragon.updaters as updaters

if __name__ == '__main__':

    net = caffe.Net('cifar10.prototxt')
    loss = net.blobs['loss'].data
    updater = updaters.SGDUpdater(base_lr=0.001, momentum=0.9, l2_decay=0.004)
    for k,v in net.params().iteritems():
        for blob in v:
           updater.append(v, theano.grad(loss, v))
    train = theano.function(outputs=loss)
    update = theano.function(updater=updater)
    max_iters = 2333
    iter = 0
    while iter < max_iters:
        train()
        update()

我为这种怪异的写法取名叫CGVM(Computational Graph Virtual Machine)网络

而后过了几天,在微博上看到了陈天奇在MXNet的进一步工做NNVM的发布 (o(╯□╰)o)......数据结构

NNVM使用2000行模拟出了TensorFlow,我大概用了500行模拟出了Caffe1。app

VM(Virtual Machine)的想法实际上是一个很正常的想法,这几年咱们搞了不少新框架,

名字一个比一个炫,可是本质都差很少,框架的使用者其实是苦不堪言的:

○ 这篇paper使用了A框架,我要花1天配置A框架。

○ 这篇paper使用了B框架,我要花1天配置B框架。

.......

正如LLVM不是一种编译器,NNVM也不是一种框架,看起来更像是框架的屠杀者。

NNVM的可行性偏偏证实了现行的各大框架底层的重复性,而上层的多样性只是一个幌子。

咱们真的须要为仅仅是函数封装不一样的框架买单吗?这是值得思考的。

计算图走向成熟

计算图的两种形式

计算图最先的出处应该是追溯到Bengio在09年的《Learning Deep Architectures for AI》,

Bengio使用了有向图结构来描述神经网络的计算:

如图,符号集合{*,+,sin} 构成图的结点,整张图可当作三部分:输入结点、输出结点、从输入到输出的计算函数。

随后在Bengio组的Theano框架执行中,Graph就被隐式应用于Op的链接。

不过这时候,Op仍是执行时-动态编译的。

 

Caffe1中计算图其实就是Net,由于Net能够被Graph模拟出来(CGVM和Caffe2Keras都实现了)。

贾扬清在Caffe1中显式化了计算图的表示,用户能够经过编辑net.prototxt来设计计算图。

Caffe1在Jonathan LongEvan Shelhamer接手后,他们开发了PyCaffe

PyCaffe经过Python自然的工厂(__getattr__),实现了net.prototxt的隐式生成。

以后的Caffe2,也就直接取消了net.prototxt的编辑,一样利用Python的(__getattr__)获取符号类型定义。

Caffe1带来一种新的计算图组织Op的描述方式,不一样于Theano直接翻译Op为C执行代码,而后动态编译,

软件工程中的高级设计模式——工厂模式被普遍使用。

计算图被划分为三个阶段,定义阶段、构造阶段、执行阶段:

一、定义阶段:定义Layer/Op的name、type、bottom(input),top(output)及预设参数。

二、构造阶段:经过工厂模式,由字符串化的定义脚本构造类对象。

三、执行阶段:根据传入的bottom(input),获得额外参数(如shape),此时计算图才能开始执行。

阶段划分带来的主要问题是限制了编译代码的完整性和优化程度。

在Theano中,C代码生成是最后一步,你能够组合数片细粒度的代码块,依靠编译器作硬件执行前的超级优化。

编译器优化是的提高指令流水线效率的重要手段,编译器调度技术减小数据冲突,编译器分支预测技术减小控制冲突。

而工厂模式编译符号时只考虑了单元自己,编译器没有上下文环境可供参考,故最终只能顺序执行多个预先编译的符号单元。

当符号粒度过细时,一个Layer的实现就会变成连续执行数个单元,每一个单元都要处理一遍向量/矩阵,致使“TensorFlowSlow”。

计算图做为中间表示(IR)

PyCaffe和Caffe2将定义阶段移到Python中,而将构造和执行阶段保留在C++中作法,是计算图做为IR的思想启蒙。

Python与C++最大的不一样在于:一个是脚本代码,用于前端。一个是本地代码,用于后端。

脚本代码建立/修改模型方便(无需因模型变更而从新编译)、执行慢,本地代码则正好相反。

二者取长补短,因此深度学习框架在2016年,迎来了先后端开发的黄金时代。

如上图,不管是9月份先提出的NNVM,仍是最近Intel曝光的Nervana,都分离了先后端。

后端的独立,不只减小了编译工做,最大的优点在于下降了传统框架作跨设备计算的代码耦合度。

在paper每周都有一大堆的如今,若是后端的每一次变更都要大量修改前端,那么框架的维护开销是很是大的。

 

在前端定义用于描述输入-输出关系的计算图有着良好的交互性,咱们能够经过函数和重载脚本语言的操做符,

定义出媲美MATLAB的运算语言,这些语言以显式的Tensor做为数据结构,Operator做为计算符和函数,

Theano和MXNet都是这样隐蔽处理由表达式向计算图过渡的。

而Caffe2则比较直接,你须要先建立一个Graph,而后显示地调用Graph.AddOperator(xxx)

TensorFlow一样能够显式化处理Graph。

 

与用户交互获得的计算图描述字串是惟一的,可是与用户交互的方式倒是不惟一的。

因此IR之上,分为两派:

第一派要搞本身的API,函数封装很是有个性,宣示这是本身的专利、独门语言。

第二派不搞本身的API,反而去模拟现有的API,表示我很低调。

显然,用户更喜欢用本身熟悉框架的写法去描述模型,不喜欢每天背着个函数速查手册。

计算图优化

用于中间表示获得的计算图描述最好不要直接构造,由于存在冗余的求解目标,且可共享变量还没有提取。

当限制计算图描述为有向无环图(DAG)时,一些基本的图论算法即可应用于计算图描述的化简与变换。

陈天奇在今年的MSR Talk:Programming Models and Systems Design for Deep Learning中,总结了计算图优化的三个点:



①依赖性剪枝

分为前向传播剪枝,例:已知A+B=X,A+B=Y,求X?

      反向传播剪枝,  例:A+B=X,A+B=Y,求X、Y,dX/dA?

根据用户的求解需求,能够剪掉没有求解的图分支。

②符号融合

符号融合的自动实现是困难的,由于Kernel基本再也不实时编译了,因此更多体如今符号粗细粒度的设计上。

粗粒度的符号融合了数个细粒度的符号,一次编译出连续多个执行步骤的高效率代码。

粗粒度和细粒度并没有好坏区分,一个速度快,一个更灵活。

从贪心角度,VM框架一般会提供粗细粒度两种实现給用户,于是须要更多人力维护编译后端。

③内存共享

Caffe1对于激活函数大多使用的inplace处理——即bottom和top是同一个Blob。

inplace使用新的输出y当即覆盖的输入x,须要如下两个条件:

  一、bottom和top数量都为1,即:计算图中构成一条直线路径,

  二、d(y)/d(x)与x是无关的,因此x被y覆盖不影响求导结果。

常见的激活函数都符号以上两个条件,于是能够减小内存的开销。

可是Caffe1在多网络内存共享优化上极其糟糕的,以致于Caffe1并不适合用来跑GAN,以及更复杂的网络。

一个简单例子是交叉验证上的优化:训练网络和验证网络的大部分Layer都是能够共享的,

可是因为Caffe1错误地将Blob独立的放在每一个Net里,使得跨Net间很难共享数据。

除此以外,Caffe1还错误地将临时变量Blob独立放在每一个Layer里,致使列卷积重复占用几个G内存。

让Net和Layer都能共享内存,只须要将Tensor/Blob置于最顶层,采用MVC来写框架便可。

Caffe2引入了Workspace来管理Tensor,并将工做空间的指针传给每个Op、每个Graph的构造函数。

这将使内存区域彻底暴露在全局(相似MFC的Document),与TensorFlow同样,提供Feed/Fetch这一组API用于Python的外部访问。

这种内存的管理方式,同时也为模拟Theano的API提供便利(e.g. theano.shared和get_value,本质就是Feed与Fetch)。

使用Workspace优化显存,可以使Caffe1作列卷积仅比CUDNN多300M(VGG16全链接) / 900M(VGG16全卷积),且时间开销近似为零。

遗憾的是,Caffe1臃肿、错误的代码结构彷佛是无缘Workspace的引入了(这将面临大面积的代码重写,后果就是社区爆炸)。

P.S: 贾扬清在知乎还吐槽过Caffe1中大面积错误的模板写法,致使Caffe1彷佛也是无缘FP16了..(你们赶忙研究Caffe2吧)

面向计算图的直接调试

不少用户抱怨TensorFlow调试困难,不像Caffe1那样更容易命中Bug的要害。

Caffe1的调试简单,源于Layer/Op的执行段很容易定位,Debug信息能够有效的输出。

而TensorFlow在计算图之上,为了迎合工业界搞了许多莫名其妙的API。

面向计算图的调试技术宗旨就是,能够实时输出模型执行计算图的文本描述。

对于一个符号单元而言:

①明确它的输入输出是什么Tensor,附加的静态参数是什么。

②它的符号名是什么,是什么符号类型,若是怀疑错了,直接if(name=xxx) {.....} 便可针对性调试。

对于几个符号组成的局部图单元:

只须要各个符号间输入输出的拓扑链接关系,这个和看net.prototxt没什么区别。

以上两种规格的单元调试,最好可以跳过API,直接暴露给用户。

只要符号单元的实现正确、计算图的拓扑链接及传入参数正确,那么模型的执行结果理论上是不会错的。

新的风暴已经出现

VM的侧重点

CGVM和NNVM的侧重点是不太同样的,CGVM更强调前端上的扩展化,后端上的惟一化。

因此CGVM不会去支持Torch编译后端,也不会去支持Caffe编译后端。

在NNVM的知乎讨论帖中,有一种观点认为VM是轻视Operator的实现。

但实际上,咱们手里的一堆框架,在Operator、Kernel、Math级别的很多实现是没有多少区别的。

但偏偏折磨用户的正是这些没有多少区别的编译后端:各类依赖库、装Linux、编译各类错。

因此我我的更倾向整个DL社区可以提供一份完善的跨平台、跨设备解决方案,而不是多而杂的备选方案。

从这点来看,CGVM彷佛是一个更完全的框架杀手,但在ICML'15上, Jürgen Schmidhuber指出:

真正运行AI 的代码是很是简短的,甚至高中生都能玩转它。不用有任何担忧会有行业垄断AI及其研究。

 

简短的AI代码,未必就是简单的框架提供的,有多是本身熟悉的框架,这种需求体如今前端而不是后端。

VM指出了一条多框架混合思路:功能A,框架X写简单。功能B,框架Y写简单。

功能A和功能B又要end-to-end,那么显然混起来用不就好了。

只有使用频率不高的框架才会消亡,VM将框架混合使用后,熟悉的味道更浓了,那么便构不成”框架屠杀者“。

强大的AI代码,未必就是VM提供的,有多是庞大的后端提供的。

随着paper的快速迭代,后端的扩展仍然是最繁重的编程任务。

VM和后端侧重点各有不一样,难分好坏。但分离二者的作法确实是成功的一步。

VM的形式

VM及计算图描述方式是链接先后端的桥梁。

即使后端是惟一的,根据支持前端的不一样,各家写的VM也很难统一。

实际上这就把框架之间的斗争引向了VM之间的斗争。

两人见面谈笑风生,与其问对方用什么框架,不如问对方用什么VM。

VM的主要工做

合成计算图描述的过程是乏味的,在Caffe1中,咱们恐怕已经受够了人工编辑prototxt。

API交互方面,即使是MXNet提供给用户的API也是复杂臃肿的,或许仍然须要一个handbook。

TensorFlow中的TensorBoard借鉴了WebOS,VM上搞一个交互性更强的操做系统也是可行的。

除此以外,我可能比较熟悉一些经典框架,那么不妨让VM去实现那些耳熟能详的函数吧!

一、模拟theano.function

Theano的function是一个很是贴近数学表达计算图掩饰工具。

function内部转化表达式为计算图定义,同时返回一个lambda函数引向计算图的执行。

总之这是一个百看不腻的API。

二、模拟theano.tensor.grad

结合计算图优化,咱们如今能够指定任意一对求导二元组(cost, wrt)。

于是,放开手,让自动求导在你的模型中飞舞吧。

三、模拟theano.scan

theano.scan是一个用来搭建RNN的神器。尽管最近Caffe1更新了RNN,可是只支持固定循环步数的RNN。

而theano.scan则能够根据Tensor的shape,为RNN建动态的计算图,这适合在NLP任务中处理不定长句子。

四、模拟pyCaffe

pyCaffe近来在RCNN、FCN、DeepDream中获得普遍应用,成为搞CV小伙伴们的最爱。

pyCaffe大部分是由C++数据结构经过Boost.Python导出的,

不幸的是,Boost.Thread导出以后与Python的GIL冲突,致使PyCaffe里没法执行C++线程。

尝试模拟移除Boost.Python后的PyCaffe,在Python里把Solver、Net、Layer給写出来吧。

五、模拟你熟悉的任意框架

.......等等,怎么感受在写模拟器.....

固然写模拟器基本就是在重复造轮子,这个在NNVM的知乎讨论帖中已经指明了。

VM的重要性

VM是深度学习框架去中心化、解耦化发展迈出的重要一步。

同时暴露了目前框架圈混乱的本质:计算图之下,众平生等。计算图之上,群魔乱舞。

在今年咱们能够看多许多框架PK对比的文章,然而大多只是从用户观点出发的简单评测。

对比之下,NNVM关注度不高、反对者还很多这种状况,确实让人感到意外。

回顾与展望

回顾2016:框架圈减肥大做战的开始

高调宣布开源XXX框架,再封装一些API,实际上已经多余了。

VM的出现,将上层接口的编写引向模拟经典的框架,从而达到减肥的目的。

框架维护者应当将大部分精力主要放在Kernel的编写上,而不是考虑搞一些大新闻。

展望2017:DL社区可否联合开源出跨平台、跨设备的后端解决方案

后端上,随着ARM、神经芯片的引入,咱们迫切须要紧跟着硬件来完成繁重的编程。

后端是一个敏感词,由于硬件能够拿来卖钱,因此更倾向于闭源。

除此以外,即使出现了开源的后端,在山寨和混战以前是否能普及也是一个问题。

展望2017:来写框架吧

VM的出现,带来另外一个值得思考的问题:如今是否是人人应该学写框架了?

传统框架编写的困难在代码耦合度高,学习成本昂贵。

VM流框架分离了先后端以后,前端编写难度很低,后端的则相对固定。

这样一来,框架的编程层次更加分明,Keras地位彷佛要危险了。

展望2017:更快迭代的框架,更多变的风格,更难的垄断地位

相比于paper的迭代,框架的迭代彷佛更快了一点。

余凯老师前段时间发出了TensorFlow垄断的担心,但咱们能够很乐观地看到:愈来愈多的用户,在深刻框架的底层。

TensorFlow并非最好的框架,MXNet也不是,最好的框架是本身用的舒服的框架,最好是一行行本身敲出来的。

若是你已经积累的数个框架的使用经验,是时候把它们无缝衔接在一块儿了。

相关文章
相关标签/搜索