0

GPU が 2 つ、カーネルが 1 つ、コンテキストが 1 つ、コマンド キューが 2 つ (GPU ごとに 1 つ) あります。各コマンド キューが実行されるループでそれらを実行しようとしましたが、GPU で同時に作業を実行することを期待して、両方queue.finish()を試しました。queue.flush()

しかし実際には、データが最初に 1 つのデバイスに送信され、GPU がその作業を実行し、次に別の GPU が作業を開始します。単一の GPU の場合の 2 倍の時間がかかります。これは私が達成しようとしているものではありません!

また、バッファーをホスト コードに読み込んでいますが、2 番目の GPU が 1 番目の GPU の結果を待機することが問題になる可能性があると考える人もいるかもしれません。しかし、私はまた、結果の読み取りをコメントアウトしましたが、運がありませんでした。それはまだ同じです。

for (unsigned int iter = 0; iter < numberOfDevices; iter++) {
    // Load in kernel source, creating a program object for the context
     cl::Program programGA(context, stringifiedSourceCL, true);

    // Create the kernel functor
    auto kernelGA = cl::make_kernel<cl::Buffer,
                                    cl::Buffer,
                                    cl::Buffer>
                                    (programGA, "kernelGA");

    // CREATE THE BUFFERS.

    d_pop = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                      (Length * POP_SIZE * sizeof(double)),
                       pop);
    // And other buffers...

    // Enqueue the kernel.
    kernelGA(cl::EnqueueArgs(queue[iter],
                             cl::NDRange(POP_SIZE / numberOfDevices)),
                             d_integerParameters,
                             d_doubleParameters, ... and so on...);

    // Enqueue in the corresponding device.
    queue[iter].flush();

    // Get results from the queue.
    queue[iter].enqueueReadBuffer(buf_half_population,
                                        true,
                                        0,
                                        popSizeMD * sizeof(double),
                                        popMD[iter]);

    // Add up the results after every iteration.
    for (int in_iter = 0; in_iter < populationSizeMD; in_iter++, it_j++) {
         population[it_j] = populationMD[iter][in_iter];
    }
}

私の質問は次のとおりです。真の同時実行性を実現し、他の GPU の結果を待たずに GPU を同時に実行するにはどうすればよいですか? 2 つのコンテキストを作成する必要がありますか? 私は何か他のことをすべきですか?

カーネルが 1 つあることに注意してください

4

1 に答える 1

0

ClFinish はブロッキング コマンドです。

すべてのキューのすべてのコマンドをキューに入れた後、ホスト側の同時実行 + 複数のコンテキスト (デバイスごとに 1 つ) またはすべてのキューの遅延フラッシュ/終了が必要です。

ホスト側の同時実行の場合、

変換

for (unsigned int iter = 0; iter < numberOfDevices; iter++) {...}

Concurrent.for(){} // if there is any for the language you working on

Parallel.For(0,n,i=>{...}); // C#

各反復が並行して行われるようにします。たとえば、Parallel.For は C# で動作しています。次に、バッファーのコピー操作が一致しないように、異なる範囲の配列で作業するようにしてください。PCI-E 帯域幅不足がある場合、最初の反復で gpu-1 にコピーし、gpu-1 で計算 + 2 回目の反復で gpu-2 にコピーし、gpu-1 から結果を取得し、3 番目に gpu-2 で計算できます。反復、最後の反復で gpu-2 から結果を取得します。飢餓がない場合は、次のように、すべてのコピー + すべての計算 + すべての結果を異なるループで実行できます。

Parallel.For( ... copy to gpus)
sync_point() ---> because other gpus result can change some input arrays,
             need to be sure all gpus have their own copies/buffers updated
             but not needed if it is an embarrassingly parallel workload
Parallel.For( ... compute on gpus + get results)

遅延仕上げ/フラッシュの場合:

 for(){...} // divide work into 4-8 parts per gpu, 
               so all gpu can have its turn without waiting much
               computing concurrently between mgpus
 flush1                        
 flush2
 finish1
 finish2

そのため、両者は同時に gpu に作品を発行し始めます。このコードのパフォーマンスは GPU ドライバーに依存する必要がありますが、ホスト側の同時実行パフォーマンスは最適化に依存します。

最初のタイプは、各デバイスのより良いタイミングデータを取得して、すべての gpu で作業を負荷分散できるため、私にとっては簡単です (半分に分割するだけでなく、各 gpu、バッファーコピー、および作業の範囲に費やされた時間に応じて変更します)。ただし、ドライバーがコピーを適切に管理している場合は、2 番目のタイプの方が高速になるはずです。特に、書き込み/読み取りの代わりにマップ/マップ解除を行っている場合は、結果を取得したり gpu にコピーしたりするときに、マップ/マップが cpu の代わりに dma エンジンを使用するためです。

于 2016-10-14T15:40:22.620 に答える