Skip to content

MCMM GPU Parallelization #27

Description

@SabariKumar

Background

The MCMM sampler (src/mcmm.py, src/concerted_rotation.py) already batches its two
GPU-bound stages across all N walkers: MMFF minimisation runs in one
nvmolkit.mmffOptimization.MMFFOptimizeMoleculesConfs call and MACE scoring goes through the
batched _mace_batch_energies path. Coordinates, RMSD, inertia, and basin memory are already
torch tensors. The ParallelMCMMDriver / ReplicaExchangeMCMMDriver orchestration is in place.

The one stage still running CPU-sequential per walker is the DBT concerted-rotation move
proposal
— specifically its ring-closure solve (scipy.optimize.least_squares, one walker at a
time) and the finite-difference Wu-Deem |det J| estimate. Now that MMFF is batched, this serial
proposal is the dominant per-step cost at large walker counts. This is a quality-of-life /
throughput improvement: it does not change what the sampler finds, only how fast it runs.

Proposed approach

Move the move-proposal stage onto the GPU as a batched torch batch_propose_fn, drop-in for the
interface ParallelMCMMDriver already expects
(batch_propose_fn(coords_list) -> [(new_coords, energy, det_j, success)]). No changes to the
driver, replica exchange, or basin memory.

  • Batched dihedral rotation. apply_dihedral_changes is pure rigid rotation — vectorise over
    walkers as [N, W, 3] with [N] batched Rodrigues matrices (the _kabsch_rmsd_pairwise batched
    pattern is the template). No solver.
  • Batched ring closure. Replace the per-walker least_squares with either a batched
    Gauss-Newton / Levenberg-Marquardt (all N walkers' 6-residual/3-unknown systems solved in
    lockstep via torch.linalg.solve, warm-started from the previous step) or the analytical
    Coutsias/Plucker closure (closed-form, trivially batched).
  • Batched Wu-Deem |det J| via torch.autograd.functional.jacobian / torch.func.vmap on the
    batched closure, instead of finite-difference re-solves.
  • Uniform control flow. Per-walker branching (move type, accept/reject, convergence) handled by
    compute-all-then-mask, keeping the whole MC step on-device (no per-step host<->device transfers).

Key questions / unknowns

  • Profile first: confirm the CPU proposal is now the per-step ceiling vs the already-batched MMFF,
    and quantify the achievable speedup as a function of N.
  • Does the batched closure reproduce the scipy path's closed geometries and |det J| to
    tolerance? Detailed balance depends on it — needs a parity regression test against the current
    proposer.
  • Numerical precision: closure Jacobians likely need float64 (as the existing Kabsch/inertia code
    already uses).
  • Convergence robustness of batched Gauss-Newton near the closure-manifold boundary (where the
    current code already returns success=False).

Relationship to other work

Other notes

Phased path: (1) profile to confirm the ceiling; (2) batched dihedral rotation; (3) batched
Gauss-Newton closure as a drop-in batch_propose_fn with scipy parity; (4) autograd |det J|;
(5) swap in the analytical #24 closure for the final speedup + shared code. Warrants its own branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions