Skip to main content

GPU-accelerated molecular toolkit on Apple Silicon: conformer generation (ETKDG + MMFF94) and Butina clustering via Metal/MLX

Project description

mlxmolkit — GPU-accelerated molecular toolkit on Apple Silicon

Port of nvMolKit (CUDA) to Apple Metal via MLX. Two pipelines:

  1. Molecular Clustering — Morgan FP → Tanimoto similarity → Butina clustering
  2. 3D Conformer Generation — DG (4D) → ETK (3D) → MMFF94 optimization

Installation

pip install mlxmolkit-rdkit

Requires macOS with Apple Silicon (M1/M2/M3/M4). RDKit is needed for molecular input:

conda install -c conda-forge rdkit
pip install mlxmolkit-rdkit

Quick Start

from mlxmolkit import generate_conformers_nk, butina_tanimoto_mlx

# 3D conformers: 100 molecules x 10 conformers each
result = generate_conformers_nk(
    smiles_list=["c1ccccc1", "CC(=O)O", "CC(=O)Oc1ccccc1C(=O)O"],
    n_confs_per_mol=10,
    run_mmff=True,
)

# Clustering: 150k+ molecules
import mlx.core as mx
result = butina_tanimoto_mlx(mx.array(fp_bytes), cutoff=0.4)

Features

  • Conformer Generation — Drop-in replacement for RDKit's ETKDG (EmbedMolecules). Supports ETKDG, ETKDGv2, ETKDGv3, srETKDGv3, KDG, ETDG, and pure DG.
  • MMFF94 Optimization — GPU-accelerated force field optimization (MMFFOptimizeMoleculesConfs). All 7 MMFF energy terms with fused Metal kernel. Full BFGS or L-BFGS in-kernel (zero CPU round-trips).
  • Molecular Clustering — Butina clustering at 150k+ molecules with divide-and-conquer memory management.
  • N x k Parallel — Generate k conformers for N molecules simultaneously. Constraints shared across conformers (conf_to_mol indirection, 50% memory savings).

Performance

Conformer Generation (1000 distinct SPICE molecules, Apple M3 Max)

Benchmark uses 1000 distinct drug-like molecules from SPICE-2.0.1 (see data/benchmark_1000_smiles.csv).

Scale Pipeline Time Throughput
N=100 x k=50 DG only 5.6s 900 conf/s
N=100 x k=50 DG + ETKDGv2 6.6s 761 conf/s
N=100 x k=50 DG + ETK + MMFF 6.6s 758 conf/s

Conformer Memory Scaling (DG + ETK + MMFF, batch=500)

Conformers Batch GPU (BFGS) GPU (L-BFGS) Time Throughput
1,000 1000 5.1 MB 2.9 MB 0.43s 2,342/s
2,000 500 2.6 MB 1.5 MB 1.43s 1,402/s
4,000 500 2.6 MB 1.5 MB 1.91s 2,094/s
10,000 500 2.6 MB 1.5 MB 4.82s 2,075/s

GPU memory stays constant regardless of total conformers thanks to divide-and-conquer batching.

Scale Tests (1000 distinct molecules, Apple M3 Max)

Scale Pipeline Time Throughput
N=1000, k=10 DG only 11.5s 870 conf/s
N=1000, k=10 DG + ETKDGv2 14.9s 672 conf/s
N=1000, k=10 DG + ETK + MMFF 15.0s 664 conf/s

All stages on Metal (including MMFF94 — zero RDKit post-processing).

Batch Size Impact

Larger batches = fewer kernel launches = higher throughput. Auto-sizing (default) picks the largest batch that fits in free memory.

GPU Memory per Conformer

Atoms DG (4D) ETK (3D) MMFF (BFGS) MMFF (L-BFGS)
5 1.9 KB 1.4 KB 1.3 KB 1.4 KB
12 4.4 KB 3.3 KB 6.0 KB 3.3 KB
21 7.6 KB 5.7 KB 17.2 KB 5.7 KB
30 10.8 KB 8.1 KB 34.1 KB 8.1 KB
50 18.0 KB 13.5 KB 92.0 KB 13.5 KB
64 23.1 KB 17.3 KB 149.2 KB 17.3 KB

MMFF BFGS memory grows as O(n^2) due to the dense Hessian (n_atoms x 3)^2. BFGS is faster than L-BFGS at all typical drug-like sizes (up to 74 atoms with H) because the better curvature information requires fewer iterations. L-BFGS is only needed for very large molecules (>150 atoms) where the Hessian exceeds ~1 MB per conformer.

Molecule Atoms (with H) BFGS L-BFGS Winner
Methane 5 0.255s 0.215s BFGS
Benzene 12 0.213s 0.222s ~tie
Aspirin 21 0.241s 0.230s ~tie
Testosterone 49 0.364s 0.335s BFGS
Cholesterol 74 0.590s 0.486s BFGS

Recommendation: use BFGS (default) for all molecules <150 atoms with H. The pipeline auto-switches to L-BFGS at 150+ atoms (mmff_use_lbfgs=None, the default).

Important: Always add explicit hydrogens (Chem.AddHs) before conformer generation. Convergence is significantly better with explicit H because the distance geometry constraints are more complete and the force field terms (bond/angle/torsion) are fully defined. The pipeline calls AddHs automatically.

With 64 GB unified memory, a single batch can hold:

Molecule size DG/ETK MMFF (BFGS) MMFF (L-BFGS)
12 atoms ~9.8M conformers ~7.4M ~9.8M
30 atoms ~3.9M conformers ~1.3M ~3.9M
64 atoms ~1.8M conformers ~300K ~1.8M

The divide-and-conquer queue automatically splits into multiple batches when total exceeds free memory.

Clustering (Enamine REAL subset, Apple M3 Max)

N Fused sim→CSR Butina Total vs RDKit Memory
20k 0.26s 0.09s 0.35s 152x 0.1 MB
50k 1.26s 0.36s 1.62s 0.5 MB
100k 4.87s 0.97s 5.84s 1.3 MB
150k+ blockwise scales bounded

ETKDG Variant Comparison (N=20, k=50, same molecule)

Throughput for homogeneous batches (all conformers of the same molecule, best case):

Variant conf/s Convergence
DG 7,549 96.6%
KDG 7,243 96.6%
ETDG 1,844 96.6%
ETKDG 6,064 96.6%
ETKDGv2 6,228 96.6%
ETKDGv3 6,636 96.6%
srETKDGv3 6,678 96.6%

Note: throughput is lower for diverse molecule batches due to variable atom counts and padding overhead. The Scale Tests section above shows realistic numbers for heterogeneous batches.

Architecture

Conformer Generation (N x k parallel)

SMILES x N
    |
[RDKit CPU] Extract params ONCE per molecule
    |
[Pack] SharedConstraintBatch (conf_to_mol indirection)
    |
+-- Stage 1: DG minimize (4D, Metal TPM=32) --------+
|   One threadgroup per conformer                    |
|   L-BFGS in-kernel, GPU-parallel line search       |
|   Shared constraints via conf_to_mol               |
+----------------------------------------------------+
    |
[Extract 3D] Drop 4th coordinate
    |
+-- Stage 2: ETK minimize (3D, Metal TPM=32) --------+
|   CSD torsion + improper + 1-4 distance             |
|   Optional parallel_grad for large molecules        |
+-----------------------------------------------------+
    |
+-- Stage 3: MMFF94 optimize (Metal, in-kernel) ------+
|   7 energy terms: bond, angle, stretch-bend,         |
|   OOP, torsion, vdW, electrostatic                   |
|   BFGS (default) or L-BFGS option                    |
+------------------------------------------------------+
    |
Optimized 3D conformers

Clustering (divide-and-conquer for 150k+)

Morgan FP (RDKit CPU)
        |
   uint8 -> uint32 packing
        |
+-- N <= 100k: Fused Metal Kernel ------+
|   Single dispatch, no NxN matrix       |
+-- N > 100k: Blockwise D&C ------------+
|   Tile both dimensions (auto-sized)    |
|   mx.eval() between tiles (free GPU)  |
+----------------------------------------+
        |
   Butina greedy (CPU, numpy CSR)
        |
   Clusters

Adaptive Iteration Scaling

Iterations auto-scale by molecule complexity (default). Small molecules converge early via in-kernel TOLX/gradient checks — no wasted GPU compute.

Formula: max_iters = base + scale * max(n_atoms, sqrt(n_constraints))

Molecule Atoms Constraints DG iters ETK iters MMFF iters
Methane 5 10 400 200 275
Benzene 12 66 540 270 380
Aspirin 21 210 720 360 515
Testosterone 49 1176 1280 640 935
64-atom 64 2016 1580 790 1160

Override with explicit values when needed:

# Auto (default) — scales with molecule size
result = generate_conformers_nk(smiles_list, n_confs_per_mol=10)

# Fixed iterations for fine control
result = generate_conformers_nk(smiles_list, n_confs_per_mol=10,
    dg_max_iters=1000, etk_max_iters=500, mmff_max_iters=400)

Optimization Options

Option Flag Effect When to use
Auto iterations (default) dg_max_iters=0 Scales with molecule size Always (default)
Warm-start retry automatic Re-runs non-converged with 2x iters Always (automatic)
ETK parallel gradient parallel_grad=True 1.18x ETK speedup Many distance constraints
DG parallel gradient parallel_grad=True Parallelizes dist gradient >500 distance constraints
MMFF94s variant mmff_variant="MMFF94s" Softer torsion barriers Conjugated/aromatic molecules
MMFF L-BFGS mmff_use_lbfgs=True 5x less memory Molecules >50 atoms
MMFF BFGS (default) mmff_use_lbfgs=False 2x faster for small mols Molecules <50 atoms
ETKDG variant variant="ETKDGv3" 7 variants supported Choose per use case

MMFF94 Force Field Variants

Variant Flag Torsion barriers Best for
MMFF94 (default) mmff_variant="MMFF94" Standard General molecules
MMFF94s mmff_variant="MMFF94s" Softer for conjugated systems Aromatic, planar, conjugated

Usage

3D Conformer Generation

from mlxmolkit.conformer_pipeline_v2 import generate_conformers_nk

# Basic: 10 conformers per molecule, ETKDGv2
result = generate_conformers_nk(
    smiles_list=["c1ccccc1", "CC(=O)O", "CC(=O)Oc1ccccc1C(=O)O"],
    n_confs_per_mol=10,
)
for mol in result.molecules:
    print(f"{mol.n_atoms} atoms, {len(mol.positions_3d)} conformers")

# Full pipeline with MMFF94
result = generate_conformers_nk(
    smiles_list=["c1ccccc1", "CC(=O)O"],
    n_confs_per_mol=50,
    variant="ETKDGv3",
    run_mmff=True,
    mmff_use_lbfgs=False,       # BFGS (default, fast for <50 atoms)
    max_confs_per_batch=500,    # divide-and-conquer batch size
)

# Pure distance geometry (no torsion refinement)
result = generate_conformers_nk(
    smiles_list=["c1ccccc1"],
    n_confs_per_mol=100,
    variant="DG",
)

# Large molecules: use L-BFGS for MMFF
result = generate_conformers_nk(
    smiles_list=[large_smiles],
    n_confs_per_mol=20,
    run_mmff=True,
    mmff_use_lbfgs=True,        # L-BFGS for >50 atoms
)

Example Script

# Basic: 20 molecules x 10 conformers
python examples/conf3d_example.py

# Scale test: 1000 molecules x 10 conformers with MMFF
python examples/conf3d_example.py --n-mols 1000 --n-confs 10 --mmff

# All options
python examples/conf3d_example.py --n-mols 100 --n-confs 20 --variant ETKDGv3 \
    --mmff --mmff-variant MMFF94s --batch-size 200

# Custom SMILES
python examples/conf3d_example.py --smiles "c1ccccc1" "CC(=O)O" --n-confs 50 --mmff

Molecular Clustering

from mlxmolkit import butina_tanimoto_mlx
import mlx.core as mx

# Automatic: fused kernel for N<=100k, blockwise for N>100k
result = butina_tanimoto_mlx(mx.array(fp_bytes), cutoff=0.4)
print(f"{len(result.clusters)} clusters")

Low-level API

from mlxmolkit import (
    fp_uint8_to_uint32,
    fused_neighbor_list_metal,
    tanimoto_neighbors_blockwise,
    butina_from_neighbor_list_csr,
)

fp_u32 = fp_uint8_to_uint32(mx.array(fp_bytes))

# Small N: fused single-dispatch
offsets, indices = fused_neighbor_list_metal(fp_u32, cutoff=0.4)

# Large N (150k+): divide-and-conquer blockwise
offsets, indices = tanimoto_neighbors_blockwise(fp_u32, cutoff=0.4)

result = butina_from_neighbor_list_csr(offsets, indices, N, cutoff=0.4)

Tests

pip install -e .
pytest tests/ -v

Conformer Quality vs RDKit

mlxmolkit conformers rescored by RDKit's MMFF94 for fair comparison (k=20, ETKDGv2):

After MMFF optimization, mlxmolkit conformers converge to the same energy basins as RDKit:

Molecule Atoms RMSD (pre-MMFF) RMSD (post-MMFF) E gap
Benzene 12 0.12 A 0.00 A 0.0
Aspirin 21 0.98 A 0.00 A 0.0
Ibuprofen 33 1.79 A 0.96 A 0.6
Acetaminophen 20 0.99 A 0.00 A 0.0

Full nvMolKit pipeline: DG (4D) → 4D→3D collapse → setReferenceValues → stereo checks → ETK (3D) → MMFF94. Bond/angle geometry matches RDKit within 0.03 A. After MMFF, energy gap is <1 kcal/mol.

References

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mlxmolkit_rdkit-0.5.0.tar.gz (86.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mlxmolkit_rdkit-0.5.0-py3-none-any.whl (89.6 kB view details)

Uploaded Python 3

File details

Details for the file mlxmolkit_rdkit-0.5.0.tar.gz.

File metadata

  • Download URL: mlxmolkit_rdkit-0.5.0.tar.gz
  • Upload date:
  • Size: 86.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mlxmolkit_rdkit-0.5.0.tar.gz
Algorithm Hash digest
SHA256 4b3e63a95607cc1e5803167576acfb4e7a93aa54af1f6fccdaae0344244f0074
MD5 5a0529858fef3aebb9a087ba51e14ddd
BLAKE2b-256 ae19cb96ae64e4885177e277e276f3c80d0ecac965cbb95b7afe24b154e0decb

See more details on using hashes here.

File details

Details for the file mlxmolkit_rdkit-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: mlxmolkit_rdkit-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 89.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mlxmolkit_rdkit-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 505529e0b2cda26d365538fb2f1eceb566c8224edbb8fffdb9c357d757c97eec
MD5 a24296617747809cb558bf386c431305
BLAKE2b-256 bb140fe7ae9b7e65ee2332e38ff11d21acbf76aab2cd48d9f58783bc597af464

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page