NVIDIA HPC SDKの利用方法

概要

NVIDIA HPC SDK(以下HPC SDK)はNVIDIA社が提供する開発環境(コンパイラやライブラリなどをまとめたもの)です。


玄界にインストールされているもの以外に、 公式Webページから自分でダウンロードしてきたものをインストールして利用することも可能です。 より新しいコンパイラやライブラリなどを試したい場合は各自で最新版をインストールしてご利用ください。 (システムへのインストールを行わなければ使えないプログラムやライブラリも含まれていることがあります。)


HPC SDKの詳細な情報、最新ドキュメントは NVIDIAのWebサイト を確認してください。


利用準備

HPC SDKを利用するには、あらかじめmoduleをloadしておく必要があります。 HPC SDKに含まれるコンパイラやライブラリを用いて作成したプログラムをジョブとして実行する際は、 ジョブスクリプト内でも同じmoduleのloadが必要です。


はじめに nvidia モジュール(nvidia/23.9モジュール)をloadしてください。


MPIを使う場合はさらに nvhpcx または nvompi モジュールもloadする必要があります。 ( nvidia モジュールをloadするとavailで見えるようになります。) 特にこだわりが無い場合は nvhpcx/23.9-cuda12 の利用を推奨します。

[ku40000105@genkai0002 ~]$ module avail
----------------------------------------- /home/modules/modulefiles/LN/core ------------------------------------------
cuda/11.8.0           gcc-toolset/12  intel/2023.2           nvidia/23.9(default)
cuda/12.2.2(default)  gcc/8(default)  intel/2024.1(default)

----------------------------------------- /home/modules/modulefiles/LN/util ------------------------------------------
avs/express85(default)     julia/1.10.3(default)            mathematica/14.0(default)  molpro/2024.1.0_sockets
fieldview/2023(default)    jupyter_notebook/7.2.1(default)  matlab/R2024a(default)     nastran/2024.1(default)
gaussian/16.C.01(default)  marc/2024.1(default)             molpro/2024.1.0_mpipr      singularity-ce/4.1.3(default)
[ku40000105@genkai0002 ~]$ module load nvidia
[ku40000105@genkai0002 ~]$ module avail
--------------------------------- /home/modules/modulefiles/LN/compiler/nvidia/23.9 ----------------------------------
nvhpcx/23.9  nvhpcx/23.9-cuda12  nvompi/23.9

----------------------------------------- /home/modules/modulefiles/LN/core ------------------------------------------
cuda/11.8.0           gcc-toolset/12  intel/2023.2           nvidia/23.9(default)
cuda/12.2.2(default)  gcc/8(default)  intel/2024.1(default)

----------------------------------------- /home/modules/modulefiles/LN/util ------------------------------------------
avs/express85(default)     julia/1.10.3(default)            mathematica/14.0(default)  molpro/2024.1.0_sockets
fieldview/2023(default)    jupyter_notebook/7.2.1(default)  matlab/R2024a(default)     nastran/2024.1(default)
gaussian/16.C.01(default)  marc/2024.1(default)             molpro/2024.1.0_mpipr      singularity-ce/4.1.3(default)
[ku40000105@genkai0002 ~]$

コンパイラの利用方法

HPC SDKにはCPUやGPUに対応した複数のコンパイラが含まれています。 以下ではそれらの基本的な使い方を示します。


コンパイラの種類

HPC SDKの提供するコンパイラと対応する並列化手法の対応は以下の通りです。

  • nvc/nvc++ : C/C++コンパイラ。CPU向けにもGPU向けにも利用可能。OpenMPやOpenACCに対応。
  • nvfortran : Fortranコンパイラ。CPU向けにもGPU向けにも利用可能。OpenMPやOpenACCに加えて、CUDA Fortranにも対応。
  • nvcc : CUDA Toolkitに含まれているものと同じ。(HPC SDKの中に特定バージョンのCUDA Toolkitも同梱されている。)

コンパイルに利用するコマンドはMPIの有無により以下に対応します。 nvccについてはCUDAの利用方法のページを参照してください。


プログラミング言語 コンパイルコマンド
非MPI並列 Fortran nvfortran
C nvc
C++ nvc++
MPI並列 Fortran mpifort
C mpicc
C++ mpic++

主な共通のコンパイルオプションとしては以下が利用できます。 詳細はコンパイルオプションに -help を指定すると確認できます。


コンパイル時オプション 説明
-c オプジェクトファイルの作成までを行います。(実行可能ファイルの作成を行いません。)
-o filename 出力される実行可能ファイル/オブジェクトファイル名をfilenameに変更します。デフォルトの実行ファイル名は a.out です。
-O コンパイラが行う最適化の度合いを指定します。最適化を基本的に行わない-O0から、数字が大きくなるほど最適化対象が増加する-O1, -O2, -O3, -O4の他、-O-Ofastもあります。特に-O3以上は対象プログラムによっては効果がなかったり逆効果だったりするとも言われています。さらに、基本的な(多くの場合に高速となる)最適化オプションとして -fast も用意されています。各最適化オプションの詳細は-helpで確認してください。
-acc OpenACC指示文を有効にします。
-mp OpenMP指示文を有効にします。GPUを対象とする場合は-mp=gpuと指定してください。
-gpu OpenACCやOpenMPを用いる場合に対象GPUの詳細な指定を追加できます。debug, pinned, managed, deepcopy, fastmathなど様々なオプションとその組み合わせが利用可能です。H100 GPU向けコードのみの生成を行わせる場合はcc90 (-gpu=cc90)を組み合わせてください。
-tp 最適化対象とするCPUを指定します。玄界では-tp=sapphirerapids(Intel Xeon SapphireRapids)または-tp=native(コンパイル環境と同じCPU)を推奨します。
-Minfo コンパイラが最適化を行う際に詳細情報を出力させることができます。-Minfo=allを指定すれば全てのメッセージが出力されます。GPU化に関する情報だけ確認したい場合は-Minfo=accelが推奨です。

以下にいくつかのコンパイル例を示します。


  • OpenMPプログラムをコンパイルする(CPU向け、非MPI版)
$ nvc -Minfo -fast -mp -tp=native -o openmp1_c openmp1.c
$ nvc++ -Minfo -fast -mp -tp=native -o openmp1_cpp openmp1.cpp
$ nvfortran -Minfo -fast -mp -tp=native -o openmp1_f openmp1.f90
  • OpenMPプログラムをコンパイルする(GPU向け、非MPI版)
$ nvc -Minfo -fast -mp=gpu -gpu=pinned,cc90 -tp=native -o openmp2_c openmp2.c
$ nvc++ -Minfo -fast -mp=gpu -gpu=pinned,cc90 -tp=native -o openmp2_cpp openmp2.cpp
$ nvfortran -Minfo -fast -mp=gpu -gpu=pinned,cc90 -tp=native -oopenmp2_f openmp2.f90
  • OpenACCプログラムをコンパイルする(GPU向け、非MPI版)
$ nvc -Minfo -fast -acc -gpu=pinned,cc90 -tp=native -o openacc1_c openacc1.c
$ nvc++ -Minfo -fast -acc -gpu=pinned,cc90 -tp=native -o openacc1_cpp openacc1.cpp
$ nvfortran -Minfo -fast -acc -gpu=pinned,cc90 -tp=native -o openacc1_f openacc1.f90
  • MPI + OpenMPプログラムをコンパイルする(CPU向け)
$ mpicc -Minfo -fast -tp=native -o mpi_openmp1_c mpi_openmp1.c
$ mpic++ -Minfo -fast -tp=native -o mpi_openmp1_cpp mpi_openmp1.cpp
$ mpifortran -Minfo -fast -tp=native -o mpi_openmp1_f mpi_openmp1.f90
  • MPI + OpenMPプログラムをコンパイルする(GPU向け)
$ mpicc -Minfo -fast -mp=gpu -gpu=pinned,cc90 -tp=native -o mpi_openmp2_c mpi_openmp2.c
$ mpic++ -Minfo -fast -mp=gpu -gpu=pinned,cc90 -tp=native -o mpi_openmp2_cpp mpi_openmp2.cpp
$ mpifortran -Minfo -fast -mp=gpu -gpu=pinned,cc90 -tp=native -o mpi_openmp2_f mpi_openmp2.f90
  • MPI + OpenACCプログラムをコンパイルする(GPU向け)
$ mpicc -Minfo -fast -acc -gpu=pinned,cc90 -tp=native -o mpi_openacc1_c mpi_openacc1.c
$ mpic++ -Minfo -fast -acc -gpu=pinned,cc90 -tp=native -o mpi_openacc1_cpp mpi_openacc1.cpp
$ mpifortran -Minfo -fast -acc -gpu=pinned,cc90 -tp=native -o mpi_openacc1_f mpi_openacc1.f90

バッチジョブの実行方法

HPC SDKを用いたプログラムを実行するバッチジョブスクリプトの例を示します。


OpenMPを用いたCPUプログラム(ノード内CPU実行の例)

$ cat job_openmp1.sh
#!/bin/bash
#PJM -L rscgrp=a-batch
#PJM -L elapse=10:00
#PJM -L vnode-core=4
#PJM -j

module load nvidia/23.9
export OMP_NUM_THREADS=4
./a.out

ノードグループAの4CPUコアを用いたOpenMP並列プログラムの実行例です。 OMP_NUM_THREADS 環境変数で実行スレッド数を指定します。 割り当てられるコア数を超えない範囲でスレッド数を指定することを強く推奨します。


OpenACCプログラムまたはOpenMPを用いたGPUプログラム(1サブGPU実行の例)

$ cat job_openacc1.sh
#!/bin/bash
#PJM -L rscgrp=b-batch-mig
#PJM -L elapse=10:00
#PJM -L gpu=1
#PJM -j

module load nvidia/23.9
./a.out

ノードグループBの1サブGPUを用いた、OpenACCまたはOpenMPによるGPU並列化プログラムの実行例です。


OpenACC実行時は環境変数NVCOMPILER_ACC_NOTIFYNVCOMPILER_ACC_TIMEをセットしておくことで、OpenACCによりGPUを用いた計算が行われていることを簡単に確認できます。 OpenMPにはそのような環境変数がないため、プロファイラなど(後述)で確認すると良いでしょう。


MPI + OpenMPを用いたCPUプログラム(1ノード内8プロセス実行の例)

$ cat job_mpi_openmp1.sh
#!/bin/bash
#PJM -L rscgrp=a-batch
#PJM -L elapse=10:00
#PJM -L node=1
#PJM -j

module load nvidia/23.9
module load nvhpcx/23.9
export OMP_NUM_THREADS=15
mpirun -n 8 --map-by ppr:1:numa --bind-to numa numactl -l ./mpi_openmp.out

ノードグループAの1ノードを全て使ったMPI+OpenMPハイブリッド並列化プログラムの実行例です。 ノードグループAは60コアCPUが2ソケット搭載されており、各ソケットのコアとメモリは4つのサブNUMAに分かれているため、各サブNUMAに1プロセス配置され、プロセスあたり15スレッド実行されるようにしました。


最適なプロセスとスレッドの割り当て方については別途解説ページを作成予定です。


MPI + OpenMPを用いたCPUプログラム(2ノード×1ノード内8プロセス実行の例)

$ cat job_mpi_openmp2.sh
#!/bin/bash
#PJM -L rscgrp=a-batch
#PJM -L elapse=10:00
#PJM -L node=2
#PJM -j

module load nvidia/23.9
module load nvhpcx/23.9
export OMP_NUM_THREADS=15
mpirun -n 16 --map-by ppr:1:numa --bind-to numa numactl -l ./mpi_openmp.out

上述した1ノード実行用のスクリプトは、 -L node=1で指定している必要ノード数と、 mpirun-nで指定している総プロセス数を適切に増やせば、 複数ノード実行へ簡単に拡張できます。 この記述方法では、ノードに必要なだけのプロセスを配置してから、 次のノードへプロセスを配置します。 (はじめに各ノードに1プロセス目を配置、次に各ノードに2プロセス目を配置、 という動作にはなりません。)


MPI + OpenACCまたはOpenMPを用いたGPUプログラム(1ノード内4GPU実行の例)

複数のGPUを利用したプログラムとその実行にはいくつかの方法がありますが、 最もわかりやすく使いやすいと思われるのが 「OpenACCやOpenMPで1GPUを操作するプログラムをMPIにより並列化する。 1MPIプロセスと1GPUを対応づける。」という考え方です。 ノードグループBの各ノードは2基のCPUと4基のGPUを搭載しており、 各CPUは2つのサブNUMAに分かれていることから、 1プロセスあたり1サブNUMAと1GPUを担当するような割り当てにするのが適切です。 ノードグループCの各ノードは2基のCPUと8基のGPUを搭載しており、 各CPUは4つのサブNUMAに分かれていることから、 やはり1プロセスあたり1サブNUMAと1GPUを担当するような割り当てにするのが適切です。 (サブNUMAと対応しているCPUコアの数がノードグループによって異なる点に注意が必要です。)


以下はノードグループBの1ノードに搭載された4GPUを使ったジョブ実行の例です。 job_openacc2.shでは、PJMオプションでは1ノードをフルに使ったジョブの設定(node=1)を行い、mpirunではサブNUMAあたり1プロセス、 合計4プロセスを起動しています。 13行目では、nvidia-smi -LでGPUのIDを出力し、利用するGPUの特定に必要な情報のみをテキストファイルに書き出しています。 run2.shではOpen MPIのランク番号から通し番号(ID)を生成し、 それを元に利用するGPUを選択してプログラムを起動しています。 これによりMPIプロセスごとに別のGPUが使えるようになります。 なおrun2.shには実行権限が付与されている必要があります。 (例 chmod u+x ./run2.sh


(gpu.txtというファイルに情報を書き込んで参照している都合上、同じディレクトリで複数のプログラム(ジョブ)を同時に実行すると問題が起きます。 必要に応じてジョブごとにサブディレクトリを作成して実行するなどの工夫をしてください。)

$ cat -n job_openacc2.sh
 1  #!/bin/bash
 2  #PJM -L rscgrp=b-batch
 3  #PJM -L elapse=10:00
 4  #PJM -L node=1
 5  #PJM -j
 6  #PJM -S
 7
 8  numactl -s
 9  numactl -H
10
11  module load nvidia/23.9
12  module load nvhpcx/23.9
13  nvidia-smi -L | grep GPU | awk '{print $6}' | sed 's/)//' 2>&1 | tee gpu.txt
14  mpirun -n 4 --map-by ppr:1:numa --bind-to numa numactl -l ./run2.sh ./mpi_openacc.out
$ cat -n run2.sh
 1  #!/bin/bash
 2  ID=$(( ${OMPI_COMM_WORLD_LOCAL_RANK} + 1 ))
 3  GPU=`head -n ${ID} ./gpu.txt | tail -n 1`
 4  export CUDA_VISIBLE_DEVICES=${GPU}
 5  $@

ジョブスクリプト冒頭のノード数設定とmpirunのプロセス数設定(-nオプション)を 適切に設定すれば、複数ノードでも同様に実行できます。


ノード内の一部のGPUのみを使いたい場合は、-L node=1の部分を-L gpu=2のように 利用するGPU数に変更し、mpirunのプロセス設定も適切に設定してください。


MIGを用いた(複数)サブGPUプログラム

ノードグループBに搭載されたH100 GPUはMIG機能により最大7つのサブGPUに分割して利用することができます。 玄界ではいくつかノードのGPUをMIG機能により7つのサブGPUに分割し、4GPU合計で28サブGPUが見える状態にしています。 分割されたGPUは演算性能が低くメモリ容量が小さなGPUとなりますが、機能としては普通に利用できますので、 デバッグ用などに有効に活用してください。


なお1サブGPUあたりのポイントの消費具合はフルGPUの1/7になります。 サブGPUの理論演算性能やメモリ量はフルGPUの1/7よりも若干低い点には注意してください。


サブGPUを使ってプログラムを実行する際は、リソースグループとしてb-batch-migb-inter-migを指定してください。 gpu=で指定した数量はフルGPUではなくサブGPUの数となります。 1ノードあたり28サブGPUありますので、最大で28を指定することができます。 ただしこれらのリソースグループでは複数のサブGPUを要求した際にそれらが同一の物理GPU上に割り当たる保証がありません。 ある程度は同一の物理GPUを確保しようとしますが、 資源の空き状況によってはGPU数に7以下を指定しても異なる物理GPU上のサブGPUが確保されてしまうことがあります。


以下に4サブGPUを用いたインタラクティブジョブにて異なる物理GPU上のサブGPUが割り当たった例を示します。 この例ではCPUコアは同じソケット側のみが割り当たっていますが、ノードの利用状況によってはソケットにまたがって割り当たる可能性もあります。

[ku40000105@genkai0002 ~]$ pjsub --interact -L rscgrp=b-inter-mig,elapse=10:00,gpu=4
[INFO] PJM 0000 pjsub Job 84169 submitted.
[INFO] PJM 0081 .connected.
[INFO] PJM 0082 pjsub Interactive job 84169 started.
[ku40000105@b0036 ~]$ nvidia-smi -L
GPU 0: NVIDIA H100 (UUID: GPU-c2b4960d-78ab-1043-5478-8454fa020a11)
  MIG 1g.12gb     Device  0: (UUID: MIG-835ebdbf-f00e-52b2-9adc-37cc5051f230)
  MIG 1g.12gb     Device  1: (UUID: MIG-ba0d9ecb-6ecb-5f02-b6ed-93253b38dc50)
  MIG 1g.12gb     Device  2: (UUID: MIG-0e23b49e-b8ea-5c54-bc08-54d94d7480ea)
GPU 1: NVIDIA H100 (UUID: GPU-7721b910-c226-83a7-13a2-b4951ea9af7c)
  MIG 1g.12gb     Device  0: (UUID: MIG-4e9bed12-d011-5bf4-9a59-3f5fa51fdb22)
[ku40000105@b0036 ~]$ numactl -s
policy: default
preferred node: current
physcpubind: 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
cpubind: 1
nodebind: 1
membind: 0 1

ライブラリの利用方法

HPC SDKには様々なライブラリが含まれています。


Version 23.9に含まれているライブラリ

  • cuBLAS
  • cuBLASMp
  • cuTENSOR
  • cuSOLVER
  • cuSOLVERMp
  • cuFFT
  • cuFFTMp
  • cuRAND
  • NCCL
  • NVSHMEM

これらを利用する際は、必要に応じてヘッダファイルの参照やコンパイル時のオプション追加が必要です。 一部の例を紹介しますが、詳細は公式ドキュメントやサンプル等を参照してください。


cuBLASを使う例

CUDA CからcuBLASを使う場合は、cublas_v2.hのincludeとcublasのリンク(nvccコンパイラに-lcublasオプションを追加)が必要です。 C言語 + OpenACC/OpenMP(GPU)からcuBLASを使う場合も同様に、cublas_v2.hのincludeとnvcコンパイラに対する-lcublasオプションの追加が必要ですが、 これだけでは-lcublasでリンクするライブラリファイル(libcublas.so)を見つけられずにエラーが発生します。 解決のためには、cuda moduleもloadするか、HPC SDKディレクトリ内にあるlibcublas.soのパスを-Lオプションで与える必要があります。 (-L/home/app/nvhpc/23.9/Linux_x86_64/23.9/math_libs/lib64/ を指定してください。)


CUDA FortranからcuBLASを使うには、use cublasの記述とcublasのリンク(nvfortranコンパイラに-cudalib=cublasオプションを追加)が必要です。 Fortran + OpenACC/OpenMP(GPU)からcuBLASを使う場合も同様です。


その他のツールの利用方法

HPC SDKには上記の他にもデバッガやプロファイラなどいくつかのツールが含まれています。 ここでは多くの玄界ユーザにとって便利であると思われるデバッガやプロファイラの使い方を簡単に紹介します。


デバッガの使い方

GPUカーネルのデバッグにはcuda-gdbが使用できます。 gdbとほぼ同様の使い方でGPUカーネルのデバッグが行えます。


cuda-gdbを使う際は、まずデバッガ向けのオプションを付けてプログラムをコンパイルします。 nvcやnvfortranを使う場合は-g-goptを (-gは最適化が無効化される、-goptは最適化が無効化されない)、 nvccを使う場合は-g-G-gはホストコード向け、-Gはデバイスコード向けの デバッグ情報生成有効化オプション)を指定します。

# 計算ノード上で実行(インタラクティブジョブでの利用が想定される)
$ nvc -Minfo -gopt -acc -gpu=pinned,cc90 -tp=native -o openacc1_c openacc1.c
# cuda-gdbを実行
$ cuda-gdb ./openacc1_c
# cuda-gdb上でプログラムを実行開始
(cuda-gdb) run
# 実行され、プログラムに問題があれば情報が出力される

cuda-gdbではスレッドごとのトレースなどGPU向けの機能がサポートされています。 詳細はドキュメントをご確認ください。


プロファイラの使い方

パフォーマンス分析ツール(プロファイラ)としてNVIDIA Nsight SystemsやNVIDIA Nsight Computeが利用できます。 Nsight Systemsはプログラム全体の性能、Nsight ComputeはGPUカーネルの詳細な把握に役立ちます。


以下では基本的な使い方のみを紹介します。 詳細はマニュアル等(Nsight SystemsNsight Compute)を参照してください。

Nsight Systemsの使い方

情報の収集はnsysプログラムで行います。


実行例(ジョブスクリプト内)

$ nsys profile -o out_${PJM_JOBID} --stats=true ./a.out

-oオプションでプロファイル結果を書き出すファイル名を指定します。 上記の例ではバッチジョブシステムが各ジョブに設定するジョブID情報を元にファイル名を決めています。 実際にはout_82448.nsys-repのようなファイルが作られます。


生成されたnsys-repファイルを Nsight Systems (GUIプログラム)で開くとプログラムの情報を閲覧できます。 Nsight Systemsはログインノード上でもnsys-uiコマンドで起動可能です(X転送が必要です)が、 Windows、Linux、macOS向けのクライアントが配布されているため、 nsys-repファイルをダウンロードして手元のPCで閲覧した方が快適に使えることが多いでしょう。


--stats=trueは必須ではないオプションです。 このオプションを付けておくとプログラム実行終了時にプロファイル結果がテキスト表示されます。 対象となる情報が多いとそれだけジョブ実行時間が伸びる点には注意してください。

Nsight Computeの使い方

情報の収集はncuプログラムで行います。


実行例(ジョブスクリプト内)

$ ncu -o out_${PJM_JOBID} ./a.out

-oオプションでプロファイル結果を書き出すファイル名を指定します。 上記の例ではバッチジョブシステムが各ジョブに設定するジョブID情報を元にファイル名を決めています。 実際にはout_82449.ncu-repのようなファイルが作られます。


生成されたncu-repファイルを Nsight Compute (GUIプログラム)で開くとプログラムの情報を閲覧できます。 Nsight Computeはログインノード上でもncu-uiコマンドで起動可能です(X転送が必要です)が、 Windows、Linux、macOS向けのクライアントが配布されているため、 ncu-repファイルをダウンロードして手元のPCで閲覧した方が快適に使えることが多いでしょう。


ログインノード上でncu-uiを実行する際は、さらに以下の設定が必要です。 ulimit -v 26000000 (仮想メモリの上限を上げています。エラーメッセージがでて終了してしまう場合は、28000000など、より大きな値を試してみてください。)