Refinement: the per-evaluation host chain on the threads and in place
Byte-identical: every pass is element by element in the serial loop's order, or a copy that is no longer made. - The engine's gradients are read in place from its pinned buffers and widened to double on the threads (RefinementGPUEngine::HostGradient/HostRestraintGradient); the positions go up from a pinned vector of the engine's host side; the model's atom pointers are kept between calls (the model is the context's own). - Context: the riders moved and folded on the threads - a parent takes its riders in index order through a CSR list (SetRiderParents), as the serial walk added them. - AtomTarget: the parameters set and the gradient assembled on the threads; the per-atom gradient vectors kept between evaluations (7 MB each on a large model: made anew, their pages fault in every time). - RefinementMinimiser: the sweeps over the parameters (x + alpha d, s, y) on the threads it is given, the dot products the serial chains they were; Direction()'s working vector and a let-go pair's vectors reused. A/B against the frozen combined2 binary, one lock hold each, outputs identical but provenance lines: cytc refinement 1.600 -> 1.548 s (live), 1.724 -> 1.668 (CLI); thau 1.906 -> 1.885, 2.031 -> 2.027; 4nwv (with the commit before) 39.9 -> 35.5 s, the ladder 14.6 -> 12.7, each step 8.3 -> 7.2. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
This commit is contained in:
1 parent
b16873036c
commit
d122118d73
5 files changed
+174
-99
No files matched your search
@@ -28,6 +28,7 @@
|
||||
#include "../../common/JFJochException.h"
|
||||
#include "../../common/JFJochMath.h"
|
||||
#include "../../common/Logger.h"
|
||||
#include "../../common/ParallelFor.h"
|
||||
#include "ModelFFT.h"
|
||||
#include "ModelScaling.h"
|
||||
#include "RefinementMath.h"
|
||||
@@ -137,6 +138,7 @@ struct Context {
|
||||
double b_ceiling = B_CEILING_MAX;
|
||||
// Riding hydrogens: per atom the parent it rides on (-1: none), and where it sits from the parent.
|
||||
std::vector<int> rider_parent;
|
||||
std::vector<int> rider_start, rider_index; // SetRiderParents()
|
||||
std::vector<gemmi::Position> rider_offset;
|
||||
int evaluations = 0;
|
||||
const std::atomic<bool> *cancel = nullptr; // RefinementSettings::cancel
|
||||
@@ -145,20 +147,42 @@ struct Context {
|
||||
|
||||
gemmi::Model &Model() { return st.models[0]; }
|
||||
|
||||
// The hydrogens moved with their parents, at the offsets they were last placed at, and given their B.
|
||||
void MoveRiders() {
|
||||
for (size_t i = 0; i < rider_parent.size(); i++)
|
||||
if (rider_parent[i] >= 0) {
|
||||
atoms[i]->pos = atoms[rider_parent[i]]->pos + rider_offset[i];
|
||||
atoms[i]->b_iso = atoms[rider_parent[i]]->b_iso;
|
||||
}
|
||||
}
|
||||
// What the target says of a hydrogen is said of its parent.
|
||||
void FoldRiders(std::vector<std::array<double, 4>> &g) const {
|
||||
// The riders' parents, and per parent its riders in index order (rider_start, rider_index: CSR over the atoms).
|
||||
void SetRiderParents(const std::vector<int> &parents) {
|
||||
rider_parent = parents;
|
||||
rider_start.assign(atoms.size() + 1, 0);
|
||||
for (int p : rider_parent)
|
||||
if (p >= 0)
|
||||
rider_start[p + 1]++;
|
||||
for (size_t i = 0; i < atoms.size(); i++)
|
||||
rider_start[i + 1] += rider_start[i];
|
||||
rider_index.resize(rider_start.back());
|
||||
std::vector<int> fill(rider_start.begin(), rider_start.end() - 1);
|
||||
for (size_t i = 0; i < rider_parent.size(); i++)
|
||||
if (rider_parent[i] >= 0)
|
||||
for (int c = 0; c < 4; c++)
|
||||
g[rider_parent[i]][c] += g[i][c];
|
||||
rider_index[fill[rider_parent[i]]++] = static_cast<int>(i);
|
||||
}
|
||||
// The hydrogens moved with their parents, at the offsets they were last placed at, and given their B.
|
||||
void MoveRiders() {
|
||||
const int n = static_cast<int>(rider_parent.size());
|
||||
ParallelChunks(n, ThreadsForWork(n, nthreads, 16384), [&](int lo, int hi) {
|
||||
for (int i = lo; i < hi; i++)
|
||||
if (rider_parent[i] >= 0) {
|
||||
atoms[i]->pos = atoms[rider_parent[i]]->pos + rider_offset[i];
|
||||
atoms[i]->b_iso = atoms[rider_parent[i]]->b_iso;
|
||||
}
|
||||
});
|
||||
}
|
||||
// What the target says of a hydrogen is said of its parent: each parent takes its riders in index order, as
|
||||
// the serial walk over the atoms added them (a rider has no riders of its own).
|
||||
void FoldRiders(std::vector<std::array<double, 4>> &g) const {
|
||||
const int n = static_cast<int>(rider_parent.size());
|
||||
ParallelChunks(n, ThreadsForWork(n, nthreads, 16384), [&](int lo, int hi) {
|
||||
for (int i = lo; i < hi; i++)
|
||||
for (int r = rider_start[i]; r < rider_start[i + 1]; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
g[i][c] += g[rider_index[r]][c];
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<std::array<double, 4>> XyzB() const {
|
||||
@@ -509,13 +533,16 @@ public:
|
||||
}
|
||||
// The atoms set to the parameters, their riders with them.
|
||||
void Set(const double *p) const {
|
||||
for (size_t k = 0; k < param_atoms_.size(); k++) {
|
||||
gemmi::Atom &a = *ctx_.atoms[param_atoms_[k]];
|
||||
if (mode_ == Mode::Xyz)
|
||||
a.pos = gemmi::Position(p[3 * k], p[3 * k + 1], p[3 * k + 2]);
|
||||
else
|
||||
a.b_iso = static_cast<float>(std::clamp(p[k], ctx_.b_floor, ctx_.b_ceiling));
|
||||
}
|
||||
const int n = static_cast<int>(param_atoms_.size());
|
||||
ParallelChunks(n, ThreadsForWork(n, ctx_.nthreads, 16384), [&](int lo, int hi) {
|
||||
for (int k = lo; k < hi; k++) {
|
||||
gemmi::Atom &a = *ctx_.atoms[param_atoms_[k]];
|
||||
if (mode_ == Mode::Xyz)
|
||||
a.pos = gemmi::Position(p[3 * k], p[3 * k + 1], p[3 * k + 2]);
|
||||
else
|
||||
a.b_iso = static_cast<float>(std::clamp(p[k], ctx_.b_floor, ctx_.b_ceiling));
|
||||
}
|
||||
});
|
||||
ctx_.MoveRiders();
|
||||
}
|
||||
// the most a parameter moves in the first trial of a line search
|
||||
@@ -525,7 +552,7 @@ public:
|
||||
const RefinementStage stage(mode_ == Mode::Xyz ? "eval.xyz" : "eval.b");
|
||||
Set(p);
|
||||
ctx_.evaluations++;
|
||||
std::vector<std::array<double, 4>> gx, gr;
|
||||
std::vector<std::array<double, 4>> &gx = gx_, &gr = gr_;
|
||||
double xray = 0, restraints = 0;
|
||||
{
|
||||
const RefinementStage s1("eval.engine");
|
||||
@@ -536,18 +563,21 @@ public:
|
||||
if (gradient == nullptr)
|
||||
return std::isfinite(*cost);
|
||||
ctx_.FoldRiders(gx);
|
||||
for (size_t k = 0; k < param_atoms_.size(); k++) {
|
||||
const int i = param_atoms_[k];
|
||||
if (mode_ == Mode::Xyz)
|
||||
for (int c = 0; c < 3; c++)
|
||||
gradient[3 * k + c] = weight_ * gx[i][c] + gr[i][c];
|
||||
else {
|
||||
// held at a bound, B does not move further out: by the whole target, restraints included
|
||||
const double g = weight_ * gx[i][3] + gr[i][3];
|
||||
const bool held = (p[k] <= ctx_.b_floor && g > 0) || (p[k] >= ctx_.b_ceiling && g < 0);
|
||||
gradient[k] = held ? 0.0 : g;
|
||||
const int n = static_cast<int>(param_atoms_.size());
|
||||
ParallelChunks(n, ThreadsForWork(n, ctx_.nthreads, 16384), [&](int lo, int hi) {
|
||||
for (int k = lo; k < hi; k++) {
|
||||
const int i = param_atoms_[k];
|
||||
if (mode_ == Mode::Xyz)
|
||||
for (int c = 0; c < 3; c++)
|
||||
gradient[3 * k + c] = weight_ * gx[i][c] + gr[i][c];
|
||||
else {
|
||||
// held at a bound, B does not move further out: by the whole target, restraints included
|
||||
const double g = weight_ * gx[i][3] + gr[i][3];
|
||||
const bool held = (p[k] <= ctx_.b_floor && g > 0) || (p[k] >= ctx_.b_ceiling && g < 0);
|
||||
gradient[k] = held ? 0.0 : g;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return std::isfinite(*cost);
|
||||
}
|
||||
|
||||
@@ -557,6 +587,9 @@ private:
|
||||
const std::vector<int> ¶m_atoms_;
|
||||
double weight_;
|
||||
int restraint_set_;
|
||||
// the gradients per atom, kept from evaluation to evaluation (7 MB each on a large model: made anew, their
|
||||
// pages fault in every time)
|
||||
mutable std::vector<std::array<double, 4>> gx_, gr_;
|
||||
};
|
||||
|
||||
// The minimiser's diagonal for the B factors of `param_atoms` (RefinementMinimiser::SetDiagonal): weight times the
|
||||
@@ -998,7 +1031,7 @@ std::unique_ptr<OwnSlot> MakeOwnSlot(const gemmi::Structure &before_topology, co
|
||||
ctx.mask = std::make_unique<BulkSolventMask>(ctx.cell, *ctx.sg, ctx.d_min, nthreads);
|
||||
slot->topo = BuildRefinementTopology(ctx.st, settings.riding_hydrogens);
|
||||
ctx.atoms = AtomList(ctx.Model());
|
||||
ctx.rider_parent = slot->topo.rider_parent;
|
||||
ctx.SetRiderParents(slot->topo.rider_parent);
|
||||
#ifdef JFJOCH_USE_CUDA
|
||||
if (main.engine->OnGPU()) {
|
||||
try {
|
||||
@@ -1077,7 +1110,7 @@ TrialOutcome RunTrial(TrialSlot slot, const std::vector<int> &refinable, const s
|
||||
ctx.SetB(b0);
|
||||
// the positions and the B factors in turn, each with a memory of its own kept from macro-cycle to macro-cycle
|
||||
const AtomTarget xyz(ctx, AtomTarget::Mode::Xyz, refinable, w, 0), b(ctx, AtomTarget::Mode::B, refinable, adp_w, 1);
|
||||
RefinementMinimiser xyz_minimiser, b_minimiser;
|
||||
RefinementMinimiser xyz_minimiser(20, ctx.nthreads), b_minimiser(20, ctx.nthreads);
|
||||
double xray_scale = 0;
|
||||
for (int cycle = 0; cycle < cycles && !ctx.Cancelled(); cycle++) {
|
||||
PlaceRiders(ctx, topo);
|
||||
@@ -1241,7 +1274,7 @@ RefinementResult RefineModel(const gemmi::Structure &placed, const std::vector<M
|
||||
topo = BuildRefinementTopology(ctx.st, settings.riding_hydrogens);
|
||||
topo_stage.reset();
|
||||
ctx.atoms = AtomList(ctx.Model());
|
||||
ctx.rider_parent = topo.rider_parent;
|
||||
ctx.SetRiderParents(topo.rider_parent);
|
||||
PlaceRiders(ctx, topo);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user