教你GPU編程,NVIDIA Python CUDA Numba 大法好

0 前言

2018年暑假7月開始,我跟着一個學長做項目。學長負責理論和算法,我負責編程實現和做實驗。Python程序寫出來了,但是很慢。Python的for loop真是龜速呀。這個程序的瓶頸部分,就是一個雙層for loop,內層for loop裏是矩陣乘法。於是乎想到了numba來給瓶頸部分做優化。簡單的@numba.jit可以加速幾十倍,但是很奇怪無法和joblib配合使用。

最終解決方案是使用@numba.cuda.jit,他可以輕鬆加速三千多倍 — 這片博客就帶你入門GPU編程,本文出了闡述我對於GPU編程的理解和小結,還引用了一些非常好的學習資料。我這裏說的GPU,專門指的是NVIDIA GPU的CUDA編程。


1 GPU 編程思想

傳統意義上來講,大部分程序是運行在CPU上的,GPU只是玩遊戲的時候派上用場。然而現在GPU的重要性大幅度提升,NVIDIA的股票也是高速上漲,因爲GPU除了遊戲,增加了一個killer app:機器學習。

編寫CPU的程序,有兩個特點:單線程思想,面向對象思想。
編寫GPU的程序,有兩個特點:千線程思想,面向數組思想

1.1 千線程思想

(這個千線程思想是我自己想出來的名詞)CPU當然也有多線程程序,比如我的Macbook Pro是雙核四線程,可以同時跑四個線程。但是CPU的多線程和GPU的多線程有兩點本質區別:1)CPU的“多”,規模是十,然而GPU的“多”,規模是;2)CPU多線程之間的並行,是多個function之間的並行,然而GPU多線程的並行,是一個function內部的並行


圖:本圖和下文很多圖片,來自這裏

進一步解釋第二點,假設一個function 內部是一個雙層for loop i := 0 999,程序需要調用四次function 。那麼CPU的程序會同時搞出四個線程,每個線程調用一次function,每次順序執行for loop10002 次。而GPU的騷操作是,順序調用四次function,每次搞出10002 個線程一下子解決這個雙層for loop。當然了,你需要改寫程序,改爲function_gpu (GPU裏面可以同時執行幾千的線程,其他線程處於等待狀態,不過你儘管搞出上百萬個線程都沒問題)。當然了,你可以CPU和GPU同時配合使用,CPU搞出四個線程,每個線程調用function_gpu ,這樣子可以增大GPU的利用率。

這裏申明很重要一點,不是所有的function 能(適合)改寫爲function_gpu 。不過很多機器學習相關的算法,很多計算密集行算法,是可以改寫的。會把function 改寫爲function_gpu 是一種當下少數人掌握的技能。在本文chapter 3有三份入門代碼。

1.2 面向數組思想

面向對象的編程思想是當下主流思想:一個對象有若干個屬性,一個容器裝很多個對象,如果想獲取對象的屬性,需要獲取對象然後進行點操作。面向數組則完全不同。在做data science(DS) 和 machine learning(ML) 項目的時候,都是面向數組的思想。Numpy.ndarray和Pandas.DataFrame都是設計的非常棒的”超級數組”。

在DS和ML項目裏面,數組之所以作爲唯一欽定的數據結構,我認爲是數組能夠完美勝任DS和ML 項目裏面組織和管理數據的工作。DS和ML項目完全不需要object-oriented的那一套封裝和繼承的思想,也不需要鏈表、棧、隊列、哈希表、二叉樹、B-樹、圖等其他數據結構。這背後的原因,我認爲是DS和ML項目和那種企業級開發存在天然的區別。比如企業級開發需要處理複雜的業務邏輯,DS和ML項目就沒有複雜的業務邏輯,只有對數組裏的數據的反覆“暴力計算”,這種對一個數組裏的數據進行反覆的暴力計算,正好是GPU說擅長的東西,有些SIMD(single instruction multiple data)的既視感。


圖:截屏自這個YouTube視頻。


2 圖解GPU


圖:CPU裏面有很大的緩存(黃色),GPU裏面緩存很少。CPU擅長複雜的程序控制(藍色),但是ALU算術運算單元(綠色)比較少。GPU最大的特點就是ALU很多,擅長算數計算。


圖:GPU加速的方法是,把程序裏面計算密集型的CPU代碼,改寫爲GPU代碼。讓CPU做CPU擅長的事情,讓GPU做GPU擅長的事情,優勢互補。


圖:GPU是如何和內存和CPU相配合的,分爲4個步驟。其中步驟1和4是很消耗時間的,實際編程的時候,需要考慮如何減少1和4。


圖:CUDA是這樣子組織上千個線程的,若干線程匯聚爲一個block,若干block匯聚爲一個grid。這就是CUDA的two-level thread hierarchy。深刻理解這個two-level對於編寫CUDA程序十分重要。


圖:GPU的內存模型。GPU裏面的內存分爲三種:per thread local memory, per block shared memory,和global memory。在實際編程的時候,需要考慮多用shared memory,少用global memory,因爲shared比global的訪問和存取速度快很多。


3 Talk is cheap, show me the code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章