大家好!
在本指南中,我想告诉您如何结交CUDA C / C ++和Rust。 并以一个示例为例,我们将在Rust中编写一个小程序来计算向量的标量积,将使用CUDA C在GPU上执行标量积的计算。
谁在乎猫!
CUDA C
第一步是安装CUDA编译器-nvcc。 什么是CUDA,为什么我需要对其进行描述,例如,我可以
在此处阅读有关内容。 我只能说,借助它的帮助,您可以编写将在NVIDIA视频卡(以下称为GPU)上运行的代码,并将其所有功能用于并行计算和图形处理。 再一次,本教程不是关于如何在CUDA中编写代码,而是关于如何利用Rust代码的优势以及如何在GPU上编写并行计算。
因此,请安装nvcc和CUDA工具包。 由于存在这种复杂性,因此不应出现详细的说明:
在异地 。
锈蚀+ CUDA C
如前所述,在本教程中,我们将在Rust中编写一个程序来查找两个向量的标量积,计算过程本身将在GPU上进行。
两个向量的标量积。假设我们有两个向量:
和
,这些向量的标量积:
让我们开始创建程序。 此外,我假设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" {
要调用它,您需要使用不安全的代码块,因为我们将一个可变的指针传递给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);
完整的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上查看。