web-dev-qa-db-ja.com

CUDAカーネルのグリッドとブロックの次元を選択するにはどうすればよいですか?

これは、CUDAグリッド、ブロック、およびスレッドのサイズを決定する方法に関する質問です。これは、ここに投稿された質問に対する追加の質問です。

https://stackoverflow.com/a/5643838/1292251

このリンクに続いて、talonmiesからの回答にはコードスニペットが含まれています(以下を参照)。 「チューニングとハードウェアの制約によって通常選択される値」というコメントがわかりません。

CUDAのドキュメントでこれを説明する良い説明や説明が見つかりませんでした。要約すると、私の質問は、次のコードを使用して最適なblocksize(=スレッド数)を決定する方法です。

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);

ところで、最初の質問に部分的に答えているので、上のリンクから質問を始めました。これがStack Overflowで質問する適切な方法ではない場合、言い訳またはアドバイスしてください。

95
user1292251

その答えには2つの部分があります(私が書いた)。 1つは定量化が容易で、もう1つは経験的です。

ハードウェアの制約:

これは数量化が簡単な部分です。現在のCUDAプログラミングガイドの付録Fには、カーネル起動がブロックごとにいくつのスレッドを持つことができるかを制限する多くのハード制限がリストされています。これらのいずれかを超えると、カーネルは実行されません。大まかに要約すると、次のようになります。

  1. 各ブロックに合計で512/1024を超えるスレッドを含めることはできません( Compute Capability 1.xまたは2.x以降)
  2. 各ブロックの最大寸法は[512,512,64]/[1024,1024,64]に制限されています(計算1.x/2.x以降)
  3. 各ブロックは、合計で8k/16k/32k/64k/32k/64k/32k/64k/32k/64kレジスタを消費できません(計算1.0、1.1/1.2、1.3/2.x-/3.0/3.2/3.5-5.2/5.3/6-6.1/6.2/7.0)
  4. 各ブロックは16kb/48kb/96kbを超える共有メモリを消費できません(Compute 1.x/2.x-6.2/7.0)

これらの制限内に留まると、正常にコンパイルできるカーネルがエラーなしで起動します。

性能調整:

これは経験的な部分です。上記のハードウェア制約内で選択するブロックごとのスレッド数は、ハードウェアで実行されるコードのパフォーマンスに影響を与える可能性があり、実際に影響します。各コードの振る舞いは異なり、それを定量化する唯一の実際の方法は、慎重なベンチマークとプロファイリングによるものです。しかし、ここでも大まかに要約します。

  1. ブロックあたりのスレッド数は、ワープサイズのラウンド倍数である必要があります。これは、現在のすべてのハードウェアで32です。
  2. GPUの各ストリーミングマルチプロセッサユニットには、アーキテクチャのさまざまなメモリと命令パイプラインのレイテンシをすべて十分に隠し、最大のスループットを達成するのに十分なアクティブワープが必要です。ここでの正統的なアプローチは、最適なハードウェア占有率を達成しようとすることです( Roger Dahlの答え が指しているもの)。

2番目のポイントは巨大なトピックであり、誰もがStackOverflowの単一の回答でそれをカバーしようとは思わないでしょう。問題の側面の定量的分析について博士論文を書いている人がいます( このプレゼンテーション UC BerkleyのVasily Volkovと この論文 トロント大学のHenry Wongを参照)質問が実際にどれほど複雑であるかの例)。

エントリレベルでは、選択したブロックサイズ(上記の制約で定義された正当なブロックサイズの範囲内)がコードの実行速度に影響を与える可能性があり、それがハードウェアに依存することにほとんど注意してください実行しているコードがあります。ベンチマークを行うことで、ほとんどの非自明なコードには、ブロック範囲ごとの128〜512スレッドに「スイートスポット」があることがわかりますが、それがどこにあるかを調べるには、ある程度の分析が必要です。幸いなことに、ワープサイズの倍数で作業しているため、検索スペースは非常に有限であり、特定のコードに対して最適な構成が比較的簡単に見つかります。

137
talonmies

上記の回答は、ブロックサイズがパフォーマンスにどのように影響するかを示し、占有率の最大化に基づいて選択するための一般的なヒューリスティックを示しています。ブロックサイズを選択するための基準を提供したくない場合、CUDA 6.5(現在リリース候補バージョンにあります)が占有計算を支援するいくつかの新しいランタイム関数を含むことに言及する価値があります起動設定、参照

CUDA Proのヒント:Occupancy APIは起動構成を簡素化します

便利な関数の1つはcudaOccupancyMaxPotentialBlockSizeであり、最大占有率を達成するブロックサイズをヒューリスティックに計算します。その関数によって提供された値は、起動パラメーターの手動最適化の開始点として使用できます。以下は小さな例です。

#include <stdio.h>

/************************/
/* TEST KERNEL FUNCTION */
/************************/
__global__ void MyKernel(int *a, int *b, int *c, int N) 
{ 
    int idx = threadIdx.x + blockIdx.x * blockDim.x; 

    if (idx < N) { c[idx] = a[idx] + b[idx]; } 
} 

/********/
/* MAIN */
/********/
void main() 
{ 
    const int N = 1000000;

    int blockSize;      // The launch configurator returned block size 
    int minGridSize;    // The minimum grid size needed to achieve the maximum occupancy for a full device launch 
    int gridSize;       // The actual grid size needed, based on input size 

    int* h_vec1 = (int*) malloc(N*sizeof(int));
    int* h_vec2 = (int*) malloc(N*sizeof(int));
    int* h_vec3 = (int*) malloc(N*sizeof(int));
    int* h_vec4 = (int*) malloc(N*sizeof(int));

    int* d_vec1; cudaMalloc((void**)&d_vec1, N*sizeof(int));
    int* d_vec2; cudaMalloc((void**)&d_vec2, N*sizeof(int));
    int* d_vec3; cudaMalloc((void**)&d_vec3, N*sizeof(int));

    for (int i=0; i<N; i++) {
        h_vec1[i] = 10;
        h_vec2[i] = 20;
        h_vec4[i] = h_vec1[i] + h_vec2[i];
    }

    cudaMemcpy(d_vec1, h_vec1, N*sizeof(int), cudaMemcpyHostToDevice);
    cudaMemcpy(d_vec2, h_vec2, N*sizeof(int), cudaMemcpyHostToDevice);

    float time;
    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    cudaEventRecord(start, 0);

    cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, MyKernel, 0, N); 

    // Round up according to array size 
    gridSize = (N + blockSize - 1) / blockSize; 

    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&time, start, stop);
    printf("Occupancy calculator elapsed time:  %3.3f ms \n", time);

    cudaEventRecord(start, 0);

    MyKernel<<<gridSize, blockSize>>>(d_vec1, d_vec2, d_vec3, N); 

    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&time, start, stop);
    printf("Kernel elapsed time:  %3.3f ms \n", time);

    printf("Blocksize %i\n", blockSize);

    cudaMemcpy(h_vec3, d_vec3, N*sizeof(int), cudaMemcpyDeviceToHost);

    for (int i=0; i<N; i++) {
        if (h_vec3[i] != h_vec4[i]) { printf("Error at i = %i! Host = %i; Device = %i\n", i, h_vec4[i], h_vec3[i]); return; };
    }

    printf("Test passed\n");

}

編集

cudaOccupancyMaxPotentialBlockSizecuda_runtime.hファイルで定義され、次のように定義されます。

template<class T>
__inline__ __Host__ CUDART_DEVICE cudaError_t cudaOccupancyMaxPotentialBlockSize(
    int    *minGridSize,
    int    *blockSize,
    T       func,
    size_t  dynamicSMemSize = 0,
    int     blockSizeLimit = 0)
{
    return cudaOccupancyMaxPotentialBlockSizeVariableSMem(minGridSize, blockSize, func, __cudaOccupancyB2DHelper(dynamicSMemSize), blockSizeLimit);
}

パラメーターの意味は次のとおりです

minGridSize     = Suggested min grid size to achieve a full machine launch.
blockSize       = Suggested block size to achieve maximum occupancy.
func            = Kernel function.
dynamicSMemSize = Size of dynamically allocated shared memory. Of course, it is known at runtime before any kernel launch. The size of the statically allocated shared memory is not needed as it is inferred by the properties of func.
blockSizeLimit  = Maximum size for each block. In the case of 1D kernels, it can coincide with the number of input elements.

CUDA 6.5では、APIによって提案された1Dブロックサイズから自分の2D/3Dブロックの寸法を計算する必要があることに注意してください。

また、CUDAドライバーAPIには占有計算用の機能的に同等のAPIが含まれているため、上記の例のランタイムAPIで示したのと同じ方法でドライバーAPIコードで cuOccupancyMaxPotentialBlockSize を使用できます。

34
JackOLantern

通常、ブロックサイズは「占有率」を最大化するために選択されます。詳細については、CUDA Occupancyを検索してください。特に、CUDA Occupancy Calculatorスプレッドシートをご覧ください。

10
Roger Dahl