【问题标题】:Merge two CUDA kernels into one将两个 CUDA 内核合并为一个
【发布时间】:2018-10-18 00:37:09
【问题描述】:

我正在使用 CUDA 计算 out = C(b(A(in))),其中函数 AC 是卷积,b 是逐元素函数。一个玩具例子是:

#define N 1000

__device__ float b(float d_in){return min(d_in + 10.0f, 100.0f);}
__global__ void bA(float *d_in, float *d_out){
    const int x = threadIdx.x + blockIdx.x * blockDim.x;
    if (x >= N)  return;

    // replicate boundary
    int x_left  = max(x-1, 0); int x_right = min(x+1, N-1);

    d_out[x] = b( d_in[x_left] + d_in[x] + d_in[x_right] );
}
__global__ void C(float *d_in, float *d_out){
    const int x = threadIdx.x + blockIdx.x * blockDim.x;
    if (x >= N)  return;

    // replicate boundary        
    int x_left  = max(x-1, 0); int x_right = min(x+1, N-1);

    d_out[x] = d_in[x_left] + d_in[x] + d_in[x_right];
}
void myfunc(float *d_data, float *d_temp){
    dim3 threads(256);
    dim3 blocks( (N + threads.x - 1) / threads.x ); // divide up

    // kernels that I would like to merge into one:
    bA<<<blocks, threads>>>(d_data, d_temp);
    C <<<blocks, threads>>>(d_temp, d_data);
}

这样的计算需要一个额外的变量d_temp,这是我不想要的。所以我想将这些内核合并为一个,即一个用于计算C(b(A(in)))的内核。

一个难点是,我怎样才能保存b(A(in))的临时结果,然后执行卷积函数C()?我曾尝试使用共享内存,但不知道如何将临时结果b(A(in)) 加载到共享内存。例如:

#define BLOCK_SIZE 32

__global__ void CbA(float *d_in, float *d_out){
    const int x = threadIdx.x + blockIdx.x * blockDim.x;
    if (x >= N)  return;

    // replicate boundary
    int x_left  = max(x-1, 0); int x_right = min(x+1, N-1);

    // temp result for b(A(in))
    float temp = b( d_in[x_left] + d_in[x] + d_in[x_right] );

    // shared memory for convolution (stencil size of 3)
    __shared__ float shmem[BLOCK_SIZE+2];

    // load center part to shared memory
    shmem[threadIdx.x+1] = temp;

    // but how to load boundary parts from temp to shmem?
    // ...

    __syncthreads();

    // perform function C()
    // ...
}

非常感谢任何建议或提示。

【问题讨论】:

  • 使用一个内核有什么好处?合并后的内核更复杂,分支更多,在大多数情况下,与 2 个不同的内核调用相比,您会损失性能。
  • @SRhm 此处显示的示例是我实际工作内核的简化,它位于 for 循环中。对于每次运行,需要多次读取和重新加载的多个临时数组(例如这里的d_temp),所以我想做一些潜在的改进。是的,你是对的,合并后的内核可能会变得更糟,但我仍然想尝试一下,看看我是否可以进一步改进我的内核。

标签: cuda


【解决方案1】:

先评论一下

// load center part to shared memory
shmem[threadIdx.x+1] = temp;

我会称之为保存到共享内存...

除此之外还有一些想法:

使用块中的第一个和最后一个线程只计算b(A(in))

当然,您必须在计算 x (const int x = threadIdx.x + blockIdx.x * (blockDim.x-2);) 时考虑这一点,并使用更多线程/块调用内核。
然后,当您执行C() 时,每个块将有两个空闲线程。但这应该不会产生很大的影响。
这是内核。如果您尝试将计算流程可视化,则更容易理解。

__global__ void CbA(float *d_in, float *d_out)
{
  const int x = threadIdx.x + blockIdx.x * (blockDim.x - 2);
  if (x >= N) return;
  int x_left  = max(x-1, 0); int x_right = min(x+1, N-1);
  float temp = b( d_in[x_left] + d_in[x] + d_in[x_right] );
  __shared__ float shmem[BLOCK_SIZE]; // = 256
  shmem[threadIdx.x] = temp;
  __syncthreads();
  if (threadIdx.x > 0 && threadIdx.x < blockDim.x-1)
    d_out[x-1] = shmem[threadIdx.x-1] + d_in[threadIdx.x] + d_in[threadIdx.x+1];
}

让块中的一个线程也为块的“边界部分”执行b(A())

但是对于每个块,您将只使用 32 个线程中的 1 个来进行该计算。最坏的情况是整个 SM 在额外计算时的比率为 1/32。

...
// but how to load boundary parts from temp to shmem?
if (threadIdx.x == 0)
{
  {
    const int x = 0 + blockIdx.x * blockDim.x;
    int x_left = max(x-1, 0); int x_right = min(x+1, N-1);
    float temp = b( d_in[x_left] + d_in[x] + d_in[x_right] );
    shmem[0] = temp;
  }
  {
    const int x = blockDim.x-1 + blockIdx.x * blockDim.x;
    int x_left = max(x-1, 0); int x_right = min(x+1, N-1);
    float temp = b( d_in[x_left] + d_in[x] + d_in[x_right] );
    shmem[blockDim.x-1] = temp;
  }
}
// perform function C()
...

避免使用共享内存

(至少在您的简化示例中)temp 的值是非常简单计算的结果。也许最好在该线程的本地线程中计算您需要在该线程中执行C() 的所有值。

__global__ void CbA(float *d_in, float *d_out)
{
  const int x = threadIdx.x + blockIdx.x * blockDim.x;
  if (x >= N)  return;

  float temp[3];
  for (int i(0); i < 3; ++i)
  {
    int x_left  = max(x-1-1+i, 0); int x_right = min(x+1-1+i, N-1);
    temp[i] = b( d_in[x_left] + d_in[x-1+i] + d_in[x_right] );
  }

  // perform function C()
  ...
}

【讨论】:

  • 感谢您的建议。对不起,我没有得到任何分数,不幸的是......“使用块中的第一个和最后一个线程仅计算b(A(in))”是什么意思?你能给出一个简短的示例代码(甚至是伪代码)来展示这些想法吗?
  • @WDC 我预计很难理解我的意思。对于作者来说总是很容易理解因为他写了它:) 所以我添加了代码。还更正了第二个示例。我希望这现在更容易理解。最难的版本可能是第一个。
  • 谢谢。现在对我来说好多了。
猜你喜欢
  • 2021-12-11
  • 2020-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-13
  • 2014-04-01
  • 2017-08-02
  • 2017-10-22
相关资源
最近更新 更多