web-dev-qa-db-ja.com

グリッド、ブロック、スレッドサイズを取得し、非正方行列計算を並列化する方法をCUDA

私はCUDAを初めて使用し、いくつかのことを理解するのに助けが必要です。これら2つのforループを並列化するのに助けが必要です。具体的には、これをより高速に実行するためにdimBlockとdimGridを設定する方法。これはSDKのベクトル追加の例のように見えますが、その例は正方行列専用であり、128 x1024行列のコードを変更しようとすると正しく機能しません。

__global__ void mAdd(float* A, float* B, float* C)
{
    for(int i = 0; i < 128; i++)
    {
        for(int j = 0; j < 1024; j++)
        {
            C[i * 1024 + j] = A[i * 1024 + j] + B[i * 1024 + j];
        }
    }
}

このコードはより大きなループの一部であり、コードの最も単純な部分であるため、thiaを並列化すると同時にCUDAを学習することにしました。私はガイドを読みましたが、それでも適切な番号を取得する方法を理解していません。グリッド/ブロック/スレッドの数が増え、それらを効果的に使用します。

20
user656210

あなたが書いたように、そのカーネルは完全にシリアルです。それを実行するために起動されたすべてのスレッドは、同じ作業を実行します。

CUDA(およびOpenCLおよび他の同様の「単一プログラム、複数データ」タイプのプログラミングモデル)の背後にある主な考え方は、「データ並列」操作を実行することです。つまり、同じ、ほぼ独立した操作を何度も実行する必要があります。その操作を実行するカーネルを作成します。次に、入力データセット全体でその操作を実行するために、多数の(半)自律スレッドが起動されます。

配列の追加の例では、データの並列操作は次のとおりです。

C[k] = A[k] + B[k];

0から128 * 1024までのすべてのkに対して。各加算操作は完全に独立しており、順序付けの要件がないため、異なるスレッドで実行できます。これをCUDAで表現するには、次のようにカーネルを記述します。

__global__ void mAdd(float* A, float* B, float* C, int n)
{
    int k = threadIdx.x + blockIdx.x * blockDim.x;

    if (k < n)
        C[k] = A[k] + B[k];
}

[免責事項:ブラウザで記述されたコード、テストされていない、自己責任で使用]

ここでは、シリアルコードの内側と外側のループが操作ごとに1つのCUDAスレッドに置き換えられ、必要な操作よりも多くのスレッドが起動された場合にバッファオーバーフローが発生しないように、コードに制限チェックを追加しました。その後、カーネルが次のように起動された場合:

const int n = 128 * 1024;
int blocksize = 512; // value usually chosen by tuning and hardware constraints
int nblocks = n / nthreads; // value determine by block size and total work

madd<<<nblocks,blocksize>>>mAdd(A,B,C,n);

次に、それぞれ512スレッドを含む256ブロックが、GPUハードウェア上で起動され、アレイの追加操作が並行して実行されます。入力データサイズがブロックサイズのニースラウンド倍数として表現できない場合は、入力データセット全体をカバーするためにブロック数を切り上げる必要があることに注意してください。

上記のすべては、非常に簡単な操作のためのCUDAパラダイムの非常に単純化された概要ですが、おそらくそれはあなたが自分自身を続けるのに十分な洞察を与えるでしょう。 CUDAは最近かなり成熟しており、この回答で私が説明したプログラミングモデルの多くの側面をさらに明らかにするために使用できる、優れた無料の教材がWeb上にたくさんあります。

37
talonmies