从我开发的深度学习框架看深度学习这几年:TensorFlow, PaddlePaddle(飞桨), 无穷
起源:https://zhuanlan.zhihu.com/p/363271864l
和深度学习框架打交道已有多年工夫。从 Google 的 TensorFlow, 到百度的 PaddlePaddle,再到当初腾讯的无穷。很庆幸在 AI 技术暴发的这些年横跨中美几家公司,站在一个比拟好的视角看着世界产生微小的变动。在这些经验中,视角在一直切换,从最早的算法钻研,到起初的框架开发,到机器学习平台和更多基础架构,每一段都有不同的感触和更深的领悟。
清明节这几天有些工夫写了这篇文章,从我的视角,用几个深度学习框架串起来这些年历史上的一些乏味的插曲,和技术背地的一些故事,省得贵重的记忆随着工夫在脑中淡去。
TensorFlow
入门
故事开始在 2015 年底,我完结了在 Google Core Storage 和 Knowledge Engine 的工作,退出了 Google Brain,在 Samy Bengio 下负责一名 Research Software Engineer,简称 RSWE。RSWE 角色的产生次要是因为 Google Brain 和 DeepMind 发现 Research Scientist 很难在钻研中解决简单的工程问题,并最终技术落地。因而须要卷入一些工程能力比拟强的 Engineer 和 Scientist 一起工作。而我比拟“侥幸”的成为 Google Brain 第一个 RSWE。
退出新组的前一个周末,十分兴奋的提前探访了 Google Brain 的办公地点。想到能近距离在 Jeff Dean 旁边工作还是有些小冲动,毕竟是读着 MapReduce, BigTable, Spanner 那些论文一路成长起来的。办公场合没有特地,Jeff 和大家一样坐在一起,比拟意外的是发现我工位旁几米的办公室门牌上写着谷歌创始人 Larry Page\&Sergey Brin,办公室被许多奖杯,证书,太空服之类的杂物突围着。看来公司对于 AI 技术的器重水平实在十分的高。
言归正传,晚期的 TensorFlow 比拟缺模型示例,相干 API 文档还不太标准,于是先开始给 TensorFlow 搭建模型库。我花了一年工夫把 Speech Recognition, Language Model, Text Summarization, Image Classification, Object Detection, Segmentation, Differential Privacy, Frame Prediction 等模型写了一遍,起初成为 TensorFlow github 上 model zoo 的雏形。那年还是个到处都是高扬果实的时候,没有 GPT3 这种极其烧钱的大模型,只有对模型做一些小的调整,扩充模型的规模,就能刷新 State-Of-The-Art。Bengio 大佬常常在世界各地云游,偶然回来后的 1v1 还是能给我不少的指引。印象粗浅的第一次面聊,这位写过几百篇论文的一字眉大神给刚刚入门的我在白板上手推了 gradient descent 的一些公式。另外一次 1v1,他发给我一本 Ian Goodfellow 写的书(过后还是草稿 pdf),而后我每天晚上就躺在茶水间的沙发上一边做试验一边读书。
那年还产生了件有意思的插曲,AlphaGo 大战人类棋手。DeepMind 和 Brain 有十分严密的单干关系,组里组织了一轮 paper reading,认真研读了相干的 paper,而后大家带上啤酒和零食组织了观战流动,感觉就有点像是在看球赛。那次学会了两件事,强化学习算法,还有围棋的英文是 Go。
16 年是 TensorFlow 高速倒退的一年。Jeff 的演讲里常常有 TensorFlow 代码被援用次数指数级暴涨的图。然而 16 年也是 TensorFlow 被喷的比拟惨的一年。TF 的 Operator 粒度是十分细的,据说这是从外部上一代框架 DistBelif 上汲取的教训。细粒度的 Operator 能够通过组合造成各种高层的 Layer,具备更好的灵活性和扩展性。然而,对于性能和易用性来说却是比较严重的问题,一个模型轻易就有几千个甚至跟多的 Operator。举几个例子:
过后我要实现第一个基于 TensorFlow 的 ResNet,光为了写一个 BatchNormalization(查了好几个外部版本居然都有些问题),须要通过 5~10 个细粒度的算子通过加减乘除的形式组装起,1001 层的 ResNet 有十分多的 BatchNorm,整个 ResNet 有成千上万个 Operator,可想而知,性能也不怎么样。不久后我敌人 Yao 搞了个 Fused BatchNormalization,据说能让整个 ResNet 提速好几成。
BatchNormalization 只是高级难度,做 Speech Recognition 时为了在 Python 层用 TensorFlow 实现 BeamSearch 也花了不少功夫。过后写了个 End-to-End 的模型,用的是 Seq2Seq with Attention,可能一个模型间接把声音转成文字。为了把搜寻生产线上语音辨认的数据训到最初收敛,用 128 个 GPU 整整花了 2 个月的工夫。每天早上下班第一件事就是关上 TensorBoard,放大后能力看到 Loss 又降落了那么一点点。
16 年时 TensorFlow 训练模式次要是基于 Jeff 等几位的 Paper,基于参数服务器的异步训练是支流。训练速度线性扩展性不错,然而明天基于 ring 的同步训练在 NLP,CV 这些畛域的声音更响一些。记得第一次和 Jeff 独自交换是对于 Speech Recognition 分布式训练的试验状况,加到 128 个 GPU 做异步训练根本能保障线性扩大,然而基于 SyncOptimizer 的同步训练速度会慢很多。过后 Jeff 问了下收敛成果有没有收到影响,我懵了一下,说没有仔细分析过,连忙回去查一下。顺便八卦一下,Jeff 真是十分瘦,握手的时候感觉简直就剩皮包骨了。
开发过一些模型后发现算法研究员其实还有不少痛点。1. 不晓得怎么 Profile 模型。2. 不晓得怎么优化性能。为了解决这两个问题,我抽空写了个 tf.profiler。tf.profiler 的原理比较简单,就是把 Graph, RunMeta 和一些其余的产物做一些剖析,而后用户能够通过 CLI,UI 或者 python API 疾速的去分析模型的构造,Parameter, FLOPs, Device Placement, Runtime 等属性。另外还做了个外部数据的抓取工作,去抓算法研究员的训练任务的 metrics,如果发现 GPU 利用率异样,网络通行量过大,数据 IO 慢时会主动发邮件揭示,并给出一些批改的倡议。
让一个分心搞算法钻研的人写一个白板的数学公式不难,然而让他去搞明确简单的工作配置,分布式系统里的性能、资源、带宽问题确是件很艰难的事。无论如许牛的研究员都会问为什么工作没能跑起来,是资源不够还是配置不对。记得有天黄昏,人不多,Geoffrey Hinton 大神忽然走过去问到 Can you do me a favor?My job cannot start…(正当我筹备许可时,Quoc Lee 曾经领先接单了,真是个精力的越南小哥。。。)
Moonshots
Google Brain 每年会组织一次 Moonshots 提案,许多起初比拟胜利的我的项目都是这样孵化进去的,比方 AutoML,Neural Machine Translation 等等。团队成员会提出一些过后技术比拟难达到的我的项目,大家组成相似兴趣小组的模式投入到这些我的项目中。
当初火的一塌糊涂的 AutoML 有点因为商业化或者其余起因,感觉曾经对原始的定义做了极大的拓展。过后 Brain 孵化这个我的项目的时候有两队人在做 LearningToLearn 的我的项目,一个小队心愿通过遗传算法来搜寻更优的模型构造,另一个小队则决定应用强化学习算法搜寻。遗传算法小队在应用资源时比拟审慎,通常只应用几百个 GPU。而另一个小队则应用了几千个 GPU。最初强化学习小队更早的做出了成绩,也就是 Neural Architecuture Search。而另一个小队尽管起初应用更多的 GPU 也达到了相似的成果,然而要晚了不少。
一个比拟乏味的插曲是 Brain 尽管很早就有几万张 GPU,然而每当论文截稿的前一段时间总是不够用,其中搞 NAS 的同学经常在邮件中被暗示。为了解决资源的调配问题,领导们被卷入了一个十分长的 email,起初大略解决方案是每个人会被调配大量的高优先级 GPU 和适量的竞争级 GPU 资源。而 NAS 的同学因为曾经实现了资本的原始积累成为了一个很火的我的项目,失去了特批的独立资源池。为了反对这个策略,我又开发了个小工具,当初回头想想还挺吃力不讨好的。
动态图
疾速成长的工夫总是过得很快,Megan 退出 Brain 后,我被安顿向她汇报,过后的 RSWE 团队曾经有十几人,而 Google Brain 也从几十人变成了几百人。
2017 年初,经 Megan 介绍,TensorFlow 团队一位资深专家 Yuan Yu 找到我,问有没有关注 Pytorch,约我调研后一块聊聊。于是我就去网上收集了一下 Pytorch 的材料,又试用了一下。作为一个 TensorFlow 的深度用户,我的第一反馈就是 Pytorch 解决了 TensorFlow 很大的痛点,用起来十分的“天然”。
和 Yuan 聊完后,咱们疾速的决定在 TensorFlow 上也尝试反对相似 Pytorch 的 imperative programming 用法。Demo 的开发过程还算比较顺利,我大略花了一个多月的工夫。记得过后我把我的项目命名为 iTensorFlow, short for imperative TensorFlow。(起初被改名成 eager,感觉好奇怪)。
Demo 的设计思路其实也不简单:1. TensorFlow graph 能够被切分成任意粒度的 Subgraph,能够通过函数调用的语法间接执行,2. TensorFlow 对用户通明的记录执行过程以用于反向梯度计算。给用户的感觉就就相似 Python native 的运行。
进而产生几个推导:1. 当 Subgraph 的粒度是 operator 时,根本等价于 Pytorch。2. 当 Subgraph 粒度由多个 operator 组成时,保留了 graph-level optimization 的能力,能够编译优化。
最初再埋个伏笔:1. tf.Estimator 能够主动的去交融 Subgraph,造成更大的 Subgraph。用户在开发阶段基于 imperative operator-level Subgraph 能够简略的调试。用户在部署阶段,能够主动交融大的 Subgraph,造成更大的 optimization space。
做完之后,我十分兴奋的和 Yuan 演示成绩。Yuan 也说要帮我在 TensorFlow 外面推这个计划。过后 Pytorch 的成长速度十分的快,TensorFlow 的 Director 也招集了多名专家级的工程师同时进行计划的摸索。过后我还没能进入 TensorFlow 的决策层,最终失去的论断是 1. 让咱们成立一个虚构组专门做这个我的项目。2. 之前的 Demo 全副推倒从新做,TensorFlow 2.0 作为最重要 Feature 公布,默认应用 Imperative Mode (后改名叫 Eager Mode,中文经常叫动态图)。我则作为团队的一员在我的项目中奉献来一些代码。
起初 Brain 来了位新的大神,Chris Lattner,在编程语言和编译畛域钻研的同学预计很多意识他。他提出来心愿用 Swift 来实现 Deep Learning Model 的 Progamming,也就是起初的 Swift for TensorFlow。理由大略是 Python 是个动静的语言,很难动态编译优化。起初我和他深刻探讨来几次,从技术上十分同意他的观点,然而也明确的示意 Swift for TensorFlow 是一条很难走的路。用 Python 并不是因为 Python 语言如许好,而是因为很多人用 Python。和 Chris 的一些交换中我对编译过程中的 IR 和 Pass 有了更深的理解,对起初在 PaddlePaddle 中的一些工作产生了不少的影响。
一个插曲是某位 TensorFlow 团队的资深专家有次轻轻和我说:Python is such a bad language。这句话我品尝了良久,不过和他一样没有勇气大声说进去。。。
过后动态图的我的项目还延展出两个比拟乏味的我的项目。有两个其余团队的哥们想对 Python 做语法分析,进而编译 control flow。我很婉转的示意这个计划做成通用解决方案的可能性不太大,然而这个我的项目仍然被很执着的做了很长一段时间,并且进行了开源,然而这个我的项目也就缓缓死于非命了。另一个很酷的我的项目是齐全用 numpy 来结构一个 deep learning model。通过隐式的 tape 来实现主动的求导,起初我的项目如同逐步演化成来 JAX 我的项目。
API
前面我逐步转到了 TensorFlow 做开发。记得 2017 年还产生了一件印象粗浅的事件,当 TensorFlow 播种海量用户时,网上一篇“TensorFlow Sucks” 火了。尽管那篇文章很多观点我不能苟同,许多想法比拟浮浅。然而,有一点不能否定,TensorFlow API 是比拟让人蛋疼的。1. 同一个性能往往几套反复的 API 反对。2. API 常常变动,而且常常产生不向后兼容的问题。3. API 的易用性不高。
为什么会产生这个问题呢?可能要从 Google 这个公司的工程师文化说起。Google 是十分激励自在翻新和跨团队奉献的。常常会有人给另一个团队奉献代码,并以此作为有影响力的论据参加降职。所以在晚期 TensorFlow 还不是特地欠缺的时候,常常有内部的团队给 TensorFlow 奉献代码,其中就蕴含了 API。另外,在 Google 外部的对立代码仓库下,放出去的 API 是能够很容易的降级批改的,很多时候只须要 grep 和 replace 一下就行。然而 github 上放出去的 API 齐全不一样,Google 的员工不能去批改百度,阿里,腾讯外部的 TensorFlow 应用代码。对此 TensorFlow 团队晚期确实没有十分无效的计划,起初才呈现了 API Committee 对 public API 做对立的把关和布局。
在我做视觉的时候,和 Google 外部一个视觉团队有过很多单干,其中一个是 slim API。这个视觉团队十分的强,当年还拿了 CoCo 的冠军。随着他们模型的推广风行,他们的 tf.slim API 也被广为流传。slim API 的 arg\_scope 应用了 python context manager 的个性。相熟晚期 TensorFlow 的人晓得还有 tf.variable\_scope, tf.name\_scope, tf.op\_name\_scope 等等。with xxx\_scope 一层套一层,简单的时候代码简直没有什么可读性。另外就是各种 global collection,什么 global variable, trainable variable, local variable。这在传统的编程语言课里,全局变量这种货色可能是拿来当反面教材的。然而,算法人员的视角是不一样的,with xxx\_scope 和 global collection 能缩小他们的代码量。尽管咱们晓得正当的程序设计办法也能够做到,然而算法专家预计须要把工夫用来读 paper,不太违心钻研这些程序设计的问题。
记得在晚期外部还有两个流派的争执:面向对象和面向过程的 API 设计。
基于我教育历史的洗脑,感觉这个是不须要争执的问题。Keras 的 Layer class 和 Pytorch 的 Module class 这些面向对象的接口设计无疑是十分优雅的。然而,其实过后确实产生了十分强烈的争执。一些 functional API 的作者认为 functional 的调用十分节俭代码量:一个函数就能够解决的问题,为什么须要先结构一个对象,而后再 call 一下?
在 TensorFlow 动态图能力开发的晚期,咱们也重复探讨了 2.0 外面接口的设计方案。作为炮灰的我又接下了写 Demo 的工作。
闭关两周后,我给出了一个计划:1. 复用 Keras 的 Layer 接口。2. 然而不复用 Keras 的 Network,Topology 等其余更高层的简单接口。
起因次要又两点:1. Layer 是十分简洁优雅的,Layer 能够套 Layer,整个网络就是一个大 Layer。Layer 形象成 construction 和 execution 两阶段也十分天然。2. Keras 有很多历史上为了极简设计的高层接口。我集体教训感觉很难满足用户灵便的需要,并不需要官网提供。而且这样可能会导致 TensorFlow API 层适度简单。
起初计划被驳回了一半,大佬们心愿可能更多的复用 Keras 接口。其实没有完满的 API,只有最适宜某类人群的 API。有个小插曲,过后 Keras 的作者 François 也在 Google Brain。为了在 TensorFlow 2.0 的动态图和动态图同时应用 Keras 的接口,不得不在 Keras API 内做很多革新。通常 François 在 Review 代码时都十分的不愿意,然而最初又往往斗争。很多时候,特地是技术方面,假相可能在多数不被大多数人了解的人手上,须要工夫来发现。
TPU
感觉互联网公司那几年,真正把 AI 芯片做得成熟且宽泛可用的,只有 Google 一家。TPU 始终都是 Google Brain 和 TensorFlow 团队关注的重点。起因可能是 Jeff 老是提起这件事,甚至一度在 TensorFlow 搞 GPU 优化是件很没前途的事件。
TPU 有个比拟特地的中央,在于 bfloat16 的类型。现在 bfloat16,还有英伟达最新 GPU 上的 TF32 都曾经被广为理解了。在过后还是个不小的翻新。
bfloat16 的原理非常简单,就是把 float32 的后 16bit 全副截掉。和 IEEE 的 float16 相比,bfloat16 的 mantissa bits 会少一些,然而 exponential bits 会多一些。保留更多的 exponential bits 有利于 gradients 很靠近 0 时不会隐没,保障 bfloat16 训练时可能更好的保留模型的成果。而传统基于 float16 训练时,往往须要做 loss scaling 等调试能力达到相似的成果。因而 bfloat16 能让 AI 芯片更快的运算,同时又确保收敛成果通常不会有损失。
为了在 TensorFlow 上全面的反对 bfloat16,我过后花了不少的功夫。尽管之前有基于 bfloat16 通信的计划,然而要在所有中央都无缝买通 bfloat16,还有十分多的工作要做。比方 eigen 和 numpy 都不反对 bfloat16 这种非凡的货色。幸好他们都能够扩大数据类型(就是文档太少了)。而后还要修复成千盈百个 fail 掉的 unit tests 来证实 bfloat16 能够在 python 层齐备的应用。
TPU 是一个十分高难度,跨团队,跨技术栈的简单工程。据说 Google 有位十分优良的工程师,为了在 TPU 上反对 depthwise convolution 一个 TPU kernel,花掉了半年的工夫。
其实这一点也不夸大,除了底层的硬件设计,单是将 tensorflow graph 编译成硬件 binary 的 XLA 我的项目晚期就至多卷入几十人。从 HLO 到底层的 target-specific code generation,简直又重写了一遍 TensorFlow C++ 层,远比之前的解释型执行器简单。
TPU 的训练在底层跑通后,我基于底层接口的根底上实现 Python 层的撑持 API,而后去实现几个模型。过后碰到了好几个难题,有些在几周工夫内解决了,有些继续到我不再团队后好些年。这里举几个例子。
- 过后一个 TPU Pod(如同是 512 个 chips)算得太快了,即便是很简单模型的计算也会卡在数据的 IO 和预处理上。起初搞了个分布式的 data processing,通过多个 CPU 机器来同时去解决数据,能力喂饱 TPU。
- 早器的 TPU API 易用性比拟弱。通常一个 model 须要在 TPU 上 train 几百步而后再返回 python 层,否则 TPU 的性能会飞快的进化。这对于算法人员是很不敌对的,这意味着 debug 能力的缺失,以及大量简单模型无奈实现。记得当年 OKR 被迫升高为反对常见的 CV 模型。
- TPU 如何反对动态图。记得我过后迫于 TPU 的束缚,做了个所谓的 JIT 的能力。就是 Estimator 先在 CPU 或者 GPU 上迭代 N 步,实现模型的初步调试,而后再主动的 deploy 到 TPU 上。从算法人员角度,既满足单步调试的能力,又能在次要 training 过程用上 TPU。
团队
Google Brain 是个很神奇的团队,比拟不客气的说,在 2015 年后的几年间包揽了全世界在深度学习畛域一半以上的关键技术冲破,比方 TPU,TensorFlow, Transformer, BERT, Neural Machine Translation, Inception, Neural Architecture Search, GAN,Adverserial Training, Bidrectional RNN 等等。这里不只有深度学习畛域的图灵奖获得者,还有编程语言、编译器、计算机体系结构、分布式系统的顶级专家,甚至还有生物,物理学专家。Jeff 将这些人放在一起后,产生了神奇的化学反应,放慢了技术扭转世界的步调。
PaddlePaddle 飞桨
Paddle 其实诞生工夫比拟早,据说是大概 13~14 年的时候徐伟老师的作品。起初据说 Andrew Ng 感觉 Paddle 叫一次不过瘾,就改名成了 PaddlePaddle。Paddle 和那个年代的框架 Caffe 有相似的问题,灵活性不够。很多中央用 C ++ 写成比拟粗粒度的 Layer,无奈通过 Python 等简略的编程语言实现模型的疾速结构。
起初 17 年下半年,团队开始齐全从新写一个框架,然而继承了 Paddle 的名字。2017 年底的时候,Paddle 国内的团队找到了我,邀请我负责 Paddle 国内研发团队的负责人。抱着打造国产第一框架的现实,我承受了邀请,一个月后就在北京入职了。
晚期设计
退出团队的时候,新的 Paddle 还是一个比拟晚期的原型零碎,外面有一些设计曾经被开发了进去。我发现其中有些设计理念和 TensorFlow 有显著的差别,然而实现的时候却又模拟了 TensorFlow。
仿编程语言
设计者心愿设计一种编程语言来实现深度学习模型的构建(有点相似 Julia 等把深度学习模型的个性嵌入到了编程语言中)。然而在实现上,我发现其实和 TensorFlow 比拟相似。都是通过 Python 去申明一个动态模型构造,而后把模型构造交给执行器进行解释执行。并没有创造一种新的深度学习编程语言。
这块我根本没有对设计进行调整。实质上和 TensorFlow 晚期动态图的没有区别。然而在细节上,TF 基于 Graph 的模型能够通过 feed/fetch 选择性的执行任意一部分子图,更加灵便。Paddle 中与 Graph 对应的是 Program。Program 就像失常程序一样,只能从头到尾残缺的执行,无奈选择性的执行。因而 Paddle 在这块绝对简化了一些,然而能够通过在 Python 层结构多个 Program 的形式补全这部分灵活性的缺失,总体来说表达能力是足够的。
Transpiler
Transpiler 是对 Program 进行间接改写,进而能够让模型可能被分布式运行,或者进行优化。初衷是比拟好的,能够升高算法人员的应用难度。然而在实现上,最开始是在 Python 层间接对 Program 构造进行改写。起初我从新设计了 IR+Pass 的 Compiler 体系,通过一种更系统性的形式做了实现。
LoDTensor
可能是因为团队的 NLP 和搜寻背景比拟强,对于变长序列的器重水平很高。Paddle 的底层数据是 LoDTensor,而不是相似其余框架 Tensor。LoDTensor 相当于把变长序列信息耦合进了 Tensor 外面。这可能导致比拟多的问题,比方很多 Operator 是齐全序列无关的,根本无法解决序列信息在输出 Tensor 和输入 Tensor 的关系,进而比拟随机的解决,给框架的健壮性埋下隐患。尽管我始终想推动序列信息和 Tensor 的解耦合,然而因为种种原因,没有彻底的实现这个重构的指标,心愿前面能改掉。
性能
18 年初的时候,Paddle 还是个原型零碎。因为 OKR 指标,团队曾经开始初步接入一些业务场景。其实一个比拟大的痛点就是性能太差。单机单卡速度十分慢,单机 4 卡减速比只有 1.x。然而性能问题的定位却十分艰难。我花了些工夫写了些 profile 的工具,比方 timeline。一些显著的性能问题能够被疾速的定位进去并修复。
然而单机多卡的速度还是十分慢,timeline 剖析后发现其中有个 ParallelOp,存在大量的 Barrier。最初改写成了 ParallelExecutor,把 Program 复制了 N 份部署在多张卡上,在其中插入 AllReduce 通信算子,而后这 N 倍的算子基于图依赖关系,一直把 ready 的算子扔进线程池执行。即便这样,咱们也发现在多卡的性能上,不同模型须要应用不同的线程调度策略来达到最优。很难有一种完满的 one-fits-all 的计划。前面咱们再聊如何通过 IR+Pass 的办法插件化的反对不同的算子调度策略。
分布式的训练也碰到不少的问题。一开始应用 grpc,花了挺大的功夫做并行申请,而后又切成了 brpc,在 RDMA 等方面做了不少的优化。分布式训练的性能逐渐失去了晋升。另外为了做到自动化分布式部署,后面提到的 Transpiler 随着场景的减少,Python 代码也变得越来越简单。
模型推理在公司内碰到来十分强劲的对手。Anakin 的 GPU 推理速度确实很快,让我吃惊的是他们居然是用 SASS 汇编实现大量根底算子的开发,针对 Pascal 架构做了异样极致的优化,甚至在某些场景远超 TensorRT。我始终主张训练和推理要尽量用一样的框架,并不需要一个独自的推理框架来解决性能问题。应用不同的框架做推理会造成很多意外的精度问题和人工开销。
因为推理性能的问题,咱们和兄弟团队产生来旷日持久比赛,作为狗头军事,我充分发挥来团队在 CPU 这块的技术积攒、以及和 Intel 外援的良好关系,在 CPU 推理场景经常稍逊一筹。在 GPU 方面苦于对手无底线应用汇编,和我方阵线太多、人员不够,只能战略性放弃了局部头部模型,通过反对子图扩大 TensorRT 引擎的形式,利用 Nvidia 的技术劣势在许多个通用场景下开展防御。当初回想起来实在一段乏味的经验。
Imtermediate Representation\&Pass
Imtermediate Representation+Pass 的模式次要是从 LLVM 的架构上借鉴来的。在编译器上次要是用来解决把 M 个编程语言中任意一个编译到 N 个硬件设施中任意一个执行的问题。简略的解决方案是为每个编程语言和硬件独自写一个编译器。这须要 M * N 个编译器。显然这对于简单的编译器开发来说,是十分高老本的。
Intermediate Representation 是架构设计中形象能力的典型体现。不同编程语言的档次不一样,或者仅仅是单纯的反对的性能有些差别。然而,这些编程语言终归须要在某种硬件指令集上执行。所以在编译的过程中,他们会在某个抽象层次上造成共性的表白。而 IR+Pass 的办法很好的利用了这一点。其根本思维是通过多层 Pass (编译改写过程),逐步的把不同语言的表达方式在某个档次上改写成对立的 IR 的表达方式。在这个过程中,表达方式逐步靠近底层的硬件。而 IR 和 Pass 能够很好的被复用,极大的升高了研发的老本。
深度学习框架也有着十分相似的需要。
- 用户心愿通过高层语言形容模型的执行逻辑,甚至是仅仅申明模型的构造,而不去关怀模型如何在硬件上实现训练或者推理。
- 深度学习框架须要解决模型在多种硬件上高效执行的问题,其中包含协同多个 CPU、GPU、甚至大规模分布式集群进行工作的问题。也包含优化内存、显存开销、进步执行速度的问题。
更具体的。前文说到须要可能主动的将用户申明的模型 Program 主动的在多张显卡上并行计算、须要将 Program 拆分到多个机器上进行分布式计算、还须要批改执行图来进行算子交融和显存优化。
Paddle 在一开始零散的发展了下面形容的工作,在分布式、多卡并行、推理减速、甚至是模型的压缩量化上各自进行模型的改写。这个过程非常容易产生重复性的工作,也很难对立设计模式,让团队不同的研发疾速了解这些代码。
意思到这些问题后,我写了一个 Single Static Assignment(SSA)的 Graph,而后把 Program 通过第一个根底 Pass 改写成了 SSA Graph。而后又写了第二个 Pass 把 SSA Graph 改写成了能够多卡并行的 SSA Graph。
前面的事件就应该能够以此类推了。比方推理减速能够在这个根底上实现 OpFusionPass, InferenceMemoryOptimizationPass, PruningPass 等等,进而达到执行时推理减速的目标。分布式训练时则能够有 DistributedTransPass。量化压缩则能够有 ConvertToInt8Pass 等等。这一套货色根本解决了下层 Program 申明到底层执行器的 Compiler 问题。
这个过程中确实碰到了不少的阻力。比方分布式晚期通过 Python 实现了这个逻辑,须要迁徙到 C ++ 层。压缩量化的研发更喜爱写 Python,而 IR\&Pass 是基于 C ++ 的。不同 Pass 间程序依赖和 Debug 等。
全套深度学习框架工具
TensorFlow Everywhere 本来是 TensorFlow 团队时的一个口号,意思是 TensorFlow 须要反对深度学习模型在任意的场景下运行,进而达到 AI Everywhere 的指标。能够说深度学习框架心愿成为 AI 的“操作系统”,就像鱼离不开水、App 离不开 iOS/Android 一样。
Paddle 作为全面对标 TensorFlow 的国产深度学习框架,天然也心愿提供全套的解决方案。在晚期的时候,Paddle 和公司其余团队单干了 PaddleMobile,提供了挪动端的推理能力。起初又发展了 Paddle.js,反对在 H5、Web 等场景的推理能力。为了在 toB,在 Linux 的根底上又新增了 Windows 的反对。为了反对无人车等设施、又反对了在更多不同设施上运行。
举个 PaddleMobile 的例子。深度学习框架想再挪动设施上部署面临这比拟多的挑战。手机的空间和算力都比服务器小很多,而模型最开始在服务器训练好后体积绝对较大,须要从很多角度下手。1. 应用较小的模型构造。2. 通过量化,压缩等伎俩削减模型体积。
另外挪动段深度学习框架是通常基于 ARM CPU,GPU 则有 Mali GPU, adreno GPU 等等。为了最求比拟极致性能,经常须要应用汇编语言。有个同学写到前面简直狐疑人生,感觉本人大学学的货色不太对。为了不显著减少 APP 的体积,框架编译后的体积须要在 KB~几 MB 的级别,因而须要基于部署的模型构造自身用到的算子进行选择性编译。极其的时候甚至须要是通过 C ++ Code Gen 的办法间接生成前向计算必须的代码,而不是通过一个通用的解释器。
回顾
随着我的项目的复杂化,很多辣手的问题逐步从深度学习的畛域技术问题转变成了软件工程开发和团队治理分工的问题。随着团队的一直变动,本人有时候是作为一个 leader 的角色在解决问题,有的时候又是以一个 independent contributor 的角色在参加探讨。很庆幸本人经验过这么一段,有些问题在亲身经历后能力想得明确,想得开。时代有时候会把你推向风口浪尖,让你带船队扬帆起航,在更多的时候是在一直的斗争与摸索中寻找后退的方向。
无穷
无穷是腾讯 PCG 建设的一个深度学习框架,次要心愿解决大规模举荐场景下的训练和推理问题。深度学习在举荐场景的利用和 CV、NLP、语音有些不一样。
- 业务会继续的产生用户的行为数据。当用户规模达到数千万或者上亿时就会产生海量的训练数据,比方用户的画像,用户的点击,点赞,转发行为,还有 Context 等等。
- 这些数据是高度稠密的,通常会编码成 ID 类的特色进而通过 Embedding 的形式进入模型训练。随着业务规模的晋升和特色工程日渐简单,比方累计用户数,商品,内容减少,以及特色穿插的应用,Embedding 参数的体积能够达到 GB,甚至 TB 级。
- 举荐场景是实时动态变化的,新用户,内容,热点一直产生。用户的趣味,用意逐步变动,因而模型须要继续一直的适应这些变动,时刻放弃最好的状态。
调整
19 年中这个我的项目时大略有 2~3 人。团队心愿开发一个新的版本,基于 TensorFlow 进行扩大增强,使得无穷能够复用 TensorFlow 已有的能力,并且可能反对举荐场景下的非凡需要。无穷一开始采纳的是基于参数服务器的架构。TensorFlow 被复用来提供 Python API 以及实现根底算子的执行。而参数服务器,分布式通信等方面则是本人开发,没有复用 TensorFlow。
这个抉择在团队过后的状况下是比拟正当的。如果抉择另一种方向,基于 TensorFlow 底层进行革新,研发难度会比拟大,而且很可能与社区版 TensorFlow 走向不同的方向,进而导致 TensorFlow 版本难以降级。而把 TensorFlow 作为一个本地执行的 lib 则能够在外围开发,不须要理解 TensorFlow 外部的简单逻辑,也能够复用一些其余开源组件,比方 pslib。
晚期在软件开发的流程上绝对比拟欠缺。为了保障工程的推动,我先帮忙做了些根底工作,比方加上了第一个自动化测试和继续集成,对一些适度封装和奇怪的代码做了重构和简化。
另外,在接口层也做了一些调整。原来框架开始执行后就进入 C ++ 执行器,无奈从 python 层提供或者返回任何执行后果,也无奈在 python 层执行逻辑进行插件化的扩大。为了满足预期用户未来须要进行调试的需要,我模仿 tf.Session 和 tf.Estimator 对执行层的接口做了重构。这样用户能够通过 feed/fetch 的形式单步调试执行的过程。也能够通过 Hook 的形式在执行前后扩大任意的逻辑,进步框架的实用场景。
另外一个问题是 python 层根本齐全是全局变量,很难进行多模型的封装。像 TensorFlow 有 Graph 实例或者 Paddle 有 Program 实例。因为 python 层须要重构的量比拟大,我临时先退出了 Context 的封装,勉强将各种状态和配置封装在了 Context 下。思考到短期可能不会有更简单的需要,临时没有把这件事做完。
reader 那块也做了一些重构。最开始那块的线程模型异样简单,一部分是因为分布式文件系统等基础设施无奈提供比拟好的 SDK,导致许多逻辑不得不在深度学习框架外面,比方文件的本地缓存。思考到特色加工的逻辑比较复杂,以及一些老的 TensorFlow 用户可能习惯于 tf.Example 和 tf.feature\_column 等根底算子库,我在 reader 层引入了基于 TensorFlow 的 tf.dataset。不过起初发现用户仿佛更关怀性能问题,喜爱自定义 C ++ lib 的形式来解决特色解决的问题。
API 设计是个老大难的问题。TensorFlow,Paddle,无穷都没能幸免。在一个多人协同的团队里,每个研发更多还是关注每个独立性能是否实现开发,而性能的接口往往须要思考到整体的 API 设计格调,易用性,兼容性等许多因素,经常在高速迭代的过程中被疏忽掉。可怜的是 API 经常不能像外部实现一样前期优化。当 API 被放给用户应用后,后续的批改往往会毁坏用户代码的正确性。很多时候只能本人评审一下。
降级
无穷通过一年根底能力的打磨,逐步的成为来整个事业群对立的大规模举荐模型训练和推理框架,撑持数十个业务场景,每天都能生产数千个增量和全量的模型。简略的实现性能曾经不能满足业务和团队倒退的需要,须要在技术上更加前沿。
数据处理
数据格式上要从原来的明文转到更高效的二进制。另外基于 CSR 编码的稠密数据能够进一步的缩小数据处理时的拷贝等额定开销。
流水线
尽量开掘训练中能够并行的中央,通过流水线的形式进步并发度,进而进步训练的速度。比方在数据读取的过程中,就能够提前依照参数服务器的规模对数据进行预切分,并告知参数服务器须要提前准备哪些参数。这样当 pull/push 的时候可能更快的实现计算,进而进步每个 minibatch 的速度。
同样,当应用 GPU 训练时,也能够在数据 IO 的并行过程中,预计算将来须要用的的 Embedding 参数。这样 GPU 训练下一轮的数据时,须要用到的 Embedding 曾经提前被计算好,能够间接开始训练,缩小来期待的工夫。
定制化参数服务器
因为无穷解决的一个关键问题是举荐模型的海量参数问题,因而参数服务器必须是高度优化过的。并且应该正当的将举荐模型的畛域常识引入到设计中,通过非凡的策略进一步产生差异化的劣势。
定制化的线程模型,内存治理和 HashMap。因为参数是被切分归属到不同线程上,所以能够通过无锁化的把每次 pull/push 的参数解决好。另外因为海量参数耗费较大硬件老本,内存空间都须要通过定制化的内存池来治理。否则很可能有大量的空间碎片在默认内存库中无奈及时归还给操作系统。另外也有无奈精细化管制内存清理机制,导致内存 OOM 或者节约。定制化的内存治理能够解决这些问题,甚至通过非凡的内存淘汰策略,在不侵害模型成果的根底上进一步升高内存的开销。高性能 HashMap 则是须要解决 Embedding 疾速的增删改查的问题。
Embedding 向量的治理也是有十分多能够改良的中央。1. 动静的扭转 Embeding 向量的长度来反对模型的压缩,进步模型成果。2. 扩大 Embedding 的元数据来记录热度,点击展示等统计值,有助于进步训练推理时高级分布式架构的 Cache 命中率,曾经模型的训练成果。3. 模型的复原和导出机制在大规模 Embedding 场景对于 Serving 时可能实时加载模型更新重要。另外还须要思考到工作失败重启后资源伸缩等问题。
GPU 训练
传统 PS 架构的训练模式下,因为单台机器的计算能力无限,须要几十甚至上百个实例进行分布式训练。然而这样会导致大量的计算被用在来有效的开销上。比方稠密特色在网络通信两边的解决。这种额定开销甚至常常超过无效计算。
GPU 和相应的高速网络链接能够解决这一问题。单台 8 卡机器通过 NVLink 连接起来,速度甚至能够超过几十台物理机,有更高的性价比。然而因为几百 GB,甚至 TB 级的参数问题,还有 Embedding 的 GPU 计算问题,导致 GPU 始终都没有被宽泛的用起来。
然而试验发现其实稠密特色存在显著的 Power-law 散布,少部分 Hot 特色应用远多于其余大量不 Hot 的特色。因而,通过在数据处理时统计特色,而后批量将未来新须要的 Embedding 换入 GPU,就能够让 GPU 长时间的进行间断训练,而不须要频繁的和 CPU 内存替换参数。
GPU 预测
随着举荐模型复杂度进步,引入传统 CV,NLP 的一些构造须要耗费更多的计算。CPU 往往很难在无效的时间延迟下(几十毫秒)实现大量候(几百上千)全集在简单举荐模型的推理。而 GPU 则成为了一个潜在的解决方案。
同样,GPU 推理也须要解决显存远小于 Embedding 参数的问题。通过在训练时事后计算 Hot Embedding,而后加载如推理 GPU,能够肯定水平的缓解这个问题。在推理时仅有少部分的 Embedding 没有在 GPU 显存中缓存,须要通过 CPU 内存拷贝进入 GPU。
而通过模型的量化和压缩能进一步缩小 Embedding 参数的规模。试验表明当大部分 Embedding 参数的值管制为 0 时,模型仍然可能体现出原来的成果,甚至略优。
总结
深度学习算法的倒退和深度学习框架的倒退是相辅相成,互相促进的。从 2002 年时 Torch 论文发表后,框架的技术倒退绝对迟缓,性能无奈显著晋升导致无奈摸索更加简单的算法模型,或者利用更加大规模的数据集。
在 2010 年后逐步呈现了 Caffe, Theano 等框架,通过将更高性能的 GPU 引入,能够训练更加简单的 CNN 和 RNN 模型,深度学习算法的倒退呈现来显著的减速。
到了 2014\~2017 年几年间,TensorFlow 的呈现让用户能够通过简略的 Python 语言将细粒度的算子组装各种模型构造。并且模型能够简略的被分布式训练,而后自动化部署在服务器,手机,摄像头等各种设施上。而 Pytorch 的动态图用法满足了钻研人员对易用性和灵活性更高的要求,进一步推动算法钻研。
国内的深度学习框架技术在这股浪潮中也紧跟这世界的步调。Paddle 在 14 年左右产生,在国内积攒了肯定的用户,在过后根本能比肩其余的框架。尽管在 TensorFlow 和 Pytorch 等更先进的框架呈现后,国内错过了贵重的几年技术升级的窗口以及社区生态培养机会,然而咱们看到从 18 年到 20 年间,新版的 PaddlePaddle,OneFlow, MindSpore 等深度学习框架陆续开源,技术上逐步赶了上来。
举荐场景在电商,视频,资讯等泛滥头部互联网公司的火爆导致举荐系统对 AI 硬件的耗费在互联网公司超过了传统 NLP,CV,语音等利用的总和。许多公司开始针对举荐场景(以及广告,搜寻)的非凡需要对深度学习框架进行定制优化。百度的 abacus 是比拟晚期的框架,和其余晚期框架一样,在易用性和灵活性上较弱。无穷,XDL 等框架则进行了改良,兼顾了社区兼容性,算法易用性和零碎的性能等纬度。
深度学习的框架的触角其实远不止咱们常见到的。随着 AI 技术的推广,Web、H5、嵌入式设施、手机等场景下都有许多优良的深度学习框架产生,如 PaddleMobile, TFLite,tensorflow.js 等等。
深度学习框架的技术也逐步从更多纬度开始拓展。ONNX 被提出来作为对立的模型格局,尽管离指标有很长的间隔和问题须要解决。然而从它的风行咱们能看到社区对于框架间互通的渴望。随着摩尔定律难以维持,框架开始更多的从新的硬件和异构计算畛域寻求冲破。为了反对海量的算子在 CPU、FPGA、GPU、TPU、NPU、Cerebras 等泛滥 AI 芯片上运行,TVM、XLA 等借鉴编译技术几十年来的积攒,在更加艰巨的路线上进行来继续的摸索,常常能传来新进展的好消息。深度学习框架也不再仅利用于深度学习,还在科学计算,物理化学等畛域发光发热。
【我的项目举荐】
面向小白的顶会论文外围代码库:https://github.com/xmu-xiaoma666/External-Attention-pytorch
面向小白的 YOLO 指标检测库:https://github.com/iscyy/yoloair
面向小白的顶刊顶会的论文解析:https://github.com/xmu-xiaoma666/FightingCV-Paper-Reading
“点个在看,月薪十万!”
“学会点赞,身价千万!”
【技术交换】
已建设深度学习公众号——FightingCV,关注于最新论文解读、基础知识坚固、学术科研交换,欢送大家关注!!!
请关注 FightingCV 公众号,并后盾回复 ECCV2022 即可取得 ECCV 中稿论文汇总列表。
举荐退出 FightingCV交换群 ,每日会发送论文解析、算法和代码的干货分享,进行学术交流,加群请增加小助手 wx:FightngCV666,备注: 地区 - 学校(公司)- 名称
【赠书流动】
为感激各位老粉和新粉的反对,FightingCV 公众号 将在 10 月 1 日包邮送出 4 本 《智能数据分析:入门、实战与平台构建》 来帮忙大家学习,赠书对象为当日浏览榜和分享榜前两名。想要参加赠书流动的敌人,请增加小助手微信FightngCV666(备注“城市 - 方向 -ID”),不便分割取得邮寄地址。
本文由 mdnice 多平台公布