この記事はtraPアドベントカレンダー2019の 56日目(12/25) の記事です。
19の翠(sappi_red)です。普段はSysAd班で部内サービスを触ってます。
このアドベントカレンダーではこれが二記事目で、一記事目はそのSysAd班でのことを書いているのでよろしければそちらもぜひ。[1]
Web Assembly
2013年頃に「C言語などのJavaScriptではない別の言語をブラウザで効率よく実行したい」というasm.jsやPNaClが登場しました。
それらの流れを汲み取りながら開発が進められてきたWeb Assemblyの仕様が、6年ほど経った今月の頭にW3Cで勧告に到達しました。
このニュースを見て、以前より気になっていたTensorFlow.jsのwasm対応のissueを思い出し、確認したところアルファ版が出ていることに気づきました。
これは試してみるしかないということで試してみました。
TensorFlow.jsのwasmを試す
以前、らんぷろ(traPの全体集会でのLT)で発表した定点動画から背景画像を抽出するツール(ソース)で試していきます。
らんぷろのときは、時間指定もできずシングルスレッドで処理をするようなものでしたが、今回前後の時間指定ができるようにし、処理もマルチスレッドに書き換えました。[2]
実行するとこのように抽出できます。[3]
以下の図のように処理を行っています。
利用しているモデルはPretrained Tensorflow.js ModelsにあるBodyPix v2のMobileNetV1(multiplier 0.75)です。
READMEの通りにimport
などをするだけで特に難しいことをせずに簡単にwasm版に差し替えられました。[4]
ですが、アルファ版ということもあり、そんなにサポートされている関数は多くないので、サポートされてない関数使ってるよっていうエラーが出ました。
ということでプルリクをいくつか送って動くようにしました![5]
これらのプルリクはマージされて、TensorFlow.js backend wasm v1.5.0-alpha4で利用可能になってます!
パフォーマンス
JSはJavaScript版のことで、Chromeの設定から「ハードウェアアクセラレーションが使用可能な場合は使用する」を無効にすることで計測しました。
wasmは「ハードウェアアクセラレーションが…」を無効にして、Chrome Flagsなどをdefaultの状態でwasm版を実行したものです。
wasm-Sは上記wasmの状態からchrome://flags
のwasm SIMDを有効に変えた状態でのwasm版を計測したものです。[6]
WebGLは「ハードウェアアクセラレーションが…」を有効にしたものです。
動画 | A 480p |
A 720p |
---|---|---|
JS | ^1028.96s, 128.69s |
200.27s |
wasm | ^123.35s, 25.27s |
30.31s |
wasm-S | ^123.83s, 25.33s |
30.38s |
WebGL | ^62.44s, 57.68s |
47.46s |
動画 | A 1080p |
A 1440p |
---|---|---|
JS | 607.57s | --- |
wasm | 99.70s | (279.39s) |
wasm-S | 100.85s | (279.44s) |
WebGL | 91.27s | 181.62s |
動画 | A 2160p |
B 480p |
---|---|---|
JS | --- | 188.25s |
wasm | (635.22s) | 40.39s |
wasm-S | (685.04s) | 40.56s |
WebGL | 584.36s | 68.86s |
動画 | B 720p |
B 1080p |
---|---|---|
JS | 532.16s | --- |
wasm | 116.91s | 313.43s |
wasm-S | 117.28s | 315.19s |
WebGL | 114.53s | 161.78s |
動画 | B 1440p |
B 2160p |
---|---|---|
JS | --- | --- |
wasm | 537.83s | (1153.20s) |
wasm-S | 542.49s | (1156.22s) |
WebGL | 235.42s | 671.79s |
動画 | JS | wasm | wasm-S | WebGL |
---|---|---|---|---|
A 480p | ^1028.96s, 128.69s |
^123.35s, 25.27s |
^123.83s, 25.33s |
^62.44s, 57.68s |
A 720p | 200.27s | 30.31s | 30.38s | 47.46s |
A 1080p | 607.57s | 99.70s | 100.85s | 91.27s |
A 1440p | --- | (279.39s) | (279.44s) | 181.62s |
A 2160p | --- | (635.22s) | (685.04s) | 584.36s |
B 480p | 188.25s | 40.39s | 40.56s | 68.86s |
B 720p | 532.16s | 116.91s | 117.28s | 114.53s |
B 1080p | --- | 313.43s | 315.19s | 161.78s |
B 1440p | --- | 537.83s | 542.49s | 235.42s |
B 2160p | --- | (1153.20s) | (1156.22s) | 671.79s |
A, Bの動画の詳細
- A: 「CC BY 3.0, (C) yiyo不是废喵, 【YIYO☆】くれよん❤crayon(在夕阳下起舞)」(動画時間: 66秒)を前6秒、後3秒除いたもの
- B: 「CC BY 3.0, (C) Candy Yuzuki, 【糖柚希】【CandyYuzuki】ぶれないアイで 踊ってみた」(動画時間: 239秒)を前3秒、後4秒除いたもの
実行マシンスペック/ブラウザ
- CPU: Ryzen 3700X
- メモリ: DDR4-3200 16GB
- GPU: RTX 2070 SUPER
- SSD: WD Black SN750
- OS: Windows 10 Pro 1903
- ブラウザ: Chrome 77.0.3865.120 64bit
^
が前についている値はシングルスレッドで実行しています。
()
で表記したものはChromeのタブ一つで使えるメモリで足りなくなって落ちたり、処理がほぼ進まなくなったりするのでフラグ[7]をつけて実行したものです。(なので自分のツールの実装でこれらの画質のものを処理するのはあまり実用的ではない)
タスクマネージャーを見るとChromeに落とされているのがわかりますね。
自分が実装した関数はSIMDに対応させれていないこととSIMDに対応していない関数が他にもあるので、wasm with SIMDは今後速くなっていくと思われます。(有効にすると軒並み遅くなる原因はわからないですが…)(うまく有効にできてなさそう…)
大学に持っていっているノートPC(i7 8550U/RAM 8GB/Intel UHD Graphics 620)だとJS版では動きはじめすらしないのがwasm版だとそこそこ動きます。速さの傾向は上の表と同じ感じです。(1440pとかは時間がかかりすぎるのとメモリが足りないですが)
公式のREADMEでは、wasm版の利点としてWebGL版よりも幅広い端末で利用可能である点と、重くないモデルであればWebGL版とほぼ同等、重いモデルであってもWebGL版より5~7倍程度の遅いだけでありJS版よりは相当速いという点があると書かれています。
さらにこれら以外の利点として、モデルの処理が軽い場合ならGPUのオーバーヘッドのが大きいのかCPUでマルチスレッドでwasmを用いて処理を行う方がかなり速く処理ができるということがわかりました。[8]
また、マルチスレッド化することでモデルの処理が重い場合でもWebGL版の1.5~3倍あたりまで近づけるということもわかりました。
CPUとGPUの性能のバランスにもよりますが、利用した環境は割と一般的なバランスだと思います。
wasm自体からマルチスレッドを利用できるようにする提案も出ているので(現在Stage2)、JavaScriptを介しているこのツールの実装よりも今後高速に動作するようになるでしょう。
タスクマネージャーを見ると、GPUのVideo Decodeの欄が頭打ちになっていたみたいだったので、「ハードウェアアクセラレーションを有効にして動画のデコードをGPUで行うようにして、モデルの処理はwasmを利用してCPUで行うようにすると、もしかしたら速くなるかもしれない」と思い、試したのですが、上の表のwasmよりも遅くなりました。原因として、GPUとCPU間のPCIeの転送速度が足りないのかもしれないですがよくわからないです。(これが原因ならX570マザーボードにしてグラボをRadeon RX 5700 XTあたりにすると改善するのですが、さすがに調達できないので…)
ただし、この計測はChrome77という少し古いバージョンで行っています。最新版のChrome(Stable/Dev/Beta/Canary)で行うとJS版の動作がすごく遅いということがあったのが理由です。また、動画のデコード処理をGPUで行ってwasm版を使用するとうまく動かないということも発生しました。(よくわからないですが内部的なblob:~.webm
の206 Partialリクエストがうまく返ってこないみたいです)
利用したツール(ソース)は?p_time=true&wasm=true&single=true
をURLにつけることで動作を変更できるようにしてあります。指定しなかったものはfalse
として扱われます。
p_time
はコンソールに処理時間を出力するwasm
はwasmを使用するsingle
はシングルスレッドで処理をする
終わりに
wasm版が出たことで今後軽いモデルを手元で動かすことはある程度気軽に行えるようになっていくのかなって思います。
細かなところで活用されていくと面白そうです。
コラム
asm.jsとPNaCl
asm.jsはFirefoxに実装されたもので、JavaScriptに特殊な方法で型を指定することで、型を利用して高速に処理できるようにするというものである。特殊な方法で型を指定するが、これは通常のJavaScriptとして解釈が可能なものなので、asm.jsに対応していないブラウザでもパフォーマンスの利点は得られないが実行することができる。また、C/C++のソースコードをasm.jsに変換することも可能である。現在ではWeb Assemblyへの対応に伴ってほぼ使われていない(元々そんなに使われていないとは思うけど)。
PNaClはChromeに実装されたNaCl(Native Client)というものをもとにしている。このNaClはChromeアプリ(ChromeOS以外では今後廃止予定)でネイティブコードを実行できるようにするもので、CPU依存のバイナリを利用する。そこで中間表現を利用してCPU非依存にしたものがPNaClで、こちらはChromeのアプリだけじゃなく通常のウェブページでも利用できた。現在はWeb Assemblyへの対応に伴ってChromeアプリ以外では廃止されている。
SIMDとは
元々CPUは一つの命令に対して一個の足し算(のような)しか行えなかった。しかし、動画などのデータ処理をする際に、同じ演算を複数の値に行うことがあり、これでは回数分命令をしなければならず効率が悪かった。そこで、一度に同じ演算を複数の値に行えるような命令をCPUに追加した。このような命令をするやり方のことをSIMDと呼ぶ。
JavaScriptからSIMDを扱えるようにするSIMD.jsという仕様が一時期考えられていたことがあったが、wasmの実装とともに消滅した。
sandbox
[7:1]のもう少し詳しい話。Chromiumでは、サイトごとに環境を隔離して、OS上のファイルシステムやほかのサイトのタブに干渉できないようにしている。これをsandboxとよぶ。(ブラウザに限らず仮想マシンなどでも同じような技術がある)
sandboxによるメモリ制約は以下のようになっているみたい。
参考 (16GB limit per chrome tab - Googleグループ Chromium-dev)
Chromiumの当該コードを少し書き換えたのが以下
#ifdef _WIN64 // 利用したマシンはWindowsの64bitなのでifdefの中が実行される
size_t memory_limit = static_cast<size_t>(sandbox::kDataSizeLimit);
int64_t GB = 1024 * 1024 * 1024;
int64_t physical_memory = base::SysInfo::AmountOfPhysicalMemory();
if (physical_memory > 16 * GB) {
memory_limit = 16 * GB;
} else if (physical_memory > 8 * GB) {
memory_limit = 8 * GB; //利用したマシンの物理メモリは16GBちょうどなのでこっち
}
return policy->SetJobMemoryLimit(memory_limit); // 最大8GBまでにセットされる
#else
return sandbox::SBOX_ALL_OK;
#endif
Web WorkerがそのPCのスレッド数個ほど動きます。ただし、そもそもの自分のコードがよくないところがあるので、そっちを直すほうが純粋な処理速度の面であれば効果的だと思います ↩︎
CC BY 3.0 (C) 217仮面ライアー, 【仮面ライアー217】 ハイドアンド・シーク 【踊ってみた】
CC BY 3.0 (C) ま, ニア 踊ってみた 【オリジナル振付】 ↩︎tensorflow/tfjs#2528, tensorflow/tfjs#2543。
Minimum
、Maximum
、Greater
、GreaterEqual
を実装しました。関数が追加された過去のコミットを見つつ、コードを書いたらうまく動いてくれました ↩︎wasm版で使われているライブラリのXNNPACKは
WebAssembly SIMD (experimental)
に対応しています。experimentalなのでchrome://flags/#enable-webassembly-simd
のフラグで有効にする必要があります。chrome://flags
から有効にする以外にchrome.exe --js-flags="--experimental-wasm-simd"
として起動する方法もあります。WebAssembly SIMDの対応状況 ↩︎chrome.exe --no-sandbox
というフラグを指定して起動しました。ただ、セキュリティ的にこれを常用するのはよくないです。詳細は上のコラムを参照。 ↩︎ ↩︎