使用 Go 和 C 進行 CUDA 編程

關於 CUDA

NVIDIA 和 AMD 等圖形芯片製造商的圖形處理器 (GPU) 銷量一直在飆升,這主要歸功於加密貨幣礦工和機器學習應用程序,它們在遊戲和模擬之外發現了這些圖形處理器的用途。這主要是因爲 GPU 提供了通用處理器所沒有的並行能力,而這些能力恰好非常適合大規模哈希和矩陣計算等操作,這些操作是挖掘和機器學習工作負載的基礎。

NVIDIA 的 CUDA 爲可用於數值計算的圖形處理器提供了大規模並行架構。典型的通用 Intel 處理器可能有 4 個或 8 個內核,而 NVIDIA GPU 可能有數千個 CUDA 內核和一個支持在數千個線程上並行處理的管道,從而大大加快處理速度。這可用於大大減少機器學習應用程序中的訓練時間,從而增加在調整模型時可以運行的實驗和迭代次數。

CUDA 和並行處理的挑戰之一是它需要使用專門的技術和技能,因此它的使用受到限制。近年來,這個領域變得越來越民主化,編程技能有限的人也可以更容易地使用該技術來獲取它並優化他們的應用程序,以利用 GPU 提供的大規模並行能力。

Go 和 CUDA

使用 Go 對 CUDA 進行編程比使用其他語言要複雜一些。儘管有一些優秀的軟件包,例如 mumax,但文檔很差,缺少示例並且難以使用。

CUDA 適用於 C,因此最好的選擇是使用 Command cgo 並使用您的 Cuda Kernel 調用外部函數 。這就是我將在此示例中執行的操作,其中我使用 CUDA 將兩個矩陣相乘。

Kernel

這裏有一個 簡單 的內核,它具有內核函數和一個要在外部調用的輔助函數。請注意,我使用了 extern C ,因爲這是 cgo 調用函數的方式:

#include <stdio.h>
#include <cuda.h>

__global__ void vecmul(float *A, float* B, float *C, int size)
{
    // Row and Column indexes: 
    int row = blockIdx.y*blockDim.y+threadIdx.y;
    int col = blockIdx.x*blockDim.x+threadIdx.x;

    // Are they bellow the maximum?
    if (col < size && row < size) {
       float result = 0;
       for(int ix=0;ix<size;ix++) {
          result += A[row*size+ix]*B[ix*size+col];
       }
       C[row*size+col] = result;
    }
}

extern "C" {
    void maxmul(float *A, float* B, float *C, int size) {
        int total = size*size;

        // Allocate device memory:
        float* gpu_A;
        float* gpu_B;
        float* gpu_C;
        int msize = total * sizeof(float);
        cudaMalloc((void**)&gpu_A, msize);
        cudaMemcpy(gpu_A,A,msize,cudaMemcpyHostToDevice);
        cudaMalloc((void**)&gpu_B, msize);
        cudaMemcpy(gpu_B,B,msize,cudaMemcpyHostToDevice);
        cudaMalloc((void**)&gpu_C,msize);

        // Blocks & grids:
        dim3 blocks(size,size);
        dim3 grid(1,1);

        // Call the kernel:
        vecmul<<<grid,blocks>>>(gpu_A,gpu_B,gpu_C,size);

        // Get the result Matrix:
        cudaMemcpy(C,gpu_C,msize,cudaMemcpyDeviceToHost);

        //Free device matrices
        cudaFree(gpu_A);
        cudaFree(gpu_B);
        cudaFree(gpu_C);
    }
}

vecmul() 函數是內核,而 maxmul() 函數是助手。它的作用是在 GPU 中分配內存,複製參數,調用內核,複製結果。值通過引用傳遞。

Go 代碼

程序 maxmul.go 調用 輔助 函數並顯示結果:

package main

/*
void maxmul(float *A, float* B, float *C, int size);
#cgo LDFLAGS: -L. -L./ -lmaxmul
*/
import "C"

import "fmt"

func Maxmul(a []C.float, b []C.float, c []C.float, size int) {
    C.maxmul(&a[0], &b[0], &c[0], C.int(size))
}

func main() {
    //in := []C.float{1.23, 4.56}
    //C.test(&in[0]) // C 1.230000 4.560000
    a := []C.float{-1,2,4,0,5,3,6,2,1}
    b := []C.float{3,0,2,3,4,5,4,7,2}
    var c []C.float = make([]C.float, 9)
    Maxmul(a,b,c,3)
    fmt.Println(c)
}

在導入 C 包之前,允許以純 C 代碼 (extern C) 調用外部函數的,我傳遞了 cgo 的配置,指明瞭函數 C 的原型、lib 的路徑及其名稱。

我必須在 Go 代碼中創建一個包裝函數來調用外部函數,以使事情變得更容易。它只是將引用傳遞給數組(第一個位置的地址)和數組大小(在本例中爲 3x3 = 9)。在 CUDA 中,我們使用 平面 矩陣。

我使用 C.float 類型創建包含我的數組的 slices(轉換爲向量)。然後我調用了這個函數。請注意,我傳遞了每行(或列)的大小。

編譯

要編譯 C 代碼,請使用以下命令:

nvcc --ptxas-options=-v --compiler-options '-fPIC' -o libmaxmul.so --shared maxmul.cu

您需要安裝 CUDA 和 Nvidia 驅動程序!

然後只需使用以下命令運行 Go 代碼:

go run maxmul.go
...
[19 36 16 27 41 31 28 15 24]

而這就是矩陣相乘的結果!

完整的源代碼在這裏:https://github.com/cleuton/golang-network/tree/master/english/cuda/nostress

總結

  1. Cgo 讓代碼變得羞澀難懂,加大了維護成本和學習成本。

  2. 個人觀點:Gpu 的使用發展還不成熟,以後會誕生新的編程語言來原生支持 或者 Go 語言充分利用 CPU 多核的性能,對於 GPU 多核的支持還不到位。

  3. 有份比較古老的文檔,感興趣的可以看下。使用 Go 進行科學 GPU 計算:https://archive.fosdem.org/2014/schedule/event/hpc_devroom_go/attachments/slides/486/export/events/attachments/hpc_devroom_go/slides/486/FOSDEM14_HPC_devroom_14_GoCUDA.pdf

Have a nice day, Happy coding.

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/R3mIYogdv6ALU6cTKJStJQ