Skip to content

Commit 2206ec1

Browse files
committed
perf(mta): triclinic shifts, ring hop limit, configurable sparse threshold
Triclinic-aware shift in exchangeBackwardPairs via invertBoxMatrix, matching LAMMPS cell_shifts(). Ring exchange now accepts maxRounds capped by ceil(cutoff/minCellSize) from DD geometry. Sparse/dense force threshold configurable via GMX_METATOMIC_SPARSE_THRESHOLD.
1 parent de02ec3 commit 2206ec1

2 files changed

Lines changed: 57 additions & 18 deletions

File tree

src/gromacs/applied_forces/metatomic/metatomic_forceprovider.cpp

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
#include "gromacs/domdec/domdec_network.h"
6969
#include "gromacs/domdec/domdec_struct.h"
7070
#include "gromacs/domdec/localatomset.h"
71+
#include "gromacs/math/boxmatrix.h"
7172
#include "gromacs/mdlib/broadcaststructs.h"
7273
#include "gromacs/mdrunutility/mdmodulesnotifiers.h"
7374
#include "gromacs/mdtypes/enerdata.h"
@@ -152,6 +153,8 @@ struct MetatomicData
152153
torch::Tensor cachedTypes;
153154
torch::Tensor cachedPbc;
154155

156+
//! Sparse/dense force exchange threshold (atom count). Env: GMX_METATOMIC_SPARSE_THRESHOLD.
157+
int32_t sparseThreshold = 1000;
155158
};
156159

157160
MetatomicForceProvider::MetatomicForceProvider(const MetatomicOptions& options,
@@ -172,6 +175,15 @@ MetatomicForceProvider::MetatomicForceProvider(const MetatomicOptions& options,
172175

173176
data_->debugEnabled = (std::getenv("GMX_METATOMIC_DEBUG") != nullptr);
174177

178+
if (const char* env = std::getenv("GMX_METATOMIC_SPARSE_THRESHOLD"))
179+
{
180+
data_->sparseThreshold = std::stoi(env);
181+
GMX_LOG(logger_.info)
182+
.asParagraph()
183+
.appendTextFormatted("Metatomic sparse force threshold: %d",
184+
data_->sparseThreshold);
185+
}
186+
175187
// Force single-threaded PyTorch operations in all parallel runs.
176188
// In thread-MPI, ranks share a process; in real MPI, each rank is a process.
177189
// In both cases, GROMACS manages CPU affinity, and having PyTorch spawn its
@@ -710,7 +722,7 @@ int32_t MetatomicForceProvider::exchangeBackwardGhosts(
710722
}
711723

712724

713-
void MetatomicForceProvider::exchangeBackwardPairs(const matrix box)
725+
void MetatomicForceProvider::exchangeBackwardPairs(const matrix box, int maxRounds)
714726
{
715727
backwardPairsMta_.clear();
716728
backwardShiftsMta_.clear();
@@ -772,10 +784,14 @@ void MetatomicForceProvider::exchangeBackwardPairs(const matrix box)
772784
const int sendTo = (myRank + 1) % numRanks;
773785
const int recvFrom = (myRank - 1 + numRanks) % numRanks;
774786

787+
// Compute box inverse once for triclinic-safe shift computation.
788+
matrix boxInv;
789+
invertBoxMatrix(box, boxInv);
790+
775791
std::vector<int> sendBuf = myPairsBuf;
776792
std::vector<int> recvBuf;
777793

778-
for (int round = 0; round < numRanks - 1; round++)
794+
for (int round = 0; round < maxRounds; round++)
779795
{
780796
// Exchange counts first so receiver knows buffer size.
781797
int sendCount = static_cast<int>(sendBuf.size());
@@ -820,7 +836,8 @@ void MetatomicForceProvider::exchangeBackwardPairs(const matrix box)
820836
const int32_t localI = itI->second;
821837
const int32_t localJ = itJ->second;
822838

823-
// Compute minimum-image shift from local positions (orthorhombic).
839+
// Triclinic-safe minimum-image shift (matches LAMMPS cell_shifts).
840+
// invertBoxMatrix returns lower-triangular inverse, so upper triangle is 0.
824841
const double rawDx =
825842
static_cast<double>(positions_[localJ][XX] - positions_[localI][XX]);
826843
const double rawDy =
@@ -829,12 +846,11 @@ void MetatomicForceProvider::exchangeBackwardPairs(const matrix box)
829846
static_cast<double>(positions_[localJ][ZZ] - positions_[localI][ZZ]);
830847

831848
IVec shift;
832-
shift[XX] = static_cast<int>(
833-
std::round(-rawDx / static_cast<double>(box[XX][XX])));
834-
shift[YY] = static_cast<int>(
835-
std::round(-rawDy / static_cast<double>(box[YY][YY])));
836-
shift[ZZ] = static_cast<int>(
837-
std::round(-rawDz / static_cast<double>(box[ZZ][ZZ])));
849+
shift[XX] = static_cast<int>(std::round(
850+
-(boxInv[XX][XX] * rawDx + boxInv[YY][XX] * rawDy + boxInv[ZZ][XX] * rawDz)));
851+
shift[YY] = static_cast<int>(std::round(
852+
-(boxInv[YY][YY] * rawDy + boxInv[ZZ][YY] * rawDz)));
853+
shift[ZZ] = static_cast<int>(std::round(-(boxInv[ZZ][ZZ] * rawDz)));
838854

839855
backwardPairsMta_.push_back(localI);
840856
backwardPairsMta_.push_back(localJ);
@@ -872,7 +888,7 @@ void MetatomicForceProvider::distributeNonHomeForces(const double* forces
872888

873889
// For small systems, dense allreduce has lower latency than the
874890
// sparse exchange (gather counts + allgatherv).
875-
constexpr int32_t sparseThreshold = 1000;
891+
const int32_t sparseThreshold = data_->sparseThreshold;
876892

877893
if (numTotalMta < sparseThreshold)
878894
{
@@ -997,16 +1013,19 @@ void MetatomicForceProvider::calculateForces(const ForceProviderInput& inputs, F
9971013

9981014
if (useNewtonNL)
9991015
{
1016+
// Compute max cutoff across all NL requests (used by both ghost
1017+
// exchange and ring hop limit).
1018+
double maxCutoff = 0.0;
1019+
for (const auto& req : data_->nl_requests)
1020+
{
1021+
maxCutoff = std::max(maxCutoff, req->engine_cutoff("nm"));
1022+
}
1023+
10001024
// Step 1: Exchange backward ghost atoms to fill the backward gap
10011025
// in the DD halo. Extends positions_, atomNumbers_, mtaToGlobalMta_
10021026
// and numLocalMta_ with atoms from the backward PBC neighbor.
10031027
{
10041028
MetatomicTimer timer("exchangeBackwardGhosts", mpiComm_);
1005-
double maxCutoff = 0.0;
1006-
for (const auto& req : data_->nl_requests)
1007-
{
1008-
maxCutoff = std::max(maxCutoff, req->engine_cutoff("nm"));
1009-
}
10101029
exchangeBackwardGhosts(inputs.dd_, inputs.box_, maxCutoff);
10111030
}
10121031

@@ -1016,9 +1035,27 @@ void MetatomicForceProvider::calculateForces(const ForceProviderInput& inputs, F
10161035

10171036
// Step 2: Exchange backward-direction pairs. Discovers pairs from
10181037
// other ranks' pairlists that involve this rank's home atoms.
1038+
// Limit ring rounds to ceil(cutoff/minCellSize) when DD is available.
10191039
{
10201040
MetatomicTimer timer("exchangeBackwardPairs", mpiComm_);
1021-
exchangeBackwardPairs(inputs.box_);
1041+
int maxRounds = mpiComm_.size() - 1;
1042+
if (inputs.dd_ != nullptr && inputs.dd_->ndim > 0)
1043+
{
1044+
double minCellSize = 1e30;
1045+
for (int d = 0; d < inputs.dd_->ndim; d++)
1046+
{
1047+
const int dim = inputs.dd_->dim[d];
1048+
const double cs = static_cast<double>(inputs.box_[dim][dim])
1049+
/ inputs.dd_->numCells[dim];
1050+
minCellSize = std::min(minCellSize, cs);
1051+
}
1052+
if (minCellSize > 0.0)
1053+
{
1054+
maxRounds = std::min(maxRounds,
1055+
static_cast<int>(std::ceil(maxCutoff / minCellSize)));
1056+
}
1057+
}
1058+
exchangeBackwardPairs(inputs.box_, maxRounds);
10221059
}
10231060

10241061
// Step 3: Temporarily extend pairlistMta_ with backward pairs

src/gromacs/applied_forces/metatomic/metatomic_forceprovider.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,11 @@ class MetatomicForceProvider final : public IForceProvider
131131
* Must be called after exchangeBackwardGhosts() (so all atom positions
132132
* are available) and before the NL building loop.
133133
*
134-
* \param[in] box Current simulation box.
134+
* \param[in] box Current simulation box.
135+
* \param[in] maxRounds Maximum number of ring exchange rounds (capped by
136+
* ceil(cutoff/minCellSize) when DD info is available).
135137
*/
136-
void exchangeBackwardPairs(const matrix box);
138+
void exchangeBackwardPairs(const matrix box, int maxRounds);
137139

138140
/*! \brief Exchange backward ghost MTA atoms via DD to fill the backward gap.
139141
*

0 commit comments

Comments
 (0)