VASP acelerado por GPU

Empieza hoy mismo con esta Guía para aplicaciones preparadas para GPU.

VASP

VASP (Vienna Ab Initio Simulation Package) es una aplicación de cálculos del primer principio de la estructura electrónica en escala atómica mediante la aproximación de soluciones a la ecuación de Schrödinger. Esta aplicación implementa la teoría del funcional de la densidad estándar, además de funciones modernas como funcionales híbridos, métodos de la función de Green (GW y ACFDT-RPA) y la teoría de perturbaciones MP2 (de Møller-Plesset de segundo orden).

VASP funciona hasta 10 veces más rápido utilizando GPU NVIDIA Tesla P100 en comparación con los sistemas de una sola CPU, lo que permite el uso de métodos más precisos y más exigentes computacionalmente al mismo tiempo.

VASP funciona hasta 10 veces más rápido en GPU

Instalación

Requisitos del sistema

VASP se distribuye como código fuente y tiene un par de dependencias de tiempo de compilación y de tiempo de ejecución. A efectos de esta guía, se da por hecho que los siguientes paquetes de software ya están instalados en tu sistema Linux y que las respectivas variables de entorno están configuradas:

  1. Intel Compiler Suite (especialmente, Fortran, C/C++ y MKL)
  2. Intel MPI
  3. NVIDIA CUDA 8.0

En la mayoría de los casos, estos paquetes han sido instalados en tu superordenador por los administradores y se pueden cargar mediante un sistema de módulos. Instalar los paquetes mencionados está más allá del ámbito de esta guía; si necesitas ayuda, ponte en contacto con el equipo de soporte técnico del clúster.

La última revisión de la versión para GPU de VASP se puede compilar con el paquete compilador de PGI (hay una edición para la comunidad gratuita). Muchos usuarios de VASP usan el compilador de Intel, así que nos ceñiremos a él para este tutorial.

Descarga y compilación

DESCARGA DE LAS FUENTES REQUERIDAS

VASP es un software comercial y, como titular de una licencia, puedes descargar la versión para GPU más actualizada. Consulta esta página para comprar una licencia. Escribe las credenciales de inicio de sesión en “Community Portal” (portal de la comunidad) y haz clic en “Login” (iniciar sesión) para acceder a la sección de descargas. Haz clic en “VASP5” y selecciona la carpeta “src”. En el momento de escribir este documento, tienes que descargar los siguientes archivos:

vasp.5.4.4.tar.gz

Asegúrate de comprobar el sitio de VASP habitualmente para obtener los últimos parches o versiones nuevas.

EXTRACCIÓN E INSTALACIÓN DE PARCHES

Primero, extrae el código fuente de VASP que acabas de descargar:

tar xfz vasp.5.4.4.tar.gz

Ahora, cambia al directorio extraído que se acaba de crear y que contiene las fuentes y, después, instala los parches:

cd vasp.5.4.4

El archivo Make de VASP necesita algunas modificaciones para reflejar tu entorno de software local. VASP contiene una selección de plantillas del archivo Make para diferentes configuraciones, las cuales están en la subcarpeta arch/. Copia un archivo makefile.include de la carpeta arch/ (en esta guía, usamos el compilador de Fortran de Intel y NVIDIA CUDA en Linux):

cp arch/makefile.include.linux_intel makefile.include

Si tienes que adaptar el archivo makefile.include, consulta la sección Solución de problemas al final del documento.

La mayoría de las opciones del archivo makefile.include están preparadas para su uso al detectar los valores necesarios de las variables de entorno, pero es muy recomendable configurar la variable GENCODE_ARCH para tus GPU en el archivo que acabas de copiar. Comprueba las capacidades de computación (CC) de tus tarjetas GPU y edita el archivo makefile.include con un editor (por ejemplo: nano, vim o emacs están disponibles de forma predeterminada en la mayoría de los sistemas):

nano makefile.include

Utilizamos tarjetas NVIDIA P100, por lo que la capacidad de computación 6.0 para la compilación ofrece el mejor rendimiento. Por lo tanto, no es necesario cambiar la línea GENCODE_ARCH que de forma predeterminada es de esta manera:

GENCODE_ARCH  := -gencode=arch=compute_30,code=\"sm_30,compute_30\" \
-gencode=arch=compute_35,code=\"sm_35,compute_35\" \
-gencode=arch=compute_60,code=\"sm_60,compute_60\"

No usar modificadores de capacidades de computación (por ejemplo, “3,5”) no solo no supone ningún problema, sino que además permite la ejecución del binario resultante en otras arquitecturas de GPU. Si la GPU de destino tiene una capacidad de computación diferente, asegúrate de adaptar la línea en consecuencia. Por ejemplo, si quieres que el destino sea también una tarjeta V100, cámbiala para que sea así (y use CUDA 9):

GENCODE_ARCH  := -gencode=arch=compute_30,code=\"sm_30,compute_30\" \
-gencode=arch=compute_35,code=\"sm_35,compute_35\" \
-gencode=arch=compute_60,code=\"sm_60,compute_60\" \
-gencode=arch=compute_70,code=\"sm_70,compute_70\"

Ahora compila la versión para GPU de VASP al ejecutar (asegúrate de que no agregas -j, ya que VASP no es compatible con compilación en paralelo):

make gpu

Si la compilación se completa correctamente, deberías tener la versión acelerada por GPU de VASP en bin/vasp-gpu. Comprueba si el binario está ahí con

ls -l bin/vasp-gpu

Si se producen errores, consulta la sección “Adaptación de variables de compilación” en la sección Solución de problemas.

Si la compilación se completa correctamente, es el momento de compilar la versión para GPU de VASP que permite cálculos no colineales (cuando aparece LNONCOLLINEAR=.TRUE. o LSORBIT=.TRUE. en INCAR) con gpu_ncl. Ten en cuenta que el punto Γ de VASP todavía no es compatible en las GPU.

 

De la misma manera, puedes compilar las versiones de una sola CPU (std, ncl, gam):

make gpu_ncl std ncl gam

Esto te dará la siguiente lista de binarios; pero, para este tutorial, solo se usará vasp_gpu y, de manera opcional, vasp_std:

Tabla 1. Resumen de los diferentes archivos ejecutables compilados para VASP.

vasp_std Versión predeterminada de VASP
vasp_ncl Versión especial necesaria para ejecutar cálculos con LNONCOLLINEAR=.TRUE. o LSORBIT=.TRUE. en INCAR
vasp_gam Versión especial que ahorra memoria y cálculos, solo para cálculos en Γ.
vasp_gpu Igual que vasp_std, pero con aceleración de GPU
vasp_gpu_ncl Igual que vasp_ncl, pero con aceleración de GPU

Recomendamos instalar los binarios de VASP fuera del directorio de compilación (por ejemplo, en ~/bin) para evitar sobrescribirlos por error con futuras versiones:

mkdir -p ~/bin
cp bin/vasp* ~/bin

INSTALACIÓN DE LA BASE DE DATOS POTCAR

VASP depende del uso de datos tabulados para suavizar todas las funciones de ondas de electrones, a menudo llamadas pseudopotenciales. Puedes descargar estos pseudopotenciales.

Escribe las credenciales de inicio de sesión en “Community Portal” y haz clic en “Login” para acceder a la sección de descargas. Después, haz clic en “Potentials” y empieza por “LDA”. Descarga todos los archivos disponibles y sigue el mismo procedimiento con las carpetas “PBE” y “PW91”.

Se mostrará el siguiente conjunto de archivos:

  1. potpaw_PBE.tgz
  2. potpaw_PBE.54.tar.gz
  3. potpaw_PBE.52.tar.gz
  4. potpaw_LDA.tgz
  5. potpaw_LDA.54.tar.gz
  6. potpaw_LDA.52.tar.gz
  7. potpaw_GGA.tar.gz
  8.  potUSPP_LDA.tar.gz
  9. potUSPP_GGA.tar.gz

Extráelos con el script: extractPOTCARs.sh.

Todos los scripts que aparecen en este tutorial están disponibles para su descarga. También puedes clonar el repositorio directamente a tu sistema de archivos con este comando:

Ejecución de trabajos

Primer cálculo de VASP acelerado por GPU

Existen algunas opciones en el archivo de control principal INCAR que necesitan atención especial para la versión para GPU de VASP. GPU VASP mostrará mensajes de error y advertencia si los ajustes en el archivo INCAR no son compatibles o recomendables.

¡No ignores los mensajes relacionados con la GPU y actúa en consecuencia! Esta sección explica los ajustes INCAR relevantes para la GPU.

Limítate a usar las siguientes opciones para modificador ALGO:

  1. Normal
  2. Fast
  3. Veryfast

Hay otros algoritmos disponibles en VASP que no han sido probados de forma exhaustiva y no se garantiza que funcionen o incluso pueden generar resultados incorrectos. Además, tienes que usar los ajustes a continuación en el archivo INCAR:

  1. LREAL = .TRUE. or LREAL = A
  2. NCORE = 1

Para empezar, ofrecemos algunos cálculos de ejemplo que utilizaremos más tarde para mostrar cómo alcanzar un rendimiento mejorado en comparación a configuraciones sencillas. Puedes encontrar algunos archivos de entrada de ejemplo en el repositorio Git. Ve al directorio adecuado y echa un vistazo al archivo INCAR; verás que está configurado según opciones mencionadas antes:

cd gpu-vasp-files/benchmarks

Por motivos de derechos de autor, debes generar los archivos POTCAR por tu cuenta. Se da por hecho que has descargado y extraído la base de datos de pseudopotenciales como se muestra, y se utiliza ~/vasp/potcars/ como el directorio de su ubicación. En los archivos de entrada de ejemplo, se incluye un script que realiza la generación de forma automática, pero necesita conocer la ruta a la base de datos POTCAR:

cd siHugeShort
bash generatePOTCAR.sh ~/vasp/potcars

Después, ya estará todo listo para iniciar el primer cálculo de VASP acelerado por GPU:

~/bin/vasp_gpu

Esto iniciará un proceso que usará solo una GPU y un núcleo de la CPU, independientemente de cuántos haya disponible en el sistema. Ejecutarlo de esta manera puede que requiera mucho tiempo, pero muestra que todo funciona correctamente. Para confirmar que se usa activamente la GPU, escribe lo siguiente:

nvidia-smi -l

en un terminal conectado al mismo nodo en el que se ejecute el proceso. Deberías ver el proceso VASP e indicarse hasta qué punto se usa la GPU. Para detener el proceso, presiona CTRL+c.

Uso de un único nodo computacional

Al igual que la versión estándar de VASP, la versión para GPU está paralelizada con MPI y puede distribuir la carga de trabajo computacional en varias CPU, GPU y nodos. Utilizaremos Intel MPI en esta guía, pero todas las técnicas descritas aquí también funcionan con otras implementaciones MPI. Consulta la documentación de tu implementación MPI concreta para encontrar las opciones de la línea de comandos equivalente.

VASP es compatible con varias características y algoritmos que hacen que su perfil computacional sea muy diverso. Por lo tanto, según los cálculos específicos, puede que necesites parámetros diferentes para obtener los tiempos de ejecución más rápidos. Estos aspectos también afectan a la versión para GPU.

En este tutorial, ofreceremos varias técnicas que contribuyen a la aceleración de las ejecuciones mediante GPU. Sin embargo, como no hay una configuración óptima, tienes que realizar las pruebas de referencia de los casos de forma individual para encontrar los ajustes que ofrezcan el mejor rendimiento.

Primero vamos a comprobar cuántas (y cuáles) GPU ofrece tu nodo:

nvidia-smi –L

El resultado del comando indica que tenemos cuatro GPU Tesla P100 disponibles y muestra una lista los identificadores únicos (UUID) que utilizaremos después:

GPU 0: Tesla P100-PCIE-16GB (UUID: GPU-74dff14b-a797-85d9-b64a-5293c94557a6)
GPU 1: Tesla P100-PCIE-16GB (UUID: GPU-576df4e5-8f0c-c773-23d2-7560fd29542e)
GPU 2: Tesla P100-PCIE-16GB (UUID: GPU-cff44500-e07e-afef-8231-0bdd32dde61f)
GPU 3: Tesla P100-PCIE-16GB (UUID: GPU-54c0a6db-b406-3e24-c28b-0b517549e824)

Normalmente, las GPU necesitan transferir datos entre su propia memoria y la principal. En sistemas de varios sockets, el rendimiento de la transferencia depende de la ruta que tienen que seguir los datos. En el mejor de los casos, hay un bus directo entre las dos regiones separadas de memoria. En el peor de los casos, el proceso de la CPU necesita acceder a la memoria que está ubicada físicamente en un módulo RAM asociado al otro socket de la CPU y, después, copiarla en la memoria de la GPU a la que solo se puede acceder (nuevamente) mediante una línea PCI-E controlada por el otro socket de la CPU. La información sobre la topología de bus se puede mostrar con:

nvidia-smi topo -m

Como VASP acelerado por GPU no es compatible (todavía) con la comunicación directa de GPU a GPU, podemos omitir la mayoría de los resultados que se muestran, las parejas de GPU que podrían comunicarse más rápido (PIX e incluso NV#) a más lento (SOC) entre ellas:

  GPU0 GPU1 GPU2 GPU3 mlx5_0 Afinidad de CPU
GPU0 X SOC SOC SOC SOC 0-15
GPU1 SOC X PHB PHB PHB 16-31
GPU2 SOC PHB X PIX PHB 16-31
GPU3 SOC PHB PIX X PHB 16-31
mlx5_0 SOC PHB PHB PHB X  

La última columna, “Afinidad de CPU”, es importante porque indica en qué núcleos de la CPU deberían ejecutarse los rangos MPI si se comunican con una GPU concreta. Vemos que todos los núcleos de la CPU del primer socket (0-15) pueden comunicarse directamente con GPU0, mientras que los de la CPU del segundo socket (16-31) tendrán un mejor rendimiento al combinarse con GPU1, GPU2 y GPU3.

Pruebas de referencia

Rendimiento esperado

Cada vez que quieras comparar tiempos de ejecución de ejecuciones en varias configuraciones, es esencial evitar divergencias inesperadas. Las GPU NVIDIA presentan técnicas para permitir temporalmente el aumento y la reducción de las frecuencias del reloj en función de la situación térmica y la carga computacional actuales. Pese a que esto es un aspecto positivo a la hora de ahorrar energía, a efectos de pruebas de referencia puede ofrecer números erróneos por una discrepancia ligeramente mayor en los tiempos de ejecución entre varias ejecuciones. Por lo tanto, para realizar la prueba de referencia, tenemos que desactivarlo de todas las tarjetas del sistema:

Cuando hayas acabado con la prueba de referencia, puedes restablecer las tarjetas para que funcionen con las frecuencias soportadas al máximo:

DESCARGO DE RESPONSABILIDAD SOBRE LAS CIFRAS DE RENDIMIENTO

Aunque las cifras que representan el rendimiento se han generado en sistemas de producción, solo deben utilizarse como guía para demostrar los métodos presentados en este documento. Ten en cuenta que tu sistema puede ser diferente dado a que hay muchos aspectos que afectan al rendimiento de la CPU y la GPU.

El método más sencillo: un proceso por GPU

El método más sencillo para usar las cuatro GPU presentes en el sistema es iniciar cuatro procesos MPI de VASP y que la asignación (es decir, en qué núcleos de la CPU se ejecutarán los procesos) se ocupe de ello automáticamente:

mpirun -n 4 ~/bin/vasp_gpu

El entorno Intel MPI fija automáticamente los procesos a núcleos de la CPU concretos, de forma que el sistema operativo no puede moverlos a otros núcleos durante la ejecución del trabajo, así se evitan escenarios desfavorables para el movimiento de datos. No obstante, esto puede llevar a una solución subóptima, ya que la implementación MPI no es compatible con la topología de las GPU. Podemos investigar la fijación de procesos al incrementar la verbosidad:

mpirun -n 4 -genv I_MPI_DEBUG=4 ~/bin/vasp_gpu

Al comprobar el resultado y compararla con los hallazgos sobre la topología de interconexión, vemos que la situación no es ideal:

...

[0] MPI startup():
Rank
Pid
Node name
Pin cpu

 
[0] MPI startup():
0
41587
hsw227
{16,17,18,19,20,21,22,23}

 
[0] MPI startup():
1
41588
hsw227
{24,25,26,27,28,29,30,31}

 
[0] MPI startup():
2
41589
hsw227
{0,1,2,3,4,5,6,7}

 
[0] MPI startup():
3
41590
hsw227
{8,9,10,11,12,13,14,15}


Using device 0 (rank 0, local rank 0, local size 4) : Tesla P100-PCIE-16GB

Using device 1 (rank 1, local rank 1, local size 4) : Tesla P100-PCIE-16GB

Using device 2 (rank 2, local rank 2, local size 4) : Tesla P100-PCIE-16GB

Using device 3 (rank 3, local rank 3, local size 4) : Tesla P100-PCIE-16GB

...

El rango 0 usa GPU0, pero está fijado a los núcleos lejanos 16-23 de la CPU. El mismo problema sucede para los rangos 2 y 3. Solo el rango 1 usa GPU1 y está fijado a los núcleos 24-31, que ofrecen el mejor rendimiento de transferencia.

Echemos un vistazo a las cifras de rendimiento reales. Con el uso de los 32 núcleos de las dos CPU Intel® Xeon® E5-2698 v3 presentes en el sistema sin aceleración por GPU, la prueba de referencia siHugeShort ha tardado 607,142 s en completarse.1 Usar cuatro GPU en este escenario, pero de forma subóptima, ofrece un tiempo de ejecución de 273,320 s y una aceleración 2,22 veces mayor. Utiliza las medidas a continuación incluidas en VASP para averiguar rápidamente el tiempo de ejecución de tu cálculo

1 Si has compilado la versión de una CPU de VASP antes, usa el siguiente comando para comprobar cuánto tarda en tu sistema: mpirun -n 32 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_std

grep Elapsed\ time OUTCAR

VASP asigna las GPU a rangos MPI de forma consecutiva, a la vez que evita las GPU con capacidades de computación insuficientes (en caso de haber alguna). Entonces, utilizando la sintaxis a continuación, podemos controlar manualmente la ubicación de procesos en la CPU y distribuir los rangos de forma que todos los procesos utilicen una GPU que tenga la ruta de transferencia de memoria más corta:

mpirun -n 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,16,21,26 ~/bin/vasp_gpu

Este método no ofrece una mejora en nuestro sistema (el tiempo de ejecución fue de 273, 370 s), provocado probablemente por un uso desequilibrado de los recursos comunes de la CPU, como el ancho de banda y las cachés de memoria (tres procesos que comparten una CPU). Como arreglo, se pueden distribuir los rangos de forma que estén propagados equitativamente por los sockets de la CPU, pero solo un rango debe usar la ruta de memoria a la GPU más lenta:

mpirun -n 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24 ~/bin/vasp_gpu

Un tiempo de ejecución de 268,939 s nos ofrece una ligera mejora de un 3 % para esta prueba de referencia, pero si tu carga de trabajo es mayor en las transferencias de memoria, puede que obtengas más.

La selección manual de la distribución puede resultar tediosa, sobre todo para números elevados de rangos, o quizá decidas que compartir equitativamente los recursos de la CPU sea más importante que las transferencias de memoria en tu sistema. El siguiente comando asigna los rangos de manera consecutiva, pero evita compartir los recursos comunes en la medida de lo posible:

mpirun -n 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_gpu

Esto ha generado un tiempo de ejecución de 276,299 s y puede ser particularmente útil si algunos de los núcleos de la CPU permanecen sin ocupar. Quizá quieras hacerlo a propósito si un único proceso por GPU satura un recurso de la GPU que limita el rendimiento. Sobrecargar la GPU todavía más afectaría al rendimiento. Estos son los resultados obtenidos con el ejemplo de la prueba de referencia siHugeShort, no puede ser mejor (no dudes en probar las opciones siguientes). Sin embargo, suele ser una mala idea desperdiciar los núcleos de CPU disponibles siempre y cuando no sobrecargues las GPU, por lo que te recomendamos que realices tus propias pruebas.

El segundo método más sencillo: varios procesos por GPU

Para demostrar cómo usar más núcleos de la CPU que GPU disponibles, cambiaremos a una prueba de referencia diferente llamada silicaIFPEN. Tarda 710,156 s en ejecutarse utilizando solo los 32 núcleos de la CPU. Utilizando cuatro GPU P100 con un rango MPI por GPU y el arreglo relacionado con la ubicación de los procesos, tarda 241,674 s en completarse (2,9 veces más rápido). Las GPU NVIDIA tienen la capacidad de compartirse entre varios procesos. Para usar esta característica, debemos asegurarnos de que todas las GPU tienen el modo de computación en “default”:

Entonces ejecutamos el cálculo en sí:

mpirun -n 8 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,4,12,20,28 ~/bin/vasp_gpu

Para la prueba de referencia silicaIFPEN, el tiempo de ejecución en nuestro sistema ha mejorado a 211, 576 s con 12 procesos compartiendo cuatro GPU P100 (es decir, tres procesos por GPU), lo que ha aumentado la velocidad hasta 3,36 veces. Aunque usar cuatro o más procesos por GPU provoca un efecto adverso en el tiempo de ejecución. La comparación en la siguiente tabla muestra que los procesos ubicados manualmente ya no ofrecen una ventaja.

Tabla 2. Comparación entre los tiempos transcurridos de la prueba de referencia silicaIFPEN variando la cantidad de procesos MPI por GPU

Rangos MPI por GPU Total de rangos MPI Tiempo transcurrido (map:scatter) Tiempo transcurrido (map:ideal)
0 32 (una CPU) 710,156 s  
1 4 242,247 s 241,674 s
2 8 214,519 s 212,389 s
3 12 212,623 s 211,576 s2
4 16 220,611 s 224,013 s3
5 20 234,540 s  
6 24 243,665 s  
7 28 259,757 s  
8 32 274,798 s  

2 mpirun -n 12 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,3,11,19,27,6,14,22,30 ~/bin/vasp_gpu
3 mpirun -n 16 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,2,10,18,26,4,12,20,28,6,14,22,30 ~/bin/vasp_gpu

Después de llegar al punto óptimo, agregar más procesos por GPU afecta al rendimiento incluso más. Pero ¿por qué? Cada vez que una GPU tiene que cambiar de contexto (es decir, permitir que otro proceso tome el control), se introduce un punto de sincronización complicado. De esta forma, no existe la posibilidad de que las instrucciones de procesos diferentes se solapen en la GPU, y el uso excesivo de esta característica puede provocar ralentizaciones de nuevo. Echa un vistazo a la ilustración a continuación.

En resumen, parece buena idea probar cuánta sobresuscripción es beneficiosa para tu tipo de cálculos. Los cálculos muy grandes llenarán con más facilidad una GPU con un proceso único que los cálculos pequeños, ¡así que te animamos encarecidamente a que realices tus propias pruebas!

VASP funciona hasta 10 veces más rápido en GPU

NVIDIA MPS: permitir el solapamiento mientras se comparten GPU

Este método está estrechamente relacionado con el anterior, pero soluciona el problema de que las instrucciones de varios procesos no se solapen en la GPU, tal y como aparece en la segunda fila de la ilustración. Es recomendable configurar las GPU para procesar el modo de computación exclusivo al usar MPS:

Lo único que queda por hacer para aprovechar esta posibilidad es iniciar el servidor MPS antes de comenzar con los trabajos MPI:

nvidia-cuda-mps-control -d
mpirun -n 8 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_gpu
echo quit | nvidia-cuda-mps-control

El primer comando inicia el servidor MPS en segundo plano (modo demonio). Cuando está activo, interceptará instrucciones emitidas por procesos que comparten una GPU y las ubicará en el mismo contexto antes de enviarlas a la GPU. La diferencia con la sección anterior es que, desde la perspectiva de las GPU, las instrucciones pertenecen a un único proceso y contexto, por lo que ahora pueden solaparse, como si estuvieras utilizando flujos en una aplicación CUDA. Puedes comprobar con nvidia-smi -l que solo un único proceso accede a las GPU.

Este modo de ejecución de la versión para GPU de VASP puede contribuir a incrementar el uso de GPU cuando un único proceso no sature los recursos de la GPU. Para demostrar esto, utilizamos el tercer ejemplo B.hR105, un cálculo que usa un intercambio exacto en el funcional HSE06. Lo hemos ejecutado con diferentes cantidades de rangos MPI por GPU con MPS activado y desactivado.

Tabla 3. Comparación entre los tiempos transcurridos de la prueba de referencia B.hR105 variando la cantidad de procesos MPI por GPU con MPS activado y desactivado para NSIM=4

Rangos MPI por GPU Total de rangos MPI Tiempo transcurrido sin MPS Tiempo transcurrido con MPS
0 32 (una CPU) 1027,525 s
1 4 213,903 s 327,835 s
2 8 260,170 s 248,563 s
4 16 221,159 s 158,465 s
7 28 241,594 s 169,441 s
8 32 246,893 s 168,592 s

Lo más importante es que MPS mejora el tiempo de ejecución entre 55,4 s (un 26 %) y 158,465 s, en comparación con el mejor resultado sin MPS (213,903 s). Aunque hay un punto óptimo con cuatro rangos por GPU sin usar MPS, iniciar tantos procesos como núcleos de CPU haya disponibles ofrece el mejor rendimiento con MPS.

Nos hemos saltado los cálculos con tres, cinco y seis rangos por GPU a propósito porque el número de bandas (224) que se usa en este ejemplo no es divisible por la cantidad resultante de rangos, por lo que sería incrementado por VASP automáticamente, incrementando así la carga de trabajo. Si solo te interesa la solución de tiempo, te recomendamos que experimentes un poco con el parámetro NSIM. Al configurarlo en 32 y utilizando solo un proceso por GPU (sin MPS), hemos podido reducir el cálculo de tiempo a 108,193 s, lo que supone una aceleración 10 veces mayor.

Avanzado: una instancia MPS por GPU

Para configuraciones concretas, sobre todo en versiones antiguas de CUDA, podría resultar beneficioso iniciar varias instancias del demonio MPS; por ejemplo, un servidor MPS por GPU. Sin embargo, esto es un poco más complicado porque hay que indicar a todos los procesos MPI qué servidor MPS tienen que usar y todas las instancias MPS deben estar fijadas a otra GPU. No recomendamos este método, sobre todo en GPU P100 con CUDA 8.0, pero eso no quiere decir que no te resulte útil. El siguiente script se puede usar para iniciar las instancias de MPS:

Para que funcione este método, la versión para GPU de VASP debe iniciarse mediante un desvío utilizando un script contenedor. Esto configurará las variables de entorno de forma que todos los procesos utilizarán la instancia correcta de los servidores MPS que acabamos de iniciar: runWithMultipleMPSservers-RR.sh.

Este script genera una lista con las rutas para configurar las variables de entorno que deciden qué instancia MPS se usa. La cuarta última línea (myMpsInstance=...) selecciona esta instancia en función del ID del proceso MPI local. Hemos decidido aplicar una distribución en cadena al repartir los procesos uno a cuatro a GPU0 a GPU3. El proceso 5 usa GPU0 de nuevo, así como lo hace el proceso nueve, mientras que los procesos seis y diez están asignados a GPU2, etc. Si has utilizado una ruta diferente para instalar tu binario de GPU VASP, asegúrate de adaptar la línea que empieza por runCommand en consecuencia. Entonces inicia el cálculo:

mpirun -n 16 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ./runWithMultipleMPSservers-RR.sh

Al usar MPS, ten en cuenta que los propios servidores MPS usan la CPU. De esta forma, cuando inicies tantos procesos como núcleos de la CPU haya disponibles, puede que también sobrecargues la CPU. Así que es una buena idea reservar uno o dos núcleos para MPS.

Cuando el cálculo termine, el siguiente script despeja las instancias MPS:

Utilizar varios nodos de procesamiento

UN PROCESO POR GPU

Todo lo que se ha dicho acerca de usar GPU almacenadas en un único nodo también se aplica a varios nodos. Así que, lo que has decidido que funcionaba mejor para tus sistemas en relación con la asignación de procesos, seguramente también funcione bien en más nodos. A partir de aquí asumiremos que tienes una configuración de archivo de host con una lista de todos los nodos asociados con el trabajo. En nuestro caso, hemos utilizado dos nodos y el archivo de host tiene este aspecto:

hsw224
hsw225

Si procedes con la selección manual de la asignación de procesos, solo hay una pequeña diferencia respecto al comando presentado en el capítulo anterior:

mpirun -f hostfile -n 8 -ppn 4 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24 ~/bin/vasp_gpu

La salida de depuración de la implementación MPI indica que los procesos están distribuidos en dos nodos que residen en los núcleos de la CPU, como esperábamos:

[0] MPI startup(): Rank Pid Node name Pin cpu
[0] MPI startup(): 0 38806 hsw224 0
[0] MPI startup(): 1 38807 hsw224 8
[0] MPI startup(): 2 38808 hsw224 16
[0] MPI startup(): 3 38809 hsw224 24
[0] MPI startup(): 4 49492 hsw225 0
[0] MPI startup(): 5 49493 hsw225 8
[0] MPI startup(): 6 49494 hsw225 16
[0] MPI startup(): 7 49495 hsw225 24

El resultado de GPU VASP confirma que también las GPU están asignadas a los rangos MPI, como queríamos:

Using device 0 (rank 0, local rank 0, local size 4) : Tesla P100-PCIE-16GB
Using device 1 (rank 1, local rank 1, local size 4) : Tesla P100-PCIE-16GB
Using device 2 (rank 2, local rank 2, local size 4) : Tesla P100-PCIE-16GB
Using device 3 (rank 3, local rank 3, local size 4) : Tesla P100-PCIE-16GB
Using device 0 (rank 4, local rank 0, local size 4) : Tesla P100-PCIE-16GB
Using device 1 (rank 5, local rank 1, local size 4) : Tesla P100-PCIE-16GB
Using device 2 (rank 6, local rank 2, local size 4) : Tesla P100-PCIE-16GB
Using device 3 (rank 7, local rank 3, local size 4) : Tesla P100-PCIE-16GB

El rendimiento de la prueba de referencia siHugeShort es un poco más rápido con 258,917 s; pero, comparado con el tiempo de ejecución de 268,939 s en un nodo, no es justificable su uso. Por otro lado, la prueba de referencia silicaIFPEN mejora notablemente de 241, 674 s a 153,401 s al pasar de cuatro a ocho GPU P100 con un proceso MPI por GPU.

VARIOS PROCESOS POR GPU

En relación con las secciones anteriores, cambiar a varios nodos con varios procesos por GPU es sencillo:

mpirun -f hostfile -n 16 -ppn 8 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=0,8,16,24,4,12,20,28 ~/bin/vasp_gpu

O si quieres usar todos los núcleos de la CPU:

mpirun -f hostfile -n 64 -ppn 32 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ~/bin/vasp_gpu

Al incrementar el número de rangos por GPU, la prueba de referencia silicaIFPEN muestra un comportamiento diferente al del caso de un único nodo (la configuración más rápida llevó 211, 576 s): usar dos procesos por GPU mejora el tiempo de ejecución de forma insignificante a 149,818 s en comparación con los 153,401 s del caso de un proceso por GPU. Sobrecargar las GPU vuelve a provocar un efecto adverso porque usar tres procesos por GPU incrementa el tiempo de ejecución a 153,516 s y 64 rangos en total toman 231,015 s. Así que usar uno o dos procesos por GPU en cada nodo es suficiente en este caso.

NVIDIA MPS EN VARIOS NODOS

Utilizar una única instancia de MPS por nodo resulta trivial cuando se inician las instancias. Algunos programadores de trabajos ofrecen opciones de envío para hacerlo por ti (por ejemplo, SLURM a veces ofrece --cuda-mps). Si algo así está disponible en tu clúster, es muy recomendable que lo uses y procedas como se describe en la sección anterior.

Pero ¿qué hay que hacer si tu programador no ofrece una solución tan elegante? Tienes que asegurarte de que se inicia una instancia MPS (solo una) en cada nodo antes de lanzar VASP. Proporcionamos otro script que se encarga de esto: runWithOneMPSPerNode.sh.

De nuevo, si has instalado el binario de VASP acelerado por GPU en una ubicación alternativa, adapta la variable runCommand al principio del script. Las variables siguientes calculan la clasificación local de cada nodo porque la implementación de Intel MPI no ofrece esta información fácilmente. El script inicia un servidor MPS en cada primer rango por nodo, lo que garantiza que el proceso MPS no está fijado al mismo núcleo, por lo que un proceso VASP se fijará más tarde. Este paso es crucial porque, de lo contrario, MPS estaría limitado a usar un único núcleo (puede usar más) e incluso a competir con VASP por ciclos de CPU en ese núcleo. El script continúa ejecutando VASP y, después, detiene MPS.

Se usa el comando mpirun para llamar al script, como puede que hayas visto en la sección avanzada. El comando mpirun funciona como cuando se ejecuta sin MPS, pero ten en cuenta que llamamos al script en vez de al binario de VASP:

mpirun -f hostfile -n 24 -ppn 12 -genv I_MPI_DEBUG=4 -env I_MPI_PIN_PROCESSOR_LIST=allcores:map=scatter ./runWithOneMPSPerNode.sh

En relación con la prueba de referencia B.hR105, MPS ha mejorado el tiempo de ejecución en un único nodo, también en dos nodos: activar MPS acelera el tiempo de cálculo y usar más rangos por GPU es beneficioso hasta cierto punto de (sobre)saturación. El punto óptimo en nuestro sistema era cuatro rangos por GPU, que ha generado un tiempo de ejecución de 104,052 s. Comparado con la referencia de un único nodo Haswell, supone una aceleración 9,05 veces mayor y, comparado con los 64 núcleos de CPU, sigue siendo más rápido (6,61 veces más).

Si utilizamos NSIM=32 con cuatro rangos por GPU en cada uno de los dos nodos y no utilizamos MPS, el cálculo solo lleva 71,222 s.

Tabla 4. Comparación entre los tiempos transcurridos de la prueba de referencia B.hR105 variando la cantidad de procesos MPI por GPU con MPS activado y desactivado para NSIM=4 utilizando dos nodos

Rangos MPI por GPU Total de rangos MPI Tiempo transcurrido sin MPS Tiempo transcurrido con MPS
0 32 (una CPU: 1 nodo) 1027,525 s
0 64 (una CPU) 763,939 s4
1 8 127,945 s 186,853 s
2 16 117,471 s 110,158 s
4 32 130,454 s 104,052 s
7 56 191,211 s 148,662 s
8 64 234,307 s5 182,260 s

4 y 5: aquí se han usado 256 bandas, lo que incrementa la carga de trabajo.

VASP funciona hasta 10 veces más rápido en GPU

Configuraciones recomendadas del sistema

Configuración de hardware

Estación de trabajo

Parámetro
Especificaciones

Arquitectura de CPU

x86_64

Memoria del sistema

32-64 GB

CPU

8 núcleos, más de 3 GHz
10 núcleos, más de 2,2 GHz
16 núcleos, más de 2 GHz

Modelo de GPU

NVIDIA Quadro GP100

GPU

2-4

Servidores

Parámetro
Especificaciones

Arquitectura de CPU

x86_64

Memoria del sistema

64-128 GB

CPU

Más de 16 núcleos, más de 2,7 GHz

Modelo de GPU

NVIDIA Tesla P100, V100

GPU por nodo

2-4

Configuración de software

Pila de software

Parámetro
Versión

SO

Linux 64

Controlador de GPU

352,79 o posterior

Kit de herramientas de CUDA

8.0 o posteriores

Compilador

Compilador PGI 16.10
Intel Compiler Suite 16

MPI

OpenMPI
Intel MPI

Solución de problemas

ADAPTACIÓN DE VARIABLES DE COMPILACIÓN (opcional)

El entorno de software local puede diferir de lo que el sistema de compilación VASP puede encargarse automáticamente. En este caso, la compilación producirá errores y tendrás que realizar ajustes menores en el archivo makefile.include. Abre el archivo makefile.include con el editor que quieras (por ejemplo: nano, vim o emacs están disponibles de forma predeterminada en la mayoría de los sistemas) y realiza los cambios necesarios (a continuación):

nano makefile.include

Cada vez que realices cambios en algún archivo, asegúrate de ejecutar el siguiente comando para iniciar la compilación desde cero:

make veryclean

A partir de aquí listamos algunos mensajes de error típicos y cómo solucionarlos:

mpiifort: Command not found

Ese mensaje de error indica que, en tu sistema, el compilador de Fortran de Intel compatible con MPI tiene un nombre diferente al esperado. En el archivo makefile.include, cambia todos los casos de mpiifort a comoquiera que se llame en tu sistema (por ejemplo, mpif90).

# error "This Intel is for use with only the Intel compilers!" Para solucionar este error tienes que hacer dos cosas. Primero, edita el archivo makefile.include y agrega -ccbin=icc a la variable NVCC, de forma que sea así:

Para solucionar este error tienes que hacer dos cosas. Primero, edita el archivo makefile.include y agrega -ccbin=icc a la variable NVCC, de forma que sea así:

NVCC := $(CUDA_ROOT)/bin/nvcc -ccbin=icc

Después tienes que editar un segundo archivo:

nano src/CUDA/common.mk

En el archivo, verás una sección como la que aparece a continuación:

# Compilers

NVCC ?= $(CUDA_INSTALL_PATH)/bin/nvcc

CXX := g++ -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

CC := gcc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

#CXX := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

#CC := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

LINK := g++ -fPIC

Cámbiala para que sea de esta manera:

# Compilers

NVCC ?= $(CUDA_INSTALL_PATH)/bin/nvcc

#CXX := g++ -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

#CC := gcc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

CXX := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

CC := icc -fPIC -DADD_ -Wall -openmp -DMAGMA_WITH_MKL -DMAGMA_SETAFFINITY -DGPUSHMEM=300 -DHAVE_CUBLAS

LINK := g++ -fPIC

/usr/local/cuda//bin/nvcc: Command not found

Este mensaje te dice que el archivo Make no encuentra el compilador nvcc de NVIDIA CUDA. Puedes corregir la ruta en la línea

CUDA_ROOT := /usr/local/cuda/

o incluso comentarlo (utilizando # como primer símbolo de la línea) si CUDA_ROOT está configurado como una variable de entorno.

No rule to make target `/cm/shared/apps/intel/composer_xe/2015.5.223/mkl/interfaces/fftw3xf/libfftw3xf_intel.a', needed by `vasp'. Stop.

Seguramente tu MKL local se instaló sin soporte para la interfaz FFTW3 como biblioteca estática. Si marcas como comentario la línea haciendo referencia a esa biblioteca estática (al insertar un # al principio), el enlazador recopilará la señal analógica dinámica. Asegúrate de marcar como comentario la línea asociada (y las siguientes) a OBJECTS_GPU, no solo la línea después de OBJECTS.

Mi error no aparece aquí

Si tu sistema cumple los requisitos mencionados al principio, seguramente haya que cambiar únicamente una ruta a una biblioteca en el archivo makefile.include. En http://cms.mpi.univie.ac.at/wiki/index.php/Installing_VASP, se explican con más detalle las variables definidas.

Crea tu solución ideal de GPU hoy mismo.