Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

20190625 OpenACC 講習会 第2部

第23回 GPUコンピューティング講習会 - OpenACC講習会 -
http://gpu-computing.gsic.titech.ac.jp/node/97

  • Login to see the comments

20190625 OpenACC 講習会 第2部

  1. 1. 第2部 – OpenACC によるデータ移動の管理 Naruhiko Tan, Solution Architect, NVIDIA OPENACC 講習会
  2. 2. COURSE OBJECTIVE 参加者のみなさんが、 OpenACC を⽤いてみなさん ⾃⾝のアプリケーションを加速 できるようにすること
  3. 3. 第2部のアウトライン カバーするトピック § CPU と GPU メモリ § CUDA Unified (Managed) Memory § OpenACC データ管理
  4. 4. 第1部の復習
  5. 5. OPENACC による開発サイクル 分析 並列化最適化 § 分析 - コードを分析し、並列化や最適 化が必要と思われる箇所を⾒出す。 § 並列化 - 最も処理に時間がかかる箇所 からコードの並列化を⾏う。計算結果が正 しいことも確認する。 § 最適化 – 並列化によって得られた性能 から、さらなる⾼速化を図る。
  6. 6. OpenACC ディレクティブ データ移動の 管理 並列実⾏の 開始 ループ マッピングの 最適化 #pragma acc data copyin(a,b) copyout(c) { ... #pragma acc parallel { #pragma acc loop gang vector for (i = 0; i < n; ++i) { c[i] = a[i] + b[i]; ... } } ... } CPU, GPU, Manycore 移植性 相互操作可能 1つのソース コード 段階的
  7. 7. OpenACC ディレクティブ 第2部︓ データ移動の 管理 第1部︓ 並列実⾏の 開始 #pragma acc data copyin(a,b) copyout(c) { ... #pragma acc parallel { #pragma acc loop gang vector for (i = 0; i < n; ++i) { c[i] = a[i] + b[i]; ... } } ... } CPU, GPU, Manycore 移植性 相互操作可能 1つのソース コード 段階的 第3部︓ ループマッピン グの最適化
  8. 8. while ( err > tol && iter < iter_max ) { err=0.0; #pragma acc parallel loop reduction(max:err) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[OFFSET(j, i, m)] = 0.25 * ( A[OFFSET(j, i+1, m)] + A[OFFSET(j, i-1, m)] + A[OFFSET(j-1, i, m)] + A[OFFSET(j+1, i, m)]); error = fmax( error, fabs(Anew[OFFSET(j, i, m)] - A[OFFSET(j, i , m)])); } } #pragma acc parallel loop for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[OFFSET(j, i, m)] = Anew[OFFSET(j, i, m)]; } } iter++; } OPENACC PARALLEL LOOP による並列化 8 最初のループを並列化 max reduction を指定 2つ⽬のループを並列化 どのループを並列化するかのみを指⽰し、 どのように並列化するかの詳細は指⽰ していない。
  9. 9. OPENACC による⾼速化 1.00X 3.05X 10.18X 37.14X 0.00X 5.00X 10.00X 15.00X 20.00X 25.00X 30.00X 35.00X 40.00X SERIAL MULTICORE NVIDIA TESLA K80 NVIDIA TESLA V100 Speed-Up Speed-up PGI 18.7, NVIDIA Tesla V100, Intel i9-7900X CPU @ 3.30GHz
  10. 10. CPU と GPU メモリ
  11. 11. CPU + GPU 構成図 § 容量は CPU メモリの⽅が⼤きく、バンド幅は GPU メ モリの⽅が広い。 § CPU と GPU のメモリはそれぞれ独⽴しており、何らか の I/O バスによって接続されている (伝統的には PCI-e が⽤いられる)。 § CPU – GPU 間で転送されるあらゆるデータは、I/O バ スを通して⾏われる。 § GPU のメモリバンド幅に⽐べて、I/O バスは遅い。 § GPU メモリ上にデータがない限り、GPU は計算を⾏う ことができない。 High Capacity Memory Shared Cache High Bandwidth Memory Shared Cache $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ IO Bus GPUCPU
  12. 12. CUDA UNIFIED MEMORY
  13. 13. 開発者の労⼒を軽減 Without Managed Memory With Managed Memory Managed Memoryシステム メモリ GPU メモリ “Managed memory” とも呼ばれる CUDA UNIFIED MEMORY CPU と GPU のメモリが 1つの共有メモリ空間として統合
  14. 14. CUDA MANAGED MEMORY § 明⽰的にホスト-デバイス (CPU-GPU) 間のデータ転送を取り扱うのは、難しい場合がある。 § CUDA Managed Memory を活⽤することにより、データ管理を PGI コンパイラに任せることができ る。 § これにより、開発者はデータ管理については最適化としてとらえて、まずは並列化に集中することが できる。 有⽤性 $ pgcc –fast –acc –ta=tesla:managed –Minfo=accel main.c $ pgfortran –fast –acc –ta=tesla:managed –Minfo=accel main.f90
  15. 15. MANAGED MEMORY § ほぼ全ての場合において、⼿動でデータ転送を⾏うこと で、より良いパフォーマンスを得ることができる。 § メモリの確保/開放は、managed memory を⽤いた ⽅がより時間がかかる。 § データ転送を⾮同期で⾏うことはできない。 § 今現在、PGI コンパイラと NVIDIA GPU の組み合わせ でのみ利⽤可能。 制限事項 With Managed Memory Managed Memory
  16. 16. * Slide Courtesy of PGI
  17. 17. 第1部では UNIFIED MEMORY を利⽤ Why? § PGI と NVIDIA GPU への依存を排除。 § 現状のコードでは、データは “少し遅い” タイミングで GPU に到着しているので、改善してみましょ う。 Unified Memory なしでコードを実⾏してみましょう
  18. 18. 基本的なデータ管理
  19. 19. 基本的なデータ管理 § ホストは CPU。 § デバイスは何らかのアクセラレータ。 § ターゲット ハードウェアがマルチ コアCPUの場合、ホ ストとデバイスは同⼀となる。つまり、メモリも同⼀と なる。 § マルチ コアのように、メモリ空間が共有されたアクセ ラレータを使う場合は、明⽰的なデータ管理の必要 はない。 ホスト - デバイス間 Host Device Host Memory Device Memory
  20. 20. 基本的なデータ管理 § ターゲット ハードウェアが GPU の場合、CPU-GPU 間でのデータ転送が必要となる。 § GPU で使われる配列は、GPU 上でメモリ確保され ていなければならない。 § CPU か GPU 上のデータが更新された場合、もう⼀ ⽅のデータも更新しなければならない。 ホスト – デバイス間 High Capacity Memory Shared Cache High Bandwidth Memory Shared Cache $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ IO Bus GPUCPU
  21. 21. “MANAGED” オプションなしでビルド pgcc -ta=tesla -Minfo=accel laplace2d.c jacobi.c laplace2d.c: PGC-S-0155-Compiler failed to translate accelerator region (see -Minfo messages): Could not find allocated-variable index for symbol (laplace2d.c: 47) calcNext: 47, Accelerator kernel generated Generating Tesla code 48, #pragma acc loop gang /* blockIdx.x */ Generating reduction(max:error) 50, #pragma acc loop vector(128) /* threadIdx.x */ 48, Accelerator restriction: size of the GPU copy of Anew,A is unknown 50, Loop is parallelizable PGC-F-0704-Compilation aborted due to previous errors. (laplace2d.c) PGC/x86-64 Linux 18.7-0: compilation aborted jacobi.c: –ta=tesla:managed から “managed” を削除
  22. 22. データ整形
  23. 23. DATA クローズ copy ( list ) GPU 上にメモリを確保し、データ領域に⼊る際に、ホストから GPU にデータを コピーし、データ領域から出る際に、GPU からホストにデータをコピー。 主な使⽤例: GPU へのインプットとなり、かつ上書きされ、ホストに返される 様々なデータについては、これがデフォルト。 copyin ( list ) GPU 上にメモリを確保し、データ領域に⼊る際に、ホストから GPU にデータを コピー。 主な使⽤例: あるサブルーチンに対するインプット配列とみなせる。 copyout ( list ) GPU 上にメモリを確保し、データ領域から出る際に、GPU からホストにデータを コピー。 主な使⽤例: インプット データを上書きしない計算結果。 create ( list ) GPU 上にメモリを確保するが、データ コピーは⾏わない。 主な使⽤例: ⼀時配列。
  24. 24. 配列の整形 § コンパイラが配列の形を理解するのに、プログラマの助けが必要な場合がある。 § 最初の数字は、配列要素の最初のインデックス。 § C/C++ では、2番めの数字は転送される配列の⻑さ。 § Fortran では、2番めの数字は最後のインデックス。 copy(array(starting_index:ending_index)) copy(array[starting_index:length]) C/C++ Fortran
  25. 25. 配列の整形 (続き) 多次元配列 copy(array(1:N, 1:M)) copy(array[0:N][0:M]) C/C++ Fortran これらの例は、2次元配列をデバイスにコピーする
  26. 26. 配列の整形 (続き) 部分配列 copy(array(i*N/4:i*N/4+N/4)) copy(array[i*N/4:N/4]) C/C++ Fortran これらの例は、配列全体の ¼ のみデバイスにコピーする
  27. 27. while ( err > tol && iter < iter_max ) { err=0.0; #pragma acc parallel loop reduction(max:err) copyin(A[0:n*m]) copy(Anew[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[OFFSET(j, i, m)] = 0.25 * ( A[OFFSET(j, i+1, m)] + A[OFFSET(j, i-1, m)] + A[OFFSET(j-1, i, m)] + A[OFFSET(j+1, i, m)]); error = fmax( error, fabs(Anew[OFFSET(j, i, m)] - A[OFFSET(j, i , m)])); } } #pragma acc parallel loop copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[OFFSET(j, i, m)] = Anew[OFFSET(j, i, m)]; } } iter++; } 最適化されたデータ転送 データ クローズで配列 形状を指定
  28. 28. “MANAGED” オプションなしでビルド pgcc -ta=tesla -Minfo=accel laplace2d.c jacobi.c laplace2d.c: calcNext: 47, Generating copyin(A[:m*n]) Accelerator kernel generated Generating Tesla code 48, #pragma acc loop gang /* blockIdx.x */ Generating reduction(max:error) 50, #pragma acc loop vector(128) /* threadIdx.x */ 47, Generating implicit copy(error) Generating copy(Anew[:m*n]) 50, Loop is parallelizable swap: 62, Generating copyin(Anew[:m*n]) Generating copyout(A[:m*n]) Accelerator kernel generated Generating Tesla code 63, #pragma acc loop gang /* blockIdx.x */ 65, #pragma acc loop vector(128) /* threadIdx.x */ 65, Loop is parallelizable jacobi.c: –ta=tesla:managed から “managed” を削除
  29. 29. OPENACC による ⾼速化 低速化︖ 1.00X 3.23X 41.80X 0.33X 0.00X 5.00X 10.00X 15.00X 20.00X 25.00X 30.00X 35.00X 40.00X 45.00X SERIAL MULTICORE V100 V100 (DATA CLAUSES) Speed-Up Speed-up
  30. 30. 問題は何か︖ § Magaged memory オプションなしでビルドするのに必要な情報は全て揃っているはずだが、実⾏ 時間が⼤幅に増加。 § ここでプロファイリング ツールが活躍。
  31. 31. アプリケーションのプロファイル (2 STEPS)
  32. 32. アプリケーションのプロファイル (2 STEPS) データコピー
  33. 33. 実⾏時間のブレークダウン Data Copy H2D Data Copy D2H CalcNext Swap ほとんど全ての時間が ホスト-デバイス間の データ転送に費やされ ている
  34. 34. while ( err > tol && iter < iter_max ) { err=0.0; #pragma acc parallel loop reduction(max:err) copyin(A[0:n*m]) copy(Anew[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[OFFSET(j, i, m)] = 0.25 * ( A[OFFSET(j, i+1, m)] + A[OFFSET(j, i-1, m)] + A[OFFSET(j-1, i, m)] + A[OFFSET(j+1, i, m)]); error = fmax( error, fabs(Anew[OFFSET(j, i, m)] - A[OFFSET(j, i , m)])); } } #pragma acc parallel loop copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[OFFSET(j, i, m)] = Anew[OFFSET(j, i, m)]; } } iter++; } 最適化されたデータ転送 while ループのイタレー ションごとにデータをコ ピーしているが、それら を再利⽤可能︖
  35. 35. 最適化されたデータ転送
  36. 36. OPENACC DATA ディレクティブ § data ディレクティブは、個々のループをま たいで、データがデバイスに存在するライフ タイムを定義する。 § データ領域中は、デバイスがデータを所有 している。 § data クローズは、データ領域でのデータ 転送と形状を表現する。 定義 #pragma acc data clauses { < Sequential and/or Parallel code > } !$acc data clauses < Sequential and/or Parallel code > !$acc end data
  37. 37. STRUCTURED DATA ディレクティブ 使⽤例 #pragma acc data copyin(a[0:N],b[0:N]) copyout(c[0:N]) { #pragma acc parallel loop for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; } } 動作 ホスト メモリ デバイス メモリ A B C デバイス上に A の メモリ領域を確保 A を CPU からデ バイスにコピー A デバイス上に B の メモリ領域を確保 B を CPU から デバイスにコピー B デバイス上に C の メモリ領域を確保 デバイス上で ループを実⾏ C’ C をデバイスから CPU へコピー C’ デバイス上の C を解放 デバイス上の B を解放 デバイス上の A を解放
  38. 38. #pragma acc data copy(A[0:n*m]) copyin(Anew[0:n*m]) while ( err > tol && iter < iter_max ) { err=0.0; #pragma acc parallel loop reduction(max:err) copyin(A[0:n*m]) copy(Anew[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { Anew[OFFSET(j, i, m)] = 0.25 * ( A[OFFSET(j, i+1, m)] + A[OFFSET(j, i-1, m)] + A[OFFSET(j-1, i, m)] + A[OFFSET(j+1, i, m)]); error = fmax( error, fabs(Anew[OFFSET(j, i, m)] - A[OFFSET(j, i , m)])); } } #pragma acc parallel loop copyin(Anew[0:n*m]) copyout(A[0:n*m]) for( int j = 1; j < n-1; j++) { for( int i = 1; i < m-1; i++ ) { A[OFFSET(j, i, m)] = Anew[OFFSET(j, i, m)]; } } iter++; } 最適化されたデータ転送 必要な時のみ A をコピー。 Anew の初期条件はコピーす るが、最終的な値はしない。
  39. 39. コードを再度ビルド pgcc -fast -ta=tesla -Minfo=accel laplace2d_uvm.c main: 60, Generating copy(A[:m*n]) Generating copyin(Anew[:m*n]) 64, Accelerator kernel generated Generating Tesla code 64, Generating reduction(max:error) 65, #pragma acc loop gang /* blockIdx.x */ 67, #pragma acc loop vector(128) /* threadIdx.x */ 67, Loop is parallelizable 75, Accelerator kernel generated Generating Tesla code 76, #pragma acc loop gang /* blockIdx.x */ 78, #pragma acc loop vector(128) /* threadIdx.x */ 78, Loop is parallelizable データ転送はデータ領域で のみ発生。
  40. 40. OPENACC による⾼速化 1.00X 3.23X 41.80X 42.99X 0.00X 5.00X 10.00X 15.00X 20.00X 25.00X 30.00X 35.00X 40.00X 45.00X 50.00X SERIAL MULTICORE V100 V100 (DATA) Speed-Up Speed-up
  41. 41. これまでに学んだこと § CUDA Unified (Managed) Memory はポーティングのための強⼒なツール。 § Managed memory を活⽤せずに GPU プログラミングを⾏う場合は、データ転送に際して配列の 形状を指定する必要がある。 § 各ループごとにデータ転送を⾏うのは、概して⾮効率。 § OpenACC data 領域によって、データ転送と計算を別々に管理することができる。
  42. 42. データの同期
  43. 43. update: ホストとデバイス間でデータを明⽰的に転送する データ領域の途中でデータを同期したい場合に有効 クローズ: self: ホスト側のデータをデバイス側のデータと⼀致させる device: デバイス側のデータをホスト側のデータと⼀致させる #pragma acc update self(x[0:count]) #pragma acc update device(x[0:count]) !$acc update self(x(1:end_index)) !$acc update device(x(1:end_index)) Fortran C/C++ OPENACC UPDATE ディレクティブ
  44. 44. BB* A*A OPENACC UPDATE ディレクティブ A CPU Memory device Memory #pragma acc update device(A[0:N]) B* #pragma acc update self(B[0:N]) update ディレクティブが動作 するためには、CPU とデバイ ス両⽅にデータが存在してい る必要がある。
  45. 45. UPDATE でデータ同期 int* A=(int*) malloc(N*sizeof(int)) #pragma acc data create(A[0:N]) while( timesteps++ < numSteps ) { #pragma acc parallel loop for(int i = 0; i < N; i++){ a[i] *= 2; } if (timestep % 100 ) { #pragma acc update self(A[0:N]) checkpointAToFile(A, N); } } § データ領域中で、ホストあるいはデバイス側のデー タが上書きされる時がある。 § データ領域を終了し、再度新しいデータ領域を開 始するのはコストがかかる。 § 代わりに、ホストとデバイス側のデータが同⼀となる よう更新。 § 例︓ファイル I/O、通信、etc…
  46. 46. ⾮構造 DATA ディレクティブ
  47. 47. ⾮構造 DATA ディレクティブ § データのライフタイムは、必ずしもきれいに構造 化されているとは限らない。 § Enter data ディレクティブはデバイスのメモリ 確保を処理する。 § メモリ確保のために、create か copyin のど ちらかを使うことができる。 § 複数の enter data ディレクティブを挿⼊する ことができるので、その enter data ディレク ティブが、data 領域の開始とは限らない。 Enter Data ディレクティブ #pragma acc enter data clauses < Sequential and/or Parallel code > #pragma acc exit data clauses !$acc enter data clauses < Sequential and/or Parallel code > !$acc exit data clauses
  48. 48. ⾮構造 DATA ディレクティブ § Exit data では、デバイスのメモリ解放を処理 する。 § メモリ解放のために、delete か copyout のど ちらかを使うことができる。 § ある配列に対して、enter data の数だけ exit data を挿⼊する。 § これらは、別々の関数に挿⼊することができる。 Exit Data ディレクティブ #pragma acc enter data clauses < Sequential and/or Parallel code > #pragma acc exit data clauses !$acc enter data clauses < Sequential and/or Parallel code > !$acc exit data clauses
  49. 49. ⾮構造 DATA クローズ copyin ( list ) enter data に際し、デバイス上にメモリを確保し、ホストからデバイスへデータ をコピー。 copyout ( list ) exit data に際し、デバイスからホストへデータをコピーし、デバイス上のメモリ を解放。 create ( list ) enter data に際し、データ転送なしでデバイス上にメモリを確保。 delete ( list ) exit data に際し、データ転送なしでデバイス上のメモリを解放。
  50. 50. ⾮構造 DATA ディレクティブ 基本的な例 #pragma acc parallel loop for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; }
  51. 51. ⾮構造 DATA ディレクティブ 基本的な例 #pragma acc enter data copyin(a[0:N],b[0:N]) create(c[0:N]) #pragma acc parallel loop for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; } #pragma acc exit data copyout(c[0:N]) delete(a,b)
  52. 52. ⾮構造 VS. 構造 簡単なコードを例に #pragma acc enter data copyin(a[0:N],b[0:N]) create(c[0:N]) #pragma acc parallel loop for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; } #pragma acc exit data copyout(c[0:N]) delete(a,b) #pragma acc data copyin(a[0:N],b[0:N]) copyout(c[0:N]) { #pragma acc parallel loop for(int i = 0; i < N; i++){ c[i] = a[i] + b[i]; } } ⾮構造 構造 § 複数の開始/終了ポイントを持ち得る。 § 異なる関数にまたがることができる。 § 解放されるまではメモリは確保されたまま。 § 明⽰的に開始/終了ポイントが必要。 § 1つの関数内に収まる必要がある。 § メモリはデータ領域中のみ確保される。
  53. 53. ⾮構造 DATA ディレクティブ 複数の関数をまたいでの利⽤ int* allocate_array(int N){ int* ptr = (int *) malloc(N * sizeof(int)); #pragma acc enter data create(ptr[0:N]) return ptr; } void deallocate_array(int* ptr){ #pragma acc exit data delete(ptr) free(ptr); } int main(){ int* a = allocate_array(100); #pragma acc kernels { a[0] = 0; } deallocate_array(a); } § この例では、enter data と exit data がそれぞれ 異なる関数で宣⾔されている。 § プログラマはデバイス メモリの確保/解放をホスト メ モリのそれと対応させることができる。 § C++ のような、構造化スコープの設定が難しい場 合に特に有効。
  54. 54. C/C++ 構造体/クラス
  55. 55. typedef struct { float x, y, z; } float3; int main(int argc, char* argv[]){ int N = 10; float3* f3 = malloc(N * sizeof(float3)); #pragma acc enter data create(f3[0:N]) #pragma acc kernels for(int i = 0; i < N; i++){ f3[i].x = 0.0f; f3[i].y = 0.0f; f3[i].z = 0.0f; } #pragma acc exit data delete(f3) free(f3); } C 構造体 動的なメンバなしの場合 § 動的なメンバとは、動的にメモリ確保される配 列のような、構造体内の可変サイズを持つメ ンバのこと。 § Float3 構造体のメンバは全て固定⻑の変数 なので、OpenACC はこの構造体をデバイス メモリにコピーすることができる。 § 動的にメモリが確保される配列が構造体に含 まれていたらどうなるか︖
  56. 56. C 構造体 動的なメンバを含む場合 typedef struct { float *arr; int n; } vector; int main(int argc, char* argv[]){ vector v; v.n = 10; v.arr = (float*) malloc(v.n*sizeof(float)); #pragma acc enter data copyin(v) #pragma acc enter data create(v.arr[0:v.n]) ... #pragma acc exit data delete(v.arr) #pragma acc exit data delete(v) free(v.arr); } § OpenACC は構造体とその動的メンバをコ ピーするのに⼗分な情報を与えられていない。 § まず最初に構造体をデバイス メモリにコピーし、 その後に動的メンバのメモリを確保、あるいは コピーする。 § メモリ解放時は、動的メンバ、構造体の順に 処理する。 § OpenACC は動的メンバを⾃動的に構造体 にアタッチする。
  57. 57. おわりに
  58. 58. キー コンセプト この講義で学んだこと… § CPU と GPU の違いと、Unified Memory。 § OpenACC 配列の整形。 § OpenACC Data クローズ。 § OpenACC 構造 Data 領域。 § OpenACC Update ディレクティブ。 § OpenACC ⾮構造 Data ディレクティブ。

×