On macOS, where a Metal device may be used (not under --no-gpu), each merge's rotation stage - the sphere and the peaks' refinement - runs on the GPU: the same trilinear interpolation of the same transform in float, one threadgroup per orientation, every bin's sums of |F|^2, |F|^4 and |F|^2 (E^2 - 1) in a fixed order, and the correlation made of them in double on the host. The GPU reads its own copy of the transform in 2 x 2 x 2 bricks (1.3x faster than the grid's rows, as the eight corners then lie on fewer cache lines), made for the rotation stage and given back before the translation function; the seeds' orientations later on stay on the CPU. Where the GPU fails, the stage goes on on the CPU with a warning. The kernel is MolecularReplacementMetal.metal, carried in the binary as the Bragg integration's are. Elsewhere nothing changes. Measured on an Apple M6 (16 GB), alternating with rc176 + the CPU prefetch, two runs each: - 4nwv (the timing benchmark's moved model): rotation 78.9 -> 30.7 s, wall 344.5 -> 298.8 s; peak footprint 16.1-16.8 -> 18.5 GB. - 9s02: rotation 2.99 -> 0.28 s, wall 79.2 -> 76.4 s. p.hkl, the placed model and the maps are identical to the CPU's on both, and so is every line of the molecular-replacement log (RF Z, TF Z, R values). The RF itself agrees with the CPU's to 1e-4, with the same best orientation (MolecularReplacementMetal_MatchesCPU, [portable]). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018My7tqt8a4LyKKjP3FhScT
39 lines
1.8 KiB
C++
39 lines
1.8 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
// The rotation function of MolecularReplacement.h on an Apple GPU (macOS only): the same trilinear interpolation
|
|
// of the same molecular transform, the same bins and the same correlation, in float on the device. It agrees with
|
|
// the CPU to rounding, not bit for bit. The device sums every bin's |F|^2, |F|^4 and |F|^2 (E^2 - 1) for each
|
|
// orientation in a fixed order, and the host makes the correlation of them in double.
|
|
//
|
|
// The device reads its own copy of the transform, in 2 x 2 x 2 bricks: the eight corners of an interpolation then
|
|
// lie on fewer cache lines than in the grid's rows, which the GPU, waiting on memory as the CPU does, reads 1.3x
|
|
// faster. The copy takes as much memory as the transform and is held as long as this object.
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "MolecularReplacement.h"
|
|
|
|
class MolecularReplacementMetal {
|
|
public:
|
|
// Whether the rotation function of `ft` can run on the GPU: a Metal device the process may use (rugnux
|
|
// --no-gpu hides it, see hide_gpus) whose buffers hold the transform.
|
|
static bool Available(const MolecularTransform &ft);
|
|
|
|
// Copies the transform into the device's layout. Throws JFJochException on a Metal failure, as every call
|
|
// below does.
|
|
MolecularReplacementMetal(const MolecularTransform &ft, size_t nthreads);
|
|
~MolecularReplacementMetal();
|
|
|
|
// RotationFunction() of MolecularReplacement.h; may be called from several threads at once.
|
|
std::vector<double> RotationFunction(const SearchReflections &refl, const gemmi::UnitCell &cell,
|
|
const gemmi::SpaceGroup &sg, const std::vector<gemmi::Mat33> &rotations) const;
|
|
|
|
private:
|
|
struct State;
|
|
std::unique_ptr<State> state_;
|
|
};
|