Yolov3 を wasm module化して、いろんなプラットフォームで動かしてみた。

## WebAssembly(Wasm)とは

　・WASMはウェブブラウザ（クライアントサイド）で動作する低水準プログラミング言語

　・現在はウェブブラウザにとどまらず、サーバー、IoTデバイスなどで動作するようになってきている

　・wasmの実行環境がsandbox化されて、メモリ空間が隔離されていたり、ファイルシステムなどのリソースへのアクセスを制限することができる。

　・wasm moduleは、ポータビリティが高く、同一のbinaryが異なるCPU アーキテクチャ上で動作させることができる。

　・機械学習の分野でもwasm を使うケースが増えてきている、クラウド上で動かしたモデルをwasmのポータビリティを活かして、同じmoduleをエッジ側にdeployするなどが可能になっている。

## WebAssemblyが生まれた経緯

　・元々ブラウザでは対応できない高度な処理を行うための言語としてJava Scriptが開発された
　　例: ブラウザ上でのリアルタイムグラフの作成、検索、ソート、ブラウザゲームの実装など


　・ 時代と共にJSに求められる機能も増えていったが、JAVAと同様にインタプリタ言語であり、サイズが大きく、CやRustなどの
　　コンパイル言語と比較して速度が遅い

　・ ブラウザがスマートフォンなどの携帯端末に実装されるようになり、低速なCPUでもより実行速度を求められるようになった

　・ JSを高速化する方法としてasm.jsが開発されたが、コンパイル時間が長く、データ構造の概念が存在しないなど使用上の
　　問題が多く、JavaScriptの根本的な問題を解決する方法としてWebAssemblyが提唱される


## Yolov3をwasmにビルドする
　・Cで書かれたYolov3 を WASI-SDKを使ってwasm module にビルドしてみた。

#### やったこと①
　　・wasm module化するためのWASI-SDKのインストール。
　　https://github.com/WebAssembly/wasi-sdk

~~~
export WASI_VERSION=14
export WASI_VERSION_FULL=${WASI_VERSION}.0
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz
tar xvf wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz
sudo mkdir /opt/
sudo cp -rf ./wasi-sdk-${WASI_VERSION_FULL} /opt/
~~~
 
#### やったこと②
　　・Makefileの修正
　　コンパイラをgccからwasi-sdkに含まれているclangに変更

```make
-CC=gcc
-CPP=g++
+WASI_SDK_PATH = /opt/wasi-sdk-14.0
+CC=$(WASI_SDK_PATH)/bin/clang --sysroot=$(WASI_SDK_PATH)/share/wasi-sysroot
+CPP=$(WASI_SDK_PATH)/bin/clang++ --sysroot=$(WASI_SDK_PATH)/share/wasi-sysroot
```

#### やったこと③
　　・推論(detector)だけ動かしたかったので、不要な物をbuild対象から外す。
　　　推論に必要なものは、yolo.o と detector.o と darknet.o のみ。
```
-EXECOBJA=captcha.o lsd.o super.o art.o tag.o cifar.o go.o rnn.o segmenter.o regressor.o classifier.o coco.o yolo.o detector.o nightmare.o instance-segmenter.o darknet.o
+EXECOBJA=yolo.o detector.o darknet.o
```

#### やったこと④
　　・実行時にout of bounds memory accessが起きるので、その修正。

~~~
Loading weights from ./yolov3-tiny.weights...Done!
Enter Image Path: ./data/dog.jpg
./data/dog.jpg: Predicted in 47.716545 seconds.
Exception: out of bounds memory access
~~~

　　最大の難所だったのが、使ったyolov3-tinyのweightファイル(https://pjreddie.com/darknet/yolo/) が64bit環境で動かす前提で作られていた。
　　wasmはまだ32bit 対応しかされてないので、weightファイルのパースに失敗していた。ここを強制的に修正。

```c
--- a/src/parser.c
+++ b/src/parser.c
@@ -1234,7 +1234,8 @@ void load_weights_upto(network *net, char *filename, int start, int cutoff)
     fread(&minor, sizeof(int), 1, fp);
     fread(&revision, sizeof(int), 1, fp);
     if ((major*10 + minor) >= 2 && major < 1000 && minor < 1000){
-        fread(net->seen, sizeof(size_t), 1, fp);
+        fread(net->seen, 8, 1, fp);
     } else {
         int iseen = 0;
         fread(&iseen, sizeof(int), 1, fp);
```


#### 実際にビルドして動作させたコードはこちら
　　https://github.com/daiki0321/darknet_wasm


## buildしたwasmをWeb Assembly Micro Runtime上で実行する
　　・今回buildしたyolo wasmの実行環境は、Web Assembly Micro Runtimeを使用。
　　　このruntimeをx86_64環境と、ラズパイ(arm64) 用にビルドして、同一のyolo wasmを実行してみる。

#### Web Assembly Micro Runtimeのビルド方法
　　・こちらを参考にして、Runtimeのビルド 
https://github.com/bytecodealliance/wasm-micro-runtime/blob/main/doc/build_wamr.md#linux

~~~
cd product-mini/platforms/linux/
mkdir build
cd build
cmake -DWAMR_BUILD_INTERP=1 -DWAMR_BUILD_FAST_INTERP=1 -DWAMR_BUILD_JIT=0 -DWAMR_BUILD_AOT=1 -DWAMR_BUILD_LIBC_WASI=1 -DWAMR_BUILD_SHARED_MEMORY=1 -DWAMR_BUILD_LIB_PTHREAD=1 ..
make
# iwasm is generated under current directory
~~~
ラズパイ用にビルドするときは、cmake時に-DWAMR_BUILD_TARGET=AARCH64を追加する。

#### Web Assembly Micro Runtime上で実行する。
　・--dir を使って、wasm moduleが指定したディレクトリにアクセスすることを許可する。
　　darknet.wasm 以降のコマンドはもとのyolov3 のコマンドと同じ。
　　weightsファイルは[yolov3の本家のサイト](https://pjreddie.com/darknet/yolo)からDownlonad.

~~~
$ ./iwasm --dir=cfg --dir=data --dir=. ./darknet.wasm detect cfg/yolov3-tiny.cfg ./yolov3-tiny.weights ./data/dog.jpg 

layer     filters    size              input                output
    0 conv     16  3 x 3 / 1   416 x 416 x   3   ->   416 x 416 x  16  0.150 BFLOPs
    1 max          2 x 2 / 2   416 x 416 x  16   ->   208 x 208 x  16
    2 conv     32  3 x 3 / 1   208 x 208 x  16   ->   208 x 208 x  32  0.399 BFLOPs
    3 max          2 x 2 / 2   208 x 208 x  32   ->   104 x 104 x  32
    4 conv     64  3 x 3 / 1   104 x 104 x  32   ->   104 x 104 x  64  0.399 BFLOPs
    5 max          2 x 2 / 2   104 x 104 x  64   ->    52 x  52 x  64
    6 conv    128  3 x 3 / 1    52 x  52 x  64   ->    52 x  52 x 128  0.399 BFLOPs
    7 max          2 x 2 / 2    52 x  52 x 128   ->    26 x  26 x 128
    8 conv    256  3 x 3 / 1    26 x  26 x 128   ->    26 x  26 x 256  0.399 BFLOPs
    9 max          2 x 2 / 2    26 x  26 x 256   ->    13 x  13 x 256
   10 conv    512  3 x 3 / 1    13 x  13 x 256   ->    13 x  13 x 512  0.399 BFLOPs
   11 max          2 x 2 / 1    13 x  13 x 512   ->    13 x  13 x 512
   12 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024  1.595 BFLOPs
   13 conv    256  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x 256  0.089 BFLOPs
   14 conv    512  3 x 3 / 1    13 x  13 x 256   ->    13 x  13 x 512  0.399 BFLOPs
   15 conv    255  1 x 1 / 1    13 x  13 x 512   ->    13 x  13 x 255  0.044 BFLOPs
   16 yolo
   17 route  13
   18 conv    128  1 x 1 / 1    13 x  13 x 256   ->    13 x  13 x 128  0.011 BFLOPs
   19 upsample            2x    13 x  13 x 128   ->    26 x  26 x 128
   20 route  19 8
   21 conv    256  3 x 3 / 1    26 x  26 x 384   ->    26 x  26 x 256  1.196 BFLOPs
   22 conv    255  1 x 1 / 1    26 x  26 x 256   ->    26 x  26 x 255  0.088 BFLOPs
   23 yolo
Loading weights from ./yolov3-tiny.weights...Done!
./data/dog.jpg: Predicted in 48.841929 seconds.
dog: 57%
car: 62%
car: 52%
truck: 56%
bicycle: 59%
~~~

　・X86_64のubuntuでは、大体50sec かかった
　　ラズパイにubuntu(22.04) を入れて測ると、963 secもかかった。

　　さすがにwasmのままだと速度が出てなくて使い物にならない。

## JITとAOTコンパイラの速度比較
　・Wasmは、AOT (Ahead-of-time) コンパイルができ、事前にWeb Assembly のByte CodeからCPUに応じた機械語にコンパイルしておくことができる。

　　以下のコマンドでAoTコンパイルする。
　　https://github.com/bytecodealliance/wasm-micro-runtime/blob/main/doc/build_wasm_app.md#compile-wasm-to-aot-module

~~~
./wamr-compiler/wamrc --disable-simd --target=x86_64 -o ../darknet_wasm/darknet.wasm.aot ../darknet_wasm/darknet.wasm
~~~


　・以下の4パターンで実行時間を計測した。
　　① JITコンパイラで実行した場合(darknet.wasm)
　　② AOTコンパイル後に実行した場合(darknet.wasm.aot)
　　③ ② + yoloの中で行列計算を行う部分のgemm_nnのみをnativeで実行した場合
　　④ wasmを使わずに、nativeで実行した場合(darknet)

　・機械学習モデルをwasm化する場合は、AOTコンパイルは必須で必要になる。
　　AIモデルの推論は行列計算のような同じコードを何度も通るので、その部分のみをnativeにするか、GPUやアクセラレータにオフロードすれば、wasm化したことによる速度低下はほとんどなく、さらにwasmの良さであるsandboax化やPortabilityの恩恵を受けることができる。

![Performance Measurement(Sec).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535710/2507baed-a7a1-51ae-afed-6e85d7687e34.png)