Commit Graph
6 Commits
Author SHA1 Message Date
leonarski_f 8718065a7c Refinement GPU: the bulk-solvent mask on its own fine grid on the card
The score draws the mask on a grid of its own (min(d_min/3, 0.4 A), BulkSolventMask) on the host; on a GPU
engine that was a CPU mask and transform every cycle, per trial slot on one thread. Now the engine masks
on that grid itself:

- RefinementGPUSetup::mask_grid (BulkSolventMask::MaskGrid, the grid the host mask uses); where it is not
  the structure factors' grid the engine reserves a grid, a spectrum and an R2C plan for it (counted in
  DeviceBytes and the FFT work area), and Fmask masks (ModelMaskGPU), transforms and reads the rows there.
  Refit takes the engine's Fmask on a GPU, BulkSolventMask on the CPU.
- ModelMaskGPU's shrink: each edge point marks its stencil as solvent instead of every macromolecule point
  searching its stencil for an edge - the same idempotent stores to the same points, so the same mask to
  the bit (ModelMaskGPU_* tests unchanged), and a thin shell of points visited instead of half the grid.
- BulkSolventMask finds its symmetry orbits at its first mask: a GPU refinement never draws one on the host
  (1.1 s of trial-slot setup on the 3.6 A set).

The card's mask is gemmi's but at points exactly on an atom's radius (ModelMaskGPU.h): R-work/R-free of the
14 parity sets on the GPU are the CPU engine's to 4 digits. Refinement deterministic run to run on the GPU
(7 timing sets, refined model and report identical over 5 runs of successive builds that did not change
results).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 89b5d00907c9ad99e8b389f6272a3724f981cd10)
2026-10-26 01:38:24 +01:00
leonarski_f 5569f3d1f2 Refinement GPU: the X-ray target and the restraints in one submission with one wait
RefinementEngine::EvaluateWithRestraints evaluates both targets at the model's atoms. On the GPU the X-ray
chain is queued first, the restraints' coordinates are written into a pinned buffer while the device works
on it, the restraint kernels follow on the same stream, and the host waits once; the sums and gradients
come back through pinned buffers. Same kernels on the same inputs, same bits. The CPU engine keeps calling
the two in turn.

9yl4: evaluation 5.1 -> 4.7 ms, refinement 14.2 -> 13.4 s; 5reo 4.0 -> 3.9 s (RTX 5080, -N 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 51434b98b62a9e6322ffe811bf272a45ff6ce003)
(cherry picked from commit 0bcfa3f8041cec4bb4bc376b8559f30c36d3ac80)
2026-10-26 01:38:23 +01:00
leonarski_f e3782cabe4 Refinement GPU: atom densities uploaded only when some B moved, one device copy, positions through a pinned buffer
Every evaluation uploaded both copies of the atoms' densities (the gather's and the gradient's) and their
boxes, although in a positions-only minimisation no B changes between two Refits. The engine now keeps one
copy (the gradient kernel reads ModelDensityGPU's atoms and boxes), uploads it only where some atom's
density was made again, and sends the positions from a pinned buffer without waiting. The densities that
are made again (every atom in a B minimisation) and the grid positions are worked out on the refinement's
threads. Same bits.

9yl4: upload 3.29 -> 0.56 s, host side 1.29 -> 0.61 s, refinement 22.3 -> 17.7 s; 5reo 4.8 -> 4.4 s
(RTX 5080, -N 8, loaded box).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 46c3b67c91aee0aec3473b0fb1a4945c00bda070)
(cherry picked from commit 0aebc98110c8155932af647d6204891aec662294)
2026-10-26 01:38:22 +01:00
leonarski_f 24b1711d64 Refinement: time per stage - an NVTX range per call and one TIMING line per stage with its sum and calls
RefinementStage sums the time of each stage over a refinement (Refit and its parts, the evaluations and
their X-ray, restraint, host and upload shares, the repulsion build, the riding hydrogens, the rungs) and
logs it at debug level at the end; rugnux_refine_test -v shows it. No change to any result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 9491f54e0beccf36c65ebcec9afff90d08485f86)
(cherry picked from commit 0ce206a88df960754476ce2f75ffaed90fbf3123)
2026-10-26 01:38:22 +01:00
leonarski_fandClaude Opus 5.5 f077320efa Refinement restraints: n_slots, not slots - Qt defines slots as a macro, which broke the viewer's build through Rugnux.h
Build Packages / Create release (push) Successful in 8s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m21s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 10m42s
Build Packages / build:portable:linux-x86_64:cuda (push) Failing after 14m59s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 20m37s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 23m21s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 23m43s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 27m30s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 28m21s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 29m50s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 32m0s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 24m33s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / Build documentation (push) Successful in 1m8s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 27m31s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 28m26s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 28m6s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 22m30s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 28m12s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 25m20s
Build Packages / Unit tests (push) Successful in 1h36m6s
Build Packages / Validation Battery (push) Successful in 34m44s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
2026-10-25 10:22:02 +01:00
leonarski_f b4b68d368f Restrained refinement on the GPU: ML amplitude target, FFT gradient, dictionary restraints, R1 + R2 driver, blobs
RefineModel (Refinement.h) refines a placed model against the merge, reading and writing nothing:
- placement stage (R1): every chain of 50+ atoms as a rigid body at once (L-BFGS on six parameters per
  chain, their gradient from the per-atom one through Ceres jets), then one B shift per residue;
- restrained stage (R2): positions and isotropic B of every atom of a residue the embedded dictionary
  describes, the rest held fixed but kept in Fcalc and the mask; riding hydrogens added by gemmi's
  topology, moved with their parents, re-placed every macro-cycle. Macro-cycles refit the mask, the
  scale and bulk solvent (FitModelScale / ModelScaleGPUPool) and the likelihood's D and beta (ML on
  the free set in resolution bins), rebuild the repulsion pairs (symmetry mates included), then L-BFGS
  on positions and, where chosen, on B.
- weights: a ladder of full refinements at multiples of the gradient-balanced X-ray weight, chosen by
  R-free within bond/angle bounds (ties to the tighter geometry), then the B refinement tried at its own
  weights and kept only where it beats B held by more than the tie.

The target and its gradient (RefinementXray, RefinementGPU): the Rice/centric amplitude likelihood
(RefinementMath.h, shared host/device code), the gradient by Agarwal's FFT method - the adjoint of the
reciprocal-space symmetry composition into a half-l (CPU) or half-u (cuFFT) spectrum, one inverse real
transform, then each atom's density derivatives (xyz and B) summed over its box. On the GPU everything
per evaluation stays on the device: ModelDensityGPU's gather, r2c, a fused compose + likelihood kernel,
a CSR gather for the adjoint (no atomics), c2r, one warp per atom for the gradient; the restraints are
one kernel per restraint plus a per-atom gather of gradient slots; cub reductions. Deterministic.

Restraints (RefinementRestraints): bonds, angles, torsions (with esd, no phi/psi, no alternative sugar
puckers), chiral volumes, least-squares planes from gemmi's Topo; repulsions beyond 1-4 (vdW radii from
the dictionary's energy library, 2.6 A for H-bond pairs, 1.9 A metal-N/O/S); B similarity of bonded and
angle-related atoms.

DensityBlobs (D1): gemmi's flood-fill blobs of mFo-DFc above 3 sigma peaking above 4 sigma, placed beside
the model and labelled WATER / ION / LIGAND / MODEL_ERROR by distance, contacts, volume and the anomalous
map.

tools/rugnux_refine_test refines a model against a finished run's p.mtz (developer utility, not
installed). Tests: likelihood derivative, the embedded library, restraint values on ideal residues and
every restraint kind's gradient against finite differences (CPU, and the GPU against the CPU), the X-ray
gradient against finite differences in P1, P212121, C2 and P6122 (CPU and GPU), and a synthetic
refinement that recovers a shaken model. Credits in docs/ACKNOWLEDGEMENT.md and at the algorithms.

Measured on the open arm's battery outputs against REFMAC 5.8, 10 cycles, same p.mtz, free set and start
model (R-free): 5reo 0.2228 vs 0.2236, 8dyz 0.1733 vs 0.1651, 9chw 0.1995 vs 0.1969 (CPU engine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 8efe6f2322b3bfdae580bb34f256e67730dfc3c2)
2026-10-25 07:20:32 +01:00