長岡技術科学大学電気電子情報工学専攻 出川智啓
GPGPU講習会
CUDA C以外の開発環境(PyCUDA)
本講習会の目標
 GPGPU先端シミュレーションシステムの使用方法の
習得
 GPUの活用方法の修得
 CUDAプログラミング技法の修得
 並列計算手法の修得
2015/11/25GPGPU講習会2
本日の内容
2015/11/25GPGPU講習会3
 CUDA C以外のGPU開発環境
 Python
 Pythonについて
 Pythonのライブラリ
 numpy
 移流方程式の計算
 PyCUDAの使い方
既に使える人には必要ありません
お断り
2015/11/25GPGPU講習会4
 デモは全てWindows, Python 3.4で行います
 シェルの実行イメージはLinuxです
 プロンプトは$です
 grouseではPython 2.4が利用できます
 Pythonはバージョン2と3があり,互換性がない処理もあ
ります
Python
Python
2015/11/25GPGPU講習会6
 インタプリタ言語
 コンパイル不要
 変数の型宣言不要
 型は代入する右辺値から動的に決定
 汎用プログラミング言語
 Web開発や数値計算向けなどと用途を限定していない
 海外では広く普及
 GoogleやNASAでも利用
 日本での知名度は上昇しているが今一歩
Python
2015/11/25GPGPU講習会7
 利点
 様々なライブラリが利用可能
 読みやすいコードが書ける
 メモリ管理が楽
 フリー
 欠点
 コンパイルが不要な分,実行速度が遅い
 MatlabやMathematicaと比べると開発環境が使いにくい
 メーカーサポートは存在しない
 専門的すぎる分野ではライブラリがない場合がある
PythonでHello World
2015/11/25GPGPU講習会8
 中カッコが存在しない
 インデントでスコープ(構造の深さ)を表現
 海外では,「Pythonユーザは中カッコを嫌っている人もしくは
科学者である」とも言われている
def main():
˽˽˽˽print("hello world")
if __name__ == "__main__":
˽˽˽˽main()
#include<stdio.h>
int main(void){
printf("hello world¥n");
return 0;
}
helloworld.py helloworld.c
print("hello world")
or
Pythonスクリプトの実行
2015/11/25GPGPU講習会9
 .pyファイルの中身を解釈して実行
 コンパイル不要
$ python helloworld.py⏎
hello world
$
$ python⏎
>>> def main():⏎
... ˽˽˽˽print("hello world")⏎
... ⏎
>>> main()⏎
hello world
>>> print("hello world")⏎
hello world
>>> Ctrl + D (Ctrl+Dでpython shellを終了,WindowsはCtrl+Z)
Pythonスクリプトの実行
2015/11/25GPGPU講習会10
 処理するプログラムを明記実行権限を付与すればファイ
ル単体でスクリプトが実行可能
$ chmod u+x helloworld.py⏎
$ ./helloworld.py⏎
hello world
$
#!/usr/bin/env python
print("hello world")
#!/usr/bin/env python
def main():
˽˽˽˽print("hello world")
if __name__ == "__main__":
˽˽˽˽main()
コメント
2015/11/25GPGPU講習会11
 1行コメント
 #がコメント記号
 #以降がコメントとして扱われる
 複数行コメント
 ダブルクオート3個を複数行コメントとして利用することもある
def main():
˽˽˽˽"""hello world
˽˽˽˽画面にhelloと表示"""
˽˽˽˽print("hello world")
#この項目については後ほど説明
if __name__ == "__main__":
˽˽˽˽main() #main関数を呼出
コメント
2015/11/25GPGPU講習会12
 複数行コメント(ドットストリング)の正しい使い方
 関数の使い方をダブルクオート3個で囲んで記述
$ python⏎
>>> def main():⏎
... ˽˽˽˽"""hello world⏎
... ˽˽˽˽画面にhelloと表示"""⏎
... ˽˽˽˽print("hello world")⏎
... ⏎
>>> help(main)⏎
Help on function main in module __main__:
main()
hello world
画面にhelloと表示
(END) q (qキーでヘルプを終了)
>>> 
既存スクリプトの読込
2015/11/25GPGPU講習会13
 import ファイル名(拡張子.pyは不要)
 関数を呼び出す際は,ファイル名.関数名
$ python⏎
>>> import helloworld⏎
>>> helloworld.main()⏎
hello world
>>> help(helloworld.main)⏎
Help on function main in module helloworld:
main()
hello world
画面にhelloと表示
(END) q
>>> 
変数
2015/11/25GPGPU講習会14
 Pythonの変数
 変数の型宣言が不要
 名前を書いて数値を代入すれば自動で型を決定
 異なる型を代入すると新たな型になる
 Pythonの変数の型
 int型(整数型)
 float型(浮動小数点型)
 complex型(複素数型)
 文字列
 論理型
int型
2015/11/25GPGPU講習会15
 精度の制限がない
 メモリのある限り大きな値を保持
$ python⏎
>>> a = 1⏎
>>> a⏎
1
>>> a = ‐1⏎
>>> a⏎
‐1
>>> ‐a⏎
1
>>> import math⏎
>>> math.factorial(100)⏎
9332621544394415268169923885626670049071596826438162146859296389
5217599993229915608941463976156518286253697920827223758251185210
916864000000000000000000000000
int型
2015/11/25GPGPU講習会16
 2から36進数で記述可能
 基本は10進数
 0b, 0o, 0xをつけるとそれぞれ2, 8, 16進数表記
$ python⏎
>>> a = 0x11⏎
>>> a⏎
17
>>> a = 0b01010⏎
>>> a⏎
10
>>> bin(10)⏎
'0b01010'
>>> hex(10)⏎
'0xa'
int型
2015/11/25GPGPU講習会17
 2から36進数で記述可能
 int('文字',基数)で10進数を生成
$ python⏎
>>> int('0')⏎ #省略すると10進数
0
>>> int('a')⏎ #10進数ではaは使えないのでエラー
>>> int('a',16)⏎ #基数を指定
10
>>> int('z',36)⏎ #36進数は0~9,a~zを利用
35
>>> int('python2',36)⏎
56524942334
>>> int('python3',36)⏎
56524942335
float型
2015/11/25GPGPU講習会18
 Cのdouble型と同じ
 8バイト(64bit),数字の表現は53bit
 有効桁数は10進数で約15桁
 指数表記も可能
 有効桁数や丸めの問題は回避できない
$ python⏎
>>> a = 3.14⏎
>>> a⏎
3.1400000000000001
>>> a = 1e‐2⏎
>>> a⏎
0.01
>>> a = 0.1+0.1+0.1⏎
>>> a⏎
0.30000000000000004
float型
2015/11/25GPGPU講習会19
 int型同士の除算でfloat型が生成される場合
 Python2系はint型を生成
 小数点以下は切り捨て
 Python3系はfloat型を生成
>>> 3/2⏎
1.5
>>> 3//2⏎
1
>>> 3/2⏎
1
complex型
2015/11/25GPGPU講習会20
 実数と虚数からなる
 実数+虚数j として記述
 変数名.realで実数を取り出す
 変数名.imagで虚数を取り出す
>>> a = 1+5j⏎
>>> a⏎
(1+5j)
>>> a.real⏎
1.0
>>> a.imag⏎
5.0
>>>
complex型
2015/11/25GPGPU講習会21
 四則演算も可能
>>> a=1+5j⏎
>>> a⏎
(1+5j)
>>> b=2+6j⏎
>>> b⏎
(2+6j)
>>> a+b⏎
(3+11j)
>>> a‐b⏎
(‐1‐1j)
>>> a*b⏎
(‐28+16j)
>>> a/b⏎
(0.79999999999999993+0.099999999999999978j)
数学関数
2015/11/25GPGPU講習会22
 mathモジュール
 int型とfloat型に対する演算(返値はfloat型)
 cmathモジュール
 complex型に対する演算(返値はcomplex型)
>>> import math⏎
>>> math.sin(math.pi)⏎
1.2246467991473532e‐16
>>> math.cos(math.pi)⏎
‐1.0
>>> import cmath⏎
>>> a = cmath.sqrt(complex(1,1))⏎
>>> a⏎
(1.09868411346781+0.45508986056222733j)
>>> a*a⏎
(1.0000000000000002+1j)
文字列
2015/11/25GPGPU講習会23
 シングルクオート''もしくはダブルクオート""で文字を囲
んで表現
 Python2系ではASCII, Python3系ではUnicodeで保持
>>> a = 'string'⏎
>>> a⏎
'string'
>>> a*2⏎
'stringstring'
>>> b = 'other string'⏎
>>> a+b⏎
'stringother string'
>>> a = a+b⏎
>>> a⏎
'stringother string'
>>> len(a)⏎
18
文字列へのアクセス
2015/11/25GPGPU講習会24
 インデックスを使ったアクセス
 変数名[インデックス]
 範囲は0~文字列の長さ‐1 (C言語と同じ)
 正の整数は先頭からの位置
 負の整数は終端からの位置
>>> a = 'string'⏎ #文字列の長さは6なので,インデックスの範囲は0~5
>>> a[0]⏎
's'
>>> a[1]⏎
't'
>>> a[6]⏎ #エラー
>>> a[‐1]⏎ #実質の終端
'g'
>>> a[‐4]⏎
'r'
文字列へのアクセス
2015/11/25GPGPU講習会25
 スライスを使ったアクセス
 変数名[開始位置:終了位置:ストライド]
 : だけ書けば全範囲
 C言語のfor文の処理に類似
 for(i=開始位置;i<終了位置;i+=ストライド)
>>> a = 'string'⏎ #文字列の長さは6なので,インデックスの範囲は0~5
>>> a[:]⏎
'string'
>>> a[5]⏎
'g'
>>> a[2:5]⏎ #5番目のインデックスを含むなら'g'まで出てくるはず
'rin'
>>> a[2:]⏎ #終端まで表示したいときは終了インデックスを書かない
'ring'
文字列の要素へのアクセス
2015/11/25GPGPU講習会26
 スライスを使ったアクセス
 変数名[開始位置:終了位置:ストライド]
 : だけ書けば全範囲
 C言語のfor文の処理に類似
 for(i=開始位置;i<終了位置‐|負のインデックス|;i+=ストライド)
>>> a[:‐1]⏎ #開始インデックスを書かなければ先頭から
'strin'
>>> a[3:‐2]⏎ #for(i=3;i<6‐|‐2|;i+=1)
'i'
>>> a[1:5:3]⏎
'tn'
>>> a[::2]⏎ #先頭から終端まで1文字飛ばしで表示
'srn'
分岐
2015/11/25GPGPU講習会27
 if
 渡された値の真偽を評価し,実行する処理を切替
if 値1:
˽˽˽˽値1が真の時の処理
elif 値2:
˽˽˽˽値2が真の時の処理
elif 値3:
˽˽˽˽値3が真の時の処理
else:
˽˽˽˽上の全ての値が偽のときに実行する処理
if 値: #elifやelseは必須ではない
˽˽˽˽値が真の時の処理
分岐
2015/11/25GPGPU講習会28
 比較
 a > b aがbより大きい
 a >= b aがbより大きいか等しい
 a < b aがbより小さい
 a <= b aがbより小さいか等しい
 a == b aとbが等しい
 a != b aとbが等しくない
 論理演算
 条件1 and 条件2 条件1が真 かつ 条件2が真
 条件1 or 条件2 条件1が真 もしくは 条件2が真
 not 条件 条件が真でない
分岐
2015/11/25GPGPU講習会29
 比較結果はTrueかFalseで評価
>>> a=3⏎
>>> b=4⏎
>>> a>b⏎
False
>>> a<=b⏎
True
>>> a==b⏎
False
>>> a!=b⏎
True
>>> 0<=a<=b⏎ #比較はまとめて行うことが可能
True
>>> flag = 0<=a<=b⏎ #flagは論理型変数(TrueかFalseを扱う)
>>> flag⏎
True
分岐
2015/11/25GPGPU講習会30
 比較結果はTrueかFalseで評価
>>> a>=3 and b<=4⏎
True
>>> a>3 or b<=4⏎
True
>>> not a>3⏎
True
>>> a = 'string'⏎
>>> b = 'string'⏎
>>> a==b⏎ #文字列同士の比較も可能
True
>>> b = 'string2'⏎
>>> a==b⏎
False
分岐
2015/11/25GPGPU講習会31
 絶対値の計算
 実行
def main():
a = 10
if a<0:
a=‐a
print(a)
if __name__ == "__main__":
main()
abs.py
$ python abs.py⏎
分岐
2015/11/25GPGPU講習会32
 if __name__ == "__main__":の意味
 実行の仕方で__name__の値が変化
 スクリプトを実行 __name__の値は"__main__"
 shellからimport __name__の値はファイル名
 スクリプトを実行したときはmain関数を実行
 shellからimportされたときはmain関数を実行しない
print("__name__ is"+__name__)
$ python test.py⏎
__name__ is __main__
$ python⏎
>>> import test⏎
__name__ is test #importした時点で勝手に実行されている
test.py
繰り返し
2015/11/25GPGPU講習会33
 for
 処理をある一定回数繰り返す
 C言語とは書き方が異なる
 コンテナ
 データの格納方法の一つ
for ループ内変数 in コンテナ:
˽˽˽˽繰り返し実行する処理
else:
˽˽˽˽ループ終了後(forループを実行しなかった場合)に行う処理
>>> x = [0,1,2,3,4]
>>> x[1]
1
繰り返し
2015/11/25GPGPU講習会34
>>> x = [0,1,2,3,4]⏎
>>> for i in x:⏎ #xの最初から最後まで変化させながら処理を実行
... ˽˽˽˽print(i)⏎ #print(x[i])と等価
... ⏎
0
1
2
3
4
>>> a = 'string'⏎ #文字列もコンテナの一種
>>> for i in a:⏎ #aの最初から最後まで変化させながら処理を実行
... ˽˽˽˽print(i)⏎ #print(a[i])と等価
... ⏎
s
t
r
i
n
g
繰り返し
2015/11/25GPGPU講習会35
 決まった回数繰り返す
 range()関数でコンテナを生成
 range(終端) 0から終端‐1まで1ずつ変化するコンテナを生成
 range(先頭,終端,ストライド)という使い方もできる(先頭から終端‐1ま
でストライドずつ変化するコンテナを生成)
>>> range(5)⏎
[0,1,2,3,4]
>>> range(1,5)⏎
[1,2,3,4]
>>> range(1,5,2)⏎
[1,3]
>>> sum=0⏎ #1から10までの合計を計算
>>> for i in range(1,11):⏎ #
... ˽˽˽˽sum+=i⏎ #
... ⏎
>>> sum⏎
55
関数の定義と呼出
2015/11/25GPGPU講習会36
 関数の定義
 関数の名前
 仮引数の名前
 0個以上
 返値(戻り値)
 型の指定は不要
 省略可能
def 関数名(仮引数,仮引数 ・・・(必要な数だけ書く))
˽˽˽˽処理の内容
˽˽˽˽return 戻り値
絶対値を計算する関数の定義と呼出
2015/11/25GPGPU講習会37
 定義位置はかなり柔軟
def abs(i): #C言語などを知っている人にとって標準的な位置
if i<0:
return ‐i
else:
return i
def main():
i = 10
absi = abs(i)
print(absi)
if __name__ == "__main__":
main()
abs.py
絶対値を計算する関数の定義と呼出
2015/11/25GPGPU講習会38
 定義位置はかなり柔軟
def main():
i = 10
absi = abs(i)
print(absi)
def abs(i): #関数を呼び出す位置より下で定義することも可能
if i<0:
return ‐i
else:
return i
if __name__ == "__main__":
main()
abs.py
絶対値を計算する関数の定義と呼出
2015/11/25GPGPU講習会39
 定義位置はかなり柔軟
def main():
i = 10
def abs(i): #関数の中で別の関数を定義する事も可能
if i<0:
return ‐i
else:
return i
absi = abs(i)
print(absi)
if __name__ == "__main__":
main()
abs.py
Pythonのライブラリ
2015/11/25GPGPU講習会40
 数学関数のライブラリ(モジュール)
 math, cmath
 数値計算に有用なライブラリ(拡張モジュール)
 別途インストールする必要がある
 NumPy
 多次元配列や行列の宣言と操作,演算
 SciPy
 線形代数演算,フーリエ変換,補間,画像処理など
 SymPy
 記号計算(数式の微積分,因数分解など)
 matplotlib
 グラフ描画
NumPy(Numerical Python)
2015/11/25GPGPU講習会41
 NumPyを使う理由
 多次元配列arrayが利用できる
 C言語の配列に相当
 配列の各要素の型は全て同じ
 配列の形状が固定
 配列の各要素が連続なメモリアドレスに配置
 PyCUDAでもarrayを利用
 CUDAのカーネルに配列を渡す場合
 Pythonではarrayを利用して明示的に型を指定
 NumPyの機能は全てSciPyで提供されている
NumPy
2015/11/25GPGPU講習会42
 NumPyの利用方法
 import numpy
 NumPyの機能を利用するにはnumpy.を付ける必要がある
 import numpy as np
 numpyよりも記述量が少なく,他のモジュールと衝突することもない
>>> import numpy
>>> numpy.array([0,1,2,3])
array([0,1,2,3])
>>> import numpy as np
>>> np.array([0,1,2,3])
array([0,1,2,3])
1次元配列
2015/11/25GPGPU講習会43
 定義とアクセス
>>> import numpy as np⏎
>>> x = np.array([0,1,2,3])⏎
>>> print(x)⏎
[0 1 2 3]
>>> x[3]⏎
3
>>> for i in range(4):⏎
... ˽˽˽˽print(x[i])⏎
... ⏎
0
1
2
3
>>>
1次元配列
2015/11/25GPGPU講習会44
 演算
>>> import numpy as np⏎
>>> x = np.array([0,1,2,3])⏎
>>> y = np.array([4,5,6,7])⏎
>>> print(x+y)⏎
[ 4 6 8 10]
>>> print(x*y)⏎
[ 0 5 12 21]
>>> print(x.dot(y))⏎
38
>>> dot = 0⏎
>>> for i in range(4):⏎
... ˽˽˽˽dot += x[i]*y[i]⏎
... ⏎
>>> dot⏎
38
2次元配列
2015/11/25GPGPU講習会45
 定義とアクセス
>>> import numpy as np⏎
>>> A = np.array([[1,2,3],[4,5,6],[7,8,9]])⏎
>>> print(A)⏎
[[1 2 3]
[4 5 6]
[7 8 9]]
>>> A[2][1]⏎
8
>>> for i in range(3):⏎
... ˽˽˽˽for j in range(3):⏎
... ˽˽˽˽˽˽˽˽print(A[i][j])⏎ #格納順序はj方向が優先
... ⏎ #行方向を固定して列方向に変化
1
2
:
9
2次元配列
2015/11/25GPGPU講習会46
 演算
 2次元配列は行列ではない
 行列として取り扱いたい場合はarrayではなくmatrixを利用
>>> import numpy as np⏎
>>> A = np.array([[1,2,3],[4,5,6],[7,8,9]])⏎
>>> B = np.array([[10,11,12],[13,14,15],[16,17,18]])⏎
>>> print(A+B)⏎
[[11 13 15]
[17 19 21]
[23 25 27]]
>>> print(A*B)⏎ #行列‐行列積ではなく要素同士の積
[[ 10  22  36]
[ 52  70  90]
[112 136 162]]
>>>
配列情報の確認
2015/11/25GPGPU講習会47
 変数名からarrayの情報を確認可能
 ndim 次元
 size 配列の要素数
 shape 配列の形状(各次元の要素数)
 dtype 配列要素の型
>>> import numpy as np⏎
>>> x = np.array([0,1,2,3])⏎
>>> x.ndim⏎
1
>>> x.size⏎
4
>>> x.shape⏎
(4,)
>>> x.dtype⏎
dtype('int32')
ベクトル和の計算
2015/11/25GPGPU講習会48
import numpy as np
def main():
N = 1024
a = np.ones(N) #N個の要素を持つ配列を確保し,1.0で初期化
b = np.empty_like(a) #aの形状と同じ配列を確保し,初期化はしない
c = np.zeros_like(a) #aの形状と同じ配列を確保し,0.0で初期化
b[:]=2.  #b=2と書くと,配列bが破棄されてスカラ変数b(値は2)になる
c=a+b #[:]を書いて配列の演算であることを示す方がよいと思う
print(a)
print(b)
print(c)
if __name__ == "__main__":
main() vectoradd.py
配列の作り方
2015/11/25GPGPU講習会49
 形状を指定して一定値で初期化(あるいは未初期化)
 引数で指定された形状の配列を生成
 _likeを付けると,引数として既存のarrayをとる
 型はfloat型(C言語のdouble型)
 empty() 全要素を初期化しない
 zeros() 全要素を0.0で初期化
 ones() 全要素を1.0で初期化
>>> import numpy as np⏎
>>> a = np.empty(1024)⏎
>>> a⏎
array([  2.00229098e‐295,               nan,   1.12646967e‐321, ...,
1.00000000e+000,   1.00000000e+000,   1.00000000e+000])
>>> b = np.zeros_like(a)⏎
>>> b⏎
array([ 0.,  0.,  0., ...,  0.,  0.,  0.])
>>> b.dtype⏎
dtype('float64')
配列の作り方
2015/11/25GPGPU講習会50
 生成する数値の範囲と間隔を指定して生成
 numpy.arange(始点,終点,間隔)
 終点は含まれない
 生成する数値の範囲と点数を指定して生成
 numpy.linspace(始点,終点,点数)
 終点を含む
>>> import numpy as np⏎
>>> np.arange(0, 3, 0.5)⏎
array([ 0. ,  0.5,  1. ,  1.5,  2. ,  2.5])
>>> import numpy as np⏎
>>> np.linspace(0, 3, 7)⏎
array([ 0. ,  0.5,  1. ,  1.5,  2. ,  2.5,  3. ])
差分法による1階微分の計算
2015/11/25GPGPU講習会51
 計算機で微分を計算する方法の一つ
 微分の定義
 xの関数uについて,xだけ離れた2点間の傾きを計算し,2点の間隔を
無限小に近づけたときの極限
 差分近似
 関数をある間隔でサンプリング
 その間隔xがuの変化に対して十分小さいと仮定
Δx
xuΔxxu
dx
du )()( 

Δx
xuΔxxu
dx
du
Δx
)()(
lim
0



差分法による1階微分の計算
2015/11/25GPGPU講習会52
 複数の点の取り方が存在
 中心差分を採用
u(x)
x
x=0 ・・・ x−x x x+x
x
Δx
xuΔxxu )()( 
Δx
Δxxuxu )()( 
Δx
ΔxxuΔxxu
2
)()( 
中心差分
前進差分
後退差分
差分法による1階微分の計算
2015/11/25GPGPU講習会53
 複数の点の取り方が存在
 中心差分を採用
u(x)
x
i=0 ・・・ i−1 i i+1
x=0 ・・・ (i−1)x ix (i+1)x
x
サンプリングされた関数値
をarray uで保持
差分法による1階微分の計算
2015/11/25GPGPU講習会54
 複数の点の取り方が存在
 中心差分を採用
u[i]
i
i=0 ・・・ i−1 i i+1
dx
サンプリングされた関数値
をarray uで保持
u[i]
u[i‐1]
u[i+1]
中心差分
(u[i+1]‐u[i‐1])/(2*dx)
差分法による1階微分の計算
 計算領域内部
 dudx[i]=(u[i+1]‐u[i‐1])/(2.*dx)
dudx[i]
u[i]
+ + + +
2015/11/25GPGPU講習会55
Δx2
1
×−1
差分法による1階微分の計算
 境界条件(関数値が無いため処理を変更)
 dudx[0] =(‐3*u[0] +4*u[1] ‐u[2] )/(2.*dx)
 dudx[‐1]=( 3*u[‐1]‐4*u[‐2]+u[‐3])/(2.*dx)
 2階微分値が一定と仮定して関数を補外した事に相当
dudx[i]
u[i]
+
2015/11/25GPGPU講習会56
Δx2
1
×−3 ×4 ×−1
差分法による1階微分の計算
 境界条件(関数値が無いため処理を変更)
 dudx[0] =(‐3*u[0] +4*u[1] ‐u[2] )/(2.*dx)
 dudx[‐1]=( 3*u[‐1]‐4*u[‐2]+u[‐3])/(2.*dx)
 2階微分値が一定と仮定して関数を補外した事に相当
dudx[i]
u[i]
+
2015/11/25GPGPU講習会57
Δx2
1
×−4 ×3
差分法による1階微分の計算
2015/11/25GPGPU講習会58
import numpy as np
def main():
Nx = 5 #サンプリング点の数
Lx = 2.*np.pi #関数の範囲
dx = Lx/(Nx‐1) #サンプリング点の間隔
x = np.linspace(0, Lx, Nx) #x座標の値を持つarrayを作成
u = np.sin(x) #全てのxに対して関数値uを計算
dudx = diff(u,dx) #微分を計算
print(dudx) #結果を表示
print(np.cos(x)) #理論値を表示
differentiate.py
差分法による1階微分の計算
2015/11/25GPGPU講習会59
def diff(u,dx):
dudx = np.empty_like(u)
dudx[ 0]   = (‐3.*u[ 0] + 4.*u[ 1] ‐ u[ 2] )/(2.*dx)
dudx[1:‐1] = (u[2:]‐ u[:‐2])/(2.*dx) #スライスは終端を含まない
dudx[‐1]   = ( 3.*u[‐1] ‐ 4.*u[‐2] + u[‐3] )/(2.*dx)
return dudx
if __name__ == "__main__":
main()
differentiate.py
NumPyの数学関数
2015/11/25GPGPU講習会60
 sin,cosなどmathモジュールと同じ関数を提供
 配列を引数に渡すと,全ての配列要素に対して関数を
適用し,結果を配列で返す
>>> import numpy as np⏎
>>> import math⏎
>>> x = np.linspace(0,2*np.pi,5)⏎
>>> x⏎
array([ 0.        ,  1.57079633,  3.14159265,  4.71238898,  
6.28318531])
>>> math.sin(x[0])⏎
0.0
>>> math.sin(x)⏎ #エラー
>>> np.sin(x)⏎
array([  0.00000000e+00,   1.00000000e+00,   1.22464680e‐16,
‐1.00000000e+00,  ‐2.44929360e‐16])
実行結果
2015/11/25GPGPU講習会61
 結果の確認
 どの程度理論値と一致しているかを図で視覚的に確認
 ファイル出力関数を書いて,gnuplotで・・・
 グラフ描画ライブラリを利用
$ python differentiate.py⏎
[  1.27323954e+000   3.89817183e‐017  ‐6.36619772e‐001   1.38338381e‐321
1.27323954e+000]
[  1.00000000e+00   6.12323400e‐17  ‐1.00000000e+00  ‐1.83697020e‐16
1.00000000e+00]
$
matplotlib
2015/11/25GPGPU講習会62
 Pythonから利用できるグラフ描画ライブラリ
 2次元および3次元グラフの描画が可能
 非常に多くの機能を備えており,大体のグラフが描ける
 matplotlibのギャラリー
 http://matplotlib.org/gallery.html
 Python+NumPyと併用することで,計算しながら結果の
図示が可能
 設定はプログラム中で命令を記述して決定
 同じ言語で計算とグラフ描画を制御
 C,Fortran+gnuplotよりも親和性が高い
matplotlib
2015/11/25GPGPU講習会63
 matplotlibのインポート
 from matplotlib import pyplot as pl
 1次元データのプロット
 プロットするデータやグラフの体裁の設定
 pl.plot(x座標, 値, (ラベルやプロットの方法など))
 pl.xlim(x座標の左端,右端)
 pl.ylim(x座標の最小値,最大値)
 pl.xlable(x座標の軸ラベル)
 pl.ylable(y座標の軸ラベル)
 グラフをプロット
 pl.show()
差分法による計算結果のプロット
2015/11/25GPGPU講習会64
import numpy as np
from matplotlib import pyplot as pl
def main():
:(これ以前は同じなので省略)
pl.plot(x,dudx, label='Computational')
X = np.linspace(0,Lx,101)
pl.plot(X, np.cos(X), label = 'Analytical')
pl.xlim(0,Lx)
pl.ylim(‐1.3,1.3)
pl.xlabel('$x$')
pl.ylabel(r'$¥frac{d}{dx}¥sin x')
pl.xticks([0,np.pi/2,np.pi,1.5*np.pi,2*np.pi],
[r'$0$',r'$¥pi/2$',r'$¥pi$',r'$1.5¥pi$',r'$2¥pi$'])
pl.legend(loc='best')
pl.show()
:(これ以後は同じなので省略) differentiate.py
実行結果
2015/11/25GPGPU講習会65
$ python differentiate.py⏎
[  1.27323954e+000   3.89817183e‐017  ‐6.36619772e‐001   1.38338381e‐321
1.27323954e+000]
[  1.00000000e+00   6.12323400e‐17  ‐1.00000000e+00  ‐1.83697020e‐16
1.00000000e+00]
$
移流方程式の計算
2015/11/25GPGPU講習会66
 流体中の物質の移動を表す方程式
 流れている水の中に落ちたインクの移動等
 時刻t=0におけるuの分布(初期値)が既知
 時間進行に伴い,uがどのように変化するかを計算
 時間積分しながらuの分布を求める
0
),(),(






x
txu
c
t
txu
c : x方向速度
移流方程式の計算
2015/11/25GPGPU講習会67
 時間微分項の離散化
 時間微分項を前進差分で離散化
 右辺のt+tの項を移行
Δt
txuΔttxu
t
u ),(),( 



x
u
c
t
u





t
u
ΔttxuΔttxu


 ),(),(
移流方程式を代入
x
txu
ΔtctxuΔttxu



),(
),(),(
移流方程式の計算
2015/11/25GPGPU講習会68
 連続系
 離散系
 t秒後の値
0
),(),(






x
txu
c
t
txu
0
2
11
1



 

Δx
uu
c
Δt
uu n
i
n
i
n
i
n
i
Δx
uu
Δtcuu
n
i
n
in
i
n
i
2
111  

計算手順
1. 計算条件の決定
 計算領域の大きさLx,分割数(離散点の個数)Nx,離散点の間隔x
 計算時間間隔t
2. 初期値の決定
 uの初期分布の決定
3. 差分値の計算
 uの分布からx方向の1階微分値を計算
 境界条件に基づいて境界の値を決定
 t秒後のuを計算
5. 3.にもどり,t秒後のuを基に1階微分の計算と積分を,所定
の時間まで繰り返す
GPGPU講習会69 2015/11/25
移流方程式の計算
2015/11/25GPGPU講習会70
import numpy as np
import time
def main():
Lx = 2. #計算領域の長さ
Nx = 2**20 #離散点の数
dx = Lx/(Nx‐1) #離散点の間隔
C  = 1. #移流速度
dt = 0.01*dx/C #時間積分の間隔
Lt = 2. #計算終了時間
Nt = int(Lt/dt) #積分回数
t = 0. #初期値の設定
x = np.linspace(0, Lx, Nx).astype(np.float32) #
uNew = ( 0.5*(1‐np.cos(2.*np.pi*(x‐C*t)/Lx)) )**3 #
uOld = np.empty_like(uNew)
X = np.linspace(0,Lx, (Nx‐1)*10+1) #解析解の設定
u_analytical = ( 0.5*(1‐np.cos(2.*np.pi*(X‐C*t)/Lx)) )**3 #
start_s = time.time()
for n in range(0,Nt):
uOld = uNew.copy()
uNew = uOld ‐ C*dt*diff(uOld,dx) #移流方程式を時間積分
end_s = time.time()
print('processing time',(end_s‐start_s)*1e+3/Nt,'msec/step')
t = (Nt+1)*dt
u_analytical = ( 0.5*(1‐np.cos(2.*np.pi*(X‐C*t)/Lx)) )**3
3
2
cos1
2
1

























xL
x
u

3
exact
)(2
cos1
2
1























 

xL
tCx
u

convection.py
移流方程式の計算
2015/11/25GPGPU講習会71
def diff(u, dx):
Nx = u.size
d_u_dx = np.zeros(Nx)
d_u_dx[0]    = (u[1] ‐ u[‐2] )/(2.*dx) #周期境界条件を採用
d_u_dx[1:‐1] = (u[2:]‐ u[:‐2])/(2.*dx)
d_u_dx[‐1]   = (u[1] ‐ u[‐2] )/(2.*dx) #周期境界条件を採用
return d_u_dx
if __name__ == '__main__':
main()
convection.py
実行結果
2015/11/25GPGPU講習会72
t=0 s
t=1 s
t=2 s
PyCUDA
PyCUDA
2015/11/25GPGPU講習会74
 PythonからCUDAを利用するためのモジュール群
 CUDAのPythonバインディングと表現される
 現在の所,PyCUDAが最も利用しやすい
 CUDA Cのカーネルの取込みが可能
 CUDAのDriver APIを全て利用できる
 自動でエラーチェックを行い,Pythonのエラーとして表示
 C++で書かれており,高速に動作
 cuBLASなどのライブラリを直接呼ぶ事は不可能
 BLAS‐1については配列に対する処理として再実装されている
PyCUDAのデモ
2015/11/25GPGPU講習会75
 PyCUDAのチュートリアルにあるdemo.py
 4×4の配列に乱数を代入し,それをGPUで2倍して返却
import pycuda.gpuarray as gpuarray
import pycuda.driver as cuda
import pycuda.autoinit
import numpy
a_gpu = gpuarray.to_gpu(numpy.random.randn(4,4).astype(numpy.float32))
a_doubled = (2*a_gpu).get()
print(a_doubled)
print(a_gpu)
[[ 0.51360393  1.40589952  2.25009012  3.02563429]
[‐0.75841576 ‐1.18757617  2.72269917  3.12156057]
[ 0.28826082 ‐2.92448163  1.21624792  2.86353827]
[ 1.57651746  0.63500965  2.21570683 ‐0.44537592]]
[[ 0.25680196  0.70294976  1.12504506  1.51281714]
[‐0.37920788 ‐0.59378809  1.36134958  1.56078029]
[ 0.14413041 ‐1.46224082  0.60812396  1.43176913]
[ 0.78825873  0.31750482  1.10785341 ‐0.22268796]]
PyCUDA/demo.py
CUDA Cで書くと
2015/11/25GPGPU講習会76
#include<stdio.h>
#include<stdlib.h>
#define nbytes (4*4*sizeof(float))
__global__ void doublify(float *a){
int i = blockIdx.x*blockDim.x + threadIdx.x;
a[i] *= 2.0f;
}
int main(void){
float *a = (float *)malloc(nbytes);
float *a_gpu;
cudaMalloc((void **)&a_gpu , nbytes);
for(int i=0;i<4*4;i++)
a[i] = (float)rand()/RAND_MAX;
cudaMemcpy(a_gpu, a, nbytes, cudaMemcpyHostToDevice);
doublify<<<4,4>>>(a_gpu);
float *a_doubled = (float *)malloc(nbytes);
cudaMemcpy(a_doubled, a_gpu, nbytes, cudaMemcpyDeviceToHost);
return 0;
}
PyCUDAのデモ
2015/11/25GPGPU講習会77
 これがPyCUDAですと言われても・・・
 Pythonの知識との対応
 CUDA Cの知識との対応
 段階を踏んでPyCUDAの使い方を確認
1. PyCUDAからGPU情報を取得
2. mathモジュールやnumpyモジュールの関数をCUDAに置換
1. cumathモジュール
2. Elementise Operation
3. CUDA Cのkernelの取込
4. Pythonスクリプト内のパラメータをCUDA kernelに反映
5. 少し進んだ使い方
が不可欠
GPU情報の取得
2015/11/25GPGPU講習会78
 処理系がGPUの情報にアクセスできているかの確認
 pgaccelinfoやdeviceQueryに相当
 複数GPUを利用する場合やGPUの世代に応じてパラメータを
変更する場合にも必要
 PyCUDAのモジュールのimport
 import pycuda.autoinit
 初期化や解放を自動で行う場合に利用
 特に理由がない限り利用した方がよい(Pythonらしさを維持)
 import pycuda.driver as cuda
 CUDAのAPIを利用
GPU情報の取得
2015/11/25GPGPU講習会79
 実行結果
1 device(s) found.
Deivce : Quadro 2000M
Compute Capalibity : 2.1
Total Memory Size : 2048 MB
import pycuda.autoinit
import pycuda.driver as cuda
print("%d device(s) found." % cuda.Device.count())
for id in range(cuda.Device.count()):
dev = cuda.Device(id)
print("Deivce : %s" % dev.name())
print("¥t Compute Capalibity : %d.%d" % dev.compute_capability())
print("¥t Total Memory Size : %s MB" % (dev.total_memory()//(2**20)))
PyCUDA/device.py
GPU情報の取得
2015/11/25GPGPU講習会80
 PyCUDAが取得できる全情報の表示
 属性(attribute)を取得し,その名称と対応する値を表示
 Pythonのfor文のよい練習
import pycuda.autoinit
import pycuda.driver as cuda
print("%d device(s) found." % cuda.Device.count())
for id in range(cuda.Device.count()):
dev = cuda.Device(id)
print("Deivce : %s" % dev.name())
print("¥t Compute Capalibity : %d.%d" % dev.compute_capability())
print("¥t Total Memory Size : %s MB" % (dev.total_memory()//(2**20)))
attrs = dev.get_attributes()
for key, value in attrs.items():
print("¥t %s : %s" % (str(key), str(value)) )
PyCUDA/devicequery.py
GPU情報の取得
2015/11/25GPGPU講習会81
 実行結果
1 device(s) found.
Deivce : Quadro 2000M
Compute Capalibity : 2.1
Total Memory Size : 2048 MB
MAX_THREADS_PER_BLOCK : 1024
MAX_BLOCK_DIM_X : 1024
MAX_BLOCK_DIM_Y : 1024
MAX_BLOCK_DIM_Z : 64
MAX_GRID_DIM_X : 65535
MAX_GRID_DIM_Y : 65535
MAX_GRID_DIM_Z : 65535
:(中略)
STREAM_PRIORITIES_SUPPORTED : 0
GLOBAL_L1_CACHE_SUPPORTED : 1
LOCAL_L1_CACHE_SUPPORTED : 1
MAX_SHARED_MEMORY_PER_MULTIPROCESSOR : 49152
MAX_REGISTERS_PER_MULTIPROCESSOR : 32768
MANAGED_MEMORY : 0
MULTI_GPU_BOARD : 0
MULTI_GPU_BOARD_GROUP_ID : 0
NumPyの置換(cumathモジュール)
2015/11/25GPGPU講習会82
 numpyの数学関数
 sin,cosなどmathモジュールと同じ関数を提供
 配列を引数に渡すと,全ての配列要素に対して関数を適用し,
結果を配列で返す
 配列を宣言→全要素に値を設定→全要素に同じ処理を実行
 GPU向きの処理
import numpy as np
N  = 2**20
Lx = 2*np.pi
x = np.linspace(0, Lx, N).astype(np.float32) #C言語のfloat型として宣言
y = np.sin(x)
NumPyの置換(cumathモジュール)
2015/11/25GPGPU講習会83
 配列の型指定
 numpyのarrayはC言語のdouble型
 C言語のfloat型を利用する場合はastype(型)で型を指定
 float型 np.float32
 double型 np.float64
 型は配列変数名.dtypeで確認
>>> import numpy as np
>>> x = np.linspace(0, 1, 101) #何も指定しないとC言語のdouble型に相当
>>> x.dtype
dtype('float64')
>>> x=np.linspace(0,1,101).astype(np.float32) #C言語のfloat型を指定
>>> x.dtype
dtype('float32')
>>> x = x.astype(np.float32)
>>> x.dtype
dtype('float32')
GPUで処理を実行する流れ
2015/11/25GPGPU講習会84
 GPUのメモリ上に配列を確保
 CPUのデータをGPUへ転送
 GPU上で関数を呼び出して処理を実行
 GPUから結果を読み出す
cumathモジュールを利用したy=sin(x)
2015/11/25GPGPU講習会85
 GPU上に確保する配列はpycuda.gpuarrayを利用
 GPUの初期化やメモリの確保を隠蔽
 CPU‐GPU間通信はpycuda.gpuarrayの機能を利用
 数学関数をcumath.sinに置換
import numpy as np
import pycuda.gpuarray as gpuarray
import pycuda.cumath as cumath
import pycuda.autoinit
N  = 2**20
Lx = 2*np.pi
x = np.linspace(0, Lx, N).astype(np.float32)
dev_x = gpuarray.to_gpu(x)
dev_y = cumath.sin(dev_x)
y = dev_y.get()
PyCUDA/cumath.py
cumathモジュールを利用したy=sin(x)
2015/11/25GPGPU講習会86
 GPU上で変数を確保+CPU→GPUのコピー
 gpuarray変数=gpuarray.to_gpu(array変数)
 GPU→CPUのコピー
 array変数=gpuarray変数.get()
import numpy as np
import pycuda.gpuarray as gpuarray
import pycuda.cumath as cumath
import pycuda.autoinit
N  = 2**20
Lx = 2*np.pi
x = np.linspace(0, Lx, N).astype(np.float32)
dev_x = gpuarray.to_gpu(x) #デバイス変数dev_xを動的に確保しつつxをコピー
dev_y = cumath.sin(dev_x) #cumathモジュールでdev_xの要素ごとにsin()を計算
y = dev_y.get() #デバイス変数dev_yの内容をyにコピー
PythonからCUDAを利用
2015/11/25GPGPU講習会87
 GPUのメモリ上に配列を確保
 numpy.arrayの代わりにpycuda.gpuarrayを利用
 CPUのデータをGPUへ転送
 pycudaの機能を利用(CUDA CのAPIを隠蔽)
 GPU上で関数を呼び出して処理を実行
 cumathモジュールを利用
 GPUから結果を読み出す
 pycudaの機能を利用(CUDA CのAPIを隠蔽)
実行したい処理がcumathモジュール
に無い場合はどうする?
Elementwise Operation
2015/11/25GPGPU講習会88
 配列の全要素に対して同じ処理を行う
 ‐wise 名詞や副詞の後ろに付けて,方法や方向を表す
 clockwise 時計回り(右回り)
 lengthwise 縦方向
 Elementwise Operationの利用
 ElementwiseKernelをimport
 1要素に対する処理と引数を記述したElementwisekernel
のオブジェクトを定義
 通常の関数のように関数名+実引数(gpuarray)を指定して
実行
Elementwise Operationを利用したy=sin(x)
2015/11/25GPGPU講習会89
import numpy as np
import pycuda.gpuarray as gpuarray
from pycuda.elementwise import ElementwiseKernel #pycuda.cumathから置き換え
import pycuda.autoinit
N  = 2**20
Lx = 2*np.pi
sin_kernel = ElementwiseKernel(
"float *y, float *x", #引数
"y[i] = sin(x[i])", #1要素に対する処理(引数の全要素に適用)
"elementwise_sin") #名前
x = np.linspace(0, Lx, N).astype(np.float32)
dev_x = gpuarray.to_gpu(x)
dev_y = gpuarray.empty_like(dev_x)
sin_kernel(dev_y, dev_x)
y = dev_y.get()
PyCUDA/elementwise1.py
2変数以上のElementwise Operation
2015/11/25GPGPU講習会90
import numpy as np
import pycuda.gpuarray as gpuarray
from pycuda.elementwise import ElementwiseKernel #pycuda.cumathから置き換え
import pycuda.autoinit
N  = 2**20
x = np.ones(N).astype(np.float32)
y = np.zeros_like(x)
z = np.zeros_like(x)
y[:]=2.0
saxpby_kernel = ElementwiseKernel(
"float a, float *x, flato b, float *y, float *z", #引数
"z[i] = a*x[i]+b*y[i]", #1要素に対する処理(引数の全要素に適用)
"linear_combination") #名前
dev_x = gpuarray.to_gpu(x)
dev_y = gpuarray.to_gpu(y)
saxpby_kernel(1.0, dev_x, 2.0, dev_y, dev_z)
z = dev_z.get()
PyCUDA/elementwise2.py
PythonからCUDAを利用
2015/11/25GPGPU講習会91
 GPUのメモリ上に配列を確保
 numpy.arrayの代わりにpycuda.gpuarrayを利用
 CPUのデータをGPUへ転送
 pycudaの機能を利用(CUDA CのAPIを隠蔽)
 GPU上で関数を呼び出して処理を実行
 cumathモジュールを利用
 ElementwiseKernelの作成
 GPUから結果を読み出す
 pycudaの機能を利用(CUDA CのAPIを隠蔽)
もっと複雑な処理を実行したり,
既存のCUDAのプログラムを再利
用したいときはどうする?
CUDA Cプログラムの取り込み
2015/11/25GPGPU講習会92
 SourceModule
 ElementwiseKernelには1要素に対する処理を記述
 SourceModuleにはCUDA Cのカーネルそのものを記述
 SourceModuleの利用
 SourceModuleをimport
 CUDA Cカーネルを複数行コメントで記述したSourceModule
のオブジェクトを生成
 SourceModuleオブジェクトから利用するカーネルを選択
 CUDA Cと同様に,カーネル名,実引数,実行時の並列度を指
定して実行
SourceModuleを利用したy=sin(x)
2015/11/25GPGPU講習会93
import numpy as np
import pycuda.gpuarray as gpuarray
from pycuda.compiler import SourceModule #ElementwiseKernelから置き換え
import pycuda.autoinit
N  = 2**20
Lx = 2*np.pi
module = SourceModule("""
__global__ void sin_kernel(float *y, float *x)
{
int i = blockIdx.x*blockDim.x + threadIdx.x;
y[i] = sin(x[i]);
}
""")
sin = module.get_function("sin_kernel") #実行するカーネルを決定
block = (256, 1, 1) #並列実行時のパラメータを設定
grid  = (N//block[0], 1, 1) #単純な除算を行うとパラメータがfloatになり,エラーが発生
x = np.linspace(0, Lx, N).astype(np.float32)
dev_x = gpuarray.to_gpu(x)
dev_y = gpuarray.empty_like(dev_x)
sin(dev_y, dev_x, grid = grid, block = block)
y = dev_y.get()
PyCUDA/sourcemdule.py
PythonからCUDAを利用
2015/11/25GPGPU講習会94
 GPUのメモリ上に配列を確保
 numpy.arrayの代わりにpycuda.gpuarrayを利用
 CPUのデータをGPUへ転送
 pycudaの機能を利用(CUDA CのAPIを隠蔽)
 GPU上で関数を呼び出して処理を実行
 cumathモジュールを利用
 ElementwiseKernelの作成
 SourceModuleを利用してCUDA Cのカーネルを取込
 GPUから結果を読み出す
 pycudaの機能を利用(CUDA CのAPIを隠蔽)
CUDA CのAPIを明
示的に利用したい
CUDA CのAPIを明
示的に利用したい
CUDA CのAPIを明
示的に利用したい
CUDA APIの利用
2015/11/25GPGPU講習会95
import numpy as np
import pycuda.gpuarray as gpuarray
from pycuda.compiler import SourceModule
import pycuda.autoinit
import pycuda.driver as cuda
:
: (ここは同じなので省略)
:
x = np.linspace(0, Lx, N).astype(np.float32)
dev_x = cuda.mem_alloc(x.nbytes) #CUDAのAPIを利用してメモリ確保
dev_y = cuda.mem_alloc(x.nbytes) #確保された変数dev_x,dev_yはgpuarrayではない
cuda.memcpy_htod(dev_x, x) #CUDAのAPIを利用してメモリ転送(host‐>device)
sin(dev_y, dev_x, grid = grid, block = block)
y = np.empty_like(x)
cuda.memcpy_dtoh(y, dev_y) #CUDAのAPIを利用してメモリ転送(host‐>device)
print(y)
PyCUDA/sourcemdule_api.py
PythonからCUDAを利用
2015/11/25GPGPU講習会96
 GPUのメモリ上に配列を確保
 numpy.arrayの代わりにpycuda.gpuarrayを利用
 CUDA APIでメモリ確保(gpuarrayではないので制限がある)
 CPUのデータをGPUへ転送
 pycudaの機能を利用
 CUDA APIを利用してコピー
 GPU上で関数を呼び出して処理を実行
 cumathモジュールを利用
 ElementwiseKernelの作成
 SourceModuleを利用してCUDA Cのカーネルを取込
 GPUから結果を読み出す
 pycudaの機能を利用
 CUDA APIを利用してコピー
CUDA Cプログラムの取り込み
2015/11/25GPGPU講習会97
 SourceModuleを利用してCUDA Cカーネルを取り込む
 Hello Threads
 GPUを使って並列実行できているかを確認
 GPGPU関連講義や講習会でもこれまで取り扱ってきた内容
GPU
Streaming 
Multiprocessor
CUDA 
Core
ハードウェア構成
並列に実行する
処理
スレッドの集
まり
スレッド
並列化の階層
Grid
Block
Thread
CUDA
GPUの並列化の階層
 グリッド-ブロック-スレッドの3階層
 各階層の情報を参照できる変数
 x,y,zをメンバにもつdim3型構造体
 グリッド(Grid)
 gridDim グリッド内にあるブロックの数
 ブロック(Block)
 blockIdx ブロックに割り当てられた番号
 blockDim ブロック内にあるスレッドの数
 スレッド(Thread)
 threadIdx スレッドに割り当てられた番号
2015/11/25GPGPU講習会98
CUDA Cカーネルの取り込み
2015/11/25GPGPU講習会99
 Hello Threads
from pycuda.compiler import SourceModule
import pycuda.autoinit
module = SourceModule("""
#include<stdio.h> //#includeも利用可能
__global__ void hello_thread()
{
printf("hello thread¥¥n"); //改行は¥¥n
}
__global__ void hello_threads()
{
printf("gridDim.x=%d,blockIdx.x=%d,blockDim.x=%d,threadIdx.x=%d¥¥n",
gridDim.x, blockIdx.x, blockDim.x, threadIdx.x);
}
""")
hello = module.get_function("hello_threads")
block = (4, 1, 1)
grid  = (2, 1, 1)
hello(grid = grid, block = block)
PyCUDA/HelloThreads.py
CUDA Cカーネルの取り込み
2015/11/25GPGPU講習会100
 実行結果
gridDim.x=2,blockIdx.x=0,blockDim.x=4,threadIdx.x=0
gridDim.x=2,blockIdx.x=0,blockDim.x=4,threadIdx.x=1
gridDim.x=2,blockIdx.x=0,blockDim.x=4,threadIdx.x=2
gridDim.x=2,blockIdx.x=0,blockDim.x=4,threadIdx.x=3
gridDim.x=2,blockIdx.x=1,blockDim.x=4,threadIdx.x=0
gridDim.x=2,blockIdx.x=1,blockDim.x=4,threadIdx.x=1
gridDim.x=2,blockIdx.x=1,blockDim.x=4,threadIdx.x=2
gridDim.x=2,blockIdx.x=1,blockDim.x=4,threadIdx.x=3
CUDA Cカーネルの取り込み
2015/11/25GPGPU講習会101
 #includeが利用可能
 別ファイルに記述したカーネルを取り込むことができる(はず)
 既存のCUDAのカーネルが書かれたソースの取込
 標準のincludeディレクトリ以外にあるファイルをinclude
 #include"ファイル名"で指定
 SourceModuleの引数include_dirsで場所を指定
 ディレクトリパスが長いとエラーがでることがある
 includeするファイルの名前がkernel.cuだとエラーが発生
 PyCUDAは一時的にkernel.cuというファイルを作るらしい
CUDA Cカーネルの取り込み
2015/11/25GPGPU講習会102
from pycuda.compiler import SourceModule
import pycuda.autoinit
module = SourceModule("""
#include "hello_kernel.cu" //kernel.cuという名前は利用不可
""",include_dirs=['C:¥Python34¥PyCUDA'])#hello_kernel.cuの場所を指定
hello = module.get_function("hello_threads") #("hello_thread")
block = (4, 1, 1)
grid  = (2, 1, 1)
hello(block = block, grid = grid)
#include<stdio.h>
__global__ void hello_thread()
{
printf("hello thread¥n");
}
__global__ void hello_threads()
{
printf("gridDim.x=%d,blockIdx.x=%d,blockDim.x=%d,threadIdx.x=%d¥n",
gridDim.x, blockIdx.x, blockDim.x, threadIdx.x);
}
PyCUDA/HelloThreads_incl.py
PyCUDA/hello_kernel.cu
CUDA Cプログラムの取り込み
2015/11/25GPGPU講習会103
 ベクトル和C=A+B
 配列A, B, Cで参照する配列要素番号iが同じ
 各スレッドがある配列添字iを処理
・・・
・・・
・・・c[i]
a[i]
b[i]
+ + + + + +
スレッド0 スレッド2スレッド1 スレッド3 ・・・
CUDA Cプログラムの取り込み
2015/11/25GPGPU講習会104
 VectorAddimport numpy as np
import pycuda.gpuarray as gpuarray
import pycuda.autoinit
from pycuda.compiler import SourceModule
#main関数の外で定義
module = SourceModule("""
__global__ void init(float *a, float *b, float *c){
int i = blockIdx.x*blockDim.x + threadIdx.x;
a[i] = 1.f;
b[i] = 2.f;
c[i] = 0.f;
}
__global__ void add(float *a, float *b, float *c){
int i = blockIdx.x*blockDim.x + threadIdx.x;
c[i] = a[i] + b[i];
}
""")
PyCUDA/vectoradd.py
CUDA Cプログラムの取り込み
2015/11/25GPGPU講習会105
 VectorAdddef main():
N  = 2**20
Nt = 2**8
Nb = N//Nt
c = np.zeros(N).astype(np.float32)  #配列Cをfloat型で宣言し,0で初期化
dev_a = gpuarray.to_gpu(c) #cを基にGPU上に配列dev_aを宣言し,内容を転送
dev_b = gpuarray.empty_like(dev_a) #dev_aと同じ型,サイズの配列を宣言
dev_c = gpuarray.empty_like(dev_a) #dev_aと同じ型,サイズの配列を宣言
global module #関数外で定義された変数(オブジェクト)を利用するためにglobal宣言を行う
init = module.get_function("init") #実行するカーネルを決定
add  = module.get_function("add") #実行するカーネルを決定
init(dev_a, dev_b, dev_c, grid=(Nb,1), block=(Nt,1,1))
add(dev_a, dev_b, dev_c, grid=(Nb,1), block=(Nt,1,1))
c=dev_c.get()
print(c)
if __name__ == "__main__":
main() PyCUDA/vectoradd.py
実行時間の測定
2015/11/25GPGPU講習会106
 関数の実行時間
 timeモジュールのtime関数を利用
 epochから関数呼び出し時までの経過時間を秒で返す
 epochとは
 時間計測の基準となる時刻
 多くの場合,1970年1月1日午前0時0分0秒
import time
start_s = time.time()
: 関数呼び出しや他の処理を実行
end_s = time.time()
print(end_s‐start_s,"sec")
実行時間の測定
2015/11/25GPGPU講習会107
 カーネルの実行時間
 CUDA APIのEvent(cudaEvent)を利用
 イベントを生成
 プログラムがそのイベントを通過した時間を記録
 二つのイベントが記録された時間の差から実行時間(ミリ秒)を測定
import pycuda.driver as cuda
start = cuda.Event()
end   = cuda.Event()
start.record()
start.synchronize()
: カーネル呼出
end.record()
end.synchronize()
print(start.time_till(end),"msec")
CPUとGPUの実行時間の比較
2015/11/25GPGPU講習会108
import numpy as np
import pycuda.gpuarray as gpuarray
import pycuda.autoinit
from pycuda.compiler import SourceModule
import pycuda.driver as cuda #cudaEventを利用
import time #time()を利用
module = SourceModule("""
__global__ void init(float *a, float *b, float *c){
int i = blockIdx.x*blockDim.x + threadIdx.x;
a[i] = 1.f;
b[i] = 2.f;
c[i] = 0.f;
}
__global__ void add(float *a, float *b, float *c){
int i = blockIdx.x*blockDim.x + threadIdx.x;
c[i] = a[i] + b[i];
}
""")
PyCUDA/vectoradd_time.py
CPUとGPUの実行時間の比較
2015/11/25GPGPU講習会109
def init(a, b, c,N):
for i in range(N):
a[i] = 1.
b[i] = 2.
c[i] = 0.
def add(a, b, c,N):
for i in range(N):
c[i] = a[i] + b[i]
def main():
N  = 2**20
Nt = 2**8
Nb = N//Nt
a = np.zeros(N).astype(np.float32)
b = np.zeros(N).astype(np.float32)
c = np.zeros(N).astype(np.float32)
init(a,b,c,N)
start_s = time.time()
add(a,b,c,N)
end_s = time.time()
print((end_s‐start_s)*1e+3,"msec") #395 msec
PyCUDA/vectoradd_time.py
CPUとGPUの実行時間の比較
2015/11/25GPGPU講習会110
dev_a = gpuarray.to_gpu(c)
dev_b = gpuarray.empty_like(dev_a)
dev_c = gpuarray.empty_like(dev_a)
global module
gpuinit = module.get_function("init")
gpuadd = module.get_function("add")
gpuinit(dev_a, dev_b, dev_c,grid=(Nb,1),block=(Nt,1,1))
start = cuda.Event()
end   = cuda.Event()
start.record()
start.synchronize()
gpuadd(dev_a, dev_b, dev_c, grid=(Nb,1), block=(Nt,1,1))
end.record()
end.synchronize()
print(start.time_till(end),"msec") #0.603 msec (CPU実行は395 msec)
if __name__ == "__main__":
main() PyCUDA/vectoradd_time.py
デバイス変数の確保とメモリ転送の簡略化
2015/11/25GPGPU講習会111
 デバイス変数の確保とメモリ転送
 カーネルを1回実行する場合でも必要
 デバイス変数を複数のカーネルで利用しない場合,確保+転
送を行うのは冗長
 カーネル引数の自動転送
 pycuda.driver.In
 カーネル実行時にメモリをGPUへコピー
 pycuda.driver.Out
 カーネル終了時にメモリをGPUからコピー
 pycuda.driver.InOut
 In,Outの両方の動作
デバイス変数の確保とメモリ転送の簡略化
2015/11/25GPGPU講習会112
: (省略)
def main():
N  = 2**20
Nt = 2**8
Nb = N//Nt
a = np.zeros(N).astype(np.float32)
b = np.zeros(N).astype(np.float32)
c = np.zeros(N).astype(np.float32)
a[:] = 1.
b[:] = 2.
global module
add  = module.get_function("add")
#デバイス変数を確保せず,カーネル実行時にメモリを自動で転送
add(cuda.InOut(a), cuda.InOut(b), cuda.InOut(c), grid=(Nb,1),block=(Nt,1,1))
print(c)
if __name__ == "__main__":
main()
PyCUDA/vectoradd_inout.py
デバイス変数の確保とメモリ転送の簡略化
2015/11/25GPGPU講習会113
 メモリ転送の指定
 a,bはカーネル内で変更されない→copyinのみでよい
 cはカーネル実行時に値が不要→copyoutのみでよい
 全てInOutとした場合
 カーネルの内容に応じてInとOutを選択
add(cuda.InOut(a), cuda.InOut(b), cuda.InOut(c), grid=(Nb,1),block=(Nt,1,1))
#実行時間 875 msec
add(cuda.In(a), cuda.In(b), cuda.Out(c), grid=(Nb,1),block=(Nt,1,1))
#実行時間 487 msec
#無駄な転送(a,bのcopyout, cのcopyin)が阻止されて高速化
C++の機能(Template)の利用
2015/11/25GPGPU講習会114
 コンパイル時にコードを生成する機能
 テンプレート仮引数(パラメータ)を利用して処理を記述
 テンプレート実引数の情報からコードを生成(実体化)
 C言語の関数形式マクロの安全かつ高機能版
template<class T>
T add(T a, T b){
return a + b;
}
int main(void){
int ia=1,ib=2;
float fa=1.0f,fb=2.0f;
add<int>(ia,ib);   //Tが全てintになる
add<float>(fa,fb); //Tが全てfloatになる
return 0;
}
C++の機能(Template)の利用
2015/11/25GPGPU講習会115
module = SourceModule("""
template <class T>
__device__ T add_func(T x, T y){
return (x+y);
}
extern "C" {
__global__ void init(float *a, float * b, float *c){
int i = blockIdx.x*blockDim.x + threadIdx.x;
a[i] = 1.0f;
b[i] = 2.0f;
c[i] = 0.0f;
}
__global__ void add(float *a, float * b, float * c){
int i = blockIdx.x*blockDim.x + threadIdx.x;
c[i] = add_func<float>(a[i],b[i]);
}
}
""", no_extern_c=True)
PyCUDA/vectoradd_tmpl.py
C++の機能(Template)の利用
2015/11/25GPGPU講習会116
 C以外からC/C++の関数を利用する場合
 内部的にCのルールで関数名が表現されていることを前提
 C++では関数名の表現がCとは異なる
 extern "C"により,関数名の内部表現をCに変換
 SourceModuleはCのルールで解釈
 C++の機能を利用するため,no_extern_cをTrueに
 内部的にはC++で処理を記述
 CUDA CのカーネルはC++の機能を利用
 Pythonから呼び出すカーネルにはextern "C"を指定
Pythonスクリプト内のパラメータの反映
2015/11/25GPGPU講習会117
 CUDA C Kernel内にPythonスクリプトの数値を反映
 既存のCUDA C Kernelが#defineでパラメータを設定してい
た場合など
 メタプログラミング可能なモジュール
 Jinja
 Cheetah
 Pythonの機能のみで同様の事が可能
 画面表示の際に書式制御文字列(%s)を指定する機能を流用
 %d等が存在していると,それらにも値を指定しなければならない
>>> a = '%(dummystring)s'
>>> a % {'dummystring':1}
'1'
>>> b = 'string'
>>> a % {'dummystring':b}
'string'
Pythonスクリプト内のパラメータの反映
2015/11/25GPGPU講習会118
 ループの回数をPythonスクリプトから決定
from pycuda.compiler import SourceModule
import pycuda.autoinit
#ここではまだSourceModuleに渡さない
kernel_template=""" 
#include<stdio.h>
__global__ void hello_thread()
{
for(int i=0;i<%(ITERi)s;i++)
for(int j=0;j<%(ITERj)s;j++)
printf("hello thread¥¥n");
}
"""
kernel = kernel_template % {'ITERi':2,'ITERj':2} #%sを他の文字に置き換えた文字列を作成
module = SourceModule(kernel) #文字列を基にSourceModuleを作成
hello = module.get_function("hello_thread")
block = (2, 1, 1)
grid  = (2, 1, 1)
hello(block = block, grid = grid) PyCUDA/HellosThread_tmpl.py
移流方程式の計算
2015/11/25GPGPU講習会119
import numpy as np
import pycuda.gpuarray as gpuarray
import pycuda.autoinit
from pycuda.compiler import SourceModule
import pycuda.driver as cuda
convection_kernel_template = """
#define Nx (%(Nx)s) //Pythonスクリプト内で設定した値を反映させる
#define dx (%(dx)s) //
#define C  (%(C)s)  //
#define dt (%(dt)s) //
__global__ void convection(float *u, float *uNew){ //通常のCUDA Cカーネルを取り込む
int i = blockIdx.x*blockDim.x + threadIdx.x;
float d_u_dx;
if (i == 0){
d_u_dx = (u[i+1] ‐ u[Nx‐2])/(2.0f*dx);     //周期境界条件を設定
}
else if (0 < i && i < Nx‐1){
d_u_dx = (u[i+1] ‐ u[ i‐1])/(2.0f*dx);
}
else if (i == Nx‐1){
d_u_dx = (u[1]   ‐ u[ i‐1])/(2.0f*dx);     //周期境界条件を設定
}
uNew[i] = u[i] ‐ C*dt*d_u_dx;      //移流方程式を時間積分
}
""" PyCUDA/convection.py
移流方程式の計算
2015/11/25GPGPU講習会120
def main():
Lx = 2. #計算領域の長さ
Nx = 2**10 #離散点の数
dx = Lx/(Nx‐1) #離散点の間隔
C  = 1. #移流速度
dt = 0.01*dx/C #時間積分の間隔
Lt = 2. #計算終了時間
Nt = int(Lt/dt) #積分回数
numThreads = 128 #1ブロックあたりのスレッド数の上限
convection_kernel = convection_kernel_template % {'Nx':Nx, 'dx':dx, 'C':C, 'dt':dt }
module = SourceModule(convection_kernel)
convection = module.get_function('convection')
block = (min((numThreads,Nx)),1,1) #並列実行パラメータの設定
grid  = (Nx//block[0],1,1) #
t = 0. #初期値の設定
x = np.linspace(0, Lx, Nx).astype(np.float32) #
u = ( 0.5*(1‐np.cos(2.*np.pi*(x‐C*t)/Lx)) )**3 #
X = np.linspace(0,Lx, (Nx‐1)*10+1) #解析解の設定
u_analytical = ( 0.5*(1‐np.cos(2.*np.pi*(X‐C*t)/Lx)) )**3
3
2
cos1
2
1

























xL
x
u

3
exact
)(2
cos1
2
1























 

xL
tCx
u

PyCUDA/convection.py
移流方程式の計算
2015/11/25GPGPU講習会121
uNew = gpuarray.to_gpu(u)
uOld = gpuarray.empty_like(uNew)
start = cuda.Event()
end   = cuda.Event()
start.record()
start.synchronize()
for n in range(0,Nt):
uOld = uNew.copy()
convection(uOld, uNew, grid=grid, block=block) #移流方程式を計算
end.record()
end.synchronize()
elapsed_s = start.time_till(end)
print('processing time',elapsed_s/Nt,'msec/step')
if __name__ == '__main__':
main()
PyCUDA/convection.py
実行時間の比較
2015/11/25GPGPU講習会122
 Nx(x方向の離散点の点数=配列要素数)
 25,210,215,220
 1ステップあたりの実行時間を算出
 CPUはtime()で測定
 GPUはcudaEventで測定
 複数step時間進行させ,要した時間/実行step数
Nx CPU実行
[ms/step]
GPU実行
[ms/step]
25 0.010 0.102
210 0.024 0.119
215 0.535 0.137
220 24.9 1.82
PythonからCUDAを利用
2015/11/25GPGPU講習会123
 GPUのメモリ上に配列を確保
 numpy.arrayの代わりにpycuda.gpuarrayを利用
 CUDA APIでメモリ確保(gpuarrayではないので制限がある)
 CPUのデータをGPUへ転送
 pycudaの機能を利用
 CUDA APIを利用してコピー
 GPU上で関数を呼び出して処理を実行
 cumathモジュールを利用
 ElementwiseKernelの作成
 SourceModuleを利用してCUDA Cのカーネルを取込
 Cに加えてC++の機能も利用可能
 #include""でソースファイルを取り込む事も可能
 Pythonで設定した値をCDUA Cのカーネルに反映させることも可能
 GPUから結果を読み出す
 pycudaの機能を利用
 CUDA APIを利用してコピー

GPGPU Seminar (PyCUDA)