Jean Zay : MPI CUDA-aware et GPUDirect

Pour une performance optimale, des bibliothèques OpenMPI CUDA-aware supportant le GPUDirect sont disponibles sur Jean Zay.

Ces bibliothèques MPI permettent d'effectuer des communications utilisant des buffers d'envoi et de réception alloués sur la mémoire du GPU. Grâce au support de GPUDirect, ces transferts se font directement de GPU à GPU sans recopie intermédiaire sur la mémoire du CPU, lorsque c'est possible.

Compilation du code

Il est nécessaire de compiler le code en utilisant l'une des bibliothèques OpenMPI CUDA-aware disponibles sur Jean Zay.

Après avoir chargé le compilateur que vous souhaitez utiliser, vous devez charger l'un des modules suivants :

$ module avail openmpi/*-cuda
------------- /gpfslocalsup/pub/modules-idris-env4/modulefiles/linux-rhel8-skylake_avx512 -------------
openmpi/3.1.4-cuda openmpi/3.1.6-cuda openmpi/4.0.2-cuda openmpi/4.0.4-cuda
 
$ module load openmpi/4.0.4-cuda

Si OpenMPI n'est pas disponible pour le compilateur désiré, un message d'erreur sera affiché. N'hésitez pas à contacter l'assistance pour demander une nouvelle installation si nécessaire.

Pour connaître la liste des compilateurs pour lesquels une version donnée d'OpenMPI est disponible, vous pouvez utiliser la commande module show openmpi/<version>. Par exemple :

$ module show openmpi/4.0.2-cuda
------------------------------------------------------------------
/gpfslocalsup/pub/modules-idris-env4/modulefiles/linux-rhel8-skylake_avx512/openmpi/4.0.2-cuda:
 
module-whatis   {An open source Message Passing Interface implementation.}
prereq          intel-compilers/19.0.4 pgi/20.1 pgi/19.10 gcc/10.1.0 gcc/8.3.1
conflict        openmpi
conflict        intel-mpi
 
Available software environment(s):
- intel-compilers/19.0.4
- pgi/20.1
- pgi/19.10
- gcc/10.1.0
- gcc/8.3.1
 
If you want to use this module with another software environment,
please contact the support team.
-------------------------------------------------------------------

La compilation se fait en utilisant les wrappers d'OpenMPI :

$ mpifort source.f90
 
$ mpicc source.c
 
$ mpic++ source.C

Aucune option particulière n'est nécessaire pour la compilation, vous pouvez vous référer à la rubrique Compilation GPU de l'index pour plus d'information sur la compilation des codes utilisant les GPU.

Adaptation du code

L'utilisation de la fonctionnalité MPI CUDA-aware GPUDirect sur Jean Zay impose de respecter un ordre d'initialisation bien précis pour CUDA ou OpenACC et MPI dans le code :

  1. initialisation de CUDA ou OpenACC
  2. choix du GPU que chaque processus MPI doit utiliser (étape de binding)
  3. initialisation de MPI.

Attention : des plantages aléatoires peuvent survenir si cette contrainte sur l'initialisation n'est pas respectée.

Une légère adaptation de votre code peut donc être nécessaire pour pouvoir profiter de cette fonctionnalité sur Jean Zay.

Vous trouverez ci-dessous un exemple de sous-routine en Fortran et en C permettant d'initialiser OpenACC avant d'initialiser MPI ainsi qu'un exemple CUDA.

Attention : cet exemple ne fonctionne que dans le cas où on alloue exactement un processus MPI par GPU.

Exemple OpenACC

Version Fortran

init_acc.f90
#ifdef _OPENACC
subroutine initialisation_openacc
 
    USE openacc
 
    character(len=6) :: local_rank_env
    integer          :: local_rank_env_status, local_rank
 
    ! Initialisation d'OpenACC
    !$acc init
 
    ! Récupération du rang local du processus via la variable d'environnement
    ! positionnée par Slurm, l'utilisation de MPI_Comm_rank n'étant pas encore
    ! possible puisque cette routine est utilisée AVANT l'initialisation de MPI
    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
        ! Définition du GPU à utiliser via OpenACC
        call acc_set_device_num(local_rank, acc_get_device_type())
    else
        print *, "Erreur : impossible de déterminer le rang local du processus"
        stop 1
    end if
end subroutine initialisation_openacc
#endif

Exemple d'utilisation :

init_acc_mpi.f90
! On initialise OpenACC...
#ifdef _OPENACC
  call initialisation_openacc
#endif
! ... avant d'initialiser MPI
  call mpi_init(code)

Version C

init_acc.c
#ifdef _OPENACC
void initialisation_openacc()
{
    char* local_rank_env;
    int local_rank;
 
    /* Initialisation d'OpenACC */
    #pragma acc init
 
    /* Récupération du rang local du processus via la variable d'environnement
       positionnée par Slurm, l'utilisation de MPI_Comm_rank n'étant pas encore
       possible puisque cette routine est utilisée AVANT l'initialisation de MPI */
    local_rank_env = getenv("SLURM_LOCALID");
 
    if (local_rank_env) {
        local_rank = atoi(local_rank_env);
        /* Définition du GPU à utiliser via OpenACC */
        acc_set_device_num(local_rank, acc_get_device_type());
    } else {
        printf("Erreur : impossible de déterminer le rang local du processus\n");
        exit(1);
    }
}
#endif

Exemple d'utilisation :

init_acc_mpi.c
#ifdef _OPENACC
/* On initialise OpenACC... */
initialisation_openacc();
#endif
/* ... avant d'initialiser MPI */
MPI_Init(&argc, &argv);

Exemple CUDA

init_cuda.c
#include <cuda.h>
#include <cuda_runtime.h>
 
void initialisation_cuda()
{
    char* local_rank_env;
    int local_rank;
    cudaError_t cudaRet;
 
     /* Récupération du rang local du processus via la variable d'environnement
        positionnée par Slurm, l'utilisation de MPI_Comm_rank n'étant pas encore
        possible puisque cette routine est utilisée AVANT l'initialisation de MPI */
    local_rank_env = getenv("SLURM_LOCALID");
 
    if (local_rank_env) {
        local_rank = atoi(local_rank_env);
        /* Définition du GPU à utiliser pour chaque processus MPI */
        cudaRet = cudaSetDevice(local_rank);
        if(cudaRet != CUDA_SUCCESS) {
            printf("Erreur: cudaSetDevice a échoué\n");
            exit(1);
        }
    } else {
        printf("Erreur : impossible de déterminer le rang local du processus\n");
        exit(1);
    }
}

Exemple d'utilisation :

init_cuda_mpi.c
/* On initialise CUDA... */
initialisation_cuda();
/* ... avant d'initialiser MPI */
MPI_Init(&argc, &argv);

Exécution du code

Lors de l'exécution, vous devez vous assurez de charger avec la commande module la même bibliothèque MPI que celle qui a été utilisée pour la compilation du code, puis de bien utiliser la commande srun pour démarrer celui-ci.

Le support de CUDA-aware et GPUDirect est alors activé par défaut sans opération supplémentaire.

Pour plus d'informations sur la soumission des travaux multi-GPU utilisant MPI, consultez la page exécution d'un travail multi-GPU MPI CUDA-aware et GPUDirect en batch.