5行程式碼,在C語言中呼叫CUDA加速進行張量計算
迄今為止最懶但是最好用的方法
============================================================================================================================================================================================================================
我們先給出一個需求:
int main(int argc, char *argv[]) { int c_arr_0[] = {1,2,3,4,5,6}; int c_arr_1[] = {7,8,9,10,11,12}; int c_arr_2[] = {0,0,0,0,0,0}; //計算c_arr_0與c_arr_1的元素乘積,程式碼開始 //開始你的表演 //程式碼結束,越少越好 for (int i=0;i<sizeof(c_arr_2)/sizeof(int);++i) { printf("%d ",c_arr_2[i]); //顯示結果 } }
計算過程中的需求是
- 要適應各種尺寸的輸入資料、要支援各種各樣的計算型別(加減乘除,各種向量、矩陣計算、各種張量計算還有神經網路前向後項……),上面的程式碼就是給了個簡單的例子
- GPU加速
- 程式碼量要少,超過10行就頭疼
我的結果
#include "py.h" int main(int argc, char *argv[]) { int c_arr_0[] = {1,2,3,4,5,6}; int c_arr_1[] = {7,8,9,10,11,12}; int c_arr_2[] = {0,0,0,0,0,0}; //convert c array to py list int c_shape[] = {6}; py shape = py_from_int_list(c_shape); //convert c array to torch tensor with shape py array0 = py_from_array(c_arr_0,sizeof(c_arr_0),shape,PY_INT); py array1 = py_from_array(c_arr_1,sizeof(c_arr_1),shape,PY_INT); //call torch functions. need specify number of arguments py array2 = py_call("torch.mul",2,array0,array1); //convert back and display py_to_array(array2,c_arr_2,sizeof(c_arr_2)); for (int i=0;i<sizeof(c_arr_2)/sizeof(int);++i) { printf("%d ",c_arr_2[i]); } }
上面的程式碼非常明快,而且符合人類的基本認知:我們的目標是完成數學計算,沒必要在這個過程中學習CUDA、OpenCL等一大堆並行裝置程式設計的知識。也不用學習C++、STL,libtorch也沒必要學了。
特別指出的是,儘管這段程式碼的背後都是Python,但是在API中完全掩蓋了Python的痕跡。仔細觀察發現它實際上呼叫了PyTorch,PyTorch的功能非常豐富(也有CUDA加速),只需要修改py_call的引數就能呼叫PyTorch中的任意功能。
想實際操練這個程式碼,可以移步jerry-jho/c_and_python
關鍵技術
實際上這些C程式碼的背後都是Python和PyTorch的驅動。利用Cython所提供的C和Python混合程式設計的方法提供一個友好人性化的API。其中涉及到幾個關鍵技術:
1. C陣列與Python型別的轉換
這個操作比較簡單,Cython提供了非常好的支援
2. C陣列與Numpy Array/Torch Tensor型別的轉換
我們這裡採用一個投機取巧的方法,即首先把C的陣列強制轉換成char *,即原始的記憶體空間,然後利用Cython的儲存轉換功能得到bytes型別的Python值,然後使用numpy array的frombuffer方法將bytes轉換為array。得到numpy array之後,就可以比較容易的得到Torch Tensor以及複製到GPU當中了。這個過程沒有記憶體複製。
反向轉換也比較容易,首先先轉換為Numpy的Array,然後用tobytes的方法得到儲存值。
3. 呼叫PyTorch的函式
從Python API中返回的都是PyObject * 的型別,我們在API裡掩人耳目,把PyObject * 進行了typedef,即程式碼裡的 py 型別。、
呼叫PyTorch裡的函式,我們用一種曲折的方法成功實現了Cython匯出函式的變長引數,大家可以看看程式碼裡是怎麼實現的。用這種方法,可以呼叫Python庫中的任何函式。
思考題
在之前的文章中提到,如果想在C中呼叫Python,必須呼叫Py_Initialize()進行初始化,但是今天我們的main函式裡卻沒有這個呼叫。這個是如何實現的呢?(提示:不能用修改程式入口點的方法)