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
@@ -46,6 +46,31 @@ std::vector<const gemmi::Atom *> Atoms(const gemmi::Model &model) {
|
||||
return atoms;
|
||||
}
|
||||
|
||||
// Whether `atoms` is Atoms(model) still: as many atoms, the first and the last the same ones.
|
||||
bool SameAtoms(const gemmi::Model &model, const std::vector<const gemmi::Atom *> &atoms) {
|
||||
size_t n = 0;
|
||||
const gemmi::Atom *first = nullptr, *last = nullptr;
|
||||
for (const gemmi::Chain &ch : model.chains)
|
||||
for (const gemmi::Residue &r : ch.residues)
|
||||
if (!r.atoms.empty()) {
|
||||
if (first == nullptr)
|
||||
first = &r.atoms.front();
|
||||
last = &r.atoms.back();
|
||||
n += r.atoms.size();
|
||||
}
|
||||
return n > 0 && n == atoms.size() && first == atoms.front() && last == atoms.back();
|
||||
}
|
||||
|
||||
// The engine's single-precision gradient into the caller's doubles, element by element in parallel: bit for bit
|
||||
// the serial loop's.
|
||||
void Widen(const std::array<float, 4> *from, size_t n, std::vector<std::array<double, 4>> &to, size_t nthreads) {
|
||||
to.resize(n);
|
||||
ParallelChunks(static_cast<int>(n), ThreadsForWork(n, nthreads, 16384), [&](int lo, int hi) {
|
||||
for (int i = lo; i < hi; i++)
|
||||
to[i] = {from[i][0], from[i][1], from[i][2], from[i][3]};
|
||||
});
|
||||
}
|
||||
|
||||
class RefinementEngineGPU : public RefinementEngine {
|
||||
public:
|
||||
RefinementEngineGPU(int device, const gemmi::UnitCell &cell, const gemmi::SpaceGroup &sg, double d_min,
|
||||
@@ -201,44 +226,33 @@ public:
|
||||
|
||||
double Evaluate(const gemmi::Model &model, std::vector<std::array<double, 4>> *grad) override {
|
||||
SetAtoms(model);
|
||||
std::vector<std::array<float, 4>> g;
|
||||
double value = 0;
|
||||
{
|
||||
const RefinementStage stage("gpu.evaluate");
|
||||
value = engine_->Evaluate(grad != nullptr ? &g : nullptr);
|
||||
}
|
||||
if (grad != nullptr) {
|
||||
grad->resize(g.size());
|
||||
for (size_t i = 0; i < g.size(); i++)
|
||||
(*grad)[i] = {g[i][0], g[i][1], g[i][2], g[i][3]};
|
||||
value = engine_->Evaluate(grad != nullptr);
|
||||
}
|
||||
if (grad != nullptr)
|
||||
Widen(engine_->HostGradient(), atom_ptrs_.size(), *grad, nthreads_);
|
||||
return value;
|
||||
}
|
||||
|
||||
double EvaluateWithRestraints(const gemmi::Model &model, int which, std::vector<std::array<double, 4>> *grad,
|
||||
std::vector<std::array<double, 4>> *restraint_grad, double &restraints) override {
|
||||
SetAtoms(model);
|
||||
std::vector<std::array<float, 4>> g, gr;
|
||||
double value = 0;
|
||||
{
|
||||
const RefinementStage stage("gpu.evaluate");
|
||||
value = engine_->EvaluateWithRestraints(grad != nullptr ? &g : nullptr, which, [&](std::array<float, 4> *x) {
|
||||
value = engine_->EvaluateWithRestraints(grad != nullptr, which, [&](std::array<float, 4> *x) {
|
||||
for (size_t i = 0; i < atom_ptrs_.size(); i++) {
|
||||
const gemmi::Atom &a = *atom_ptrs_[i];
|
||||
x[i] = {static_cast<float>(a.pos.x), static_cast<float>(a.pos.y), static_cast<float>(a.pos.z), a.b_iso};
|
||||
}
|
||||
}, restraint_grad != nullptr ? &gr : nullptr, restraints);
|
||||
}
|
||||
if (grad != nullptr) {
|
||||
grad->resize(g.size());
|
||||
for (size_t i = 0; i < g.size(); i++)
|
||||
(*grad)[i] = {g[i][0], g[i][1], g[i][2], g[i][3]};
|
||||
}
|
||||
if (restraint_grad != nullptr) {
|
||||
restraint_grad->resize(gr.size());
|
||||
for (size_t i = 0; i < gr.size(); i++)
|
||||
(*restraint_grad)[i] = {gr[i][0], gr[i][1], gr[i][2], gr[i][3]};
|
||||
}, restraint_grad != nullptr, restraints);
|
||||
}
|
||||
if (grad != nullptr)
|
||||
Widen(engine_->HostGradient(), atom_ptrs_.size(), *grad, nthreads_);
|
||||
if (restraint_grad != nullptr)
|
||||
Widen(engine_->HostRestraintGradient(), atom_ptrs_.size(), *restraint_grad, nthreads_);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -280,32 +294,34 @@ private:
|
||||
// atoms whose B moved).
|
||||
void SetAtoms(const gemmi::Model &model) {
|
||||
std::optional<RefinementStage> stage("gpu.atoms_host");
|
||||
atom_ptrs_ = Atoms(model);
|
||||
// the pointers kept from the last call where the model is the same one still (its context's own)
|
||||
if (!SameAtoms(model, atom_ptrs_))
|
||||
atom_ptrs_ = Atoms(model);
|
||||
const std::vector<const gemmi::Atom *> &atoms = atom_ptrs_;
|
||||
const int n = static_cast<int>(atoms.size());
|
||||
if (last_b_.size() != atoms.size()) {
|
||||
last_b_.assign(atoms.size(), NAN);
|
||||
atoms_.resize(atoms.size());
|
||||
pos_.resize(atoms.size());
|
||||
}
|
||||
std::vector<std::array<float, 4>> pos(atoms.size());
|
||||
std::vector<char> changed(atoms.size(), 0);
|
||||
changed_.assign(atoms.size(), 0);
|
||||
const int n3[3] = {setup_.grid.nu, setup_.grid.nv, setup_.grid.nw};
|
||||
ParallelChunks(n, ThreadsForWork(atoms.size(), nthreads_, 4096), [&](int lo, int hi) {
|
||||
for (int i = lo; i < hi; i++) {
|
||||
if (!(atoms[i]->b_iso == last_b_[i])) {
|
||||
atoms_[i] = DensityAtom(*atoms[i], atoms[i]->b_iso, dc_);
|
||||
last_b_[i] = atoms[i]->b_iso;
|
||||
changed[i] = 1;
|
||||
changed_[i] = 1;
|
||||
}
|
||||
const gemmi::Fractional f = cell_.fractionalize(atoms[i]->pos);
|
||||
pos[i] = {static_cast<float>(f.x * n3[0]), static_cast<float>(f.y * n3[1]), static_cast<float>(f.z * n3[2]), 0};
|
||||
pos_[i] = {static_cast<float>(f.x * n3[0]), static_cast<float>(f.y * n3[1]), static_cast<float>(f.z * n3[2]), 0};
|
||||
}
|
||||
});
|
||||
stage.reset();
|
||||
const RefinementStage upload("gpu.upload");
|
||||
if (std::find(changed.begin(), changed.end(), 1) != changed.end())
|
||||
if (std::find(changed_.begin(), changed_.end(), 1) != changed_.end())
|
||||
engine_->SetDensities(atoms_.data(), atoms_.size());
|
||||
engine_->SetPositions(pos);
|
||||
engine_->SetPositions(pos_.data(), pos_.size());
|
||||
}
|
||||
|
||||
int device_;
|
||||
@@ -321,6 +337,8 @@ private:
|
||||
std::vector<const gemmi::Atom *> atom_ptrs_; // the model's atoms at the last SetAtoms()
|
||||
std::vector<float> last_b_;
|
||||
PinnedVector<ModelDensityAtom> atoms_; // pinned: a B rung sends them every evaluation (37 MB on a large model)
|
||||
PinnedVector<std::array<float, 4>> pos_;
|
||||
std::vector<char> changed_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in new issue
Block a user