Jetson Orin NX + reComputer J401 セットアップ & DockerでGPUを使う

## はじめに
先日、Jetson Orin NX(16G)モジュールを勢いで買いました。
しかし、キャリアボードを買うことを忘れていました。

手元にモノが届いたときに、「モジュールだけじゃないか！忘れてた！」となり、慌てて色々買いました。

以下のものを追加購入しました。

- [reComputer J401 - Jetson Orin NX/Orin Nano用キャリアボード (ACアダプタなし)](https://www.switch-science.com/products/9022)
- [Jetson Xavier NXモジュール用 ファン付きアルミヒートシンク](https://www.switch-science.com/products/7434?variant=42382160134342)
- Crucial P3plus 1TB CT1000P3PSSSD8JP
- USB-C PD トリガーケーブル (DCプラグ 5.5mm/2.5mm) 15V
- Intel 8265NGW ネットワークカード
- WiFiアンテナ MHF4コネクタのやつ

初めは、調子乗ってWi-Fi 6Eが使える **Intel AX210NGW** を使おうとしてダメでした。
そこで、Jetson Nanoで使っていた **Intel 8265NGW** を付けたらすんなり動きました。
実績あるデバイスを使うのは大切ですね。

トリガーケーブルですが、**キャリアボードの定格電圧が9～19V**なので**15V**にしました。
20Vでも大丈夫な気はしますが・・・回路図をしっかりチェックするのも面倒だったり、動作報告も見つからなかったのでやめました。

とりあえず、モバイルバッテリで動いたので、これでよしとします。

:::note
モバイルバッテリを使うのは、趣味で作っている自走する玩具に搭載するためです
:::

---

今回、こちらの記事をかなり参考にしました。
[reComputer J4012セットアップ（Jetson Linuxインストール編）](https://qiita.com/dandelion1124/items/58210e472b3057af97f5)

上記の記事では`JetPack 5.1.1`ですが、私の時は`JetPack 5.1.2`がリリースされていたので、そちらの手順でセットアップを行いました。少し手順が異なります。

## セットアップ作業

今回は母艦としてLinux機が必要です。Windowsでやろうとして失敗したということもありますが、Linuxで設定する方がネット上の情報も多く、おそらく問題が起こりにくいです。

### 0. 組み立て
自作PCと難易度は変わらないので省略です。
静電気にだけは注意しましょう。

### 1. キャリアボードを Force Recovery Mode で起動する

キャリアボードのメーカーのドキュメントによると、`FC REC`端子を`GND`に落として電源を投入すると`Force Recovery Mode`で起動し、USB経由でストレージの書き換えができるようになります。
適当なジャンパ線でつなぎましょう
https://wiki.seeedstudio.com/reComputer_J4012_Flash_Jetpack/#enter-force-recovery-mode

USB-Cのコネクタにケーブルを差して、母艦Linux機と接続します。
その状態で以下のコマンドを打つと、デバイスIDが取得できます
```bash
# このコマンドで確認する
lsusb

# 以下のIDで応答がある
# Orin NX 16GB の場合: 0955:7323 NVidia Corp
# Orin NX 8GB の場合: 0955:7423 NVidia Corp
```
:::note warn
**ここまで確認したらジャンパ線は外しましょう。**
外さないとセットアップに失敗します
:::

### 2. Jetson Linux をダウンロードして構成する
[Jetson Linux 35.4.1](https://developer.nvidia.com/embedded/jetson-linux-r3541)をダウンロードしてきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248663/ca248be2-44fa-adce-006c-e6cdc6bcaec8.png)
上記の画像のDRIVERSと書かれているところの2つ(`Driver Package(BSP)`, `Sample Root Filesystem`)をダウンロードします。

そして以下のコマンドを順番に実行していきます

```bash
# ダウンロード先のフォルダに移動して以下のコマンドを実行
tar xf Jetson_Linux_R35.4.1_aarch64
sudo tar xpf Tegra_Linux_Sample-Root-Filesystem_R35.4.1_aarch64 -C Linux_for_Tegra/rootfs/
cd Linux_for_Tegra/
sudo ./apply_binaries.sh
sudo ./tools/l4t_flash_prerequisites.sh
```

### 3. HDMI搭載サードパーティ製キャリアボード対応
次にディレクトリを移動します
```bash
cd Linux_for_Tegra/bootloader/t186ref/BCT
```

そして`tegra234-mb2-bct-scr-p3767-0000.dts`というファイルを探して、以下を追記します。
`tfc`というセクションの中に追記です。
```yaml:tegra234-mb2-bct-scr-p3767-0000.dts
tfc {
    reg@322 { /* GPIO_M_SCR_00_0 */
    exclusion-info = <2>;
    value = <0x38008080>;
    };
```
tfcというセクションに`reg@322`のブロックを追加します。
**上記の記述を丸々コピペしてtfcというセクションが2個あるということにならないように注意です**

:::note warn
ここで失敗すると起動しなくなります
その時は設定を再チェックしましょう
:::

Jetson Linux r35.4.1のリリースノートに上記の変更が必要な旨が記載されています。
HDMIホットプラグ対応のために`GPIO_M_0`という端子を割り当てているようですが、純正キャリアボードと純正ファームの組み合わせ以外の場合はこのピンにアクセスさせないように変更が必要とのこと。

### 4. ログインを簡略化する設定(オプション)
必要に応じて実施。やらなくてもセットアップはできると思います。
一つ上のディレクトリに移動して、ユーザーを設定して自動ログインするように構成します。
**ユーザー名、パスワード、ホスト名の部分を自分用に書き換えてから実行してください**
```bash
cd Linux_for_Tegra
sudo tools/l4t_create_default_user.sh -u {USERNAME} -p {PASSWORD} -a -n {HOSTNAME} --accept-license

# 例えば以下のように入力
udo tools/l4t_create_default_user.sh -u nvidia -p nvidia -a -n nvidia-desktop --accept-license
```

### 5.書き込み
以下のコマンドを実行すると書き込みが始まります
```bash
sudo ./tools/kernel_flash/l4t_initrd_flash.sh --external-device nvme0n1p1 \
  -c tools/kernel_flash/flash_l4t_external.xml -p "-c bootloader/t186ref/cfg/flash_t234_qspi.xml" \
  --showlogs --network usb0 p3509-a02+p3767-0000 internal
```

最後、自動的に再起動します。
**ここで、Force Recovery Modeで起動するためのジャンパ線がつながったままだとエラーが出ます**

:::note info
もしJetsonのセットアップに失敗したときは、
1. `Force Recovery Mode` で起動してジャンパを外す
1. USB接続して書き込みのコマンドを実行

これで最初からやり直すことができます。セットアップがひと段落するまでは、ダウンロードしたセットアップ用のデータは消さないほうが良いと思います。
:::

## Jetson Statsのインストール
モニタリングツールがあると何かと便利なので入れます。
```bash
sudo apt install python3-pip
sudo -H pip3 install jetson-stats
```

下記のコマンドで情報を取得する
```bash
# 以下のコマンドを実行
jetson_release

# 下記の応答が返ってくる
Software part of jetson-stats 4.2.3 - (c) 2023, Raffaello Bonghi
Model: NVIDIA Orin NX Developer Kit - Jetpack 5.1.2 [L4T 35.4.1]
NV Power Mode[3]: 25W
Serial Number: [XXX Show with: jetson_release -s XXX]
Hardware:
 - Module: Check with sudo
Platform:
 - Distribution: Ubuntu 20.04 focal
 - Release: 5.10.120-tegra
jtop:
 - Version: 4.2.3
 - Service: Active
Libraries:
 - CUDA: Not installed
 - cuDNN: Not installed
 - TensorRT: Not installed
 - VPI: Not installed
 - Vulkan: 1.3.204
 - OpenCV: 4.2.0 - with CUDA: NO
```
上記の応答ではCUDAが機能していませんね。Jetpackのインストールが必要です。

ちなみに、`jetson-stats`は本来モニタリングツールです。
以下のコマンドで色々なステータスが見れます。
```bash
jtop
```

## Jetpackのインストール

以下のコマンドでインストールできます。
```bash
sudo apt update
sudo apt install nvidia-jetpack
```


シェル起動時にパスを通すため、ホームディレクトリの`.bashrc`に以下を追記します
```bash: .bashrc
export PATH="/usr/local/cuda/bin:$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH"
```

その後、以下のコマンドが実行できれば成功です
```bash
nvcc --version
```

`nvidia_release`コマンドを打つと以下の応答が来ます
```bash
Software part of jetson-stats 4.2.3 - (c) 2023, Raffaello Bonghi
Model: NVIDIA Orin NX Developer Kit - Jetpack 5.1.2 [L4T 35.4.1]
NV Power Mode[3]: 25W
Serial Number: [XXX Show with: jetson_release -s XXX]
Hardware:
 - Module: Check with sudo
Platform:
 - Distribution: Ubuntu 20.04 focal
 - Release: 5.10.120-tegra
jtop:
 - Version: 4.2.3
 - Service: Active
Libraries:
 - CUDA: 11.4.315
 - cuDNN: 8.6.0.166
 - TensorRT: 5.1.2
 - VPI: 2.3.9
 - Vulkan: 1.3.204
 - OpenCV: 4.5.4 - with CUDA: NO
```
CUDA 11.4が入っています。


## Dockerを準備
### 準備
ユーザーグループに追加します
これにより`sudo`を毎回つける必要はなくなります
```
sudo gpasswd -a $USER docker
```

コンテナランタイムの確認
```bash
docker info | grep Runtimes

# 下記が出力
 Runtimes: io.containerd.runc.v2 nvidia runc
```

### 動作確認
下記で適当なL4T向けのコンテナを探します
https://catalog.ngc.nvidia.com/containers?filters=&orderBy=scoreDESC&query=L4T

今回は下記で確認します
```bash
# TensorFlow向けコンテナの場合(Google製)
# イメージをプル
docker pull nvcr.io/nvidia/l4t-tensorflow:r35.3.1-tf2.11-py3

# インタラクティブモードでコンテナを起動
docker run -it --rm --runtime nvidia --network host nvcr.io/nvidia/l4t-tensorflow:r35.3.1-tf2.11-py3
```
一応動いたのでよしとします。

:::note
私は動作確認していませんが、ほかにも使えそうなコンテナあります。
```bash
# PyTorchの場合(Meta製)
docker pull nvcr.io/nvidia/l4t-pytorch:r35.2.1-pth2.0-py3
docker run -it --rm --runtime nvidia --network host nvcr.io/nvidia/l4t-pytorch:r35.2.1-pth2.0-py3
```
:::

## コンテナ内からGPUを使えるか確認
### まずは認識状態を確認
手段は色々ありますが、上記コンテナはデフォルトで`nvidia-smi`が動かない様子
代わりにTensorFlowが認識しているかを確認します。

コンテナ内でpythonを起動します
```bash
$ python3
```
以下を入力
```python:対話モード
from tensorflow.python.client import device_lib
device_lib.list_local_devices()
```

こんな出力があれば良さそう
```
[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 13133368536749627121
xla_global_id: -1
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 10984450560
locality {
  bus_id: 1
  links {
  }
}
incarnation: 9057423686936424877
physical_device_desc: "device: 0, name: Orin, pci bus id: 0000:00:00.0, compute capability: 8.7"
xla_global_id: 416903419
]
```

### 動作確認
やはり使える状態になっているか気になります。
超絶適当なCNNを学習させて、GPU使用率が上がることを確認しておきます。

<details><summary>サンプルコード</summary>

```python:cifar10-cnn.py
import tensorflow as tf
from tensorflow.keras import datasets, layers, models

# Load and prepare the CIFAR10 dataset
(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()

# Normalize pixel values to be between 0 and 1
train_images, test_images = train_images / 255.0, test_images / 255.0

# Define the CNN architecture
model = models.Sequential([
    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), activation='relu'),
    layers.Flatten(),
    layers.Dense(64, activation='relu'),
    layers.Dense(10)
])

# Compile the model
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

# Train the model
history = model.fit(train_images, train_labels, epochs=5, 
                    validation_data=(test_images, test_labels))

# Evaluate the model
test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)

print(test_acc)
print("finish")
```

</details>

コンテナを起動するときに、ホストの`home`ディレクトリをコンテナ内の`home`にマウントするコマンドを追加して実行します。
```bash
docker run -it --rm --runtime nvidia --network host -v /home:/home nvcr.io/nvidia/l4t-tensorflow:r35.3.1-tf2.11-py3
```
サンプルコードを分かりやすい場所に置いてコンテナの中から実行します。

次に学習中に別ターミナルを立ち上げて`jtop`で状態を確認します。
```bash
$ jtop
```
以下の表示が確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248663/c419fe9f-ec57-6d0c-d81d-1617df52449f.png)
見た感じ動いていそうです。



## GUIの無効化
以下の記事を参考にGUIを無効化してメモリを節約します。
https://www.youtalk.jp/2023/03/28/jetson-orin-nano.html

まずはSSHサーバーを入れておきます
```bash
sudo apt install openssh-server
```

:::note
`openssh-server`を入れるとホスト機のVSCodeでリモート開発ができるので、GUIを無効にしない場合でも入れておくと便利です。
:::

GUI無効化はこのコマンド打って再起動です
```bash
sudo systemctl set-default multi-user.target
```

GUIを再度有効化するときは以下のコマンド
```bash
sudo systemctl set-default graphical.target
```

以上でセットアップ完了です。

## おまけ
### Tensorflow を直接セットアップする

:::note info
dockerコンテナを使う場合は不要ですが、一応記載しておきます
:::

以下を参考に進めていきます
https://docs.nvidia.com/deeplearning/frameworks/install-tf-jetson-platform/index.html

Jetpackのインストールが完了している必要があります。

依存関係をインストールしていきます
```bash
sudo apt-get update
sudo apt-get install libhdf5-serial-dev hdf5-tools libhdf5-dev zlib1g-dev zip libjpeg8-dev liblapack-dev libblas-dev gfortran
```

pip3をアップデートします
```bash
sudo apt-get install python3-pip
sudo python3 -m pip install --upgrade pip
sudo pip3 install -U testresources setuptools==65.5.0
```
関連ライブラリを入れていきます
```bash
sudo pip3 install -U numpy==1.22 future==0.18.2 mock==3.0.5 keras_preprocessing==1.1.2 keras_applications==1.0.8 gast==0.4.0 protobuf pybind11 cython pkgconfig packaging h5py==3.6.0
```

以下のコマンドでインストールします
```bash
sudo pip3 install --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v512 tensorflow==2.12.0+nv23.06
```
インストールするバージョンは、CUDAバージョンと対応する必要があります。
別バージョンが使いたいときは、以下のリストを参考にバージョン指定を行ってください。
https://docs.nvidia.com/deeplearning/frameworks/install-tf-jetson-platform-release-notes/tf-jetson-rel.html#tf-jetson-rel