0

一部の画像処理に nVidia 980 GTX で CUDA 7.0 を使用しています。特定の反復では、15 ~ 20 回のカーネル呼び出しと複数の cuFFT FFT/IFFT API 呼び出しを介して、複数のタイルが個別に処理されます。

このため、各タイルを独自の CUDA ストリーム内に配置して、各タイルが一連の操作をホストに対して非同期で実行するようにしました。各タイルは反復内で同じサイズであるため、cuFFT プランを共有します。ホスト スレッドは、GPU に作業をロードし続けようとして、コマンドをすばやく移動します。これらの操作が並行して処理されている間、定期的な競合状態が発生していますが、特に cuFFT について質問がありました。タイル 0 に対して cuFFTSetStream() を使用してストリーム 0 に cuFFT プランを配置し、ホストが共有 cuFFT プランのストリームをタイル 1 のストリーム 1 に設定する前に、タイル 0 の FFT が実際には GPU でまだ実行されていない場合GPU でタイル 1 の作業を発行します。このプランの cuFFTExec() の動作は何ですか?

より簡潔に言えば、cufftExec() の呼び出しは、前の FFT 呼び出しが実際に開始される前に後続のタイルのストリームを変更するために cuFFTSetStream() が使用されているかどうかに関係なく、cufftExec() 呼び出しの時点でプランが設定されていたストリームで実行されますか? /完成?

コードを投稿していないことをお詫びしますが、実際のソースを投稿することはできません。

4

1 に答える 1

2

EDIT:コメントで指摘されているように、同じプラン(同じ作成されたハンドル)がストリームを介した同じデバイスでの同時FFT実行に使用されている場合、ユーザーはそのようなプランの使用ごとに個別の作業領域を管理する責任があります。質問はストリームの動作自体に焦点を当てているようで、私の残りの回答もそれに焦点を当てていますが、これは重要なポイントです。

タイル 0 に対して cuFFTSetStream() を使用してストリーム 0 に cuFFT プランを配置し、ホストが共有 cuFFT プランのストリームをタイル 1 のストリーム 1 に設定する前に、タイル 0 の FFT が実際には GPU でまだ実行されていない場合GPU でタイル 1 の作業を発行します。このプランの cuFFTExec() の動作は何ですか?

NULL ストリームに関する混乱を避けるために、ストリーム 1 とストリーム 2 と言ったとしましょう。

CUFFT は、計画が を介して CUFFT に渡されたときに計画に対して定義されたストリームを尊重する必要がありますcufftExecXXX()。その後プランを変更しても、以前に発行された呼び出しcufftSetStream()に使用されたストリームには影響しません。cufftExecXXX()

これは、プロファイラーを使用したかなり単純なテストで確認できます。次のテスト コードを検討してください。

$ cat t1089.cu
// NOTE: this code omits independent work-area handling for each plan
// which is necessary for a plan that will be shared between streams
// and executed concurrently
#include <cufft.h>
#include <assert.h>
#include <nvToolsExt.h>

#define DSIZE 1048576
#define BATCH 100

int main(){

  const int nx = DSIZE;
  const int nb = BATCH;
  size_t ws = 0;
  cufftHandle plan;
  cufftResult res = cufftCreate(&plan);
  assert(res == CUFFT_SUCCESS);
  res = cufftMakePlan1d(plan, nx, CUFFT_C2C, nb, &ws);
  assert(res == CUFFT_SUCCESS);
  cufftComplex *d;
  cudaMalloc(&d, nx*nb*sizeof(cufftComplex));
  cudaMemset(d, 0, nx*nb*sizeof(cufftComplex));
  cudaStream_t s1, s2;
  cudaStreamCreate(&s1);
  cudaStreamCreate(&s2);
  res = cufftSetStream(plan, s1);
  assert(res == CUFFT_SUCCESS);
  res = cufftExecC2C(plan, d, d, CUFFT_FORWARD);
  assert(res == CUFFT_SUCCESS);
  res = cufftSetStream(plan, s2);
  assert(res == CUFFT_SUCCESS);
  nvtxMarkA("plan stream change");
  res = cufftExecC2C(plan, d, d, CUFFT_FORWARD);
  assert(res == CUFFT_SUCCESS);
  cudaDeviceSynchronize();
  return 0;
}


$ nvcc -o t1089 t1089.cu -lcufft -lnvToolsExt
$ cuda-memcheck ./t1089
========= CUDA-MEMCHECK
========= ERROR SUMMARY: 0 errors
$

2 つの順方向 FFT を続けて実行し、2 つの間でストリームを切り替えます。nvtxマーカーを使用して、プラン ストリームの関連付けの変更要求が発生するポイントを明確に識別します。次に、出力を見てみましょうnvprof --print-api-trace(長いスタートアップ プリアンブルを削除します)。

983.84ms  617.00us  cudaMalloc
984.46ms  21.628us  cudaMemset
984.48ms  37.546us  cudaStreamCreate
984.52ms  121.34us  cudaStreamCreate
984.65ms     995ns  cudaPeekAtLastError
984.67ms     996ns  cudaConfigureCall
984.67ms     517ns  cudaSetupArgument
984.67ms  21.908us  cudaLaunch (void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [416])
984.69ms     349ns  cudaGetLastError
984.69ms     203ns  cudaPeekAtLastError
984.70ms     296ns  cudaConfigureCall
984.70ms     216ns  cudaSetupArgument
984.70ms  8.8920us  cudaLaunch (void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [421])
984.71ms     272ns  cudaGetLastError
984.71ms     177ns  cudaPeekAtLastError
984.72ms     314ns  cudaConfigureCall
984.72ms     229ns  cudaSetupArgument
984.72ms  9.9230us  cudaLaunch (void spRadix0256B::kernel3Mem<unsigned int, float, fftDirection_t=-1, unsigned int=16, unsigned int=2, L1, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix3_t, unsigned int, float>) [426])
984.73ms     295ns  cudaGetLastError
984.77ms         -  [Marker] plan stream change
984.77ms     434ns  cudaPeekAtLastError
984.78ms     357ns  cudaConfigureCall
984.78ms     228ns  cudaSetupArgument
984.78ms  10.642us  cudaLaunch (void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [431])
984.79ms     287ns  cudaGetLastError
984.79ms     193ns  cudaPeekAtLastError
984.80ms     293ns  cudaConfigureCall
984.80ms     208ns  cudaSetupArgument
984.80ms  7.7620us  cudaLaunch (void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [436])
984.81ms     297ns  cudaGetLastError
984.81ms     178ns  cudaPeekAtLastError
984.81ms     269ns  cudaConfigureCall
984.81ms     214ns  cudaSetupArgument
984.81ms  7.4130us  cudaLaunch (void spRadix0256B::kernel3Mem<unsigned int, float, fftDirection_t=-1, unsigned int=16, unsigned int=2, L1, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix3_t, unsigned int, float>) [441])
984.82ms     312ns  cudaGetLastError
984.82ms  152.63ms  cudaDeviceSynchronize
$

各 FFT 操作には 3 つのカーネル呼び出しが必要であることがわかります。その間に、計画ストリーム変更のリクエストがいつ行われたかを示す nvtx マーカーが表示されます。これが最初の 3 つのカーネルの起動後、最後の 3 つの起動前に行われることは驚くことではありません。実行時間は最後のcudaDeviceSynchronize()呼び出しに吸収されます。前述の呼び出しはすべて非同期であるため、実行の最初のミリ秒で多かれ少なかれ「すぐに」実行されます。最後の同期では、6 つのカーネルのすべての処理時間が吸収され、約 150 ミリ秒になります。

したがってcufftSetStream、呼び出しの最初の繰り返しに影響を与える場合cufftExecC2C()、最初の 3 つのカーネルの一部またはすべてが、最後の 3 つのカーネルに使用されたものと同じストリームに起動されることが予想されます。しかし、nvprof --print-gpu-trace出力を見ると:

$ nvprof --print-gpu-trace ./t1089
==3757== NVPROF is profiling process 3757, command: ./t1089
==3757== Profiling application: ./t1089
==3757== Profiling result:
   Start  Duration            Grid Size      Block Size     Regs*    SSMem*    DSMem*      Size  Throughput           Device   Context    Stream  Name
974.74ms  7.3440ms                    -               -         -         -         -  800.00MB  106.38GB/s  Quadro 5000 (0)         1         7  [CUDA memset]
982.09ms  23.424ms          (25600 2 1)        (32 8 1)        32  8.0000KB        0B         -           -  Quadro 5000 (0)         1        13  void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [416]
1.00551s  21.172ms          (25600 2 1)        (32 8 1)        32  8.0000KB        0B         -           -  Quadro 5000 (0)         1        13  void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [421]
1.02669s  27.551ms          (25600 1 1)       (16 16 1)        61  17.000KB        0B         -           -  Quadro 5000 (0)         1        13  void spRadix0256B::kernel3Mem<unsigned int, float, fftDirection_t=-1, unsigned int=16, unsigned int=2, L1, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix3_t, unsigned int, float>) [426]
1.05422s  23.592ms          (25600 2 1)        (32 8 1)        32  8.0000KB        0B         -           -  Quadro 5000 (0)         1        14  void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [431]
1.07781s  21.157ms          (25600 2 1)        (32 8 1)        32  8.0000KB        0B         -           -  Quadro 5000 (0)         1        14  void spRadix0064B::kernel1Mem<unsigned int, float, fftDirection_t=-1, unsigned int=32, unsigned int=4, CONSTANT, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix1_t, unsigned int, float>) [436]
1.09897s  27.913ms          (25600 1 1)       (16 16 1)        61  17.000KB        0B         -           -  Quadro 5000 (0)         1        14  void spRadix0256B::kernel3Mem<unsigned int, float, fftDirection_t=-1, unsigned int=16, unsigned int=2, L1, ALL, WRITEBACK>(kernel_parameters_t<fft_mem_radix3_t, unsigned int, float>) [441]

Regs: Number of registers used per CUDA thread. This number includes registers used internally by the CUDA driver and/or tools and can be more than what the compiler shows.
SSMem: Static shared memory allocated per CUDA block.
DSMem: Dynamic shared memory allocated per CUDA block.
$

実際、要求どおり、最初の 3 つのカーネルが最初のストリームに発行され、最後の 3 つのカーネルが 2 番目のストリームに発行されていることがわかります。(また、すべてのカーネルの合計実行時間は、API トレース出力で示唆されているように、約 150 ミリ秒です。) 基礎となるカーネルの起動は非同期であり、cufftExecC2C()呼び出しが返される前に発行されるため、これについて慎重に考えると、こうでなければならないという結論に至ります。カーネルを起動するストリームは、カーネルの起動時に指定されます。(そしてもちろん、これは「好ましい」動作と見なされると思います。)

于 2016-02-25T23:21:06.447 に答える