Files
leonarski_fandClaude Opus 5.5 67ad89de11 Refinement GPU: the transform buffers in lanes - a slot's own where the card holds one, shared otherwise
The previous commit put every engine on a card on one set of working buffers, one evaluation at a
time. On a T4 two evaluations side by side cost only 9% more each than one, so serialising them
gives back the concurrency of two slots. The buffers (grid, spectrum, cuFFT work area, the mask's
own grid and spectrum) now come in RefinementGPULane: a slot takes a lane of its own where the card's
budget holds its engine and a lane, else only its engine on the card's least used lane (under that
lane's lock from launches to wait). The bulk-solvent mask's buffers stay one per card, under their
own lock. OwnSlotPlaces plans both per card.

4YBB (engine 2.02 GB, lane 1.48 GB, card 1.73 GB, scale pool 0.91 GB): RTX 5080 3 slots on 3 lanes,
512 s (4 slots on one lane 500 s, 3 slots unshared 516 s); a T4 (12.52 GB budget) 3 slots on 2 lanes,
an L4 5 on 4. Refined model CIF byte-identical. 51 cases / 22236 assertions pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVeDQqN9qRD57vnShzTC37
2026-10-28 02:43:28 +01:00

40 lines
2.9 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
// Restrained refinement's targets on a GPU (CUDA builds only): the RefinementEngine of RefinementXray.h with
// everything per evaluation on the device - the atoms' density (ModelDensityGPU's gather), its transform, the
// symmetry composed in reciprocal space, the likelihood target, the derivative map by the inverse transform
// of the target's derivatives and every atom's gradient read off it, and the geometry restraints. The host
// passes positions in and gets one value and one gradient per atom back.
#include <memory>
#include "RefinementXray.h"
// What an engine holds on its card: its own part; the part the engines like it on the same card share (the
// reflections' tables and the bulk-solvent mask's buffers), which the first of them there makes; and a lane of the
// transforms' working buffers, which an engine has to itself or shares with others, one evaluation at a time.
struct RefinementGPUBytes {
size_t engine = 0, card = 0, lane = 0;
};
// The engine for refining `model` against `refl` on `device`, or null where the GPU cannot take this case (an
// atom's box wider than the cell, too many symmetry operators) or its memory is over `budget` bytes.
// `b_max`: the largest B an atom may reach, which the gather's reserve is sized for. Throws JFJochException on a
// CUDA failure. `nthreads`: the host threads that work out the atoms' densities. `restraint_records`,
// `restraint_slots`: the most records (plane atoms included) and gradient slots its two restraint sets hold together,
// which the memory is worked out for (0: about thirty records and sixty slots per atom).
std::unique_ptr<RefinementEngine> MakeRefinementEngineGPU(int device, const gemmi::UnitCell &cell,
const gemmi::SpaceGroup &sg, double d_min,
const RefinementReflections &refl,
const gemmi::Model &model, double b_max, size_t budget,
RefinementGPUBytes *bytes = nullptr, size_t nthreads = 1,
size_t restraint_records = 0, size_t restraint_slots = 0);
// An engine like `like` (one the function above made) on `device`: the same case, and on a card where an engine
// like it is, that card's shared part with it - so it holds RefinementGPUBytes::engine there, and ::lane beside where
// `own_lane` (else it shares the card's least used lane). Throws JFJochException on a CUDA failure.
std::unique_ptr<RefinementEngine> MakeRefinementEngineGPU(const RefinementEngine &like, int device, size_t nthreads,
bool own_lane);