複数GPUの利用 - 長岡技術科学大学

第14回 複数GPUの利用
長岡技術科学大学
電気電子情報工学専攻
出川智啓
今回の内容

並列アーキテクチャと並列処理の分類

OpenMP

複数GPUの利用

GPU Directによるデータ通信
1040
先端GPGPUシミュレーション工学特論
2015/07/16
複数のGPUを利用する目的

grouseの1ノードには4台のGPUを搭載




Tesla M2050
1T FLOPS/台
3 GB/台
4台全てを使う事で期待できる性能

GPUを1台だけ使った時の4倍高速化

GPU1台では収まらないサイズの問題を計算
1041
先端GPGPUシミュレーション工学特論
2015/07/16
複数のGPUを利用する目的

4台全てを使う事で期待できる性能

GPUを1台だけ使った時の4倍高速化
 高速化は達成可能
 複数のGPUを使うための手続きやデータの移動が必要
 GPU間のデータ交換が必要になると速度が低下

GPU1台では収まらないサイズの問題を計算
 利用できるメモリ容量はGPUの台数に比例
 1台では実行できない大規模な問題も実行可能
1042
先端GPGPUシミュレーション工学特論
2015/07/16
複数のGPUが利用できる環境

1台の計算機に複数のGPUが搭載



複数のGPUを搭載するために大きな筐体が必要
1台のPCで完結するため,プログラム中でネットワーク通信
を行う必要がない
帯域の狭いPCI‐Exバスを複数のGPUで取り合うため,デー
タ移動の効率は悪い
PCI‐Exバス
1043
先端GPGPUシミュレーション工学特論
2015/07/16
複数のGPUが利用できる環境

1台のGPUが搭載された計算機をネットワークで接続



小さい筐体のPC(キューブ型PCなど)を並べて作成
PCごとにCPUやメモリが必要
複数GPUの利用にはネットワーク通信が必須
PCI‐Ex
PCI‐Ex
PCI‐Ex
PCI‐Ex
高速ネットワーク,LAN
1044
先端GPGPUシミュレーション工学特論
2015/07/16
複数のGPUが利用できる環境

複数のGPUが搭載された複数の計算機をネットワー
クで接続


スーパーコンピュータ等の大規模計算環境
性能の大半はGPUによって実現
2~4CPUs
2~4GPUs
2~4CPUs
2~4GPUs
2~4CPUs
2~4GPUs
・・・
高速ネットワーク
1045
先端GPGPUシミュレーション工学特論
2015/07/16
複数のGPUが利用できる環境


CUDAにおける複数GPU利用

新しいバージョン
 GPU間通信を行う命令が用意されたり,複数のGPUを利用
する数値計算ライブラリが登場
 (古いバージョンと比べれば)利用しやすくなっている

古いバージョン
 複数のGPUを利用するための機能は未搭載
従来から並列計算に利用されていたライブラリと併用

1046
OpenMPやMPIなど
先端GPGPUシミュレーション工学特論
2015/07/16
複数GPUの利用(CUDA 3.2以前)

CUDAの制約




OpenMPやMPIを利用



1スレッドは複数のGPUを利用できない
GPUは必ずCPUの1スレッド(もしくは1プロセス)に割当て
4台のGPUを利用するためには4スレッド(4プロセス)必要
並列計算を行うプログラムを作成
スレッドやプロセス番号を基に利用するGPUを決定,処理を
実行
従来の並列計算の素直な拡張
1047
先端GPGPUシミュレーション工学特論
2015/07/16
複数GPUの利用(CUDA 4.0以降)

CUDAの制約の緩和

1スレッドから複数のGPUを利用(切替)可能

2台のGPU間でデータ通信を行う命令の追加
 GPU Direct v2.0 peer‐to‐peer

複数GPU間で統一されたメモリ空間を利用可能
 Unified Virtual Addressing
1048
先端GPGPUシミュレーション工学特論
2015/07/16
複数GPUの利用(CUDA 4.0以降)

複数GPUを利用するプログラムの作成が容易

cudaSetDevice()でカレントデバイスを指定
 CUDAの命令はそのカレントデバイスに対して発行

別のデバイスを利用する際はcudaSetDevice()でカレン
トデバイスを切替

複数のGPUへのデータコピー,複数のGPUでカーネルの起
動が容易
1049
先端GPGPUシミュレーション工学特論
2015/07/16
複数GPU化の例題

ベクトル和
1.OpenMPを利用する方法

従来の並列計算の拡張
2.CUDA4.0の機能を利用する方法
1050
先端GPGPUシミュレーション工学特論
2015/07/16
並列アーキテクチャの分類

システムの特徴付け,プロセッサの分類

Flynnの分類






1051
並列アーキテクチャのグループ分け
データの処理と命令の並列性に着目
SISD
SIMD
MISD
MIMD
単一命令単一データ
単一命令複数データ
複数命令単一データ
複数命令複数データ
先端GPGPUシミュレーション工学特論
2015/07/16
Single Instruction Multiple Data streams

単一命令複数データ




数値シミュレーションに最適
命令
数学のベクトルや配列計算の概念
に一致


複数のまとまったデータに対して同じ演算を同時に実行
命令は一つ,その命令が同時に多くのデータに対して適用
されるアーキテクチャ
ベクトルプロセッサとも呼ばれる
データ
GPUもここに分類
A0
B0
A1
B1
A2
A3
1052
先端GPGPUシミュレーション工学特論
+
B2
B3
2015/07/16
プロセス

OSから資源(コア,メモリ,外部記憶など)を割り当て
られ,実行状態(または待機状態)にあるプログラム

システムプロセス


OSの実行に関係するプログラム
ユーザプロセス

1053
ユーザ権限で実行されているプログラム
先端GPGPUシミュレーション工学特論
2015/07/16
マルチプロセス

複数のプロセスが存在し,並列に実行



シングルプロセス
マルチプロセス
マルチプロセスに対応したOSが必要


プロセスが一つのみ
プロセスが二つ以上
現在のOSはマルチプロセスに対応
シングルコアCPU一つでもマルチプロセスが可能


1054
OSが複数のプロセスを切替
複数のプロセスが並列に実行されているように見せる
先端GPGPUシミュレーション工学特論
2015/07/16
マルチプロセス

各プロセスに専用のメモリ領域を割り当て


CPUやメモリは複数のプログラムに割り当てられる
プログラムはCPUやメモリを独占しているように振る舞う
プロセスA
スレッド
メモリ
CPU
メモリ
OS
プロセスB
スレッド
メモリ
1055
先端GPGPUシミュレーション工学特論
2015/07/16
スレッド

プログラムの処理の最小実行単位

プロセス内で複数のスレッドが存在


シングルスレッド
マルチスレッド
1プロセスに一つのスレッドのみ
1プロセスに二つ以上のスレッド
シングルプロセス
シングルスレッド
マルチスレッド
マルチプロセス
シングルスレッド
マルチスレッド
1056
先端GPGPUシミュレーション工学特論
2015/07/16
マルチスレッド


一つのプロセスに二つ以上のスレッドが存在
一つのプロセスに専用のメモリ領域を割当

プロセス内の複数のスレッドはメモリ領域を共有
プロセスA
スレッド
スレッド
メモリ
CPU
メモリ
OS
プロセスB
スレッド
スレッド
メモリ
1057
先端GPGPUシミュレーション工学特論
2015/07/16
並列計算機システム

並列処理の基本



処理を何らかの方法で分割
分割した処理をプロセッサ(やコア)に割り当て同時に処理
並列計算機システム





1058
複数のプロセッサをもつ
主にメモリに違いがある
共有メモリシステム
分散メモリシステム
ハイブリッドシステム
先端GPGPUシミュレーション工学特論
2015/07/16
共有メモリシステム



複数のプロセッサがメモリ空間を共有
分割した処理は各プロセッサ上で並列的に処理
共有されたメモリ空間上の変数は全てのCPU(やコ
ア)からアクセス(読み書き)可能

他からアクセスされない変数を持つことも可能
CPU
CPU
CPU
CPU
メモリ
1059
先端GPGPUシミュレーション工学特論
2015/07/16
並列処理の分類

タスク並列


データ並列


独立なタスクを異なるCPU・コアで同時に実行
独立なタスクが処理するデータを分割し,異なるCPU・コアがデータ
を参照し,処理を実行
Embarrassingly parallel (perfectly parallel)

1060
各CPU・コアが同じタスクを異なるパラメータで実行
 GPUが各ピクセルの色を決定し,ディスプレイに描画する処理
 あるタスクに対してパラメータの影響を調査するような問題
先端GPGPUシミュレーション工学特論
2015/07/16
データ並列
独立な処理A,B,Cが取り扱うデータを分割して実行


逐次処理
処理A
コア

処理B
処理C
並列処理
コア1
処理A
処理B
処理C
コア2
処理A
処理B
処理C
高速化
1061
先端GPGPUシミュレーション工学特論
2015/07/16
OpenMP

共有メモリシステムでの並列処理に利用

標準化されたオープンな規格

OpenMPをサポートしているコンパイラであれば同じ
書き方が可能

並列化したい箇所をコンパイラに指示

ディレクティブ
コンパイラが対応していなければコメントとして扱われる
修正が最小限で済み,共通のソースコードで管理
1062
先端GPGPUシミュレーション工学特論


2015/07/16
OpenMP

並列に処理を実行させる箇所に指示句(ディレクティ
ブ)を挿入

for文の並列化

ディレクティブを一行追加(#pragma omp ~)
#pragma omp parallel for
for(int i=0; i<N; i++)
C[i] = A[i] + B[i]
1063
先端GPGPUシミュレーション工学特論
2015/07/16
逐次(並列化前)プログラム
#include<stdio.h>
#include<stdlib.h>
#define N (1024*1024)
#define Nbytes (N*sizeof(float))
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]);
int main(){
float *a,*b,*c;
int i;
return 0;
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;
}
1064
先端GPGPUシミュレーション工学特論
2015/07/16
並列化プログラム
#include<stdio.h>
#include<stdlib.h>
#define N (1024*1024)
#define Nbytes (N*sizeof(float))
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]);
int main(){
float *a,*b,*c;
int i;
return 0;
a = (float *)malloc(Nbytes);
b = (float *)malloc(Nbytes);
c = (float *)malloc(Nbytes);
#pragma omp parallel for
for(i=0; i<N; i++){
a[i] = 1.0;
b[i] = 2.0;
c[i] = 0.0;
}
#pragma omp parallel for
1065
}
先端GPGPUシミュレーション工学特論
2015/07/16
並列化プログラム
#include<stdio.h>
#include<stdlib.h>
#define N (1024*1024)
#define Nbytes (N*sizeof(float))
}
#pragma omp for
for(i=0; i<N; i++)
c[i] = a[i] + b[i];
}
int main(){
float *a,*b,*c;
int i;
for(i=0; i<N; i++)
printf("%f+%f=%f¥n",
a[i],b[i],c[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;
1066
return 0;
}
先端GPGPUシミュレーション工学特論
2015/07/16
コンパイル

コンパイル時にコンパイルオプションを付与
‐fopenmp

‐fopenmpを付けるとディレクティブを処理

‐fopenmpを付けないとディレクティブは無視される
1067
先端GPGPUシミュレーション工学特論
2015/07/16
処理の並列化

データ並列


コア0
forループをスレッドの数だけ分割
タスク並列もできる
for(i=0; i<N/2‐1; i++)
c[i] = a[i] + b[i];
スレッド0
スレッド1
+
+
a[i]
+
+
b[i]
コア1
for(i=N/2; i<N; i++)
c[i] = a[i] + b[i];
c[i]
1068
先端GPGPUシミュレーション工学特論
2015/07/16
OpenMPの指示文

並列処理制御




同期制御


OpenMP並列領域内でのデータアクセス,命令実行の同期
データ属性制御


OpenMPで並列処理を行う領域の定義
並列実行領域(Parallel Region)構文
ワークシェアリング(Work sharing)構文
並列領域内で利用されるデータの属性を定義
その他
1069
先端GPGPUシミュレーション工学特論
2015/07/16
並列実行領域(Parallel Region)構文

parallel構文


parallel構文で指示された領域では指定されたスレッド
が並列に処理を実行
全てのスレッドが同じ処理を実行
#pragma omp parallel //{ <‐ここに括弧を書くとエラー
{
複数のスレッドが起動され,ここに書いてある処理を実行
全てのスレッドが同じ処理を実行
}
1070
先端GPGPUシミュレーション工学特論
2015/07/16
ワークシェアリング(Work sharing)構文

for構文




parallel構文で指定された並列実行領域内で利用
直後のforループを各スレッドに分割して並列処理を実行
for(初期化;継続条件;再初期化)で構成されるforルー
プが対象
全てのスレッドが処理を終了するまで他のスレッドは待機
#pragma omp parallel
{
#pragma omp for
for(i=0; i<N; i++){
forループを自動的に分割して各スレッドが実行
}
全てのスレッドが処理を終了するまで待機
}
1071
先端GPGPUシミュレーション工学特論
2015/07/16
データ属性制御

shared指示節


parallel構文で指定された並列実行領域内での変数の
取り扱いを指示
指定した変数を全てのスレッドで共有
int data;
#pragma omp parallel shared(data)
{
全てのスレッドがdataを共有
あるスレッドがdataを変更すると,他のスレッドが参照する
dataの値も変更
}
1072
先端GPGPUシミュレーション工学特論
2015/07/16
データ属性制御

private指示節




parallel構文で指定された並列実行領域内での変数の
取り扱いを指示
指定した変数のコピーを全てのスレッドが個別に保持
コピーされた変数の値は引き継がれない
 0もしくは未定義
for構文でワークシェアされたfor文のループカウンタは
private
int data;
#pragma omp parallel private(data)
{
全てのスレッドがdataのコピーを個別に保持
}
1073
先端GPGPUシミュレーション工学特論
2015/07/16
データ属性制御

reduction指示節


値の総和や最大値などを求めるfor文の並列化に利用
結果を保持する変数のコピーが各スレッドに作成され,並
列処理の最後でまとめられる


reduction(op:変数) opは+,‐,*,&,|,^,&&,||のいずれか
変数 = 変数 op 変数もしくは値
int sum=0, c[N];
#pragma omp parallel for reduction(+:sum)
for(i=0; i<N; i++){
sum += c[i];
forループを分割し,各スレッドがsumを個別に保持して
総和計算を実行
}
各スレッドのsumをまとめて総和を計算
1074
先端GPGPUシミュレーション工学特論
2015/07/16
OpenMPランタイムAPI

omp_set_num_threads


並列実行領域のスレッド数を指定
並列実行領域の直前で呼出
omp_set_num_threads(4);
#pragma omp parallel
{
4スレッドが起動し,並列実行領域内の処理を実行
}

omp_get_num_threads

並列実行領域内で実行されているスレッド数を返す
omp_set_num_threads(12);
#pragma omp parallel
{
printf("%d¥n",omp_get_num_threads());//12と表示
}
1075
先端GPGPUシミュレーション工学特論
2015/07/16
OpenMPランタイムAPI

omp_get_thread_num

並列実行領域内で実行されているスレッドに割り振られた番号を返す
omp_set_num_threads(4);
#pragma omp parallel
{
printf("%d¥n",omp_get_thread_num());
//呼び出したスレッドに応じて0~3のいずれかを表示
}

omp_get_wtime


倍精度浮動小数点で時間(秒単位)を返す
全てのOSで実際の実行時間を取得可能
double time_start = omp_get_wtime();
...
double time_end
= omp_get_wtime();
printf("実行時間 %f sec¥n",time_end‐time_start);
1076
先端GPGPUシミュレーション工学特論
2015/07/16
OpenMPを利用して複数GPUを利用
#include<stdio.h>
#include<stdlib.h>
#include<omp.h>
__global__ void add
(float *a, float *b, float *c){
int i = blockIdx.x*blockDim.x
+ threadIdx.x;
#define N (1024*1024*2)
#define Nbytes (N*sizeof(float))
#define NT 256
#define NB (N/NT)
#define GPUs 4
c[i] = a[i] + b[i];
}
__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;
}
vectoradd_omp.cu
1077
先端GPGPUシミュレーション工学特論
2015/07/16
OpenMPを利用して複数GPUを利用
int main(){
float *a,*b,*c;
float *host_c
= (float *)malloc(Nbytes);
int th;
cudaMemcpy(&host_c[N/GPUs * th], c,
Nbytes/GPUs, cudaMemcpyDeviceToHost);
cudaFree(a);
cudaFree(b);
cudaFree(c);
omp_set_num_threads(GPUs);
#pragma omp parallel private(th,a,b,c) }
double sum=0;
shared(host_c)
for(int i=0;i<N;i++)
{
sum+=host_c[i];
th = omp_get_thread_num();
cudaSetDevice(th);
printf("%f¥n",sum/N);
cudaMalloc((void **)&a,Nbytes/GPUs);
cudaMalloc((void **)&b,Nbytes/GPUs);
free(host_c);
cudaMalloc((void **)&c,Nbytes/GPUs);
return 0;
}
init<<< NB/GPUs, NT >>>(a,b,c);
add<<< NB/GPUs, NT >>>(a,b,c);
vectoradd_omp.cu
1078
先端GPGPUシミュレーション工学特論
2015/07/16
コンパイル

コンパイルのみ実行し,オブジェクトファイルを作成


OpenMPディレクティブが記述されたソース(.c, .cpp)は
cc/gcc/g++でコンパイル
CUDAのカーネルが記述されたソース(.cu)はnvccでコン
パイル
#pragma...
$ cc ‐fopenmp ‐c cpu.c
cpu.c
cpu.o
__global__ void ...
$ nvcc ‐c gpu.cu
gpu.cu
1079
gpu.o
先端GPGPUシミュレーション工学特論
2015/07/16
リンク

オブジェクトファイルをリンクして実行ファイルを作成

OpenMPのランタイムライブラリ(libgomp)を追加
cpu.o
$ nvcc cpu.o gpu.o ‐lgomp
a.out
gpu.o
1080
先端GPGPUシミュレーション工学特論
2015/07/16
コンパイル

コンパイルのみ実行し,オブジェクトファイルを作成


OpenMPディレクティブとCUDAのカーネルが混在する場合
ソースファイルが一つだけなら‐cオプションは不要
 nvccでコンパイルからリンクまで実行できる
$ nvcc ‐c ‐Xcompiler ‐fopenmp gpu.cu
gpu.cu
1081
__global__ void ...
gpu.o
#pragma...
先端GPGPUシミュレーション工学特論
2015/07/16
リンク

オブジェクトファイルをリンクして実行ファイルを作成


OpenMPのランタイムライブラリ(libgomp)を追加
ソースファイルが一つだけなら‐cオプションは不要
 nvccでコンパイルからリンクまで実行できる
nvcc gpu.o ‐lgomp
gpu.o
1082
a.out
先端GPGPUシミュレーション工学特論
2015/07/16
1スレッドで複数のGPUを利用
#include<stdio.h>
#include<stdlib.h>
#define N (1024*1024*2)
#define Nbytes (N*sizeof(float))
#define NT 256
#define NB (N/NT)
#define GPUs 4
__global__ void add
(float *a, float *b, float *c){
int i = blockIdx.x*blockDim.x
+ threadIdx.x;
c[i] = a[i] + b[i];
}
__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;
}
vectoradd_multigpu.cu
1083
先端GPGPUシミュレーション工学特論
2015/07/16
1スレッドで複数のGPUを利用
int main(){
float *a[GPUs],*b[GPUs],*c[GPUs];
int dev;
for(dev=0;dev<GPUs;dev++){
cudaSetDevice(dev);
cudaMemcpy(&host_c[dev*N/GPUs],
c[dev], Nbytes/GPUs,
cudaMemcpyDeviceToHost);
}
double sum=0;
for(int i=0;i<N;i++)sum+=host_c[i];
printf("%f¥n",sum/N);
free(host_c);
for(dev=0;dev<GPUs;dev++){
cudaSetDevice(dev);
cudaMalloc((void **)&a[dev],Nbytes/GPUs);
cudaMalloc((void **)&b[dev],Nbytes/GPUs);
cudaMalloc((void **)&c[dev],Nbytes/GPUs);
}
for(dev=0;dev<GPUs;dev++){
cudaSetDevice(dev);
for(dev=0;dev<GPUs;dev++){
cudaSetDevice(dev);
cudaFree(a[dev]);
cudaFree(b[dev]);
cudaFree(c[dev]);
}
return 0;
init<<<NB/GPUs,NT>>(a[dev],b[dev],c[dev]);
add<<<NB/GPUs,NT>>>(a[dev],b[dev],c[dev]);
}
float *host_c = (float *)malloc(Nbytes);
1084
}
vectoradd_multigpu.cu
先端GPGPUシミュレーション工学特論
2015/07/16
GPU Direct v2.0

2個のGPU間でデータを移動

ホストメモリを経由せず,GPU間でpeer to peer通信
ホストメモリを経由した従来のデータコピー
メモリ
メモリ
メモリ
CPU
GPU1
GPU2
PCI‐Ex
1085
先端GPGPUシミュレーション工学特論
2015/07/16
GPU Direct v2.0

2個のGPU間でデータを移動

ホストメモリを経由せず,GPU間でpeer to peer通信
ホストメモリを経由した従来のデータコピー
メモリ
メモリ
メモリ
CPU
GPU1
GPU2
PCI‐Ex
1086
先端GPGPUシミュレーション工学特論
2015/07/16
GPU Direct v2.0

2個のGPU間でデータを移動


ホストメモリを経由せず,GPU間でpeer to peer通信
同じIO‐Hubに接続されていることが条件
GPU Directによるpeer to peerコピー
メモリ
メモリ
メモリ
CPU
GPU1
GPU2
PCI‐Ex
1087
先端GPGPUシミュレーション工学特論
2015/07/16
GPU Direct v2.0

利用できるデバイス

Fermi以降のGPU

64 bit LinuxもしくはWindowsで動作

Windowsの場合はTesla GPUのみで利用可能

Linuxの場合はGeForceでも利用可能
1088
先端GPGPUシミュレーション工学特論
2015/07/16
GPU Direct v2.0を利用したデータ交換

手順

GPUを2個選択

GPUがpeer to peer通信可能かを確認

peer to peer通信を個別に有効化
 1台目から2台目,2台目から1台目の双方向を有効化

データをコピー
1089
先端GPGPUシミュレーション工学特論
2015/07/16
GPU Direct v2.0を利用したデータ交換

GPUの選択

cudaSetDevice(使用GPU番号)
cudaSetDevice(2); //GPU2を選択

GPUがpeer to peer通信可能かを確認

cudaDeviceCanAccessPeer(結果, アクセス元GPU,アクセス先GPU)
cudaDeviceCanAccessPeer(&result,2,3);
//GPU2からGPU3へPeer通信できるかをresultに書き込む
//resultが1ならPeer通信可能,0なら不可能
1090
先端GPGPUシミュレーション工学特論
2015/07/16
GPU Direct v2.0を利用したデータ交換

Peer to peer通信を個別に有効化

cudaDeviceEnablePeerAccess(アクセス先GPU,0);
cudaSetDevice(2);
cudaDeviceEnablePeerAccess(3,0);
//GPU2から3へのpeer通信を有効化

データをコピー

cudaMemcpyPeer(コピー先GPU, コピー先変数, コピー元GPU, コ
ピー元変数, 変数サイズ);
cudaMemcpyPeer(3, b, 2, a, sizeof(float)*100);
//GPU2の変数aからGPU3の変数bへfloat型100個分コピー
1091
先端GPGPUシミュレーション工学特論
2015/07/16
GPU DirectによるP2P通信
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define N 65536
#define Bytes (N*sizeof(float))
void p2p (float *, float *, float *, float *);
//a,bをそれぞれa_cp,b_cpにコピー
p2p(a,b,a_cp,b_cp);
//正しくコピーできているかチェック
int num_error=0;
for(int i=0;i<N;i++)
if(abs(a[i]‐a_cp[i])>0.1f)
num_error++;
printf("error = %d¥n",num_error);
int main(void){
float *a = (float *)malloc(Bytes);
float *b = (float *)malloc(Bytes);
float *a_cp=(float *)malloc(Bytes);
float *b_cp=(float *)malloc(Bytes);
for(int i=0;i<N;i++){
a[i]=i+1;
b[i]=2*(i+1);
a_cp[i]=0;
b_cp[i]=0;
}
1092
num_error=0;
for(int i=0;i<N;i++)
if(abs(b[i]‐b_cp[i])>0.1f)
num_error++;
printf("error = %d¥n",num_error);
return 0;
}
peer2peer.cu
先端GPGPUシミュレーション工学特論
2015/07/16
GPU DirectによるP2P通信
void p2p(float *a, float *b, float *a_cp, float *b_cp){
float *dev0_a, *dev0_b;
float *dev1_a, *dev1_b;
cudaDeviceProp deviceProp;
int dev0, dev1, canAccess0To1, canAccess1To0;
//使用するGPUを選択.dev0が1台目,dev1が2台目
dev0 = 2;
dev1 = 3;
//peer to peer通信が可能なGPUかチェックする
//1台目から2台目へP2P通信が可能かチェック
cudaDeviceCanAccessPeer(&canAccess0To1, dev0, dev1);
//2台目から1台目へP2P通信が可能かチェック
cudaDeviceCanAccessPeer(&canAccess1To0, dev1, dev0);
printf("dev 0 To 1 P2P access = %d¥n
dev 1 To 0 P2P access = %d¥n",canAccess0To1,canAccess1To0);
peer2peer.cu
1093
先端GPGPUシミュレーション工学特論
2015/07/16
GPU DirectによるP2P通信
//2台のGPUがお互いにP2P通信できるならデータコピーを実行
if(canAccess0To1 == 1 && canAccess1To0 == 1){
//1台目のGPUがUVAをサポートしているかをチェック
cudaSetDevice(dev0);
cudaGetDeviceProperties(&deviceProp, dev0);
printf("device %d supports Unified Virtual Addressing : %d¥n"
,dev0, deviceProp.unifiedAddressing);
//1台目から2台目へのP2P通信を有効化.2個目の引数は必ず0
cudaDeviceEnablePeerAccess(dev1,0);
//2台目のGPUがUVAをサポートしているかをチェック
cudaSetDevice(dev1);
cudaGetDeviceProperties(&deviceProp, dev1);
printf("device %d supports Unified Virtual Addressing : %d¥n"
,dev1, deviceProp.unifiedAddressing);
//2台目から1台目へのP2P通信を有効化.2個目の引数は必ず0
cudaDeviceEnablePeerAccess(dev0,0);
peer2peer.cu
1094
先端GPGPUシミュレーション工学特論
2015/07/16
GPU DirectによるP2P通信
//1台目のGPUで使うメモリを確保し,変数aをCPUからGPUへコピー(dev0_bは未初期化)
cudaSetDevice(dev0);
cudaMalloc((void **)&dev0_a, Bytes);
cudaMalloc((void **)&dev0_b, Bytes);
cudaMemcpy(dev0_a, a, Bytes, cudaMemcpyHostToDevice);//dev0_aを初期化
//2台目のGPUで使うメモリを確保し,変数bをCPUからGPUへコピー(dev1_aは未初期化)
cudaSetDevice(dev1);
cudaMalloc((void **)&dev1_a, Bytes);
cudaMalloc((void **)&dev1_b, Bytes);
cudaMemcpy(dev1_b, b, Bytes, cudaMemcpyHostToDevice);//dev1_bを初期化
cudaSetDevice(dev0);
//2台目のGPU(dev1)にあるdev1_aへ,1台目のGPU(dev0)のdev0_aをBytes分コピー
cudaMemcpyPeer(dev1_a, dev1, dev0_a, dev0, Bytes);
//1台目のGPU(dev0)にあるdev0_bへ,2台目のGPU(dev1)のdev1_bをBytes分コピー
cudaMemcpyPeer(dev0_b, dev0, dev1_b, dev1, Bytes);
peer2peer.cu
1095
先端GPGPUシミュレーション工学特論
2015/07/16
GPU DirectによるP2P通信
//1台目のGPU(dev0)にあるdev0_bを,CPUへコピー(結果の確認用)
cudaSetDevice(dev0);
cudaMemcpy(b_cp,dev0_b, Bytes, cudaMemcpyDeviceToHost);
//2台目のGPU(dev1)にあるdev1_aを,CPUへコピー(結果の確認用)
cudaSetDevice(dev1);
cudaMemcpy(a_cp,dev1_a, Bytes, cudaMemcpyDeviceToHost);
//1台目のGPU(dev0)で確保したメモリをクリア
cudaSetDevice(dev0);
cudaFree(dev0_a);
cudaFree(dev0_b);
cudaDeviceDisablePeerAccess(dev1);//dev1へのPeer通信を無効化
//2台目のGPU(dev1)で確保したメモリをクリア
cudaSetDevice(dev1);
cudaFree(dev1_a);
cudaFree(dev1_b);
cudaDeviceDisablePeerAccess(dev0);//dev0へのPeer通信を無効化
}
}
peer2peer.cu
1096
先端GPGPUシミュレーション工学特論
2015/07/16
Unified Virtual Addressing

複数のGPUのメモリアドレスとcudaHostAlloc()で
確保したCPUのメモリアドレスを統一的に管理



40 bitメモリ空間を構成
異なるGPUのメモリやCPUのメモリの区別が不要
異なるGPUに置かれたメモリを参照可能

GPU directを利用してアクセス


1097
cudaDeviceEnablePeerAccessを利用
記述は楽になるが性能は出ない
先端GPGPUシミュレーション工学特論
2015/07/16
UVAを利用したベクトル和
#include <stdio.h>
#include <stdlib.h>
#define N (1024*1024*1)
#define Nbytes (N*sizeof(float))
#define NT 256
#define NB (N/NT)
//カーネルは変更なし
__global__ void add
(float *a, float *b, float *c){
int i = blockIdx.x*blockDim.x
+ threadIdx.x;
c[i] = a[i] + b[i];
}
//カーネルは変更なし
__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;
}
vectoradd_uva.cu
1098
先端GPGPUシミュレーション工学特論
2015/07/16
UVAを利用したベクトル和
int main(void){
int dev0,dev1;
float *a,*b,*c;
//使用するGPUを選択.dev0が1台目,dev1が2台目
dev0 = 2;
dev1 = 3;
int canAccess1To0=0;
//2台目から1台目へP2P通信が可能かチェック
cudaDeviceCanAccessPeer(&canAccess1To0, dev1, dev0);
printf("dev 1 To 0 P2P access = %d¥n",canAccess1To0);
//2台目から1台目へP2P通信が可能ならif文の中を実行
if(canAccess1To0 == 1){
//1台目のGPUでメモリを確保(変数a,b,cは1台目のメモリに存在)
cudaSetDevice(dev0);
cudaMalloc( (void **)&a, Nbytes);
cudaMalloc( (void **)&b, Nbytes);
cudaMalloc( (void **)&c, Nbytes);
vectoradd_uva.cu
1099
先端GPGPUシミュレーション工学特論
2015/07/16
UVAを利用したベクトル和
//2台目のGPUを利用するようにデバイスを切替
cudaSetDevice(dev1);
//2台目から1台目へのP2P通信を有効化.2個目の引数は必ず0
cudaDeviceEnablePeerAccess(dev0, 0);
//1台目のGPUのメモリを参照して2台目のGPUで初期化とベクトル和を実行
init<<<NB, NT>>>(a, b, c);
add<<<NB, NT>>>(a, b, c);
//2台目のGPUが1台目のGPUのメモリをCPUへコピー
float *host_c = (float *)malloc(Nbytes);
cudaMemcpy(host_c, c, Nbytes, cudaMemcpyDeviceToHost);
int i;double sum=0;
for(i=0;i<N;i++) sum+=host_c[i];
printf("%f¥n",sum/N);
free(host_c);
//利用するGPUを1台目に切り替えて確保したメモリをクリア
cudaSetDevice(dev0);
cudaFree(a);
cudaFree(b);
cudaFree(c);
vectoradd_uva.cu
}
1100
先端GPGPUシミュレーション工学特論
2015/07/16
UVAを利用したベクトル和
else{//P2P通信が不可能なら1台のGPUで実行
cudaMalloc( (void **)&a, Nbytes);
cudaMalloc( (void **)&b, Nbytes);
cudaMalloc( (void **)&c, Nbytes);
init<<<NB, NT>>>(a, b, c);
add<<<NB, NT>>>(a, b, c);
float *host_c = (float *)malloc(Nbytes);
cudaMemcpy(host_c, c, Nbytes, cudaMemcpyDeviceToHost);
int i;double sum=0;
for(i=0;i<N;i++)sum+=host_c[i];
printf("%f¥n",sum/N);
free(host_c);
cudaFree(a);
cudaFree(b);
cudaFree(c);
}
return 0;
}
vectoradd_uva.cu
1101
先端GPGPUシミュレーション工学特論
2015/07/16
実行結果

配列の要素数
N=220
実行時間[ms]
カーネル


1102
単一GPU
UVA
初期化init
0.108
2.28
ベクトル和add
0.113
1.30
Unified Virtual Addressingを利用してP2Pアクセス
すると10~20倍以上の時間を要する
大量のデータにアクセスせず,限定的な利用が重要
 データが少なく,わざわざコピーするまでもない場合等
先端GPGPUシミュレーション工学特論
2015/07/16