⚛️ ☕ Quantum Espresso#

Quantum Espresso (QE) es un código DFT que implementa pseudopotenciales y ondas planas de manera eficiente para obtener la densidad electrónica de sistemas materiales y moleculares, que es muy usado por un buen número de usuarias/os en los clusters de UNC Supercómputo. El código está diseñado para correr de manera eficiente en infraestructuras HPC aprovechando los protocolos de paralelización MPI/OpenMP.

El software está disponible a través de modules.

Almacenamiento#

Los clusters cuentan con distintos sistemas para el almacenamiento de archivos. Es importante distinguir entre almacenamiento local y almacenamiento remoto.

Ver almacenamiento /home y /scratch.

Buenas prácticas en la gestión de datos#

  • Almacene archivos esenciales y de largo plazo en la partición home

    Para tareas computacionales, copie los archivos de entrada necesarios al espacio scratch local al inicio de su trabajo y transfiera los archivos de salida de regreso a la partición home al finalizar. Este enfoque aprovecha las capacidades de alta velocidad del espacio scratch durante la computación, asegurando al mismo tiempo que los datos importantes se preserven en la partición home.

  • Monitoree y limpie regularmente los archivos en la partición home

    Para liberar espacio y cumplir con las políticas del sistema respecto al uso de almacenamiento de datos ya que el espacio en la partición home es limitado. Se recomienda fuertemente borrar todos los archivos temporales que hayan sido generados por los códigos de cálculo y mantener sólo los archivos esenciales.

Archivos generados por QE#

El proceso de entrada/salida (I/O) de archivos en una corrida típica de optimización de geometría (relax) con QE implica la creación y gestión de varios archivos temporales y permanentes que contienen información esencial para la simulación.

A continuación, se detalla cómo QE maneja estos archivos durante una ejecución de pw.x en modo relax.

Variables clave#

  • outdir

    Especifica el directorio donde QE almacena archivos temporales y de salida. Debe ser accesible para todos los procesos en ejecuciones paralelas. Por ejemplo:

    &CONTROL
        outdir = '/scratch/$USER/qe_temp'
        ...
    /
    

    Es recomendable que outdir apunte a un sistema de archivos rápido y local al nodo de cómputo para mejorar el rendimiento de I/O. Recomendamos usar la variable del entorno ESPRESSO_TMPDIR para definir el directorio de almacenamiento de archivos en lugar de la variable outdir en el input para QE.

  • prefix

    Define el prefijo utilizado para nombrar los archivos generados. Por ejemplo:

    &CONTROL
        prefix = 'mi_simulacion'
        ...
    /
    

    QE creará un subdirectorio llamado mi_simulacion.save/ dentro de outdir, que contendrá los archivos necesarios para reiniciar o continuar la simulación.

Estructura de directorios#

Durante una corrida de pw.x en modo relax, QE genera varios archivos:

  • prefix.save/

    Contiene información esencial de la simulación, como la densidad de carga, potenciales y funciones de onda. Este directorio es crucial para reiniciar simulaciones interrumpidas.​

  • prefix.wfc*

    Archivos binarios que almacenan las funciones de onda de los electrones.​

  • prefix.igk*

    Contienen información sobre los vectores de onda utilizados en la simulación.​

  • prefix.dos, prefix.bands, etc.

    Dependiendo de las opciones de salida seleccionadas, QE puede generar archivos adicionales con información sobre la densidad de estados, bandas electrónicas, entre otros.

Durante los cálculos realizados con pw.x, cada ciclo scf genera un proceso de lectura/escritura que puede involucrar desde unos pocos cientos de MB hasta decenas de GB, dependiendo del tamaño del sistema y de los parámetros utilizados. En modos como relax, md o neb, este proceso se repite múltiples veces, ya que se ejecutan varios ciclos scf sucesivos. Por este motivo, no se recomienda escribir los archivos temporales en el directorio /home, ya que no está optimizado para operaciones intensivas de I/O. Para mejorar el rendimiento, se aconseja redireccionar estos archivos a un directorio temporal en cada nodo de cálculo.

Ver también

Para ver un ejemplo de cómo implemetar esto, ver script de slurm.

Paralelismo#

Quantum Espresso implementa varias estrategias de paralelización, y entenderlas es de gran importancia para aprovechar al máximo un cluster de alto rendimiento. Todos los detalles sobre paralelismo en QE se pueden consultar en esta sección del manual. Resumimos aquí algunas ideas principales.

Estrategias comunes#

Las estrategias de paralelización más comúnmente utilizados son:

  • MPI («Message Passing Interface»):

    • Capa principal de paralelismo (estados electrónicos).

    • Distribuye los puntos-k y las funciones de onda entre los procesos (ranks).

  • OpenMP (multihilo):

    • Puede usarse para paralelizar bucles sobre grillas FFT, grillas en el espacio real y vectores-G.

    • Funciona mejor cuando se combina con MPI en una configuración híbrida.

  • Paralelismo por Grupos o «Pools» (-npool):

    • Útil cuando se tienen múltiples puntos-k.

    • Cada grupo maneja un subconjunto de puntos-k, muy eficiente para metales o sistemas grandes.

  • Paralelismo por Imágenes (-nimage):

    • Para cálculos NEB.

    • Distribuye cada imagen de la trayectoria a diferentes grupos de procesos.

  • Paralelismo en la Diagonalización (-ndiag):

    • Paraleliza la diagonalización de matrices para cada punto-k.

    • Particularmente importante para sistemas grandes con muchas bandas.

    • Funciona con ScaLAPACK o ELPA.

  • Grupos de Tareas (-ntg):

    • Menos utilizado.

    • Distribuye las FFT entre grupos para reducir el uso de memoria.

Guía rápida de parámetros#

Parámetro

Descripción

Cuándo Usarlo

Ejemplo

-npool N

Paralelismo por puntos k.
Distribuye los puntos k en N grupos.

Sistemas con múltiples puntos k (metales, superceldas, grillas densas).

-npool 4

-nimage N

Paralelismo por imágenes(NEB o caminos de reacción).
Cada imagen recibe un grupo de procesos MPI.

Cálculos NEB.

-nimage 8

-ndiag N

Paralelismo en la diagonalización.
Mediante ScaLAPACK o ELPA. Paraleliza sobre los índices de bandas.

Sistemas grandes con muchas bandas (>100) o funcionales híbridos.

-ndiag 8

-ntg N

Paralelismo por grupos de tareas.
Distribuye las FFTs para reducir el uso de memoria.

Grandes redes de FFT o cuando la memoria por núcleo es limitada.

-ntg 2

-nb N

Paralelismo por bandas.
Distribuye las funciones de onda dentro de cada punto k.

Sistemas muy grandes con muchas bandas, especialmente con DFT híbrido.

-nb 2

-nt N

Multihilo OpenMP.
Se usa para paralelismo híbrido MPI + OpenMP.

Cuando se usa una combinación de MPI y OpenMP.

export OMP_NUM_THREADS=2

-input

Especifica el archivo de entrada (alternativo a < entrada.in o -in).

Sintaxis alternativa opcional.

-input scf.in

Guía para elegir los parámetros#

Característica del sistema

Estrategia Recomendada

Pocos átomos, muchos puntos k

Usar -npool

Muchos átomos, pocos puntos k

Usar -ndiag, -nt o -nb

Cálculo NEB

Usar -nimage junto con -npool

Cálculo de fonones

Usar -npool en ph.x para paralelizar por puntos q

Memoria limitada por núcleo

Usar -ntg

DFT híbrido o meta-GGA

Usar -nb, -ndiag y aumentar el uso de hilos/memoria

Guía para encontrar el punto óptimo#

Para poder escoger los parámetros de paralelización de manera adecuada y juiciosa es absolutamente necesario realizar un estudio de benchmarking.

Paso a paso:

  1. Seleccionar un sistema representativo

    Elegir un sistema realista y representativo del sistema bajo estudio, por ejemplo:

    • Un sólido periódico con ~100 - 300 átomos

    • Un camino de NEB con ~6 - 8 imágenes

    • Un sistema de molécula + superficie con 1 - 4 puntos-k

    Importante

    Para todos los benchmarks, mantener fija la entrada: geometría, cutoffs, pseudopotenciales, etc.

  2. Probar configuraciones paralelas distintas

    Variar el número de ranks MPI, hilos OpenMP y usar distintas combinaciones de -npool, -ndiag, etc.

    ID de prueba

    Nodos

    MPI por nodo

    Hilos

    Núcleos totales

    Notas

    A

    1

    32

    2

    64

    MPI por socket + OpenMP

    B

    2

    64

    1

    128

    Todo MPI

    C

    4

    32

    2

    256

    Híbrido MPI + OpenMP

    D

    4

    64

    1

    256

    Todo MPI (nodos densos)

    Probar diferentes combinaciones, como por ejemplo:

    • -npool 4, -ndiag 4, -nb 2

    • -npool 8, -ndiag 8, sin paralelismo por bandas

    • Usar OMP_NUM_THREADS para habilitar OpenMP

  3. Medir tiempos y escalabilidad

    Después de cada corrida, extraer del output:

    • El tiempo total transcurrido (wall time)

    • Las líneas de «CPU/Wall» informadas por QE para cada una de las rutinas (de ser necesario)

    Ejemplo de salida
    output.log#
    Parallel routines
    
        PAW routines
        PAW_pot      :      8.70s CPU     12.33s WALL (     204 calls)
    
        PWSCF        :   3m43.66s CPU   5m11.34s WALL
    
    
    This run was terminated on:  10:55:31  24Apr2025
    
    =------------------------------------------------------------------------------=
    JOB DONE.
    =------------------------------------------------------------------------------=
    

    Para obtener el tiempo total de ejecución se puede usar:

    $ grep "PWSCF.*WALL" output.log
    

    O agregar al script:

    time srun … pw.x …
    
  4. Calcular la eficiencia paralela

    Usar un tiempo de referencia \(T_{ref}\)​ (por ejemplo, la corrida en 1 nodo) y calcular:

    • Aceleración (speedup):

    \[S_p = \frac{T_{ref}}{T_p}\]
    • Eficiencia:

    \[E_p = \frac{S_p}{p}\]

    donde \(p\) es la cantidad de nodos (o núcleos totales).

    Una eficiencia menor al 50 - 60 % indica sobreescalado: se están malgastando núcleos.

  5. Identificar el punto óptimo

    Graficar:

    • Tiempo de ejecución vs cantidad de núcleos

    • Eficiencia vs cantidad de núcleos

    Buscar el «hombro de la curva», es decir el punto donde agregar más núcleos apenas mejora el tiempo. Este es el punto óptimo de paralelización.

    Ejemplo: Si duplicar los núcleos de 128 a 256 sólo reduce el tiempo un 15 %, conviene quedarse en 128.

Buenas prácticas para usuarios (resumen)#

Situación

Estrategia paralela recomendada

Sistema con muchos puntos-k

Aumentar -npool (un múltiplo del número total de cores; idealmente los puntos k deben distribuirse homogéneamente entre los pools)

Sistema con muchas bandas

Usar -ndiag, -nb, y OMP_NUM_THREADS > 1

NEB con muchas imágenes

Usar -nimage (idealmente una imagen por nodo)

Poca memoria por núcleo

Usar -ntg, reducir hilos, activar paralelismo por bandas

Script de slurm#

A continuación un script de ejemplo para usar como template, que incluye algunas de las buenas practicas que ya fueron explicadas:

submit.sh#
 1#!/bin/bash
 2#SBATCH --partition=multi
 3#SBATCH --nodes=4
 4#SBATCH --ntasks-per-node=64
 5#SBATCH --cpus-per-task=1
 6#SBATCH --time=0-1:00
 7#SBATCH --exclusive
 8#SBATCH --output=output.log
 9#SBATCH --error=error.log
10
11# Load Quantum ESPRESSO
12module load quantum-espresso/7.2
13
14export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
15
16echo "USER = $USER"
17echo "SLURM_JOB_ID = $SLURM_JOB_ID"
18echo "SLURMD_NODENAME = $SLURMD_NODENAME"
19echo "SLURM_NODELIST = $SLURM_NODELIST"
20echo "SLURM_NNODES = $SLURM_NNODES"
21
22# Define scratch and temporary directories
23export SCRATCH_DIR=/scratch/$USER/$SLURM_JOB_ID
24export TMP_DIR=./tmp
25
26# Create directory for temporary files in /home partition
27mkdir -p "$TMP_DIR"
28# Create directories for temporary files in each local node /scratch partition
29srun --ntasks=$SLURM_NNODES --ntasks-per-node=1 mkdir -p "$SCRATCH_DIR"
30
31# Copy previous run's temporary files to scratch directory
32srun --ntasks=$SLURM_NNODES --ntasks-per-node=1 rsync -a "$TMP_DIR/" "$SCRATCH_DIR/"
33
34# Define relevant QE environment variables
35# Location of pseudopotentials - Edit this line if necessary
36export ESPRESSO_PSEUDO=~/QE_PSP
37# Directory for temporary files - Do not edit this line!
38export ESPRESSO_TMPDIR="$SCRATCH_DIR"
39# NB: Do not use keywords pseudo_dir & outdir in QE input
40
41# Run QE
42srun --cpu_bind=cores pw.x -in pw_relax.inp >> pw_relax.out
43exit_status=$?
44if [ $exit_status -ne 0 ]; then
45    echo "pw.x exited with status $exit_status"
46    # Optionally perform additional error handling here
47fi
48
49# Synchronize files from scratch to home directory
50echo "Initiating sync at $(date)"
51srun --ntasks=$SLURM_NNODES --ntasks-per-node=1 rsync -a "$SCRATCH_DIR/" "$TMP_DIR/"
52if [ $? -ne 0 ]; then
53    echo "Warning: rsync operation failed."
54elif
55    echo "Sync completed."
56fi

En este ejemplo, se utilizan 4 nodos para lanzar un cálculo ejecutando pw.x.

En principio, lo único que sería necesario editar es:

  • La ruta a los pseudopotenciales (línea 36) se recomienda, tal como en el ejemplo, utilizar la variable de ambiente ESPRESSO_PSEUDO en lugar de declarar la ruta en el input

  • Nombres de los archivos input / output (línea 42)

El input que se utilice en este caso debe contener siguientes keywords en el módulo &CONTROL:

&CONTROL
    calculation = 'relax' ,
    restart_mode = 'restart' ,
    max_seconds =  172000,
    disk_io = 'low' ,
    verbosity = 'high' ,
    nstep = 50 ,
/

Como se puede ver que no se utilizan las keywords outdir ni pseudo_dir, ya que ambos directorios vienen definidos en el script de slurm mediante las variables de ambiente ESPRESSO_TMPDIR y ESPRESSO_PSEUDO, respectivamente.

Por otro lado, es importante utilizar la keyword max_seconds para evitar que el cálculo exceda el tiempo límite de la cola de cálculo, para que de este modo se escriban archivos temporales para un hacer un restart.

El restart_mode se pone directamente en restart, ya que en la nueva versión de QE no da error aunque el cálculo sea from_scratch. De este modo, si es necesario realizar un restart, solo basta volver a enviar el script a la cola sin realizar ninguna modificación, ni en el propio script ni el input.

Incluso, es posible lanzar los sucesivos cálculos en distinta cantidad de nodos de manera transparente.

Nota

Al ejecutar pw.x en modo scf, los archivos se escriben únicamente al finalizar el ciclo de autoconsistencia, ya sea porque se alcanzó la convergencia (conv_thr), se completó el número máximo de pasos (electron_maxstep) o se agotó el tiempo máximo asignado al cálculo (max_seconds).

En cualquier caso, ocurre un único evento de escritura, por lo que este tipo de cálculos pueden realizarse de la manera habitual, es decir, utilizando la partición /home para almacenar los archivos temporales.

De todas formas, el script propuesto es igualmente compatible y se recomienda su uso para minimizar posibles errores.