Build Packages / Create release (push) Successful in 7s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 4m38s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m58s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 14m20s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 18m0s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 21m31s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 21m55s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 21m49s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 13m41s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 26m7s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 15m22s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 21m54s
Build Packages / Generate python client (push) Successful in 31s
Build Packages / Build documentation (push) Successful in 1m13s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 18m45s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 23m54s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 25m56s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 25m47s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 20m33s
Build Packages / Unit tests (push) Failing after 1h28m5s
Build Packages / Validation Battery (push) Successful in 34m34s
Combines mr-multigpu (per-card engine budgets, a transform copy on each other card, the rotation/translation functions split over the cards) with mr-transform-layout (the GPU transform optionally held corner-packed, same bits, 2.2x faster rotation function). Conflict (MolecularReplacement.cpp): the two new functions side by side (PackMolecularTransformGPU next to SplitRotationFunction/SplitTranslationFunction). On top of the textual resolution: - One layout decision for all cards, not one per card: the functions are split into equal slices, so a mix of packed and plain cards would run at the speed of the plain ones; either layout gives the same bits. - PackMolecularTransformGPU now plans with the other cards' budgets too and packs only where every card keeps what the plain plan gives it: this card's transform, engines and score engine, and on every other card its copy and its engine count. A packed copy is made after the card's engines are reserved; copy and engines within half of that card's budget leave room for the plain grid it is made from. - The copies on the other cards are made in the chosen layout, and the per-card plan (copies, engines, room) is made with the bytes actually placed. - Test: four 15.6 GB cards stay plain (packed, each other card would fall from T+2 to T+1); with 24 GB cards beside, packed. So on a 4x T4 4nwv runs split and plain, on one card packed; whether giving up 3 of 7 engines for the packed rotation function would pay there is left to a server measurement. Workstation (1x RTX 5080, --gpulock), against rc176: battery timing --benchmark standard (9zlo, 9s02, 9gjx, 4nwv) and --benchmark inhouse (myob, cytc, 4nwv) on GPU, inhouse also --no-gpu: p.hkl, p_model.pdb and the MR_ report block byte-identical on every set. 4nwv GPU: rotation 5.53 -> 2.50 s, MR step 20.1 -> 16.5 s (packed, 2.28 GB in place of 0.84 GB). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
91 lines
5.7 KiB
C++
91 lines
5.7 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
// The molecular-replacement search on a GPU (CUDA builds only; the header itself needs no CUDA): the
|
|
// rotation function and the translation function of MolecularReplacement.h, the same functions as the
|
|
// CPU computes - the same trilinear interpolation of the same molecular transform, the same bins, the
|
|
// same coefficients on the same grid - in float on the device, with cuFFT for FFTW. The two agree to
|
|
// rounding, not bit for bit. Every reduction runs in a fixed order and nothing is summed with an
|
|
// atomic, so the GPU is deterministic on its own.
|
|
|
|
#include <complex>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "MolecularReplacement.h"
|
|
|
|
// CropHalfL(MapToFPhi(map), reach) (ModelFFT.h) with cuFFT on the calling thread's current device: the same
|
|
// transform, the same half-l layout and conventions, to float rounding. For the molecular transform of a large
|
|
// model, whose box is hundreds of millions of points. MapToFPhiGPUBytes: the device memory it takes while it runs.
|
|
size_t MapToFPhiGPUBytes(const gemmi::Grid<float> &map, int reach);
|
|
gemmi::FPhiGrid<float> MapToFPhiGPU(const gemmi::Grid<float> &map, int reach);
|
|
|
|
struct MolecularReplacementGPUState;
|
|
struct MolecularReplacementGPUGroup;
|
|
|
|
struct TranslationSetupGPU;
|
|
|
|
class MolecularReplacementGPU {
|
|
public:
|
|
// Uploads the molecular transform, on the calling thread's current device; `packed`: with each point's
|
|
// interpolation corners together, read from two places instead of four, for more memory (TransformBytes)
|
|
// and to the same bits. Throws JFJochException on a CUDA failure, as every call below does.
|
|
explicit MolecularReplacementGPU(const MolecularTransform &ft, bool packed = false);
|
|
~MolecularReplacementGPU();
|
|
|
|
std::vector<double> RotationFunction(const SearchReflections &refl, const gemmi::UnitCell &cell,
|
|
const gemmi::SpaceGroup &sg, const std::vector<gemmi::Mat33> &rotations);
|
|
|
|
// The translation function of one group at each of `rotations`; with `known`, beside a known partial
|
|
// structure (KnownStructureFactors). `prepared`: Prepare() of this setup, for a setup used again and again;
|
|
// made for this call otherwise.
|
|
std::vector<TranslationPeak> TranslationFunction(const TranslationSetup &setup,
|
|
const std::vector<gemmi::Mat33> &rotations,
|
|
const std::vector<std::complex<double>> *known = nullptr,
|
|
TranslationSetupGPU *prepared = nullptr);
|
|
// What the translation function needs of `setup`, worked out once on the host; with `known_part`, for the
|
|
// function beside a known partial structure.
|
|
std::unique_ptr<TranslationSetupGPU, void (*)(TranslationSetupGPU *)> Prepare(const TranslationSetup &setup,
|
|
bool known_part);
|
|
|
|
// The device memory the transform is held in, plain or packed; while the packed one is made, both.
|
|
static size_t TransformBytes(const MolecularTransform &ft, bool packed);
|
|
// The device memory a call takes for as long as it runs, and gives back when it returns: RotationFunction()
|
|
// of `rotations` against `refl`'s reflections in `sg`, and TranslationFunction() of `rotations` (with
|
|
// `known`, beside a known partial structure).
|
|
static size_t RotationBytes(const SearchReflections &refl, const gemmi::SpaceGroup &sg, size_t rotations);
|
|
static size_t TranslationBytes(const TranslationSetup &setup, size_t rotations, bool known);
|
|
|
|
private:
|
|
std::unique_ptr<MolecularReplacementGPUState> state_;
|
|
};
|
|
|
|
// The search on one card: the transform uploaded on `device`.
|
|
struct MolecularReplacementGPUOnCard {
|
|
int device = 0;
|
|
MolecularReplacementGPU *gpu = nullptr;
|
|
};
|
|
|
|
// What a function's slice holds on its card while it runs: called on the slice's thread, with the card and the
|
|
// bytes its buffers take, and kept until the slice is done. Empty: nothing is held.
|
|
using MolecularReplacementGPUHold = std::function<std::shared_ptr<void>(int device, size_t bytes)>;
|
|
|
|
// The rotation and translation functions with the orientations split over `cards`: the k-th card scores the
|
|
// k-th of cards.size() contiguous slices of `rotations`, on a thread of its own beside the others, and the
|
|
// results are put back in the order of `rotations`. Every orientation is scored by itself - one block of the
|
|
// rotation function, one map of the translation function - and nothing is summed across orientations, so the
|
|
// result is that of one card, bit for bit, on any number of cards.
|
|
std::vector<double> SplitRotationFunction(const std::vector<MolecularReplacementGPUOnCard> &cards,
|
|
const SearchReflections &refl, const gemmi::UnitCell &cell,
|
|
const gemmi::SpaceGroup &sg, const std::vector<gemmi::Mat33> &rotations,
|
|
const MolecularReplacementGPUHold &hold = {});
|
|
std::vector<TranslationPeak> SplitTranslationFunction(const std::vector<MolecularReplacementGPUOnCard> &cards,
|
|
const TranslationSetup &setup,
|
|
const std::vector<gemmi::Mat33> &rotations,
|
|
const std::vector<std::complex<double>> *known,
|
|
TranslationSetupGPU *prepared,
|
|
const MolecularReplacementGPUHold &hold = {});
|