Adding a Supercomputer Machine#
This section describes how to add a permanent machine definition to machines.py. These definitions are used by Nexus to generate
job submission scripts, and subsequently to help submit and monitor jobs. Within Nexus, Supercomputer refers to any machine that
uses a batch scheduler such at Slurm or PBS and not the size of the machine. This could include even a small workstation where a
batch scheduler is installed and used to schedule jobs overnight. If a new machine only requires common or default behaviors, the
new machine definition will require minimal Python code due to the functionality provided by the Supercomputer base class. Runs
on machines that do not have a batch scheduler should use one of the workstation machine definitions.
We encourage submission of new permanent machine definitions to the Nexus repository on GitHub to reduce the need for ongoing local maintenance and to share them with other users. For a one-off local computational cluster, the same class and instantiation patterns can be used in``~/.nexus/local_machines.py``.
In the following we describe a step-by-step process for adding a new machine.
Choose the Closest Existing Machine#
To minimize the work required, start by finding a machine that uses the same scheduler and run launcher. For example:
Slurm plus
srun: seeAndes,Rhea,Frontier,Leonardo.Slurm plus
mpirun: seeCadesSlurm,Tomcat3,Improv.PBS plus
mpiexec: seePolaris,Aurora.PBS plus
aprun: seeBlueWatersXE,BlueWatersXK,Theta.LSF plus
jsrunorlrun: seeSummit,Lassen.
If the header and launcher behavior are shared by several machines, consider
deriving from the existing intermediate class, such as NerscMachine or
SnlMachine. Otherwise, derive directly from Supercomputer.
Adding the Machine Class#
Place the new class near similar machines in machines.py. At minimum, define
a unique lower-case name, the account/capability flags, and
write_job_header.
class NewMachine(Supercomputer):
name = 'newmachine'
requires_account = True
batch_capable = True
def post_process_job(self,job):
if len(job.run_options)==0:
job.run_options.add(
N = '-N {}'.format(job.nodes),
n = '-n {}'.format(job.processes),
c = '-c {}'.format(job.threads),
)
#end if
#end def post_process_job
def write_job_header(self,job):
if job.queue is None:
job.queue = 'regular'
#end if
c = '#!/bin/bash\n'
c += '#SBATCH -A {}\n'.format(job.account)
c += '#SBATCH -p {}\n'.format(job.queue)
c += '#SBATCH -J {}\n'.format(job.name)
c += '#SBATCH -t {}\n'.format(job.sbatch_walltime())
c += '#SBATCH -N {}\n'.format(job.nodes)
c += '#SBATCH --ntasks-per-node={}\n'.format(job.processes_per_node)
c += '#SBATCH --cpus-per-task={}\n'.format(job.threads)
c += '#SBATCH -o {}\n'.format(job.outfile)
c += '#SBATCH -e {}\n'.format(job.errfile)
if job.user_env:
c += '#SBATCH --export=ALL\n'
else:
c += '#SBATCH --export=NONE\n'
#end if
return c
#end def write_job_header
#end class NewMachine
Please note: the only required class method is write_job_header. Defining the post_process_job function can be useful in special cases, see below.
Important class details:
nameis the key used byMachine.get,job(machine=...), and the tests. It must be unique inMachine.machines.requires_account = Truemakes jobs requireaccount; the machine tests supplyABC123automatically for such machines.write_job_headerreturns only the batch-script header and any setup lines.Supercomputer.write_jobappends environment exports and the run command.Use
pre_process_jobto set defaults or hardware variants before generic node/core calculations. Usepost_process_jobto add launcher options afterjob.nodes,job.processes,job.processes_per_node, andjob.threadshave been finalized.Keep
process_jobbehavior idempotent. The tests call it more than once on already-processed jobs, so avoid appending duplicate options or mutating machine-wide state in a way that changes later jobs unexpectedly.
Understanding the role of the Base Class#
Supercomputer.process_job fills in missing cores or nodes, computes
processes, processes_per_node, processes_per_proc, ppn, applies the
machine account default, sets OMP_NUM_THREADS, and then calls
process_job_options.
Default launcher handling is limited:
mpirunadds-np <processes>.mpiexecadds-n <processes>.aprunadds-n <processes>and, for threaded jobs,-d <threads>.runjobadds Blue Gene style--np,-p,$LOCARGS, and--envs.ibrunadds-n <processes> -o 0.srun,jsrun, andlrunintentionally add nothing by default.
For launchers that need machine-specific options, override post_process_job
or process_job_options. Prefer post_process_job when you only need to add
or adjust job.run_options; override process_job_options when the base
launcher behavior is not appropriate at all.
Options.write() sorts option keys before building the command, so choose
stable keys if test output ordering matters.
Registering the Machine#
At the bottom of machines.py, add an instance to the #Known machines block:
# nodes sockets cores ram qslots qlaunch qsubmit qstatus qdelete
NewMachine( 1000, 2, 32, 256, 1000, 'srun', 'sbatch','squeue','scancel')
The positional arguments are:
nodes: total compute nodes.procs_per_node: usually sockets or processor packages per node.cores_per_proc: cores per socket/package.ram_per_node: memory per node, in GB.queue_size: maximum queued/running jobs Nexus should track.app_launcher: command used inJob.run_command.sub_launcher: command used bysub_commandand submission file names.queue_querier: queue-status command/parser. Supported values includeqstat,qstata,squeue,sacct,llq,bjobs, andtest_query.job_remover: cancellation command. Existing support includesqdelandscancel; add support if the scheduler needs another remover.
cores_per_node is computed as procs_per_node * cores_per_proc. Use the
actual socket/package layout when it matters for binding or
processes_per_proc.
Updating the Machine Tests#
Adding the instance registers the machine in Machine.machines, so the generic
machine tests will include it automatically.
Update tests/test_machines.py::test_job_run_command by adding entries to
job_run_ref for the new machine. Unless the test has a special case for the
machine, add all six standard job shapes:
n1n1_p1n2n2_t2n2_t2_en2_t2_p2
For a permanent machine, also update test_write_job by adding the expected
batch script to job_write_ref; otherwise that test will hit a missing
reference for the newly registered machine.
To print fresh references, run this command from the Nexus repository root:
dev_utils/write-job-ref
This writes updated_job_ref_table.txt to the current working directory. The
file separates the entries for job_run_ref in test_job_run_command from the
entries for job_write_ref in test_write_job. Copy only the lines for the new
machine into the corresponding table, then review them manually. The generated
commands and submission files are useful starting points, not a substitute for
checking scheduler syntax against the machine documentation.
Running the Tests#
From the repository root, run the machine tests that cover the new definition:
python -m pytest \
nexus/tests/test_machines.py::test_process_job \
nexus/tests/test_machines.py::test_job_run_command \
nexus/tests/test_machines.py::test_write_job
If the new machine requires an extra mandatory job field, either provide a safe
default in the machine class or add a narrow special case in the relevant test,
as summit and flight already do.
If you are also using and configuring QMCPACK alongside Nexus, you can more simply run the machines test using ctest -R ntest_nexus_machines, or all the Nexus tests with ctest -R nexus,
from the QMCPACK build directory.