Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.
第15回 CPUとGPUの協調
長岡技術科学大学 電気電子情報工学専攻 出川智啓
今回の内容
2015/07/23先端GPGPUシミュレーション工学特論2
 並列処理と並行処理
 ストリーム
 複数ストリームを用いたベクトル和の計算
 CPUとGPUを用いたベクトル和の計算
並列処理と並行処理
2015/07/23先端GPGPUシミュレーション工学特論3
 並列処理(Parallel Processing)
 一つの処理を複数の処理に分割
 協調しながら複数の処理を実行
 プログラムを高速化するために行われ...
CUDAプログラムの実行
 実行時の流れ(CPU視点)
 利用するGPUの初期化やデータの転送などを実行
 GPUで実行する関数を呼び出し
 GPUから結果を取得
初期化の指示
初期化
カーネルの実行指示
カーネルを実行
結果の取得
実...
GPUとCPUの並行実行
2015/07/23先端GPGPUシミュレーション工学特論5
 GPUのみの処理
float *a = (float *)malloc(NBytes);
float *dev_a;
cudaMalloc((void ...
GPUとCPUの並行実行
2015/07/23先端GPGPUシミュレーション工学特論6
 GPUとCPUの非同期処理
float *a = (float *)malloc(NBytes), *dev_a;
cudaMalloc((void *...
GPUとCPUの並行実行
2015/07/23先端GPGPUシミュレーション工学特論7
 GPUとCPUが非同期で行う処理とは
 cudaMemcpy
 同期通信(synchronous)
 同期が完了するまでCPUとGPUが他の処理を...
GPUの並行実行(要望)
2015/07/23先端GPGPUシミュレーション工学特論8
 通信(データ転送)を同時に行いたい
 通信(データ転送)している間に計算を実行したい
 負荷の軽い計算を複数同時に実行したい
float *a, *...
GPUの並行実行(要望)
2015/07/23先端GPGPUシミュレーション工学特論9
cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, b, NBy...
GPUの並行実行(要望)
2015/07/23先端GPGPUシミュレーション工学特論10
cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, b, NB...
Concurrent Kernel Execution
2015/07/23先端GPGPUシミュレーション工学特論11
 GPUが並行に処理を実行可能かの確認
 cudaGetDeviceProperties()のメンバdeviceOver...
GPUの並行実行
2015/07/23先端GPGPUシミュレーション工学特論12
 データ通信とカーネル実行を並行に実行可能
 データ通信と並行に実行できるカーネルは一つ
 CPUからGPUへの転送,GPUからCPUへの転送,カーネル実
...
ストリーム
2015/07/23先端GPGPUシミュレーション工学特論13
 GPUで実行される処理の流れ
 同じストリームに属する処理は,必ず命令発行され
た順に実行される
 異なるストリームに属する処理は並行に実行できる
 異なるス...
標準のストリーム(ストリーム0)
2015/07/23先端GPGPUシミュレーション工学特論14
 ストリームを指定しない場合はストリーム0に所属
 ストリーム0は他の処理とは同時に実行されない
 ストリーム0はCPUと同期して実行
 ...
float *a, *b, *dev_a, *dev_b;
cudaStream_t stream1, stream2;
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);
cudaM...
ストリームを用いた並行実行
2015/07/23先端GPGPUシミュレーション工学特論16
 ストリームの作成(具体的に番号を付けるわけではない)
 cudaStreamCreate(cudaStream_t *);
 ストリームの破棄
...
ストリームを用いた並行実行
2015/07/23先端GPGPUシミュレーション工学特論17
 cudaMemcpyAsync
 非同期転送(asynchronous)
 5番目の引数としてストリームを指定
 CPUのメモリには必ずページ...
並行実行の同期
2015/07/23先端GPGPUシミュレーション工学特論18
 cudaDeviceSynchronize()
 GPUで実行している全ての処理が完了するまで待機
 cudaStreamSynchronize(ストリーム...
ベクトル和C=A+Bの並行実行
2015/07/23先端GPGPUシミュレーション工学特論19
 並行実行の方針
 複数のストリームを作成
 配列のまとまったサイズを異なるストリームに所属
 A, Bの初期化はCPUで実行し,GPUへ転...
ベクトル和C=A+Bの並行実行
2015/07/23先端GPGPUシミュレーション工学特論20
 同期実行
 並行実行
 処理をオーバーラップできれば若干高速化
Aを転送(H2D) Bを転送(H2D) add dev_Cを転送(D2H)
...
#include<stdio.h>
#include<stdlib.h>
#include<omp.h>
#define N (1024*1024*8)
#define Nbytes (N*sizeof(float))
#define NT 2...
int main(){
float *a,*b,*c;
int stm;
cudaStream_t stream[Stream];
//ページロックホストメモリを確保
float *host_a, *host_b, *host_c;
cudaH...
double time_start = omp_get_wtime();
for(stm=0;stm<Stream;stm++){
int idx = stm*N/Stream;
cudaMemcpyAsync(&a[idx], &host_a...
double sum=0;
for(int i=0;i<N;i++)sum+=host_c[i];
printf("%f¥n",sum/N);
printf("elapsed time = %f sec¥n",
time_end‐time_st...
実行結果
2015/07/23先端GPGPUシミュレーション工学特論25
 配列の要素数 N=223
 1ブロックあたりのスレッド数 256
 OpenMPの関数を用いて実行時間を測定
 コンパイルにはオプション‐Xcompiler ‐...
プロファイラによる確認
2015/07/23先端GPGPUシミュレーション工学特論26
 ストリーム数4
 メモリ転送とベクトル和を4セット実行
# CUDA_PROFILE_LOG_VERSION 2.0
# CUDA_DEVICE 0 ...
Nsightによる確認
2015/07/23先端GPGPUシミュレーション工学特論27
 ストリーム数8
 カーネル実行と転送(CPUからGPU,GPUからCPU)がオー
バーラップ
CPUとGPUの協調
2015/07/23先端GPGPUシミュレーション工学特論28
 cudaDeviceSynchronize()が呼ばれるまでCPUと
GPUは非同期
 GPUでベクトル和を計算している間,CPUは待機
 処理の一部...
#include<stdio.h>
#include<stdlib.h>
#include<omp.h>
#define N (1024*1024*8)
#define Nbytes (N*sizeof(float))
#define NT 2...
int main(){
float *a,*b,*c;
int stm;
cudaStream_t stream[Stream];
float *host_a, *host_b, *host_c;
cudaHostAlloc((void **)...
double time_start = omp_get_wtime();
for(stm=0;stm<Stream‐CPU;stm++){
int idx = stm*N/Stream;
cudaMemcpyAsync(&a[idx], &ho...
double sum=0;
for(int i=0;i<N;i++)sum+=host_c[i];
printf("%f¥n",sum/N);
printf("elapsed time = %f sec¥n",
time_end‐time_st...
実行結果
2015/07/23先端GPGPUシミュレーション工学特論33
 配列の要素数 N=223
 1ブロックあたりのスレッド数 256
 ストリームの数,CPUの負荷割合を変えて計算
 CPU負荷割合=CPUが計算するストリーム数...
実行結果
2015/07/23先端GPGPUシミュレーション工学特論34
ストリーム
数
CPU
負荷割合
実行時間
[ms]
1
0 (GPU) 17.6
1 (CPU) 36.4
2
0.0 15.5
0.5 18.3
1.0 36.4
4...
実行結果
2015/07/23先端GPGPUシミュレーション工学特論35
ストリーム
数
CPU
負荷割合
実行時間
[ms]
16
0.0000 14.0
0.0625 13.1
0.1250 12.2
0.1875 11.5
0.2500 ...
実行結果
2015/07/23先端GPGPUシミュレーション工学特論36
CPU負荷割合
実行時間[ms]
ストリーム数
OpenMPによる処理の効率化
2015/07/23先端GPGPUシミュレーション工学特論37
 CPUとGPUの協調版
 1スレッドが転送やカーネルを呼び出し
 全て呼び出した後にベクトル和を実行
 複数のスレッドを起動
 1スレッ...
OpenMP
2015/07/23先端GPGPUシミュレーション工学特論38
 並列に処理を実行させる箇所に指示句(ディレクティ
ブ)を挿入
 for文の並列化
 ディレクティブを一行追加(#pragma omp ~)
#pragma o...
#include<stdio.h>
#include<stdlib.h>
#define N (1024*1024)
#define Nbytes (N*sizeof(float))
int main(){
float *a,*b,*c;
in...
並列化プログラム
2015/07/23先端GPGPUシミュレーション工学特論40
#include<stdio.h>
#include<stdlib.h>
#define N (1024*1024)
#define Nbytes (N*size...
OpenMPの指示文
2015/07/23先端GPGPUシミュレーション工学特論41
 並列処理制御
 OpenMPで並列処理を行う領域の定義
 並列実行領域(Parallel Region)構文
 ワークシェアリング(Work sha...
並列実行領域(Parallel Region)構文
2015/07/23先端GPGPUシミュレーション工学特論42
 parallel構文
 parallel構文で指示された領域では指定されたスレッド
が並列に処理を実行
 全てのスレッド...
ワークシェアリング(Work sharing)構文
2015/07/23先端GPGPUシミュレーション工学特論43
 for構文
 parallel構文で指定された並列実行領域内で利用
 直後のforループを各スレッドに分割して並列処理を...
single構文
2015/07/23先端GPGPUシミュレーション工学特論44
 parallel構文で指定された並列実行領域内で利用
 一つのスレッドのみが処理を実行
 処理を終了するまで他のスレッドは待機
 待機させる必要がない場...
nowait指示節
2015/07/23先端GPGPUシミュレーション工学特論45
 ワークシェア構文で指定されたブロックの最後で同
期せず,処理を継続
 全スレッドが処理を終了するまで待たず,次の処理を実行
#pragma omp par...
#include<stdio.h>
#include<stdlib.h>
#include<omp.h>
#define N (1024*1024*8)
#define Nbytes (N*sizeof(float))
#define NT 2...
int main(){
float *a,*b,*c;
int stm;
cudaStream_t stream[Stream];
float *host_a, *host_b, *host_c;
cudaHostAlloc((void **)...
#pragma omp single nowait
{
for(stm=0;stm<Stream‐CPU;stm++){
int idx = stm*N/Stream;
cudaMemcpyAsync(&a[idx], &host_a[idx]...
cudaDeviceSynchronize();
double time_end = omp_get_wtime();
double sum=0;
for(int i=0;i<N;i++)sum+=host_c[i];
printf("%f¥n...
実行結果
2015/07/23先端GPGPUシミュレーション工学特論50
 配列の要素数 N=223
 1ブロックあたりのスレッド数 256
 ストリームの数,CPUの負荷割合,CPUスレッド数を
変えて計算
 OpenMPの関数を用い...
実行結果(CPUスレッドごとに最速となる条件)
2015/07/23先端GPGPUシミュレーション工学特論51
 スレッド数が多くなると実行時間が実行毎に変化
 1スレッドCPU+GPU協調版のような評価ができない
CPUスレッド数 ストリ...
実行結果
2015/07/23先端GPGPUシミュレーション工学特論52
 CPU 1スレッド 36.4 ms
 GPU(1ストリーム) 17.5 ms
 GPU(16ストリーム) 14.0 ms
 CPUとGPUの協調 10.6 ms...
複数GPUでのストリームの利用
2015/07/23先端GPGPUシミュレーション工学特論53
 各GPUでストリームを作り,並行実行することが可能
 grouseでは
 CPUとGPU4台で非同期実行が可能
 各GPUに複数のストリー...
Prochain SlideShare
Chargement dans…5
×

2015年度先端GPGPUシミュレーション工学特論 第15回 CPUとGPUの協調

697 vues

Publié le

長岡技術科学大学
2015年度先端GPGPUシミュレーション工学特論(全15回,大学院生対象講義)
第15回CPUとGPUの協調

2015年度先端GPGPUシミュレーション工学特論
・第1回 先端シミュレーションおよび産業界におけるGPUの役割
http://www.slideshare.net/ssuserf87701/2015gpgpu1-59180313
・第1回補足 GROUSEの利用方法
http://www.slideshare.net/ssuserf87701/2015gpgpu1-59180326
・第2回 GPUによる並列計算の概念とメモリアクセス
http://www.slideshare.net/ssuserf87701/2015gpgpu2-59180382
・第3回 GPUプログラム構造の詳細(threadとwarp)
http://www.slideshare.net/ssuserf87701/2015gpgpu3-59180483
・第4回 GPUのメモリ階層の詳細(共有メモリ)
http://www.slideshare.net/ssuserf87701/2015gpgpu4-59180572
・第5回 GPUのメモリ階層の詳細(様々なメモリの利用)
http://www.slideshare.net/ssuserf87701/2015gpgpu5-59180652
・第6回 プログラムの性能評価指針(Flop/Byte,計算律速,メモリ律速)
http://www.slideshare.net/ssuserf87701/2015gpgpu6-59180736
・第7回 総和計算(Atomic演算)
http://www.slideshare.net/ssuserf87701/2015gpgpu7-59180844
・第8回 偏微分方程式の差分計算(拡散方程式)
http://www.slideshare.net/ssuserf87701/2015gpgpu8-59180918
・第9回 偏微分方程式の差分計算(移流方程式)
http://www.slideshare.net/ssuserf87701/2015gpgpu9-59180982
・第10回 Poisson方程式の求解(線形連立一次方程式)
http://www.slideshare.net/ssuserf87701/2015gpgpu10-59181031
・第11回 数値流体力学への応用(支配方程式,CPUプログラム)
http://www.slideshare.net/ssuserf87701/2015gpgpu11-59181134
・第12回 数値流体力学への応用(GPUへの移植)
http://www.slideshare.net/ssuserf87701/2015gpgpu12-59181230
・第13回 数値流体力学への応用(高度な最適化)
http://www.slideshare.net/ssuserf87701/2015gpgpu13-59181316
・第14回 複数GPUの利用
http://www.slideshare.net/ssuserf87701/2015gpgpu14-59181367
・第15回 CPUとGPUの協調
http://www.slideshare.net/ssuserf87701/2015gpgpu15-59181450

2015年度GPGPU実践基礎工学
・第1回 学際的分野における先端シミュレーション技術の歴史
http://www.slideshare.net/ssuserf87701/2015gpgpu1

2015年度GPGPU実践プログラミング
・第1回 GPGPUの歴史と応用例
http://www.slideshare.net/ssuserf87701/2015gpgpu1-59179080

講義には長岡技術科学大学のGPGPUシステム(GROUSE)を利用しています。
開発環境
CPU Intel Xeon X5670 × 32
GPU NVIDIA Tesla M2050(Fermi世代) × 64
CUDA 4.0(諸般の事情によりバージョンアップされていません)
PGI Fortran 11.3

Publié dans : Ingénierie
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

2015年度先端GPGPUシミュレーション工学特論 第15回 CPUとGPUの協調

  1. 1. 第15回 CPUとGPUの協調 長岡技術科学大学 電気電子情報工学専攻 出川智啓
  2. 2. 今回の内容 2015/07/23先端GPGPUシミュレーション工学特論2  並列処理と並行処理  ストリーム  複数ストリームを用いたベクトル和の計算  CPUとGPUを用いたベクトル和の計算
  3. 3. 並列処理と並行処理 2015/07/23先端GPGPUシミュレーション工学特論3  並列処理(Parallel Processing)  一つの処理を複数の処理に分割  協調しながら複数の処理を実行  プログラムを高速化するために行われる  並行処理(Concurrent Processing)  複数の処理を実行  複数の処理は必ずしも協調していない  利便性の向上のために行われる  OSが複数のプログラムを実行する等
  4. 4. CUDAプログラムの実行  実行時の流れ(CPU視点)  利用するGPUの初期化やデータの転送などを実行  GPUで実行する関数を呼び出し  GPUから結果を取得 初期化の指示 初期化 カーネルの実行指示 カーネルを実行 結果の取得 実行結果をコピー time CPUとGPUは非同期 CPUは別の処理を実行可能 2015/07/23先端GPGPUシミュレーション工学特論4 必要なデータのコピー メモリに書込
  5. 5. GPUとCPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論5  GPUのみの処理 float *a = (float *)malloc(NBytes); float *dev_a; cudaMalloc((void **)&dev_a, NBytes); cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); kernel<<<NB, NT>>>(dev_a); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); aを転送(H2D) kernel dev_aを転送(D2H)GPU 待機 待機CPU cudaMemcpy実行 カーネル起動 cudaMemcpy実行 GPUとは同期せず(カーネルの終了を待たず), 直ちに次の処理を実行
  6. 6. GPUとCPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論6  GPUとCPUの非同期処理 float *a = (float *)malloc(NBytes), *dev_a; cudaMalloc((void **)&dev_a, NBytes); cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); kernel<<<NB, NT>>>(dev_a);  do_something(a); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); CPUとGPUは非同期で処理を実行 aを転送(H2D) kernel 待機 dev_aを転送(D2H)GPU 待機 do_something() 待機CPU cudaMemcpy実行 カーネル起動 cudaMemcpy実行
  7. 7. GPUとCPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論7  GPUとCPUが非同期で行う処理とは  cudaMemcpy  同期通信(synchronous)  同期が完了するまでCPUとGPUが他の処理を実行しない  ブロッキング(blocking)型とも呼ばれる  カーネル実行  非同期で実行(asynchronous)  カーネルを実行した直後にCPUが他の処理を実行可能
  8. 8. GPUの並行実行(要望) 2015/07/23先端GPGPUシミュレーション工学特論8  通信(データ転送)を同時に行いたい  通信(データ転送)している間に計算を実行したい  負荷の軽い計算を複数同時に実行したい float *a, *b, *dev_a, *dev_b; a = (float *)malloc(NBytes); b = (float *)malloc(NBytes); cudaMalloc((void **)&dev_a, NBytes); cudaMalloc((void **)&dev_b, NBytes); cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, NBytes, cudaMemcpyHostToDevice); kernel1<<<1, NT>>>(dev_a); kernel2<<<1, NT>>>(dev_b); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); cudaMemcpy(b, dev_b, NBytes, cudaMemcpyDeviceToHost); 転送が終わると直ち にカーネル実行 負荷が軽いカーネル は同時に実行 カーネルが終わると 直ちに転送
  9. 9. GPUの並行実行(要望) 2015/07/23先端GPGPUシミュレーション工学特論9 cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, NBytes, cudaMemcpyHostToDevice); kernel1<<<1, NT>>>(dev_a); kernel2<<<1, NT>>>(dev_b); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); cudaMemcpy(b, dev_b, NBytes, cudaMemcpyDeviceToHost); 転送が終わると直ち にカーネル実行 負荷が軽いカーネル は同時に実行 カーネルが終わると 直ちに転送 aを転送(H2D) kernel1 dev_aを転送(D2H) bを転送(H2D) kernel2 dev_bを転送(D2H) 転送の 並行実行 転送と カーネ ルの並 行実行 カーネルの 並行実行 転送とカーネル の並行実行 転送の 並行実行
  10. 10. GPUの並行実行(要望) 2015/07/23先端GPGPUシミュレーション工学特論10 cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, NBytes, cudaMemcpyHostToDevice); kernel1<<<1, NT>>>(dev_a); kernel2<<<1, NT>>>(dev_b); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); cudaMemcpy(b, dev_b, NBytes, cudaMemcpyDeviceToHost); 転送が終わると直ち にカーネル実行 負荷が軽いカーネル は同時に実行 カーネルが終わると 直ちに転送 aを転送(H2D) kernel1 dev_aを転送(D2H) bを転送(H2D) kernel2 dev_bを転送(D2H) 転送の 並行実行 転送と カーネ ルの並 行実行 カーネルの 並行実行 転送とカーネル の並行実行 転送の 並行実行 転送の方向が異 なる場合は可能 転送の方向が異 なる場合は可能
  11. 11. Concurrent Kernel Execution 2015/07/23先端GPGPUシミュレーション工学特論11  GPUが並行に処理を実行可能かの確認  cudaGetDeviceProperties()のメンバdeviceOverlap を確認  1なら並行実行可能 int dev = 0; cudaDeviceProp deviceProp; cudaGetDeviceProperties(&deviceProp, dev); if(deviceProp.deviceOverlap == 1) printf("Device %d: ¥"%s¥" supports concurrent kernel execution¥n", dev, deviceProp.name); concurrent.cu
  12. 12. GPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論12  データ通信とカーネル実行を並行に実行可能  データ通信と並行に実行できるカーネルは一つ  CPUからGPUへの転送,GPUからCPUへの転送,カーネル実 行を並行に実行可能  Compute Capability 2.0以上のGPU  pgaccelinfoのDevice Revision Numberと同じ  複数のカーネルを並行に実行  並行実行可能なカーネルの数  32 Compute Capability 3.5以上  16 Compute Capability 2.0以上3.5未満  並行実行するカーネルが資源を利用できることが条件
  13. 13. ストリーム 2015/07/23先端GPGPUシミュレーション工学特論13  GPUで実行される処理の流れ  同じストリームに属する処理は,必ず命令発行され た順に実行される  異なるストリームに属する処理は並行に実行できる  異なるストリームに属する処理間に依存性がないと仮定  ストリームを複数作ることで処理を並行に実行
  14. 14. 標準のストリーム(ストリーム0) 2015/07/23先端GPGPUシミュレーション工学特論14  ストリームを指定しない場合はストリーム0に所属  ストリーム0は他の処理とは同時に実行されない  ストリーム0はCPUと同期して実行  例外はカーネル実行,明示的な非同期通信等  並行に実行するためには,0でない複数のストリーム に所属させる必要がある
  15. 15. float *a, *b, *dev_a, *dev_b; cudaStream_t stream1, stream2; cudaStreamCreate(&stream1); cudaStreamCreate(&stream2); cudaMalloc((void **)&dev_a, NBytes); cudaMalloc((void **)&dev_b, NBytes); cudaHostAlloc((void **)&a, NBytes, cudaHostAllocDefault); cudaHostAlloc((void **)&b, NBytes, cudaHostAllocDefault); cudaMemcpyAsync(dev_a, a, Nbytes, cudaMemcpyHostToDevice, stream1); cudaMemcpyAsync(dev_b, b, Nbytes, cudaMemcpyHostToDevice, stream2); kernel1<<<1, NT, 0, stream1>>>(dev_a); kernel2<<<1, NT, 0, stream2>>>(dev_b); cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream1); cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream2); cudaDeviceSynchronize(); ストリームを用いた並行実行 2015/07/23先端GPGPUシミュレーション工学特論15
  16. 16. ストリームを用いた並行実行 2015/07/23先端GPGPUシミュレーション工学特論16  ストリームの作成(具体的に番号を付けるわけではない)  cudaStreamCreate(cudaStream_t *);  ストリームの破棄  cudaStreamDestroy(cudaStream_t); cudaStream_t stream1, stream2,stream[10]; cudaStreamCreate(&stream1); //個別の変数でストリームを管理することが可能 cudaStreamCreate(&stream2); // for(int i=0;i<10;i++) cudaStreamCreate(&stream[i]);//配列でストリームを管理することも可能 cudaStreamDestroy(stream1);  cudaStreamDestroy(stream2);  for(int i=0;i<10;i++) cudaStreamDestroy(stream[i]);
  17. 17. ストリームを用いた並行実行 2015/07/23先端GPGPUシミュレーション工学特論17  cudaMemcpyAsync  非同期転送(asynchronous)  5番目の引数としてストリームを指定  CPUのメモリには必ずページロックメモリを指定  cudaHostAlloc()もしくはcudaMallocHost()で確保  カーネル実行  <<<>>>内の4番目にストリームを指定  (1)ブロック数 (2)1ブロックあたりのスレッド数 (3)共有メモリサイズ (4)ストリーム  共有メモリのサイズを指定しない場合は0を記述
  18. 18. 並行実行の同期 2015/07/23先端GPGPUシミュレーション工学特論18  cudaDeviceSynchronize()  GPUで実行している全ての処理が完了するまで待機  cudaStreamSynchronize(ストリーム)  引数で指定したストリームに属する命令が完了するまで待機 … cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream1); cudaMemcpyAsync(b, dev_b, Nbytes, cudaMemcpyDeviceToHost, stream2); cudaDeviceSynchronize(); //dev_a,dev_b両方の転送が終わるまで待機 … cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream1); cudaMemcpyAsync(b, dev_b, Nbytes, cudaMemcpyDeviceToHost, stream2); cudaStreamSynchronize(stream1); //dev_aの転送が終わると次の処理へ移行
  19. 19. ベクトル和C=A+Bの並行実行 2015/07/23先端GPGPUシミュレーション工学特論19  並行実行の方針  複数のストリームを作成  配列のまとまったサイズを異なるストリームに所属  A, Bの初期化はCPUで実行し,GPUへ転送  A, Bの転送が終わったストリームから足し算を実行し, CPUへCを転送
  20. 20. ベクトル和C=A+Bの並行実行 2015/07/23先端GPGPUシミュレーション工学特論20  同期実行  並行実行  処理をオーバーラップできれば若干高速化 Aを転送(H2D) Bを転送(H2D) add dev_Cを転送(D2H) A(H2D) B(H2D) add dev_C(D2H) A(H2D) B(H2D) add dev_C(D2H) A(H2D) B(H2D) add dev_C(D2H) Stream 0 Stream 1 Stream 2 Stream 3 高速化 実行開始 処理時間
  21. 21. #include<stdio.h> #include<stdlib.h> #include<omp.h> #define N (1024*1024*8) #define Nbytes (N*sizeof(float)) #define NT 256 #define NB (N/NT) //Streamの数=並行実行数 #define Stream 4 //カーネルは変更なし __global__ void init (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } //カーネルは変更なし __global__ void add (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; c[i] = a[i] + b[i]; } ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論21 vectoradd_stream.cu
  22. 22. int main(){ float *a,*b,*c; int stm; cudaStream_t stream[Stream]; //ページロックホストメモリを確保 float *host_a, *host_b, *host_c; cudaHostAlloc((void **)&host_c, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_a, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_b, Nbytes, cudaHostAllocDefault); for(int i=0;i<N;i++){ host_a[i] = 1.0f; host_b[i] = 2.0f; host_c[i] = 0; } for(stm=0;stm<Stream;stm++){ cudaStreamCreate(&stream[stm]); } cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論22 vectoradd_stream.cu
  23. 23. double time_start = omp_get_wtime(); for(stm=0;stm<Stream;stm++){ int idx = stm*N/Stream; cudaMemcpyAsync(&a[idx], &host_a[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); cudaMemcpyAsync(&b[idx], &host_b[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); add<<< NB/Stream, NT, 0 ,stream[stm]>>>(&a[idx],&b[idx],&c[idx]); cudaMemcpyAsync(&host_c[idx], &c[idx], Nbytes/Stream, cudaMemcpyDeviceToHost,  stream[stm]); } cudaDeviceSynchronize(); double time_end = omp_get_wtime(); ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論23 vectoradd_stream.cu
  24. 24. double sum=0; for(int i=0;i<N;i++)sum+=host_c[i]; printf("%f¥n",sum/N); printf("elapsed time = %f sec¥n", time_end‐time_start); cudaFreeHost(host_a); cudaFreeHost(host_b); cudaFreeHost(host_c); cudaFree(a); cudaFree(b); cudaFree(c); for(stm=0;stm<Stream;stm++){ cudaStreamDestroy(stream[stm]); } return 0; } ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論24 vectoradd_stream.cu
  25. 25. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論25  配列の要素数 N=223  1ブロックあたりのスレッド数 256  OpenMPの関数を用いて実行時間を測定  コンパイルにはオプション‐Xcompiler ‐fopenmpが必要 ストリーム数 実行時間[ms] 1 17.5 2 15.5 4 14.5 8 14.1 16 14.0 ストリーム数 実行時間[ms]
  26. 26. プロファイラによる確認 2015/07/23先端GPGPUシミュレーション工学特論26  ストリーム数4  メモリ転送とベクトル和を4セット実行 # CUDA_PROFILE_LOG_VERSION 2.0 # CUDA_DEVICE 0 Tesla M2050 # TIMESTAMPFACTOR fffff60a9ac44950 timestamp,method,gputime,cputime,occupancy timestamp=[ 92925.000 ] method=[ memcpyHtoDasync ] gputime=[ 1403.488 ] cputime=[ 16.000 ] timestamp=[ 92948.000 ] method=[ memcpyHtoDasync ] gputime=[ 1410.880 ] cputime=[ 5.000 ] timestamp=[ 92977.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 253.472 ] cputime=[ 23.000 ] occupancy=[ 1.000 ] timestamp=[ 93005.000 ] method=[ memcpyDtoHasync ] gputime=[ 1520.448 ] cputime=[ 6.000 ] timestamp=[ 93014.000 ] method=[ memcpyHtoDasync ] gputime=[ 1931.936 ] cputime=[ 6.000 ] timestamp=[ 93021.000 ] method=[ memcpyHtoDasync ] gputime=[ 1414.272 ] cputime=[ 5.000 ] timestamp=[ 93029.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 253.248 ] cputime=[ 8.000 ] occupancy=[ 1.000 ] timestamp=[ 93038.000 ] method=[ memcpyDtoHasync ] gputime=[ 1521.216 ] cputime=[ 5.000 ] timestamp=[ 93045.000 ] method=[ memcpyHtoDasync ] gputime=[ 1935.456 ] cputime=[ 6.000 ] timestamp=[ 93053.000 ] method=[ memcpyHtoDasync ] gputime=[ 1417.152 ] cputime=[ 6.000 ] timestamp=[ 93060.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 252.352 ] cputime=[ 7.000 ] occupancy=[ 1.000 ] timestamp=[ 93068.000 ] method=[ memcpyDtoHasync ] gputime=[ 1566.624 ] cputime=[ 6.000 ] timestamp=[ 93076.000 ] method=[ memcpyHtoDasync ] gputime=[ 1978.208 ] cputime=[ 6.000 ] timestamp=[ 93083.000 ] method=[ memcpyHtoDasync ] gputime=[ 1414.528 ] cputime=[ 6.000 ] timestamp=[ 93090.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 225.568 ] cputime=[ 8.000 ] occupancy=[ 1.000 ] timestamp=[ 93099.000 ] method=[ memcpyDtoHasync ] gputime=[ 1319.040 ] cputime=[ 6.000 ]
  27. 27. Nsightによる確認 2015/07/23先端GPGPUシミュレーション工学特論27  ストリーム数8  カーネル実行と転送(CPUからGPU,GPUからCPU)がオー バーラップ
  28. 28. CPUとGPUの協調 2015/07/23先端GPGPUシミュレーション工学特論28  cudaDeviceSynchronize()が呼ばれるまでCPUと GPUは非同期  GPUでベクトル和を計算している間,CPUは待機  処理の一部をCPUが実行する事で高速化が可能  GPUのカーネル,非同期転送処理を全て呼び終わった後に CPUでもベクトル和を計算 A(H2D) B(H2D) add dev_C(D2H) A(H2D) B(H2D) add dev_C(D2H) 転送・カー ネル呼出 add Stream 1 Stream 2 CPU
  29. 29. #include<stdio.h> #include<stdlib.h> #include<omp.h> #define N (1024*1024*8) #define Nbytes (N*sizeof(float)) #define NT 256 #define NB (N/NT) //Streamの数=並行実行数 #define Stream 4 //CPUのStream分担数 //GPUはStream‐CPU分の数だけ並行処理を実行 #define CPU 1 //カーネルは変更なし __global__ void init (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } //カーネルは変更なし __global__ void add (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; c[i] = a[i] + b[i]; } ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論29 vectoradd_coop.cu
  30. 30. int main(){ float *a,*b,*c; int stm; cudaStream_t stream[Stream]; float *host_a, *host_b, *host_c; cudaHostAlloc((void **)&host_c, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_a, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_b, Nbytes, cudaHostAllocDefault); for(int i=0;i<N;i++){ host_a[i] = 1.0f; host_b[i] = 2.0f; host_c[i] = 0; } for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamCreate(&stream[stm]); } cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論30 vectoradd_coop.cu
  31. 31. double time_start = omp_get_wtime(); for(stm=0;stm<Stream‐CPU;stm++){ int idx = stm*N/Stream; cudaMemcpyAsync(&a[idx], &host_a[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); cudaMemcpyAsync(&b[idx], &host_b[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); add<<< NB/Stream, NT, 0 ,stream[stm]>>>(&a[idx],&b[idx],&c[idx]); cudaMemcpyAsync(&host_c[idx], &c[idx], Nbytes/Stream, cudaMemcpyDeviceToHost,  stream[stm]); } for(int i=(Stream‐CPU)*N/Stream;i<N;i++) host_c[i] = host_a[i] + host_b[i]; cudaDeviceSynchronize(); double time_end = omp_get_wtime(); ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論31 vectoradd_coop.cu
  32. 32. double sum=0; for(int i=0;i<N;i++)sum+=host_c[i]; printf("%f¥n",sum/N); printf("elapsed time = %f sec¥n", time_end‐time_start); cudaFreeHost(host_a); cudaFreeHost(host_b); cudaFreeHost(host_c); cudaFree(a); cudaFree(b); cudaFree(c); for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamDestroy(stream[stm]); } return 0; } ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論32 vectoradd_coop.cu
  33. 33. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論33  配列の要素数 N=223  1ブロックあたりのスレッド数 256  ストリームの数,CPUの負荷割合を変えて計算  CPU負荷割合=CPUが計算するストリーム数/ストリーム数  0のとき全てGPUで計算,1のとき全てCPUで計算  OpenMPの関数を用いて実行時間を測定  コンパイルにはオプション‐Xcompiler ‐fopenmpが必要
  34. 34. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論34 ストリーム 数 CPU 負荷割合 実行時間 [ms] 1 0 (GPU) 17.6 1 (CPU) 36.4 2 0.0 15.5 0.5 18.3 1.0 36.4 4 0.00 14.5 0.25 11.2 0.50 18.3 0.75 27.4 1.00 36.3 ストリーム 数 CPU 負荷割合 実行時間 [ms] 8 0.000 14.1 0.125 12.5 0.250 10.8 0.375 13.8 0.500 18.3 0.625 22.9 0.750 27.4 0.875 31.9 1.000 36.3
  35. 35. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論35 ストリーム 数 CPU 負荷割合 実行時間 [ms] 16 0.0000 14.0 0.0625 13.1 0.1250 12.2 0.1875 11.5 0.2500 10.6 0.3125 11.6 0.3750 13.9 0.4375 16.2 0.5000 18.4 ストリーム 数 CPU 負荷割合 実行時間 [ms] 16 0.5625 20.6 0.6250 22.9 0.6875 25.1 0.7500 27.5 0.8125 29.6 0.8750 31.9 0.9375 34.3 1.0000 36.3 grouseではCPUが全体の処理の1/4を処理すると最も効率が良い
  36. 36. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論36 CPU負荷割合 実行時間[ms] ストリーム数
  37. 37. OpenMPによる処理の効率化 2015/07/23先端GPGPUシミュレーション工学特論37  CPUとGPUの協調版  1スレッドが転送やカーネルを呼び出し  全て呼び出した後にベクトル和を実行  複数のスレッドを起動  1スレッドをカーネル起動,非同期転送呼出に充てる  CPUではベクトル和を並列に計算
  38. 38. OpenMP 2015/07/23先端GPGPUシミュレーション工学特論38  並列に処理を実行させる箇所に指示句(ディレクティ ブ)を挿入  for文の並列化  ディレクティブを一行追加(#pragma omp ~) #pragma omp parallel for for(int i=0; i<N; i++) C[i] = A[i] + B[i]
  39. 39. #include<stdio.h> #include<stdlib.h> #define N (1024*1024) #define Nbytes (N*sizeof(float)) int main(){ float *a,*b,*c; int i; a = (float *)malloc(Nbytes); b = (float *)malloc(Nbytes); c = (float *)malloc(Nbytes); for(i=0; i<N; i++){ a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } for(i=0; i<N; i++) c[i] = a[i] + b[i]; for(i=0; i<N; i++) printf("%f+%f=%f¥n", a[i],b[i],c[i]); return 0; } 逐次(並列化前)プログラム 2015/07/23先端GPGPUシミュレーション工学特論39
  40. 40. 並列化プログラム 2015/07/23先端GPGPUシミュレーション工学特論40 #include<stdio.h> #include<stdlib.h> #define N (1024*1024) #define Nbytes (N*sizeof(float)) int main(){ float *a,*b,*c; int i; a = (float *)malloc(Nbytes); b = (float *)malloc(Nbytes); c = (float *)malloc(Nbytes); #pragma omp parallel { #pragma omp for for(i=0; i<N; i++){ a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } #pragma omp for for(i=0; i<N; i++) c[i] = a[i] + b[i]; } for(i=0; i<N; i++) printf("%f+%f=%f¥n", a[i],b[i],c[i]); return 0; }
  41. 41. OpenMPの指示文 2015/07/23先端GPGPUシミュレーション工学特論41  並列処理制御  OpenMPで並列処理を行う領域の定義  並列実行領域(Parallel Region)構文  ワークシェアリング(Work sharing)構文  同期制御  OpenMP並列領域内でのデータアクセス,命令実行の同期  データ属性制御  並列領域内で利用されるデータの属性を定義  その他
  42. 42. 並列実行領域(Parallel Region)構文 2015/07/23先端GPGPUシミュレーション工学特論42  parallel構文  parallel構文で指示された領域では指定されたスレッド が並列に処理を実行  全てのスレッドが同じ処理を実行 #pragma omp parallel //{ <‐ここに括弧を書くとエラー { 複数のスレッドが起動され,ここに書いてある処理を実行 全てのスレッドが同じ処理を実行 }
  43. 43. ワークシェアリング(Work sharing)構文 2015/07/23先端GPGPUシミュレーション工学特論43  for構文  parallel構文で指定された並列実行領域内で利用  直後のforループを各スレッドに分割して並列処理を実行  for(初期化;継続条件;再初期化)で構成されるforルー プが対象  全てのスレッドが処理を終了するまで他のスレッドは待機 #pragma omp parallel { #pragma omp for for(i=0; i<N; i++){ forループを自動的に分割して各スレッドが実行 } 全てのスレッドが処理を終了するまで待機 }
  44. 44. single構文 2015/07/23先端GPGPUシミュレーション工学特論44  parallel構文で指定された並列実行領域内で利用  一つのスレッドのみが処理を実行  処理を終了するまで他のスレッドは待機  待機させる必要がない場合はnowait節を指定 #pragma omp parallel { #pragma omp single { 1スレッドのみが処理を実行 他のスレッドは待機 } single構文内のスレッドが処理を終了するまで待機 }
  45. 45. nowait指示節 2015/07/23先端GPGPUシミュレーション工学特論45  ワークシェア構文で指定されたブロックの最後で同 期せず,処理を継続  全スレッドが処理を終了するまで待たず,次の処理を実行 #pragma omp parallel { #pragma omp single nowait { 1スレッドのみが処理を実行 他のスレッドは待機せず,以降の処理を実行 } ... }
  46. 46. #include<stdio.h> #include<stdlib.h> #include<omp.h> #define N (1024*1024*8) #define Nbytes (N*sizeof(float)) #define NT 256 #define NB (N/NT) #define Stream 4 #define CPU 2 #define Threads 12 //カーネルは変更なし __global__ void init (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } //カーネルは変更なし __global__ void add (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; c[i] = a[i] + b[i]; } ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論46 vectoradd_coop_omp.cu
  47. 47. int main(){ float *a,*b,*c; int stm; cudaStream_t stream[Stream]; float *host_a, *host_b, *host_c; cudaHostAlloc((void **)&host_c, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_a, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_b, Nbytes, cudaHostAllocDefault); for(int i=0;i<N;i++){ host_a[i] = 1.0f; host_b[i] = 2.0f; host_c[i] = 0; } for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamCreate(&stream[stm]); } cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); omp_set_num_threads(Threads); double time_start = omp_get_wtime(); #pragma omp parallel { ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論47 vectoradd_coop_omp.cu
  48. 48. #pragma omp single nowait { for(stm=0;stm<Stream‐CPU;stm++){ int idx = stm*N/Stream; cudaMemcpyAsync(&a[idx], &host_a[idx], Nbytes/Stream,   cudaMemcpyHostToDevice,stream[stm]); cudaMemcpyAsync(&b[idx], &host_b[idx], Nbytes/Stream,  cudaMemcpyHostToDevice,stream[stm]); add<<< NB/Stream, NT, 0 ,stream[stm]>>>(&a[idx],&b[idx],&c[idx]); cudaMemcpyAsync(&host_c[idx], &c[idx], Nbytes/Stream,  cudaMemcpyDeviceToHost,stream[stm]); } #pragma omp for for(int i=(Stream‐CPU)*N/Stream;i<N;i++) host_c[i] = host_a[i] + host_b[i]; } } //#pragma omp parallelの終端 ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論48 vectoradd_coop_omp.cu
  49. 49. cudaDeviceSynchronize(); double time_end = omp_get_wtime(); double sum=0; for(int i=0;i<N;i++)sum+=host_c[i]; printf("%f¥n",sum/N); printf("elapsed time = %f sec¥n", time_end‐time_start); cudaFreeHost(host_a); cudaFreeHost(host_b); cudaFreeHost(host_c); cudaFree(a); cudaFree(b); cudaFree(c); for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamDestroy(stream[stm]); } return 0; } ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論49 vectoradd_coop_omp.cu
  50. 50. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論50  配列の要素数 N=223  1ブロックあたりのスレッド数 256  ストリームの数,CPUの負荷割合,CPUスレッド数を 変えて計算  OpenMPの関数を用いて実行時間を測定  コンパイルにはオプション‐Xcompiler ‐fopenmpが必要
  51. 51. 実行結果(CPUスレッドごとに最速となる条件) 2015/07/23先端GPGPUシミュレーション工学特論51  スレッド数が多くなると実行時間が実行毎に変化  1スレッドCPU+GPU協調版のような評価ができない CPUスレッド数 ストリーム数 CPU負荷割合 実行時間[ms] 1 16 0.1875 11.4 2 16 0.3125 9.82 3 16 0.4375 8.32 4 16 0.4375 8.23 5 2 0.5000 10.1 6 16 0.3750 9.18 7 16 0.6875 8.89 8 16 0.6250 8.70 9 4 0.7500 8.87 10 16 0.7500 8.76 11 16 0.5625 10.3 12 16 0.4375 17.8 スレッド数の増加と ともに,CPUの負荷 割合も増加させる と高速化に有効
  52. 52. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論52  CPU 1スレッド 36.4 ms  GPU(1ストリーム) 17.5 ms  GPU(16ストリーム) 14.0 ms  CPUとGPUの協調 10.6 ms  16ストリーム,CPU負荷割合0.25  CPUとGPUの協調 8.23 ms  CPU 4スレッド  16ストリーム,CPU負荷割合0.4375 単一GPU,1ストリームの 2倍程度高速化
  53. 53. 複数GPUでのストリームの利用 2015/07/23先端GPGPUシミュレーション工学特論53  各GPUでストリームを作り,並行実行することが可能  grouseでは  CPUとGPU4台で非同期実行が可能  各GPUに複数のストリームが存在し,処理を並行実行  処理するデータの割当と管理,処理の進行状況の把握が 著しく複雑化  注意点  あるGPUが作成したストリームは他のGPUでは利用できない  cudaSetDeviceでGPUを切り替えてからストリームを作成

×