NOTES B – GPUs, tasks, and what the scheduler is telling us#
Making GPUs requestable per job turned out to expose three assumptions in this plug-in that had held only because nothing had ever asked for a GPU explicitly. They are recorded together because they share a root: an allocation describes what a job was given, not what the calculation needs.
A GPU in the allocation is not a reason to use gpu-code#
NGPUS comes from the scheduler. seamm_exec reads SLURM_JOB_GPUS:
if "JOB_GPUS" in ce:
ce["NGPUS"] = len(ce["JOB_GPUS"].split(","))
and this plug-in used its presence alone to select gpu-code:
if "NGPUS" in ce:
cmd = ["{gpu-code}"]
So the first classical job submitted with --gres=gpu:1 – an OPLS-AA water
equilibration – was handed to the MACE engine, which has no model to serve.
SEAMM_FF is "Unknown" for a non-PyTorch forcefield, and the engine died
with FileNotFoundError: 'Unknown' before LAMMPS ran at all.
gpu-code serves two unrelated purposes: a Kokkos command is how a
classical forcefield uses a GPU, and an MDI command serves a machine-learned
one. The test is therefore not “are there GPUs” but “does this command suit
this forcefield”, which is decided by whether it drives MDI. A classical
forcefield with an MDI gpu-code now runs on the CPU and says why.
GPU indices are not the scheduler’s indices#
GPU selection went through GPUtil, which reads nvidia-smi and therefore
reports physical indices, knowing nothing about CUDA_VISIBLE_DEVICES. A
scheduler that allocates a subset of a node’s GPUs sets that variable, and doing
so renumbers the devices: given physical GPU 1, the job’s own device 0 is
that GPU.
The step reported 1. The calculation would have run on a card it had not been allocated, beside whichever job actually held it, while the reserved one sat idle. Selection now happens in the numbering CUDA itself uses.
mdi_bind.sh had the same bug from the other end: it exported
CUDA_VISIBLE_DEVICES unconditionally. That variable is not composable –
setting it again is interpreted against the machine’s full set of devices, not
against the allocation – so writing an index there was itself a way to land on
the wrong GPU. It now leaves a scheduler’s value alone, and maps back to a
physical index only for the nvidia-smi monitor, which ignores the variable.
Neither bug appears where every job sees every GPU, which is why they went unnoticed for so long: they need a scheduler handing out individual GPUs.
An MDI run needs two tasks#
An MDI gpu-code launches the engine and the LAMMPS driver as separate MPI
ranks, and those counts are written into the command rather than derived from
the allocation. A job given fewer tasks than the command asks for dies inside
mpirun with:
All nodes which are allocated for this job are already filled.
which mentions neither MDI, nor tasks, nor anything to change – and arrives
wrapped in ERROR conda.cli.main_run:execute(127).
This is easy to hit precisely because the obvious reading is wrong: asking for
one GPU and one task looks correct and cannot work. The step now counts the
ranks the command asks for, compares them with SLURM_NTASKS, and says so
before launching.
Configuration consequences#
On a machine with GPUs, gres should be overridable rather than defaulted.
A default makes every job request a GPU, including CPU-only work, which
serialises the queue to the number of cards regardless of any concurrency
limit. Jobs that need one ask for it.
Nothing requests a GPU automatically. A machine-learned forcefield run gets one only if the submission includes it. Whether that should be inferred is left open: the forcefield is often only known at run time, from a variable, so the information is not reliably available at submission – which is the same reason a step cannot declare its own resource needs.