RotationScaleMerge: fit the error model on the device in the GPU build (tier F)
FitErrorModelGPU puts the samples in the host's rank order with four stable radix passes, so the equal-count bins hold the same samples; computes every normalised deviation with the host's rounding, so every bin median is the host's to the bit; and hands the per-bin terms to the host's own update step, now ErrorModelUpdate (a pure extraction from FitErrorModel). The one thing that differs is the order each bin's two sums are added in: a fixed tree on the device, the order nth_element happened to leave the bin in on the host. a and b^2 can therefore differ from the host fit in their last bits - measured on two synthetic pools: relative difference 2e-16 and 3e-13 in a, 5e-16 in b^2. The device fit is deterministic (same pool, same bits). p.mtz md5 nevertheless unchanged on myob, cytc, 8a1a and 8qaw (GPU build): the difference does not survive the float rounding of the merged sigmas there. A knife-edge decision elsewhere could still see it, which is why this is kept as its own commit. The host fit took 0.4-0.6 s per fit on the large merges, about half of it the initial equal-count split, with only 16-way parallelism in the iterations. Device peak about 72 bytes per sample (the rank sort; each stage's buffers are freed when it is done); the free memory is checked first and a device without it stops with a message naming the CPU build. Kept last on the branch so that it can be dropped on its own. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
@@ -502,3 +502,32 @@ TEST_CASE("ErrorModel_BNotMeasuredOnWeakData") {
|
||||
CHECK(fit.b2 == 0.0);
|
||||
CHECK(fit.a == Catch::Approx(0.9).epsilon(0.03));
|
||||
}
|
||||
|
||||
#ifdef JFJOCH_USE_CUDA
|
||||
#include "../common/CUDAWrapper.h"
|
||||
#include "../image_analysis/scale_merge/ErrorModelGPU.h"
|
||||
|
||||
// The device fit against the host one. Same bins and same medians; only the order each bin's two sums
|
||||
// are added in differs, so a and b^2 agree to rounding and every flag agrees exactly. The same pool
|
||||
// twice on the device gives the same bits.
|
||||
TEST_CASE("ErrorModel_DeviceFitIsTheHostFit") {
|
||||
if (get_gpu_count() == 0) {
|
||||
WARN("No CUDA GPU present. Skipping ErrorModel_DeviceFitIsTheHostFit");
|
||||
return;
|
||||
}
|
||||
for (const auto &pool : {SyntheticErrorModelSamples(1.3, 0.03, 300.0, 0.003),
|
||||
SyntheticErrorModelSamples(0.9, 0.05, 1.5, 0.0)}) {
|
||||
std::vector<ErrorModelBinned> scratch;
|
||||
const auto host = FitErrorModel(pool, scratch, 4);
|
||||
const auto dev = FitErrorModelGPU(pool);
|
||||
const auto again = FitErrorModelGPU(pool);
|
||||
CHECK(dev.active == host.active);
|
||||
CHECK(dev.b_measured == host.b_measured);
|
||||
CHECK(dev.b_resolved == host.b_resolved);
|
||||
CHECK(dev.a == Catch::Approx(host.a).epsilon(1e-12));
|
||||
CHECK(dev.b2 == Catch::Approx(host.b2).epsilon(1e-12).margin(1e-300));
|
||||
CHECK(again.a == dev.a);
|
||||
CHECK(again.b2 == dev.b2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user