[Jetson Nano] Nsight で TensorFlow Lite アプリの 性能ボトルネック を観測

#1. 概要
　NVIDIA が提供するプロファイリングツール　**Nsight** を使って、Jetson Nano で動作するアプリの性能ボトルネックを見つける方法をまとめます。

　今回私が **Nsight** を使おうと思ったきっかけは、[前回記事](https://qiita.com/terryky/items/d24f1fc14698f8d3697b) でもネチネチと作業したように、
TensorFlow Lite GPU Delegate (OpenGLESバックエンド) を使ったアプリを少しでも速くしたかったからです。

　ですので、下記のような Posenet による姿勢推測処理負荷の高いアプリを観測対象とします。

　なお、観測対象アプリのソースコードは下記にあります：
　https://github.com/terryky/tflite_gles_app/tree/master/gl2posenet

![gl2posenet_m.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/a27e6119-aed0-20ce-3d82-d52674a6edf6.png)


#2. Nsight について
#2.1 Nsight 3兄弟
　[公式ページ](https://developer.nvidia.com/tools-overview) にある通り、2019/12月時点において、**Nsight** は、３種類のツールから構成されます。

<img width="600" alt="nsight_diagram.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/4edf5f53-7762-c3d5-cfc4-4939cab37b0b.png">


##### 1) Nsight Systems
　・性能解析のとっかかりとして、システム全体を俯瞰して大まかな性能ボトルネックを見つけるためのツール。
　・ボトルネックがCPU処理なのか、OpenGLES や CUDA などGPUオフロード先の処理なのかの判定を行う。
##### 2) Nsight Graphics
　・Nsight Systems による観測の結果、ボトルネックがOpenGLESやVulkanによる描画処理だと分かった際に、描画に焦点をあててさらに深く観測するためのツール。
　・OpenGLES や Vulkan など、バイナリライブラリとして隠蔽されているAPIの内部の処理負荷を観測できる。
##### 3) Nsight Compute
　・Nsight Systems による観測の結果、ボトルネックがCUDA処理だと分かった際に、CUDAをさらに深く観測するためのツール。

#2.2 Jetson Nanoでの Nsight
　Jetson Nano 上のアプリを **Nsight** で観測するには、ホストPCからJetson Nano へssh接続した状態で観測を行います。このとき、Jetson Nano側のアプリは、プロファイル情報取得するため ```sudo``` 等で root 権限を付与した状態で実行する必要があります。私はここでハマったのですが、ホストPCからJetsonNanoへssh接続する際、通常ユーザアカウントではなく、root アカウントで接続しないとうまくプロファイルが取得できませんでした。（root アカウントによる ssh 接続は何か気持ち悪いですが仕方なく。。）

　なお、Jetson Nano で使用可能な **Nsight** は **JetPack** として提供されています。JetPack 4.3 には、Nsight 3兄弟のうち **Nsight Systems** と **Nsight Graphics** だけが含まれているようです。**Nsight Compute** は見つけられませんでしたが、今回観測対象のアプリは CUDA を使っていないため、問題ありませんでした。

<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/5ed57257-db10-b071-8484-d09fd398fd49.png">




#3. Nsight のインストール
　[公式手順](https://docs.nvidia.com/sdk-manager/index.html) に従って JetPack をインストールすることで、Nsight が使えるようになります。
　以下、簡単にインストールの流れを説明します。

### 3.1 SDK Manager のダウンロード
　[NVIDIA のWebページ](https://developer.nvidia.com/embedded/jetpack) から ```NVIDIA SDK Manager``` をダウンロードします。
　（ダウンロードには、NVIDIA Developer Program への参加が必要です）

<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/03151cfa-3a28-c5fa-78de-ae701ff82a9f.png">

　Developer Program にログインした状態で上記画像の赤枠ボタンをクリックすると、```sdkmanager_1.0.0-5517_amd64.deb``` がダウンロードできるので、ホストPC (Ubuntu) の適当な場所へ保存します。

### 3.2 SDK Manager の実行
 ホストPC上で、```SDK Manager``` を起動します。

```sh
$ cd ~/download
$ sudo apt install ./sdkmanager_1.0.0-5517_amd64.deb
$ sdkmanager
```

　SDK Manager を起動するとログインを促されますので、SDK Manager をダウンロードした時と同じアカウントでログインします。
<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/8fe427bd-6741-6218-a188-d8b2195f5c40.png">


　ログイン後、ターゲットとなるデバイスの選択画面となります。
今回はホストPC側に Nsight をインストールしたいだけなので、Target Hardware のチェックを外します。
(Jetson Nano 側はSDカードに最新版イメージが書き込み済みの前提) 

<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/16cba1a8-8b41-c7a0-c3f4-c1cafca040a5.png">


　次に、インストールするソフトモジュールの確認画面となります。
　ホストPC側に、NVIDIA Nsight Graphics/Systems がインストール対象となっていることが確認できます。
　「I accept ...」にチェックをいれて、continue します。

<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/9e645f12-6535-0fad-03fc-61887649e0df.png">


　sudo するためのパスワードを入力すると、あとは自動でインストールが完了します。
![sdk_manager4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/c92e767f-7516-22f4-882d-e1c54feef4b4.png)



#4. Nsight Systems の実行
　まずは大まかにボトルネックをつかまえるために、**Nsight Systems** によるプロファイルを行います。

#4.1 前準備
　前述の通り、ホストPCからJetson Nano へ root アカウントで ssh 接続できる環境を準備します。
　例えば下記のように。（正式な手順はわかっていません。すみません）

（参考記事）https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10190946705
（参考記事）https://qiita.com/____easy/items/60d8b5d5ca03a3ce250d

#4.2 Nsight Systems 起動
　これ以降、全ての作業はホストPC側から行います。
　まず下記コマンドで起動します。

```sh
$ nsight-sys
```

 ターゲット選択画面になるので、スパナみたいなアイコンをクリック
<img width="300" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/9116dfde-ad1e-9314-7273-66de64e61c77.png">

　Create a new connection をクリック
<img width="300" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/cd2d0f3c-b89e-668e-b511-c56936f84377.png">

　ssh 接続するために、Jetson Nano の IP とアカウントを設定。
<img width="300" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/680d6945-9679-7ffc-2b54-24e6dfd4eb05.png">


　connect
<img width="300" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/8e76f3c9-e1c7-a249-3eae-382e557c4c38.png">


 Jetson Nano へssh接続するパスワード入力
<img width="300" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/1cddf9a9-fbfb-14a1-51c6-1326a446cf45.png">


　観測対象アプリを指定する画面になるので、下記設定後、```Start```。
　・観測対象アプリのパスを指定
　・XWindow を開くアプリなので、```DISPLAY```変数の値を ```:1```　に指定　（不要かも）
　・```Launch as root``` にチェック
　・```Collect OpenGL trace``` にチェック
<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/678c0f05-a9c0-9957-c2b3-f28cc3b1d73c.png">


　Jetson Nano 側で観測対象アプリが立ち上がり、プロファイルデータの取得が始まります。
　観測対象アプリが定常状態となり有効なデータが取得できたと思われるタイミングで ```Stop``` させます。
<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/f1438087-6da7-2d67-cc47-c00f7def2a3c.png">

#4.3 Nsight Systems で観測

　プロファイル結果が表示されるので、下記のようにしてボトルネックを探します。
　・右上の ```Timeline View``` で、Ctrl+マウスホイールで適当なスケールに拡大
　・１フレーム分の時間と思われる範囲をマウス左ドラッグで選択
　・右クリック→ ```Filter by Selection```
　・画面真ん中あたり、```Top-Down View``` を選択
　・画面右下に、時間占有率の高い関数が表示される

![nsys9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/129bb574-677f-ba8b-69ea-81e5594e63fd.png)

　上の例の場合、１フレームあたり 81[ms]くらい時間がかかり、そのうち 85% が、```WaitForCompletion()``` 関数が消費していることがわかります。つまり、TFLite GPUDelegate において、OpenGLES Compute shaderのGPU処理完了を待っている時間が大半であることを示しています。

#4.4 GPU Delegate 有無での比較
 比較のため、GPU Delegate を使わないときのプロファイルも取得してみます。
![nsys10a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/70f28965-fde0-edf1-82e2-7c60f7d6b762.png)

　両者のプロファイル結果を比較することで、下記のようなことが確認できます。

|比較項目     |GPU Delegateあり |GPU Delegateなし|
|:-----------:|:--------------:|:--------------:|
|処理時間     | 81 [ms]        | 165 [ms]        |
|CPUコア      | 1コアだけ忙しい| 4コアとも忙しい |
|占有率高い関数| OpenGLES     | Eigen           |


　今回の目的は GPU Delegate 時のボトルネック解析なので、次は **Nsight Graphics** を使って、GPU Delegate時の OpenGLES の内部動作をさらに詳しく見ていきます。



#5. Nsight Graphics を使った性能プロファイル
　Nsight Graphics によるプロファイリング作業も、全てホストPC側から行います。
　下記コマンドで起動します。

```sh
$ nv-nsight-gfx-for-l4t
```

 Quick Launch の Continue をクリック
<img width="400" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/a18ac13b-cd03-77e0-feb0-4b4148806545.png">

・Jetson NanoへのSSH接続情報を設定
・観測対象アプリを設定
・```Frame Profiler``` を選択
・```Frame Delimiter``` は ```SwapBuffers``` を選択。(描画しないアプリなら glFinish() 等を選択)
<img width="400" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/1092d6a1-5215-a6b7-4d1c-e9c61aa4831a.png">

 Jetson Nano への接続に成功すると、Jetson Nano側で観測対象アプリが起動され、プロファイル待機状態となります。適当なタイミングで ```Cpature for Live Analysis``` を押すと、その直後の１フレームのプロファイリング情報が取得されます。
<img width="600" alt="download_sdk_manager.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/cb646011-859b-5890-5cce-0ee314bc4ac8.png">


下図が取得したプロファイル結果です。
　・glDispatchCompute() による GPU 稼働時間が 69.85[ms] とダントツであることがわかります。
　・１フレームあたり glDispatchCompute() は複数回よびだされますが、最も時間のかかるものは 7.22 [ms]
　・GPUパイプラインにおけるボトルネックを表示させると、```L2 Bottleneck``` がダントツ
以上のことから、性能改善を実現するには、L2$ の負荷が下がるように、ComputeShaderに手を入れていく必要があることがわかります。（これが難しいのですが。。。）

![gfx5a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265135/4a7cd4ca-b956-4294-37c8-5c214a8cefc6.png)


#6. 最後に
　Jetson Nano 上で、Nsight を使ってアプリの性能プロファイリングする方法を説明しました。
　[公式ページ](https://developer.nvidia.com/tools-overview) にはもっと詳しい情報がありますのでご参考になさってください。

　**Nsight Systems** を使えば、**GPUを使っていないアプリでも**、どの関数実行に時間がかかっているのかを簡単に取得できるので、とても汎用性の高いツールだと思います。
　**Nsight Graphics** では、**バイナリで隠蔽されている OpenGLES 内部を詳しく観測** してボトルネックを見つけることができるので、こちらも有用なツールです。

　強力なGPUハードを提供するだけでなく、その性能を引き出すための良いソフトツール群が揃っているのがNVIDIAの強みですね。