【问题标题】:Divide the work of a 3D kernel among blocks在块之间划分 3D 内核的工作
【发布时间】:2013-05-19 10:58:32
【问题描述】:

我有这样一个 3D 内核,我目前在一个块上运行:

// The two following variables are set elsewhere in the program.
// I give them possible value here for demonstration purposes.
int* N = {14, 5, 1};
int L = 2; // N's size - 1

int idx = blockIdx.x * blockDim.x + threadIdx.x;
int idy = blockIdx.x * blockDim.y + threadIdx.y;
int idz = blockIdx.x * blockDim.z + threadIdx.z;

int idxInc = idx + 1; // for not to waste threads whose idx = 0
if (idxInc >= 1 && idxInc <= L)
{
    if (idy < N[idxInc])
    {       
        if (idz < N[idxInc-1])
        {
            dw[ idxInc ][ idy ][ idz ] = 0;
        }
    }
}

如果我在一个尺寸为 {2, 5, 14} 的块上启动这个内核,一切都很好。这正是块的每个维度所需的线程数,以便内核执行前两行中定义的数据工作。现在,我不知道如何在多个块之间划分这项工作。我的大脑错误只是试图在两个块上为每个维度找到合适数量的线程。 此外,L 可能会有所不同(但我可能会对此有所限制),更有可能 N[1] 会发生很大变化(在此示例中为 5,但可能是 128、256 或 2048...)。所以我必须找到一种算法,自动平衡块的数量,以及块的三个维度中每个维度的线程数。

我真的不知道该怎么办,现在我觉得自己很愚蠢! 我开始认为我应该停止玩 3 维......或者也许有一个我看不到的简单技巧......

一些帮助? 谢谢!

编辑:连续检查结果...

for (layer = 1; layer <= L; layer++)
{
    for (i = 0; i < N[layer]; i++)
    {
        for (j = 0; j < N[layer-1]; j++)
        {
            printf("%1.0f", dw[ layer ][ i ][ j ]);
        }
        printf("\n");
    }
    printf("\n");
}

显示的每个数字都应该是 0。

【问题讨论】:

  • 您是说问题的总范围是由 {2, 5, 14} 线程块结构定义的吗?如果是这样,那只是一个小问题。扩展它的方法是提出一个更大的数据集。在这种情况下,我见过的大多数代码只是选择一些方便的线程块大小,例如 {8, 8, 8} 并将整个 3D 域划分为该大小的块,每个线程在一个元素上运行。就像在一维情况下一样,我们应该在内核代码中进行线程检查,检查线程索引与有效数据索引。你会在域的边缘有一些浪费的线程;没关系。
  • 这是我上面描述的complete code example
  • int* N = {2, 5, 14} 只是我使用的一个示例,因为我发现更容易考虑小数据。如果你还记得的话,它仍然是我试图并行化的神经网络……中心值 5 是隐藏层的神经元数量。它可能设置为 128 或 1024。用户必须选择此值。但是层数 2 不太可能改变。并且输入层的神经元数量 14 永远不会改变。因此,如果我为这样一组数据选择一个对称块:{2, 1024, 14},可能会导致大量线程浪费...
  • 无论如何,我的代码不起作用,即使对于对称块也是如此。如果我把 128 个神经元放在隐藏层 -> N = {14, 128, 1},并选择你方便的块 {8,8,8},我需要发射 16 个块。结果只是一个 0 的小方块,其大小为...8x8x8!其余的没有初始化。我想这与线程 ID 计算有关。我正在寻找,但我的大脑坏了……
  • 这个 -> "if (idx==127 && idy == 127 && idz == 127) printf("HELLO\n");"没问题,打印出来了。但是这个->“if (idx==0 && idy == 127 && idz == 13) printf("BONJOUR\n");"由于某种原因,不显示!为什么有线程 {127,127,127} 而没有线程 {0, 127, 13} ???我疯了!

标签: cuda


【解决方案1】:

这是一个简单的示例代码(我认为)与您所描述的内容大致相同:

#include <stdio.h>

#define cudaCheckErrors(msg) \
    do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

// for simplicity assume grid is an even multiple of blocksizes
#define XSIZE 1024
#define YSIZE 14
#define ZSIZE 2
#define TSIZE (XSIZE*YSIZE*ZSIZE)
#define BLKX 16
#define BLKY 14
#define BLKZ 2


#define IDX(x,y,z) ((z*(XSIZE*YSIZE))+(y*XSIZE)+x)

typedef float mytype;

__global__ void mykernel(mytype *data){

  int idx = threadIdx.x + blockDim.x*blockIdx.x;
  int idy = threadIdx.y + blockDim.y*blockIdx.y;
  int idz = threadIdx.z + blockDim.z*blockIdx.z;

  if ((idx < XSIZE)&&(idy < YSIZE)&&(idz < ZSIZE))
    data[IDX(idx,idy,idz)] = (mytype)idx;
  if ((idx==127)&&(idy==13)&&(idz==1)) printf("BONJOUR\n");
}

int main(){


// for simplicity assume grid is an even multiple of blocksizes
  dim3 block(BLKX, BLKY, BLKZ);
  dim3 grid(XSIZE/BLKX, YSIZE/BLKY, ZSIZE/BLKZ);

  mytype *h_data, *d_data;

  h_data=(mytype *)malloc(TSIZE*sizeof(mytype));
  if (h_data == 0) {printf("malloc fail\n"); return 1;}
  cudaMalloc((void **)&d_data, TSIZE*sizeof(mytype));
  cudaCheckErrors("cudaMalloc fail");

  for (int x=0; x<XSIZE; x++)
    for (int y=0; y<YSIZE; y++)
      for (int z=0; z<ZSIZE; z++)
        h_data[IDX(x,y,z)] = (mytype)0;

  cudaMemcpy(d_data, h_data, TSIZE*sizeof(mytype), cudaMemcpyHostToDevice);
  cudaCheckErrors("cudaMemcpy fail");

  mykernel<<<grid, block>>>(d_data);
  cudaDeviceSynchronize();
  cudaCheckErrors("kernel fail");

  cudaMemcpy(h_data, d_data, TSIZE*sizeof(mytype), cudaMemcpyDeviceToHost);
  cudaCheckErrors("cudaMemcpy fail");


  for (int x=0; x<XSIZE; x++)
    for (int y=0; y<YSIZE; y++)
      for (int z=0; z<ZSIZE; z++)
        if(h_data[IDX(x,y,z)] != (mytype)x) {printf("data check fail at (x,y,z) = (%d, %d, %d), was: %f, should be: %f\n", x,y,z, h_data[IDX(x,y,z)], x); return 1;}
  printf("Data check passed!\n");


  return 0;
}

编译:

nvcc -arch=sm_20 -o t159 t159.cu

当我运行它时,我得到:

BONJOUR
Data check passed!

【讨论】:

  • 你好!好吧,让我问你几点,因为我正在从你的每一行中学习!首先,我不知道可以启动 3D 网格。现在,ids 计算变得有意义,一切都被初始化了!我刚刚添加了 ceil 用于网格尺寸计算:(ceil(XSIZE/BLKX), ceil(YSIZE/BLKY), ceil(ZSIZE/BLKZ))。接下来,您使用连续向量而不是 3D 数组。据说这样更快。我猜从 3D 数组访问一个案例意味着 3 次内存访问,这比您的绝对 id 计算和内存访问慢。是这样的吗?
  • 我还被告知我应该选择我的块尺寸为包裹尺寸的倍数。我想不要加载和卸载只有几个线程的包裹。您使用 16*14*2,它是 32 的倍数。但是,如果我想在我的网格中使用 2*128*128 线程,我可以选择 2*22*22 的块大小,这会导致非 32每个块有多个线程数,但网格为 1*6*6 块,或者选择 1*32*32 的块大小,它会启动 2*4*4 网格!理论上哪种解决方案是最好的?更多带有完整包装的块?还是更少的不完全包裹的块?
  • 是的,您可以在网格大小计算中使用ceil。为了清楚/简单起见,我只是将其省略了。一维数组更容易在主机和设备之间来回复制。我认为在访问速度/时间方面没有任何区别。是的,块尺寸应该是扭曲尺寸的倍数。我们还希望 X 维度中有许多相邻的线程,并且我们希望 X 成为我们的“快速变化维度”,以实现合并,即高效的内存访问。所以考虑重新排序你的数据存储,让 X 像我一样成为最大的维度。 32*32*1 (x,y,z) 优于 2*22*22。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-17
  • 1970-01-01
  • 2015-12-19
  • 2013-08-09
  • 2011-11-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多