web-dev-qa-db-ja.com

CUDAのブロック削減

私はCUDAを削減しようとしていますが、本当に初心者です。私は現在、NVIDIAのサンプルコードを研究しています。

特に入力配列が単一のブロックサイズよりも大きい場合(512 X 512)、ブロックサイズとグリッドサイズを設定する方法が本当にわからないと思います。

これがコードです。

template <unsigned int blockSize>
__global__ void reduce6(int *g_idata, int *g_odata, unsigned int n)
{
    extern __shared__ int sdata[];
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x*(blockSize*2) + tid;
    unsigned int gridSize = blockSize*2*gridDim.x;
    sdata[tid] = 0;

    while (i < n) 
    { 
        sdata[tid] += g_idata[i] + g_idata[i+blockSize]; 
        i += gridSize; 
    }

    __syncthreads();

    if (blockSize >= 512) { if (tid < 256) { sdata[tid] += sdata[tid + 256]; } __syncthreads(); }
    if (blockSize >= 256) { if (tid < 128) { sdata[tid] += sdata[tid + 128]; } __syncthreads(); }
    if (blockSize >= 128) { if (tid < 64) { sdata[tid] += sdata[tid + 64]; } __syncthreads(); }

    if (tid < 32) 
    {
        if (blockSize >= 64) sdata[tid] += sdata[tid + 32];
        if (blockSize >= 32) sdata[tid] += sdata[tid + 16];
        if (blockSize >= 16) sdata[tid] += sdata[tid + 8];
        if (blockSize >= 8) sdata[tid] += sdata[tid + 4];
        if (blockSize >= 4) sdata[tid] += sdata[tid + 2];
        if (blockSize >= 2) sdata[tid] += sdata[tid + 1];
    }

    if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}

ただし、g_odata[blockIdx.x]はすべてのブロックから部分的な合計を保存しているように見えます。最終結果を取得する場合は、g_odata[blockIdx.x]配列内のすべての項を合計する必要があります。

私は疑問に思っています:全体の合計を行うためのカーネルはありますか?それとも私はここで物事を誤解していますか?誰かがこれで私を教育することができれば本当にありがたいです。どうもありがとう。

9
Ono

このトピックをよりよく理解するために、コードで使用したすべての戦略をグラフィカルに説明しているNVIDIAの このpdf を見ることができます。

4
Leos313

あなたの理解は正しいです。示されている削減 ここ は、グローバルメモリに格納された一連のブロック合計になります。

これらのブロック合計をすべて合計するには、何らかの形式のグローバル同期が必要です。すべてのブロックが完了するまで待ってから、それらの合計を合計する必要があります。この時点でいくつかのオプションがあり、そのうちのいくつかは次のとおりです。

  1. メインカーネルの後に新しいカーネルを起動して、ブロック合計を合計します
  2. ホストにブロック合計を追加します
  3. メインカーネルの最後で、アトミックを使用してブロック合計を加算します
  4. threadfence Reduction のようなメソッドを使用して、メインカーネルでブロック合計を合計します。
  5. CUDA協調グループ を使用して、カーネルコードにグリッド全体の同期を配置します。グリッド全体の同期後のブロック合計を合計します(おそらく1つのブロックで)。

CUDAタグの周りを検索すると、これらすべての例と、それらの長所と短所の議論を見つけることができます。投稿したメインカーネルが完全な削減にどのように使用されているかを確認するには、 並列削減サンプルコード を参照してください。

12
Robert Crovella

Robert Crovellaはすでにこの質問に答えています。これは主に、パフォーマンスではなく理解に関するものです。

ただし、この質問にぶつかるすべての人のために、 [〜#〜] cub [〜#〜] がブロック削減機能を利用できるようにすることを強調したいと思います。以下に、CUBの BlockReduce の使用方法に関する簡単な実例を示します。

#include <cub/cub.cuh>
#include <cuda.h>

#include "Utilities.cuh"

#include <iostream>

#define BLOCKSIZE   32

const int N = 1024;

/**************************/
/* BLOCK REDUCTION KERNEL */
/**************************/
__global__ void sum(const float * __restrict__ indata, float * __restrict__ outdata) {

    unsigned int tid = blockIdx.x * blockDim.x + threadIdx.x;

    // --- Specialize BlockReduce for type float. 
    typedef cub::BlockReduce<float, BLOCKSIZE> BlockReduceT; 

    // --- Allocate temporary storage in shared memory 
    __shared__ typename BlockReduceT::TempStorage temp_storage; 

    float result;
    if(tid < N) result = BlockReduceT(temp_storage).Sum(indata[tid]);

    // --- Update block reduction value
    if(threadIdx.x == 0) outdata[blockIdx.x] = result;

    return;  
}

/********/
/* MAIN */
/********/
int main() {

    // --- Allocate Host side space for 
    float *h_data       = (float *)malloc(N * sizeof(float));
    float *h_result     = (float *)malloc((N / BLOCKSIZE) * sizeof(float));

    float *d_data;      gpuErrchk(cudaMalloc(&d_data, N * sizeof(float)));
    float *d_result;    gpuErrchk(cudaMalloc(&d_result, (N / BLOCKSIZE) * sizeof(float)));

    for (int i = 0; i < N; i++) h_data[i] = (float)i;

    gpuErrchk(cudaMemcpy(d_data, h_data, N * sizeof(float), cudaMemcpyHostToDevice));

    sum<<<iDivUp(N, BLOCKSIZE), BLOCKSIZE>>>(d_data, d_result);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());

    gpuErrchk(cudaMemcpy(h_result, d_result, (N / BLOCKSIZE) * sizeof(float), cudaMemcpyDeviceToHost));

    std::cout << "output: ";
    for(int i = 0; i < (N / BLOCKSIZE); i++) std::cout << h_result[i] << " ";
    std::cout << std::endl;

    gpuErrchk(cudaFree(d_data));
    gpuErrchk(cudaFree(d_result));

    return 0;
}

この例では、長さNの配列が作成され、結果は32の連続する要素の合計になります。そう

result[0] = data[0] + ... + data[31];
result[1] = data[32] + ... + data[63];
....
6
JackOLantern