WSL2でGPGPU

# 概要
本稿では，[WSL2](https://learn.microsoft.com/ja-jp/windows/wsl/install)にGPGPU環境を整備する方法について解説しようと思います。そのような情報はすでに各所でわかりやすく解説されているため([NVIDIAのサイト](https://docs.nvidia.com/cuda/wsl-user-guide/index.html), [このQiitaの記事](https://qiita.com/ksasaki/items/ee864abd74f95fea1efa)など) 今さら付け加えることはないかもしれませんが，備忘録もかねて書いておこうと考えました。おおよそ次のような内容になります。
- CUDA環境をインストールする方法
- NVIDIAの開発環境をインストールし，環境設定を行う方法
- [以前作成した試作プログラム]((https://qiita.com/jkoga/items/00ff29a3ac00df495831))の実行

WSL2およびWSL2上のLinuxディストリビューションはインストールされていることを想定して説明していきます。また，NVIDIA製のGPUが搭載されていることを想定して説明をしていきます。さらに，WSL2上にインストールされているLinuxディストリビューションはUbuntuであることを想定します。なお，最新のUbuntuには今回説明する作業に必要なライブラリーが標準リポジトリにまだ登録されておらず，一部の作業が実行できないようです。以下はUbuntu 22.04で行った作業になります。

# CUDA環境のインストール
まずは[このサイト](https://www.nvidia.com/Download/index.aspx)から利用しているGPUに適したWindows用ドライバーをダウンロードし，インストールします。サイトの説明にもありますが，インストールする必要のあるドライバーはこれのみで，Linux用のドライバーなどを別途インストールする必要はありません。私のマシンにはGeForce GTX 1650 superというモデルのGPUが搭載されているので，Product TypeはGeForce, Product SeriesはGeForce 16 Series, ProductはGeForce 1650 super, Operating Systemは (Windows11を用いているので) Windows11, Download TypeはGame Ready Driver (GRD) を選んで"Search"をクリックするとインストーラーをダウンロードする画面に遷移するので，そこからダウンロードしました。選択肢の最後のDownload Typeはよく分からないのですが，ウェブサイトには Game Ready Driverにするように書いてあったのでそうしました。ドライバーのインストール自体はインストーラーを実行し，指示に従えば行うことができます。

続いてCUDA環境のインストールを行っていきます。以下の説明はほぼ上述のウェブサイトからそのまま引き写したものです。

まずは古いGPGキーを削除しておきます。
```
sudo apt-key del 7fa2af80
```
[このサイト](https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=WSL-Ubuntu&target_version=2.0&target_type=deb_local)の説明にしたがってWSL2用のCUDAをインストールします。このサイトでは環境に合わせて選択肢を選択すると実行すべきコマンドが現れますが，この際DistributionとしてWSL-Ubuntuを選ぶようにしてください。以下のように実行すべきコマンドが表示されます（バージョン番号などは実行した日時に応じて変わるかもしれません）。
```
wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin
sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/12.4.1/local_installers/cuda-repo-wsl-ubuntu-12-4-local_12.4.1-1_amd64.deb
sudo dpkg -i cuda-repo-wsl-ubuntu-12-4-local_12.4.1-1_amd64.deb
sudo cp /var/cuda-repo-wsl-ubuntu-12-4-local/cuda-*-keyring.gpg /usr/share/keyrings/
sudo apt-get update
sudo apt-get -y install cuda-toolkit-12-4
```
WSL2を起動し，このコマンド群をそのままコピーペーストすれば問題なくインストールできるはずです。

# NVIDIA HPCSDKのインストール
つぎに，開発環境として[NVIDIA HPSDK](https://developer.nvidia.com/hpc-sdk)をインストールします。ウェブサイトのDownload Nowを選ぶとライセンスに同意するかどうかをうながされるのでI accept the license agreementにチェックを入れます。するとDistributionを選ぶ画面が現れるので，ここでLinux x86_64 Linux (apt)を選びましょう。以下のようなコマンドが表示されます。
```
$ curl https://developer.download.nvidia.com/hpc-sdk/ubuntu/DEB-GPG-KEY-NVIDIA-HPC-SDK | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-hpcsdk-archive-keyring.gpg
$ echo 'deb [signed-by=/usr/share/keyrings/nvidia-hpcsdk-archive-keyring.gpg] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | sudo tee /etc/apt/sources.list.d/nvhpc.list
$ sudo apt-get update -y
$ sudo apt-get install -y nvhpc-24-3
```
これらをコピーペーストし，逐次実行するだけでNVIDIA HPSDKをインストールすることができるはずです。インストール後，環境変数`LD_LIBRARY_PATH`に`/usr/lib/wsl/lib`を加えないとうまくGPUを認識してくれないようなので加えるようにしてください。
```
export LD_LIBRARY_PATH=/usr/lib/wsl/lib:$LD_LIBRARY_PATH
```

NVIDIA HPCSDKの環境設定は[Environment Modules](https://modules.readthedocs.io/en/latest/)を用いて行うので，Environment Modulesもインストールします。通常通り`apt`コマンドを用いてインストールします。Environment ModulesはTclを必要とするので，そのインストールコマンドもあわせて記します。
```
sudo apt install tcl
sudo apt install environment-modules
```

Environment ModulesにNVIDIA HPCSDKの`modulefiles`を登録するため，以下のようなコマンドを実行します。
```
. /etc/profile.d/modules.sh
module use --append /opt/nvidia/hpc_sdk/modulefiles
```

`module avail`コマンドによって確認し，`module load`コマンドによって環境設定をしましょう。
```
$ module avail
------------------------------- /usr/share/modules/modulefiles -------------------------------
dot  module-git  module-info  modules  null  use.own

------------------------------- /usr/share/modules/modulefiles -------------------------------
dot  module-git  module-info  modules  null  use.own

------------------------------ /opt/nvidia/hpc_sdk/modulefiles -------------------------------
nvhpc-byo-compiler/24.3  nvhpc-hpcx/24.3   nvhpc-openmpi3/24.3
nvhpc-hpcx-cuda12/24.3   nvhpc-nompi/24.3  nvhpc/24.3
$ module load nvhpc
$ which nvcc
/opt/nvidia/hpc_sdk/Linux_x86_64/24.3/compilers/bin/nvcc
```
以上の手続きによってNVIDIA HPCSDKが利用可能な状態になります。

# インストール検証
ここでCUDAとHPCSDKのインストールが正しくできたことを検証することが推奨されます。`module load`コマンドで環境が設定できたら，以下の要領でサンプルを適当な場所にコピーしましょう。
```
cp $NVHPC_ROOT/examples .
```
非常に多くの例題が用意されています。たとえば`CUDA-Libraries/cuBLAS`には後述の試作プログラムにおいても利用されているcuBLASのテストを行う例題が用意されています。このディレクトリーには`Makefile`が用意されており，`make`とするとテストの実行の仕方が表示されます。
```
$ make
To build and run a specific example, do the following:

make ftn_test3       : OpenACC FTN with cublasXt host calls
make ftn_test4       : OpenACC FTN calls within data regions
make c_testxt        : OpenACC C with cublasXt host calls
make c_test1         : OpenACC C calls within data regions
make cpp_testxt      : OpenACC C++ with host calls
make cpp_test2       : OpenACC C++ calls within data regions
make ftn_test3_omp   : OpenMP FTN calls within data regions
make ftn_testxt_omp  : OpenMP FTN calls with cublasXt host calls
make c_testxt_omp    : OpenMP C with cublasXt host calls
make c_test1_omp     : OpenMP C calls within data regions
make cpp_testxt_omp  : OpenMP C++ with host calls
make cpp_test2_omp   : OpenMP C++ calls within data regions

make all             : all of the above tests
```
たとえば`make all` としてすべてのテストを実行すると以下のような結果が得られるはずです。
```
$ make all
cd test_blas_oacc_ftn; make TEST=tblas3; make clean
make[1]: Entering directory '/home/ubuntu/examples/CUDA-Libraries/cuBLAS/test_blas_oacc_ftn'
nvfortran -fast -acc=gpu -cudalib=cublas -o tblas3.exe tblas3.f90
./tblas3.exe
 Test PASSED
    2000.000        2000.000
 Test PASSED
    2000.000000000000         2000.000000000000
 Test PASSED
 (2000.000,0.000000)  (2000.000,0.000000)
 Test PASSED
 (2000.000000000000,0.000000000000000)  (2000.000000000000,0.000000000000000)
 ...
 ...
```
このような出力が得られれば無事インストールは成功していると考えられます。

# 試作プログラムの実行
[この記事で紹介した](https://qiita.com/jkoga/items/00ff29a3ac00df495831)試作プログラムを用いてベンチマークをとってみます。試作プログラムにおいては著者が実際に遭遇したことのある処理のパターンを簡略化し，GPGPU化したものです。大きな行列を細切れにし，Level3 BLASを用いて行列-行列積の計算を実行します。

実行環境は下記の通り。
- CPU : Core i7-12700 プロセッサー
- GPU : GeForce GTX 1650 super

用いるCPUのperformance-coresの数は8なので，CPU実行は8スレッド並列で実行しました。スレッド並列数は環境変数`OMP_NUM_THREADS`によって制御することができます。
```
export OMP_NUM_THREADS=8
```
また，特にGPUは単精度と倍精度の性能差が大きいので，もともと倍精度だった試作プログラムを単精度でも実行できるように手を加え，その比較を行いました。実行条件としては[この記事](https://qiita.com/jkoga/items/00ff29a3ac00df495831)の検証 1.の条件を採用しました。ループを計25回まわし，一回あたりの計算時間を計測しました。結果は以下の通り（時間の単位は秒）

 |CPU単精度|CPU倍精度|GPU単精度|GPU倍精度|
 |-|-|-|-|
 |1.368|2.809|0.185|3.685|

CPU, GPU双方において当然のことながら単精度の演算の方が高速ですが，CPUは倍程度の違いであるのに対し，GPUは20倍くらいの差がついています。[GeForce GTX 1650 superの倍精度演算の理論性能は単精度の1/32](https://www.techpowerup.com/gpu-specs/geforce-gtx-1650-super.c3411)なので，これでも低速化の具合は抑えられている方です。演算と関係ないCPUとGPUの通信分が理論値との差なのではないかと思います。CPU 8並列の結果と比較すると倍精度はCPUの方が高速ですが，単精度は7倍ほどGPUの方が高速です。アプリケーションの種類によっては単精度で十分な場合があり，そのようなケースにおいてはグラフィックス処理用の（数値計算向きではなく安価な）GPUも数値計算に活用できそうです。

 # 終わりに
GPGPU環境を手軽に手に入れる方法として，WSL2にCUDA環境を構築する方法を紹介しました。GPGPUに限らず数値計算を行う環境としてはLinuxが主流ですが，Linuxマシンをあらたに用意することは手間や費用の関係で難しい場合があるかもしれません。そのような場合にWSL2を活用して環境を構築することができるのは，有用なのではないかと著者は考えています。