knowhow:slurm:guias_de_uso

¡Esta es una revisión vieja del documento!


Página visible a externos sin autenticación

Envío de trabajos al clúster con sbatch

Esta página explica cómo lanzar trabajos al clúster ANTS usando Slurm y una plantilla base de sbatch pensada para entrenamientos de modelos en GPU. La plantilla está optimizada para usar el scratch local del nodo durante la ejecución y persistir los resultados en tu home al terminar.

Antes de lanzar un trabajo conviene tener claro lo siguiente:

  • sbatch envía un script a la cola de Slurm; el clúster decide cuándo y dónde se ejecuta.
  • Las líneas que empiezan por #SBATCH son directivas que se leen antes de ejecutar nada y configuran el trabajo (partición, recursos, tiempo, etc.).
  • El scratch de cada nodo ($SLURM_TMPDIR o /scratch/…) es almacenamiento rápido pero temporal: se borra al terminar el job. El home (/slurm/home/$USER) es persistente pero más lento.
  • La buena práctica es: copiar al scratch → trabajar allí → copiar resultados al home al terminar.

La plantilla espera que tu proyecto siga esta estructura en el directorio desde el que lances sbatch:

mi-proyecto/
├── code/                  # Código fuente (main.py y módulos)
│   └── main.py            # Punto de entrada del entrenamiento
├── data/                  # Dataset
├── lib/
│   └── install_env.sh     # Script que crea/activa el entorno (venv, módulos, CUDA)
├── artifacts/             # (opcional) checkpoints previos para reanudar
└── train.sbatch           # La plantilla de este documento

Guarda el siguiente contenido como train.sbatch en la raíz de tu proyecto.

train.sbatch
#!/usr/bin/env bash
#SBATCH --job-name=base-train          # Nombre identificador del trabajo en la cola
#SBATCH --partition=gpu                # Particion (cola) a usar — CPU o GPU
#SBATCH --nodes=1                      # Numero de nodos del cluster a reservar
#SBATCH --ntasks=1                     # Numero de tareas MPI (procesos paralelos)
#SBATCH --cpus-per-task=19             # Hilos de CPU por tarea
#SBATCH --gres=gpu:1                   # Recurso generico: 1 GPU H100 NVL o gpu:1 para cualquiera
#SBATCH --mem=31G                      # Memoria RAM total reservada en el nodo
#SBATCH --time=24:00:00                # Tiempo maximo de ejecucion (HH:MM:SS)
#SBATCH --output=/slurm/home/%u/output/%j/terminal.out  # stdout
#SBATCH --error=/slurm/home/%u/output/%j/terminal.err   # stderr
 
set -euo pipefail
 
OUTDIR="/slurm/home/$USER/output/$SLURM_JOB_ID"
mkdir -p "$OUTDIR"
 
SCRATCH="${SLURM_TMPDIR:-/scratch/slurm/$USER/$SLURM_JOB_ID/tmp}"
JOBSCRATCH="${SCRATCH}/base-${SLURM_JOB_ID}"
mkdir -p "$JOBSCRATCH"
 
SUBMIT_DIR="${SLURM_SUBMIT_DIR:-$PWD}"
 
cleanup() {
  rsync -a --ignore-missing-args \
    "$JOBSCRATCH/code/artifacts/" "$SUBMIT_DIR/artifacts/" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
 
rsync -a "$SUBMIT_DIR/code/" "$JOBSCRATCH/code/"
rsync -a "$SUBMIT_DIR/data/" "$JOBSCRATCH/data/"
 
if [[ -d "$SUBMIT_DIR/artifacts" ]]; then
  mkdir -p "$JOBSCRATCH/code/artifacts"
  rsync -a "$SUBMIT_DIR/artifacts/" "$JOBSCRATCH/code/artifacts/"
fi
 
rsync -a "$SUBMIT_DIR/lib/" "$JOBSCRATCH/lib/"
source "$JOBSCRATCH/lib/install_env.sh"
 
export ARTIFACTS_DIR="$JOBSCRATCH/code/artifacts"
export PERSISTENT_ARTIFACTS_DIR="/slurm/home/$USER/artifacts"
 
cd "$JOBSCRATCH/code"
python main.py 2>&1 | tee "$OUTDIR/training.log"

Estas líneas configuran el trabajo antes de empezar a ejecutarse. Slurm las usa para reservar recursos y planificar el job en la cola.

Directiva Significado
#SBATCH –job-name=base-train Nombre que verás en squeue. Útil para identificar el trabajo.
#SBATCH –partition=gpu Partición (cola). Usa sinfo para ver las disponibles (típicamente cpu, gpu).
#SBATCH –nodes=1 Número de nodos físicos a reservar. Para un único proceso, 1.
#SBATCH –ntasks=1 Número de tareas MPI. Sin MPI deja 1.
#SBATCH –cpus-per-task=19 Hilos de CPU por tarea. Ajusta a los workers de tu DataLoader.
#SBATCH –gres=gpu:nvidia_h100_nvl:1 1 GPU H100 NVL. Para cualquier GPU: gpu:1. Para 2: gpu:2.
#SBATCH –mem=31G RAM total del nodo. Pide solo lo necesario para no bloquear a otros.
#SBATCH –time=24:00:00 Límite de tiempo en HH:MM:SS. Al superarlo, Slurm mata el job.
#SBATCH –output=…/%j/terminal.out Fichero para stdout. %u=usuario, %j=Job ID.
#SBATCH –error=…/%j/terminal.err Fichero para stderr.

Pide siempre el mínimo de recursos que necesites. Cuanto más pidas, más tardará Slurm en planificarte y peor fair-share tendrás frente a otros usuarios. Si no estás seguro, lanza pruebas cortas con –time=00:30:00.

set -euo pipefail

Hace que el script aborte ante cualquier error: -e (sale si un comando falla), -u (error si usas una variable no definida), -o pipefail (un fallo dentro de un pipe propaga el código de error). Imprescindible para no continuar ejecutando si, por ejemplo, rsync del dataset falla.

OUTDIR="/slurm/home/$USER/output/$SLURM_JOB_ID"
mkdir -p "$OUTDIR"

Crea un directorio por Job ID en tu home donde se guardarán los logs (terminal.out, terminal.err y training.log). Sobrevive al borrado del scratch.

SCRATCH="${SLURM_TMPDIR:-/scratch/slurm/$USER/$SLURM_JOB_ID/tmp}"
JOBSCRATCH="${SCRATCH}/base-${SLURM_JOB_ID}"
mkdir -p "$JOBSCRATCH"

SLURM_TMPDIR lo define automáticamente Slurm cuando hay scratch local configurado; el ${VAR:-fallback} usa una ruta alternativa si no existe. Aquí trabajará el job: lectura/escritura rápidas porque suele ser NVMe local del nodo, no NFS.

cleanup() {
  rsync -a --ignore-missing-args \
    "$JOBSCRATCH/code/artifacts/" "$SUBMIT_DIR/artifacts/" 2>/dev/null || true
}
trap cleanup EXIT INT TERM

trap registra la función cleanup para que se ejecute siempre al terminar el script: tanto si finaliza con éxito (EXIT), si lo cancelas con Ctrl+C (INT), o si Slurm lo mata por tiempo (TERM). Así nunca pierdes los checkpoints del scratch.

Si Slurm mata el job con SIGKILL (señal 9, no capturable), el trap no se ejecuta. Esto ocurre típicamente al agotar el –time tras un periodo de gracia. Configura tu entrenamiento para guardar checkpoints periódicamente al PERSISTENT_ARTIFACTS_DIR como red de seguridad.

rsync -a "$SUBMIT_DIR/code/" "$JOBSCRATCH/code/"
rsync -a "$SUBMIT_DIR/data/" "$JOBSCRATCH/data/"
 
if [[ -d "$SUBMIT_DIR/artifacts" ]]; then
  mkdir -p "$JOBSCRATCH/code/artifacts"
  rsync -a "$SUBMIT_DIR/artifacts/" "$JOBSCRATCH/code/artifacts/"
fi

Trae el código y el dataset al scratch local antes de empezar a entrenar. Si existen artefactos previos (checkpoint de un entrenamiento anterior) los restaura, permitiendo reanudar sin volver a empezar.

rsync -a "$SUBMIT_DIR/lib/" "$JOBSCRATCH/lib/"
source "$JOBSCRATCH/lib/install_env.sh"

install_env.sh es responsabilidad tuya: típicamente carga módulos (module load cuda/12.x), crea/activa un venv, e instala dependencias con pip. Debe ser idempotente.

Un comando o instrucción es idempotente si puedes ejecutarlo una o varias veces y el resultado final siempre será exactamente el mismo. No importa cuántas veces lo repitas, no causará efectos secundarios no deseados después de la primera ejecución ni depende del resultado de ejecuciones anteriores.

export ARTIFACTS_DIR="$JOBSCRATCH/code/artifacts"
export PERSISTENT_ARTIFACTS_DIR="/slurm/home/$USER/artifacts"

Tu main.py debe leerlas para decidir dónde escribir:

  • ARTIFACTS_DIR: checkpoints frecuentes durante el entrenamiento (scratch, rápido).
  • PERSISTENT_ARTIFACTS_DIR: el checkpoint final o copias periódicas de seguridad (home, persistente).
cd "$JOBSCRATCH/code"
python main.py 2>&1 | tee "$OUTDIR/training.log"

2>&1 fusiona stderr en stdout y tee escribe a la vez por pantalla (que va a terminal.out) y al log persistente en el home.

Desde la raíz de tu proyecto:

sbatch train.sbatch

Slurm devolverá algo como:

Submitted batch job 12345

Ese número es el Job ID ($SLURM_JOB_ID). Lo necesitarás para consultar el estado y leer los logs.

Cosas que cambiarás casi siempre:

  • –job-name: pon un nombre descriptivo (resnet50-imagenet, llama-finetune-v3…).
  • –cpus-per-task, –mem, –time: ajusta a tus necesidades reales.
  • –gres: ver tabla siguiente.
  • main.py: añade los argumentos que necesites después.
Necesidad Directiva
Cualquier GPU disponible #SBATCH –gres=gpu:1
1 GPU H100 NVL específica #SBATCH –gres=gpu:nvidia_h100_nvl:1
2 GPUs del mismo nodo #SBATCH –gres=gpu:2
Sin GPU (partición CPU) Eliminar –gres y cambiar –partition=cpu

Lista las GPUs disponibles con:

sinfo -o "%P %N %G"

Modifica la última línea:

python main.py --epochs 50 --batch-size 64 --lr 1e-4 2>&1 | tee "$OUTDIR/training.log"

O mejor, parametriza el sbatch aceptando variables de entorno:

python main.py \
  --epochs "${EPOCHS:-50}" \
  --batch-size "${BATCH_SIZE:-64}" \
  2>&1 | tee "$OUTDIR/training.log"

Y lánzalo así:

sbatch --export=ALL,EPOCHS=100,BATCH_SIZE=128 train.sbatch
squeue -u $USER                # Solo tus trabajos
squeue --me                    # Equivalente moderno
squeue -p gpu                  # Todos los trabajos de la particion gpu

Códigos de estado más habituales: R (running), PD (pending), CG (completing), F (failed).

scontrol show job 12345
tail -f /slurm/home/$USER/output/12345/terminal.out
tail -f /slurm/home/$USER/output/12345/terminal.err
sacct -j 12345 --format=JobID,JobName,State,Elapsed,MaxRSS,ReqMem,ReqCPUS,AllocTRES%40

Útil para ajustar peticiones en el siguiente lanzamiento: si MaxRSS fue de 8G y pediste –mem=31G, estás desperdiciando memoria.

scancel 12345              # Cancela un job concreto
scancel -u $USER           # Cancela TODOS tus jobs (uso con cuidado)
scancel -n base-train      # Cancela por nombre

Para depurar sin pasar por la cola, abre una sesión interactiva con los mismos recursos:

srun --partition=gpu --gres=gpu:1 --cpus-per-task=4 --mem=8G \
     --time=01:00:00 --pty bash

Te dará una shell dentro de un nodo del clúster. Cuando termines, exit libera los recursos.

Síntoma Causa probable
Invalid partition specified El nombre de la partición no existe. Comprueba con sinfo.
Requested node configuration is not available Pides más recursos de los que tiene cualquier nodo (p. ej. 5 GPUs en un nodo de 4).
Job pasa horas en PD con razón Resources No hay nodos libres con tus requisitos; espera o reduce la petición.
Job pasa horas en PD con razón Priority Otros trabajos van por delante. Tu fair-share se restaura con el tiempo.
CUDA error: no CUDA-capable device Olvidaste –gres=gpu:… o estás en la partición cpu.
DUE TO TIME LIMIT Tu job superó –time. Aumenta el tiempo o guarda checkpoints.
oom-kill Te pasaste de –mem. Aumenta o reduce batch size.
  • Pide solo lo necesario: recursos sobrantes empeoran tu prioridad y bloquean a otros.
  • Guarda checkpoints periódicos en PERSISTENT_ARTIFACTS_DIR, no solo al final.
  • Usa el scratch para I/O intensivo; el home vía NFS es lento.
  • Lanza un job corto de prueba (–time=00:15:00) antes de uno de 24 horas.
  • Versiona tu sbatch junto al código en Git para reproducibilidad.
  • No ejecutes entrenamientos directamente en el nodo de login; usa sbatch o srun.

Disponibles dentro del script:

Variable Contenido
$SLURM_JOB_ID ID numérico del trabajo.
$SLURM_JOB_NAME Valor de –job-name.
$SLURM_SUBMIT_DIR Directorio desde el que se lanzó sbatch.
$SLURM_TMPDIR Scratch local del nodo (si está configurado).
$SLURM_CPUS_PER_TASK Valor de –cpus-per-task; útil para OMP_NUM_THREADS.
$SLURM_GPUS_ON_NODE Nº de GPUs asignadas al job en este nodo.
$SLURM_NTASKS Valor de –ntasks.
$SLURM_NODELIST Lista de nodos asignados.
  • knowhow/slurm/guias_de_uso.1779293690.txt.gz
  • Última modificación: 2026/05/20 16:14
  • por adriancr