Ubuntu Server で稼働する GPU 搭載マシンにドライバを組み込んで「AI 用計算機」にする

# 本記事の前提

1. Ubuntu Server 21.04 がセットアップされている
2. NVIDIA 製の GPU がマシンに搭載されている

# 事前確認

そもそも何の GPU が挿さっているのか知らなかったので、まずはそれの確認からです。

```
$ lspci | grep -i nvidia
01:00.0 VGA compatible controller: NVIDIA Corporation TU117 [GeForce GTX 1650] (rev a1)
01:00.1 Audio device: NVIDIA Corporation Device 10fa (rev a1)
```

NVIDIA の GeForce GTX 1650 でした。
１年前に買ったマシンなのですが、本記事の執筆時点でも最新のゲームが遊べる程度にはしっかりとした性能のようでした。正直しらんかった（無関心）

次に、起動ログを確認し「nouveau（ヌーヴォー）」が暫定ドライバとして読み込まれていることを確認します。

```
$ sudo dmesg | grep -i nouveau
[    1.517791] fb0: switching to nouveaufb from EFI VGA
[    1.520475] nouveau 0000:01:00.0: vgaarb: deactivate vga console
[    1.520537] nouveau 0000:01:00.0: NVIDIA TU117 (167000a1)
...（後略）
```

確かに、nouveau が GPU ドライバとして初期インストールされています。

ちなみに、[nouveau を Wikipedia で調べる](https://ja.wikipedia.org/wiki/Nouveau)と**「現在は NVIDIA のプロプライエタリな Linux 用ドライバをリバースエンジニアリングして、 NVIDIA のビデオカード用のフリーなドライバを開発することを狙いとしている」**と書いてあって、OSS 開発の天才たちが、儲かってる巨大企業をグーで殴りにいくアベンジャーズ感がすごくてステキでした。いいぞ！もっとやれ！！

……まあ、とはいえ、普通に NVIDIA の純正ドライバを入れるんですけどね。

次に、```ubuntu-drivers``` で搭載している GPU の推奨ドライバを確認します。

```
$ ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias : pci:v000010DEd00001F82sv0000103Csd00008558bc03sc00i00
vendor   : NVIDIA Corporation
model    : TU117 [GeForce GTX 1650]
driver   : nvidia-driver-418-server - distro non-free
driver   : nvidia-driver-450 - distro non-free
driver   : nvidia-driver-450-server - distro non-free
driver   : nvidia-driver-460 - distro non-free recommended
driver   : nvidia-driver-460-server - distro non-free
driver   : xserver-xorg-video-nouveau - distro free builtin
```

推奨ドライバが「nvidia-driver-460」であることが確認できました。

ちなみに、このコマンド用のパッケージ ```ubuntu-drivers-common``` は、Ubuntu Server には初期インストールされていないので、apt でインストールしておきましょう。

# ドライバのインストールと読み込み確認

一応、apt のレポジトリを更新し、現在マシンにインストールされている全部のパッケージを最新版にしておきます。

```
$ sudo apt-get update
$ sudo apt-get upgrade
```

で、インストールなのですが、私の環境では、次のコマンドで入りました。

```
$ sudo ubuntu-drivers autoinstall
```

ものすごい勢いでさまざまなパッケージがインストールされていくので、端末がログまみれになるのをコーヒーでも飲みながら眺めましょう。

さて、デバイスドライバを組み込んだので、まずはマシンを再起動します。
忘れがちですが必須です。

```
$ sudo reboot
```

# インストール後の確認

再起動後、きちんとドライバが読み込まれていることを確認します。

まずは、nouveau ではなく、NVIDIA 純正ドライバが起動時に読み込まれていることを確認します。

```
$ sudo dmesg | grep -i nouveau
（結果なし）

$ sudo dmesg | grep -i nvidia
...（前略）
[    6.420835] nvidia: loading out-of-tree module taints kernel.
[    6.420843] nvidia: module license 'NVIDIA' taints kernel.
[    6.437721] nvidia-nvlink: Nvlink Core is being initialized, major device number 511
[    6.438240] nvidia 0000:01:00.0: vgaarb: changed VGA decodes: olddecodes=io+mem,decodes=none:owns=io+mem
[    6.481330] NVRM: loading NVIDIA UNIX x86_64 Kernel Module  460.73.01  Thu Apr  1 21:40:36 UTC 2021
[    6.499844] nvidia-modeset: Loading NVIDIA Kernel Mode Setting Driver for UNIX platforms  460.73.01  Thu Apr  1 21:32:31 UTC 2021
...（後略）
```

次に、```$ lsmod | grep -i nvidia``` でドライバが読み込まれていることを確認します。

```
$ lsmod | grep -i nvidia
nvidia_uvm           1032192  0
nvidia_drm             57344  3
nvidia_modeset       1228800  5 nvidia_drm
nvidia              34131968  191 nvidia_uvm,nvidia_modeset
drm_kms_helper        245760  1 nvidia_drm
drm                   552960  6 drm_kms_helper,nvidia_drm
```

また、組み込まれたドライバの種類も確認しておきましょう。

```
$ ubuntu-drivers list
nvidia-driver-450-server, (kernel modules provided by linux-modules-nvidia-450-server-generic)
nvidia-driver-460, (kernel modules provided by linux-modules-nvidia-460-generic)
nvidia-driver-418-server, (kernel modules provided by linux-modules-nvidia-418-server-generic)
nvidia-driver-460-server, (kernel modules provided by linux-modules-nvidia-460-server-generic)
nvidia-driver-450, (kernel modules provided by linux-modules-nvidia-450-generic)
```

（460 以外にも 450, 418 が組み込まれている理由は分からない…）

最後に、```nvidia-smi``` で次のような結果が返ってくることを確認します。

```
$ nvidia-smi
Mon May 10 04:13:41 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.73.01    Driver Version: 460.73.01    CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1650    Off  | 00000000:01:00.0  On |                  N/A |
| 30%   32C    P8     4W /  75W |     81MiB /  3889MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A      1249      G   /usr/lib/xorg/Xorg                 71MiB |
|    0   N/A  N/A      1476      G   /usr/bin/gnome-shell                7MiB |
+-----------------------------------------------------------------------------+
```

# CUDA コンパイラドライバのインストール

NVIDIA がバイナリパッケージを配布してくれているおかげで、apt 一発でインストールされました。

```
$ sudo apt-get install nvidia-cuda-toolkit
```

きちんとインストールされていることを確認します。

```
$ nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2021 NVIDIA Corporation
Built on Sun_Feb_14_21:12:58_PST_2021
Cuda compilation tools, release 11.2, V11.2.152
Build cuda_11.2.r11.2/compiler.29618528_0
```

やっぱ NVIDIA 神っすよ。さっきはグーで殴るとか言ってごめんね。

# 実行時間の確認

GPU は本当に使えるようになったのか？ 
数値計算の速度は上がっているのか？

その謎を探るべく我々はアマゾンの奥地に……は向かいませんが、とりあえず次のプログラムを動かしてみることにしました。

```python:inverse.py
import numpy as np
import cupy as cp
import time

N = [1000, 5000, 8000, 10000]

for n in N:

    start = time.time()
    W_cpu = 2 * np.random.rand(n, n) -1
    inv_W_cpu = np.linalg.inv(W_cpu)
    
    print ("CPU: elapsed_time:{0}".format(time.time() - start) + "[sec]")
    
    start = time.time()
    W_gpu = 2 * cp.random.rand(n, n) -1
    inv_W_gpu = cp.linalg.inv(W_gpu)
    
    print ("GPU: elapsed_time:{0}".format(time.time() - start) + "[sec]")
```

つまり、1000, 5000, 8000, 10000 次元における逆行列の計算時間を、CPU と GPU で比較しようということです。

結果は次のとおりでした（単位は秒、有効数字は小数点以下３桁）

|  | CPU | GPU |
|:-:|:-:|:-:|
| 1000 | 0.029 | 0.645 |
| 5000  |  1.730 | 0.990 |
| 8000  | 6.521 | 3.546 |
| 10000  | 12.150 | 7.500 |

1000 次元程度では、CPU と GPU でデータをやり取りするオーバヘッドが大きいせいか、逆に遅くなっていますが、次元が大きくなるにつれて GPU の方が速くなっています。

また、10000 次元の逆行列の計算中に ```$ nvidia-smi``` を実行すると、プロセス名に「python3」が表示されます。

```
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.73.01    Driver Version: 460.73.01    CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1650    Off  | 00000000:01:00.0 Off |                  N/A |
| 30%   42C    P0    36W /  75W |   3588MiB /  3889MiB |    100%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A      2057      G   /usr/lib/xorg/Xorg                134MiB |
|    0   N/A  N/A      2192      G   /usr/bin/gnome-shell                6MiB |
|    0   N/A  N/A      7534      C   python3                          3443MiB |
+-----------------------------------------------------------------------------+
```

温度も消費電力も消費メモリも上がっているので、動いてるっぽいですね。

ちなみに、[このプログラムはパッケージ管理まわりも含めて GitHub で公開している](https://github.com/hajime-f/inverse)ので、次のコマンドを叩いて使ってみてください。

```
$ make install
$ make
```

# メモリが足りねえ

10000 次元の逆行列の計算中、GPU 計算ではメモリが足りなくてプログラムが停止することが何回かありました。この GeForce GTX 1650 には 3889 MiB ＝ 約 4 GB のメモリしか載っておらず、数値計算のキャッシュが追いつかないためと考えられます。

本当に実用的な数値計算に使えるのか？
実際の現場で「AI 用計算機」としてどれほどパフォーマンスを発揮できるのか？

その謎を探るべく我々はアマゾンの奥地に……は向かいませんが、とりあえずユニファイドメモリの導入などを検討しましょう。

それにしても、10000 次元の逆行列が、10 秒程度で計算できるようになるとは……恐ろしい時代が来たもんですね。そりゃディープラーニングも流行るわけだ。

# デバッグ？

## Error: no integrated GPU detected

私の環境では、```$ sudo ubuntu-drivers autoinstall``` すると、最後に次のようなエラーメッセージが出ました。

```
Error: no integrated GPU detected.
```

「統合された GPU が見つからないよ」というエラーなのですが、おそらく（あくまで推測ですが）、GPU が１枚しか挿さっていないため表示されたエラーだと思います。

つまり、「１枚しか挿さってないから複数枚を統合させて動作するわけじゃないよ」と。なので、Error というより Warning かなと思ってます。

実際、インストール後の確認結果は、上記のように動いてるっぽい結果が出力されるので、まあ、たぶん、、、いいのかなと。もしこのあたりに認識違いがあれば、エロい人に教えて欲しいです。

## FileNotFoundError: [Errno 2] No such file or directory: 'aplay'

上記のとおり ```$ ubuntu-drivers list``` を実行すると、このエラーが出ました。

```
$ ubuntu-drivers list
ERROR:root:could not open aplay -l
Traceback (most recent call last):
  File "/usr/share/ubuntu-drivers-common/detect/sl-modem.py", line 35, in detect
    aplay = subprocess.Popen(
  File "/usr/lib/python3.9/subprocess.py", line 951, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.9/subprocess.py", line 1821, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'aplay
nvidia-driver-450-server, (kernel modules provided by linux-modules-nvidia-450-server-generic)
... （後略）
```

おそらく、サウンド関連のデバイスドライバがうまくいってないせいで出ているのかなと推測してます。サーバ用途でマシンを使うので、サウンドなんぞいらんから無視しました。

# （補足） 謎の「Automatic suspend」をオフにする

これは本記事の趣旨とは外れるのですが、、、（というより、自分用のメモ）

NVIDIA のドライバと依存関係にある GUI 関連のパッケージもインストールされるので、マシンを再起動させた時点で、Ubuntu Server で簡易なデスクトップ環境が使えるようになります。

この GUI を利用して、Ubuntu Server の自動サスペンドをオフにしておきましょう。これをしなければ、サーバなのに 20 分操作がなかった時点で、本体ボタンを押さないとスリープ解除されないという意味のわからないことになります。

![IMG_5045.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667942/a33d102c-707f-fe0e-5780-cafe6a1ab5bb.png)

ディスプレイを直接カメラ撮影した雑な画像ですが……上記項目の「Automatic Suspend」を「Off」にしておきます。