高位合成によるFPGA回路設計を読んで、YOLOv3を試してみた2

## 前回の経緯
YOLOv3で、DPUの処理が始まるとリブートする問題が発生。Ultra96v2のエラッタのPMIC電流制限にかかっていると思われ、次のいずれかで回避できないか？と考え、ひとまず簡単にできそうな、DPUの規模 or 周波数を落として、どうなるかを試してみました。

 - Infinion USB005を購入して、PMICのFWを更新
 - 誰かに更新してもらう(他力本願)
 - **PMICの電流制限に引っかからない程度に、DPUの規模と周波数を落とす**。<==これを試してみた
 - PGOODの回路を改造

## DPUの規模変更
### DPUのConfiguration
Ultra96v2に搭載されているZynq Ultrascale+ MPSoCでは、B512～B4096と8種類のconfigurationに対応している。各DPUのConfigurationについて、ユーザーガイドから抜粋した。B2304で、PP=8, ICP=12, OCP=12となっているので、B512, B800あたりにすれば大きく消費電流は減りそうです。

https://docs.xilinx.com/r/en-US/pg338-dpu/Architecture-of-the-DPUCZDX8G

|DPUCZDX8G<br>Architecture|Pixel<br>Parallelism (PP)|Input Channel<br>Parallelism (ICP)|Output Channel<br>Parallelism (OCP)|Peak Ops<br>(operations/per clock)|
|-------|-------|-------|-------|-----|
|B512	|4	|8	|8	|512  |
|B800	|4	|10	|10	|800  |
|B1024	|8	|8	|8	|1024 |
|B1152	|4	|12	|12	|1150 |
|B1600	|8	|10	|10	|1600 |
|B2304	|8	|12	|12	|2304 |
|B3136	|8	|14	|14	|3136 |
|B4096	|8	|16	|16	|4096 |

### DPUの実装
各DPUを実装した場合の、Ultra96v2のリソース使用率を以下にまとめておきます。ユーザーガイドを見れば、ビルドせずとも、リソース使用率は分かるんですが、一応、Vitisでビルドした際の使用率になります。LUTとREGはユーザーガイドのResource Useとズレはありますが、BRAMとDSPは全くの同値です。(まあ、当たり前か)
DPU以外は、最低限のAXI interconnect, AXI interrupt Controller, Clocking wizard, Processor System Resetしか実装しておらず、Platformのリソース使用は殆どなく、DPUが大半を占めています。にもかかわらず、B2304だとLUT/BRAM/DSPが結構な使用率になっているので、他の回路の実装が必要なユースケースだと、B1600あたりが限界な気もしますね。


| Name        |             | LUT             | LUTAsMem        | REG              | BRAM          | DSP           |
|-----------  |----------   |-----------------|-----------------|------------------|---------------|---------------|
| Ultra96v2   | Platform    |  2089<br> [  2.96%] |   147<br> [  0.51%] |   2564<br> [  1.82%] |   0<br> [  0.00%] |   0<br> [  0.00%] |
|             | User Budget | 68471<br> [100.00%] | 28653<br> [100.00%] | 138556<br> [100.00%] | 216<br> [100.00%] | 360<br> [100.00%] |
| DPUCZDX8G   | B512        | 27644<br> [ 40.37%] |  1887<br> [  6.59%] |  35036<br> [ 25.29%] |  73<br> [ 33.80%] |  78<br> [ 21.67%] |
|             | B800        | 30403<br> [ 44.40%] |  2227<br> [  7.77%] |  42257<br> [ 30.50%] |  91<br> [ 42.13%] | 117<br> [ 32.50%] |
|             | B1152       | 33227<br> [ 48.53%] |  2568<br> [  8.96%] |  49060<br> [ 35.41%] | 123<br> [ 56.94%] | 164<br> [ 45.56%] |
|             | B1600       | 38891<br> [ 56.80%] |  2845<br> [  9.93%] |  62636<br> [ 45.21%] | 127<br> [ 58.80%] | 232<br> [ 64.44%] |
|             | B2304       | 42823<br> [ 62.54%] |  3298<br> [ 11.51%] |  73622<br> [ 53.14%] | 167<br> [ 77.31%] | 326<br> [ 90.56%] |

### DPUの変更手順
簡単に記しておきます。

 - dpu_config.vhの変更
```shell
//`define B4096 
`define B1600
```
 - DPUの再ビルド
再ビルドして生成された、BOOT.BIN, image.ub, dpu.xclbinに置き換える。
```shell
make KERNEL=DPU DEVICE=ULTRA96V2
```
 - YOLOv3モデルの再コンパイル
再ビルド後に生成されたhwhファイルにて、モデルのコンパイルを再実行。 

## YOLOv3の実行
B2304ではリブートしてしまったんですが、B1600に変更したところ、正常に動作することを確認できました。やはりPMICの電流制限にひっかかってリブートしているってことで間違いなさそう。

```shell
root@ultra96v2_v2020_1:~/YOLOv3# ./YOLOv3 dpu_yolov3_b1600.elf 
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0325 19:58:46.659310   635 main.cc:14] create running for subgraph: yolov3_b1600
conv2d_58_Conv2D_60
conv2d_66_Conv2D_70
conv2d_74_Conv2D_79
in_dims   : 1, 416, 416, 3
out_dims0 : 1, 13, 13, 255
out_dims1 : 1, 26, 26, 255
out_dims2 : 1, 52, 52, 255
Infering COCO_train2014_000000000078.jpg...
Infering COCO_train2014_000000000036.jpg...
Infering COCO_train2014_000000000077.jpg...
Infering COCO_train2014_000000000071.jpg...
Infering COCO_train2014_000000000025.jpg...
Infering COCO_train2014_000000003259.jpg...
Infering COCO_train2014_000000000072.jpg...
Infering COCO_train2014_000000000030.jpg...
Infering COCO_train2014_000000000064.jpg...
Infering COCO_train2014_000000000009.jpg...
Infering COCO_train2014_000000000034.jpg...
Elapsed Time per frame: 293368[us]
```

<br>
B512, B800, B1152でも問題なく動作したので、各DPUで実行した場合の時間もまとめてみました。上に記載した、Peak Opsと大よそ反比例に近いような実行時間になっていますが、Peak Opsの割合ほど、実行時間が短縮されていないのは、データの転送時間などのオーバーヘッドが存在する分かな？もっと小さいモデルで、オーバーヘッドが支配的な場合だと、より顕著に見えてきそう。
<br>
また、参考までにUltra96v2のCPUで、YOLOv3を実行した場合の時間も確認してみましたが、約146秒となりました。CPUで処理した場合の詳細な実装内容までは確認していないので、同じ土俵での比較になっているか？までは分かりませんが、これだけ差があるというのは劇的ですね。ただ、モデルの量子化によって、32bit浮動小数点から8bit固定小数点に変更されている分、精度がどこまで維持できているかも気になる。


|CPU/DPU  |周波数[MHz]|時間[ms] |
|---------|-----------|---------|
|DPU B512 |200/400|725      |
|DPU B800 |200/400|511      |
|DPU B1152|200/400|367      |
|DPU B1600|200/400|293      |
|DPU B2304|200/400|リブート  |
|CPU      |    -       |146093   |


## DPUとCPUの実行結果比較
CPUとDPUで推論精度がどの程度異なるか？が気になったので、ひとまず一つの画像で比較してみました。(正確には、mAPなどで性能評価しないと分からないと思う。Xilinxとかでやってそうだけど、、、どこかにないんだろうか。)

 - DPUの場合、スコアが低いオブジェクトの検出ができていない？一部のbook, bicycle, kerboardなどが検出できていない。
 - スコアが高いオブジェクトも、矩形領域が結構違うものもある。

mAPを評価してみたら、結構差が出そうで興味深いです。(実際、どうやったらいいか分からないが:joy:)
モデルを量子化する際のパラメータとかで、性能が変わってきたり、とかあるんだろうか？

**CPU推論結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413488/cb7fc13f-3626-f6c0-5b0f-04e3795c14f1.png)

**DPU推論結果**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413488/08c1d369-2233-3030-9f9a-d731b474706b.png)

## CPUでYOLOv3を実行(補足)
ちょっと記事の本題とは逸れるのですが、手順を簡単に記載しておきます。

Darknetのソースコードを取得
```shell
git clone https://github.com/pjreddie/darknet
```
ビルド実行
```shell
cd darknet
make
```
ただし、petalinuxにgcc, g++が入っていなかったので、ビルド前にdnfでgccとg++をインストールが必要だった。
```shell
dnf install gcc
dnf install g++
```

また、gcc, g++をインストール後もPATHが通ってなかったので、下記のシンボリックリンクを作成
```shell
cd /usr/bin
ln -s aarch64-xilinx-linux-gcc gcc
ln -s aarch64-xilinx-linux-g++ g++
```

下記にて、YOLOv3を実行
```shell
./darknet detect cfg/yolov3.cfg yolov3.weights COCO_train2014_000000003259.jpg
```