うごいた!!遺伝的プログラミングがWebGPUで!!!遅い!!!!CPUより8倍ぐらい遅い!!!俺たちの戦いはこれからだ!!!
{あれ K#EDD2/3ABC}
goから越境してrustやるの嫌じゃな~~~。
そもそも、wgpuのレポジトリ見てても動かし方がよくわからん。
なんとなくIssueをcompute shader
で検索して眺めてみる。すでにIssueが出てれば幸甚。
なかったわ。一般的には巨大シェーダーを動かす動機がないらしい。
wgpuのレポジトリにExampleを発見したので動かしてみる。
wgpuでboidsが動いた。やったねたえちゃん!
巨大シェーダー実証の元としてhello-computeが良さそう。コードが簡単。見覚えもある。
サクッとシェーダーを書き換えてみたら動いた。いいぞ。
{あれ K#EDD2/066F}
nodes_outputにアクセスするコードを並び立てて、switch文で添字を定数にしてしまえば、汎用的なシェーダーで遺伝的プログラミングができそう。
つまり以下のような擬似コードになる
fn nodes_output_access(i:u32, nodes_output:array<i32>) i32 {
switch(i) {
case 0 { return nodes_output[0]}
case 1 { return nodes_output[1]}
...
}
}
fn nodes_exec(node:Node, nodes_output:array<i32>) {
let i1=nodes_output_access(node.index1, nodes_output);
let i2=nodes_output_access(node.index2, nodes_output);
return node_function_exec(node.function, i1, i2)
}
fn main(){
nodes_output[0] = node_exec(nodes[0], nodes_output);
nodes_output[1] = node_exec(nodes[1], nodes_output);
...
}
{あれ K#EDD2/F926}
WGSLのコード内の条件分岐の数が2倍になると、compute pipelineの作成にかかる時間が10倍になった現象は、Macにおいてのみ低速な可能性がある。
MTLCompilerServiceがCPU100%に張り付いている。
WGSL → SRIP−V → Metal Shading Languageと変換される部分の、SRIP−V → Metal Shading Language が時間を取っている、ような気がする。
Windowsでも再現することが確認できた。
200個の条件分岐があるとpipelineの作成が200秒程度かかる。
{あれ K#EDD2/1CAE}
遺伝的プログラミングで生成したプログラムをWebGPUでまるっと実行することは諦めて、配列に対して基礎的な四則演算をするぐらいにするのが良さそう。
処理とデータがCPUとGPUを行ったり来たりすることになるが、実装は極度に簡単になる。
そのように思われて試してみた結果、基本的な四則演算ではGPUよりもCPUのほうが早い。for文で100回とか演算させるとやっとGPUが早くなってくる。
{あれ K#EDD2/F9D3}
WGSLからはSRIP−Vに変換されるので、これを直接生成すればいいじゃんという厄介な案が浮かぶ。
WebGPU-nativeのgo版ではGLSLが使える雰囲気あったので試そうかと思ったが、GLSLも関数の再帰呼び出しや、変数での配列の値取得はできないらしい。
腹くくってSRIP-Vしか無いのか。
{WebGPU(WGSL?)で遺伝的プログラミングをするにあたっての制約 K#EDD2/2A5A}
- 一つのBufferが128MBまでしか作れない。
- Deviceの取得時にmaxBufferSizeとか設定すれば限定解除できそうな雰囲気があるが、やり方がわからない
- というかやってみたけど(RequiredLimitsを設定)Deviceが取得できなくなった
- Deviceの取得時にmaxBufferSizeとか設定すれば限定解除できそうな雰囲気があるが、やり方がわからない
- Shaderの中で宣言したarrayは添え字が定数でしか取得できない
for(var i=0u; i<10u; i++ ){let val = arr[i]}
みたいなことができない- 正気か?
- 関数の再帰呼び出しは不可
- 「SIMDやぞ」ということで、まあわからなくはない
- 「Stackを作って関数の再帰呼び出しをfor文で再現」と思うも、「Shaderの中で宣言したarrayは添え字が定数でしか取得できない」
- 関数型がなさそう
- 「配列に関数を入れて処理を出しわける」みたいなことができない
- 無理やりやるとif文のお化けになってしまう
- SIMDでif文めっちゃ使うのはなんかヤダ
然るに
- 遺伝的プログラミングで生成したProgramを構成するNode群を実行できない
- NodeからNodeを呼び出したい
- → 「関数の再帰呼び出しは不可」
- Node群を入力側から順次実行し、Nodeの出力値をNodesOutputみたいなarrayに格納
- → 「Shaderの中で宣言したarrayは添え字が定数でしか取得できない」
- StorageBufferは変数の添え字が使えるので、それでNodesOutputを作る
- → 「一つのBufferが128MBまでしか作れない」
- 並列処理の数だけNodesOutputが必要なので、1GBぐらいほしい
- 手元にある1070 TiはCUDA Coreが2432基あるので
- 並列処理の数だけNodesOutputが必要なので、1GBぐらいほしい
- → 「一つのBufferが128MBまでしか作れない」
- 並列処理したい数だけStorageBufferを作る(シェーダーを自動生成する)
- → 処理が複雑過ぎて3日もたてば何やってるかわからないコードになる
- NodeからNodeを呼び出したい
- どれか一つでも制約をなんとかできれば遺伝的プログラミングはできそう。
きえええええええええええええぇぇぇぇぇ
GPGPUあるあるの制約なのかもしれないが、世の機械学習ライブラリはこういう制約をどう御しているのか。
なぜこんな古生代みたいな制約があるのか。令和やぞ。
一部WGSLの制約かもしれない。
{あれ K#EDD2/EA59}
「遺伝的プログラミングの学習中に生成したプログラムからWebGPUのシェーダーを自動生成して実行する」みたいなことをやったらアホほど処理が遅かった。
- プログラムを普通に実行するのに比べて、50倍実行に時間がかかってすごくすごくすごく遅い
- 自動生成されたプログラムなので1KB~10MB程度となって大きく、これのコンパイルは極めて時間がかかる
- Step数でいうと1000 step~100000 stepみたいな感じ
- 本当はプログラムをもっとデカくして、100MBぐらいにして推論の精度を上げたい
- 学習中、個体が発生する毎にシェーダープログラムのコンパイルが走る
- 自動生成されたプログラムなので1KB~10MB程度となって大きく、これのコンパイルは極めて時間がかかる