Refinement GPU: the robust scale fit on the card, the pool back in every trial slot

The score's robust fine pass (pseudo-Huber IRLS of ModelScaleBox::robust) on the device, so that refinement on
a GPU no longer fits its scale on the host every cycle - with the trials side by side that was a robust grid
search on one thread per slot (13-133 s per refinement on the seven timing sets, against 1.7-8 s before the
score commit).

- ModelScaleGPU: the weights multiply the WSSR and J^T J / J^T r in scale_sums (a weight row per fit,
  reserved at the first robust fit); robust_residuals / robust_weights kernels; the median of each fit's
  first residuals taken on the host as FitModelScale takes it (nth_element at n/2, on the fits' threads);
  LevMarBatch split out of FitBatch so that the rounds restart from where a fit stands. FitSolvent(...,
  robust) refits the coarse winner in the fine pass, as FitModelScale's robust fit does; the plain fit is
  unchanged.
- ModelScaleGPUPool::Fit(..., robust); its streams at the highest priority: a fit is a chain of small
  launches the host waits on, and on a card shared with the trials' long kernels each wait queued behind
  them (refit.scale 58-87 ms -> 14-45 ms per cycle, the result the same to the bit).
- Refinement: the main context and every trial slot fit on their pool (robust); the CPU engine keeps
  FitModelScale's robust fit.

The card's fit is FitModelScale's to rounding (float residuals and weights, the steps' anisotropic factor in
float), deterministic run to run and whichever engine a fit takes: R-work/R-free of the 14 parity sets on
the GPU equal the CPU engine's to 4 digits (largest difference 1.4e-6 on the 3.6 A set).
Test: ModelScaleGPUPool_RobustMatchesFitModelScale (one reflection in fifty a 4x outlier, five cells).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit d15f048cbd7ed2d6b430f9f9cb5018ca284da8f0)
This commit is contained in:
leonarski_f committed 2026-10-26 01:38:24 +01:00
1 parent a218c0bec8
commit 46ad2c10ed
6 files changed
+202 -30

No files matched your search

@@ -100,6 +100,9 @@ struct Context {
RefinementReflections refl;
std::unique_ptr<RefinementEngine> engine;
std::unique_ptr<BulkSolventMask> mask;
#ifdef JFJOCH_USE_CUDA
std::unique_ptr<ModelScaleGPUPool> scale_gpu;
#endif
size_t nthreads = 1;
int device = -1; // the engine's GPU
size_t engine_bytes = 0; // and what it holds there
@@ -193,8 +196,13 @@ struct Context {
gemmi::Scaling<float> scaling(cell, sg);
scaling.use_solvent = true;
scaling.prepare_points(fc, fo, &fm);
// the scale of the score: by robust least squares (ModelScaleBox::robust), on the CPU
FitModelScale(scaling, {.b_hi = SolventBMax(Model()), .robust = true}, nthreads);
// the scale of the score: by robust least squares (ModelScaleBox::robust)
#ifdef JFJOCH_USE_CUDA
if (scale_gpu)
scale_gpu->Fit(scaling, SolventBMax(Model()), nthreads, true);
else
#endif
FitModelScale(scaling, {.b_hi = SolventBMax(Model()), .robust = true}, nthreads);
s3.reset();
k_sol = scaling.k_sol;
b_sol = scaling.b_sol;
@@ -668,6 +676,7 @@ void MakeEngine(Context &ctx, bool gpu, const RefinementSettings &settings, Logg
const RefinementStage stage("engine");
ctx.engine.reset();
#ifdef JFJOCH_USE_CUDA
ctx.scale_gpu.reset();
if (gpu && get_gpu_count() > 0) {
try {
const int device = get_gpu();
@@ -679,12 +688,15 @@ void MakeEngine(Context &ctx, bool gpu, const RefinementSettings &settings, Logg
ctx.b_ceiling, budget, &bytes, settings.nthreads);
ctx.device = device;
ctx.engine_bytes = bytes;
if (!ctx.engine)
if (ctx.engine)
ctx.scale_gpu = std::make_unique<ModelScaleGPUPool>(device, ctx.refl.Size(), 1);
else
logger.Info("Refinement: on the CPU - the GPU cannot take this case or {:.1f} GB is over its budget",
bytes / 1e9);
} catch (const JFJochException &e) {
logger.Warning("Refinement: the GPU failed ({}), on the CPU", e.what());
ctx.engine.reset();
ctx.scale_gpu.reset();
}
}
#else
@@ -752,6 +764,7 @@ std::unique_ptr<OwnSlot> MakeOwnSlot(const gemmi::Structure &before_topology, co
ctx.b_ceiling, SIZE_MAX, nullptr, nthreads);
if (!ctx.engine)
return nullptr;
ctx.scale_gpu = std::make_unique<ModelScaleGPUPool>(device, ctx.refl.Size(), 1);
} catch (const JFJochException &) {
return nullptr;
}