web-dev-qa-db-ja.com

CUDA:配列のすべての要素をGPU内で1つの数値に合計する方法?

まず、私の質問がすでに尋ねられていることを完全に認識していることを述べさせてください: CUDAでのブロック削減 ただし、明確にしたいので、私の質問はそのフォローアップです。そのOPによって見つかったソリューションを不適切にする特定のニーズがあります。

それでは、説明させてください。現在のコードでは、whileループの反復ごとにCudaカーネルを実行して、配列の値に対していくつかの計算を行っています。例として、次のように考えてください。

int max_iterations = 1000;
int iteration = 0;
while(iteration < max_iterations)
{
    __global__ void calcKernel(int* idata, int* odata)
    {
        int i = blockIdx.x*blockDim.x + threadIdx.x;
        if (i < n)
        {
            odata[i] = (idata[i] + 2) * 5;
        }
    }

    iteration++;
}

ただし、次に、GPUに対して一見難しいタスクを実行する必要があります。カーネルを呼び出すwhileループの各反復で、odata内で生成されたすべての値を合計し、その結果をintと呼ばれるresultarrayの位置に保存する必要があります現在の反復に対応するそのような配列内。カーネル内で達成する必要があります、または少なくともまだGPU内で達成する必要がありますパフォーマンスの制約により、すべての反復が完了した後、最後にのみresult配列を取得できます。

間違ったナイーブな試みは次のようになります。

int max_iterations = 1000;
int iteration = 0;
while(iteration < max_iterations)
{
    __global__ void calcKernel(int* idata, int* odata, int* result)
    {
        int i = blockIdx.x*blockDim.x + threadIdx.x;
        if (i < n)
        {
            odata[i] = (idata[i] + 2) * 5;
        }
    }

    result[iteration] = 0;
    for(int j=0; j < max_iterations; j++)
    {
        result[iteration] += odata[j];            
    }

    iteration++;
}

もちろん、GPUがスレッド間でコードを分散しているため、上記のコードは機能しません。それを適切に行う方法を理解するために、私はCUDAを使用した配列の削減に関するサイトの他の質問を読んでいます。特に、そのような主題について非常に優れたNVIDIAのpdfへの言及を発見しました。これは、前に述べた前のSO質問でも説明されています: http:// developer .download.nvidia.com/compute/cuda/1.1-Beta/x86_website/projects/reduction/doc/reduction.pdf

ただし、そのようなスライドで説明されているコードのステップと一般的な最適化については完全に理解していますが、コードが実際に配列全体を占めている場合、その方法で配列を合計して1つの数値に減らす方法はわかりません(そして不明確な次元の1つ)。誰かがそれについていくつかの光を当てて、それがどのように機能するか(つまり、出力配列から1つの数値を取得する方法)の例を私に見せてもらえますか?

さて、冒頭で述べた質問に戻ります( CUDAでのブロック削減 )。その受け入れられた答えは、上記でリンクしたPDFを読むことを提案するだけであることに注意してください。これは、コードによって生成された出力配列をどうするかについてしないことを話します。コメントで、OPは、彼/彼女がCPUで出力配列を合計することによってジョブを完了することができたと述べています-これは、私のwhileループの反復ごとに出力配列をダウンロードすることを意味するため、私にはできません。最後に、そのリンクの3番目の回答は、これを実現するためにライブラリを使用することを示唆していますが、そのネイティブな方法を学ぶことに興味があります。

あるいは、上記で説明した内容を実装する方法に関する他の命題にも非常に興味があります。

7
AndrewSteer

ブロックの並列削減に関する標準的な情報はすでに見つけたので、ここでは繰り返しません。これを行うために自分で多くの新しいコードを記述したくない場合は、CUBライブラリーを調べることをお勧めします block_reduce実装 。これは、既存のカーネルに約4行のコードを追加して、最適なブロックごとの削減演算を提供します。

ここでの本当の質問では、次のようなことをすれば、やりたいことができます。

__global__ void kernel(....., int* iter_result, int iter_num) {

    // Your calculations first so that each thread holds its result

    // Block wise reduction so that one thread in each block holds sum of thread results

    // The one thread holding the adds the block result to the global iteration result
    if (threadIdx.x == 0)
        atomicAdd(iter_result + iter_num, block_ressult);
}

ここで重要なのは、 アトミック関数 を使用して、カーネル実行の結果を、メモリ競合のない特定のブロックの結果で安全に更新することです。絶対にmust初期化iter_resultカーネルを実行する前に、そうでなければコードは機能しませんが、それは基本的なカーネル設計パターンです。

5
talonmies

この例のように、2つの連続した数値を追加し、それらの数値を保存するスロットのいずれかで結果を保存する場合、同じカーネルを複数回実行して、アレイの合計を2の累乗で削減し続ける必要があります。 :

値を合計する配列:

[·1,·2,·3,·4,·5,·6,·7,·8,·9,·10]

最初にn/2スレッドを実行し、隣接する配列要素を合計して、それぞれの「左側」に格納すると、配列は次のようになります。

[·3,2,·7,4,·11,6,·15,8,·19,10]

同じカーネルを実行し、n/4スレッドを実行して、各2つの要素を追加し、それを左端の要素に格納すると、配列は次のようになります。

[·10,2,7,4,·26,6,15,8,·19,10]

同じカーネルを実行し、n/8スレッドを実行して、4つの要素をそれぞれ追加し、配列の左端の要素に格納して、以下を取得します。

[·36,2,7,4,26,6,15,8,·19,10]

最後にもう一度、単一のスレッドを実行して各8要素を追加し、配列の左端の要素に格納して、以下を取得します。

[55,2,7,4,26,6,15,8,19,10]

このように、一部のスレッドをパラメーターとしてカーネルを実行するだけで、最後にreduxを取得できます。最初の要素(55)で「ドット」(・)を見て、配列内のどの要素が「アクティブ」かを確認します。 "それらを合計するには、各実行。