KOKKOS: sync per-atom data on demand during variable evaluation - #47
Merged
Conversation
The KOKKOS versions of fix setforce, fix addforce, and fix efield had to call atomKK->sync(Host,ALL_MASK) before evaluating their force variables, since variable evaluation happens on the host. Copying all per-atom data back from the device every step is far more than these formulas need, and adding the same boilerplate to every caller of compute_atom() would not scale: there are more than fifty of them, and a caller that forgets an entry gets a silently incomplete sync. Let the Variable class request the data instead, at the points where it reads per-atom arrays on the host. The functions that do so become virtual, and VariableKokkos overrides them to make the data current and then delegate to the base class: compute_atom() for the group test, atom_vector() for an atom vector in a formula, group_function() for xcm() and friends, special_function() for gmask, rmask, and grmask, and peratom2global() and custom2global() for access to a single atom by ID. Per-atom data reached through a compute, fix, or custom property has no such seam, since evaluate() handles it inline, so those three branches call a small virtual sync_peratom() instead. This keeps all knowledge of the bitmasks in VariableKokkos. atom_masks.h is not needed in variable.cpp at all, and variable.cpp gains only three one-line calls plus the virtual keywords. Input instantiates VariableKokkos when KOKKOS is active. The three fixes drop their blanket sync with no replacement, and every other caller of an atom-style variable gets the same treatment for free. The mapping is fail-safe: atom_vector() starts at ALL_MASK and only narrows, so an atom vector added to the base class without a matching entry in VariableKokkos syncs more than it needs to rather than reading stale data. The compute, fix, and group function paths request ALL_MASK for the same reason. Three cases need care: "mass" without per-atom rmass is a per-type array that eval_tree() indexes with atom->type[i], so it needs TYPE_MASK; peratom2global() reads the named vector as well as the atom map; and group_function() is tried for every function word in a formula and returns 0 for anything that is not a group function, so the guard is factored out as is_group_function() and shared rather than syncing on every function call. The modify_host() and sync() calls on the result arrays in the three fixes stay. Those are a real host to device copy of the variable result, not a stale flag, and can only go away if variables are evaluated on the device. Correct the comment that said otherwise. Add a unit test that swaps in a Variable subclass recording which seams a formula routes through, and asserts them for the core atom vectors, gmask/rmask/grmask, indexed per-atom access, custom properties, and the compute and group function fallbacks.
stanmoore1
force-pushed
the
claude/lammps-pr-4970-review-73v8x0
branch
from
August 14, 2026 21:33
44c4565 to
62555d0
Compare
# Conflicts: # src/variable.cpp
…ords Two paths in evaluate() read per-atom data on the host without going through any of the seams added in the previous commit, so removing the blanket atomKK->sync(Host,ALL_MASK) from the three fixes left them reading stale data. special_function() handles sum(), min(), max(), ave(), trap(), slope(), sort(), and rsort(), which take a compute or a fix as their argument and invoke it. The override only synced for gmask, rmask, and grmask, so a formula such as sum(c_msd) driving fix setforce/kk read a stale host copy of the atom coordinates. Factor the map lookup that guards special_function() out as is_special_function() so the override can use the same test rather than a second copy of the name list, and have the override fall back to ALL_MASK for any special function that is not on an explicit list of ones known to touch no per-atom data. A special function added later therefore syncs too much rather than too little. The thermo keyword branch of evaluate() calls Thermo::evaluate_keyword(), which invokes the thermo computes, with no seam at all. Add one. Extend the unit test to cover both paths, and record the name of any special function the formula reaches rather than just gmask, rmask, and grmask, so the test measures which seams are used instead of repeating the mask policy that belongs in VariableKokkos. Also drop an unused include from the test, keep the new includes in accelerator_kokkos.h in alphabetical order, and move is_group_function() above the comment block that describes group_function().
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The KOKKOS versions of
fix setforce,fix addforce, andfix efieldeach calledatomKK->sync(Host,ALL_MASK)before evaluating their force variables, because variable evaluation happens on the host. Copying every per-atom array back from the device on every step is far more than these formulas need. Each of those calls carried a// this can be removed when variable class is ported to Kokkoscomment.Rather than make every caller sync defensively up front, the
Variableclass now requests the data itself at the points where it reads per-atom arrays on the host. The functions that do so become virtual, andVariableKokkosoverrides each one to make just the data it needs current and then delegate to the base class:compute_atom()atom->maskfor the group testMASK_MASKatom_vector()X/V/F/Q/TYPE/TAG/MOLECULE/RADIUS/RMASSgroup_function()xcm(),fcm(),gyration(), ...ALL_MASKspecial_function()gmask(),rmask(),grmask()MASK,X,X|MASKperatom2global(),custom2global()ALL_MASKPer-atom data reached through a compute, fix, or custom property has no such seam, since
evaluate()handles it inline. Those three branches call a small virtualsync_peratom()instead, which is the only hook left invariable.cpp.The result is that all knowledge of the bitmasks lives in
VariableKokkos;atom_masks.his not included byvariable.cppat all. The net addition tovariable.cppis three one-line calls plus the extraction ofis_group_function()described below.InputinstantiatesVariableKokkoswhen KOKKOS is active. The three fixes drop their blanket sync with no replacement, and every other caller of an atom-style variable benefits automatically --variable->compute_atom()is called from more than fifty files.Related Issue(s)
This is an alternative approach to the same problem addressed by lammps#4970.
Author(s)
Stan Moore, Sandia National Laboratories
Licensing
By submitting this pull request, I agree, that my contribution will be included in LAMMPS and redistributed under either the GNU General Public License version 2 (GPL v2) or the GNU Lesser General Public License version 2.1 (LGPL v2.1).
Artificial Intelligence (AI) Tools Usage
Backward Compatibility
No backward compatibility is broken. There are no user visible changes; this only affects when per-atom data is copied back from the device in KOKKOS runs.
Implementation Notes
Changes to the
Variableclass interface:compute_atom()becomes virtual, andTree,atom_vector(),group_function(),special_function(),peratom2global(), andcustom2global()move from private to protected so the subclass can override and forward. The subclass never touches the members ofTree; it only passes the pointers through. Nothing else inVariableis exposed --evaluate(),collapse_tree(),eval_tree(), and the variable list stay private.The name to mask mapping is deliberately fail-safe.
atom_vector()starts atALL_MASKand only narrows, so an atom vector added to the base class without a matching entry inVariableKokkossyncs more than it needs to rather than reading stale data. The compute, fix, and group function paths requestALL_MASKfor the same reason.Three cases need care and are handled explicitly:
masswithout per-atomrmassis a per-type array thateval_tree()indexes withatom->type[i], so it needsTYPE_MASKrather thanRMASS_MASK.peratom2global()reads the named vector itself (x[100],vy[7]) in addition to the atom map, so it cannot be narrowed toTAG_MASK.group_function()is tried for every function word in a formula and returns 0 for anything that is not a group function. Syncing unconditionally in the override would therefore sync everything ongmask(...),rmask(...), and every special function, undoing most of the benefit. The guard is factored out of the base class asis_group_function()and shared, so there is one copy of that word list rather than two that can drift.Custom per-atom properties are identified by the
i_/d_/i2_/d2_prefix and map toIVECTOR_MASK,DVECTOR_MASK,IARRAY_MASK, andDARRAY_MASK.The
modify_host()andsync<DeviceType>()calls on the result arrays in the three fixes are unchanged and stay. Those are a real host to device copy of the variable result, not a stale flag: the value is computed on the host and consumed by a device kernel, so the bytes have to move. That can only go away if variables are evaluated on the device. The comments claiming they could be removed once the variable class was ported have been corrected.Verification performed so far, all on a CPU-only (Serial) KOKKOS build:
VariableSyncTest.AcceleratorSeamsswaps in aVariablesubclass that records which seams a formula routes through, and asserts them for the core atom vectors,gmask/rmask/grmask, indexed per-atom access, custom properties, and the compute and group function fallbacks. The fulltest_variablessuite passes (11/11). This test is what caught thegroup_function()over-sync described above.fix addforce/fix setforceinput using only narrow formulas (x,type,id*q, so thatV_MASKandF_MASKare deliberately not synced) agrees between a host run and-k on -sf kkto 6.4e-12, against a 7.5e-11 baseline from a control deck that uses no variables at all.make check-whitespaceandmake check-permissionsare clean.One limitation worth stating plainly: on a CPU-only KOKKOS build
LMPDeviceTypeandLMPHostTypeare the same type, the dual views alias, andatomKK->sync(Host,...)is a no-op. The tests above therefore establish which seams a formula uses and that formula evaluation is unaffected, but they cannot establish that the set of syncs is sufficient. A missed seam would pass these tests and still produce stale data on a GPU build. This needs a GPU regression run before it is merged.Post Submission Checklist
Further Information, Files, and Links
New files are registered in both
src/KOKKOS/Install.shandcmake/Modules/Packages/KOKKOS.cmake.src/.gitignorealready covers*_kokkos.cppand*_kokkos.hby wildcard, so no change was needed there.fix_langevin_kokkosandpair_hybrid_scaled_kokkosalso callcompute_atom()but did not perform the blanket host sync, so they were left alone; they may deserve a separate look.