Repository navigation
[Bug] v3.11.0-beta10: esolver_type=dp forces deviate from model predictions (MDCell neighbor-list path) #8071
Description
Summary
In v3.11.0-beta10, forces computed by esolver_type = dp (MD path) deviate from the model's true predictions. Compared against DFT single-point forces as ground truth, the ABACUS DP path adds ~0.02 eV/A RMS error on top of the model's intrinsic error (~0.008 eV/A via the reference Python API).
Environment
- ABACUS v3.11.0-beta10 (develop branch, source snapshot 2026-09-30), cmake Release
USE_CUDA=ON(Tesla V100-SXM2-16GB, sm_70),ENABLE_MPI=ON,ENABLE_LCAO=ON,ENABLE_LIBXC=ON- DeePMD-kit v3.2.0 C API (conda-forge, cuda129 build); model trained with the same v3.2.0 (PyTorch backend)
- OS Ubuntu 22.04, driver 580.178.04, single MPI rank
System & model
- Li20Ge2P4S24 (50 atoms), LCAO gamma-only, ONCV PBE pseudopotentials + numerical orbitals, ecutwfc=50, precision=double
- Model: se_e2_a (rcut 6.0, sel [23,5,7,26]), trained on 300 K AIMD frames of the same cell, frozen to
.pth
Reproduction
- Run MD with the DP esolver (10 steps is enough):
calculation md, esolver_type dp, pot_file <frozen.pth>, md_nstep 10, md_dt 1, md_tfirst 300, md_tlast 300, md_dumpfreq 1 - Parse forces of
MDSTEP: 0fromOUT.ABACUS/MD_dump. - Evaluate the same frozen model on the same coordinates/cell with
deepmd.infer.DeepPot(...).eval(coords, cells, atom_types, atomic=True).
Expected: agreement to ~1e-8.
Observed: mean |dF| ~ 0.007 eV/A, max ~ 0.07 eV/A. The deviation is concentrated on a few specific atoms (indices 24, 25, 26, 29, 32–35; P and S atoms), most atoms agree to ~0.001–0.008 eV/A.
Ground-truth check
Single-point DFT with the same LCAO settings (cal_force 1) on the same structure:
| path | RMSE vs DFT forces | max |dF| |
|---|---|---|
| ABACUS esolver dp | 0.0202 eV/A | 0.0763 |
| deepmd python API (same model) | 0.0076 eV/A | 0.0209 |
So the ABACUS DP path adds ~0.02 eV/A error beyond the model's intrinsic error.
Notes / suspected cause
- Running the same ABACUS job with
CUDA_VISIBLE_DEVICES=""(CPU-only inference) gives bit-identical forces, so it is not a GPU/precision issue. - The force columns are position-consistent (comparing MDSTEP k forces against the MDSTEP k positions gives the smallest difference), so it is not a frame offset.
- Energy agrees with the Python path.
- Suspect: the new MDCell-based runner in
source/source/source_esolver/esolver_dp.cpp(runner()) builds a neighbor list with MDCell'sneighbor_search()and passes it asdeepmd::hpp::InputNlisttodp.compute. The explicit-nlist path may differ from deepmd's internal neighbor list (distance-sorted, min-image) in ordering/ghost-image handling, leading to wrong per-type neighbor selection for some atoms. This MD path is new in the v3.11.0-beta9/beta10 cycle (Parallel MD / MDCell domain decomposition). - The older non-MDCell code path in the same file (
cal_force()via UnitCell) is presumably unaffected (untested here).
The frozen model, STRU/INPUT files and comparison scripts are available on request.
Activity
19hello commentedon Oct 9, 2026
Could you provide the minimal reproducible INPUT, STRU, DeePMD model (.pth/.pb), MDSTEP: 0 forces from MD_dump, and the corresponding Python DeepPot.eval() output? I need these files to compare the native DeePMD neighbor list, the external InputNlist, and the post-accumulation ABACUS forces.
MarkYoungIV commentedon Oct 9, 2026
Thanks for looking into this! I've prepared a full reproduction package:
Repro package (secret gist): https://gist.github.com/MarkYoungIV/9fe10e29d2fedee0fa7d22e87d3c9e66
The gist contains
issue8071_repro.zip.b64(base64 of the zip, base64 used because gists don't take binaries):INPUT,STRU(Li20Ge2P4S24, 50 atoms), frozen se_e2_a model (frozen_seed1.pth)MD_dumpfrom a 10-step MD run (MDSTEP 0 forces)esolver_dp_dump.txt— the exact neighbor list ABACUS passes to DeePMD (owned+ghost atoms, per-atom rows)compare_issue8071.py— reproduces everything below in one commandResults (matches the report):
Additional findings:
forward_lower) with ABACUS's own list and folding the ghost force entries back onto the owned atoms (DomainDecomposition::accumulate_ghost_forces). So the framework accumulation and unit conversions are fine.full_list=True, periodic) returns the same 2280 pairs; per-atom(j, shift)sets match 50/50 atoms.need_sorted_nlist_for_lower() == True.sel(atoms delete snap_psialpha #24/refactor potential and add DELETE_MUL_PTR() #25 are P with 28 S-neighbors vs sel_S = 26; the other large deviations are on the high-coordination P/S atoms), which produces exactly the reported residual.InputNlistremoves the discrepancy: rmse 1.65e-05 / max 5.5e-05 eV/A (see script output).Suggested fix: in
source/source/source_esolver/esolver_dp.cpprunner()(MDCell branch), sort each owned atom's neighbor indices by distance when buildingdeepmd::InputNlist, matching the LAMMPS deepmd convention. We have a small patch for this if useful.One related, more severe finding while testing this path with message-passing descriptors (DPA-2/repformers): the same
InputNlistconstruction omits DeePMD's comm setup (nswap/sendlist/sendproc/world). Consequently theborder_opghost-embedding exchange never happens (forward exchange: energies off by 20–37 eV, forces ~0.5 eV/A off), and with MPI initializedMPI_Comm_rank(NULL)aborts the run (MPI_ERR_COMM: invalid communicator). Passingnswap=1with a ghost→owned self-exchangesendlistandworld = &MPI_COMM_WORLD(while letting the framework keep doing the reverse force accumulation) makes DPA-2 MD agree with the native API to ~2e-05 eV/A. Happy to file that as a separate issue with a patch if you're interested.19hello commentedon Oct 9, 2026
Thanks for providing the complete reproduction package. I have reproduced the issue and confirmed the suspected root cause.
The ABACUS force plumbing and ghost-force accumulation are correct. Using ABACUS's original
InputNlistwith DeepMD's external-nlist path gives the same forces as those written toMD_dump, while the neighbor sets themselves match the native DeepMD neighbor sets.The discrepancy comes from the ordering of the neighbors:
se_e2_adescriptor.sel-based neighbor truncation, different neighbor ordering can result in a different subset of neighbors being selected, even though the complete neighbor sets are identical.In my reproduction, the force difference was:
MarkYoungIV commentedon Oct 10, 2026
Great, thanks for the quick turnaround! The numbers in your reproduction (RMSE 1.839e-02 -> 1.839e-05 after sorting) match our independent verification exactly, and the PR looks right to us — keeping the sorted storage alive across the DeePMD call is exactly what our testing showed is needed.
As for the separate message-passing issue you mentioned (missing
nswap/sendlist/sendproc/world), I've now filed it with a full reproduction package and a patch:#8108
It covers both failure modes (the
MPI_Comm_rank(NULL)abort at MD step 1 on MPI builds, and the wrong energies/forces without MPI), plus the fix that makes DPA-2 MD agree with the native API to ~2e-05 eV/A. The patch touches the samerunner()function but a different hunk than #8104, so they should coexist.