クーの自由研究

マスターの かえるのクーは8年の任期を終え消失しました。クーⅡ世の中の人絶賛募集中です。(特にオリオン座&プレイアデス方向の方は優遇します)助手がメイド喫茶から生還し、リハビリを兼ねて復帰しています。

CuPyでReductionKernelに入門してみる

引き続きGPUで遊んでみます

にゃんぱすー。かえるのクーです。

f:id:np2LKoo:20181126022907p:plain

GPUに入門中です。

今回はReduceしてみます

 ReduceもしくはReductionは、最初説明を読んだときは?が頭の周りを飛び回りましたが、少しだけわかってきました。ECOなのが一番です。

f:id:np2LKoo:20181126023432p:plain

たくさんあるコアで、どうすれば、合計(など)を早く計算できるか!を考えると、「なるほど」と感じられました。

でも、説明できるレベルには到底ないので、説明はいつものように諸先輩にお尋ねください。

Reduceの説明(前回貼ったのと同じです)

CUDAのアーキテクチャとも密接に関係しているのですね。

Reduceの最適化・高速化はいろいろな手法があるようですが、cupyを使うだけの場合は計算部分をいれるだけなので、意識は不要だと思います。

Reduceの高速化の説明を読めば、高速に計算するためには、カーネル内での分岐やLoopは基本的に御法度であることがよくわかります。

 CuPyのReductionKernelのマニュアル

L2距離を求める例がCuPyのスライドの13ページにありますのでそのまま実行してみます。(公式チュートリアルのものと同じです)

わからないままL2norm計算式


import cupy as cp
import numpy as np

if __name__ == '__main__':
x = cp.arange(10, dtype=np.float32).reshape(2,5)
# 以下はCuPyの説明スライドから
L2norm_kernel = cp.ReductionKernel(
'T x', # in_params:入力
'T y', # out_params:出力
' x * x', #map_expr:前処理
'a + b', #reduce_expr:リデュース
'y = sqrt(a)', #post_map_expr:後処理
'0', #identity:初期値
'l2norm') #name:名前
y = L2norm_kernel(x)
print(x)
print(y)

[[0. 1. 2. 3. 4.]
 [5. 6. 7. 8. 9.]]
16.881943

でした。今回は入力もprintしています。

これは sqrt(0*0 + 1*1 + 2*2 + 3*3 + ... 8*8 + 9*9) で答えが16.881943です。

電卓やExcelでもそうなります。

’a + b’ の部分と後処理で a がでてくる部分がどうしてもしっくりこなかったのですが、

a も b  も予約語で、自分はa + bのreduce結果は a に格納される感覚がわかるとしっくりきました。

 

行ごとの計算も指定で行えます。

y = L2norm_kernel(x, axis=1)

なら列方向のreduceを行い、それぞれの行の計算値を出してくれます。

[ 5.477226  15.9687195]

y = L2norm_kernel(x, axis=0)

なら、行方向のreduceを行ってくれます。

[5.        6.0827627 7.28011   8.5440035 9.848858 ]

例えば(ゼロからかぞえて)2列目はsqrt(2*2 + 7*7) =  7.28011 です。

y = L2norm_kernel(x, axis=1, keepdims=True)

とすれば、配列の次元をそのままにしてくれます。

[[0. 1. 2. 3. 4.]
 [5. 6. 7. 8. 9.]]
[[ 5.477226 ]
 [15.9687195]]

合計系や統計系の演算はひととおりそろっていると思うので、ReductionKernelを使うのは稀かもしれませんが、計算としてはとってもおもしろいです。

普通に合計してみます


import cupy as cp
import numpy as np

if __name__ == '__main__':
x = cp.arange(10, dtype=np.float32).reshape(2,5)
# 以下はCuPyの説明スライドから
array_sum = cp.ReductionKernel(
'T x', #入力
'T y', #出力
'x', #前処理
'a + b', #リデュース
'y = a', #後処理
'0', #初期値
'l2norm') #名前
y = array_sum(x, axis=1)
print(x)
print(y)

[[0. 1. 2. 3. 4.]
 [5. 6. 7. 8. 9.]]
[10. 35.]

ウェーブレット変換をCUDAでやる記事もあったので見てみます。

CUDAで連続ウェーブレット変換 - PukiWiki for PBCG Lab

ボクは離散ウェーブレット変換で十分なのですが(それはCUDAのSDKにあるらしいです。今度探してみます)