支付宝研究员王益:Go+可有效补全Python的不足


王益,蚂蚁集团研究员,开源项目SQLFlow 和 ElasticDL 的负责人。他从10岁开始写代码。曾经用本身焊接的电路板扩展“中华学习机”来把自家的老式“威力牌”双筒洗衣机改形成了自动洗衣机;用Apple BASIC语言和6502汇编混合编程写了人生中第一个游戏;高中自学了大学全部计算机课程,参加计算机水平测试,前后得到了“程序员”、“高级程序员”、“系统分析员”认证。王益从事 AI 基础架构工做十三年,前后在全球多家顶级互联网公司任职,亦曾在硅谷和北京两地创业。前端

 

不久前许式伟(江湖人称老许)的 Go+ 项目在 Hacker News 上掀起了一阵风潮[1]。我一见钟情,参与贡献。最近老许和社区组织了一个视频交流,拉我跟你们说说为啥关注 Go+ 以及图个啥。在直播交流后,根据弹幕反馈,以及两位好友——洪明胜(TensorFlow Runtime 负责人)以及王玉(沈雕墨)的建议,作了修改。git

 

我作分布式深度学习系统十三年了,尤为是 2016 年徐伟老师让我接替他做为他原创的 PaddlePaddle 项目的负责人以后,在工业系统中对 Python 的亲身体会让我对其局限了解愈深。而 Go+ 是我见过的弥补方案里最靠谱的。程序员

 

我期待 Go+ 对标 Python,补全 Python 的不足,而且在此基础上有一个相似 numpy 的项目(姑且称之为 numgo+ 吧)用来支持张量(tensor)运算,知足数据科学的需求;在 numgo+ 之上再构建一个相似 PyTorch 的深度学习基础库(姑且称之为 GoTorch 吧)。若是能够,进一步成为深度学习编译器生态的一种前端语言。github

 

我如今在蚂蚁集团工做,负责一个开源 SQL 编译器 SQLFlow —— 把扩展语法以支持 AI 的 SQL 程序翻译成 Python 程序。同事们说,若是 Go+ 这套生态能成熟起来,很乐意让 SQLFlow 输出 Go+ 程序。算法

 

不少读者估计以为我瞎说八道—— Python 如此如日中天通常火热的语言,何必“补足”?编程


Python 的优点设计模式

Python 的语法很灵活,融合了其余不少语言使人以为方便的特色。好比,和 C++ 同样, Python 容许重载操做符,numpy 的做者因而重载了算数操做符来作张量运算。和 Lisp 同样,Python 的 eval 函数递归地实现了 Python 解释器,能够解释执行 Python 表达式,因此 Python 程序能够生成本身。性能优化

 

这样的灵活性容许程序员为所欲为,所以特别适合探索性工做。好比研究生们用  Python 作科研;数据科学家们用来替代以前各类昂贵的商业化系统;在随后诞生的深度学习领域,Python 也迅速蓬勃。服务器

 

Python 的劣势微信

Python 的优点同时也隐含了其劣势。我亲身感觉的痛点有二。

 

难以保证代码质量

语法灵活的另外一种说法是:一个程序有多重写法。现代软件工程里没有孤胆英雄,全靠你们合做。多种可能的写法每每意味着团队容易在 code review 时吵架——并且难以平息,由于不必定有客观选择标准。不少其余语言也有相似问题,好比 Java。解法是,社区里定一些设计模式(design patterns),程序员写程序前先看看有没有能够套用的设计模式,若是有,则遵循之。因此 Java 程序员除了学习 Java 语法,还要学习设计模式。C++ 也有相似的问题。解法之一是 Google 定了一套 code style——哪些语法能够用,哪些不准用——按照 Rob Pike 的解释,容许用的部分语法挑出来,就是 Go 的设计初衷。Python 太灵活,以致于 code style 都无法定义得和 C++ 的同样细致—— PEP8 几乎只是说说排版要求,对语法的选用几乎没有限制。Python 也无法定义模式——太多了,写不完。
 
Python 为了灵活采用动态类型,因此咱们看一个 Python 函数,必须得细读其代码,不然都不知道它有没有返回值,以及返回值是啥。Python 也有语法扩展,要求编程者指明输入输出的数据类型,不过用的人很少——毕竟你们都是冲着“灵活”来的;要是限制灵活性,那就真不如用静态类型语言了。这个结果是,每一个 Python 函数都不能太长,不然看不明白了。但是 Python 程序员就是冲着灵活性来的,要的就是信马由缰的感受,管你懂不懂呢,我本身明白就行,反正发完论文就毕业了。拆分函数细化粒度?不可能的,这辈子都不可能的。
 
有没有写的很好的 Python 代码呢?有的。好比 Google Tangent。这是一个很小众的项目。做者也只有两个。其代码结构清晰——每一个函数基本都在十行代码以内,代码和注释同样长,因此很好懂。不过这也和 Python 用户众多的印象相悖了。我在负责 PaddlePaddle 项目的时候,除了本身努力学习和总结  Python 的模式,也配置 CI 调用各类工具作源码检查,然并卵,这些工具没有智能化到能够自动注释代码,也不会自动拆分太长的函数定义。
 

难以优化计算效率

Python 的语法丰富、灵活性强,因此解释器写起来很复杂,要优化性能也很难。相比之下,Go 语言语法简洁,表达能力远胜于 C 可是 keyword 总数少于 C,这种简洁使得 Go 程序的性能优化比较容易。在 Go 诞生后几年,Go 编译器对代码的性能优化水平就快速接近 GCC 对 C++ 程序的优化水平了,而 C++ 和 Python 同样,语法丰富,因此编译器里的代码性能优化功能很不容易开发。
 
有人尝试写 Python 的编译器来代替解释器,从而在程序执行以前先作性能优化。可是 Python 语法比 C++ 更灵活,以致于几乎无法写一个彻底支持 Python 标准语法的编译器出来。几个尝试所以做罢。目前的广泛的作法是解释器来作执行时优化(JIT compilation),由于有 runtime 信息,因此相对编译器更容易一些。
 
在 AI 领域,深度学习训练很是消耗计算资源。TensorFlow 的图模式的解法是:用户写的  Python 程序在执行时并不真的作训练,而是把训练过程输出成一个被称为”计算图“的数据结构,交给 TenosrFlow runtime 这个“解释器”来执行。只要保证 TensorFlow runtime 的执行效率,便可不受 Python 解释器效率的限制。
 
TensorFlow 图模式用心良苦,也多此一举——源程序、各层 IR、以及 binary code 是一直以来人们用来描述计算过程的表达方式,TensorFlow 项目早年间发明的计算图重复造了个轮子,并且造得不专业——图难以表达 if-else、循环、函数定义和调用,更别提 closure、coroutine 和 threading 这样的高级控制流结构了。人工智能工程师的非专业编译器设计让 LLVM 的做者 Chris Lattener 掩面而笑,因而他尝试用 Swift for TensorFlow 替换 Python 做为前端语言,用 MLIR 代替 TensorFlow 中的“计算图” [2]

补全局限的尝试

我在负责 PaddlePaddle 期间为了验证  Paddle Fluid  的能力,和个人同事陈曦一块儿作了一个无人驾驶船,尝试用 Fluid 写 immitation learning 方法,让船能学习人类驾驶员的驾驶技术,详情请见系列博客 [3] 。但是若是咱们把跑 Python 程序的 MacBook Pro 带上船则太费电,而嵌入式的设备上又不适合跑 Python 写的训练程序。若是每次停船后上传数据到服务器训练,那么船向人学习迭代的进度就太慢了。

 
为此,当时另外一位同事杨杨写了 Paddle Tape,用 C++ 实现了 PyTorch 的自动求导能力,结合 Paddle Fluid 积累的众多用 C++ 写的基本计算单元(operators),Tape 彻底是一个 C++ 实现的深度学习系统系统,和 Python 没啥关系了。
 
2019 年初,个人朋友洪明胜在 Google 负责 Swift for TensorFlow 项目,这也是一个 AI 基础架构去 Python 化的尝试。他当时拉我给 Chris Lattener 的团队分享了 Paddle Tape 和无人船的故事,并修改了个人幻灯片 [4] ,记录了讲稿 [5]
 
我在蚂蚁集团负责的一个开源分布式深度学习训练系统 ElasticDL,尝试过调用 TensorFlow graph mode、eager execution mode、PyTorch、和 Swift for TensorFlow,很受 Swift for TensorFlow 的设计理念以及和 Python 生态共荣的策略的启发。
 

Go+ 和数据科学

以上尝试提醒我,语言的选择标准必须包括:语法清晰简练和语法稳定容易学习。也但愿语言的使用者是比较有探索精神的一个群体。Go+ 及其基于 Go 社区的用户群体恰好符合这些条件。
 
在 Go+ 出现以前,也有把 Go 用于数据科学的尝试,也有用 Go 实现的张量运算库(好比 gonum),可是用起来都不如用 numpy 的 Python 程序简练,很直接的一个缘由是 Go 的常量须要指定数据类型,而 Python 的则不用。我写了几个对比 [6]
 
用 Go 定义一个 ndarray 类型的常量,用户须要写:
x :=numgo.NdArray(  [][]float64{ {1.0, 2.0, 3.0}, {1.0, 2.0, 3.0}})

而用 Python 是:
x = numpy.ndarray( [[1.0,2.0, 3.0], [1.0,2.0, 3.0]])
 
有了 Go+ 来自动推导数据类型,写法就和 Python 几乎同样了:
x :=numgo.NdArray( [[1.0, 2.0, 3.0], [1.0,2.0, 3.0]])
 
更进一步,老许加的一个 comment 解释 Go+ 准备支持  MATLAB  的张量定义语法。这样一来,这个程序就更简单了:
x :=numgo.NdArray( [1.0, 2.0, 3.0; 1.0, 2.0, 3.0])
 
相似的便捷的语法改进在 Go+ 已经积累了很多,我记录了一些例子 [7] 。这些语法扩展足以极大简化数据科学编程。
 
而 Go+ compiler 负责把利用这些语法糖写做的 Go+ 程序翻译成 Go 程序。这样能够和其余 Go 语言写的库一块儿编译,从而复用 Go 生态里的代码。
 
复用 Go 生态是 Go+ 语言的一个长项。在 Go 的发展过程当中,已经积累了很多科学计算的基础技术,好比实现张量的 Go 数据类型的封装。这些数据类型的计算也有高效的 Go 实现,部分缘于 Go 程序能够方便地调用 C/C++ 程序,包括科学计算领域里久经考验的基础库如 LAPACK,甚至 NVIDIA GPU 的接口库 CUDA。值得注意的是,这些基于 C/C++ 的基础库也是 Python 的数据科学生态的基础,因此本文的标题是 Go+ 补全 Python 生态。
 

Go+ 和深度学习编译器

上文提到了深度学习技术。这是 Python 被普遍使用的另外一个领域,和数据科学有天然的联系,好比 PyTorch 和 TensorFlow 的 tensor 数据结构和 numpy 的 ndarray 同样。而在深度学习领域,编译器是最新的主流研究方向。
 
Go 社区里目先后台系统开发者居多;视频直播时,有听众在弹幕里说本身不是 AI 工程师,不关注 AI。若是真的这么想,恐怕不仅是技术理想问题,并且是对饭碗不负责任了。
 
后台系统和 AI 系统之间的界限愈来愈模糊,由于后台系统指的是互联网服务的后台系统;而整个互联网经济创建在用不眠不休的服务器取代人来服务大众,而 AI 是这个逻辑成立的基础,详见个人一篇老文 [8] ,例数了最近二十年被 AI 技术淘汰的人类职业。
 
并且这个界限在不久的未来会完全消失,由于随着 online learning、reinforcement learning、 imitation learning、federated learning 技术取代 sueprvised learning 成为互联网智能(包括传统的搜索、广告、推荐,也包括新兴的无人驾驶和金融智能)的主流技术,AI 系统将再也不能被分为训练和预测两部分,也再也不由 AI 工程师负责前者,然后台工程师负责后者了。
 
在 AI 领域里,深度学习超越传统机器学习的一个重要缘由是:传统机器的每个模型(能够理解为对知识结构的描述)每每对应一种甚至多种训练算法;而深度学习里,几乎全部模型都用一种算法 stochastic gradient descend(SGD)或者其大同小异的变种来训练。这样,基础架构工程师负责训练系统的开发;模型研究人员复用之,大大减少了科研的工程负担,提高了模型研发的效率。
 
深度学习系统的核心问题在于 autodiff,这是 SGD 算法的数学特色决定的。SGD 算法经过交替执行前向计算过程(forward pass)和反向计算过程(backward pass),便可从训练数据概括出模型的参数。模型加参数就是知识。这里的工程挑战在于模型研究者在定义模型的时候,就附带描述了前向计算过程,可是反向计算过程很难由人来描述,最好有一个程序自动从前向计算过程推导出反向计算过程。这个自动推导被称为 autodiff。
 
目前有两种 autodiff 的策略。第一种在运行时推导,也被称为 dynamic net 和 tape-based approach。基本思路是无论前向计算过程有多复杂,哪怕包括 if-else、循环、函数定义和调用、甚至 coroutine 和 multithreading,只要把依次执行的基本操做(operator)记录下来,到一个 tape 里,那么反向计算过程就是回溯这个 tape 里的记录,而且依次调用每一个 operator 对应的求导数 operator(gradient operator)。这是 PyTorch、TensorFlow eager execution、以及 Paddle Tape 采用的策略。这种策略和编译器关系不大,和 JIT compilation 有点关系。
 
另外一种策略是运行以前推导反向计算过程,为此须要引入一个专门作 autodiff 的编译器。TensorFlow graph mode、Caffe/Caffe二、Paddle Fluid、Google Tangent、Julia、Swift for TensorFlow 用的是这个策略。编译器通常来讲是把源语言描述的源程序翻译成目标语言描述的目标程序。可是前三种技术偷懒了,没有引入源语言,而是让用户经过调用 Python library 来描述前向计算过程。Google Tangent、Julia、Swift for TensorFlow 分别让用户用 Python 语言、Julia 语言、Swift 语言来定义函数,从而描述前向计算过程,而且能把前向计算函数翻译成反向计算函数。
 
严格地说,Julia 的做者实现了多种 autodiff 方案:有运行时的、也有编译时的、也有两者混合的。明胜在帮我修改此文时提醒:
 
For a different vision, where the same language is used to both implement kernels and construct + executeprograms/graphs based on the kernels, see [9]

这里的 kernel 指的是深度学习基本操做单元 operator 的实现。
 
编译时和运行时 autodiff 这两种策略,也都适用于 Go+,并且并不妨碍 Go+ 复用现有技术。就像数据科学领域应该复用 LAPACK 这些基础库,深度学习领域也应该复用基础的 operators 和  gradient operators。
 
运行时用 tape 实现 autodiff 的策略的实现更简单。我记得杨扬用一个星期时间就开发了 Paddle Tape。而编译的策略复杂不少。Paddle Fluid 二十多人在 TensorFlow 团队 Yuan Yu 老师的工做 [10] 的基础上,用了好几个月的时间,才搞定 if-else、循环、函数定义和调用的 autodiff。
 
这些尝试提醒咱们复用社区核心技术的重要性。好比,用 MLIR 代替计算图从而能描述更复杂的控制流——计算图确定无法描述 goroutine 和 select。用 TVM 做为编译器后段(backend),用深度学习技术学习如何优化深度学习程序。全部这些技术的输出,都是对基本 operaotor 的调用。从这个角度看,以前深度学习技术生态积累的 operators 相似 built-in functions。这也是洪明胜在修改此文时反复提醒的。
 
但愿不久的未来,Go+ 能够做为一种新的深度学习前端语言,与 Python、Julia、Swift 并列,共同复用更底层的 IR、编译器后段、以及基本 operators。

小结

我理解将来 Go+ 项目的核心战术工做是:在维持 Go 的语法简洁性的本色之上,合理准入简化语法——不要像 Python 和 C++ 那样融入太多灵活性,同时在 Go 的极简语法规范之上,适当地更加灵活度。
 
此外,经过社区合做开发 numgo+ 和 GoTorch 这样的探索性项目,丰富技术生态是社区的战略方向。甚至更进一步,成为一种深度学习编译器的前端语言,以复用多年来社区沉淀的深度学习底层计算技术。
 
最后,感谢老许和 Go+ 的核心贡献者柴树杉和陈东坡、Go 社区的杰出贡献者 Asta Xie、以及个人同事 ONNX 社区核心贡献者张科校阅。
 
点击下方“阅读原文”,进入蚂蚁机器学习工具 SQLFlow 专栏

相关连接:

[1] https://news.ycombinator.com/item?id=23550042

[2] https://www.tensorflow.org/mlir/dialects

[3] https://zhuanlan.zhihu.com/p/38395601

[4] https://github.com/wangkuiyi/notes/tree/master/s4tf

[5] https://docs.google.com/document/d/1WLD4OcntBKQoNIH9VVYF0NPRe6V5exN3mu5uFlE0P0w/edit?usp=sharing

[6] https://github.com/qiniu/goplus/issues/307

[7] https://github.com/qiniu/goplus/tree/master/tutorial

[8] https://zhuanlan.zhihu.com/p/19901967

[9] https://julialang.org/blog/2018/12/ml-language-compiler/

[10] https://arxiv.org/pdf/1805.01772.pdf


END -

本文分享自微信公众号 - 支付宝技术(Ant-Techfin)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索