本文由 「AI前线」原创,原文连接: 开发易、通用难,深度学习框架什么时候才能飞入寻常百姓家?
做者|老师木
编辑|Emily
AI 前线导读:”深度学习框架正在快速演化,各大公司都推出了本身的框架,TensorFlow, PyTorch, Caffe2, MXNet, PaddlePaddle,大大推进了深度学习的发展,同时也让用户有应接不暇无所适从之感。咱们认为,深度学习框架用户有必要去了解深度学习框架的一些基本原理,这有助于咱们用好“框架”这个工具,也有助于根据自身须要去选择合适的框架。前端
2018 年 1 月 14 日,袁进辉(老师木)表明 OneFlow 团队在 AICon 北京站作了标题为《深度学习框架技术剖析》的演讲,AI 前线第一时间受权整理了老师木我的注解版演讲全文。”git
做为框架的开发者,OneFlow 团队(一流科技,老师木的创业公司)发现,虽然框架多种多样,但框架核心技术正呈现收敛的态势,通过几年的发展,在深度学习框架开发者眼里出现一些“共识”,或所谓“最佳实践”,几乎全部框架都去拥抱了这样技术选型,在架构和技术选择上趋同。另外一方面,也有一些技术在框架开发者眼里属于犹豫不定或机关用尽的状态。程序员
此次报告会对已经收敛的技术(“最佳实践”)作一个梳理,读者会发现,开发一个深度学习框架没有那么难。本报告也会简要讨论目前框架未解决的难题,读者也会发现,开发一个超越已有技术的框架有很难的问题。最后,咱们会从框架开发者的视角去对主流深度学习框架作一句话点评,供用户在作技术选型时参考。github
深度学习框架的定位算法
首先介绍深度学习框架的背景,而后介绍深度学习框架开发中已经收敛的技术和仍未解决的问题,其次点评主流深度学习框架,最后对 2018 年的深度学习框架技术发展作出展望。编程
在进入正文前,让咱们首先声明一些前提,若是这些前提不成立,那么“深度学习框架”就没那么重要。但这次报告不会花时间笔墨去论证每一个观点,读者若想了解这些观点背后的逻辑,能够参阅 OneFlow 微信公众号的历史文章。后端
本文仅对第四点作一些阐述,用软件实现深度学习算法加速,可分微观和宏观两个层次。浏览器
微观层次主要聚焦在单个设备或芯片上的代码优化,设备厂商一般会工做在这个层次,他们会提供高性能的库,譬如 x86 或 arm CPU 上 MKL, OpenBlas,Nvidia GPU 上的 CuBlas, Cudnn 等,在大部分稠密计算场景都能贴近设备的理论性能,优化空间不大(固然在终端设备等低功耗场景有不少发挥空间)。性能优化
宏观层次,主要是多设备和多计算节点层面的优化,这要靠分布式框架的支持,是计算力推向更高水平的关键,在性能上仍有巨大优化空间。最后,深度学习软件要解决编程不够快(程序员的效率)和程序运行不够快(计算机的效率)两个痛点,“两个痛点的描述”出自尼克著《人工智能简史》里的一句话。服务器
weixin.qq.com/r/KSrexuXEu… (二维码自动识别)
对深度学习框架感兴趣,可关注 OneFlow 公众号,下面几页报告的详细注解均可以在历史文章 GIAC 报告《深度学习平台技术演进》中看到。
神经网络由若干层次构成,每一个层次一般均可以表示成对矩阵的处理,这种稠密计算形式特别适合使用高度并行的硬件加速(GPU、TPU 等)。
限于硬件制造工艺水平,单个设备(GPU、TPU) 不可能无限大,而工业级应用对计算力的渴求是无止境的,此时就必须用高速互联的多个设备协同来完成大规模计算。上图展现了 GPU 集群和 TPU 集群,在这种配置里,一般是 CPU 和 GPU (或 TPU) 一起工做,CPU 负责任务的调度和管理,而 GPU 负责实现稠密计算,这就是常常说的异构计算(Heterogenous computing)。
“硬件越快,软件越难”这个观点分享过屡次,请参阅《深度学习平台技术演进》一文。
简要说:自上而下看,深度学习模型训练一般使用随机梯度降低(SGD) 算法,是更接近流式计算的一种负载:每处理一小片数据,就引发系统内部状态的变化;自下而上看,深度学习普遍采用异构计算技术,GPU 此类的设备吞吐率很是高,是 CPU 的 10 倍以上,意味着一样大小的计算任务,GPU 能够更快完成。从小粒度和快设备两方面看,深度学习训练中的计算任务粒度很是小,一般是数十毫秒到百毫秒级别。可是,设备互联带宽并无实质改进,譬如同机内部 PCIe 或多机互联使用的高速以太网或 Infiniband 的传输带宽要低于 GPU 内部数据带宽一两个数量级。以上因素给分布式软件框架带来巨大压力,若是处理很差,就会形成设备利用率低,总体系统性能差的后果。打个比方,虽然高铁要比普通的列车开起来快不少,但若是让高铁每一个车站都停两分钟,那么高铁并不会比普通列车快多少。
软件层和硬件层都是属于“计算力”范畴,软件层扮演了传统意义上操做系统(OS,如 Windows, Linux),或者互联网时代浏览器,或者移动互联网时代 Android, IOS,或者大数据时代 Hadoop 的角色,是上层应用的入口。同时软件生态又定义了底层硬件的角色,会影响底层硬件的发展和命运。
深度学习框架的最佳实践
咱们首先介绍深度学习框架中已经收敛的技术,理解了这些原理,每一个人应该能开发出一个本身的深度学习框架。
在进入技术细节以前,让咱们先来理解两个很重要的概念:控制流(Control flow) 和数据流(Data flow),这俩概念事关后续一些关键的技术选择。以 a = x + y; b = a * a; c = 4 - a; 这样一段简单的程序为例,有两种编程模式,一种是以 C 语言为表明的命令式编程(imperative programming),语句的排列顺序隐式的刻画了程序的执行顺序(左图中虚线箭头表示执行顺序),有哪些语句能够并行执行,并不太明确,若是要在多个线程中执行这几条语句,为了防止出现多个线程间的读写冲突,可能要使用锁 (lock)等技术来保护某一个变量(某一段内存)防止出现 data race。
另外一种编程模式是以 Lisp 为表明的函数式编程(functional programming),程序用一系列表达式来刻画,程序的执行不是按表达式的声明顺序来执行,而是从表达式中挖掘出各个表达式之间的数据依赖关系,把数据依赖关系用一个有向无环图来表示,图显式刻画了哪些表达式必须在另外一些表达式以前求值,或者哪些表达式之间不存在依赖关系,能够并行执行。在并行和并发愈来愈多的世界,functional programming 和数据流程序正在愈来愈受重视。
数据流模型通常表示成有向无环图(Directed acyclic graph, DAG)。譬如上一页的 a = x + y; b = a * a; c = 4 - a; 三个表达式能够表示成这样一张图,圆圈表示数据,方块表示算子。算子之间靠数据的生产和消费关系产生依赖。数据流模型的优点主要包括两方面:
(1) 表示上的好处,显式描述了程序中的全部并行机会;
(2)实现上的好处,数据流的执行引擎能够用很简单的方法支持并发和并行,而在控制流程序中对并发和并行的支持就要复杂的多。
比较早的框架 Caffe 经过 Layer 这种抽象,运算和数据是放在一块儿的。TensorFlow 出现后,有向无环图中两个最基本的元素,操做符(运算)和张量(数据)是分开表示的,这种抽象模式也被其它框架所采用。具体地,Operator 通常是运算的描述,Kernel 是运算的具体实现,在实现上还要考虑操做符粒度的问题,理论上若是支持了最基本的加减乘除就能够经过图计算自动支持更加复杂的运算(如一些神经网络层次的计算),但粒度太细就对编译器要求特别高,当前编译器生成的代码不必定能超过工程师手工优化的代码,所以在多数框架里都是直接支持一些粗粒度的操做符,譬如卷积操做符,矩阵乘操做符等(值得注意的是 TensorFlow XLA, TVM 在细粒度操做符描述的图优化方面有一些好的实践)。对于张量计算的支持,业界也有不少技巧,譬如通常使用 C++ 模板元编程来实现,借助于编译时计算来提升运行时的效率,TensorFlow 等框架通常基于 Eigen 库,MXNet 使用本身开发的 Mshadow。
autograd 已经成为深度学习框架的标配。有了 autograd,用户写程序时,只须要描述前向计算是怎么作的,后向计算过程由系统本身推导完成。autograd 经过导数的链式法则实现,逆拓扑序搭建反向计算图。须要注意两点:
(1)后向计算过程可能会依赖于前向计算产生的中间数据,因此前向计算的中间数据可能要一直保持到对应的后向计算完成才能释放,不然就须要在后向计算时从新进行前向计算。
(2)若是前向计算过程有多个操做符消费了同一个数据,后向计算图时就须要把这几个操做符对应的梯度算子上传过来的偏差信号进行累加。上面的示意图来自陈天奇在华盛顿大学一门《Deep learning systems》课程的课件,感兴趣的读者能够去课程网站获取更多资料。
给定用户输入的 DAG (称之为逻辑图,logical graph), 系统通常会利用编译器技术对图进行优化重写,上图罗列的一些优化技巧就不一一展开解释了。通过优化最终送到执行引擎执行的图叫物理图(physical graph),物理图可能和输入的逻辑图已经大相径庭了。在 TensorFlow, PyTorch, MXNet, Caffe2 中均可以看到这些常见的优化办法。
执行引擎是深度学习引擎的核心,基本原理是按拓扑序去执行算子 / 操做符,以上图为例,刚开始,乘法和减法运算没法执行,由于它们依赖的一个数据 a 尚未生成,引擎首先执行输入数据已经就绪的操做符,即加法,当加法完成后,执行引擎会从 DAG 中删掉已经执行的节点,而后发现乘法和减法的执行条件已经知足了,再去执行乘法和减法。事实上,当前全部大数据处理引擎的内核都是用这个原理实现的。在深度学习框架里,须要注意调度器是在 CPU 上执行的,而操做符的真实运算是在 GPU 上完成的,为了高效协调 CPU 和 GPU 之间的工做,在具体实现上有一些技巧。感兴趣的读者能够观摩 TensorFlow, MXNet, Caffe2 的执行引擎,也许你会发现有更好的实现办法。
从执行效率考虑,深度学习框架底层通常基于 C++ 开发,从易用性角度出发,也同时提供 Python 前端便于数据科学家使用。上图来自李飞飞教授在斯坦福的 cs231n 课程课件,展现了 Numpy,TensorFlow 和 PyTorch 对同一个简单神经网络训练过程的实现。
最左侧是 Numpy 代码,它的第一个特点是 imperative programming,是即时求值(eager evaluation),运行完 b = a + z 这条语句,b 的结果就出来了;第二个特点是没有 autograd,因此用户不只要写前向计算的代码,还要把后向梯度降低的代码写出来,而 TensorFlow 和 PyTorch 都支持了 autograd,在代码中只须要写前向计算的过程,然后向计算过程是系统自动构建的。
TensorFlow 和 PyTorch 的区别则是,前者是 lazy evaluation,后者是 eager evaluation。在 TensorFlow 中,a = x + y; b = a + z 只是一些表达式,构建了一个数据流图,在执行 sess.run 时刻才会被真正执行,并且执行顺序不必定和表达式声明顺序一致。在 PyTorch 中,和 Numpy 原理相似,每条语句都是马上执行,并且按照语句的排列顺序执行。看上去,PyTorch 的代码的确更加简洁,后文会进一步讨论延迟求值和即时求值的问题。
深度学习框架不仅要解决易用性和高效性,还要方便部署运维。当前主流的深度学习框架都是基于检查点机制实现容错,Fail fast and warm start。深度学习框架还须要和上下游的开源工具链有机衔接,譬如分布式数据存储和数据预处理依靠 Hadoop 或者 Spark。部署运维,如今比较推崇基于 Docker 和 Kubernetes 相结合的方案。用户有时须要在多个框架之间切换,随着 ONNX 标准的推出,也大大便利了各类框架间的迁移,譬如使用 PyTorch 描述或训练的模型能够按 ONNX 规范导出,并被 Caffe2 框架使用。除了解决训练问题,深度学习框架还便于上线部署,为此 TensorFlow 推出了单独的 serving 模块。
深度学习框架当前技术焦点
下面咱们探讨一些当前框架开发者还犹豫不定或束手无策的技术问题。
Define-and-run 和 Define-by-run 近期关注度比较高,PyTorch 靠 Define-by-run 这个特性吸引了不少开发者。这个技术问题还有其它等价的描述,譬如 define-and-run,基本上和 lazy evaluation, 或 declarative programming, data flow 是一回事,一般意味着效率高。define-by-run 则基本上和 eager evaluation, imperative programming 或 control flow 是一回事,一般意味着灵活性。最近,不少框架在积极的推动支持 define-by-run 的运行模式,譬如 TensorFlow 增长了 eager evaluation,MXNet 推出了 gluon 接口,PaddlePaddle Fluid 也是一种 imperative programming 的用法。那这两种技术选择究竟是怎么回事呢? 咱们认为:
(1)Imperative programming 只不过是大部分程序员更加熟悉的编程方式,实现一个 imperative programming 的深度学习框架要比实现一个 declarative programming 的框架简单(最简只须实现 autograd,复杂点要支持 JIT)。传统的 lazy evaluation 框架增长 imperative programming 接口能够作到和 PyTorch 彻底同样的用户体验,只不过要费些周章。
(2)只要 declarative programming 解决了调试困难等问题,就是对用户更友好的一种编程模式,用户只要在写程序时描述 what,而不须要关心 how,底层细节对用户透明,这是现代变编程语言的发展趋势。
(3)并行和并发表明着将来的趋势,那么数据流(声明式编程,函数式编程)表明着将来,data flow 模型在任务描述和系统执行引擎的简洁性上都有自然优点。
并行计算能够扩大处理任务的规模,也能够加速计算。具体到深度学习框架,整体状况是:数据并行已经获得解决,不管是哪一个框架都能把适合数据并行的任务作到接近理想加速比,譬如计算机视觉领域各类 CNN 模型;数据并行和流水线并行不支持或支持的很差,致使在某些分布式训练场景,硬件利用率太低,训练周期过长。在集合通讯(Collective communication operation)上有基于参数服务器的,MXNet、PaddlePaddle、TensorFlow、也有基于 MPI(或类 MPI)的,譬如 Caffe2。TensorFlow 在宏观架构上还区分了 Client、Master、Worker 节点,在重构版的 PaddlePaddle 也使用了相似的架构。
数据并行的原理解释,能够参见本公众号《深度学习平台技术演进》一文,现有框架都能良好支持数据并行。本来限制数据并行的一个问题是随机梯度降低算法依赖的 mini-batch 不能太大,太大的 mini-batch 算法不收敛,这就致使即便有大量的并行设备,也发挥不出来威力。近期有一系列成果攻克了这个问题,譬如 Facebook 推出的一个小时训练 ImageNet,以及尤洋作的一系列工做,能够把 mini-batch 推广到 32K, 保证算法仍然收敛,这就能充分发挥数据并行的优点。
模型并行的原理请参见本公众号《深度学习平台技术演进》一文。模型并行自己实现复杂度不是特别高,主要困难在于有的场景适合数据并行,有的场景适合数据并行,有的场景同时须要模型并行和数据并行,这就须要根据实际状况正确的对数据从新组织(分裂,合并)和路由(把数据正确的发送到目的地,scatter 或 broadcast)。再有就是,当数据路由比较复杂时,就很难高效的支持,就须要流水线并行的支持。
当神经网络模型或中间隐状态体积很大时,譬如超过一张显卡显存的容量,除了使用模型并行,还可使用流水线并行。流水线并行有点像接力比赛,上图展现了一个简单的例子,第一个 GPU 作完第一层的计算后,把结果传递给第二块 GPU,第二块 GPU 完成中间四层的计算以后,把结果传递给第三块 GPU 完成计算。一般训练深度学习模型时,存在多个阶段,譬如把数据从磁盘加载到主存,把数据从主存搬运到 GPU, GPU 完成一个阶段的计算以后,可能还须要把数据经过网络传送到另外一台机器。在这样多阶段任务中,流水线并行对于系统性能相当重要。能够注意到,大部分框架在 IO 阶段会作流水线并行,而在多个计算阶段,或者计算与通讯阶段之间都没有相应支持。基于现有框架去支持模型并行,流水线并行还很是困难。
主流深度学习框架点评
下面分享一些咱们对各类框架的理解和判断,若是观点有误差,敬请理解,欢迎批评指正。
以上框架用户比较多,开发团队技术实力雄厚。深度学习框架的先驱 Theano 已中止更新,它的 autograd 机制已经被这些现代框架所吸取;咱们没有研究微软开发的 CNTK;Intel 收购的 Nervana 在开发一个新的框架 NGraph,也值得关注,不过它目前主要聚焦在单设备优化;DMLC 的 NVVM 和 TVM 放在 MXNet 内;有一个来自日本研究人员的框架 Chainer 也比较有特点,Python 前端很是清爽。
TensorFlow
TensorFlow 是系统完整度最高的,支持 training 和 inference (serving),支持图像经常使用的 CNN,也支持天然语言和语音经常使用的 RNN/LSTM, 还有移动端的 TensorFlow Lite,支持 lazy execution 也支持 eager execution,社区生态最强大,Google 在深度学习算法和应用方向的研究也是冠绝天下(参见 Jeff Dean 写的 Google Brain 2017 年度回顾:zhuanlan.zhihu.com/p/32905123 )深度学习领域不少新的研究成果都是以 TensorFlow 代码发布的。
但 TensorFlow 的性能也是广受诟病,咱们不大清楚 TensorFlow 在 Google 内部是否是性能卓越,在外部用户那里常常能听到用户抱怨 TF 慢,在学界和工业界不少 Benchmark 里,你们也喜欢拿 TensorFlow 作 baseline,譬如 CMU Eric Xing 教授团队发表的 Poseidon 论文,基于 TF 作了一系列优化以后,性能提升很是显著;Uber 团队改造了 TensorFlow 的分布式实现后(把 PS 换成 MPI),CNN 数据并行场景还能提升一倍的性能 (见 Uber Horovod github.com/uber/horovo…) 。
从框架开发者的角度看,咱们觉得 TensorFlow 要解决性能问题,须有壮士断腕的决心,推翻一系列原有设计和实现。最后,TensorFlow 毕竟是功能最完整的框架,若是要训练大规模 RNN/LSTM,目前只能选择它,尽管要忍受一下很长的训练周期。
PyTorch
Facebook AI Lab 出品的 PyTorch 是深度学习框架的一匹黑马,靠 Eager evaluation 博得了大批深度学习研究人员的青睐。基于 Imperative programming 的理念和基于 Python 的语言构造(控制流)来搭建神经网络,灵活性高。NLP 领域常有一些动态图的需求,PyTorch 是首选。
咱们认为在单机场景下,易用性和灵活性是最重要的用户需求,其它框架为了知足这样的需求,必须在本来为分布式设计的技术架构上作一些妥协,难以和 PyTorch 的最简内核竞争。PyTorch 为了克服 Eager evaluation 的一些问题,也在经过 JIT 来享受一些 Lazy evaluation 的优势,同时也在向分布式场景进军。如前文所述,在大规模分布式应用场景下,用户程序只能是 Lazy evaluation 风格,数据流的执行引擎在高并发高并行的场景有自然的优点,PyTorch 如今的设计实现距离这个目标还比较遥远。
MXNet
MXNet 开发团队实力雄厚,如今是 Apache 孵化项目,有 Amazon 官方支持加持。MXNet 的特色是包含不少正对 Geek 品味的实现技巧, 很值得喜欢钻研前沿技术的人去观摩。但很差的地方是,给人一种比较“杂”的感受,让开发者感到困惑,譬如矩阵库有两套实现 Mshadow 和 NDArray。MXNet 在计算机视觉领域老是能紧跟前沿应用,一些新的模型出来社区老是第一时间支持。MXNet 有一些关联项目,譬如 NNVM 和 TVM,目前来看 TVM 更独特,NNVM 里实现的一些图优化技术在其它框架里也是有对应实现的,而 TVM 和 TensorFlow XLA 应该是处于一个层次:聚焦于单设备程序性能优化。基于 TVM、Halide、TensorFlow XLA ,用户能够在单设备上使用声明式编程,编译器自动生成高效的后端代码。
Caffe2
Caffe 的用户还很是多,第二代 Caffe2 和第一代已经迥然不一样了,但继承了一些简洁的品质在里面。框架的抽象很是简洁,不厚重,Op/Kernel 和引擎主要在 C++ 层实现,而复杂的图拓扑结构在 Python 层面处理。Caffe2 借鉴了 TensorFlow 对 Op/Kernel 的抽象,没有再使用以前 Layer 那种把数据和操做放在一块儿的设计。一样是 Op/Kernel 抽象,Caffe2 也不是简单的模仿, 代码看上去更加舒服。Caffe2 目前支持数据并行,曾创造了一个小时训练 ImageNet 的记录,对 Caffe 有感情的用户能够尝试。据了解,Caffe2 在 Facebook 内部承担了“工业级应用”和“部署”的重任,也开发了很好的移动端支持,这应该是 Caffe2 的特点。Caffe2 还有一个颇有特点的技术,gloo 网络库是定制的 MPI 实现,有“去中心化”集群通讯的味道,也便于支持流水线。
PaddlePaddle
PaddlePaddle 最大的优点是百度内部在普遍使用,经受了实战检验。第一代 PaddlePaddle 是比较接近 Caffe,分布式并行也依赖于 Parameter server。最近一年,Paddle 团队在开展很是激进的重构。以咱们的理解,重构版 PaddlePaddle 借鉴了不少 TensorFlow 的设计,因此 Paddle 可否解决 TensorFlow 面临的问题呢? 重构后的 PaddlePaddle 主推命令式编程接口,正像咱们评价 PyTorch 时所说的,命令式编程接口当然亲民,但数据流表示在大规模分布式运行场景有自然的优点(表示能力和引擎实现复杂度方面),要很好的支持大规模分布式场景,深度学习框架最终要把“控制流”代码翻译成“数据流”代码在后台运行。
整体来看,深度学习框架开发的绝大部分技术秘密已经公开了,开发一个简单的深度学习框架没有那么难。但从另外一方面看,要开发一个易用性和高效性都很卓越的框架是很是困难的,即便是世界上最优秀的开发团队也感到困难重重,克服这些问题须要坚苦卓绝的创新和坚持。
展 望
展望将来一年
(1) 咱们认为在计算机视觉领域也将迎来模型更大的场景,譬如把 Hinton 的 Capsule net 从 Cifar 推向 ImageNet 规模,模型大小就远超当前各类常见的 CNN, 这种状况必须把模型分裂到多个设备上去完成,也就是所谓的模型并行。并且学界很关心神经网络结构学习,元学习,也有必要去探索 CNN 以外的架构,在人脑视皮层尽管存在 CNN 这种神经元分层组织和局部感觉野的神经元,但并无所谓 weight sharing(神经元功能柱有相似的特异选择性,但不是严格同样),这种神经元之间的链接规模很是庞大,若是去掉这个约束会发生什么?若是深度学习框架不能支持模型并行,那么这种设想就只能在 Cifar, MNIST 这种数据集合上去探索,并不能在 ImageNet 甚至更大规模的数据上去探索,而有些规律是在大规模数据上才会“涌现”出来。
(2)将来,通用的深度学习框架会支持模型并行,并且用户使用模型并行易如探囊取物。
(3)深度学习向更多场景渗透,天然而然。
(4)一旦一项技术被验证,各类深度学习框架都会去拥抱它,支持它,正现在天不少框架在提供 imperative programming 接口同样,同质化是比较严重的,将来须要一些新的空气,新的思路去解决那些悬而未决的问题。
(5)在大数据和人工智能时代,数据积累到了临界点,工业界除了有数据存储,数据筛选这些需求,将广泛须要一个大脑(Brain),做为数据驱动业务的引擎,深度学习框架会像 Hadoop 同样经历一个从“旧时王谢堂前燕,飞入寻常百姓家”的过程。
更多干货内容,可关注AI前线,ID:ai-front,后台回复「AI」、「TF」、「大数据」可得到《AI前线》系列PDF迷你书和技能图谱。