2019年12月25日 | ブログ記事

TensorFlow.jsでwasmを使ってみるためにコントリビュートした【AdC2019 56日目】

この記事は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]
diff
diff2

以下の図のように処理を行っています。
Frame-1

利用しているモデルはPretrained Tensorflow.js ModelsにあるBodyPix v2のMobileNetV1(multiplier 0.75)です。

READMEの通りにimportなどをするだけで特に難しいことをせずに簡単にwasm版に差し替えられました。[4]

ですが、アルファ版ということもあり、そんなにサポートされている関数は多くないので、サポートされてない関数使ってるよっていうエラーが出ました。
minimum-not-impl

ということでプルリクをいくつか送って動くようにしました![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の動画の詳細
実行マシンスペック/ブラウザ

^が前についている値はシングルスレッドで実行しています。
()で表記したものはChromeのタブ一つで使えるメモリで足りなくなって落ちたり、処理がほぼ進まなくなったりするのでフラグ[7]をつけて実行したものです。(なので自分のツールの実装でこれらの画質のものを処理するのはあまり実用的ではない)
memoryタスクマネージャーを見ると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として扱われます。

終わりに

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

  1. traQのmarkdownのパースをWebWorkerでやるようにした話【アドベントカレンダー2019-3日目】 ↩︎

  2. Web WorkerがそのPCのスレッド数個ほど動きます。ただし、そもそもの自分のコードがよくないところがあるので、そっちを直すほうが純粋な処理速度の面であれば効果的だと思います ↩︎

  3. CC BY 3.0 (C) 217仮面ライアー, 【仮面ライアー217】 ハイドアンド・シーク 【踊ってみた】
    CC BY 3.0 (C) ま, ニア 踊ってみた 【オリジナル振付】 ↩︎

  4. 自分が使おうとしたときにはimportエラーしてたりしてました。今は直ってます ↩︎

  5. tensorflow/tfjs#2528, tensorflow/tfjs#2543MinimumMaximumGreaterGreaterEqualを実装しました。関数が追加された過去のコミットを見つつ、コードを書いたらうまく動いてくれました ↩︎

  6. wasm版で使われているライブラリのXNNPACKWebAssembly SIMD (experimental)に対応しています。experimentalなのでchrome://flags/#enable-webassembly-simdのフラグで有効にする必要があります。chrome://flagsから有効にする以外にchrome.exe --js-flags="--experimental-wasm-simd"として起動する方法もあります。WebAssembly SIMDの対応状況 ↩︎

  7. chrome.exe --no-sandboxというフラグを指定して起動しました。ただ、セキュリティ的にこれを常用するのはよくないです。詳細は上のコラムを参照。 ↩︎ ↩︎

  8. ツイートによるとシェーダ生成のオーバーヘッドらしいです。 ↩︎

この記事を書いた人
sappi_red

19B。SysAd班。 JavaScript書いたりTypeScript書いたりGo書いたりRust書いたり…

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2017年11月14日
IBIS2017参加報告
Keijan
2018年4月17日
春休みにゲームを作りました
uynet
2019年12月21日
モデリングを始めてみたい君へ、MagicaVoxelのススメ
isak
2019年12月13日
ゲーム紹介「League of Legends」【AdC2019 44日目】
Yataka_ML
2017年12月26日
RustでMCMC(Metropolis-Hasting)
David
2017年12月1日
WaltZ
Double_oxygeN
記事一覧 タグ一覧 Google アナリティクスについて