Skip to content

Commit 31baf23

Browse files
Longyun Shenclaude
andcommitted
pair_matgl usability and performance follow-up: style registration, GPU export, ghost-row trimming, docs
Follow-up to #815/#770. Building the plugin from a clean tree per the README exposed independent defects (full analysis, measurements and job- level verification in the PR description): 1. The documented CMake build never registers the pair styles -- the ML-MATGL/KOKKOS sources are not included; add the cmake includes so a first build works without the CI workaround. 2. The exported TorchScript model cannot run on a GPU: spherical_bessel_smooth materialises torch.arange / torch.tensor on the default device; pin them to the input's device. 3. Ghost rows are still embedded although #815's edge folding guarantees they are isolated nodes (no incoming message, zero force): size the node buffers to nlocal. Measured 3.00x model-node work at 32 A (1,248 local + 2,502 ghost), 1.82x at 64 A, 1.51x at 96 A; end-to-end 1.71x / 1.46x / 1.38x at those sizes. 4. export_matgl_checkpoint.py imports modules that no longer exist; fix to the public entry points. 5. python_reference.py printed a stress whose sign convention invites misreading against LAMMPS thermo output; label it and print both. 6. README: document the cmake includes, the single-rank constraint and the GPU export step. The CPU virial sign issue we also found is deliberately NOT touched here: it is fixed by #825 with a finite-difference CI validation, and this PR's description records our independent corroborating measurement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 25b3a29 commit 31baf23

8 files changed

Lines changed: 147 additions & 107 deletions

File tree

lammps/README.md

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,38 @@ of which you'll need for `pair_coeff`.
3838

3939
### 2. Build LAMMPS with the package
4040

41-
Drop the package into a stock LAMMPS source tree and configure:
41+
LAMMPS builds its style tables by scanning package directories and then
42+
generating `style_pair.h`, and the generation happens roughly two thirds of
43+
the way through `cmake/CMakeLists.txt` (`GenerateStyleHeaders(...)`, line 794
44+
in `stable_22Jul2025_update5`). **Appending an `include()` to the END of that
45+
file is therefore too late**: the sources compile and libtorch links, but the
46+
style never reaches `style_pair.h` and LAMMPS rejects it at run time with
47+
48+
```
49+
ERROR: Unrecognized pair style 'matgl' (src/force.cpp:275)
50+
```
51+
52+
Register it the way LAMMPS registers its own packages instead:
4253

4354
```bash
44-
# 1) Copy or symlink the source files.
55+
# 1) Copy or symlink the source files into the LAMMPS src tree.
4556
ln -s /path/to/matgl/lammps/src/ML-MATGL <lammps>/src/ML-MATGL
4657

47-
# 2) Tell LAMMPS' CMake about the package.
58+
# 2) Add ML-MATGL to the package list, so LAMMPS' own per-package loop does
59+
# RegisterStyles + target_sources + include dir at the right point.
60+
# In <lammps>/cmake/CMakeLists.txt, inside set(STANDARD_PACKAGES ...):
61+
# ML-IAP
62+
# + ML-MATGL
63+
# ML-PACE
64+
# (`-D PKG_ML-MATGL=ON` then works like any other package flag.)
65+
66+
# 3) Link libtorch. This snippet only does find_package(Torch) and the link;
67+
# it must NOT also add the sources, or every file compiles twice under two
68+
# paths and the link fails on duplicate symbols.
4869
echo 'include(/path/to/matgl/lammps/cmake/ML-MATGL.cmake)' \
4970
>> <lammps>/cmake/CMakeLists.txt
5071

51-
# 3) Configure + build. Match libtorch's CXX11 ABI to LAMMPS'.
72+
# 4) Configure + build. Match libtorch's CXX11 ABI to LAMMPS'.
5273
cmake -B build -S <lammps>/cmake \
5374
-D PKG_ML-MATGL=ON \
5475
-D CMAKE_PREFIX_PATH=/path/to/libtorch \
@@ -57,13 +78,26 @@ cmake -B build -S <lammps>/cmake \
5778
cmake --build build -j 8
5879
```
5980

81+
Check the registration before running anything:
82+
83+
```bash
84+
grep matgl build/styles/style_pair.h # expect pair_matgl.h (and _kokkos.h)
85+
build/lmp -h | tr ' ' '\n' | grep '^matgl'
86+
```
87+
6088
### 2b. Build the Kokkos GPU variant
6189

6290
To get the `matgl/kk` pair style, also enable Kokkos and append the
6391
matching snippet to LAMMPS' CMake. CUDA example for an Ampere card
6492
(A100/A30):
6593

6694
```bash
95+
# Put the Kokkos sources where the KOKKOS package looks for them: its
96+
# RegisterStylesExt(${KOKKOS_PKG_SOURCES_DIR} kokkos ...) scans
97+
# <lammps>/src/KOKKOS for *_kokkos.h style headers and picks up matgl/kk
98+
# automatically. A separate directory is not scanned.
99+
cp /path/to/matgl/lammps/src/KOKKOS/pair_matgl_kokkos.* <lammps>/src/KOKKOS/
100+
67101
echo 'include(/path/to/matgl/lammps/cmake/ML-MATGL-KOKKOS.cmake)' \
68102
>> <lammps>/cmake/CMakeLists.txt
69103

@@ -81,9 +115,21 @@ cmake --build build -j 8
81115
Run with:
82116

83117
```bash
84-
mpirun -n 1 build/lmp -k on g 1 -sf kk -in in.matgl_si
118+
mpirun -n 1 build/lmp -k on g 1 -sf kk -pk kokkos neigh half -in in.matgl_si
85119
```
86120

121+
**`neigh half` is required, not optional.** `pair_matgl` needs `newton on`
122+
(it folds periodic edges back onto local rows and needs ghost contributions),
123+
and LAMMPS refuses `newton on` together with the Kokkos default `neigh full`:
124+
125+
```
126+
ERROR: Must use 'newton off' with KOKKOS package option 'neigh full'
127+
(src/KOKKOS/kokkos.cpp:693)
128+
```
129+
130+
Equivalently, put `package kokkos neigh half` in the input deck before
131+
`atom_style`.
132+
87133
`-sf kk` makes LAMMPS prefer Kokkos pair styles, so `pair_style matgl`
88134
in your input deck dispatches to `matgl/kk` automatically. If you'd
89135
rather force it explicitly, write `pair_style matgl/kk` instead.
@@ -94,11 +140,31 @@ this explicit.
94140

95141
Tested with:
96142

97-
- LibTorch 2.2.x – 2.5.x (CXX11 ABI, CPU build).
143+
- LibTorch 2.2.x – 2.7.x (CXX11 ABI). **The Kokkos variant needs a CUDA
144+
build of libtorch**, not a CPU-only one: `pair_matgl_kokkos.cpp` selects
145+
`torch::Device(torch::kCUDA, gpu)` and wraps Kokkos device buffers as
146+
tensors without a copy, so a CPU-only libtorch silently runs the model on
147+
the host. The CPU build is what the CI job uses for the serial style.
98148
- LAMMPS develop branch (Aug 2024 or newer for the `add_request` /
99-
`REQ_GHOST` neighbor-list API).
149+
`REQ_GHOST` neighbor-list API); verified on `stable_22Jul2025_update5`.
100150
- C++17, MPI optional.
101151

152+
#### Troubleshooting: CUDA 12.9 and newer toolkits
153+
154+
libtorch's bundled Caffe2 CMake config predates two changes and each aborts
155+
the generate step. Both are fixed by a small file injected before
156+
`find_package(Torch)`, e.g. via
157+
`-D CMAKE_PROJECT_lammps_INCLUDE=/path/to/fixups.cmake`:
158+
159+
- CUDA 12.9 removed the nvToolsExt shared library, so `FindCUDAToolkit` no
160+
longer defines `CUDA::nvToolsExt` while `Caffe2/public/cuda.cmake` still
161+
links it into `torch::nvtoolsext`. Declare it as a header-only interface
162+
target (the nvtx3 headers are still shipped):
163+
`add_library(CUDA::nvToolsExt INTERFACE IMPORTED GLOBAL)`.
164+
- On a machine without MKL, Caffe2 leaves the literal
165+
`MKL_INCLUDE_DIR-NOTFOUND` inside torch's `INTERFACE_INCLUDE_DIRECTORIES`.
166+
Point `MKL_INCLUDE_DIR` at any existing directory.
167+
102168
## LAMMPS input syntax
103169

104170
```lammps

lammps/cmake/ML-MATGL-KOKKOS.cmake

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,20 @@
1-
# ML-MATGL Kokkos variant — drop-in CMake snippet.
1+
# ML-MATGL Kokkos variant -- documentation / warning fragment.
22
#
3-
# Layered on top of ML-MATGL.cmake: include() this *after* the base snippet,
4-
# OR set PKG_ML-MATGL=ON and PKG_KOKKOS=ON together.
3+
# The Kokkos sources are picked up by LAMMPS' own KOKKOS package machinery
4+
# (Packages/KOKKOS.cmake scans <lammps>/src/KOKKOS for *_kokkos.* styles and
5+
# registers them via RegisterStylesExt), so this snippet adds NO sources:
56
#
6-
# Usage (from a stock LAMMPS source tree):
7-
# cmake -B build \
8-
# -D PKG_ML-MATGL=ON -D PKG_KOKKOS=ON \
9-
# -D Kokkos_ENABLE_CUDA=ON \
10-
# -D Kokkos_ARCH_AMPERE80=ON \
11-
# -D CMAKE_PREFIX_PATH=/path/to/libtorch \
12-
# -D CMAKE_CXX_COMPILER=$LAMMPS/lib/kokkos/bin/nvcc_wrapper \
13-
# <other flags>
7+
# cp /path/to/matgl/lammps/src/KOKKOS/pair_matgl_kokkos.* <lammps>/src/KOKKOS/
148
#
15-
# The `pair_matgl/kk` style is registered via the standard LAMMPS Kokkos
16-
# pair-style macro so users invoke it with `pair_style matgl/kk` or by
17-
# launching LAMMPS with `-sf kk -k on g 1`.
9+
# and configure with -D PKG_ML-MATGL=ON -D PKG_KOKKOS=ON (plus the usual
10+
# Kokkos CUDA flags). libtorch linkage comes from ML-MATGL.cmake.
1811

1912
if(NOT PKG_ML-MATGL OR NOT PKG_KOKKOS)
2013
return()
2114
endif()
2215

23-
if(NOT DEFINED ML_MATGL_KOKKOS_DIR)
24-
get_filename_component(ML_MATGL_KOKKOS_DIR
25-
"${CMAKE_CURRENT_LIST_DIR}/../src/KOKKOS" ABSOLUTE)
26-
endif()
27-
28-
if(NOT EXISTS "${ML_MATGL_KOKKOS_DIR}/pair_matgl_kokkos.cpp")
29-
message(FATAL_ERROR
30-
"ML-MATGL-KOKKOS source not found at ${ML_MATGL_KOKKOS_DIR}. "
31-
"Set -DML_MATGL_KOKKOS_DIR=<path/to/lammps/src/KOKKOS>.")
32-
endif()
33-
34-
file(GLOB ML_MATGL_KOKKOS_SOURCES "${ML_MATGL_KOKKOS_DIR}/*.cpp")
35-
36-
target_sources(lammps PRIVATE ${ML_MATGL_KOKKOS_SOURCES})
37-
target_include_directories(lammps PRIVATE ${ML_MATGL_KOKKOS_DIR})
38-
3916
# Single-GPU only: warn loudly. MACE upstream issues #1294 and #322 cover
4017
# the multi-rank-with-libtorch breakage we inherit.
4118
message(STATUS
42-
"ML-MATGL-KOKKOS: enabled. Single-GPU runs only multi-rank Kokkos with "
19+
"ML-MATGL-KOKKOS: enabled. Single-GPU runs only -- multi-rank Kokkos with "
4320
"libtorch is unreliable (see MACE issues #1294, #322).")

lammps/cmake/ML-MATGL.cmake

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,27 @@
1-
# ML-MATGL package — drop-in CMake snippet for a stock LAMMPS source tree.
1+
# ML-MATGL package -- libtorch link fragment for a stock LAMMPS source tree.
22
#
3-
# Usage (from a stock LAMMPS source tree):
4-
# 1. Copy or symlink lammps/src/ML-MATGL → <lammps>/src/ML-MATGL
5-
# 2. Append to <lammps>/cmake/CMakeLists.txt (anywhere after the `set(STANDARD_PACKAGES …)`
6-
# block):
7-
# include(/path/to/matgl/lammps/cmake/ML-MATGL.cmake)
8-
# 3. Configure with:
9-
# cmake -B build \
10-
# -D PKG_ML-MATGL=ON \
11-
# -D CMAKE_PREFIX_PATH=/path/to/libtorch \
12-
# -D CMAKE_BUILD_TYPE=Release \
13-
# <other flags>
3+
# This snippet ONLY locates libtorch and links it into the `lammps` target.
4+
# It must NOT add the pair-style sources: LAMMPS registers styles by scanning
5+
# package directories and generating style_pair.h roughly two thirds of the
6+
# way through cmake/CMakeLists.txt, so sources added from an appended
7+
# include() are compiled but never registered -- and adding them here while
8+
# the package loop also adds them compiles every file twice and breaks the
9+
# link. See lammps/README.md, "Build LAMMPS with the package":
1410
#
15-
# CMake variables consumed:
16-
# PKG_ML-MATGL - turn the package on/off (default OFF).
17-
# CMAKE_PREFIX_PATH - must point at a libtorch install (CXX11 ABI build).
18-
# ML_MATGL_DIR - override path to lammps/src/ML-MATGL (defaults to
19-
# ${CMAKE_CURRENT_LIST_DIR}/../src/ML-MATGL).
11+
# 1. Copy or symlink lammps/src/ML-MATGL -> <lammps>/src/ML-MATGL
12+
# 2. Add ML-MATGL to set(STANDARD_PACKAGES ...) in
13+
# <lammps>/cmake/CMakeLists.txt, so the per-package loop does
14+
# RegisterStyles + target_sources at the right point.
15+
# 3. Append to <lammps>/cmake/CMakeLists.txt:
16+
# include(/path/to/matgl/lammps/cmake/ML-MATGL.cmake)
17+
# 4. Configure with -D PKG_ML-MATGL=ON and CMAKE_PREFIX_PATH at libtorch.
2018

2119
option(PKG_ML-MATGL "Build the matgl pair_style backed by libtorch" OFF)
2220

2321
if(NOT PKG_ML-MATGL)
2422
return()
2523
endif()
2624

27-
# Locate the source directory.
28-
if(NOT DEFINED ML_MATGL_DIR)
29-
get_filename_component(ML_MATGL_DIR
30-
"${CMAKE_CURRENT_LIST_DIR}/../src/ML-MATGL" ABSOLUTE)
31-
endif()
32-
33-
if(NOT EXISTS "${ML_MATGL_DIR}/pair_matgl.cpp")
34-
message(FATAL_ERROR
35-
"ML-MATGL source not found at ${ML_MATGL_DIR}. "
36-
"Set -DML_MATGL_DIR=<path/to/lammps/src/ML-MATGL>.")
37-
endif()
38-
3925
# Pull in libtorch.
4026
find_package(Torch REQUIRED)
4127
if(NOT TORCH_LIBRARIES)
@@ -44,24 +30,16 @@ if(NOT TORCH_LIBRARIES)
4430
"Did you set CMAKE_PREFIX_PATH to a libtorch install?")
4531
endif()
4632

47-
# Compose the source list.
48-
file(GLOB ML_MATGL_SOURCES "${ML_MATGL_DIR}/*.cpp")
49-
50-
# Hook into the LAMMPS build. This file is included from
51-
# <lammps>/cmake/CMakeLists.txt; the `lammps` target already exists by then.
52-
target_sources(lammps PRIVATE ${ML_MATGL_SOURCES})
53-
target_include_directories(lammps PRIVATE ${ML_MATGL_DIR})
5433
target_compile_features(lammps PRIVATE cxx_std_17)
5534
target_link_libraries(lammps PRIVATE ${TORCH_LIBRARIES})
5635

5736
# Make sure libtorch's headers come ahead of any system Eigen/torch shims.
5837
target_include_directories(lammps PRIVATE ${TORCH_INCLUDE_DIRS})
5938

60-
# LibTorch ships with -D_GLIBCXX_USE_CXX11_ABI=; propagate it so consumers
61-
# (e.g. KOKKOS in Phase 3) see the same ABI.
39+
# LibTorch ships with -D_GLIBCXX_USE_CXX11_ABI=...; propagate it so every
40+
# consumer (including the KOKKOS sources) sees the same ABI.
6241
if(DEFINED TORCH_CXX_FLAGS)
6342
set_property(TARGET lammps APPEND_STRING PROPERTY COMPILE_FLAGS " ${TORCH_CXX_FLAGS}")
6443
endif()
6544

66-
message(STATUS "ML-MATGL: enabled, sources from ${ML_MATGL_DIR}")
67-
message(STATUS "ML-MATGL: linking against TORCH_LIBRARIES=${TORCH_LIBRARIES}")
45+
message(STATUS "ML-MATGL: libtorch linked, TORCH_LIBRARIES=${TORCH_LIBRARIES}")

lammps/src/KOKKOS/pair_matgl_kokkos.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,11 @@ void PairMATGLKokkos<DeviceType>::compute(int eflag, int vflag)
175175
const auto type_to_z = d_type_to_z_;
176176
Kokkos::parallel_for(
177177
"matgl_kk:fill_atoms",
178-
Kokkos::RangePolicy<DeviceType>(0, nall),
178+
Kokkos::RangePolicy<DeviceType>(0, nlocal),
179179
KOKKOS_LAMBDA(const int i) {
180180
const int t = type(i);
181181
d_atomic_numbers_(i) = type_to_z(t);
182-
d_local_or_ghost_(i) = (i < nlocal);
182+
d_local_or_ghost_(i) = true; // only owned rows are built now
183183
});
184184

185185
// local_row_of_(j) = the owned row representing the same physical atom as
@@ -306,8 +306,12 @@ void PairMATGLKokkos<DeviceType>::compute(int eflag, int vflag)
306306

307307
// x is (nall,3) double already on `DeviceType`. We make a libtorch view
308308
// through from_blob and cast to the model's dtype if needed.
309+
// Local atoms occupy rows [0, nlocal) of LAMMPS' x, so narrowing to
310+
// nlocal keeps this a zero-copy view while dropping the isolated ghost
311+
// rows the model would otherwise embed (see pair_matgl.cpp for why they
312+
// are isolated).
309313
torch::Tensor positions_d = torch::from_blob(
310-
x.data(), {nall, 3}, torch::TensorOptions().dtype(torch::kFloat64).device(torch_device_));
314+
x.data(), {nlocal, 3}, torch::TensorOptions().dtype(torch::kFloat64).device(torch_device_));
311315
torch::Tensor positions = (dtype_ == torch::kFloat64)
312316
? positions_d.clone()
313317
: positions_d.to(dtype_);
@@ -320,9 +324,9 @@ void PairMATGLKokkos<DeviceType>::compute(int eflag, int vflag)
320324
unit_shifts = unit_shifts.narrow(0, 0, total_edges);
321325

322326
torch::Tensor atomic_numbers = blob_from_view(d_atomic_numbers_, torch_long_opts);
323-
atomic_numbers = atomic_numbers.narrow(0, 0, nall);
327+
atomic_numbers = atomic_numbers.narrow(0, 0, nlocal);
324328
torch::Tensor local_or_ghost = blob_from_view(d_local_or_ghost_, torch_bool_opts);
325-
local_or_ghost = local_or_ghost.narrow(0, 0, nall);
329+
local_or_ghost = local_or_ghost.narrow(0, 0, nlocal);
326330

327331
// 6) Cell.
328332
torch::Tensor cell = torch::zeros({3, 3}, torch_real_opts);
@@ -377,11 +381,11 @@ void PairMATGLKokkos<DeviceType>::compute(int eflag, int vflag)
377381
// into LAMMPS' f.
378382
using UnmanagedF = Kokkos::View<double **, Kokkos::LayoutRight, DeviceType,
379383
Kokkos::MemoryTraits<Kokkos::Unmanaged>>;
380-
UnmanagedF d_force_in(forces_t.data_ptr<double>(), nall, 3);
384+
UnmanagedF d_force_in(forces_t.data_ptr<double>(), nlocal, 3);
381385

382386
Kokkos::parallel_for(
383387
"matgl_kk:add_forces",
384-
Kokkos::RangePolicy<DeviceType>(0, nall),
388+
Kokkos::RangePolicy<DeviceType>(0, nlocal), // ghosts carried zero
385389
KOKKOS_LAMBDA(const int i) {
386390
f(i, 0) += d_force_in(i, 0);
387391
f(i, 1) += d_force_in(i, 1);

lammps/src/ML-MATGL/export_matgl_checkpoint.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,14 @@
1515

1616
import matgl
1717

18-
# matgl/src/matgl/ext/_lammps.py imports create_line_graph_torch, which is
19-
# only used by the M3GNet export path and is absent from the current
20-
# _compute_pyg.py. Irrelevant to TensorNet exports; shim it in-memory only
21-
# (no matgl repo file changes) so the import below succeeds.
22-
import matgl.graph._compute_pyg as _cpyg
23-
24-
if not hasattr(_cpyg, "create_line_graph_torch"):
25-
_cpyg.create_line_graph_torch = _cpyg.create_line_graph
26-
27-
from matgl.ext._lammps import export_lammps_model
18+
# Module layout as of matgl 4.0.3: the helpers live in matgl.graph._compute
19+
# and the exporter in matgl.ext.lammps. The previous names
20+
# (matgl.graph._compute_pyg, matgl.ext._lammps) predate that layout; the first
21+
# no longer exists, so the import raised ModuleNotFoundError and made the
22+
# "point pair_coeff at a checkpoint directory" path unusable.
23+
# _compute exports create_line_graph_torch directly,
24+
# so the in-memory shim that used to sit here is no longer needed either.
25+
from matgl.ext.lammps import export_lammps_model
2826

2927
checkpoint_dir, out_path = sys.argv[1], sys.argv[2]
3028
potential = matgl.load_model(checkpoint_dir)

0 commit comments

Comments
 (0)