我感兴趣的开源C ++ / Qt应用程序取决于CUDA。我的macbook pro(2014年中)有英特尔Iris Pro,没有NVidia显卡。当然,预先构建的应用程序不会运行。
我找到了这个模拟器:https://github.com/gtcasl/gpuocelot - 但它只针对Linux进行了测试,并且有一些未解决的问题,而不是在Mac上进行编译。
我有源代码 - 我可以用较慢的处理代替C ++等价物替换CUDA依赖吗?我希望得到像
这样的东西但我担心它并不那么简单。在我开始之前,我想要进行健全检查。
答案 0 :(得分:1)
在一般情况下,我认为没有针对" de-CUDA-fy"的具体路线图。一个应用程序。正如我不认为有一个特定的"机械"路线图" CUDA-fy"一个应用程序,我也找不到编程问题的具体路线图。
此外,我认为拟议的路线图存在缺陷。仅举一个例子,.cu
文件通常具有CUDA特定的引用,用于编译.cpp
代码的普通c ++编译器无法容忍这些引用。其中一些引用可能是依赖于CUDA运行时API的项,例如cudaMalloc
和cudaMemcpy
,虽然这些可以通过普通的c ++编译器(它们只是库调用),但它将那些已删除CUDA字符的应用程序保留在原地是不明智的。此外,一些引用可能是CUDA特定的语言功能,例如通过__global__
或__device__
声明设备代码或启动设备&#34;内核&#34;使用它的相应语法<<<...>>>
起作用。这些不能通过普通的c ++编译器,必须专门处理。此外,简单地删除那些CUDA关键字和语法将不太可能产生有用的结果。
简而言之,代码必须重构;没有相当简洁的路线图可以解释一个或多或少的机械过程。我建议重构过程的复杂性与原始过程(如果有的话)的复杂性大致相同,将非CUDA版本的代码转换为CUDA版本。至少,为了理解CUDA结构,需要一些非机械的CUDA编程知识。
对于非常简单的 CUDA代码,可能会对代码进行de-CUDA-some布局。总结一下,基本的CUDA处理顺序如下:
cudaMalloc
)并将数据复制到设备(可能使用cudaMemcpy
)__global__
或&#34;内核&#34;功能)来处理数据并创建结果cudaMemcpy
)因此,一种直截了当的方法是:
cudaMalloc
/ cudaMemcpy
操作,从而将感兴趣的数据保留在主机上由于CUDA是一种并行处理架构,因此有一种方法可以转换固有的并行CUDA&#34;内核&#34;普通c ++代码的代码(上面的步骤2)将使用循环或一组循环。但除此之外,路线图往往会变得非常不同,这取决于代码实际上在做什么。此外,线程间通信,非转换算法(如缩减)以及CUDA内在函数或其他语言特定功能的使用将使第2步复杂化。
例如,让我们采用非常简单的向量ADD代码。用于此目的的CUDA内核代码将通过许多特性来区分,这些特性可以很容易地转换为CUDA实现或从CUDA实现转换:
没有线程间通信。问题是&#34;令人难以置信的平行&#34;。每个线程完成的工作独立于所有其他线程。这仅描述了有限的CUDA代码子集。
不需要或使用任何CUDA特定的语言功能或内在函数(除了全局唯一的线程索引变量),因此内核代码已经可以识别为几乎完全有效的c ++代码。同样,这个特征可能只描述了有限的CUDA代码子集。
因此,矢量添加代码的CUDA版本可能看起来像这样(为了演示目的而大大简化):
#include <stdio.h>
#define N 512
// perform c = a + b vector add
__global__ void vector_add(const float *a, const float *b, float *c){
int idx = threadIdx.x;
c[idx]=a[idx]+b[idx];
}
int main(){
float a[N] = {1};
float b[N] = {2};
float c[N] = {0};
float *d_a, *d_b, *d_c;
int dsize = N*sizeof(float);
cudaMalloc(&d_a, dsize); // step 1 of CUDA processing sequence
cudaMalloc(&d_b, dsize);
cudaMalloc(&d_c, dsize);
cudaMemcpy(d_a, a, dsize, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, b, dsize, cudaMemcpyHostToDevice);
vector_add<<<1,N>>>(d_a, d_b, d_c); // step 2
cudaMemcpy(c, d_c, dsize, cudaMemcpyDeviceToHost); // step 3
for (int i = 0; i < N; i++) if (c[i] != a[i]+b[i]) {printf("Fail!\n"); return 1;}
printf("Success!\n");
return 0;
}
我们看到上面的代码遵循典型的CUDA处理序列1-2-3,并且每个步骤的开头都在注释中标记。所以我们的&#34; de-CUDA-fy&#34;路线图又是:
cudaMalloc
/ cudaMemcpy
操作,从而将感兴趣的数据保留在主机上对于第1步,我们只是删除cudaMalloc
和cudaMemcpy
行,而我们将计划直接在a[]
,b[]
和{{ 1}}主机代码中的变量。然后,剩下的步骤是转换c[]
CUDA&#34;内核&#34;函数到普通的c ++函数。同样,有必要了解CUDA基础知识,以了解并行执行的操作程度。但是内核代码本身(除了使用vector_add
内置CUDA变量之外)是完全有效的c ++代码,并且没有线程间通信或其他复杂因素。所以一个普通的c ++实现可能只是内核代码,放在一个适当的for循环迭代中,在并行范围内(在本例中为threadIdx.x
),并放入一个类似的c ++函数中:
N
结合上述步骤,我们需要(在这个简单的例子中):
void vector_add(const float *a, const float *b, float *c){
for (int idx=0; idx < N; idx++)
c[idx]=a[idx]+b[idx];
}
和cudaMalloc
操作cudaMemcpy
中的内核调用修复为普通的c ++函数调用这给了我们:
main
通过这个例子的过程并不是说这个过程通常很简单。但希望很明显,这个过程不是纯粹的机械过程,而是取决于对CUDA的一些知识,还需要一些实际的代码重构;它不能简单地通过更改文件扩展名和修改一些函数调用来完成。
其他一些评论:
许多笔记本电脑都有可用的CUDA(即NVIDIA)GPU。如果你有其中一个(我意识到你不是,但我将这包括在其他人可能会阅读此内容),你可以在其上运行CUDA代码。
如果您有一台可用的台式电脑,那么可能只需不到100美元即可为其添加支持CUDA的GPU。
尝试利用仿真技术IMO不是这里的方法,除非您能够以交钥匙的方式使用它。在我看来,将模拟器中的碎片拼凑成你自己的应用程序是一件非常重要的工作。
我相信一般情况下,将CUDA代码转换为相应的OpenCL代码也不是一件容易的事。 (这里的动机是CUDA和OpenCL之间有很多相似之处,并且可能会在您的笔记本电脑上运行OpenCL代码,因为OpenCL代码通常可以在各种目标上运行,包括CPU和GPU)。这两种技术之间存在足够的差异需要付出一些努力,这带来了额外的负担,需要对两者 OpenCL和CUDA有一定程度的熟悉,而你的问题的主旨似乎是想要避免那些学习曲线。