Apple M1のMacを使ってJetson用のaarch64 CUDA込みのバイナリをビルドする


Apple M1チップをロボット開発で活用する!

MacBook Airを購入した理由はまさにこれです。
そして、前記事(こちら)でParallels環境でいかに快適にUbuntuを使用するようにできるか、頑張ったのはまさにこれが理由です。

aarch64っていうことはJetsonとかRaspberry Pi 4と一緒では?

これが全ての発端です。ロボットの開発などで、ROSのパッケージなどをビルドするとき、実機に搭載するJetsonやRaspberry Pi上でビルドしないといけなくて皆さんお困りではないでしょうか?
やはりクロスコンパイルして、それをデプロイしたいところです。
しかし、一般的に使われているツールで実機にデプロイできる環境としては、私は知る限り以下の2つくらいしかしりません。

  • Isaac SDK
  • MATLAB/Simulink

まずはIsaac SDKですが、最近触っていないので、変わっていたら申し訳ないですが、こちらは完全にNVIDIAが独自に開発したロボット開発用のプラットフォームで、CUDAを使用するようなコードでもIntel PCでクロスコンパイルして、実機にデプロイできます。。。と、思いきや実際は実機でビルドしているようです(詳しい人まとめてください。。。)。ROSともシームレスには繋がらず、ROSとのインタフェースは用意されていますが、いまいち使いにくいですし、Docker環境内でIsaac SDK環境を用意しないと、開発環境をぶち壊しにくることで非常に有名です。

では、MATLAB/Simlinkですが、こちらはいうまでもなくMATLABやSimulinkで書いたコードを実機にデプロイすることができます。しかし一般的に使用できるHome EditionではCUDAには対応していませんし、対応しているのはRaspberry Piだけで、Jetsonには対応していません(ゴニョゴニョするとできますが、、、、)あと、こちらはクロスコンパイルを行うのではなく、オートコード生成で生成したファイルをtarballにして実機に送り付けて、展開して実機でビルドを行うため、Raspberry Piで実行すると、途方もなく遅かったします。あとなんといってもバグだらけで、私が確認したところまともにRaspberry PiにROSノードをデプロイできるのはR2019aのみです。

実際のロボットに搭載するコンピュータとしては、やはりRaspberry Piは力不足ですし、できればJetsonを載せたいところです。NVIDIAならではのCUDAをバリバリ使った認識なども統合してROSを動かしたいという要望も多いかと思います。真っ先に思いつくのは、なんとか実機からaarch64のcudaなどのライブラリ一式を持ってきてクロスコンパイルできるようにするというやり方ですが、できそうにもないので、今回は力技でやってみたいと思います。

M1 Mac上でJetson向けのバイナリを力技でビルドする

ということで、登場するのがJetsonと同じaarch64のM1 Macとなります。
こっからは前記事(こちら)での環境を元に説明していきます。

一体何をしてみるかというと、

Jetsonでセットアップが終わったSDカードってaarch64向けのライブラリも全部入っているし、M1 Mac上のUbuntuでマウントしてchrootしたらそのままビルドとか動くんじゃね?

という発想からきています。
chrootってなんや?という人のために軽く説明しておきますと、kernelやハードウェアはそのままで、ルートディレクトリのみ他のフォルダにしてしまうことのできるコマンドのようなものです(適当ですみません)

ものは試しです。やってみましょう。

JetsonのSDカードをセットアップする

これは各自SDカードを作成して、Jetsonに挿入し、セットアップを完了させておいてください。
注意点としては、JetPack4.5より前のものは、SDカードの前半部分にゴミのようなパーティションが残ってしまっており、Disksで表示したときに目的のパーティションが画面から消えてしまうことがあるので、JetPack4.5を使用したほうが楽です。

MacにSDカードを認識させる

ノート系のMacの場合はTypeCしかインタフェースがないので、何かしら用意して認識させてください。
PCに接続するとParallelsがUSBのカードリーダーをMacに接続するかUbuntuに接続するか聞いてくるので、Ubuntuを選択してください。

Ubuntu上でマウント

さて、ここからはUbuntuでの操作となります。

Ubuntuでディスク(英語名:Disks)というAppを実行すると、この写真の通り、SDカードが認識しています。
ここで、右下に示されているところがマウント先ですので、ターミナルで、マウント先の/media/[User名]/XXXXXに移動しておきます。

各種chrootするための準備

ここからはrootで実行したほうが効率が良いので、rootで進めたいと思います。

sudo su
apt install -y schroot

まず準備のため、schrootをインストールします。(何かで必要だったのですが、なぜ必要か忘れました。。。。)

続いて、Ubuntu側のハードウェア関連のパーティションをSDカード上にマウントします。

mount --rbind /dev dev
mount --bind /proc proc
mount --rbind /sys sys
mount --rbind /run run

続いてネットワーク設定を母艦となるUbuntuのものに一時的に入れ替えます。

mv ./etc/resolv.conf ./etc/resolv.conf.org
cp /etc/resolv.conf ./etc

これで準備完了です。

JetsonのSDカードにchrootしてみる

さて、これでやっとchrootできます。
以下のコマンドでJetsonのSDカードの/にchrootします。

chroot . /bin/bash -l

やってみるとわかりますが、Terminal上では特に何も起こっていないように見えます。しかし、ここはすでにJetsonのSDカードの中です。pwdコマンドを打つと/にいることになっているのがわかります。

せっかくなので、apt update/upgradeを実行してみましょう

apt update && apt upgrade

気づかれた方は分かったかもしれませんが、visionworksなどの文字列が出てきた通り、ここはまさしくJetsonのSDカード上です。実機でなくてもapt updateができてしまうので、かなり便利です。ただ、注意点としては、ここでsuコマンドで、Jetsonの中のUserに変更しても、User IDが変わらないのか、sudoが実行できません。ですのでSDカード内では常にrootで作業を行うことになります。

aarch64のCUDAを使っているソフトウェアをビルドしてみる

それでは、きちんとCUDAのaarch64も含めてビルドできるか試してみましょう。
今回は代表として、jetson-inferenceをビルドしてみたいと思います。
Building the Project from Sourceに書かれている内容に従って実行していきます。
途中で学習済みデータの選択画面などが出てきますが、適当にそのまま進めてください。(使うときはちゃんと説明を読んでください)
make installとldconfigは実機でやっても良いです。

cd /home/USER名
apt install git cmake libpython3-dev python3-numpy
git clone --recursive https://github.com/dusty-nv/jetson-inference
cd jetson-inference
mkdir build
cd build
cmake ../
make -j$(nproc)
make install
ldconfig

まず、ParallelsでCPU数を指定しているはずですので、その数によってビルド速度が異なってきます。また、SDカード上でビルドしているのファイルIOが遅いこともあって、いろいろボトルネックになっています。

ただ、これでも相当速いことが体感できると思います。

後始末

/devなどをアンマウントして、syncしてからSDカードをアンマウントしてください。

exit
umount -l .
rm ./etc/resolv.conf
mv ./etc/resolv.conf.org ./etc/resolv.conf
sync

ここまでやれば普通にUbuntuからSDカードを手動でアンマウントして抜いてください。

実機での動作確認

実機にSDカードを入れて起動してください。

sshでMacから入り、続いて、今までrootで作業したので、フォルダの所有者を変更しておきます。

ssh IPアドレス
sudo chown -R User名:User名 ./jetson-inference

それでは例を実行してみましょう

cd jetson-inference/examples/my-recognition
mkdir build
cd build
cmake ..
make
./my-recognition ../polar_bear.jpg 

ネットワークが読み込まれて、最後に
image is recognized as 'ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus' (class #296) with 100.000000% confidence
と出てくればOKです。

応用例

ということで、見事Apple M1チップでJetson用のCUDA込みのビルドを行うことができました。Parallelsの割り当てCPUの数を変更すればもう少し早くできるかもしれませんし、何よりこの方法から以下のような応用例を考えることができます。

  • SDカードのイメージを別の仮想HDDにコピーして、その上でビルドを行うことで、SDカードのアクセスの遅さを回避する

  • (ROSのworkspaceなどの)ビルドが完了したら自動で実機にscpでデプロイするようにする

などなど、この方法を応用すればJetsonやRaspberry Piを用いたロボットのビルド速度が劇的に改善するかと思います!

Apple M1チップをお持ちのロボット開発者の人は是非試してみてください。

ではでは。