ABEJA Platformからエッジデバイス(NVIDIA Jetson)にモデルデプロイをする



# はじめに

はじめまして。おおたぐろです。ABEJAのエンジニアです。
これは、ABEJA Platform AdventCalendar 2018 12日目の記事です。
(ごめんなさい。遅くなりました :bow: )

今回は、ABEJA Platformからエッジデバイス(Jetson TX2)にモデルをデプロイする方法をお話したいと思います。どうぞよろしくお願いいたしますmm

![20180820130526.jpg](https://qiita-image-store.s3.amazonaws.com/0/128123/1aafdbc5-bc43-90b4-c9bd-d7a332b8259c.jpeg)


# 手順

<b>流れ</b>

1. 本体(Jetson)の設定
2. Platformへのデバイス登録作業
3. Device Agentのインストール・設定
4. モデルの作成
5. デバイス上にモデルデプロイ
6. テスト

## 本体(Jetson)の設定

### L4Tのインストール

Jetson に L4T 28.2 をインストールします。 L4T 28.2 は JetPack 3.2 に含まれています。

https://developer.nvidia.com/embedded/downloads#?search=jetpack%203.2

![スクリーンショット 2018-12-19 17.08.01.png](https://qiita-image-store.s3.amazonaws.com/0/128123/7e34fb3f-7332-9e7d-fe31-2c152e342d06.png)


### Dockerのインストール

```bash

# Update libs, install prereqs for commands
nvidia@tegra-ubuntu:~$ sudo apt-get update && sudo apt-get upgrade && sudo apt-get -y install curl apt-transport-https

# Install Docker-CE
nvidia@tegra-ubuntu:~$ echo "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu xenial edge" >/etc/apt/sources.list.d/docker.list
nvidia@tegra-ubuntu:~$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -qq - >/dev/null
nvidia@tegra-ubuntu:~$ sudo apt-get update && sudo apt-get install docker-ce
nvidia@tegra-ubuntu:~$ sudo systemctl enable docker

# Test Docker install
nvidia@tegra-ubuntu:~$ sudo docker run --rm -it arm64v8/ubuntu:16.04 cat /etc/issue
Ubuntu 16.04.5 LTS \n \l
```


## PlatformとJetsonの接続

### デバイス登録

ここからは、JetsonのターミナルではなくPlatformの画面上での操作になりますm
まずはじめにデバイスに名前・説明を付けて、デバイス認証用のキー:key:を発行します。

![1-edge-device-top.png](https://qiita-image-store.s3.amazonaws.com/0/128123/a1be36a3-56bc-677d-2159-ff069c02b8da.png)
![2-edge-device-create.png](https://qiita-image-store.s3.amazonaws.com/0/128123/c4717311-3dcb-8ac9-9173-8aaa09d3ec98.png)
![3-edge-device-created.png](https://qiita-image-store.s3.amazonaws.com/0/128123/e8afc4ac-1e06-443d-46d9-eb9b2c28961d.png)

ここで表示されるDevice ID及び鍵は、は、次のステップでデバイス側に埋め込みます。
Device IDはあとから参照できますが、鍵はこのページでしか表示されないので注意が必要です。
※`Download`ボタンを押すことで、zip圧縮された鍵ファイルを直接ダウンロードする事ができます。

ダウンロードしたzipは次の工程で使うので、Jetson側のファイルシステムに転送しておきます。
筆者はscpを使ってzipファイルを移動しました(￣ー￣)

```bash
taguro@main:~/Download$ scp device_XXXXXXXXXXXXX_certifications.zip nvidia@172.16.XXX.XXX:~/device_XXXXXXXXXXXXX_certifications.zip
```

ここまでの作業が終わると、Edge Devicesに登録したデバイスが表示されます。

![スクリーンショット_2018-12-19_17_55_00.png](https://qiita-image-store.s3.amazonaws.com/0/128123/9ea9b51b-cdde-d85e-f98f-bb895a6c5ad6.png)


### ABEJA Device Agentのインストール

ABEJA Device Agentは、ABEJA Platformと接続してデプロイを担ったり&メトリクス・ログをクラウド上に送信してくれるデーモンです。これを入れる事によって、ユーザーはクラウド上のモデルをデバイスに直接デプロイしたり、デバイス本体のモニタリングをする事ができます。（要するに大事なヤツです）

```bash

nvidia@tegra-ubuntu:~$ wget https://s3-ap-northeast-1.amazonaws.com/abeja-device-resources/0.7.1/abeja-device-agent.deb
nvidia@tegra-ubuntu:~$ sudo dpkg -i abeja-device-agent.deb
```

エッジデバイスがABEJA Platformが接続して認証する為に、`/etc/abeja/keys`を作ってそこに鍵を配置します。
ここで先程生成した鍵(zipファイル)を使います。

```bash

nvidia@tegra-ubuntu:~$ sudo mkdir /etc/abeja
nvidia@tegra-ubuntu:~$ sudo unzip device_xxxxxxxxxxxxx_certifications.zip -d /etc/abeja/keys
```

鍵の配置が完了したら、次にデバイスの情報を `/etc/default/abeja-device-agent`に設定します。
先程Platform上で払い出された`Device ID`を、`DEVICE_NAME`という所に入れておきます。
※ `Device ID`は、Edge Devices一覧から確認できます。


```bash

nvidia@tegra-ubuntu:~$ sudo vim /etc/default/abeja-device-agent
DEVICE_NAME=XXXXXXXXXXXX
DEVICE_TYPE=jetson_tx2
LOCAL_LOGGING=1
```

最後に、これらの設定を反映させる為にデーモン再起動をします。

```bash

nvidia@tegra-ubuntu:~$ sudo systemctl restart abeja-device-agent.service
nvidia@tegra-ubuntu:~$ sudo systemctl status abeja-device-agent.service
```

### デバイス連携確認


Edge Devicesページにある`Metrics`ボタンを押すと、下記のようなメトリクス情報が表示されます。
これが表示されていれば、デバイスとPlatformが正しく通信できており、初期設定は全て完了です。 :tada: 

![スクリーンショット_2018-12-19_17_55_00-2.png](https://qiita-image-store.s3.amazonaws.com/0/128123/069572f7-03db-800b-9ae0-d0c621a572c7.png)

![スクリーンショット_2018-12-19_20_11_43.png](https://qiita-image-store.s3.amazonaws.com/0/128123/de8da221-07a4-a381-90a9-c9c34582b99d.png)


#### 余談
ABEJA Device Agentのインストール・設定が正しく完了すると、下記のようにコンテナが立ち始めます。
合計で3つのシステムコンテナが立ち上がれば成功です。

```bash

nvidia@tegra-ubuntu:~$ docker ps
CONTAINER ID        IMAGE                                                                                   COMMAND                  CREATED             STATUS                 PORTS                                              NAMES
9069eb198afe        xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/device-logger-fluentd:0.6.4-aarch64   "/bin/entrypoint.sh …"   3 hours ago         Up 3 hours             0.0.0.0:5140->5140/tcp, 0.0.0.0:24224->24224/tcp   device-logger
d1541745ea3a        abeja/docker-dd-agent:latest-arm64v8                                                    "/entrypoint.sh supe…"   3 hours ago         Up 3 hours (healthy)   8125/udp, 8126/tcp                                 dd-agent
9fd99a29f77d        abeja/abeja-device-agent:latest-aarch64                                                 "/bin/sh -c abeja-de…"   3 hours ago         Up 3 hours
```




## モデル作成

次にモデルの作成に移ります。基本的にはモデル名・バージョン・Runtime設定といった基本情報設定と・動かすモデル（ファイル）をアップロードする事で完了します。今回は、Runtimeに`abeja-inc/mxnet:0.1.0-arm64v8`を利用し、モデルのソースコードとしてABEJAで用意しているResnet50の[サンプル](https://s3-ap-northeast-1.amazonaws.com/abeja-device-resources/0.6.1/sample/mxnet-resnet50.tar.gz)を使いました。
※FYIですが、`Deploy after creating`にチェックを入れておくと、エッジデプロイをする為のリソースが自動でつくられます。

![スクリーンショット 2018-12-19 20.18.35.png](https://qiita-image-store.s3.amazonaws.com/0/128123/dfff22d7-8b8a-ea3c-87af-e6927172eeaa.png)

![スクリーンショット 2018-12-19 20.19.06.png](https://qiita-image-store.s3.amazonaws.com/0/128123/943930d2-db8f-a781-e2c4-b953e86ce080.png)

### 余談
今回モデルとして動かすのは下記のコードです。モデルのコードを変更する事で、細かいふるまいや後述するAPIのResponseの変更が可能です。


```python

import numpy as np
import cv2
import mxnet as mx
import argparse
import os


def ch_dev(arg_params, aux_params, ctx):
    new_args = dict()
    new_auxs = dict()
    for k, v in arg_params.items():
        new_args[k] = v.as_in_context(ctx)
    for k, v in aux_params.items():
        new_auxs[k] = v.as_in_context(ctx)
    return new_args, new_auxs

synset = [l.strip() for l in open('synset.txt').readlines()]
if os.environ.get('USE_CPU', 1):
    ctx = mx.cpu()
else:
    ctx = mx.gpu(0)
sym, arg_params, aux_params = mx.model.load_checkpoint('resnet-50', 0)
arg_params, aux_params = ch_dev(arg_params, aux_params, ctx)


def handler(iter, _ctx):
    for img in iter:
        img = cv2.resize(img, (224, 224))  # resize to 224*224 to fit model
        img = np.swapaxes(img, 0, 2)
        img = np.swapaxes(img, 1, 2)  # change to (c, h,w) order
        img = img[np.newaxis, :]  # extend to (n, c, h, w)
        arg_params["data"] = mx.nd.array(img, ctx)
        arg_params["softmax_label"] = mx.nd.empty((1,), ctx)
        exe = sym.bind(ctx, arg_params ,args_grad=None, grad_req="null", aux_states=aux_params)
        exe.forward(is_train=False)

        prob = np.squeeze(exe.outputs[0].asnumpy())
        pred = np.argsort(prob)[::-1]
        yield {'result': [synset[pred[i]] for i in range(5)]}
```


## デバイス上にモデルデプロイ

ABEJA Platformでは、ユーザーが用意(学習)したモデルを使って推論APIを展開する事ができます。
クラウド上にもエッジ上にもAPIを作成する事ができるのですが、今回はエッジ上にAPIを作成してみます。
（エッジデバイスに対してAPI作成すると、クラウド上に置かれているモデル等が勝手にダウンロードされます :wink: )

まず、Modelに紐づくDeploymentというページにいきます。
(`Deploy after creating`という所にチェックをいれると、自動でDeploymentが作られるはずです)

![スクリーンショット_2018-12-19_20_19_59.png](https://qiita-image-store.s3.amazonaws.com/0/128123/d0c997c3-6fd1-f632-aec2-ce62f3f0eb8d.png)

`Create HTTP Service`をクリックすると、下記の様なWizardが表示されます。
Edge側のタブを選択すると、デプロイ先のデバイス・APIのポート設定等が表示されるので、入力します。
※今回はポート8000でResnet50を使った推論APIを展開します

![スクリーンショット_2018-12-19_20_27_26.png](https://qiita-image-store.s3.amazonaws.com/0/128123/44ce1403-5c25-b801-44f9-2ccb94616a3a.png)

設定が完了すると下記のように、`HTTP Service`の一覧に新たな項目が増えます。

![スクリーンショット_2018-12-19_20_28_40.png](https://qiita-image-store.s3.amazonaws.com/0/128123/32a6a09b-b026-37e7-65b9-ea87280a18f3.png)

あとは放置でOKです。
ABEJA Device AgentとABEJA Platformがよしなに通信してくれて、先程作ったモデルが自動ダウンロード＆自動でAPIが作成されます。

簡単すぎて、あまりエッジデプロイした気にならないかもしれませんが、内部のメトリクス・コンテナ数を見ると動いてる感を肌で感じることができます。
下記はデプロイ前後のメトリクスの様子です。

![スクリーンショット_2018-12-19_20_49_30-2.png](https://qiita-image-store.s3.amazonaws.com/0/128123/7ed11ecf-ec32-e7fe-18f2-fed76e4540de.png)

デプロイが終わるとモデル作成時に指定したコンテナイメージ`abeja-inc/mxnet:0.1.0-arm64v8`が立ち上がっている事が確認できます。ここまで確認できればシステム的にも全く問題なしです。

```bash

root@tegra-ubuntu:~# docker ps
CONTAINER ID        IMAGE                                                                                   COMMAND                  CREATED             STATUS                 PORTS                                              NAMES
46da842d25cc        xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/abeja-inc/mxnet:0.1.0-arm64v8         "/bin/bash -l -c 'cd…"   23 minutes ago      Up 23 minutes          0.0.0.0:8000->8000/tcp                             edge-xxxx
9069eb198afe        xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/device-logger-fluentd:0.6.4-aarch64   "/bin/entrypoint.sh …"   3 hours ago         Up 3 hours             0.0.0.0:5140->5140/tcp, 0.0.0.0:24224->24224/tcp   device-logger
d1541745ea3a        abeja/docker-dd-agent:latest-arm64v8                                                    "/entrypoint.sh supe…"   3 hours ago         Up 3 hours (healthy)   8125/udp, 8126/tcp                                 dd-agent
9fd99a29f77d        abeja/abeja-device-agent:latest-aarch64                                                 "/bin/sh -c abeja-de…"   3 hours ago         Up 3 hours                                                                abeja-device-agent
```


## テスト

![cat_thumb.jpg](https://qiita-image-store.s3.amazonaws.com/0/128123/9882f891-7681-ba04-6725-c7d912383c19.jpeg)

テスト用の画像をJetsonにダウンロードします。

```bash
nvidia@tegra-ubuntu:~$ wget https://console.abeja.io/images/cat_thumb.jpg
```

localhost:8000に下記のように画像を投げると、JSONで推論結果が帰ってくる事が確認できます。
あとはビジネスロジックの乗ったコードを作り推論APIと結合する事によって、やりたい事を自由に叶える事ができます :muscle: 


```bash
nvidia@tegra-ubuntu:~$ curl -X POST -H 'Content-Type:image/jpeg' --data-binary @cat_thumb.jpg -XPOST http://localhost:8000
{"result": ["n02123045 tabby, tabby cat", "n02123159 tiger cat", "n02124075 Egyptian cat", "n02127052 lynx, catamount", "n03958227 plastic bag"]}
```


# 最後に

今回はABEJA Platformを使って、エッジデバイスに対してモデルデプロイをする流れについて解説させていただきました :muscle: 
[ハンズオンイベント](https://cloudai.connpass.com/event/112498/)も来年1月に企画しておりますので、ご興味がアレばぜひご参加いただければと思います。
ありがとうございました :bow: