Cuda内核中的数据组织

时间:2019-02-08 09:36:48

标签: cuda

我是Cuda的新手,正在阅读教程和其他开放源代码,以尝试理解事物。我知道线程层次结构的一般概念。

TL; DR,我阅读的所有教程都假定发送到内核的数据也按这种层次结构进行组织,而在启动内核之前没有明确地这样做。在传递给内核之前,难道不应该在grid> block> thread层次结构中重新排列传递给内核的数据吗?以下是两个片段,在这方面使我感到困惑。

我遵循了x_plus_y本教程here。在本教程中,以下代码段:

_global__
void add(int n, float *x, float *y)
{
  int index = blockIdx.x * blockDim.x + threadIdx.x;
  int stride = blockDim.x * gridDim.x;
  for (int i = index; i < n; i += stride)
    y[i] = x[i] + y[i];
}

在上面的代码段中,我们想在xy中添加相应的元素,但是我怎么知道xy放在了GPU,索引i(使用blockIdx,blockDim等计算)实际上指向xy的相应元素。如果xy在内存中一个接一个地放置,用于y的索引是否不考虑x的长度?我在这里缺少一些关键的直观理解。另外,我怎么知道在GPU中哪里映射了一些随机元素,例如x [1011]?还是由于某种抽象,我不需要关心数据的显式位置?

我还将讨论开源火炬库中的另一个代码段。这是用于计算两组点云之间的距离度量的内核。每个云都是Nx3矩阵(具有N 3-D点)。

b是批处理大小(因此,b数量的云被传递到内核)

n是第一组每个云中的点数

m是第二组每个云中的点数。

例如,第一组云可以是(16,1024,3),第二组云是(16,512,3):

__global__ void NmDistanceKernel(int b,int n,const float * xyz,int m,const float * xyz2,float * result,int * result_i){
    const int batch=512;
    __shared__ float buf[batch*3];
    for (int i=blockIdx.x;i<b;i+=gridDim.x){
        for (int k2=0;k2<m;k2+=batch){
            int end_k=min(m,k2+batch)-k2;
            for (int j=threadIdx.x;j<end_k*3;j+=blockDim.x){
                buf[j]=xyz2[(i*m+k2)*3+j];
            }


        for (int j=threadIdx.x+blockIdx.y*blockDim.x;j<n;j+=blockDim.x*gridDim.y){
                float x1=xyz[(i*n+j)*3+0];
                float y1=xyz[(i*n+j)*3+1];
                float z1=xyz[(i*n+j)*3+2];
            }
    }
}

上述内核按以下方式启动:

NmDistanceKernel<<<dim3(32,16,1),512>>>(batch_size, n, xyz1.data<float>(), m, xyz2.data<float>(), dist1.data<float>(), idx1.data<int>());

同样,在上述内核中,作者假设将它们传递给内核的数据进行了组织,以便索引机制可以正常工作。他们没有明确地将每个点放置在每个线程中,然后将一堆点放置在一个块中,而将一堆云放置在一个网格中。但是,这种结构是在内核内部假定的。

1 个答案:

答案 0 :(得分:1)

在调用内核之前,您必须将数据放入GPU。

数据大多以数据数组形式传入,因此这些数组的结构在GPU上与主机代码中的相同。

在您的第一个示例中,数组xy是分别传递的,因此xy的索引都从0开始。您可以将它们分成一个大数组传递,然后需要调整索引。

这在您的其他示例中已经完成。数组xyz由所有点的x y和z值组成。顺序类似于x1 y1 z1 x2 y2 z2 x3 y3 z3 ...。这就是为什么在访问值时看到x = [...]+0; y = [...]+1; z = [...]+2;的原因。对于下一点,索引全部增加3。

要在内核中访问数据,您需要参考CUDA提供的标识符。您可以在网格和块内使用线程的位置。

在第一个示例中,程序员选择启动所有读取数组中第一个连续条目的线程。他通过为每个线程分配唯一的index来做到这一点:

  

int索引= blockIdx.x * blockDim.x + threadIdx.x;

threadIdx.x告诉我们线程在一个块中的位置,所以如果我们仅启动一个块就足够了。但是,然后不同块中的不同线程将具有相同的索引。我们必须通过获取它们的blockIdx.x来分离它们。该块的长度为blockDim.x,第二个块的第一个线程应在块1的最后一个线程之后继续。因此,index的上述公式形成。

然后,每个线程向前跳转,以便第一个线程接下来读取第一个数据之后的第一个数据,最后一个线程刚刚读取,依此类推。

启动的网格使用的尺寸越多,这些计算就必须越复杂。尝试从简单的网格开始,如果您对它们感到满意,则增加复杂性。