こんにちは! テリーです。先日NVIDIA GTCというイベントがありました。GPUテクノロジーカンファレンスの略です。NVIDIAが進めている最新の技術と商品を紹介しているため、AI、動画、音声、その他あらゆる高速コンピューティングのトレンドを理解することができます。年々紹介する分量が増えてきていましたが、今年は特に量が多かった印象です。

さて、機械学習エンジニアやストリーミングエンジニアの諸氏におかれましては、開発環境の維持コストに頭を悩ませている方も多いことでしょう。なぜなら、あまりにも技術の進歩が激しく、去年50万円も出して購入したGPUパソコンでさえ、今年の新商品に搭載されている機能が使えないということが毎年のように繰り返されているからです。最新のGPUとパソコンを渋々買い替えている人が多いと思いますが、セットアップも中古売却もめんどくさいです。なんとかならないでしょうか? 今回はそういう悩みのある方に向けて、無料で最も工数の少ない環境構築手順をご紹介します。「GPUなんて人件費と比較したら誤差でしょ。ケチケチせず買えばいいじゃん」という余裕のある会社の方は読まないでください。

私が注目したのはGoogle Colaboratoryです。Colaboratoryは最大12時間、ホストとGPUを専有して、機械学習アプリを手軽に試すためにGoogleが無料で提供してくれているJupyter Notebookの実行環境です。このGoogle Colaboratoryで使用しているホストにVisual Studio Code(VSCode)から接続し、クラウド上のコンピュータとして使用します。実質的な中身はただのUbuntuですので、Python以外にもあらゆる言語をインストールして実行することができます。

ネットには同様の記事が紹介されていますが、本記事が2021年6月現在で自信を持って最速最小工数です。毎朝始業時に1日1回ですので、少しでも手をかけずにいきましょう。

本記事の対象読者

  • MacでNVIDIA GPUを使ったアプリケーションを無料で開発したい
  • 放置してもお金がかからないような環境がほしい
  • 放置してもソースコードが消えないような環境がほしい
  • 画像を表示してAIの途中経過や推論結果を何度も確認したい

環境

本記事の実行環境は下記を使用しています。

  • Mac macOS Big Sur 11.4
  • Visual Studio Code 1.56.2
  • XQuartz 2.8.1
  • Colaboratory 無料版
  • Chrome 91.0.4472.77

事前に必要なもの

  • 専用のGoogleアカウント
  • Chrome
  • VSCode
  • SSHの公開鍵・秘密鍵
  • XQuartzのインストール

構築手順概要

  1. ngrokアカウント作成と認証キーの取得
  2. ColaboratoryにGoogleドライブを接続、ngrok起動、SSHサーバ起動
  3. Chromeにauto refreshプラグインインストールと開始
  4. VSCodeにremote-sshプラグインインストール、設定、接続
  5. 環境構築コマンドを記述・実行
  6. Gitの設定
  7. X Windowの表示

手順1. ngrokアカウント作成と認証キーの取得

ngrokという無料のサービスとコマンドアプリがあります。このサービスを使用すると、ローカルIPアドレスしか持たないホストに、外部のネットワークからアクセスすることができるようになります。ノートパソコン上のWebサーバを一時的にスマホからアクセスしたり、HTTPSでなければ動かないようなWebhookの開発をしたりするために使われていますが、SSHのトンネルとしても使用することができます。右上の「Sign up」をクリックし、アカウントを作成してください。Googleアカウントでひもづけるのが一番操作が少ないです。

ngrokのサービスサイト。右上の「Sign Up」からアカウントを作成

ログインすると、下記の画面が現れます。画面右下あたりの「2. Connect your account」ブロックにあなたのアクセストークンが表示されています。のちの手順でこの文字列をコピペします。

手順2. ColaboratoryにGoogleドライブを接続し各種サーバ起動

手順2.1 GPUを接続

Colaboratoryにアクセスし、新規のノートブックを作成します。メニューから「ランタイム」-「ランタイムのタイプを変更」をクリックし、出てきたダイアログの中の「ハードウェアアクセラレータ」のドロップダウンリストから「GPU」を選択し、「保存」ボタンをクリックします。

メニューから「ランタイム」-「ランタイムのタイプを変更」を選択

「ハードウェアアクセラレータ」のドロップダウンリストから「GPU」を選択して保存

次に下記のコマンドをノートブック内で実行し、GPUがアクティブなことを確認します。OSのバージョン、NVIDIAドライババージョン、CUDAのバージョン、GPUの種類を確認できます。Tesla T4、V100など毎回違うものになり、特定のGPU型番を選択することはできません。

!cat /etc/lsb-release
!nvcc -V
!nvidia-smi

ノートブック内でコマンドを実行した結果

手順2.2 Googleドライブを接続

次に、画面左端のショートカットアイコンのうち、上から4つ目のファイルアイコンをクリックし、Googleドライブアイコンをクリックします。「アクセスを許可しますか?」というダイアログが出たら「GOOGLEドライブに接続」ボタンをクリックします。

上から4つ目のファイルアイコンをクリック(1)し、Googleドライブアイコンをクリック(2)

GOOGLEドライブに接続

手順2.3 ngrokのアクセストークンとSSH公開鍵をサーバに保存

下記のコマンドをノートブックのコードブロックにコピペし、***PASTE_HERE***の部分を置き換えます。設定する値は、ngrok_authtokenはngrokのアクセストークン、idrsa_pubはSSH公開鍵です。変数に代入しているのはコードの可読性をよくする以上の意味はありません。CTRL+Enterで実行します。画面に何も出力されなければ正常です。

ngrok_authtoken="***PASTE_HERE***"
idrsa_pub="***PASTE_HERE***"
!mkdir -p /root/.ssh
!echo $idrsa_pub > /root/.ssh/authorized_keys

手順2.4 SSHサーバのインストールと実行

次にSSHサーバをインストールします。下記のコマンドをノートブックのコードブロックにコピペし、CTRL+Enterで実行してください。「invoke-rc.d: could not determine current runlevel invoke-rc.d: policy-rc.d denied execution of start.」という、エラーのようにも見えるメッセージが出ますが無視して大丈夫です。

!apt-get install -qq -o=Dpkg::Use-Pty=0 openssh-server x11-apps> /dev/null
!mkdir -p /var/run/sshd
!echo 'X11Forwarding yes' >> /etc/ssh/sshd_config
!echo 'X11UseLocalhost no' >> /etc/ssh/sshd_config
get_ipython().system_raw('/usr/sbin/sshd -D &')

上記コマンドの実行例

手順2.5 ngrokのインストールと実行

次にngrokのコマンドラインアプリをインストールします。下記のコマンドをノートブックのコードブロックにコピペし、CTRL+Enterで実行してください。問題なければ「HostName 0.tcp.jp.ngrok.io Port 12345」のようなログが最終行に表示されます。ホスト名先頭の数字「0」とポート番号「12345」は実行のたびに毎回変わる値です。この出力は後述の手順でコピペして使用します。エラーメッセージが表示された場合はngrok_authtokenの変数に値が入力されていないか、トークンの有効期限が切れているか、コピペミスの可能性を疑ってください。

!wget -q -c -nc https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
!unzip -qq -n ngrok-stable-linux-amd64.zip
!rm ngrok-stable-linux-amd64.zip
!./ngrok authtoken $ngrok_authtoken
!pkill -f ngrok
get_ipython().system_raw('./ngrok tcp -region=jp 22 &')
!sleep 2
import urllib.request, json
with urllib.request.urlopen('http://localhost:4040/api/tunnels') as response:
  data = json.loads(response.read().decode())
(host, port) = data['tunnels'][0]['public_url'][6:].split(':')
print(f"""Host colab
  HostName {host}
  Port {port}
  User root
  AddKeysToAgent yes
  UseKeychain yes
  ForwardAgent yes
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
  ForwardX11 yes
  XAuthLocation /usr/X11/bin/xauth
""")

上記コマンドの実行例

手順2.6 ワークスペースディレクトリの作成と最低限の環境変数の設定

VSCodeで表示する作業ディレクトリを作成します。VSCode+Dockerで開発経験のある方は、Dockerのフォルダ構成と同じにすると都合がよいので、「/workspace」というフォルダがオススメです。もちろん他のフォルダ名でも構いませんが、本記事では「/workspace」を前提に話を進めます。
下記のコマンドでGoogleドライブ上にworkspaceというフォルダを作成し、/workspaceというシンボリックリンクを作成します。

!mkdir -p /content/drive/MyDrive/workspace
!ln -sfn /content/drive/MyDrive/workspace /workspace

SSH接続前に設定した方がよい環境変数の定義を登録します。JupyterNotebook上ではさまざまな環境変数が定義されていますが、SSH接続ではそれらの環境変数が定義されていません。一番困るのはNVIDIAのGPU関連のdllにパスが通っていないため、NVIDIAの関数が実行できないことです。下記のコマンドを実行してください。

!echo 'export PATH=/usr/local/cuda/bin:$PATH' >> /root/.bashrc
!echo 'export LD_LIBRARY_PATH=/usr/lib64-nvidia' >> /root/.bashrc

次のコマンドは必須ではありませんが、かなりオススメです。毎日接続するたびにホストが変わるため、コマンド入力履歴がなくなり、長いコマンドを手打ちする必要があります。下記のコマンドを実行することで、コマンド履歴がgoogleドライブ上のテキストファイルに保存され、前日打ったコマンドを上下キーで選択することができ、効率が上がります。

!echo 'export PROMPT_COMMAND="history -a"' >> /root/.bashrc
!echo 'export HISTFILE=/workspace/.bash_history' >> /root/.bashrc

手順3. ChromeにAuto Refreshプラグインをインストール

ColaboratoryはJupyter Notebookを使うために提供されており、ブラウザを閉じたり一定時間放置したりするとホストがシャットダウンされ、気づいた後に再接続しても前と同じホストに接続することができません。VSCodeで作業する場合は数分ごとにブラウザを見ることはできないので、Chromeプラグインをインストールし、数分ごとに定期的にページリロードすることでJupyter Notebookを操作中であると偽装します。

プラグインはたくさん公開されていますので、「auto refresh」などで検索し、お好みのものをインストールしてください。広告表示がなく、無料のままでも制限がないものがオススメです。

https://chrome.google.com/webstore/search/auto%20refresh?hl=ja&_category=extensions
https://chrome.google.com/webstore/detail/tab-auto-refresh/oomoeacogjkolheacgdkkkhbjipaomkn/related?hl=ja

プラグインの設定でリロードを5分に1回に設定し、Colaboratoryのページを開いた状態でタイマーを開始します。ブラウザは閉じずに画面隅の方に置いてください。休憩時や移動時にパソコンがスリープモードになったときにセッション切れを起こすケースがままあります。パソコンのスリープモードはオフにした方がよいでしょう。

手順4. VSCodeにRemote-SSHプラグインのインストール、設定、接続

手順4.1 Remote-SSHプラグインのインストール

VSCodeを立ち上げ、ウインドウ左端のアイコン列の中からExtensionsアイコンをクリックし、「Remote SSH」を検索します。1番上に出てくるMicrosoft製のプラグイン「ms-vscode-remote.remote-ssh」のインストールボタンを押します。似たような名前のプラグインが並びますので、注意して選択してください。

手順4.2 SSH接続先の設定

VSCodeウインドウ左下の緑色の「><」という部分をクリックすると、画面上部中央にメニューが表示されます。その中の上から3つ目付近の「Remote-SSH: Open SSH Configuration File…」を選択します。

続いて、別のメニューが表示され、一番上の「/Users/(ユーザー名)/.ssh/config」を選択します。

エディタが開きますので、手順2.5で出力された値をコピペし、保存します。Colaboratoryと関係ないホストが含まれている場合は、空行を1行追加してから末尾に追加してください。

手順4.3 SSH接続

VSCodeウインドウ左端のアイコン列からRemote Explorerをクリックし、「colab」を右クリックし「Connect to Host in Current Window」をクリックします。もう一つの選択肢「Connect to Host in New Window」でも構いません。

接続に成功すると下記のように表示されます。エラーが大量に出ている画面になった場合は、まずconfigのホスト名やポート番号を確認、次にSSH公開鍵がデフォルトファイル名id_rsa.pubのものを使っているかを確認、Colaboratoryのブラウザを間違って閉じていないかを確認、ブラウザのAuto Refreshが止まってセッションが切れていないかを確認してください。

「Open Folder」という青いボタンを押すと、パスを選択する画面が出てきます。「/workspace」を入力または選択し、EnterキーまたはOKボタンをクリックします。このパス以下に保存したファイルはGoogleドライブと同期保存されるため、Colaboratoryのセッションが切れてもファイルが喪失することはありません。ソースコードなどの消えたら困るファイルはこのパス以下に作成し、コンパイルの中間ファイルなどは他のパスに出力するといいでしょう。
ターミナルの接続ができたら「nvidia-smi」コマンドを実行してみましょう。Colaboratoryの画面と同じようにTesla T4なとが割り当てられていれば成功です。コマンドが見つからないとか、実行エラーなどがあれば、手順2.6の環境変数LD_LIBRARY_PATHが正しく登録されているか確認してください。

手順5. 環境構築コマンドを記述・実行

Clang、Go、Rustのコンパイラなどの巨大なファイルを毎朝コマンド入力でインストールするのは面倒です。そこで、SSHサーバの設定と同時にセットアップするためのシェルスクリプトを作成します。編集はVSCodeで行い、Colaboratoryからシェルスクリプトを呼び出すようにすると、次回以降はSSHの接続作業中にセットアップが終わります。Googleドライブ上にコンパイラ一式を置く方法もありますが、ドライブの同期がすべて完了したかどうかを気にする必要が出てくるため、私はGoogleドライブのフォルダは軽量に維持するようにしています。

ここではworkspaceフォルダの下に好きなフォルダを作成し、その下にシェルスクリプトファイルを作成します。例として「/workspace/.devcontainer/postCreate.sh」というパスに作成し、GoとCMakeをインストールします。インストールするものは開発内容によってまったく異なるでしょうから、参考程度にしてください。ポイントはカレントディレクトリがどこであっても実行できるように記述することです。Colaboratory上で実行する場合は「/content」というフォルダから実行されます。

postCreate.shの中身は以下の通りです。

which cmake
if test "$?" != "0"; then
  if [ ! -e cmake-3.20.1-linux-x86_64.sh ]; then
      wget https://github.com/Kitware/CMake/releases/download/v3.20.1/cmake-3.20.1-linux-x86_64.sh
      chmod +x cmake-3.20.1-linux-x86_64.sh
  fi
  ./cmake-3.20.1-linux-x86_64.sh --skip-license --exclude-subdir --prefix=/usr/local
fi

which go
if test "$?" != "0"; then
  if [ ! -e go1.16.4.linux-amd64.tar.gz ]; then
      wget https://golang.org/dl/go1.16.4.linux-amd64.tar.gz
  fi
  tar -C / -xzf go1.16.4.linux-amd64.tar.gz
  echo 'export PATH=/go/bin:$PATH' >> /root/.bashrc
fi

echo 'End'

Colaboratoryで実行するコードは下記のようになります。実行権限を与えてから実行します。

!chmod +x /workspace/.devcontainer/postCreate.sh
!/workspace/.devcontainer/postCreate.sh

VSCodeのプラグインも毎朝インストールするのは面倒なので、接続時に自動インストールする設定を書きます。.vscode/extensions.jsonというパスに下記の内容を記述すると、VSCodeでこのフォルダを開くときにプラグインがインストール・起動されます。また、VSCodeの設定の中には、Remote SSH接続したすべてのホストに必ずインストールするプラグインを指定する項目があります。いつもほぼ同じプラグインと言語を使用する場合はデフォルトプラグインに登録しておく方が少し楽になります。

{
  "recommendations": [
    "golang.go",
    "ms-vscode.cpptools",
    "eamodio.gitlens",
    "mutantdino.resourcemonitor"
  ]
}

手順6. GitHubの設定

開発をする場合、修正履歴を残し差分を比較したり、他の人と共有するなどのために、ソースコードをGitで管理したくなります。これまでの手順で、ssh-agentの仕組みを使ってローカルの秘密鍵を使用してVSCodeからGitHubに接続する仕組みが整っているはずです。VSCodeからGitHubのprivateリポジトリをcloneしてみてください。何事もなくcloneできれば成功です。もし「git@github.com: Permission denied (publickey).fatal: Could not read from remote repository.」というエラーが出た場合、何かしらの設定が間違っています。GitHubにclone/push/pullしたいからといって、SSHの秘密鍵をリモートホストに保存するのは大変危険ですので、ssh-agentが解決しない場合はあきらめましょう。手順4.2で、SSH接続のconfigファイルに記述した下記の3行がssh-agentの使用に関係しています。

AddKeysToAgent yes
UseKeychain yes
ForwardAgent yes

Googleドライブ上にgit cloneすると、ファイルのパーミッションが異なるため、まったくファイルに手を加えていないにもかかわらずすべてのファイルが更新されているように表示されることがあります。その場合は下記のコマンドを実行してください。この設定により、パーミッションだけが異なる場合は無視されます。

git config core.filemode false

手順7. X Windowの設定

OpenCVのコードを書いている場合、画像表示のためにimshow関数を呼ぶ処理がよく出てきます。単純なSSH接続ではローカルにウインドウ表示をすることができず、エラーで停止してしまいます。1箇所ぐらいならばコメントアウトしても問題ないでしょうが、AI学習の途中経過表示のようなケースではときどき目視で進捗確認したくなります。XQuartzをインストールした状態で、これまで紹介した手順どおり進めると、リモートのウインドウアプリケーションをローカルで表示することができます。下記のPythonファイルを作成し、実行してみてください。ウインドウが表示状態でEnterキーを押すとウインドウが閉じます。

import cv2
img = cv2.imread("/usr/local/cuda-11.0/samples/5_Simulations/nbody/doc/screenshot_sm.jpg")
cv2.imshow("Sample", img)
cv2.waitKey(20000)

注意点として、まず処理がめちゃめちゃ遅いです。よって、頻繁に更新される画像を表示し続けたり、マウス操作で何か作業することは、できなくはありませんが、とてもストレスを感じます。

次にOpenGLを使用するものはこれまでの手順だけでは実行することができません。動画を再生することもできません。ffplayコマンドを実行すると、「X Error of failed request: GLXBadContext」のようなエラーが出るでしょう。これはColaboratoryの問題ではないので、「SSH接続したLinuxサーバでOpenGLアプリを実行する方法」等で検索してみてください。

(TIPS)
OpenGLアプリケーションをウインドウ表示で実行することはできませんが、プログラムをコンパイルすることは可能です。もしただの「make」コマンドでコンパイル自体が通らない場合は、下記のコマンドのようにしてlibGLdispatch.soというライブラリにリンクを貼ると、コンパイルが通る場合があります。

make GLLINK=-lGLdispatch

手順の紹介は以上ですべて終了です。

環境構築に慣れてきたら

環境構築に慣れてきたら、下記のようにColaboratoryの全部のセルをひとまとめにして実行する方が楽になります。Chromeプラグインの5分タイマー起動を忘れがちなので注意してください。

ngrok_authtoken="***PASTE_HERE***"
idrsa_pub="***PASTE_HERE***"
!mkdir -p /root/.ssh
!echo $idrsa_pub > /root/.ssh/authorized_keys

!wget -q -c -nc https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
!unzip -qq -n ngrok-stable-linux-amd64.zip
!rm ngrok-stable-linux-amd64.zip
!./ngrok authtoken $ngrok_authtoken
!pkill -f ngrok
get_ipython().system_raw('./ngrok tcp -region=jp 22 &')
!sleep 2
import urllib.request, json
with urllib.request.urlopen('http://localhost:4040/api/tunnels') as response:
  data = json.loads(response.read().decode())
(host, port) = data['tunnels'][0]['public_url'][6:].split(':')
print(f"""Host colab
  HostName {host}
  Port {port}
  User root
  AddKeysToAgent yes
  UseKeychain yes
  ForwardAgent yes
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
  ForwardX11 yes
  XAuthLocation /usr/X11/bin/xauth
""")

!apt-get install -qq -o=Dpkg::Use-Pty=0 openssh-server x11-apps> /dev/null
!mkdir -p /var/run/sshd
!echo 'X11Forwarding yes' >> /etc/ssh/sshd_config
!echo 'X11UseLocalhost no' >> /etc/ssh/sshd_config
get_ipython().system_raw('/usr/sbin/sshd -D &')

!mkdir -p /content/drive/MyDrive/workspace
!ln -sfn /content/drive/MyDrive/workspace /workspace
!echo 'export PATH=/usr/local/cuda/bin:$PATH' >> /root/.bashrc
!echo 'export LD_LIBRARY_PATH=/usr/lib64-nvidia' >> /root/.bashrc
!echo 'export PROMPT_COMMAND="history -a"' >> /root/.bashrc
!echo 'export HISTFILE=/workspace/.bash_history' >> /root/.bashrc

#!chmod +x /workspace/.devcontainer/postCreate.sh
#!cd /workspace/.devcontainer/ && ./postCreate.sh

GPUアプリのコンパイルと実行

参考までに、NVIDIA GPUがないと動かないCUDAアプリのビルドと実行をしてみましょう。下記のコマンドをVSCodeのターミナルで実行してください。

cd /usr/local/cuda-11.0/samples
make GLLINK=-lGLdispatch

コンパイルには10分ほど時間がかかるので、その間に画像を表示するpythonコードを用意します。ファイル名は/workspace/test.pyとします。中身は以下の通りです。

import cv2
import sys

if len(sys.argv)<2:
  sys.exit(1)
file=sys.argv[1]
img = cv2.imread(file)
cv2.imshow('image',img)
cv2.waitKey(20000)

コンパイルが終わったら、次のコマンドを実行してください。

cd /usr/local/cuda-11.0/samples/bin/x86_64/linux/release
./simpleTexture
python3 /workspace/test.py /usr/local/cuda-11.0/samples/0_Simple/simpleTexture/data/lena_bw_out.pgm 

表示まで5秒くらいかかることもあるので気長にお待ちください。下図のウインドウが表示されたら、Enterキーを押してウインドウを閉じてください。マウスでウインドウを閉じるとハングアップするので注意してください。

Colaboratory Proについて

1日12時間じゃ短い。もっと長い時間連続して使いたい!という方もいると思います。Colaboratory Proを月々1072円(税込)で購入すると、最大24時間まで連続して使用することができます。24時間使用した後、また数時間待てば使える日もありましたが、場合によっては一週間使えない日もありました。毎日使いたい場合は複数アカウントを切り替えて使う必要があります。

まとめ

ColaboratoryのおかげでMacでもNVIDIA GPUを使ったアプリの開発をすることができるようになりました。DeepFakeのような学習処理が可能です。ぜひお試しください。これからも改善していきますので、もっと手軽な構築手順が見つかったらぜひ教えてください。