Haskell による並列・並行プログラミング #7 5章 Repaを用いたデータ並列プログラミング つづき
担当: lotzさん
p.96 5.4 畳み込みとシェイプ多相
畳み込み
sumAllS 中身の要素をすべて足す
ベクトルだろうが行列だろうが畳み込める(シェイプ多相)
Q. maxとかも取る関数はあるか?
foldAllS :: (Shape sh, Source r a) => (a -> a -> a) -> a -> Array r sh a -> a
foldS 最後の次元を畳み込む
foldP 並列版
computeP と同じく monadic になる
p.98 5.5 例:画像の回転
fromFunction
p.102 5.6 まとめ
関数の操作は合成できる
Pが付く操作は並列化
GHCの最適化に強く依存するので正格性注釈とかをしっかりつける
llvmオプション
6章 AccelerateによるGPUプログラミング
担当: maton
準備 CUDA インストールバトル (読まなくて良い)
カーネルバージョン 4.15.0-101-generic
GPU: GTX 1060 (Pascal)
nVidia Driver を入れる
440.82 まで対応してるみたい
NVIDIA-SMI 440.64.00 Driver Version: 440.64.00 CUDA Version: 10.2
めでたし
CUDA 8.0 ~ 10.2.89 くらいならOKっぽい
ディスクが足りない(Winとのデュアルブートで100Gしか確保してないからね…)
code:bash
du -sh .stack
35G .stack
えっ
古いGHCとか消した
sudo apt install --no-install-recommends cuda-10-2
cudnnとかは入れない(必要ない)
追加で llvm-8-dev と libnvvm3 も
p.103 6章 AccelerateによるGPUプログラミング
Accelerate を インストール
accelerate => base>=4.7 && <4.13 制約に敗北
ghcup で 8.6.5 を入れる
parconc-examples.cabal のCUDAフラグをTrueに
cabal build
code:haskell
Prelude> import Data.Array.Accelerate as A
Prelude A> import Data.Array.Accelerate.Interpreter as I
Prelude A I>
p. 104 6.1 概要
Accelrate は Haskell コードによる GPU プログラミングを実現する
acceralate-cuda が EDSL で書かれたコードを CUDA コードに変換する
GPUが利用可能でない場合は、 acceralate のインタプリタがコードを処理する
以下の2つの節は丁寧めにやる
p. 104 6.2 配列と添字
data Array sh e
sh: シェイプ
e: 要素の型
この2つはRepaと共通(表現型は持たない)
Z や tail :. head, DIM0, DIM1, DIM2もある
別名として type Scalar e = Array DIM0 e type Vector e = Array DIM1 e がある
fromList :: (Shape sh, Elt e) => sh -> [e] -> Array sh e
code:haskell
fromList (Z:.10) 1..10 :: Vector Int 無限リストを与えてもOK
code:haskell
fromList (Z:.3:.5) 1.. :: Array DIM2 Int indexArray :: Array sh e -> sh -> e
添字といえば…
(!) :: (Shape sh, Elt e) => Acc (Array sh e) -> Exp sh -> Exp e
Accelerate 計算の文脈のみで使える模様(後述)
注意
配列の配列は作れない(対応する多次元配列を作ろう)
ただし組の配列は作れる 例) Array DIM2 (Int, Int)
Repa でも作れる
p. 106 6.3 単純なAccelerate計算を実行する
ここからは Accelerate の文脈で計算を行う
run :: Arrays a => Acc a -> a
Acc a: 型aの値を返すAccelerate計算
a: Arrays 型クラスのインスタンス(配列や、配列の組など)
run にはCUDA版とインタプリタ版がある
Data.Array.Accelerate.Interpreter.run (accelerate パッケージ)
Data.Array.Accelerate.CUDA.run (accelerate-cuda パッケージ)
実例から
code:haskell
let arr = fromList (Z:.3:.5) 1.. :: Array DIM2 Int run $ A.map (+1) (use arr)
use :: Arrays arrays => arrays -> Acc arrays (arrays は型変数)
Haskellの配列をAccelerate計算に注入
code:haskell
A.map :: (Shape sh, Elt a, Elt b)
=> (Exp a -> Exp b) -- a -> b ではない。上記の例ではExp Int -> Exp Int
-> Acc (Array sh a) -- Acc 世界の配列を引数にとることに注意
-> Acc (Array sh b)
補足: Accelerate のスカラ値は Exp a という型で表現される。
なぜ数値リテラルを書いてExp Intと推論されるのか?
GHC は数値リテラルに対して暗黙にNum型クラスのfromIntegerのインスタンスを探すらしく、Num (Exp a)のためのインスタンスを提供することで推論できるらしい(演算子とかも同じ)
なんで Elt なんて型クラスがわざわざあるの?
配列のネストを防ぐため
配列はEltのインスタンスではないので、
Array DIM1 (Array DIM1 Int) とは書けない
なんで Arrays なんて型クラスがわざわざあるの?
Acc の計算対象に配列以外が混入するのを防ぐため(おそらく)
Acc a の a は必ず Arrays のインスタンスになっている
単一要素なら Scalar e で表現できる(次節参照)
また、Elt, Arrays, Shape のメソッドはパッケージに閉じていて、第三者が勝手にインスタンスを増やすことを防いでいる
> まとめ <
use で配列をGPUに飛ばし、A.なんとか でGPU内での計算を記述し、run で取り出す
頻出関数編
流す感じでやる
p. 108 6.4 スカラ配列
unit :: Elt e => Exp e -> Acc (Scalar e)
単一要素の0次元配列(スカラ配列)を作る
the :: Elt e => Acc (Scalar e) -> Exp e
スカラ配列から要素を取り出す
Q. どういう時に使うと嬉しいの?
A. Exp として取り出した時に使う (run すると Scalarになるので)
Exp はまさしく式を表現する
code:haskell
Prelude A I> the $ unit (use arr ! index1 3)
(unit
((use
!
(Z :. 3)))
!
Z
p. 109 6.5 配列に添え字でアクセスする
(!) :: (Shape ix, Elt e) => Acc (Array ix e) -> Exp ix -> Exp e
Accelerate の世界で使える添字アクセス
Exp ix 型の添字を作るには、 index1 :: Exp Int -> Exp (Z :. Int) を使う
index2 や index3 も。 あと一応 index0 もある
code:haskell
Prelude A I> let arr = fromList (Z :. 10) 1.. :: Array DIM1 Int Prelude A I> run $ unit (use arr ! index1 3)
次回 6/14 maton
p. 109 6.6 Accの中で配列を作る
配列の生成方法があるならば、リストから配列を作るよりも、Accの世界で直接作ったほうが早い
少なくとも配列のデータをGPUメモリに移動するコストは減らせる
すべて同じ値で埋める fill
fill :: (Shape sh, Elt e) => Exp sh -> Exp e -> Acc (Array sh e)
数列からの生成 enumFromN, enumFromStepN
code:haskell
enumFromN :: (Shepe sh, Elt e, IsNum e)
=> Exp sh -> Exp e -> Acc (Array sh e)
-- シェイプ 最初の要素
enumFromStepN :: (Shepe sh, Elt e, IsNum e)
=> Exp sh -> Exp e -> Exp e -> Acc (Array sh e)
-- シェイプ 最初の要素 増分
使用例
code:haskell
run $ enumFromStepN (index2 3 5) 15 (-1) :: Array DIM2 Int
関数を用いた生成 generate
code:haskell
generate :: (Shape sh, Elt a)
=> Exp ix -> (Exp ix -> Exp a) -> Acc (Array ix a)
-- シェイプ 添字から要素の値への関数
使用例
code:haskell
run $ generate (index2 3 5) (\ix -> let Z:.y:.x = unlift ix in x + y
Array (Z :. 3 :. 5) 0,1,2,3,4,1,2,3,4,5,2,3,4,5,6 unlift :: Exp (Z :. Int :. Int) -> Z :. Exp Int :. Exp Int ※定義ではない
Acc 世界のインデックスを分解して DIM2 を得る(ここでは)
これは Unlift 型クラスの DIM2 におけるインスタンス
様々な配列、要素、シェイプに対して Lift, Unlift のインスタンスがある
lift :: Z :. Exp Int :. Exp Int -> Exp (Z :. Int :. Int)
実はこれを使って index2 は作られている
p. 111 6.7 2つの配列を綴じ合わせる
2つの配列の各要素間で関数を適用し、新たな配列をつくる zipWith
code:haskell
zipWIth :: (Shape sh, Elt a, Elt b, Elt c)
=> (Exp a -> Exp b -> Exp c) -- 要素の組に適用する関数
-> Acc (Array ix a) -> Acc (Array ix b) -- 入力される2つの配列
-> Acc (Array ix c)
使用例
code:haskell
let a = enumFromN (index 2 3) 1 :: Acc (Array DIM2 Int)
let b = enumFromStepn (index 2 3) 6 (-1) :: Acc (Array DIM2 Int)
run $ A.zipWith (+) a b
使用例2
code:haskell
let a = enumFromN (index 2 3) 1 :: Acc (Array DIM2 Int)
let b = enumFromStepn (index 3 5) 10 10 :: Acc (Array DIM2 Int)
run $ A.zipWith (+) a b
-- 左上を始点とした、配列の共通部分だけが取り出されて計算される。
[ 1, 2, 3, [ 10, 20, 30, 40, 50 [ 11, 22, 33,
4, 5, 6 ] + 60, 70, 80, 90,100 = 64, 75, 86 ]
110,120,130,140,150 ]
p. 112 6.8 定数
リテラルではないInt型の値をExp Intにするには? → constant を使おう
constant :: Elt t => t -> Exp t
ここで区切るかも
実例編
TODO
p. 112 6.9 例:最短路問題
cabal install accelerate-cuda -fdebug
stack でやるには…
stack install accelerate-cuda --flag accelerate-cuda:debug
p. 116 6.10 例:マンデルブロ集合の生成器
次回
担当: maton