「1 ブロック x 32 スレッド」の構成で開始したいカーネルがあります。並列性を高めるために、「1 ブロック x 32 スレッド」よりも大きな「作業パッケージ」を実行する代わりに、複数のストリームを開始したいと考えています。データがネットワークから来るプログラムで GPU を使用したいと考えています。より大きな「作業パッケージ」が利用可能になるまで待ちたくありません。コードは次のようになります。
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
- copy data GPU -> Host [cudaMemcpyAsync(.., stream i)]
}
実際のコードはもっと複雑ですが、シンプルに保ちたいと思います (15 個の CPU スレッドが GPU を使用します)。
コードは機能しますが、ストリームは期待どおりに同時に実行されません。GTX 480 には 15 の SM があり、各 SM には 32 のシェーダー プロセッサがあります。カーネルを 15 回起動すると、15 個のストリームすべてが並行して実行されると思いますが、そうではありません。私は Nvidia Visual Profiler を使用しており、並行して実行される最大 5 つのストリームがあります。多くの場合、1 つのストリームのみが実行されます。パフォーマンスは本当に悪いです。
「64 ブロック x 1024 スレッド」構成で最良の結果が得られます。代わりに「32 ブロック x 1024 スレッド」構成を使用すると、2 つのストリームが次々に実行され、パフォーマンスが低下します。Cuda Toolkit 5.5 と Ubuntu 12.04 を使用しています。
誰かがこれが事実である理由を説明し、背景情報を教えてもらえますか? 新しい GPU ではうまく動作するはずですか? データをバッファリングしたくないタイムクリティカルなアプリケーションで GPU を使用する最良の方法は何ですか? おそらくこれは不可能ですが、解決策に近づけるテクニックを探しています。
ニュース:
さらに調査を行いました。問題は、最後の cudaMemcpyAsync(..) (GPU->ホスト コピー) 呼び出しです。削除すると、すべてのストリームが同時に実行されます。問題は、スライド 21 のhttp://on-demand.gputechconf.com/gtc-express/2011/presentations/StreamsAndConcurrencyWebinar.pdfに示されていると思います。Fermiには 2 つのコピー キューがあると言われていますが、これはテスラカードとクワドロカードですよね?問題は、GTX 480 にはコピー キューが 1 つしかなく、すべてのコピー コマンド (ホスト -> GPU および GPU -> ホスト) がこの 1 つのキューに入れられることだと思います。すべてがノンブロッキングで、最初のスレッドの GPU->host memcopy は、他のスレッドの host->GPU memcopy 呼び出しをブロックします。ここでいくつかの観察:
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
}
-> 動作: ストリームは同時に実行されます
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
- sleep(10)
- copy data GPU -> Host [cudaMemcpyAsync(.., stream i)]
}
-> 動作: ストリームは同時に実行されます
Thread(i=0..14) {
- copy data Host -> GPU [cudaMemcpyAsync(.., stream i)]
- run kernel(stream i)
- cudaStreamSynchronize(stream i)
- copy data GPU -> Host [cudaMemcpyAsync(.., stream i)]
}
- >動作しません!!! cudaStreamSynchronize が copy-queue に入れられているのではないでしょうか?
誰かがこの問題の解決策を知っていますか。ブロッキング カーネル コールのようなものがクールでしょう。カーネルが終了したら、最後の cudaMemcpyAsync() (GPU->device) を呼び出す必要があります。
Edit2: ここに私の問題を明確にする例があります:単純にするために、2つのストリームがあります:
Stream1:
------------
HostToGPU1
kernel1
GPUToHost1
Stream2:
------------
HostToGPU2
kernel2
GPUToHost2
最初のストリームが開始されます。HostToGPU1 が実行され、kernel1 が起動され、GPUToHost1 が呼び出されます。kernel1 が実行されているため、GPUToHost1 はブロックします。その間に Stream2 が開始されます。HostToGPU2 が呼び出され、Cuda はそれをキューに入れますが、カーネル 1 が終了するまで GPUToHost1 がブロックされるため、実行できません。現在、データ転送はありません。Cuda は GPUToHost1 を待つだけです。したがって、私の考えは、kernel1 が終了したときに GPUToHost1 を呼び出すことでした。これが、カーネルが終了したときに GPUToHost1 が呼び出されるため、sleep(..) で動作する理由であると思われます。CPU スレッドを自動的にブロックするカーネル起動はクールです。GPUToHost1 はキューでブロックされていません (その時点で他にデータ転送がない場合ですが、私の場合はデータ転送に時間がかかりません)。