支付寶研究員王益: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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索