「WebGPU API - Web APIs | MDN」
WGSLが使える仕様、WebGPU、まだ使えるのはPC上のChrome、Edge、Operaのみですが、Denoで動きます!(参考、Deno 1.39: The Return of WebGPU)
WGSLはじめのいっぽとして、Denoのコラッツの問題を計算するサンプルプログラム「webgpu-examples/hello-compute at main · denoland/webgpu-examples」を元に簡単な足し算と、フィボナッチ数列をGPUで並列計算して、JavaScriptとの速度を比較してみました。
fn add1(n_base: u32) -> u32 { return n_base + 1u; } @group(0) @binding(0) var<storage, read_write> v_indices: array<u32>; @compute @workgroup_size(1) fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { v_indices[global_id.x] = add1(v_indices[global_id.x]); }
関数定義はfn、シンプルな後付型表記が、ZenやRustっぽいですね。WebGPUの前身、WebGLで使う言語、GLSLがC言語風だったので思い切ってイメチェンして様子。こちら、フィボナッチ数列を計算するプログラムを
fn fib(n: u32) -> u32 { if (n < 2u) { return n; } return fib(n - 1) + fib(n - 2); }
と、再帰を使って実装しようとすると・・・
fib.html:1 Tint WGSL reader failure: :18:1 error: cyclic dependency found: 'fib' -> 'fib' fn fib(n: u32) -> u32 { ^^ :22:10 note: function 'fib' references function 'fib' here return fib(n - 1) + fib(n - 2); ^^^ - While validating [ShaderModuleDescriptor] - While calling [Device].CreateShaderModule([ShaderModuleDescriptor]).
と、エラーになります。CUDA Cと同様、GPU内で再帰は使えないので、ループで書き換えます。fn fib(n: u32) -> u32 { var cur: u32 = 0u; var bk1: u32 = 0u; var bk2: u32 = 1u; for (var i: u32 = 1u; i <= n; i++) { cur = bk1 + bk2; bk2 = bk1; bk1 = cur; } return cur; }
GLSLを動かすためには、GPU向けにコンパイルしたり、メモリをGPUに転送して、実行させ、終了を待って、メモリをGPUから受け取る処理を加えて実行します。まるでマイコン開発!(参考、WebGPU.js on GitHub)
今回のようなシンプルな並列計算のための諸々の処理をWebGPUクラスのcreateCalcIntToIntとしてまとめています。参考資料と合わせて辿って改造してみましょう。WebGPUを使って、ライフゲームを実装するまでのチュートリアルが良さそうでした。(参考、初めての WebGPU アプリ | Google Codelabs)
「フィボナッチ by WGSL」 src on GitHub
タイムを計測してみたところ、65535コの100のフィボナッチ数を求める並列計算。WebGPUを使った計算速度は、10ミリ秒。CPUで直列計算した結果、11ミリ秒とほぼ互角。
「deno run --unstable fib_bench.js」
この程度の計算では差はでないようなので、フィボナッチ計算処理を無駄に1000回繰り返したところ、M1 Macbook Proにて、CPUでは7,218ミリ秒かかりましたが、WebGPU(Chrome)では、45ミリ秒と、150倍速!
Denoで直接使えるプログラミング言語は、JavaScript、TypeScript、WebAssembly、WGSLと4つ。フロントエンドでの互換性を考えるとGLSLですが、サーバーでもGPUを使った高速化ができるのは楽しいですね!
Let's enjoy to hack GPU!