これは、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で質問する適切な方法ではない場合、言い訳またはアドバイスしてください。
その答えには2つの部分があります(私が書いた)。 1つは定量化が容易で、もう1つは経験的です。
これは数量化が簡単な部分です。現在のCUDAプログラミングガイドの付録Fには、カーネル起動がブロックごとにいくつのスレッドを持つことができるかを制限する多くのハード制限がリストされています。これらのいずれかを超えると、カーネルは実行されません。大まかに要約すると、次のようになります。
これらの制限内に留まると、正常にコンパイルできるカーネルがエラーなしで起動します。
これは経験的な部分です。上記のハードウェア制約内で選択するブロックごとのスレッド数は、ハードウェアで実行されるコードのパフォーマンスに影響を与える可能性があり、実際に影響します。各コードの振る舞いは異なり、それを定量化する唯一の実際の方法は、慎重なベンチマークとプロファイリングによるものです。しかし、ここでも大まかに要約します。
2番目のポイントは巨大なトピックであり、誰もがStackOverflowの単一の回答でそれをカバーしようとは思わないでしょう。問題の側面の定量的分析について博士論文を書いている人がいます( このプレゼンテーション UC BerkleyのVasily Volkovと この論文 トロント大学のHenry Wongを参照)質問が実際にどれほど複雑であるかの例)。
エントリレベルでは、選択したブロックサイズ(上記の制約で定義された正当なブロックサイズの範囲内)がコードの実行速度に影響を与える可能性があり、それがハードウェアに依存することにほとんど注意してください実行しているコードがあります。ベンチマークを行うことで、ほとんどの非自明なコードには、ブロック範囲ごとの128〜512スレッドに「スイートスポット」があることがわかりますが、それがどこにあるかを調べるには、ある程度の分析が必要です。幸いなことに、ワープサイズの倍数で作業しているため、検索スペースは非常に有限であり、特定のコードに対して最適な構成が比較的簡単に見つかります。
上記の回答は、ブロックサイズがパフォーマンスにどのように影響するかを示し、占有率の最大化に基づいて選択するための一般的なヒューリスティックを示しています。ブロックサイズを選択するための基準を提供したくない場合、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");
}
編集
cudaOccupancyMaxPotentialBlockSize
はcuda_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
を使用できます。
通常、ブロックサイズは「占有率」を最大化するために選択されます。詳細については、CUDA Occupancyを検索してください。特に、CUDA Occupancy Calculatorスプレッドシートをご覧ください。