DeePMD-kit Model Formats#
In the pt_expt (PyTorch experimental) backend, three file formats are in use, each mapping to a distinct inference path, neighbor-list strategy, and deployment stack. Understanding which format a model is in is critical β loading the wrong format through the wrong code path can produce silently incorrect results.
| Format | How produced | Inference path | Neighbor-list subsystem |
|---|---|---|---|
.pt | Training checkpoint (torch.save) | Dense lower (forward_common_lower) | NeighborList (nlist-form) |
.pth | JIT-compiled (torch.jit.script) | Dense lower | NeighborList (nlist-form) |
.pt2 | AOTInductor export (torch.export + aot_compile) | Graph-native | NeighborGraph (graph-form) |
The .pt and .pt2 formats are the active focus of current pt_expt development; .pth is the legacy TorchScript path from the stable pt backend.
.pt β Training Checkpoint#
A .pt file is a standard PyTorch state-dict checkpoint produced by the pt_expt trainer (torch.save({"model": wrapper.state_dict()})). It stores trainable weights and model hyperparameters in _extra_state["model_params"].
When loaded by deepmd/pt_expt/infer/deep_eval.py (_load_pt), the checkpoint is reconstructed into a live model, but the eager runner installs model.forward_common_lower(...) as its call target β the dense lower path .
For most models this is correct. However, for graph-eligible DPA1 (descriptor type: dpa1 or se_atten_v2 with attn_layer: 0), the model's public forward() instead routes through call_common β graph-native path in deepmd/pt_expt/model/ener_model.py. The two paths are not numerically equivalent when descriptor mean statistics (davg) are nonzero . See the DPA1 path bug section below.
.pt2 β AOTInductor Graph Export#
A .pt2 file is an Ahead-of-Time Inductor (AOTInductor) compiled artifact produced by torch.export + aot_compile, packaged as a ZIP archive containing compiled kernels and metadata (lower_input_kind, has_message_passing, etc.) .
Key properties:
- Dynamic edge axis β one artifact evaluates any system size (proven across multiple system sizes at β€1e-10 parity) .
- Multi-rank variant β message-passing models (DPA2, DPA3) embed a
model/extra/forward_lower_with_comm.pt2sub-artifact with an 8-tensor comm ABI for MPI ghost exchange . - Graph-native path β calls
forward_lower_graph_exportable/forward_lower_graph_exportable_with_commonEnergyModel, not the dense lower.
Export Gate#
Export is gated by model_uses_graph_lower in deepmd/pt_expt/utils/serialization.py, which requires "energy" in the model's atomic output definition . Non-energy models (dos, dipole, polar) currently have no deployment consumers for .pt2 and the gate blocks export rather than produce an unusable file .
Loading#
- Python:
torch._inductor.aoti_load_package() - C++/LAMMPS:
DeepPotPTExptviatorch::inductor::AOTIModelContainerRunnerCpu
Neighbor-List Strategy by Format#
Format choice determines which neighbor-list subsystem is used :
nlist-form (.pt / Python/ASE)#
Uses the NeighborList strategy, injected at forward_common/call_common. Configured via nlist_backend:
| Value | Behavior |
|---|---|
"auto" (default) | Uses VesinNeighborList (O(N) cell-list) when available; silently falls back to native O(NΒ²) |
"vesin" | Strict vesin β raises ValueError if unavailable or model is spin/hessian |
"native" | Unconditional dense O(NΒ²) builder |
The O(NΒ²) native baseline extends coords into ~27 periodic-image regions via extend_coord_with_ghosts in deepmd/dpmodel/utils/nlist.py .
graph-form (.pt2 / AOTI / C++)#
Uses the NeighborGraph subsystem, controlled by neighbor_graph_method :
| Value | Backend | Device |
|---|---|---|
"legacy" / "dense" (default) | O(NΒ²) builder | Any |
"vesin" | vesin.torch cell-list | Device-following |
"nv" | nvalchemiops GPU cell-list | CUDA-only, frame-batched |
"ase" | ASE per-frame builder | CPU |
There is no "auto" selector for the graph path β callers must specify explicitly .
C++ LAMMPS Bottleneck#
The C++ DeepPotPTExpt::compute() path builds edges via createEdgeTensors() β a CPU C++ loop over all neighbor pairs β then copies results to GPU with multiple .clone().to(device) calls. At 65,536 atoms this overhead accounts for 3β6Γ the model inference time itself, negating the AOTI compilation speedup . The Python path avoids this by building edges GPU-natively via VesinNeighborList or the nv backend. See issue #5595 and issue #5574 for ongoing fix work.
DPA1 Path Bug (.pt Checkpoint)#
Issue #5862 (open as of 2026-07-18): DeepPot(model.ckpt.pt) on a graph-eligible DPA1 model silently routes through the dense lower path instead of the graph-native path, producing catastrophically wrong outputs .
Trigger conditions (all required):
pt_exptbackend- Descriptor
type: dpa1orse_atten_v2withattn_layer: 0 set_davg_zero: false(default) β nonzerodavg- Loaded via
DeepPot(*.pt)ordp --pt-expt test -m model.ckpt.pt
Root cause: The dense DPA1 body (deepmd/dpmodel/descriptor/dpa1.py) retains a phantom padding-neighbor residual of -davg/dstd for all vacant neighbor slots. Models with large sel (e.g., sel=416 for OMat24) have most dense slots filled by padding, massively amplifying the residual. The graph path omits padding neighbors entirely .
Observed magnitude on an OMat24 checkpoint:
| Path | Energy MAE | Force MAE |
|---|---|---|
model.forward() (graph-native) | 0.04 eV | 0.03 eV/Γ |
DeepPot(.pt) / dp test (dense) | 2600 eV | 13 eV/Γ |
Related fix: PR #5785 (merged 2026-07-16) fixed DescrptDPA1.call routing via _call_graph_adapter, but issue #5862 specifically tracks the checkpoint-loading failure where _load_pt unconditionally installs the dense lower runner . The structural fix must make DeepPot(.pt) select the graph-native path for graph-eligible DPA1 checkpoints. The recommended regression guard is a graph-eligible DPA1 fixture with nonzero davg in source/tests/pt_expt/infer/test_deep_eval_pt_checkpoint.py.
Deployment Compatibility Summary#
| Format | Python inference | C++ / LAMMPS | Non-energy models | Multi-rank (MPI) |
|---|---|---|---|---|
.pt | β
DeepEval | β | β | β |
.pth (TorchScript) | β
DeepEval | β
DeepPotPT | Partial | β |
.pt2 (graph) | β
DeepEval graph fast-path | β
DeepPotPTExpt | β (gated; #5806) | β (with-comm artifact) |
Key limitations to be aware of :
.pt2is energy-only β themodel_uses_graph_lowergate in serialization blocks export for dos/dipole/polar models because no C++ consumer exists for those outputs.DeepTensorhas nopt_exptsupport β dipole/polar models cannot use either dense or graph.pt2schema inpt_expt.- LAMMPS consumes energy models only, via
DeepPotPTExpt.
For lifting these restrictions, see companion issues #5805 (training-side generalization) and #5806 (deployment consumers by output type).
Key Source References#
| Item | Location |
|---|---|
.pt loader (_load_pt) | deepmd/pt_expt/infer/deep_eval.py |
Graph export gate (model_uses_graph_lower) | deepmd/pt_expt/utils/serialization.py |
Graph-exportable entry points (forward_lower_graph_exportable) | deepmd/pt_expt/model/ener_model.py |
| Dense padding-neighbor residual (DPA1) | deepmd/dpmodel/descriptor/dpa1.py |
| O(NΒ²) nlist baseline | deepmd/dpmodel/utils/nlist.py |
| Vesin nlist-form builder | deepmd/pt_expt/utils/vesin_neighbor_list.py |
| Vesin graph-form builder | deepmd/pt_expt/utils/vesin_graph_builder.py |
| nvalchemiops graph-form builder (CUDA) | deepmd/pt_expt/utils/nv_graph_builder.py |
| PR #5785 β DPA1 routing fix | github.com/deepmodeling/deepmd-kit/pull/5785 |
Issue #5862 β .pt checkpoint path bug | github.com/deepmodeling/deepmd-kit/issues/5862 |
Issue #5806 β .pt2 energy-only gate | github.com/deepmodeling/deepmd-kit/issues/5806 |
| Issue #5595 β C++ LAMMPS edge-construction bottleneck | github.com/deepmodeling/deepmd-kit/issues/5595 |
| KB: Neighbor List Backend Integration | |
| KB: DPA1 Inference Bugs |