RotationScaleMerge: GPU scale-fulls, fulls kept resident (phase 2 step 2)
Scale the combined fulls (Unity model) on the device so they no longer round-trip between the combine and the merge: after the GPU combine, build the fulls' per-frame and per-ASU-group CSRs on the host from just the small key arrays (f_frame/f_group) with a deterministic counting sort - no GPU stable-sort - then scale in place and download once. The four scaling kernels are reused unchanged except FitPerFrameGKernel, which gains an optional `perm` argument (null for the partials, whose arrays are already frame-contiguous; a frame-grouping permutation for the emit-ordered fulls) so the fulls are scaled without a physical reorder. The Unity model falls out of giving the fulls all-ones partiality/rlp/zeta (coeff = mean), so no other kernel changes and the committed phase-1 partial-scaling path is bit-identical (perm == null -> idx == i). Validated across the rotation battery (JFJOCH_RSM_GPU_COMBINE=1): all 15 deterministic crystals stay run-to-run deterministic and their merged output is bit-identical to the CPU path (SG/ISa/CC1.2/completeness). The lone exception is EP_cs_01-24 (CC1/2 2%, R_meas 379% - unindexable noise): merged intensities/CC/completeness match exactly, but the ill-conditioned 16-bin error-model b fit amplifies the ~1e-7 scale-fulls rounding to ISa 10.6 vs 10.8 - benign, same class as the accepted phase-1 GPU rounding. The 3 upstream-nondeterministic crystals vary as before (GPU-prediction overflow, not this). Scale-fulls drops from ~0.09s to ~0 across the two passes; combine+scale-fulls region ~0.32s GPU vs ~0.46s CPU on lyso. Still opt-in (fulls are downloaded for the host merge; the win grows once the merge/error-model also stay resident). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1098,31 +1098,58 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search,
|
||||
|
||||
// --- 3. 3D combine of per-frame partials into fulls (fulls inherit their ASU group here). ---
|
||||
bool combined_on_gpu = false;
|
||||
bool scaled_fulls_on_gpu = false;
|
||||
#ifdef JFJOCH_USE_CUDA
|
||||
// The GPU combine mirrors Combine() exactly but keeps the fulls on the device; the diagnostic dump
|
||||
// (serial, one writer) has no GPU equivalent, so fall back to the CPU path when it is requested.
|
||||
// GPU combine (+ scale-fulls) keeps the fulls resident on the device: combine, then build the frame /
|
||||
// ASU-group CSRs on the host from just the small key arrays (a deterministic counting sort - no GPU
|
||||
// stable-sort), scale the fulls in place, and download only once. Mirrors Combine() + the Unity
|
||||
// scale-fulls loop below. The diagnostic dump (serial, one writer) has no GPU path -> CPU fallback.
|
||||
if (gpu_active_ && gpu_combine_ && observation_dump_path.empty()) {
|
||||
std::vector<float> corr(partials.size()); // refresh the smoothed corr on the device
|
||||
for (size_t i = 0; i < partials.size(); ++i) corr[i] = partials[i].corr;
|
||||
gpu_->SetCorr(corr.data());
|
||||
const int nf = gpu_->Combine(rawrun_group.data(), min_partiality, capture_uncertainty_coeff);
|
||||
fulls.assign(nf, Obs{});
|
||||
g_full.assign(n_frames, 1.0);
|
||||
|
||||
if (scale_fulls && nf > 0) {
|
||||
// Frame + group CSRs over the emit-ordered fulls, built by counting sort on the host (stable,
|
||||
// deterministic). frame is always in [0, n_frames); group is <0 for absent/out-of-range fulls.
|
||||
std::vector<int32_t> ff(nf), fg(nf);
|
||||
gpu_->GetFullsKeys(ff.data(), fg.data());
|
||||
std::vector<int32_t> f_start(n_frames, 0), f_count(n_frames, 0), f_perm(nf);
|
||||
for (int i = 0; i < nf; ++i) ++f_count[ff[i]];
|
||||
for (int f = 1; f < n_frames; ++f) f_start[f] = f_start[f - 1] + f_count[f - 1];
|
||||
{ std::vector<int32_t> fill = f_start; for (int i = 0; i < nf; ++i) f_perm[fill[ff[i]]++] = i; }
|
||||
gpu_->SetFullsFrameCSR(f_perm.data(), nf, f_start.data(), f_count.data());
|
||||
|
||||
std::vector<int32_t> g_count(n_groups, 0), g_start(n_groups, 0);
|
||||
for (int i = 0; i < nf; ++i) if (fg[i] >= 0) ++g_count[fg[i]];
|
||||
int acc = 0;
|
||||
for (int g = 0; g < n_groups; ++g) { g_start[g] = acc; acc += g_count[g]; }
|
||||
std::vector<int32_t> g_perm(acc);
|
||||
{ std::vector<int32_t> fill = g_start; for (int i = 0; i < nf; ++i) if (fg[i] >= 0) g_perm[fill[fg[i]]++] = i; }
|
||||
gpu_->SetFullsGroups(g_perm.data(), acc, g_start.data(), g_count.data());
|
||||
|
||||
gpu_->ScaleFulls(scaling_iter, SCALE_ROBUST_K, min_partiality);
|
||||
scaled_fulls_on_gpu = true;
|
||||
}
|
||||
|
||||
fulls.assign(nf, Obs{});
|
||||
std::vector<int32_t> fh(nf), fk(nf), fl(nf), fframe(nf), fgroup(nf);
|
||||
std::vector<float> fI(nf), fsig(nf), fd(nf), fimg(nf);
|
||||
std::vector<float> fI(nf), fsig(nf), fd(nf), fimg(nf), fcorr(nf, 1.0f);
|
||||
std::vector<uint8_t> fon(nf);
|
||||
gpu_->GetFulls(fh.data(), fk.data(), fl.data(), fI.data(), fsig.data(), fd.data(),
|
||||
fimg.data(), fframe.data(), fon.data(), fgroup.data());
|
||||
if (scaled_fulls_on_gpu) gpu_->GetFullsCorr(fcorr.data());
|
||||
for (int i = 0; i < nf; ++i) {
|
||||
Obs &o = fulls[i];
|
||||
o.h = fh[i]; o.k = fk[i]; o.l = fl[i];
|
||||
o.I = fI[i]; o.sigma = fsig[i]; o.d = fd[i];
|
||||
o.rlp = 1.0f; o.partiality = 1.0f; o.corr = 1.0f;
|
||||
o.rlp = 1.0f; o.partiality = 1.0f; o.corr = fcorr[i];
|
||||
o.image_number = fimg[i]; o.frame = fframe[i];
|
||||
o.on_ice = fon[i]; o.group = fgroup[i];
|
||||
}
|
||||
SortFullsByFrame();
|
||||
logger.Info("3D combine (GPU): {} fulls", nf);
|
||||
logger.Info("3D combine{} (GPU): {} fulls", scaled_fulls_on_gpu ? " + scale-fulls" : "", nf);
|
||||
combined_on_gpu = true;
|
||||
}
|
||||
#endif
|
||||
@@ -1131,7 +1158,7 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search,
|
||||
lap("combine");
|
||||
|
||||
// --- 4. Scale the fulls (XDS order, Unity model). ---
|
||||
if (scale_fulls) {
|
||||
if (scale_fulls && !scaled_fulls_on_gpu) {
|
||||
std::vector<double> full_mean;
|
||||
for (int it = 0; it < scaling_iter; ++it) {
|
||||
ReduceGroupMeans(fulls, n_groups, false, {}, full_mean);
|
||||
|
||||
Reference in New Issue
Block a user