用Rust + CUDA C编写


大家好!

在本指南中,我想告诉您如何结交CUDA C / C ++和Rust。 并以一个示例为例,我们将在Rust中编写一个小程序来计算向量的标量积,将使用CUDA C在GPU上执行标量积的计算。

谁在乎猫!

CUDA C


第一步是安装CUDA编译器-nvcc。 什么是CUDA,为什么我需要对其进行描述,例如,我可以在此处阅读有关内容。 我只能说,借助它的帮助,您可以编写将在NVIDIA视频卡(以下称为GPU)上运行的代码,并将其所有功能用于并行计算和图形处理。 再一次,本教程不是关于如何在CUDA中编写代码,而是关于如何利用Rust代码的优势以及如何在GPU上编写并行计算。

因此,请安装nvcc和CUDA工具包。 由于存在这种复杂性,因此不应出现详细的说明: 在异地

锈蚀+ CUDA C


如前所述,在本教程中,我们将在Rust中编写一个程序来查找两个向量的标量积,计算过程本身将在GPU上进行。

两个向量的标量积。
假设我们有两个向量: a=[a1a2...an]b=[b1b2...bn],这些向量的标量积:

a cdotb= sumi=1naibi



让我们开始创建程序。 此外,我假设nvcc已成功安装,rustc和货物也代表编译rust代码。

首先,创建项目文件夹。 在项目文件夹中,创建Cargo.toml文件,其中包含有关收货员的说明。 该文件如下所示:

[package] name = "rust_cuda" #   version = "0.1.0" #   authors = ["MoonL1ght <ixav1@icloud.com>"] #    build = "build.rs" #    rust links = "cudart" #  cuda,    [dependencies] libc = "0.2" #  rust     rand = "0.5.5" #  rust      [build-dependencies] cc = "1.0" # rust      

同样在项目根文件夹中,创建build.rs文件,其中将包含构建rust程序和编译CUDA C代码的说明。

将src文件夹添加到项目根目录,我们将在其中放置源代码文件。 在src文件夹中,创建四个文件:main.rs-主程序的代码,dot.cpp-C ++绑定(CUDA C的包装程序),dot_gpu.h,dot_gpu.cu-包含在GPU上执行的代码的文件。

总计,我们有这样一个项目结构:

 rust-cuda/ src/ main.rs dot.cpp dot_gpu.h dot_gpu.cu Cargo.toml build.rs 

在build.rs文件中,最重要的是编写以下代码:

 println!("cargo:rustc-link-search=native=/Developer/NVIDIA/CUDA-10.1/lib"); println!("cargo:rustc-link-search=/Developer/NVIDIA/CUDA-10.1/lib"); println!("cargo:rustc-env=LD_LIBRARY_PATH=/Developer/NVIDIA/CUDA-10.1/lib"); println!("cargo:rustc-link-lib=dylib=cudart"); 

其中/Developer/NVIDIA/CUDA-10.1/lib是CUDA可执行文件的路径,在类似Unix的系统中,可以使用以下命令找到此路径:

 which nvcc 

另外,在build.rs文件中,您需要指定dot.cpp和dot_gpu.cpp文件的路径:

 .files(&["./src/dot.cpp", "./src/dot_gpu.cu"]) 

所有build.rs代码
 extern crate cc; fn main() { cc::Build::new() .cuda(true) .cpp(true) .flag("-cudart=shared") .files(&["./src/dot.cpp", "./src/dot_gpu.cu"]) .compile("dot.a"); println!("cargo:rustc-link-search=native=/Developer/NVIDIA/CUDA-10.1/lib"); println!("cargo:rustc-link-search=/Developer/NVIDIA/CUDA-10.1/lib"); println!("cargo:rustc-env=LD_LIBRARY_PATH=/Developer/NVIDIA/CUDA-10.1/lib"); println!("cargo:rustc-link-lib=dylib=cudart"); } 


现在您可以开始编写主程序代码了。 在main.rs文件中,您需要为C / C ++函数创建一个接口,以直接从Rust代码进行调用。 您可以在FFI部分的官方文档中了解有关此内容的更多信息。

 extern "C" { //  C        fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float; } 

要调用它,您需要使用不安全的代码块,因为我们将一个可变的指针传递给Vec类型作为参数:

 unsafe { gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE); } 

完整的main.rs文件代码
 extern crate libc; extern crate rand; use libc::{c_float, size_t}; use rand::Rng; const VEC_SIZE: usize = 10; const MAX: f32 = 10.; const MIN: f32 = 0.; extern "C" { fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float; } fn cpu_dot(v1: Vec<f32>, v2: Vec<f32>) -> f32 { let mut res: f32 = 0.; for i in 0..v1.len() { res += v1[i] * v2[i]; } return res; } fn main() { let mut v1: Vec<f32> = Vec::new(); let mut v2: Vec<f32> = Vec::new(); let mut gpu_res: c_float; let mut cpu_res: f32 = 0.; let mut rng = rand::thread_rng(); for _ in 0..VEC_SIZE { v1.push(rng.gen_range(MIN, MAX)); v2.push(rng.gen_range(MIN, MAX)); } println!("{:?}", v1); println!("{:?}", v2); println!("GPU computing started"); unsafe { gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE); } println!("GPU computing finished"); println!("GPU dot product result: {}", gpu_res); cpu_res = cpu_dot(v1, v2); println!("CPU dot product result: {}", cpu_res); } 


现在,我们开始用C ++编写绑定,以及用于在CUDA C中计算向量的标量积的代码。

在dot.cpp文件中,我们编写了绑定函数,实际上是从Rust代码调用此函数:

 extern "C" { float dot(float *v1, float *v2, size_t N) { float *gpu_res; float res = 0.0; gpu_res = gpu_dot(v1, v2, N); //   GPU for (int i = 0; i < blocksPerGrid; i++) { res += gpu_res[i]; } free(gpu_res); return res; } } 

完整的dot.cpp文件代码
 #include <iostream> #include "dot_gpu.h" using namespace std; void display_vector(float *v, size_t N) { cout << "["; for (size_t i = 0; i < N; i++) { cout << v[i]; if (i != N - 1) { cout << ", "; } } cout << "]" << endl; } extern "C" { float dot(float *v1, float *v2, size_t N) { cout << "Calling gpu dot product" << endl; cout << "Got two vectors from rust:" << endl; display_vector(v1, N); display_vector(v2, N); float *gpu_res; float res = 0.0; gpu_res = gpu_dot(v1, v2, N); for (int i = 0; i < blocksPerGrid; i++) { res += gpu_res[i]; } free(gpu_res); return res; } } 


以下是dot_gpu.cu文件中执行主要计算的代码,在本教程中我将不解释代码本身,因为它不是专用于CUDA编程的。

dot_gpu.cu
 #include "dot_gpu.h" __global__ void dot__(float *v1, float *v2, float *res, int N) { __shared__ float cache [threadsPerBlock]; int tid = threadIdx.x + blockIdx.x * blockDim.x; int cacheIndex = threadIdx.x; float temp = 0.0; while (tid < N) { temp += v1[tid] * v2[tid]; tid += blockDim.x * gridDim.x; } cache[cacheIndex] = temp; __syncthreads(); int i = blockDim.x / 2; while (i != 0) { if (cacheIndex < i) { cache[cacheIndex] += cache[cacheIndex + i]; } __syncthreads(); i /= 2; } if (cacheIndex == 0) { res[blockIdx.x] = cache[0]; } } float * gpu_dot (float *v1, float *v2, size_t N) { float *dev_v1, *dev_v2, *dev_res, *res; res = new float[blocksPerGrid]; cudaMalloc((void**)&dev_v1, N * sizeof(float)); cudaMalloc((void**)&dev_v2, N * sizeof(float)); cudaMalloc((void**)&dev_res, blocksPerGrid * sizeof(float)); cudaMemcpy(dev_v1, v1, N * sizeof(float), cudaMemcpyHostToDevice); cudaMemcpy(dev_v2, v2, N * sizeof(float), cudaMemcpyHostToDevice); dot__<<<blocksPerGrid, threadsPerBlock>>>(dev_v1, dev_v2, dev_res, (int)N); cudaMemcpy(res, dev_res, blocksPerGrid * sizeof(float), cudaMemcpyDeviceToHost); cudaFree(dev_v1); cudaFree(dev_v2); cudaFree(dev_res); return res; } 


我们所有的小程序都已编写并可以构建。 为了在控制台中组装它,请调用命令:

 cargo build 

运行:

 cargo run 

生成程序后,目标文件夹将出现在项目的主目录中。 我们程序的可执行文件将位于以下文件夹中:./target/debug/

此外,如果仅运行可执行文件,则会出现错误:dyld库未加载。 也就是说,他找不到cuda动态库的路径。 要解决此问题,可以在启动控制台中的可执行文件之前,注册环境变量LD_LIBRARY_PATH = path_to_CUDA_lib_directory /或在rust工具链文件夹中为CUDA运行符号链接:

 ln -s /Developer/NVIDIA/CUDA-10.1/lib/* /Users/Alexander/.rustup/toolchains/nightly-x86_64-apple-darwin/lib 

其中/Users/Alexander/.rustup/toolchains/nightly-x86_64-apple-darwin/lib是我安装的rust工具链的路径,对于您来说可能略有不同。

通过货物运行启动程序时,不会发生这样的错误,因为我们在build.rs文件中注册了环境变量LD_LIBRARY_PATH。

最后


我们有能力直接从Rust代码运行CUDA C代码。 为了验证这一点,我们创建了一个小程序,该程序可以处理矢量并在GPU上执行所有计算。 完整的代码也可以在github上查看。

Source: https://habr.com/ru/post/zh-CN447968/


All Articles