Common Lispで
GPGPU
関東GPGPU勉強会 #4
2016-08-21
gos-k
自己紹介
●
名前 : gos-k
– gos_k : twitter, bitbucket, qiita
– gos-k : github, hatenablog
●
仕事 : 渋谷の某社でWebプログラマ
– Common LispとかReact.jsとか
– それ以前は産業用機器のソフトや3D画像処理のチューニングの仕事
●
趣味 : パンケーキ
発表の流れ
1) Common Lispに関する簡単な説明
2) Common LispにおけるGPGPU関連の現状
3) Common LispでOpenCL
4) Common Lispでカーネルコードのマクロ展開
今流行りのLisp方言
●
Clojure
– JVMで動くヤツ
●
Common Lisp
– Lisp方言を寄せ集めたヤツ
●
Scheme
– 関数型のヤツ
●
Emacs Lisp
– Emacsで動くヤツ
今回はCommon Lispを扱う
Common Lispとは
●
1980年代に数々のLispマシンやLisp方言が生まれた
●
コミュニティ分裂の懸念を解消すべく、Common Lispとし
て標準化を行う
– 既存のLispから最良の機能を集結
– ANSIから1996年に標準規格リリース
●
制御構造、再帰関数呼び出し、ガベージコレクション、インク
リメンタルコンパイル、オブジェクトシステム、コンディション
システム等々を持つ
参考文献 : Peter, 実践 Common Lisp, 2008
関連用語
●
SBCL (Steel Bank Common Lisp)
– Common Lispの処理系
– 色々あるけど非商用の処理系ならとりあえずコレ
●
Quicklisp
– Common Lispのパッケージ管理ツール
– pipとかgemとかnpmとかの仲間?
●
Roswell
– 処理系管理ツール?
– デフォルトでSBCLとQuicklispが入って、直ぐに使い始められる
発表の流れ
1) Common Lispに関する簡単な説明
2) Common LispにおけるGPGPU関連の現状
3) Common LispでOpenCL
4) Common Lispでカーネルコードのマクロ展開
GPGPU対応状況
●
CUDA
– takagi/cl-cuda (github)
●
OpenCL API
– 3b/cl-opencl-3b (github)
– guicho271828/eazy-opencl (github)
– gos-k/cl-oclapi (github)
●
OpenCL C
– gos-k/oclcl (github)
CUDAもOpenCLも、ホストもデバイスも、
Common Lispで書ける
発表の流れ
1) Common Lispに関する簡単な説明
2) Common LispにおけるGPGPU関連の現状
3) Common LispでOpenCL
4) Common Lispでカーネルコードのマクロ展開
cl-oclapi
●
3bさんのOpenCL APIは動くけど
– Quicklisp対応してない
– 何年もメンテされてない?
●
OpenCL 1.2 APIのラッパーをスクラッチから作った
– 数年だれもやってなかったネタなのに、guichoさんと数週間差でネタか
ぶり
– しかし、その他色々なライブラリの都合により、cl-oclapiがQuicklispの
GPGPU関連で一番最初に登録される
oclcl
●
cl-cudaからフォークして、カーネル部分取り出し
– S式からCを生成
– マクロのサポート
– シンボルマクロのサポート
●
変更点
– キーワードの置換や組込み関数の拡充
●
__global__を__kernelにしたり__synchronizeをbarrierにしたり
– スカラ型ベクタ型のサポート拡大
– テストコードのOpenCL APIライブラリをeazy-openclとcl-oclapiに
– Quicklisp登録済み
発表の流れ
1) Common Lispに関する簡単な説明
2) Common LispにおけるGPGPU関連の現状
3) Common LispでOpenCL
4) Common Lispでカーネルコードのマクロ展開
Common Lispで?
●
Cでカーネル書きたくない
– 不便だから
– カーネルの言語は規格上はCのみ
– その他言語はAPI対応ばかりで、カーネルまで書けるのはあまりない
●
自分で作れるか
– Common Lispなら既にcl-cudaがある
●
これをベースにすればOpenCLも出来るのでは
– マクロ使えばカーネル最適化に便利そう
Common Lispで?
●
マクロで変数が使える
●
マクロで関数が使える
●
マクロで条件分岐が使える
●
マクロでループが使える
●
マクロでプリント文が使える
●
マクロを一段階展開したものが得られる
マクロで普通にプログラミング出来る
例 : unroll
(defkernelmacro unroll ((n offset) &body body)
`(progn
,@(loop for i below n
collect `(symbol-macrolet ((,offset ,i))
,@body))))
(defkernel test-unroll (void ((input-buffer float*)
(output-buffer float*)))
(let ((offset (* 4 (to-int (get-global-id 0)))))
(unroll (4 i)
(set (aref output-buffer (+ offset i))
(aref input-buffer (+ offset i))))))
int offset = (4 * (int)(get_global_id(0)));
output_buffer[(offset + 0)] = input_buffer[(offset + 0)];
output_buffer[(offset + 1)] = input_buffer[(offset + 1)];
output_buffer[(offset + 2)] = input_buffer[(offset + 2)];
output_buffer[(offset + 3)] = input_buffer[(offset + 3)];
例 : unroll-let
(defkernelmacro unroll-let ((n offset vars) &body body)
(let ((unrolled-vars (gensym))
(collected-vars (gensym)))
(setf unrolled-vars (loop for (name init) in vars
collect (loop for i below n
collect (list (gensym (symbol-name name))
init)))
collected-vars (loop for i below n
collect (mapcar #'list
(mapcar #'car vars)
(mapcar #'(lambda (x) (car (nth i x)))
unrolled-vars))))
(append (list 'let
(reduce #'append unrolled-vars))
(loop for expr in body
append (loop for i below n
collect `(symbol-macrolet ((,offset ,i)
,@(nth i collected-vars))
,expr))))))
中間変数定義もアンロール
例 : unroll-let
(defkernel test-unroll-let (void ((input0-buffer float*)
(input1-buffer float*)
(output-buffer float*)))
(let ((offset (* 4 (to-int (get-global-id 0)))))
(unroll-let (4 i ((alfa 0.0) (bravo 0.0)))
(set alfa (aref input0-buffer (+ offset i)))
(set bravo (aref input1-buffer (+ offset i)))
(set (aref output-buffer (+ offset i)) (+ alfa bravo)))))
例 : unroll-let
int offset = (4 * (int)(get_global_id(0)));
{
float alfa898 = 0.0f;
float alfa899 = 0.0f;
float alfa900 = 0.0f;
float alfa901 = 0.0f;
float bravo902 = 0.0f;
float bravo903 = 0.0f;
float bravo904 = 0.0f;
float bravo905 = 0.0f;
alfa898 = input0_buffer[(offset + 0)];
alfa899 = input0_buffer[(offset + 1)];
alfa900 = input0_buffer[(offset + 2)];
alfa901 = input0_buffer[(offset + 3)];
bravo902 = input1_buffer[(offset + 0)];
bravo903 = input1_buffer[(offset + 1)];
bravo904 = input1_buffer[(offset + 2)];
bravo905 = input1_buffer[(offset + 3)];
output_buffer[(offset + 0)] = (alfa898 + bravo902);
output_buffer[(offset + 1)] = (alfa899 + bravo903);
output_buffer[(offset + 2)] = (alfa900 + bravo904);
output_buffer[(offset + 3)] = (alfa901 + bravo905);
}
例 : unroll-sphere
(defkernelmacro unroll-sphere ((r vx vy vz) &body body)
`(progn
,@(loop for z from (- r) to r
append (loop for y from (- r) to r
append (loop for x from (- r) to r
when (>= r (sqrt (+ (* z z)
(* y y)
(* x x))))
collect `(symbol-macrolet ((,vz ,z)
(,vy ,y)
(,vx ,x))
,@body))))))
半径rの球へのアクセスを展開
例 : unroll-sphere
(defkernel test-unroll-sphere (void ((input-buffer float*)
(output-buffer float*)))
(let ((gx (to-int (get-global-id 0)))
(gy (to-int (get-global-id 1)))
(gz (to-int (get-global-id 2)))
(sx (to-int (get-global-size 0)))
(sy (to-int (get-global-size 1)))
(total 0.0))
(unroll-sphere (2 vx vy vz)
(set total (+ total
(aref input-buffer (+ (* (+ gz vz) sy sx)
(* (+ gy vy) sx)
(+ gx vx))))))
(set (aref output-buffer (+ (* gz sy sx)
(* gy sx)
gx))
total)))
例 : unroll-let
int gx = (int)(get_global_id(0));
int gy = (int)(get_global_id(1));
int gz = (int)(get_global_id(2));
int sx = (int)(get_global_size(0));
int sy = (int)(get_global_size(1));
float total = 0.0f;
total = (total + input_buffer[(((((gz + -2) * sy) * sx) + ((gy + 0) * sx)) + (gx + 0))]);
total = (total + input_buffer[(((((gz + -1) * sy) * sx) + ((gy + -1) * sx)) + (gx + -1))]);
total = (total + input_buffer[(((((gz + -1) * sy) * sx) + ((gy + -1) * sx)) + (gx + 0))]);
… (中略) ...
total = (total + input_buffer[(((((gz + 1) * sy) * sx) + ((gy + 1) * sx)) + (gx + 0))]);
total = (total + input_buffer[(((((gz + 1) * sy) * sx) + ((gy + 1) * sx)) + (gx + 1))]);
total = (total + input_buffer[(((((gz + 2) * sy) * sx) + ((gy + 0) * sx)) + (gx + 0))]);
output_buffer[((((gz * sy) * sx) + (gy * sx)) + gx)] = total;
おわりに
●
Common LispでCUDAもOpenCLも出来る
●
カーネルのチューニングにCommon Lispの
マクロ使うと便利そう

Common LispでGPGPU

  • 1.
  • 2.
    自己紹介 ● 名前 : gos-k –gos_k : twitter, bitbucket, qiita – gos-k : github, hatenablog ● 仕事 : 渋谷の某社でWebプログラマ – Common LispとかReact.jsとか – それ以前は産業用機器のソフトや3D画像処理のチューニングの仕事 ● 趣味 : パンケーキ
  • 3.
    発表の流れ 1) Common Lispに関する簡単な説明 2)Common LispにおけるGPGPU関連の現状 3) Common LispでOpenCL 4) Common Lispでカーネルコードのマクロ展開
  • 4.
    今流行りのLisp方言 ● Clojure – JVMで動くヤツ ● Common Lisp –Lisp方言を寄せ集めたヤツ ● Scheme – 関数型のヤツ ● Emacs Lisp – Emacsで動くヤツ 今回はCommon Lispを扱う
  • 5.
    Common Lispとは ● 1980年代に数々のLispマシンやLisp方言が生まれた ● コミュニティ分裂の懸念を解消すべく、Common Lispとし て標準化を行う –既存のLispから最良の機能を集結 – ANSIから1996年に標準規格リリース ● 制御構造、再帰関数呼び出し、ガベージコレクション、インク リメンタルコンパイル、オブジェクトシステム、コンディション システム等々を持つ 参考文献 : Peter, 実践 Common Lisp, 2008
  • 6.
    関連用語 ● SBCL (Steel BankCommon Lisp) – Common Lispの処理系 – 色々あるけど非商用の処理系ならとりあえずコレ ● Quicklisp – Common Lispのパッケージ管理ツール – pipとかgemとかnpmとかの仲間? ● Roswell – 処理系管理ツール? – デフォルトでSBCLとQuicklispが入って、直ぐに使い始められる
  • 7.
    発表の流れ 1) Common Lispに関する簡単な説明 2)Common LispにおけるGPGPU関連の現状 3) Common LispでOpenCL 4) Common Lispでカーネルコードのマクロ展開
  • 8.
    GPGPU対応状況 ● CUDA – takagi/cl-cuda (github) ● OpenCLAPI – 3b/cl-opencl-3b (github) – guicho271828/eazy-opencl (github) – gos-k/cl-oclapi (github) ● OpenCL C – gos-k/oclcl (github) CUDAもOpenCLも、ホストもデバイスも、 Common Lispで書ける
  • 9.
    発表の流れ 1) Common Lispに関する簡単な説明 2)Common LispにおけるGPGPU関連の現状 3) Common LispでOpenCL 4) Common Lispでカーネルコードのマクロ展開
  • 10.
    cl-oclapi ● 3bさんのOpenCL APIは動くけど – Quicklisp対応してない –何年もメンテされてない? ● OpenCL 1.2 APIのラッパーをスクラッチから作った – 数年だれもやってなかったネタなのに、guichoさんと数週間差でネタか ぶり – しかし、その他色々なライブラリの都合により、cl-oclapiがQuicklispの GPGPU関連で一番最初に登録される
  • 11.
    oclcl ● cl-cudaからフォークして、カーネル部分取り出し – S式からCを生成 – マクロのサポート –シンボルマクロのサポート ● 変更点 – キーワードの置換や組込み関数の拡充 ● __global__を__kernelにしたり__synchronizeをbarrierにしたり – スカラ型ベクタ型のサポート拡大 – テストコードのOpenCL APIライブラリをeazy-openclとcl-oclapiに – Quicklisp登録済み
  • 12.
    発表の流れ 1) Common Lispに関する簡単な説明 2)Common LispにおけるGPGPU関連の現状 3) Common LispでOpenCL 4) Common Lispでカーネルコードのマクロ展開
  • 13.
    Common Lispで? ● Cでカーネル書きたくない – 不便だから –カーネルの言語は規格上はCのみ – その他言語はAPI対応ばかりで、カーネルまで書けるのはあまりない ● 自分で作れるか – Common Lispなら既にcl-cudaがある ● これをベースにすればOpenCLも出来るのでは – マクロ使えばカーネル最適化に便利そう
  • 14.
  • 15.
    例 : unroll (defkernelmacrounroll ((n offset) &body body) `(progn ,@(loop for i below n collect `(symbol-macrolet ((,offset ,i)) ,@body)))) (defkernel test-unroll (void ((input-buffer float*) (output-buffer float*))) (let ((offset (* 4 (to-int (get-global-id 0))))) (unroll (4 i) (set (aref output-buffer (+ offset i)) (aref input-buffer (+ offset i)))))) int offset = (4 * (int)(get_global_id(0))); output_buffer[(offset + 0)] = input_buffer[(offset + 0)]; output_buffer[(offset + 1)] = input_buffer[(offset + 1)]; output_buffer[(offset + 2)] = input_buffer[(offset + 2)]; output_buffer[(offset + 3)] = input_buffer[(offset + 3)];
  • 16.
    例 : unroll-let (defkernelmacrounroll-let ((n offset vars) &body body) (let ((unrolled-vars (gensym)) (collected-vars (gensym))) (setf unrolled-vars (loop for (name init) in vars collect (loop for i below n collect (list (gensym (symbol-name name)) init))) collected-vars (loop for i below n collect (mapcar #'list (mapcar #'car vars) (mapcar #'(lambda (x) (car (nth i x))) unrolled-vars)))) (append (list 'let (reduce #'append unrolled-vars)) (loop for expr in body append (loop for i below n collect `(symbol-macrolet ((,offset ,i) ,@(nth i collected-vars)) ,expr)))))) 中間変数定義もアンロール
  • 17.
    例 : unroll-let (defkerneltest-unroll-let (void ((input0-buffer float*) (input1-buffer float*) (output-buffer float*))) (let ((offset (* 4 (to-int (get-global-id 0))))) (unroll-let (4 i ((alfa 0.0) (bravo 0.0))) (set alfa (aref input0-buffer (+ offset i))) (set bravo (aref input1-buffer (+ offset i))) (set (aref output-buffer (+ offset i)) (+ alfa bravo)))))
  • 18.
    例 : unroll-let intoffset = (4 * (int)(get_global_id(0))); { float alfa898 = 0.0f; float alfa899 = 0.0f; float alfa900 = 0.0f; float alfa901 = 0.0f; float bravo902 = 0.0f; float bravo903 = 0.0f; float bravo904 = 0.0f; float bravo905 = 0.0f; alfa898 = input0_buffer[(offset + 0)]; alfa899 = input0_buffer[(offset + 1)]; alfa900 = input0_buffer[(offset + 2)]; alfa901 = input0_buffer[(offset + 3)]; bravo902 = input1_buffer[(offset + 0)]; bravo903 = input1_buffer[(offset + 1)]; bravo904 = input1_buffer[(offset + 2)]; bravo905 = input1_buffer[(offset + 3)]; output_buffer[(offset + 0)] = (alfa898 + bravo902); output_buffer[(offset + 1)] = (alfa899 + bravo903); output_buffer[(offset + 2)] = (alfa900 + bravo904); output_buffer[(offset + 3)] = (alfa901 + bravo905); }
  • 19.
    例 : unroll-sphere (defkernelmacrounroll-sphere ((r vx vy vz) &body body) `(progn ,@(loop for z from (- r) to r append (loop for y from (- r) to r append (loop for x from (- r) to r when (>= r (sqrt (+ (* z z) (* y y) (* x x)))) collect `(symbol-macrolet ((,vz ,z) (,vy ,y) (,vx ,x)) ,@body)))))) 半径rの球へのアクセスを展開
  • 20.
    例 : unroll-sphere (defkerneltest-unroll-sphere (void ((input-buffer float*) (output-buffer float*))) (let ((gx (to-int (get-global-id 0))) (gy (to-int (get-global-id 1))) (gz (to-int (get-global-id 2))) (sx (to-int (get-global-size 0))) (sy (to-int (get-global-size 1))) (total 0.0)) (unroll-sphere (2 vx vy vz) (set total (+ total (aref input-buffer (+ (* (+ gz vz) sy sx) (* (+ gy vy) sx) (+ gx vx)))))) (set (aref output-buffer (+ (* gz sy sx) (* gy sx) gx)) total)))
  • 21.
    例 : unroll-let intgx = (int)(get_global_id(0)); int gy = (int)(get_global_id(1)); int gz = (int)(get_global_id(2)); int sx = (int)(get_global_size(0)); int sy = (int)(get_global_size(1)); float total = 0.0f; total = (total + input_buffer[(((((gz + -2) * sy) * sx) + ((gy + 0) * sx)) + (gx + 0))]); total = (total + input_buffer[(((((gz + -1) * sy) * sx) + ((gy + -1) * sx)) + (gx + -1))]); total = (total + input_buffer[(((((gz + -1) * sy) * sx) + ((gy + -1) * sx)) + (gx + 0))]); … (中略) ... total = (total + input_buffer[(((((gz + 1) * sy) * sx) + ((gy + 1) * sx)) + (gx + 0))]); total = (total + input_buffer[(((((gz + 1) * sy) * sx) + ((gy + 1) * sx)) + (gx + 1))]); total = (total + input_buffer[(((((gz + 2) * sy) * sx) + ((gy + 0) * sx)) + (gx + 0))]); output_buffer[((((gz * sy) * sx) + (gy * sx)) + gx)] = total;
  • 22.