🚀

NVIDIAドライバを更新せずに新しいCUDAを使う(データセンタ向けGPU限定)

2021/07/13に公開

初めに

NVIDIA製のGPUを使い、CUDAを使ったアプリケーションを開発・利用する際に悩まされるのが「NVIDIAドライバのバージョン」と「CUDAのバージョン」の整合性です。
この整合性問題について、CUDA 10から「Forward Compatible Upgrade」という方法が使えるようになり、端的に言えば「古いNVIDIAドライバで新しいCUDAを使う」ことができるようになりました。

・・・ただし、データセンタ向けGPU(Teslaなど)限定です。GeForceなど、コンシューマ向けのGPUではこの方法は使用できません。

前提知識: NVIDIAドライバ、CUDAのバージョンについて

通常、あるバージョンのCUDA(ランタイム、及びツールキット)を使うためには、指定バージョン以上のNVIDIAドライバが必要です。

公式ページ「CUDA Compatibility :: GPU Deployment and Management Documentation」(以下、CUDA互換性ページ)の「Table 1. CUDA Toolkit and Minimum Required Compatible Driver Versions」を以下に引用します。

上表から、例えば「CUDA 11.3」を使うためには450.80.02以上のNVIDIAドライバが必要なことが分かります。

Linux環境では、使用しているNVIDIAドライバのバージョン、CUDAドライバAPIのバージョンは、nvidia-smiコマンドで確認することができます。

$ nvidia-smi | head -n 4
XXX XXX XX XX:XX:XX 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+

上記の例では、418.67がNVIDIAドライバのバージョン、10.1がCUDAドライバAPIのバージョンです。
「CUDAドライバAPIのバージョン」は、「このNVIDIAドライバで利用可能なCUDAランタイム/ツールキットの最大バージョン」と思っておけば大丈夫です。(実際にインストールされているCUDAツールキットのバージョンではありません)

実際にPATHが通っているCUDAツールキットのバージョンを知りたい場合は、nvcc --versionコマンドを使うことができます。

$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Tue_Jan_10_13:22:03_CST_2017
Cuda compilation tools, release 8.0, V8.0.61

「CUDAツールキットのバージョン」と「CUDAドライバAPIのバージョン」は混同しがちなので注意が必要です。
また、CUDAツールキットは1つの環境に複数インストールすることも多いため、どのバージョンにPATHが通っているかも注意が必要です。

通常のアップグレードパス

通常、新しいバージョンのCUDAツールキット/ランタイムを使用したい場合、NVIDIAドライバを更新(バージョンアップ)します。

CUDA互換性ページの「Figure 2. CUDA Upgrade Path」を以下に引用します。

NVIDIAドライバのパッケージには、カーネルモードドライバのnvidia.koと、ユーザモードライブラリのlibcuda.soなどが含まれており、通常のアップグレードパスでは、この両方を同時に更新します。

ただ、NVIDIAドライバの更新には管理者権限が必要であり、GPUを利用しているプロセスの停止が必要です。また、GPUクラスタなどの場合、複数のマシンについてNVIDIAドライバを更新する必要があるため、それなりの手間が必要です。

新しいアップグレードパス

一方、CUDA 10.0(をサポートするNVIDIAドライバ)からは「Forward Compatible Upgrade」という方法を使えるようになりました。
ただし、この方法をサポートするのは、データセンタ向けGPU、かつLinux版のみです。

CUDA互換性ページの「Figure 3. Forward Compatibility Upgrade Path」を以下に引用します。

上手の通り、カーネルモードドライバのnvidia.ko更新せずに、ユーザモードライブラリのlibcuda.soなどを更新できるようになりました。

この方法では、NVIDIAドライバのEOL(End of Life)までにリリースされたすべてのCUDAバージョンがサポートされます。
例えば、前述のNVIDIAドライバ418.67(R418、LTSB版)の場合、EOLは2022年3月です。

NVIDIAドライバのブランチの種類、EOLについては「NVIDIA Datacenter Drivers :: NVIDIA Tesla Documentation」をご参照ください。

実際の手順

では実際に、古いNVIDIAドライバ環境で新しいCUDAを使ってみましょう。手順は以下の通りです。

  1. NVIDIAドライバをダウンロードする。
  2. NVIDIAドライバを展開(解凍)する。
  3. soファイルのシンボリックリンクを作成する。
  4. LD_LIBRARY_PATH環境変数を設定する。
  5. アプリケーションを実行する。

他にもcuda-compat-*パッケージを用いる方法があるようですが、未検証です。詳しくは、CUDA互換性ページをご参照ください。

1. NVIDIAドライバをダウンロードする

まずは最新版のNVIDIAドライバをダウンロードします。コンシューマ向け(GeForceなど)とデータセンタ向け(Teslaなど)ではバージョンが異なりますので注意が必要です。

今回は以下のページから460.73.01(リリース日: 2021年4月19日)をダウンロードしました。

NVIDIA DRIVERS Data Center Driver for Linux x64

2. NVIDIAドライバを展開(解凍)する

ダウンロードしたNVIDIAドライバ(runファイル)を展開します。インストールは行いません。

通常、runファイルを用いてNVIDIAドライバをインストールする場合、管理者権限、かつオプションなしで実行します。
ですが、今回は展開を行いたいだけなので、一般ユーザ権限、かつ-xオプション付きで実行します。

$ sha1sum NVIDIA-Linux-x86_64-460.73.01.run
86a46deaf64b4d2e13416145a205cc079fc93358  NVIDIA-Linux-x86_64-460.73.01.run

$ chmod 755 NVIDIA-Linux-x86_64-460.73.01.run
$ ./NVIDIA-Linux-x86_64-460.73.01.run -x
Creating directory NVIDIA-Linux-x86_64-460.73.01
Verifying archive integrity... OK
Uncompressing NVIDIA Accelerated Graphics Driver for Linux-x86_64 460.73.01...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

NVIDIA-Linux-x86_64-460.73.01ディレクトリが作成され、そのディレクトリ内にファイルが展開されます。

$ cd NVIDIA-Linux-x86_64-460.73.01
$ ls -l *.so*
-rwxr-xr-x 1 yuya_kato yuya_kato     80224 Apr  2 06:30 libEGL.so.1.1.0
-rwxr-xr-x 1 yuya_kato yuya_kato     26752 Apr  2 06:30 libEGL.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato   1312784 Apr  2 06:33 libEGL_nvidia.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    649320 Apr  2 06:30 libGL.so.1.7.0
-rwxr-xr-x 1 yuya_kato yuya_kato     43104 Apr  2 06:30 libGLESv1_CM.so.1.2.0
-rwxr-xr-x 1 yuya_kato yuya_kato     67880 Apr  2 06:32 libGLESv1_CM_nvidia.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato     79968 Apr  2 06:30 libGLESv2.so.2.1.0
-rwxr-xr-x 1 yuya_kato yuya_kato    117032 Apr  2 06:32 libGLESv2_nvidia.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    137512 Apr  2 06:30 libGLX.so.0
-rwxr-xr-x 1 yuya_kato yuya_kato   1211504 Apr  2 06:31 libGLX_nvidia.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    952472 Apr  2 06:30 libGLdispatch.so.0
-rwxr-xr-x 1 yuya_kato yuya_kato     30856 Apr  2 06:47 libOpenCL.so.1.0.0
-rwxr-xr-x 1 yuya_kato yuya_kato    198752 Apr  2 06:30 libOpenGL.so.0
-rwxr-xr-x 1 yuya_kato yuya_kato  21795104 Apr  2 06:48 libcuda.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato  12680840 Apr  2 06:35 libglxserver_nvidia.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato   5008168 Apr  2 06:33 libnvcuvid.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato     90480 Apr  2 06:30 libnvidia-allocator.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    731648 Apr  2 06:32 libnvidia-cbl.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    221552 Apr  2 06:30 libnvidia-cfg.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato  50412024 Apr  2 06:51 libnvidia-compiler.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato     43680 Apr  2 06:33 libnvidia-egl-wayland.so.1.1.5
-rwxr-xr-x 1 yuya_kato yuya_kato  28473248 Apr  2 06:48 libnvidia-eglcore.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    112664 Apr  2 06:32 libnvidia-encode.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    129760 Apr  2 06:30 libnvidia-fbc.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato  30332968 Apr  2 06:50 libnvidia-glcore.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    635400 Apr  2 06:32 libnvidia-glsi.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato  12494904 Apr  2 06:47 libnvidia-glvkspirv.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato   1367368 Apr  2 06:31 libnvidia-gtk2.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato   1376040 Apr  2 06:31 libnvidia-gtk3.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    219736 Apr  2 06:32 libnvidia-ifr.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato   1770712 Apr  2 06:32 libnvidia-ml.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato   3301608 Apr  2 06:33 libnvidia-ngx.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato  17384136 Apr  2 06:49 libnvidia-opencl.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato     43000 Apr  2 06:32 libnvidia-opticalflow.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato  10521080 Apr  2 06:37 libnvidia-ptxjitcompiler.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato  68126944 Apr  2 06:54 libnvidia-rtcore.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato     18456 Apr  2 06:30 libnvidia-tls.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato 116719176 Apr  2 06:56 libnvoptix.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato    712400 Apr  2 06:30 libvdpau_nvidia.so.460.73.01
-rwxr-xr-x 1 yuya_kato yuya_kato   6373440 Apr  2 06:37 nvidia_drv.so

上記の通り、各種ユーザモードライブラリ(*.so)が含まれています。

3. soファイルのシンボリックリンクを作成する

展開したユーザモードライブラリは、詳細なバージョン番号付きのファイル名(例: libcuda.so.460.73.01)となっています。

このままではファイル名が違って共有ライブラリをロードできないため、ビルド済みのアプリケーションに合わせてシンボリックリンクを作成します。

$ ln -s libcuda.so.460.73.01 libcuda.so
$ ln -s libcuda.so.460.73.01 libcuda.so.1

多くの場合はlibcuda.solibcuda.so.1というファイル名で参照できれば大丈夫と思います。
ここではlibcuda.so.460.73.01に対するシンボリックリンクのみ作成していますが、必要に応じてlddコマンドなどでリンクしている共有ライブラリを確認してください。

4. LD_LIBRARY_PATH環境変数を設定する

ユーザモードライブラリを展開しただけでは、システムは共有ライブラリを探索してくれないため、共有ライブラリのロードパスであるLD_LIBRARY_PATH環境変数を設定します。

具体例を以下に示します。

$ export LD_LIBRARY_PATH=/home/yuya_kato/NVIDIA-Linux-x86_64-460.73.01:${LD_LIBRARY_PATH}

ここではLD_LIBRARY_PATH環境変数を用いていますが、ビルド時のパス設定であるRPATHを使う方法もあります。(未検証)

5. アプリケーションを実行する

上記の状態でアプリケーションを実行することで、新しいCUDAを利用することができます。

具体例: nvidia-smi

nvidia-smiコマンドを使うことで、手軽に動作を確認することができます。

まず、LD_LIBRARY_PATH環境変数を設定していない状態で動作を確認します。

$ nvidia-smi | head -n 4
XXX XXX XX XX:XX:XX 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+

続いて、LD_LIBRARY_PATH環境変数を設定し、同じコマンドを実行します。

$ export LD_LIBRARY_PATH=/home/yuya_kato/NVIDIA-Linux-x86_64-460.73.01:${LD_LIBRARY_PATH}
$ nvidia-smi | head -n 4
XXX XXX XX XX:XX:XX 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+

LD_LIBRARY_PATH環境変数を設定することで、CUDAドライバAPIのバージョンが10.1から11.2に変化していることが確認できます。

具体例: PyTorch v1.9.0(CUDA 11.1)

その他の具体例として、CUDA 11.1を利用するPyTorch v1.9.0の例を示します。

$ export LD_LIBRARY_PATH=/home/yuya_kato/NVIDIA-Linux-x86_64-460.73.01:${LD_LIBRARY_PATH}
$ nvidia-smi | head -n 4
XXX XXX XX XX:XX:XX 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 11.2     |
|-------------------------------+----------------------+----------------------+

$ python3
Python 3.6.13 (default, Jun 29 2021, 17:11:32)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch.__version__
'1.9.0+cu111'
>>> torch.cuda.is_available()
True
>>> torch.cuda.device_count()
1
>>> torch.cuda.get_device_name(0)
'Tesla V100-PCIE-16GB'
>>> device = torch.device("cuda:0")
>>> one = torch.Tensor([1.0]).to(device)
>>> two = torch.Tensor([2.0]).to(device)
>>> one + two
tensor([3.], device='cuda:0')

本来、CUDA 11.1の動作には450.80.02以上のNVIDIAドライバが必要ですが、418.67の環境で動作しています。

LD_LIBRARY_PATH環境変数を設定しない場合(CUDAドライバAPIのバージョンが10.1の場合)、以下のようなエラーメッセージが出力されます。

$ python3
Python 3.6.13 (default, Jun 29 2021, 17:11:32)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch.__version__
'1.9.0+cu111'
>>> torch.cuda.is_available()
/home/yuya_kato/work/pyenv-2.0.1/versions/3.6.13/lib/python3.6/site-packages/torch/cuda/__init__.py:52: UserWarning: CUDA initialization: CUDA driver initialization failed, you might not have a CUDA gpu. (Triggered internally at  /pytorch/c10/cuda/CUDAFunctions.cpp:115.)
  return torch._C._cuda_getDeviceCount() > 0
False

最後に

データセンタ向けGPU限定、かつLinux環境限定ではありますが、「Forward Compatible Upgrade」を使うことで、NVIDIAドライバとCUDAのバージョンの呪縛から幾分開放されました。
さらに、NVIDIA Docker(NVIDIA Container Toolkit)を組み合わせることで、CUDAツールキットのバージョン、Linuxディストリビューションの自由度も高まります。

いつの日か、コンシューマ向けのGPUでもサポートされると良いですね。

参考

Discussion