GPGPU¶
Accélération d'applications sur processeur graphique (GPU)
Documentation¶
-
CUDA
L'API CUDA, qui s'appuie sur du langage C/C++, permet l'accélération de codes de calcul sur des GPU de type NVIDIA : https://developer.nvidia.com/cuda-zone
Sur le système de fichiers de Myria, les répertoires
/soft/cuda_<version>/cuda/doc
et/soft/cuda_<version>/cuda/doc/html
contiennent les documentations de CUDA 8.0 et 9.1 -
CUDA FORTRAN
La version FORTRAN de l'API CUDA a été initialement éditée par PGI.
La documentation utilisateur est disponible au format PDF sur le site de l’éditeur : https://www.pgroup.com/resources/docs/19.7/pdf/pgi19cudaforug.pdf
La dernière version est éditée par NVIDIA : https://docs.nvidia.com/hpc-sdk/compilers/cuda-fortran-prog-guide/index.html
-
Directives OpenACC
Les directives OpenACC (http://www.openacc-standard.org/) constituent un standard de la programmation par directives pour GPU. La norme OpenACC 2.6 est supportée par le compilateur PGI 18.4.
Les développements sont simplifiés par rapport à l'utilisation directe de l'API CUDA. Cette dernière permet parfois d'exploiter plus finement l'architecture d'un GPU. Dans d'autres cas, OpenACC peut être efficace et réduire le rapport temps de développement / performance. OpenACC peut aussi être utile pour aborder un travail de portage : certaines conclusions tirées peuvent être mise en œuvre ultérieurement avec CUDA. -
Directives OpenMP target
Les directives OpenMP target sont supportées notamment par le compilateur "NVIDIA HPC SDK", à partir de sa version 20.11.
Sur Myria, le support de cours
/soft/formations/OpenMP-4to5-ATOS-CRIANN-2020/OpenMP4to5-final.pdf
comprend une partie finale "Data Management for Devices" portant sur ces directives.
Environnement logiciel¶
Les commandes suivantes activent la version voulue de CUDA, du compilateur PGI (pour OpenACC ou CUDA FORTRAN) ou du compilateur NVIDIA (HPC SDK, pour OpenACC, CUDA FORTRAN ou OpenMP target) sur Myria. Pour ces outils, des versions plus récentes que celles mentionnées plus bas peuvent être disponibles (voir module avail
).
Ces commandes doivent être exécutées sur l'une des frontales pour les compilations, et dans les commandes d'un script de soumission sur ressource GPU.
-
CUDA
- Modèles de Makefile :
/soft/makefiles/SERIAL_GPU_CODES
(exemples CUDA et OpenCL),/soft/makefiles/MPI_CUDA_CODES/MakeIntelMPI_CUDA_C++
- Modèles de Makefile :
-
CUDA avec librairie MPI (Open MPI 3.0.1)
login@Myria-1:~ module purge login@Myria-1:~ module load cuda/9.1 mpi/openmpi-3.0.1_cuda-9.1 load cuda 9.1 environment load compilers/gnu/5.5.0 environment load openmpi-3.0.1_cuda-9.1 environment
- Modèle de Makefile pour code CUDA + Open MPI 3.0.1 :
/soft/makefiles/MPI_CUDA_CODES/MakeOpenMPI_CUDA_C++
- Modèle de Makefile pour code CUDA + Open MPI 3.0.1 :
-
OpenACC ou CUDA FORTRAN
- Modèle de Makefile pour CUDA FORTRAN :
/soft/makefiles/MPI_CUDA_CODES/MakeIntelMPI_CUDA_Fortran
- Modèle de Makefile pour OpenACC :
/soft/makefiles/MPI_OpenACC_CODES/MakeIntelMPI_PGI_OpenACC
- Modèle de Makefile pour CUDA FORTRAN :
-
OpenACC avec librairie MPI (Open MPI 3.0.1)
login@Myria-1:~ module purge login@Myria-1:~ module load pgi/18.4 mpi/pgi-18.4_openmpi-3.0.1 load pgi/18.4 environment (OpenACC and CUDA FORTRAN support) load pgi-18.4_openmpi-3.0.1 environment
- Modèle de Makefile pour code OpenACC + Open MPI 3.0.1 :
/soft/makefiles/MPI_OpenACC_CODES/MakeOpenMPI_PGI_OpenACC
- Modèle de Makefile pour code OpenACC + Open MPI 3.0.1 :
-
OpenACC, CUDA FORTRAN ou OpenMP target avec librairie MPI (Open MPI 4.0.5)
login@Myria-1:~ module purge login@Myria-1:~ module load -s mpi/nvhpc-21.7_openmpi-4.0.5-cuda-11.1 login@Myria-1:~ module list Currently Loaded Modulefiles: 1) nvidia-compilers/21.7 2) cuda/11.1 3) mpi/nvhpc-21.7_openmpi-4.0.5-cuda-11.1
- Modèle de Makefile pour code CUDA FORTRAN (options du compilateur NVIDIA) + Open MPI 4.0.5 :
/soft/sample_codes/cuda_fortran/P2DJ_CUF/P2DJ_CUF_Directive_Kernel/Makefile_CudaFortran
- Modèle de Makefile pour code OpenMP target + Open MPI 4.0.5 :
/soft/sample_codes/openmp_target/P2DJ_OpenMP-target/Makefile
- Modèle de Makefile pour code CUDA FORTRAN (options du compilateur NVIDIA) + Open MPI 4.0.5 :
Soumission des calculs¶
Les nœuds de calcul GPU de Myria acceptent les partitions (classes de soumission) gpu_all, gpu_court, gpu_k80, gpu_p100 et gpu_v100.
Un ou plusieurs nœuds de calcul GPU peuvent être dédiés ponctuellement à un utilisateur, sur demande, à des fins de travaux de développement (nécessitant une haute disponibilité de la ressource GPU) : Contacter support@criann.fr.
Pour exécuter un code accéléré sur GPU, il suffit d'ajouter les directives :
# Partition (gpu_k80, gpu_p100 or gpu_v100)
#SBATCH --partition gpu_p100
# GPUs per compute node
#SBATCH --gres gpu:2
et l'activation des environnements :
dans l'un des modèles de script job_serial.sl
, job_OpenMP.sl
, job_MPI(_OpenMP).sl
présents dans /soft/slurm/criann_modeles_scripts
. Le fichier /soft/slurm/criann_modeles_scripts/job_MPI_OpenMP_GPU.sl
fournit l'exemple pour un code MPI / OpenMP / CUDA.
Remarque : dans le cas d’un code MPI accéléré sur GPU, l’application des directives #SBATCH --nodes
et #SBATCH --ntasks-per-node
(à la place de #SBATCH --ntasks
) est utile.
En effet, s’il est souhaité que chaque processus MPI de l’application adresse un GPU différent, il suffit d’appliquer #SBATCH --ntasks-per-node 4
en partition gpu_k80
(car les serveurs visés ont 4 GPUs (2 cartes Kepler K80)) ou #SBATCH --ntasks-per-node 2
en partition gpu_p100
(car les serveurs visés ont 2 GPUs (2 cartes Pascal P100)).
Exemple :
##
# Partition (gpu_k80, gpu_p100 or gpu_v100)
#SBATCH --partition gpu_p100
#
# GPUs per compute node
#SBATCH --gres gpu:2
#
# Compute nodes number
#SBATCH --nodes 3
#
# MPI tasks per compute node
#SBATCH --ntasks-per-node 2
#
# Threads per MPI tasks (if needed)
#SBATCH --cpus-per-task 8
Architecture V100-SXM2¶
Cinq serveurs sont dotés chacun de quatre GPU V100-SXM2 (32 GB de mémoire), deux CPU SkyLake (x 16 cœurs à 2,1 GHz) et 187 GB de RAM DDR4.
Les quatre GPU d'un nœud de calcul sont interconnectés deux à deux par un lien NVLink2 à 100 GB/s de bande passante (2 x 50 GB/s dans chaque direction).
Ces machines possèdent chacune deux interfaces réseau Omni-Path et sont accessibles par la partition gpu_v100
avec Slurm.
CUDA aware MPI¶
La configuration permet l'exécution de codes programmés par approche CUDA aware MPI (appel de fonctions MPI pour des données allouées en mémoire GPU).
Ce type de code peut exploiter le NVLink pour les échanges MPI intra-nœud.
Les échanges (cuda aware) MPI inter-nœud bénéficient eux aussi d'un accès direct de mémoire GPU à mémoire GPU, par le réseau rapide (technologie GPUDirect RDMA supportée par Omni-Path).
Environnements¶
-
Open MPI 4.0.5, CUDA 11.1 et gcc 7.3.0
-
ou : Open MPI 4.0.2, CUDA 10.1 et icc 17.1
-
ou : Open MPI 4.0.5, CUDA 11.1 et PGI 20.7 (pour OpenACC (C, C++, FORTRAN) ou CUDA FORTRAN)
-
ou : Open MPI 4.0.5, CUDA 11.1 et nvhpc 21.7 (pour OpenACC (C, C++, FORTRAN), CUDA FORTRAN ou les directives Open MP target pour GPU)
Programmation¶
Dans une approche CUDA+MPI, une tâche MPI doit être affectée à un GPU par des fonctions appropriées : cudaSetDevice()
pour CUDA ou acc_set_device_num()
avec OpenACC.
Pour CUDA aware MPI avec Omni-Path (réseau d'interconnexion de Myria), cette association processus parallèle / GPU doit être effectuée avant l'appel à MPI_Init()
.
Les éléments de programmation pour effectuer cette association sont fournis par l'IDRIS (pour le cas de Slurm, batch de Myria également), pour CUDA (C) ou OpenACC :
http://www.idris.fr/eng/jean-zay/gpu/jean-zay-gpu-mpi-cuda-aware-gpudirect-eng.html
Dans le cas d'utilisation de CUDA FORTRAN, cette association doit se faire par le code suivant :
subroutine init_device()
USE cudafor
implicit none
character(len=6) :: local_rank_env
integer :: local_rank_env_status, local_rank, istat
call get_environment_variable (name="SLURM_LOCALID", &
value=local_rank_env, status=local_rank_env_status)
if (local_rank_env_status == 0) then
read(local_rank_env, *) local_rank
istat = cudaSetDevice(local_rank)
else
print *, "Slurm batch system must be used"
STOP 1
end if
end subroutine init_device
Dans le cas d'utilisation des directives OpenMP target, avec le compilateur nvidia (module mpi/nvhpc
indiqué ci-dessus), cette association doit se faire par le code suivant :
subroutine init_device()
USE cudafor
USE omp_lib
implicit none
character(len=6) :: local_rank_env
integer :: local_rank_env_status, local_rank, istat
call get_environment_variable (name="SLURM_LOCALID", &
value=local_rank_env, status=local_rank_env_status)
if (local_rank_env_status == 0) then
read(local_rank_env, *) local_rank
istat = cudaSetDevice(local_rank)
call omp_set_default_device(local_rank)
else
print *, "Slurm batch system must be used"
STOP 1
end if
end subroutine init_device
Exemples didactiques de programmes¶
CUDA FORTRAN¶
/soft/sample_codes/cuda_fortran/P2DJ_CUF/
sur Myria
Cet exemple de code utilise l'API CUDA FORTRAN. Il alloue certaines données en mémoire hôte non paginée (mot clé PINNED
dans l'instruction FORTRAN déclarant le tableau). Ces données sont alors transférées de manière optimisée pour la performance, entre le GPU et l'hôte par la fonction cudaMemcpyAsync()
. Les allocations de données en mémoire GPU se font par le mot clé DEVICE
dans l'instruction FORTRAN déclarant le tableau. Ce programme appelle MPI de hôte à hôte. Le code est décliné en deux variantes :
- Rédaction des kernels CUDA sous forme classique (analogue à CUDA C) :
/soft/sample_codes/cuda_fortran/P2DJ_CUF/P2DJ_CUF_Classical_Kernel/
-
Rédaction des kernels CUDA à l'aide de la directive
!$cuf kernel do
de NVIDIA :/soft/sample_codes/cuda_fortran/P2DJ_CUF/P2DJ_CUF_Directive_Kernel/
Cette approche combine la convivialité des directives pour les kernels, et les fonctions CUDA pour les transferts optimisés de données entre hôte et GPU. L'exemple emploie la forme asynchrone de la directive
!$cuf kernel do
(qui met en jeu la notion de stream de CUDA).
Directives OpenMP target¶
/soft/sample_codes/openmp_target/P2DJ_OpenMP-target
sur Myria
Cet exemple de code utilise les directives OpenMP target en FORTRAN (!$omp target
).
Il effectue des communications MPI de GPU à GPU (cuda aware MPI).
L'association entre tâche MPI et GPU est faite avant l'appel à MPI_Init()
, par le sous-programme init_device()
(également rédigé ci-dessus).