Leonardo (CINECA)
Run src_method on the Booster partition of Leonardo, on one GPU or all four of a node.
Leonardo is a EuroHPC pre-exascale system at CINECA. Its Booster nodes have 32 Xeon 8358 cores, 512 GB of memory and 4 A100-SXM-64GB, every pair connected by NVLink. Three things differ from a workstation:
- The driver predates CUDA 13. The nodes run driver 535, while the
gpu-nvidiaextra installs CuPy for CUDA 13. NVIDIA's forward-compatibility package fills the gap without root. - There is no local disk, and
/tmpis a small in-memory file system. Large stacks, and the environments that the out-of-core sweep spills, go to the Lustre scratch area,$CINECA_SCRATCH. - The home directory has a small quota. Keep the
uvcache and Python installations in the project's work area.
Setup
Once, on a login node, from a clone of the repository in your work area (or from
your own project that depends on src_method[gpu-nvidia]):
# Keep the uv cache and Python installations out of $HOME.
export UV_CACHE_DIR=$PWD/.uv-cache UV_PYTHON_INSTALL_DIR=$PWD/.uv-python
uv sync --extra gpu-nvidia
# Unpack NVIDIA's forward-compatible libcuda for CUDA 13.
mkdir -p .cuda-compat && cd .cuda-compat
curl -sSfO https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-compat-13-4-615.71.09-1.el8.x86_64.rpm
rpm2cpio cuda-compat-13-4-*.rpm | cpio -idm && cd ..Add --group bench to uv sync to run the benchmarks. saldo -b
lists the budgets you can charge jobs to.
Job script
Save this as job.sh in the same folder, set your account, and submit any script
with sbatch job.sh my_script.py --its-options. It runs from the folder you submit
from, with the four GPUs of one node visible to a single process.
#!/usr/bin/env bash
#SBATCH --job-name=src
#SBATCH --account=<your_account>
#SBATCH --partition=boost_usr_prod
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=32
#SBATCH --gres=gpu:4
#SBATCH --mem=0
#SBATCH --exclusive
#SBATCH --time=04:00:00
#SBATCH --output=%x_%j.out
set -euo pipefail
cd "$SLURM_SUBMIT_DIR"
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK OMP_PLACES=cores OMP_PROC_BIND=spread
# No local disk, and /tmp is small: temporary files go to Lustre scratch.
export TMPDIR=$CINECA_SCRATCH/tmp
mkdir -p "$TMPDIR"
source .venv/bin/activate
# CuPy compiles its kernels on first use; a persistent cache saves that next time.
export CUPY_CACHE_DIR=$PWD/.cupy-cache
# Driver 535 predates CUDA 13: load the forward-compatible libcuda first.
export LD_LIBRARY_PATH=$PWD/.cuda-compat/usr/local/cuda-13.4/compat${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}
srun python "$@"For one GPU, use --gres=gpu:1.
In the script, split the sweep among the four GPUs and spill to scratch:
import os
from src_method import Resources, src
out = src(
*stack,
chi_out=2000,
device="gpu",
resources=Resources(
devices=4, scratch_dir=os.path.join(os.environ["CINECA_SCRATCH"], "src")
),
)Queues
- Short jobs: for anything under 30 minutes, add
--qos=boost_qos_dbg --time=00:30:00. It starts quickly but runs at most two jobs per user. - Longer jobs: the default QoS can queue for hours. Priority does not depend
on the time limit, but the scheduler backfills short jobs into gaps, so a tight
--timestarts sooner.scontrol update JobId=<id> TimeLimit=<t>can lower the limit of a queued job, never raise it.
Performance notes
- The first run of a job is slower: CuPy compiles its kernels (about 50 s with
an empty cache, hence
CUPY_CACHE_DIR), and in our runs the first SRC call of a job took a further 20 s or so, for a reason not yet isolated. - Lustre is shared. Input stacks read from scratch, and environments spilled to
it, run at whatever bandwidth the file system has to spare; reading a stack once
before the sweep (e.g.
catits files to/dev/null) avoids paying for a cold read inside the timed run. - Host memory sets how much reaches the disk. The sweep keeps 90% of the
available memory as its host budget; environments beyond it go to
scratch_dir. The out-of-core benchmark reports where the environments of its reference runs ended up.