Commit Graph
3 Commits
Author SHA1 Message Date
leonarski_fandClaude Opus 5.5 f46dc303a3 Rotation scaling on the GPU: two-pass ingest, decided before the card is touched, output unchanged
- The resident ingest reads the reflections twice: first only what the sort
  and the cut need (frame position, resolution, background, raw-hkl key),
  then - once the GPU/CPU decision has been made on the exact counts - the
  observations the cut kept, written straight to their place. The
  observation arrays and the sort buffers are never on the card together,
  and no observation field is cut (held twice) any more.
- A set too large even for that sort scales on the CPU before anything is
  allocated on the card (ScaleOnCPUWhereSortTooLarge, same 80%-of-total rule).
- The predicted detector position never goes to the card: it is taken on the
  host, and the combine emits the index of each full's peak partial instead
  of its px/py (8 B less per observation and 4 per full).
- A full carries its raw-hkl run index instead of h, k, l (8 B less per
  full); the constant-1 partiality array of the fulls is gone.
- The fulls' CSR sorts and the error-model rank sort run on cub::DoubleBuffer.

Measured with per-array device accounting on an RTX 5080, largest engine of
each set, before (c130c7923) -> after, MB:
  set   obs/hkl          ingest peak (B/refl)        post-decision peak
  4nwv  32.7 M/15.4 M     3277 (100) ->  2336 (72)    4265 -> 3625
  8qaw 122.7 M/ 8.6 M    10756 (88)  ->  7421 (61)    9327 -> 8156
  9v16  54.7 M/ 5.7 M     4838 (89)  ->  3419 (63)    4283 -> 3774
  7qij  78.1 M/ 2.2 M     6768 (87)  ->  4707 (60)    5242 -> 4707
  8sa8  50.0 M/ 5.0 M     4107 (82)  ->  3134 (63)    4205 -> 3603
The sort before the decision now holds 32 B per reflection (46 on a P1-like
set with many runs); the engine's overall peak is after the decision on all
of them.

GPUScalingPeakBytes refitted on the same fourteen engines: 63 + 115 ->
56 B/obs + 99 B/hkl, within 9%. DeviceBytes as an upper bound: 66/94/52 ->
58/30/86 per obs/event/hkl, ingest 89/56 -> 60/42 per reflection/run.

p.hkl byte-identical to the rc175 baseline on 15 battery sets (4nwv, 8qaw,
9v16, 7qij, 8sa8, 11if, 5reo, 6oel, 9jzo, 9min, metformin, lyso_x06da_ref,
thau_bl1a_3p8keV, insu_I_x06da_5keV_2, myob_x06da); compare shows no change.
[RotationScale] (RotationIngestGPU now also checks the host positions) and
ErrorModel_DeviceFitIsTheHostFit pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:13:41 +02:00
leonarski_fandClaude Opus 5.5 d5bf32763f rotation merge: the resident ingest smooths the geometry on the card (last-bit change)
The rest of the resident ingest moves to the card: the delta_phi re-solve against each
frame's smoothed lattice, the one exact-Bragg angle per rocking event, the partiality
recomputed from the smoothed mosaicity (with corr brought to it) and the histogram of the
frames an event spans. The host keeps the per-frame work (the smoothed lattices, the
mosaicity window) and reads back only corr; nothing per observation crosses back, and the
host arrays the smoothing worked on are no longer built on this path.

The integer work is the host's to the bit (the event-span histogram). The floating-point
work differs in the last bits: every rounding on the card is explicit (no contraction, so
the same bits on any GPU), the host's compiler fuses some of the same products into sums,
and erf/atan2 come from the CUDA library. This is the one place in the lever where results
change; see the hand-back report for the sets and metrics it moves.

Measured on 7qij (main engine, 78 M reflections): the whole ingest 6.5 s host + 1.3 s device
before this lever -> 2.9 s, of which 2.4 s is the upload (memory-bound, beside the
post-refinement gather) and 0.25 s this smoothing. Same space group, error model,
resolution and reflection count; IMEAN differs at the 1e-7 level except on near-zero
intensities.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
2026-10-12 22:46:16 +02:00
leonarski_fandClaude Opus 5.5 f88ff0440d rotation merge: the resident ingest sorts and cuts the reflections on the card
With the GPU resident, the host ingest swept the source reflections four times (background
and h histogram, sort keys, the in-range build, the device staging), bucket-sorted the keys
and cut the raw-hkl runs: 5-6 s of host time per engine on a 78 M-reflection sweep, run
twice beside the quality guard. Now the frames' reflections are copied once, through two
pinned buffers, to the card, where the per-frame background is summed, the reflections
are ordered by two stable radix passes ((image, index), then raw hkl - the one order the
host's total comparator gives), cut into runs by run-length encoding, filtered by the
resolution range and compacted. The observations, the runs and their order are the host
ingest's to the bit; a new GPU test checks the layout against the host's sort and cut.
The geometry smoothing still runs on the host in this step, on arrays read back.

The all-observation arm's engine is cloned after the main engine's device ingest (its
DeviceBytes are known only then) and copies the runs on the card as well. DeviceBytes now
also covers the ingest's own peak (90 bytes per reflection, 64 per run before the cut).

Measured: p.hkl and MTZ data identical on cytc and 7qij.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
2026-10-12 22:38:25 +02:00