NOTES A – How a model reaches its engine#
The question#
A machine-learned forcefield is not evaluated by LAMMPS. LAMMPS drives a separate engine process over MDI, ships it the atoms, and takes back energy and forces. Three things therefore have to line up, and they belong to different owners:
question |
owned by |
where it lives |
|---|---|---|
which model |
the flowchart |
|
which engine serves it |
the model |
– nothing, before this campaign |
how to launch it here |
the machine |
|
The middle row was missing, and its absence was filled by guessing from the model’s file name.
What the code used to do#
initialization.py chose between an MDI run and a native pair style on the
name of the model file:
if "mliap" in model:
pair_style mliap ...
elif model.endswith(".mace.pt"):
fix mdi/qm ... # the MDI engine
else:
pair_style mace ... # LAMMPS evaluates it itself
and lammps.py hard-coded one engine per machine, because gpu-code was a
single template and SEAMM_FF – the variable mace-mdi reads – was the
only way a model reached an engine.
That works for exactly as long as there is one engine. A file name says nothing
about how a model is evaluated: it is chosen by whoever trained it, and it
changes when someone renames a file. An xnns checkpoint named for what it
is, 2026-08-08.mace_les_xnns.pt, does not end in .mace.pt, so it took
the else branch and died with:
ERROR: Unrecognized pair style 'mace' (src/force.cpp:275)
Reaching that pair style needs a LAMMPS built with a MACE pair style – which is the build the MDI route exists so as not to need. Nobody was using it.
The same test also gated the comm_modify line that an MDI run needs, so it
was missing from exactly the runs that had just been routed to MDI. That would
have been the next failure.
What it does now#
The plug-in decides whether a model goes to an engine, not which engine. Everything except an ML-IAP model – which LAMMPS really does evaluate itself – is served over MDI:
if "mliap" in model:
pair_style mliap ...
else:
fix mdi/qm ...
The machine decides which engine. gpu-code is the only place that
names one. To make that possible the model is offered both ways engines take
it:
SEAMM_FFAn environment variable.
mace-mdireads it.{MODEL}Substituted into the command like
{NTASKS}, by the executor’s existing mechanism (command.format(**config, **ce)). For engines taking the model as an argument:xnns mdi --ckpt {MODEL}.
Both are always set, so nothing in lammps_step knows or needs to know which
engine is configured. Adding a third engine is a lammps.ini edit.
Why not detect the model type#
Two alternatives were considered and rejected.
Keep using the file name, but look for ``xnns`` in it. It would have worked
that day – the files are named that way – and it is one line. It was rejected
because the failure mode is silent: rename a file and the run starts the wrong
engine, which gives KeyError: 'hidden_irreps' if you are lucky and wrong
numbers if you are not.
Identify the model by reading it. This is feasible without torch, which the
SEAMM environment does not have and must not have: a .pt file is a zip, and
the pickled module references distinguish the formats. A prototype identified
an xnns checkpoint, a mace-torch model and a deployed TorchScript
archive correctly using only the standard library.
It was rejected because it answers a question nobody needs answered. Detection matters only while more than one engine is in play, and the decision below means there is one.
The decision: xnns is the mechanism#
xnns serves every model family it knows – MACE, NequIP, Allegro, CACE,
SchNet, ANI, PhysNet, HDNNP, BAMBOO – through one interface, and its MDI
engine speaks the same protocol lammps_step already drives. It is therefore
the single mechanism for machine-learned forcefields, and where it does not yet
cover something, that is a gap to close in xnns rather than a reason to add
a second path here.
One consequence is worth recording, because it would otherwise look like an
oversight: xnns cannot load a mace-torch model. MDIEngine.from_checkpoint
expects the trainer’s {"model": ..., "cfg": ...} and raises
TypeError: 'ScaleShiftMACE' object is not subscriptable on a bare model
object. This did not need solving, because the older MACE models were being
retrained – they carried a density error from the DFT functional used and were
not scientifically meaningful. Had they been kept, the choice would have been
between a transitional fallback to mace-mdi and asking for the loader to be
generalised.
mace-mdi remains usable: it is a gpu-code line, and the shipped
lammps.ini documents it as the MACE-specific alternative.
What this leaves#
File names no longer decide behaviour anywhere in this path. Checkpoints can be named for what they are.
A new engine is a configuration change, not a code change.
ff_form()still returns"PyTorch". The engine is a sub-distinction of it, and wideningff_formwould have rippled into the classical/Kokkos/MDI logic for no gain.