From c9c7a2233045f78e88e9b59e69639fe371c123c3 Mon Sep 17 00:00:00 2001 From: Greg Zynda Date: Tue, 16 Jun 2026 00:56:53 -0700 Subject: [PATCH 1/3] Initial rough draft of documentation for using cuequivariance and fold-cp --- cookbook/the_bionemo_contessa/README.md | 74 ++++++++++++++++ .../the_bionemo_contessa/cuequivariance.py | 49 +++++++++++ .../the_bionemo_contessa/cuequivariance_cp.py | 85 +++++++++++++++++++ cookbook/the_bionemo_contessa/will_fail.py | 46 ++++++++++ 4 files changed, 254 insertions(+) create mode 100644 cookbook/the_bionemo_contessa/README.md create mode 100644 cookbook/the_bionemo_contessa/cuequivariance.py create mode 100644 cookbook/the_bionemo_contessa/cuequivariance_cp.py create mode 100644 cookbook/the_bionemo_contessa/will_fail.py diff --git a/cookbook/the_bionemo_contessa/README.md b/cookbook/the_bionemo_contessa/README.md new file mode 100644 index 00000000..bc826910 --- /dev/null +++ b/cookbook/the_bionemo_contessa/README.md @@ -0,0 +1,74 @@ +# The BioNeMo Contessa — ESMFold2 folding examples + +Three scripts that fold protein/ligand complexes with `ESMFold2Model`, showing +when a single GPU suffices and when **context parallelism (CP)** is needed to fit +a large complex. All use the `cuequivariance` backend, `bf16` ESM-C, +`torch.inference_mode()`, and the same fold settings (`num_loops=10`, +`num_sampling_steps=50`, `num_diffusion_samples=1`, `seed=0`). + +Tested on 1× and 4× H100 80GB GPUs. + +| Script | Input | GPUs | Result | +| --- | --- | --- | --- | +| `cuequivariance.py` | 7ysz (1 protein chain ×2, GDP + TRS ligands) | 1 | Folds successfully | +| `cuequivariance_cp.py` | 5xgo (1 protein chain ×12, CL ligand) | n² (e.g. 4) | Folds successfully via CP | +| `will_fail.py` | 5xgo (same as CP) | 1 | **OOMs** — too large for one GPU | + +## Environment setup + +Start from the NVIDIA PyTorch container, then install the dependencies (the +active commands in [`install.sh`](../../../install.sh)): + +```bash +docker run --gpus all -it --rm nvcr.io/nvidia/pytorch:26.03-py3 + +pip install git+https://github.com/Biohub/esm.git@main +pip install cuequivariance-torch cuequivariance-ops-torch-cu13 +pip uninstall -y transformers +pip install git+https://github.com/zyndagj/transformers.git@zyndagj/foldcp_support +``` + +The `zyndagj/foldcp_support` transformers branch ships `ESMFold2Model` and its CP +utilities. (Commented-out lines in `install.sh` show an alternative `uv` +virtualenv setup.) + +## `cuequivariance.py` — single-GPU baseline + +Folds a small complex (7ysz) on one GPU and writes `esmfold2_output.cif`. + +```bash +python cuequivariance.py +``` + +## `cuequivariance_cp.py` — context-parallel for large complexes + +Folds a large 12-chain complex (5xgo) by sharding across an **n×n CP grid** of +GPUs. Both the trunk *and* the MSA encoder are sharded via `wrap_model_with_cp` +(trunk-only sharding still OOMs on the full L×L pair). Launch with `torchrun` on +a **perfect-square** number of GPUs (1, 4, 9, …); output goes to +`esmfold2_cp_output.cif`. + +```bash +PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True torchrun --nproc-per-node=4 cuequivariance_cp.py +``` + +> **CP is a capability enabler, not a speedup.** Splitting the L×L pair across +> GPUs lets you fold complexes too large for one GPU, but the per-layer cross-rank +> communication means it won't fold faster than a single GPU that already fits the +> complex. Reach for CP on out-of-memory, not for performance. + +## `will_fail.py` — what happens without CP + +The single-GPU script run on the large 5xgo complex (same input as the CP +script). The 12-chain L×L pair representation exceeds one GPU's memory, so it +OOMs — the fix is the CP sharding in `cuequivariance_cp.py`. + +```bash +python will_fail.py # expected to OOM +``` + +## Outputs + +Successful runs write an mmCIF file (`esmfold2_output.cif` / +`esmfold2_cp_output.cif`) and print `pLDDT mean`, `pTM`, `ipTM`, elapsed time, +and peak VRAM. diff --git a/cookbook/the_bionemo_contessa/cuequivariance.py b/cookbook/the_bionemo_contessa/cuequivariance.py new file mode 100644 index 00000000..08a78b7c --- /dev/null +++ b/cookbook/the_bionemo_contessa/cuequivariance.py @@ -0,0 +1,49 @@ +import gc +from time import time + +import torch +from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model +from esm.models.esmfold2 import ( + ESMFold2InputBuilder, + LigandInput, + ProteinInput, + StructurePredictionInput, +) + +spi = StructurePredictionInput( # 7ysz + sequences=[ + ProteinInput( + id=["A1", "B1"], + sequence=( + "MDNFDNYEQVASIKVIGIGGAGNNAVNRMIEAGVQGVEFIVANTDAQIISVSKSKNKIVLGKETSKGLGA" + "GANPDVGRQAAIESAEEIKDALKGADMVFVAAGMGGGTGTGAAPIIAKLAREQGALTVGIITTPFSFEGR" + "ARNSYAIQGTEELRKHVDSLIIISNDRLLEVIGGVPLKDSFKEADNILRQGVQTITDLIAVPSLINLDFA" + "DIKTVMKNKGNALFGIGIGSGKDKAIEAANKAIISPLLEASIRGARDAIINVTGGNTLTLNDANDAVDIV" + "KQAIGGEVNIIFGTAVNEHLDDEMIVTVIATGFDGSHHHHHH" + ), + ), + LigandInput(id=["C1", "E1"], ccd=["GDP"]), + LigandInput(id="D1", ccd=["TRS"]), + ] +) + +torch.cuda.reset_peak_memory_stats() + +model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision='bf16').cuda().eval() +model.set_kernel_backend("cuequivariance") + +start = time() +with torch.inference_mode(): + result = ESMFold2InputBuilder().fold( + model, spi, num_loops=10, num_sampling_steps=50, + num_diffusion_samples=1, seed=0, + ) +end = time() +peak_mib = torch.cuda.max_memory_allocated() / (1024**2) + +print(f"pLDDT mean: {float(result.plddt.mean()):.3f}, " + f"pTM: {float(result.ptm):.3f}, ipTM: {float(result.iptm):.3f}") +print(f"Elapsed: {end - start} sec") +print(f"Max VRAM: {peak_mib} MB") +with open("esmfold2_output.cif", "w") as f: + f.write(result.complex.to_mmcif()) diff --git a/cookbook/the_bionemo_contessa/cuequivariance_cp.py b/cookbook/the_bionemo_contessa/cuequivariance_cp.py new file mode 100644 index 00000000..f9970bec --- /dev/null +++ b/cookbook/the_bionemo_contessa/cuequivariance_cp.py @@ -0,0 +1,85 @@ +import os, math, gc +from collections import OrderedDict +from time import time + +import torch +from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model +from transformers.models.esmfold2.distributed import ( + DistributedManager, + wrap_model_with_cp, +) +from esm.models.esmfold2 import ( + ESMFold2InputBuilder, + LigandInput, + ProteinInput, + StructurePredictionInput, +) + +spi = StructurePredictionInput( # 5xgo + sequences=[ + ProteinInput( + id=["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1", "J1", "K1", "L1"], + sequence=( + "MAHHHHHHVDDDDKMSTAKLVKSKATNLLYTRNDVSDSEKKATVELLNRQVIQFIDLSLITKQAHWNMRG" + "ANFIAVHEMLDGFRTALIDHLDTMAERAVQLGGVALGTTQVINSKTPLKSYPLDIHNVQDHLKELADRYA" + "IVANDVRKAIGEAKDDDTADILTAASRDLDKFLWFIECNLDLIQKMGLQNYLQAQIREEG" + ), + ), + LigandInput(id=["M1", "N1"], ccd=["CL"]), + ] +) + +# torchrun entrypoint: torchrun --nproc_per_node=4 cuequivariance_cp.py +# torchrun sets LOCAL_RANK / WORLD_SIZE / RANK / MASTER_ADDR / MASTER_PORT. +# world_size must be a perfect square (the CP grid is n×n). +local_rank = int(os.environ["LOCAL_RANK"]) +world_size = int(os.environ["WORLD_SIZE"]) + +os.environ["RANK"] = str(local_rank) +torch.cuda.set_device(local_rank) +torch.cuda.reset_peak_memory_stats() + +n = math.isqrt(world_size) +DistributedManager.initialize( + grid_group_sizes=OrderedDict([("dp", 1), ("cp", (n, n))]), + device_type="cuda", + backend="nccl", +) +dm = DistributedManager() + +model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision='bf16').cuda().eval() +# Shard BOTH the folding trunk and the MSA encoder across the n×n CP grid. +# (Trunk-only sharding leaves the MSA encoder running the full L×L pair on +# every rank, which OOMs for large complexes.) +# +# Defaults applied here (override via args): bf16=True (bf16 distributed trunk) +# and offload_esmc=True (move the ~12 GB ESM-C LM to CPU after its one-shot use +# so the trunk reuses that memory). Together: ~2.14x faster, ~18.5 GB lower +# peak vs fp32, quality-neutral. comm="gather" (bit-exact) | "ring" (n>=3). +wrap_model_with_cp(model, dm, comm="ring") +# Fused kernels accelerate the modules *outside* the CP region (notably the +# diffusion sampler / structure head). The CP-wrapped trunk and MSA encoder +# no-op this call, so it never touches the distributed ring math. +model.set_kernel_backend("cuequivariance") + +start = time() +with torch.inference_mode(): + result = ESMFold2InputBuilder().fold( + model, spi, num_loops=10, num_sampling_steps=50, + num_diffusion_samples=1, seed=0, + ) +end = time() +peak_mib = torch.cuda.max_memory_allocated() / (1024**2) + +if local_rank == 0: + print(f"pLDDT mean: {float(result.plddt.mean()):.3f}, " + f"pTM: {float(result.ptm):.3f}, ipTM: {float(result.iptm):.3f}") + print(f"Elapsed: {end - start} sec") + print(f"Max VRAM: {peak_mib} MB") + with open("esmfold2_cp_output.cif", "w") as f: + f.write(result.complex.to_mmcif()) + +DistributedManager.cleanup() +DistributedManager._state.clear() +gc.collect() +torch.cuda.empty_cache() diff --git a/cookbook/the_bionemo_contessa/will_fail.py b/cookbook/the_bionemo_contessa/will_fail.py new file mode 100644 index 00000000..cd986a9c --- /dev/null +++ b/cookbook/the_bionemo_contessa/will_fail.py @@ -0,0 +1,46 @@ +import gc +from time import time + +import torch +from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model +from esm.models.esmfold2 import ( + ESMFold2InputBuilder, + LigandInput, + ProteinInput, + StructurePredictionInput, +) + +spi = StructurePredictionInput( # 5xgo + sequences=[ + ProteinInput( + id=["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1", "J1", "K1", "L1"], + sequence=( + "MAHHHHHHVDDDDKMSTAKLVKSKATNLLYTRNDVSDSEKKATVELLNRQVIQFIDLSLITKQAHWNMRG" + "ANFIAVHEMLDGFRTALIDHLDTMAERAVQLGGVALGTTQVINSKTPLKSYPLDIHNVQDHLKELADRYA" + "IVANDVRKAIGEAKDDDTADILTAASRDLDKFLWFIECNLDLIQKMGLQNYLQAQIREEG" + ), + ), + LigandInput(id=["M1", "N1"], ccd=["CL"]), + ] +) + +torch.cuda.reset_peak_memory_stats() + +model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision='bf16').cuda().eval() +model.set_kernel_backend("cuequivariance") + +start = time() +with torch.inference_mode(): + result = ESMFold2InputBuilder().fold( + model, spi, num_loops=10, num_sampling_steps=50, + num_diffusion_samples=1, seed=0, + ) +end = time() +peak_mib = torch.cuda.max_memory_allocated() / (1024**2) + +print(f"pLDDT mean: {float(result.plddt.mean()):.3f}, " + f"pTM: {float(result.ptm):.3f}, ipTM: {float(result.iptm):.3f}") +print(f"Elapsed: {end - start} sec") +print(f"Max VRAM: {peak_mib} MB") +with open("esmfold2_output.cif", "w") as f: + f.write(result.complex.to_mmcif()) From da68f5da4493ed73dd1f9c54139054b09fbf7451 Mon Sep 17 00:00:00 2001 From: Greg Zynda Date: Wed, 15 Jul 2026 10:00:28 -0700 Subject: [PATCH 2/3] Added install targets for each backend --- cookbook/the_bionemo_contessa/fold-cp.md | 244 ++++++++++++++++++++ cookbook/the_bionemo_contessa/single-gpu.md | 143 ++++++++++++ pyproject.toml | 11 + 3 files changed, 398 insertions(+) create mode 100644 cookbook/the_bionemo_contessa/fold-cp.md create mode 100644 cookbook/the_bionemo_contessa/single-gpu.md diff --git a/cookbook/the_bionemo_contessa/fold-cp.md b/cookbook/the_bionemo_contessa/fold-cp.md new file mode 100644 index 00000000..3da491a3 --- /dev/null +++ b/cookbook/the_bionemo_contessa/fold-cp.md @@ -0,0 +1,244 @@ +# `wrap_model_with_cp` — running ESMFold2 across several GPUs + +## TL;DR + +`wrap_model_with_cp(model, dm, …)` takes a normal `ESMFold2Model` and rewires it, +in place, to **spread one fold across several GPUs** so you can fold **longer +proteins** than fit on a single card. It does not make a new kind of model — it's +the same object, with a few internal pieces swapped for versions that split their +work across the GPUs. Don't wrap it, and it runs exactly as before on one GPU. + +If you just want to use it, skip to [Minimal usage](#minimal-usage). The rest of +this doc explains *why* it's built the way it is. + +## Why this exists: the "every pair of residues" table + +A protein is a chain of building blocks called **residues** (`L` = how many). To +predict its shape, ESMFold2 keeps a big table with **one cell for every pair of +residues** — cell `(i, j)` is what the model believes about how residue `i` relates +to residue `j`. This is the **pair representation** (or just `z`), and it's what +dominates memory, because `L` residues means `L × L` cells: + +- 500 residues → 250,000 cells +- 2,000 residues → 4,000,000 cells (16× bigger) + +Each cell holds ~256 numbers, and the model builds and refines several of these +tables. On one GPU the `L × L` table fills memory first — that's why a single card +caps out at a certain length. (The limiter is **per-GPU peak memory**: the most any +one GPU needs at once.) + +**Context parallelism (CP)** is the fix: instead of every GPU holding the whole +table, **cut it into blocks, one per GPU** — 4 GPUs hold a quarter each, 16 a +sixteenth. More GPUs → each holds less → longer proteins fit. Cutting a table into +per-GPU blocks is called **sharding** (the opposite, a full copy on every GPU, is +**replicated**), and it's the one idea this whole doc is about. + +## How CP splits the work: a grid of GPUs + +CP arranges the GPUs into a **square grid** (so the GPU count must be a perfect +square: 1, 4, 9, 16, …), set up by a helper called `DistributedManager`. Each GPU +process is a **rank**, and the total count is the **world size**. Picture the +`L × L` table as a checkerboard: **the GPU at grid position `(r, c)` owns the block +of rows `r` and columns `c`.** PyTorch tracks this with a **`DTensor`** — an ordinary +tensor that also knows it's split across GPUs and which block lives where. + +Two things stay small and simple: + +- **Model weights are replicated** — every GPU keeps a full copy of the network's + parameters (small compared to the `L × L` activations). +- Only the big `L × L` **activations get sharded**, so each GPU holds `L² / P` of + them (`P` = number of GPUs). That's the whole point: per-GPU memory *drops* as you + add GPUs, instead of every GPU carrying the full table. + +## The design pattern: swap pieces in, keep the same model + +The base model file (`modeling_esmfold2.py`) has **no idea CP exists** — it imports +nothing from the distributed code. Instead its `forward` has a few **optional +checks**: `getattr(self, "_cp_*", None)` lookups that ask "has a distributed helper +been plugged in here?" + +```python +_cp_pair_init = getattr(self, "_cp_pair_init", None) # sharded pair init (z_init, rel_pos, token_bonds, mask) +_cp_engine = getattr(self, "_cp_recycle_engine", None) # recycle loop +_cp_lm = getattr(self, "_cp_language_model", None) # language-model pair builder +_cp_conf = getattr(self, "_cp_confidence_head", None)# confidence head +_cp_disto = getattr(self, "_cp_distogram_head", None) # distogram head +``` + +- **Attribute absent (plain model):** the check returns `None` and the forward runs + the original code — bit-for-bit the stock model. +- **Attribute present (after wrapping):** the forward hands that step to the + distributed version. + +So `wrap_model_with_cp` parallelizes the fold in two ways: + +1. **Swaps a submodule** for a same-shaped version that runs across the grid: + - the **MSA encoder** + - every **`FoldingTrunk`** in the model (the recycle trunk, the LM encoder, + `parcae_coda`, and the confidence head's inner trunk) + - the **structure head**'s diffusion module (only if `wrap_structure=True`) + - ESM-C's **MLP** (only if `tp_esmc=True`) +2. **Plugs in a helper** — one of the `_cp_*` attributes the checks above look for: + - **`_cp_pair_init`** — the sharded pair init (`z_init`, rel_pos, token_bonds, the + starting pair table, and the pair mask) + - **`_cp_recycle_engine`** — the recycle loop + - **`_cp_language_model`** — the language-model pair builder (`lm_z`, the `L×L` + pair table built from ESM-C's per-residue embeddings) + - **`_cp_distogram_head`** — the distogram head + - **`_cp_confidence_head`** — the confidence head + +Because it edits the same object, `type(model)` is still `ESMFold2Model`, and +`from_pretrained` / `fold()` / the output dictionary all behave exactly as before. +You can even switch one piece back off at runtime +(`model._cp_confidence_head = None`), which makes it easy to turn a single piece off +and compare. + +## The fold, end to end (in plain terms) + +Before the stage table, here's the whole pipeline in one pass, naming each part: + +1. **`inputs_embedder`** turns the raw atoms into per-residue features. +2. **ESM-C** — a large protein language model (6 billion parameters) — reads the + sequence and produces rich embeddings. +3. Those feed the first `L × L` **pair table** (the "pair init"), optionally combined + with an **MSA** (a stack of evolutionarily-related sequences that gives extra + hints — skipped if you don't have one). +4. The **recycle loop** refines that table over a few passes, each pass running the + main refining network (the **trunk**, a "Pairformer") and feeding the result back + in. +5. The refined table becomes a **distogram** (the model's predicted histogram of + residue-to-residue distances) and then goes to the **structure head**, which uses + **diffusion** to turn it into actual 3D coordinates. +6. Finally the **confidence head** predicts how trustworthy the result is (the + pLDDT / pTM / ipTM scores you get back). + +## Stage-by-stage: what runs where + +The base model runs every stage at full length `L` on every GPU, with the pair table +full-size. The table below walks that same pipeline; **a `*` means +`wrap_model_with_cp` splits that stage across the grid** (everything else stays +replicated). + +| Stage (base ESMFold2Model.forward) | Split across GPUs? | What wrapping does | +|---|---|---| +| `inputs_embedder` (atom features) | — (replicated) | unchanged; its cost grows only **linearly** with size (it looks at a small window of atoms at a time), so it stays a small, fixed floor | +| ESM-C 6B language model → embeddings | `*` MLP only, if `tp_esmc=True` | splits ESM-C's big MLP across GPUs; the rest stays replicated; ESM-C is moved to CPU right after it's used, to free room | +| `language_model` → `lm_z` (`L×L`) | `*` | `_cp_language_model` builds this `L×L` table already sharded (the full thing is never assembled on any GPU) | +| pair init (`z_init`, rel_pos, token_bonds, initial `z`, pair mask) | `*` | `_cp_pair_init` builds each `L×L` table one block per GPU — no full copy anywhere. (This *used* to be built full then cut up, which is what ran short proteins out of memory.) | +| recycle loop (`_run_one_loop`) | `*` | `_cp_recycle_engine` keeps the pair table sharded the whole time — it never gathers the full table between passes | +| `parcae_readout` + `parcae_coda` | `*` | runs on each GPU's block plus a shared trunk | +| `distogram_head(z + zᵀ)` | `*` | works on the sharded table and gathers only the small result | +| `structure_head.sample` (diffusion → 3D coords) | `*` (if `wrap_structure`) | runs the diffusion with the pair table kept sharded | +| `confidence_head` (pLDDT/PAE/PDE/pTM/ipTM scores) | `*` | every `L×L` input (pair, rel_pos, token_bonds, distance bins, mask) stays sharded — nothing full-size is rebuilt; only the small final scores are gathered | + +**Net effect:** after wrapping, the pair table stays cut-into-blocks from start to +finish (recycle → parcae → distogram → structure → confidence). Only small, +per-residue results and the final outputs are ever reassembled. + +## Inside the recycle loop + +"Recycling" just means: run the refining network a few times, each pass taking the +previous pass's table as a starting point. The loop is +`for _ in range(total_steps)` with `total_steps = num_loops + 1` (so `num_loops=3` +→ 4 passes). A leading **`*`** marks steps split across the grid. + +- **Before the loop (done once, reused by every pass)** + - `inputs_embedder` → per-residue features *(replicated — grows only linearly with size, so it's a small fixed floor, not a wall)* + - **\*** ESM-C 6B → embeddings (then moved to CPU) — **MLP split only if `tp_esmc=True`** + - **\*** `language_model` → `lm_z` (the `L×L` language-model table) *(built already sharded)* + - **\*** `z_init`, rel_pos, token_bonds, the starting pair table, and the pair mask *(each built one block per GPU by `_cp_pair_init` — never full-size anywhere)* + - `a`, `b_mat`, MSA column mask *(tiny, replicated)* +- **The loop itself** + - **\*** the one thing carried from pass to pass is **`z`** (the pair table being refined) *(stays sharded the whole time)* + - each pass does, in order: + 1. **\*** **LM dropout** — randomly drop part of the language-model table, a fresh pattern each pass *(drawn per-block)* + 2. **\*** **LM encoder** — refine that table + 3. reset the injection base — `z_inject = z_init` *(just reusing the sharded starting table; no real compute)* + 4. **\*** **MSA encoder** — re-sample the MSA and fold it in (skipped entirely if you have no MSA) + 5. **\*** add the LM output into `z_inject` *(elementwise, on each GPU's block)* + 6. **\*** **parcae step** — blend the previous table with the new one *(elementwise, on each block)* + 7. **\*** **trunk (Pairformer)** — the main refining network + - the three heavy steps per pass: **\*** LM encoder, **\*** MSA encoder, **\*** trunk +- **After the loop (once)** + - **\*** `parcae_readout` → **\*** `parcae_coda` → **\*** `distogram_head` → **\*** `structure_head.sample` (→ 3D coords, if `wrap_structure`) → **\*** `confidence_head` +- **Redrawn each pass vs fixed:** + - redrawn: **\*** `z`, **\*** the LM dropout pattern, the MSA re-sample, **\*** the three heavy steps + - fixed (built once, reused): `lm_z`, `z_init`, `pair_mask` (all sharded), `a`, `b_mat`, MSA data + column mask +- **How the `*` steps actually run across GPUs (`CPRecycleEngine.run_loop`):** + - keeps `z` sharded across all passes + - calls the sharded versions of the LM encoder / MSA encoder / trunk (no full-table gather between passes) + - the blend + dropout run on each GPU's local block + +**Stays replicated on every GPU (not split):** `inputs_embedder` (but it grows only +linearly, so it never dominates at large `L`), the bulk of the ESM-C forward (unless +`tp_esmc=True`), and the tiny per-residue/scalar bits (`a`, `b_mat`, the per-residue +masks, MSA sampling prep). Note the `L×L` **pair** mask is *not* replicated — it's +built one block per GPU, like the rest of the pair init. + +## Memory: what shrinks, what doesn't + +- **Grows with `L²` (the `L×L` pair table) → now sharded:** pair init, recycle loop, + parcae, distogram, structure, confidence. Each is built one block per GPU and never + assembled full, so **adding GPUs raises the longest protein you can fold**. (The + pair init was the last holdout — it used to build these tables full on every GPU + and only cut them up later, so short proteins could still run out of memory during + setup. Now they start sharded.) +- **Grows only with `L` (atom-level work):** `inputs_embedder` — a slow-growing, + replicated floor. Splitting it would shave the baseline but wouldn't change the + ceiling, so it's left alone. +- **Roughly fixed:** ESM-C's weights (split by `tp_esmc`); ESM-C's own activations are + the remaining floor (a different technique would be needed to shrink those, not yet + built). + +## One hardware caveat: you need a fast GPU interconnect + +Splitting the table trades memory for **communication**: because each GPU holds only +part of it, the GPUs constantly shuffle blocks back and forth — many times per fold — +and they wait while that traffic moves. So CP is only fast with a fast link between +GPUs: **NVLink** (NVIDIA's direct GPU-to-GPU link) within a machine, and +**InfiniBand** (a high-speed network fabric) between machines. + +Without them — GPUs on plain PCIe, or nodes on ordinary Ethernet — communication +dominates: you still get the memory savings (longer proteins fit), but each fold can +be much slower. Treat NVLink-within-a-node and InfiniBand-between-nodes as the +intended setup, not an optimization. + +## Comparison to the single-GPU implementation + +The API and outputs are identical (`from_pretrained`, `fold()`, output keys, +`type(model)`), and almost every step matches the single-GPU result — exactly, or +within tiny bf16 rounding — checked by folding real proteins and comparing. Two +differences are expected, both small: + +- **LM dropout** picks a different (still valid) random pattern than the single-GPU + run, because the sharded table can't cheaply reproduce the exact full-table draw. + This is the main reason the pTM/ipTM scores can wiggle slightly. +- **Rounding** differs at about 1e-3, because numbers are summed across GPUs in a + different order. + +See `fold-cp_caveats.md` for how to tell these harmless differences from a real bug. + +## Requirements the wrapped model adds + +- `torch.distributed` set up via `DistributedManager` with a **square** number of GPUs + (1, 4, 9, 16 …). +- `num_diffusion_samples == 1` (the distributed diffusion path). +- `comm="gather"` (exact) or `comm="ring"` (cheaper at scale) for the MSA encoder; + `tp_esmc=True` needs ESM-C's `ffn_hidden=6912` to divide evenly by the number of + GPUs (4/9/16 all work). + +## Minimal usage + +```python +from transformers.models.esmfold2.distributed import DistributedManager, wrap_model_with_cp +from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model + +# number of GPUs must be a perfect square; launch with torchrun --nproc-per-node=

+DistributedManager.initialize(OrderedDict([("dp", 1), ("cp", (n, n))]), + device_type="cuda", backend="nccl") +dm = DistributedManager() +model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision="bf16").cuda().eval() + +wrap_model_with_cp(model, dm, comm="ring", tp_esmc=True) # rewires model in place +# still an ESMFold2Model; fold() / outputs unchanged, now spread across the GPUs. +``` diff --git a/cookbook/the_bionemo_contessa/single-gpu.md b/cookbook/the_bionemo_contessa/single-gpu.md new file mode 100644 index 00000000..37a71e2f --- /dev/null +++ b/cookbook/the_bionemo_contessa/single-gpu.md @@ -0,0 +1,143 @@ +# Single-GPU ESMFold2 — kernel backends + +ESMFold2 runs the expensive Pairformer/attention math through a selectable +**kernel backend**, chosen with: + +```python +model.set_kernel_backend(None | "fused" | "cuequivariance") +``` + +This fans out to every module that runs Pairformer-style blocks (`folding_trunk`, +`lm_encoder`, `parcae_coda`, `confidence_head`, `structure_head`). It only swaps the +*implementation* of a few hot ops — the **triangle multiplicative update** (the +dominant `L×L` Pairformer op), the transition FFN (LayerNorm+SwiGLU), the +dropout-residual, and the attention pair-bias. Weights are untouched and the three +backends are numerically equivalent to bf16 rounding (same pLDDT/pTM). They trade +**speed, memory, first-call compilation, and dependencies** — not accuracy. + +> This page covers the single-GPU backends. For multi-GPU context parallelism see +> `fold-cp.md` (`wrap_model_with_cp`), which is an orthogonal choice. + +## The three backends + +| | `None` | `"fused"` | `"cuequivariance"` | +|---|---|---|---| +| Implementation | pure PyTorch | vendored **Triton** kernels | **cuEquivariance** kernel | +| Ops accelerated | none (reference) | tri-mul **+ LN+SwiGLU + dropout-residual + pair-bias** | **tri-mul only** (rest = reference) | +| Extra dependency | none | `triton>=3` | `cuequivariance-torch` (CUDA-matched build) | +| First-call compilation | none | **yes — Triton JIT + autotune, per shape** | **no — ships precompiled kernels** | +| Steady-state speed | slowest (reference) | **fastest** | fast (tri-mul only) | +| Missing-dependency behavior | n/a | **silent no-op** → reference path | **raises** at `set_kernel_backend` | +| Runtime fallback | n/a | per-op reference fallback | logs + falls back to chunked einsum if the kernel throws | + +## What to install + +Optional extras are declared on the `esm` package: + +```bash +pip install "esm[fast]" # -> triton>=3,<4 (the "fused" backend) +pip install "esm[cueq12]" # -> cuequivariance build for CUDA 12 (stub — fill in) +pip install "esm[cueq13]" # -> cuequivariance build for CUDA 13 (stub — fill in) +``` + +- **`None`** needs nothing — it's always available and is the reference/fallback. +- **`"fused"`** needs **Triton 3** (`esm[fast]`). It's GPU-only (Triton JIT-compiles + to PTX) and inference-only (falls back to reference under autograd). If Triton is + not importable, `set_kernel_backend("fused")` installs nothing and silently runs + the reference path — so if `"fused"` is unexpectedly slow, check that Triton + imported. +- **`"cuequivariance"`** needs `cuequivariance-torch` matched to your CUDA toolkit + (`esm[cueq12]` / `esm[cueq13]` — currently stubs, fill in the right build). If it's + not installed, `set_kernel_backend("cuequivariance")` **raises** (unlike `"fused"`, + which degrades silently). + +## Performance (measured, L=1168, single H100, **steady-state after warm-up**) + +| backend | elapsed | pLDDT | note | +|---|---|---|---| +| `None` | 216.8 s | 0.793 | reference; ~7× slower | +| `"fused"` | 28.9 s | 0.793 | fastest steady-state | +| `"cuequivariance"` | 36.3 s | 0.793 | ~6× over reference; tri-mul only | + +Identical pLDDT confirms the backends are numerically equivalent. `"fused"` edges +out `"cuequivariance"` because it accelerates the *whole* block (FFN + dropout + +tri-mul), whereas `"cuequivariance"` only replaces the tri-mul and leaves the rest +on the reference path. + +## Memory + +Peak VRAM is **roughly the same across all three backends** at a given length — the +model dtype (bf16) dominates, and the kernel choice is a second-order effect: + +| backend | peak VRAM (L=1168) | +|---|---| +| `None` | ~48.3 GB | +| `"cuequivariance"` | ~48.3 GB | +| `"fused"` | ~48.4 GB (marginally higher — Triton autotune scratch) | + +So **pick the backend for speed and compilation behavior, not memory.** (To reduce +memory at a given length you want context parallelism — `fold-cp.md` — not a +different kernel backend.) + +## The compilation trade-off + +There are **two** distinct one-time costs on the first fold(s) — don't conflate them +(measured with `esmc_jit_bench.py`): + +1. **A process-global first-fold cost (~10 s), paid once by *any* backend.** The very + first fold in a process pays cuDNN/cuBLAS algorithm selection, flash-attn init, + allocator growth, and the first ESM-C / atom-encoder / diffusion run. This is + **not** a backend property — whichever backend you run first absorbs it. Both + `"fused"` and `"cuequivariance"` pay it once; `None` too. + +2. **A backend-specific kernel init on first use (~2 s), paid once by *both* + backends.** The first fold with a given backend either JIT-compiles the Triton + kernel set (`"fused"`) or loads the precompiled cuEquivariance kernels + (`"cuequivariance"`). Measured ~2.2 s for each. + +3. **A per-new-sequence-length cost — this is the only place the backends differ:** + - **`"fused"` (Triton)** recompiles its shape-specialized kernels for each new + length → **~0.5 s** per new length. + - **`"cuequivariance"`** is precompiled / shape-independent → **~0 s** (~0.2 s). + - **`None`** compiles nothing. + +Measured with `esmc_jit_bench.py` (cost = first-fold minus warm, after the global +warm-up), two lengths: + +| backend | L=772 (first use) | L=1024 (new length) | +|---|---|---| +| `"cuequivariance"` | ~2.2 s (one-time init) | **~0.2 s** | +| `"fused"` | ~2.2 s (one-time init) | **~0.5 s** | + +So the earlier worry that fused pays "seconds-to-minutes per shape" was wrong: the +big cost is the shared ~10 s global first fold, both backends then pay a ~2 s +one-time init, and fused's *extra* per-new-length compile is only ~0.5 s (cueq ~0). +The steady-state timings in the first table exclude all of this (measured after +warm-up). + +- **`apply_torch_compile()` does not stack with the Triton kernels** — call + `set_kernel_backend(None)` before compiling. (torch.compile adds its *own* compile + cost with the same warm-up caveat.) + +## Decision guide + +- **Default / fastest throughput →** `"fused"` (`esm[fast]`). Fastest steady state, + and the incremental per-new-length compile is only ~0.5 s — cheap even for one-shot + or variable-length workloads once the global first fold + ~2 s init are paid. +- **No Triton available, or you want zero per-shape compile (e.g. huge length + variety) →** `"cuequivariance"` (`esm[cueq12]`/`esm[cueq13]`) — precompiled, ~0 s + compile, steady state a bit slower than fused. +- **No extra deps, portability, or a bit-exact reference for debugging →** `None`. + +> Tip: whatever backend you pick, do **one throwaway warm-up fold** at startup to pay +> the ~10 s global cost off the critical path (that's what the benchmark's warm-up +> fold does). + +## Gotchas recap + +- `"fused"` missing Triton → silent reference fallback (looks like `None` speed). +- `"cuequivariance"` missing the package → raises immediately. +- `"cuequivariance"` has a *runtime* safety net: if the kernel throws (odd + shape/dtype), it logs a warning and uses the chunked einsum for that call. +- `"fused"` is inference-only and GPU-only; under autograd or on CPU it falls back. +- Backends are **not additive** on the tri-mul — `set_kernel_backend` selects one path. diff --git a/pyproject.toml b/pyproject.toml index 7ab6d609..c4bd0a6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,17 @@ dependencies = [ "dna_features_viewer", "accelerate", ] + +[project.optional-dependencies] +# ESMFold2 "fused" kernel backend (set_kernel_backend("fused")) +# inference kernels for tri-mul / LN+SwiGLU / dropout-residual. +fast = ["triton>=3,<4"] +# ESMFold2 context-parallel ESM-C tensor parallelism (wrap_model_with_cp(tp_esmc=True)). +fold-cp = ["transformer-engine[pytorch]>=2,<3"] +# "cuequivariance" kernel backend — pick the build matching your CUDA toolkit. +cueq12 = ["cuequivariance-torch", "cuequivariance-ops-torch-cu12"] +cueq13 = ["cuequivariance-torch", "cuequivariance-ops-torch-cu13"] + # Pytest [tool.pytest.ini_options] addopts = """ From 9987208ad90ee6369e411127d0a8edf90dea1325 Mon Sep 17 00:00:00 2001 From: gzynda-nvidia Date: Wed, 22 Jul 2026 00:23:41 -0500 Subject: [PATCH 3/3] Updated README, backend, and cp documentation files. Also added dependency targets for each backend/fold-cp --- cookbook/the_bionemo_contessa/README.md | 94 +++-- cookbook/the_bionemo_contessa/esmfold2-cp.py | 66 ++++ .../{cuequivariance.py => esmfold2-cueq.py} | 9 +- .../the_bionemo_contessa/esmfold2-fused.py | 44 +++ .../the_bionemo_contessa/esmfold2-none.py | 44 +++ .../fast_runtime_and_vram.png | Bin 0 -> 148957 bytes cookbook/the_bionemo_contessa/fold-cp.md | 350 +++++++----------- cookbook/the_bionemo_contessa/single-gpu.md | 265 +++++++------ pyproject.toml | 2 +- 9 files changed, 495 insertions(+), 379 deletions(-) create mode 100644 cookbook/the_bionemo_contessa/esmfold2-cp.py rename cookbook/the_bionemo_contessa/{cuequivariance.py => esmfold2-cueq.py} (83%) create mode 100644 cookbook/the_bionemo_contessa/esmfold2-fused.py create mode 100644 cookbook/the_bionemo_contessa/esmfold2-none.py create mode 100644 cookbook/the_bionemo_contessa/fast_runtime_and_vram.png diff --git a/cookbook/the_bionemo_contessa/README.md b/cookbook/the_bionemo_contessa/README.md index bc826910..7f8f3d3e 100644 --- a/cookbook/the_bionemo_contessa/README.md +++ b/cookbook/the_bionemo_contessa/README.md @@ -1,74 +1,66 @@ # The BioNeMo Contessa — ESMFold2 folding examples -Three scripts that fold protein/ligand complexes with `ESMFold2Model`, showing -when a single GPU suffices and when **context parallelism (CP)** is needed to fit -a large complex. All use the `cuequivariance` backend, `bf16` ESM-C, -`torch.inference_mode()`, and the same fold settings (`num_loops=10`, -`num_sampling_steps=50`, `num_diffusion_samples=1`, `seed=0`). +This cookbook shows how to run Biohub's ESMFold2 structure-prediction model with +[NVIDIA BioNeMo libraries and methods](https://github.com/NVIDIA-BioNeMo). It +covers single-GPU accelerated kernel backends for faster inference and context +parallelism (CP) for inputs whose `L x L` pair representation does not fit on one +GPU. -Tested on 1× and 4× H100 80GB GPUs. +Tested on 1x and 4x H100 80GB GPUs. | Script | Input | GPUs | Result | | --- | --- | --- | --- | -| `cuequivariance.py` | 7ysz (1 protein chain ×2, GDP + TRS ligands) | 1 | Folds successfully | -| `cuequivariance_cp.py` | 5xgo (1 protein chain ×12, CL ligand) | n² (e.g. 4) | Folds successfully via CP | -| `will_fail.py` | 5xgo (same as CP) | 1 | **OOMs** — too large for one GPU | +| `esmfold2-none.py` | 7ysz (1 protein chain ×2) | 1 | Reference single-GPU path | +| `esmfold2-cueq.py` | 7ysz (1 protein chain ×2) | 1 | cuEquivariance backend | +| `esmfold2-fused.py` | 7ysz (1 protein chain ×2) | 1 | Triton fused backend | +| `esmfold2-cp.py` | 7ysz (1 protein chain ×2) | 4 | Same fold API with CP setup | +| `cuequivariance_cp.py` | 5xgo (1 protein chain ×12, CL ligand) | 4 | Larger input via CP | +| `will_fail.py` | 5xgo (same as CP) | 1 | **OOMs** — too large for 1xH100 | ## Environment setup -Start from the NVIDIA PyTorch container, then install the dependencies (the -active commands in [`install.sh`](../../../install.sh)): +Start from the NVIDIA PyTorch container, then install the dependencies: ```bash -docker run --gpus all -it --rm nvcr.io/nvidia/pytorch:26.03-py3 - -pip install git+https://github.com/Biohub/esm.git@main -pip install cuequivariance-torch cuequivariance-ops-torch-cu13 -pip uninstall -y transformers -pip install git+https://github.com/zyndagj/transformers.git@zyndagj/foldcp_support -``` - -The `zyndagj/foldcp_support` transformers branch ships `ESMFold2Model` and its CP -utilities. (Commented-out lines in `install.sh` show an alternative `uv` -virtualenv setup.) - -## `cuequivariance.py` — single-GPU baseline - -Folds a small complex (7ysz) on one GPU and writes `esmfold2_output.cif`. - -```bash -python cuequivariance.py +docker run --gpus all -it --rm nvcr.io/nvidia/pytorch:26.03-py3 bash -l + +# Main ESM package +pip install "git+https://github.com/Biohub/esm.git@main" +# fused dependency +pip install "esm[fused] @ git+https://github.com/Biohub/esm.git@main" +# cuEquivariance dependency (cueq12 also exists) +pip install "esm[cueq13] @ git+https://github.com/Biohub/esm.git@main" +# fold-cp dependency for larger inputs +pip install "esm[fold-cp] @ git+https://github.com/Biohub/esm.git@main" ``` -## `cuequivariance_cp.py` — context-parallel for large complexes +## Single-GPU accelerated backends -Folds a large 12-chain complex (5xgo) by sharding across an **n×n CP grid** of -GPUs. Both the trunk *and* the MSA encoder are sharded via `wrap_model_with_cp` -(trunk-only sharding still OOMs on the full L×L pair). Launch with `torchrun` on -a **perfect-square** number of GPUs (1, 4, 9, …); output goes to -`esmfold2_cp_output.cif`. +[single-gpu.md](single-gpu.md) compares the three single-GPU backend choices: +`None` for the pure PyTorch reference path, `"cuequivariance"` for NVIDIA +cuEquivariance triangle-multiplication kernels, and `"fused"` for Triton kernels +that fuse several ESMFold2 hot-path operations. These backends keep the same model +weights and outputs while trading dependencies, warm-up behavior, and speed. ```bash -PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True torchrun --nproc-per-node=4 cuequivariance_cp.py +python esmfold2-none.py +python esmfold2-cueq.py +python esmfold2-fused.py ``` -> **CP is a capability enabler, not a speedup.** Splitting the L×L pair across -> GPUs lets you fold complexes too large for one GPU, but the per-layer cross-rank -> communication means it won't fold faster than a single GPU that already fits the -> complex. Reach for CP on out-of-memory, not for performance. +## Fold-CP for larger inputs -## `will_fail.py` — what happens without CP +[fold-cp.md](fold-cp.md) documents how `wrap_model_with_cp(model, dm, ...)` +spreads one ESMFold2 fold across a square grid of GPUs using the [Fold-CP methodology](https://github.com/NVIDIA-BioNeMo/boltz-cp). +CP shards the large `L x L` pair activations so longer proteins and complexes fit in memory; it is a +capability feature rather than a general speedup. -The single-GPU script run on the large 5xgo complex (same input as the CP -script). The 12-chain L×L pair representation exceeds one GPU's memory, so it -OOMs — the fix is the CP sharding in `cuequivariance_cp.py`. +Launch CP examples with `torchrun` on a perfect-square number of GPUs. ```bash -python will_fail.py # expected to OOM -``` +torchrun --nproc-per-node=4 esmfold2-cp.py -## Outputs - -Successful runs write an mmCIF file (`esmfold2_output.cif` / -`esmfold2_cp_output.cif`) and print `pLDDT mean`, `pTM`, `ipTM`, elapsed time, -and peak VRAM. +# Larger 5xgo example. +PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \ + torchrun --nproc-per-node=4 cuequivariance_cp.py +``` diff --git a/cookbook/the_bionemo_contessa/esmfold2-cp.py b/cookbook/the_bionemo_contessa/esmfold2-cp.py new file mode 100644 index 00000000..3d0ad30e --- /dev/null +++ b/cookbook/the_bionemo_contessa/esmfold2-cp.py @@ -0,0 +1,66 @@ +import math +import os +from collections import OrderedDict +from time import time + +import torch +from transformers.models.esmfold2.distributed import ( + DistributedManager, + wrap_model_with_cp, +) +from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model +from esm.models.esmfold2 import ( + ESMFold2InputBuilder, + ProteinInput, + StructurePredictionInput, +) + +spi = StructurePredictionInput( # 7ysz + sequences=[ + ProteinInput( + id=["A1", "B1"], + sequence=( + "MDNFDNYEQVASIKVIGIGGAGNNAVNRMIEAGVQGVEFIVANTDAQIISVSKSKNKIVLGKETSKGLGA" + "GANPDVGRQAAIESAEEIKDALKGADMVFVAAGMGGGTGTGAAPIIAKLAREQGALTVGIITTPFSFEGR" + "ARNSYAIQGTEELRKHVDSLIIISNDRLLEVIGGVPLKDSFKEADNILRQGVQTITDLIAVPSLINLDFA" + "DIKTVMKNKGNALFGIGIGSGKDKAIEAANKAIISPLLEASIRGARDAIINVTGGNTLTLNDANDAVDIV" + "KQAIGGEVNIIFGTAVNEHLDDEMIVTVIATGFDGSHHHHHH" + ), + ), + ] +) + +local_rank = int(os.environ["LOCAL_RANK"]) +world_size = int(os.environ["WORLD_SIZE"]) +n = math.isqrt(world_size) +assert n * n == world_size, "CP requires a square number of GPUs: 1, 4, 9, 16, ..." + +torch.cuda.set_device(local_rank) +torch.cuda.reset_peak_memory_stats() + +DistributedManager.initialize( + grid_group_sizes=OrderedDict([("dp", 1), ("cp", (n, n))]), + device_type="cuda", + backend="nccl", +) +dm = DistributedManager() + +model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision="bf16").cuda().eval() +wrap_model_with_cp(model, dm, comm="ring") + +start = time() +with torch.inference_mode(): + result = ESMFold2InputBuilder().fold( + model, spi, num_loops=10, num_sampling_steps=50, + num_diffusion_samples=1, seed=0, + ) +end = time() +peak_mib = torch.cuda.max_memory_allocated() / (1024**2) + +if local_rank == 0: + print(f"pLDDT mean: {float(result.plddt.mean()):.3f}, " + f"pTM: {float(result.ptm):.3f}, ipTM: {float(result.iptm):.3f}") + print(f"Elapsed: {end - start:.2f} sec") + print(f"Max VRAM: {peak_mib:.1f} MB") + +DistributedManager.cleanup() \ No newline at end of file diff --git a/cookbook/the_bionemo_contessa/cuequivariance.py b/cookbook/the_bionemo_contessa/esmfold2-cueq.py similarity index 83% rename from cookbook/the_bionemo_contessa/cuequivariance.py rename to cookbook/the_bionemo_contessa/esmfold2-cueq.py index 08a78b7c..09ea35cd 100644 --- a/cookbook/the_bionemo_contessa/cuequivariance.py +++ b/cookbook/the_bionemo_contessa/esmfold2-cueq.py @@ -5,7 +5,6 @@ from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model from esm.models.esmfold2 import ( ESMFold2InputBuilder, - LigandInput, ProteinInput, StructurePredictionInput, ) @@ -22,8 +21,6 @@ "KQAIGGEVNIIFGTAVNEHLDDEMIVTVIATGFDGSHHHHHH" ), ), - LigandInput(id=["C1", "E1"], ccd=["GDP"]), - LigandInput(id="D1", ccd=["TRS"]), ] ) @@ -43,7 +40,5 @@ print(f"pLDDT mean: {float(result.plddt.mean()):.3f}, " f"pTM: {float(result.ptm):.3f}, ipTM: {float(result.iptm):.3f}") -print(f"Elapsed: {end - start} sec") -print(f"Max VRAM: {peak_mib} MB") -with open("esmfold2_output.cif", "w") as f: - f.write(result.complex.to_mmcif()) +print(f"Elapsed: {end - start:.2f} sec") +print(f"Max VRAM: {peak_mib:.1f} MB") diff --git a/cookbook/the_bionemo_contessa/esmfold2-fused.py b/cookbook/the_bionemo_contessa/esmfold2-fused.py new file mode 100644 index 00000000..ef0643f3 --- /dev/null +++ b/cookbook/the_bionemo_contessa/esmfold2-fused.py @@ -0,0 +1,44 @@ +import gc +from time import time + +import torch +from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model +from esm.models.esmfold2 import ( + ESMFold2InputBuilder, + ProteinInput, + StructurePredictionInput, +) + +spi = StructurePredictionInput( # 7ysz + sequences=[ + ProteinInput( + id=["A1", "B1"], + sequence=( + "MDNFDNYEQVASIKVIGIGGAGNNAVNRMIEAGVQGVEFIVANTDAQIISVSKSKNKIVLGKETSKGLGA" + "GANPDVGRQAAIESAEEIKDALKGADMVFVAAGMGGGTGTGAAPIIAKLAREQGALTVGIITTPFSFEGR" + "ARNSYAIQGTEELRKHVDSLIIISNDRLLEVIGGVPLKDSFKEADNILRQGVQTITDLIAVPSLINLDFA" + "DIKTVMKNKGNALFGIGIGSGKDKAIEAANKAIISPLLEASIRGARDAIINVTGGNTLTLNDANDAVDIV" + "KQAIGGEVNIIFGTAVNEHLDDEMIVTVIATGFDGSHHHHHH" + ), + ), + ] +) + +torch.cuda.reset_peak_memory_stats() + +model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision='bf16').cuda().eval() +model.set_kernel_backend("fused") + +start = time() +with torch.inference_mode(): + result = ESMFold2InputBuilder().fold( + model, spi, num_loops=10, num_sampling_steps=50, + num_diffusion_samples=1, seed=0, + ) +end = time() +peak_mib = torch.cuda.max_memory_allocated() / (1024**2) + +print(f"pLDDT mean: {float(result.plddt.mean()):.3f}, " + f"pTM: {float(result.ptm):.3f}, ipTM: {float(result.iptm):.3f}") +print(f"Elapsed: {end - start:.2f} sec") +print(f"Max VRAM: {peak_mib:.1f} MB") diff --git a/cookbook/the_bionemo_contessa/esmfold2-none.py b/cookbook/the_bionemo_contessa/esmfold2-none.py new file mode 100644 index 00000000..aacf07aa --- /dev/null +++ b/cookbook/the_bionemo_contessa/esmfold2-none.py @@ -0,0 +1,44 @@ +import gc +from time import time + +import torch +from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model +from esm.models.esmfold2 import ( + ESMFold2InputBuilder, + ProteinInput, + StructurePredictionInput, +) + +spi = StructurePredictionInput( # 7ysz + sequences=[ + ProteinInput( + id=["A1", "B1"], + sequence=( + "MDNFDNYEQVASIKVIGIGGAGNNAVNRMIEAGVQGVEFIVANTDAQIISVSKSKNKIVLGKETSKGLGA" + "GANPDVGRQAAIESAEEIKDALKGADMVFVAAGMGGGTGTGAAPIIAKLAREQGALTVGIITTPFSFEGR" + "ARNSYAIQGTEELRKHVDSLIIISNDRLLEVIGGVPLKDSFKEADNILRQGVQTITDLIAVPSLINLDFA" + "DIKTVMKNKGNALFGIGIGSGKDKAIEAANKAIISPLLEASIRGARDAIINVTGGNTLTLNDANDAVDIV" + "KQAIGGEVNIIFGTAVNEHLDDEMIVTVIATGFDGSHHHHHH" + ), + ), + ] +) + +torch.cuda.reset_peak_memory_stats() + +model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision='bf16').cuda().eval() +model.set_kernel_backend(None) + +start = time() +with torch.inference_mode(): + result = ESMFold2InputBuilder().fold( + model, spi, num_loops=10, num_sampling_steps=50, + num_diffusion_samples=1, seed=0, + ) +end = time() +peak_mib = torch.cuda.max_memory_allocated() / (1024**2) + +print(f"pLDDT mean: {float(result.plddt.mean()):.3f}, " + f"pTM: {float(result.ptm):.3f}, ipTM: {float(result.iptm):.3f}") +print(f"Elapsed: {end - start:.2f} sec") +print(f"Max VRAM: {peak_mib:.1f} MB") diff --git a/cookbook/the_bionemo_contessa/fast_runtime_and_vram.png b/cookbook/the_bionemo_contessa/fast_runtime_and_vram.png new file mode 100644 index 0000000000000000000000000000000000000000..e8eda16f9e77f967a1d3ceeebd0567d750933d3c GIT binary patch literal 148957 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
cM0J%fWh8HVZhpIMtmr#j8wm#r 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�qVGX@oQP9E5~q3O1|-knJcUCS+5!AXG7M;A*r# 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

N_PlTuN= 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 ))qWZG~{rTD#IUZfT|;a6WQ&8BLg^gtNLR4w1M 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 literal 0 HcmV?d00001 diff --git a/cookbook/the_bionemo_contessa/fold-cp.md b/cookbook/the_bionemo_contessa/fold-cp.md index 3da491a3..fb3bcf5a 100644 --- a/cookbook/the_bionemo_contessa/fold-cp.md +++ b/cookbook/the_bionemo_contessa/fold-cp.md @@ -1,214 +1,109 @@ -# `wrap_model_with_cp` — running ESMFold2 across several GPUs +# Fold larger inputs by running ESMFold2 across several GPUs -## TL;DR +`wrap_model_with_cp(model, dm, …)` takes a normal `ESMFold2Model` and rewires it, to spread one fold across several GPUs using the [Fold-CP methodology](https://github.com/NVIDIA-BioNeMo/boltz-cp) so you can fold longer +proteins than fit on a single GPU. Wrapping results in the same object with a few internal pieces swapped for versions that split their work across the GPUs. -`wrap_model_with_cp(model, dm, …)` takes a normal `ESMFold2Model` and rewires it, -in place, to **spread one fold across several GPUs** so you can fold **longer -proteins** than fit on a single card. It does not make a new kind of model — it's -the same object, with a few internal pieces swapped for versions that split their -work across the GPUs. Don't wrap it, and it runs exactly as before on one GPU. +## Table of contents -If you just want to use it, skip to [Minimal usage](#minimal-usage). The rest of -this doc explains *why* it's built the way it is. +- [Quickstart](#quickstart) +- [Requirements](#requirements) +- [API: wrap_model_with_cp](#api-wrap_model_with_cp) +- [Behavior and differences from single-GPU implementation](#behavior-and-differences-from-single-gpu-implementation) +- [Larger example](#larger-example) +- [How context parallelism works](#how-context-parallelism-works) -## Why this exists: the "every pair of residues" table +## Quickstart -A protein is a chain of building blocks called **residues** (`L` = how many). To -predict its shape, ESMFold2 keeps a big table with **one cell for every pair of -residues** — cell `(i, j)` is what the model believes about how residue `i` relates -to residue `j`. This is the **pair representation** (or just `z`), and it's what -dominates memory, because `L` residues means `L × L` cells: +Install the fold-cp dependency -- 500 residues → 250,000 cells -- 2,000 residues → 4,000,000 cells (16× bigger) +```bash +pip install "esm[fold-cp] @ git+https://github.com/Biohub/esm.git@main" +``` + +Use [esmfold2-cp.py](esmfold2-cp.py) as the CP version of the single-GPU +[esmfold2-none.py](esmfold2-none.py) example. Launch it with `torchrun`, using a +perfect-square number of GPUs: -Each cell holds ~256 numbers, and the model builds and refines several of these -tables. On one GPU the `L × L` table fills memory first — that's why a single card -caps out at a certain length. (The limiter is **per-GPU peak memory**: the most any -one GPU needs at once.) +```bash +# Launch on 4 GPUs +torchrun --nproc-per-node=4 esmfold2-cp.py +``` -**Context parallelism (CP)** is the fix: instead of every GPU holding the whole -table, **cut it into blocks, one per GPU** — 4 GPUs hold a quarter each, 16 a -sixteenth. More GPUs → each holds less → longer proteins fit. Cutting a table into -per-GPU blocks is called **sharding** (the opposite, a full copy on every GPU, is -**replicated**), and it's the one idea this whole doc is about. +Compared with `esmfold2-none.py`, the `-cp.py` script changes setup, not the input or the `fold()` call: -## How CP splits the work: a grid of GPUs +- Assigns each process to its own CUDA device using `LOCAL_RANK` and `WORLD_SIZE` (set by `torchrun`) +- Initializes `DistributedManager` with `("cp", (n, n))`, where n is the `sqrt(WORLD_SIZE)` +- Wraps the normal `ESMFold2Model` with CP using `wrap_model_with_cp(model, dm, comm="ring")` +- It keeps `num_diffusion_samples=1`, which is required by the distributed diffusion path. -CP arranges the GPUs into a **square grid** (so the GPU count must be a perfect -square: 1, 4, 9, 16, …), set up by a helper called `DistributedManager`. Each GPU -process is a **rank**, and the total count is the **world size**. Picture the -`L × L` table as a checkerboard: **the GPU at grid position `(r, c)` owns the block -of rows `r` and columns `c`.** PyTorch tracks this with a **`DTensor`** — an ordinary -tensor that also knows it's split across GPUs and which block lives where. +The wrapped model is still an `ESMFold2Model`; `fold()` and the result object are +unchanged. -Two things stay small and simple: +## Requirements -- **Model weights are replicated** — every GPU keeps a full copy of the network's - parameters (small compared to the `L × L` activations). -- Only the big `L × L` **activations get sharded**, so each GPU holds `L² / P` of - them (`P` = number of GPUs). That's the whole point: per-GPU memory *drops* as you - add GPUs, instead of every GPU carrying the full table. +- Python environment: + - This ESM package + - transformer-engine 2 (installed by `esm[fold-cp]` dependency) +- Use a perfect-square number of GPUs: 1, 4, 9, 16, ... +- Keep `num_diffusion_samples=1` in `ESMFold2InputBuilder().fold(...)`. The CP + diffusion path currently expects one diffusion sample. +- If you enable `tp_esmc=True`, ESM-C's MLP hidden size must divide across the CP + ranks. The current ESM-C `ffn_hidden=6912` works for 4, 9, and 16 GPUs. -## The design pattern: swap pieces in, keep the same model +> Use a fast GPU interconnect for good throughput. NVLink within a node and + InfiniBand between nodes are the intended setup; PCIe or Ethernet work and still save + memory, but communication may dominate runtime. -The base model file (`modeling_esmfold2.py`) has **no idea CP exists** — it imports -nothing from the distributed code. Instead its `forward` has a few **optional -checks**: `getattr(self, "_cp_*", None)` lookups that ask "has a distributed helper -been plugged in here?" +## API: wrap_model_with_cp ```python -_cp_pair_init = getattr(self, "_cp_pair_init", None) # sharded pair init (z_init, rel_pos, token_bonds, mask) -_cp_engine = getattr(self, "_cp_recycle_engine", None) # recycle loop -_cp_lm = getattr(self, "_cp_language_model", None) # language-model pair builder -_cp_conf = getattr(self, "_cp_confidence_head", None)# confidence head -_cp_disto = getattr(self, "_cp_distogram_head", None) # distogram head +replaced = wrap_model_with_cp( + model, + dm, + comm="gather", + bf16=True, + offload_esmc=True, + wrap_structure=True, + tp_esmc=False, +) ``` -- **Attribute absent (plain model):** the check returns `None` and the forward runs - the original code — bit-for-bit the stock model. -- **Attribute present (after wrapping):** the forward hands that step to the - distributed version. - -So `wrap_model_with_cp` parallelizes the fold in two ways: - -1. **Swaps a submodule** for a same-shaped version that runs across the grid: - - the **MSA encoder** - - every **`FoldingTrunk`** in the model (the recycle trunk, the LM encoder, - `parcae_coda`, and the confidence head's inner trunk) - - the **structure head**'s diffusion module (only if `wrap_structure=True`) - - ESM-C's **MLP** (only if `tp_esmc=True`) -2. **Plugs in a helper** — one of the `_cp_*` attributes the checks above look for: - - **`_cp_pair_init`** — the sharded pair init (`z_init`, rel_pos, token_bonds, the - starting pair table, and the pair mask) - - **`_cp_recycle_engine`** — the recycle loop - - **`_cp_language_model`** — the language-model pair builder (`lm_z`, the `L×L` - pair table built from ESM-C's per-residue embeddings) - - **`_cp_distogram_head`** — the distogram head - - **`_cp_confidence_head`** — the confidence head - -Because it edits the same object, `type(model)` is still `ESMFold2Model`, and -`from_pretrained` / `fold()` / the output dictionary all behave exactly as before. -You can even switch one piece back off at runtime -(`model._cp_confidence_head = None`), which makes it easy to turn a single piece off -and compare. - -## The fold, end to end (in plain terms) - -Before the stage table, here's the whole pipeline in one pass, naming each part: - -1. **`inputs_embedder`** turns the raw atoms into per-residue features. -2. **ESM-C** — a large protein language model (6 billion parameters) — reads the - sequence and produces rich embeddings. -3. Those feed the first `L × L` **pair table** (the "pair init"), optionally combined - with an **MSA** (a stack of evolutionarily-related sequences that gives extra - hints — skipped if you don't have one). -4. The **recycle loop** refines that table over a few passes, each pass running the - main refining network (the **trunk**, a "Pairformer") and feeding the result back - in. -5. The refined table becomes a **distogram** (the model's predicted histogram of - residue-to-residue distances) and then goes to the **structure head**, which uses - **diffusion** to turn it into actual 3D coordinates. -6. Finally the **confidence head** predicts how trustworthy the result is (the - pLDDT / pTM / ipTM scores you get back). - -## Stage-by-stage: what runs where +`wrap_model_with_cp` mutates the existing `ESMFold2Model` in place and returns a +list of module paths that were replaced or augmented. The model type, `fold()` API, +and output object stay the same. -The base model runs every stage at full length `L` on every GPU, with the pair table -full-size. The table below walks that same pipeline; **a `*` means -`wrap_model_with_cp` splits that stage across the grid** (everything else stays -replicated). +| Argument | Default | What it controls | +|---|---:|---| +| `model` | required | The `ESMFold2Model` to rewire. Load it normally with `from_pretrained(...)` first. | +| `dm` | required | The initialized `DistributedManager`; its CP mesh must be square. | +| `comm` | `"gather"` | MSA pair-weighted-averaging communication. Use `"gather"` for exact all-gather behavior, or `"ring"` for a cheaper online-softmax ring path on larger grids. | +| `bf16` | `True` | Runs the distributed trunk/MSA path in bf16. This is the practical default for lower memory and faster inference; use `False` only for tight fp32 parity checks. | +| `offload_esmc` | `True` | Moves the ESM-C language model back to CPU after its one-shot use, freeing GPU memory for the trunk and diffusion stages. | +| `wrap_structure` | `True` | Wraps the diffusion structure head so the conditioned pair representation can stay sharded through structure sampling. | +| `tp_esmc` | `False` | Tensor-parallelizes ESM-C's MLP across the CP ranks. Useful when ESM-C's replicated weights are the remaining memory floor. | -| Stage (base ESMFold2Model.forward) | Split across GPUs? | What wrapping does | -|---|---|---| -| `inputs_embedder` (atom features) | — (replicated) | unchanged; its cost grows only **linearly** with size (it looks at a small window of atoms at a time), so it stays a small, fixed floor | -| ESM-C 6B language model → embeddings | `*` MLP only, if `tp_esmc=True` | splits ESM-C's big MLP across GPUs; the rest stays replicated; ESM-C is moved to CPU right after it's used, to free room | -| `language_model` → `lm_z` (`L×L`) | `*` | `_cp_language_model` builds this `L×L` table already sharded (the full thing is never assembled on any GPU) | -| pair init (`z_init`, rel_pos, token_bonds, initial `z`, pair mask) | `*` | `_cp_pair_init` builds each `L×L` table one block per GPU — no full copy anywhere. (This *used* to be built full then cut up, which is what ran short proteins out of memory.) | -| recycle loop (`_run_one_loop`) | `*` | `_cp_recycle_engine` keeps the pair table sharded the whole time — it never gathers the full table between passes | -| `parcae_readout` + `parcae_coda` | `*` | runs on each GPU's block plus a shared trunk | -| `distogram_head(z + zᵀ)` | `*` | works on the sharded table and gathers only the small result | -| `structure_head.sample` (diffusion → 3D coords) | `*` (if `wrap_structure`) | runs the diffusion with the pair table kept sharded | -| `confidence_head` (pLDDT/PAE/PDE/pTM/ipTM scores) | `*` | every `L×L` input (pair, rel_pos, token_bonds, distance bins, mask) stays sharded — nothing full-size is rebuilt; only the small final scores are gathered | - -**Net effect:** after wrapping, the pair table stays cut-into-blocks from start to -finish (recycle → parcae → distogram → structure → confidence). Only small, -per-residue results and the final outputs are ever reassembled. - -## Inside the recycle loop - -"Recycling" just means: run the refining network a few times, each pass taking the -previous pass's table as a starting point. The loop is -`for _ in range(total_steps)` with `total_steps = num_loops + 1` (so `num_loops=3` -→ 4 passes). A leading **`*`** marks steps split across the grid. - -- **Before the loop (done once, reused by every pass)** - - `inputs_embedder` → per-residue features *(replicated — grows only linearly with size, so it's a small fixed floor, not a wall)* - - **\*** ESM-C 6B → embeddings (then moved to CPU) — **MLP split only if `tp_esmc=True`** - - **\*** `language_model` → `lm_z` (the `L×L` language-model table) *(built already sharded)* - - **\*** `z_init`, rel_pos, token_bonds, the starting pair table, and the pair mask *(each built one block per GPU by `_cp_pair_init` — never full-size anywhere)* - - `a`, `b_mat`, MSA column mask *(tiny, replicated)* -- **The loop itself** - - **\*** the one thing carried from pass to pass is **`z`** (the pair table being refined) *(stays sharded the whole time)* - - each pass does, in order: - 1. **\*** **LM dropout** — randomly drop part of the language-model table, a fresh pattern each pass *(drawn per-block)* - 2. **\*** **LM encoder** — refine that table - 3. reset the injection base — `z_inject = z_init` *(just reusing the sharded starting table; no real compute)* - 4. **\*** **MSA encoder** — re-sample the MSA and fold it in (skipped entirely if you have no MSA) - 5. **\*** add the LM output into `z_inject` *(elementwise, on each GPU's block)* - 6. **\*** **parcae step** — blend the previous table with the new one *(elementwise, on each block)* - 7. **\*** **trunk (Pairformer)** — the main refining network - - the three heavy steps per pass: **\*** LM encoder, **\*** MSA encoder, **\*** trunk -- **After the loop (once)** - - **\*** `parcae_readout` → **\*** `parcae_coda` → **\*** `distogram_head` → **\*** `structure_head.sample` (→ 3D coords, if `wrap_structure`) → **\*** `confidence_head` -- **Redrawn each pass vs fixed:** - - redrawn: **\*** `z`, **\*** the LM dropout pattern, the MSA re-sample, **\*** the three heavy steps - - fixed (built once, reused): `lm_z`, `z_init`, `pair_mask` (all sharded), `a`, `b_mat`, MSA data + column mask -- **How the `*` steps actually run across GPUs (`CPRecycleEngine.run_loop`):** - - keeps `z` sharded across all passes - - calls the sharded versions of the LM encoder / MSA encoder / trunk (no full-table gather between passes) - - the blend + dropout run on each GPU's local block - -**Stays replicated on every GPU (not split):** `inputs_embedder` (but it grows only -linearly, so it never dominates at large `L`), the bulk of the ESM-C forward (unless -`tp_esmc=True`), and the tiny per-residue/scalar bits (`a`, `b_mat`, the per-residue -masks, MSA sampling prep). Note the `L×L` **pair** mask is *not* replicated — it's -built one block per GPU, like the rest of the pair init. - -## Memory: what shrinks, what doesn't - -- **Grows with `L²` (the `L×L` pair table) → now sharded:** pair init, recycle loop, - parcae, distogram, structure, confidence. Each is built one block per GPU and never - assembled full, so **adding GPUs raises the longest protein you can fold**. (The - pair init was the last holdout — it used to build these tables full on every GPU - and only cut them up later, so short proteins could still run out of memory during - setup. Now they start sharded.) -- **Grows only with `L` (atom-level work):** `inputs_embedder` — a slow-growing, - replicated floor. Splitting it would shave the baseline but wouldn't change the - ceiling, so it's left alone. -- **Roughly fixed:** ESM-C's weights (split by `tp_esmc`); ESM-C's own activations are - the remaining floor (a different technique would be needed to shrink those, not yet - built). +If you enable `tp_esmc=True`, ESM-C's MLP hidden size must divide across the CP ranks. +The current ESM-C `ffn_hidden=6912` works for `4`, `9`, and `16` GPUs. + +Common choices: + +```python +# Recommended large-input path. +wrap_model_with_cp(model, dm, comm="ring") -## One hardware caveat: you need a fast GPU interconnect +# Exact communication path; useful for parity checks. +wrap_model_with_cp(model, dm, comm="gather") -Splitting the table trades memory for **communication**: because each GPU holds only -part of it, the GPUs constantly shuffle blocks back and forth — many times per fold — -and they wait while that traffic moves. So CP is only fast with a fast link between -GPUs: **NVLink** (NVIDIA's direct GPU-to-GPU link) within a machine, and -**InfiniBand** (a high-speed network fabric) between machines. +# Also shard ESM-C's MLP when ESM-C memory is the bottleneck. +wrap_model_with_cp(model, dm, comm="ring", tp_esmc=True) +``` -Without them — GPUs on plain PCIe, or nodes on ordinary Ethernet — communication -dominates: you still get the memory savings (longer proteins fit), but each fold can -be much slower. Treat NVLink-within-a-node and InfiniBand-between-nodes as the -intended setup, not an optimization. +After wrapping, keep `num_diffusion_samples=1` in the `fold()` call. -## Comparison to the single-GPU implementation +## Behavior and differences from single-GPU implementation -The API and outputs are identical (`from_pretrained`, `fold()`, output keys, -`type(model)`), and almost every step matches the single-GPU result — exactly, or -within tiny bf16 rounding — checked by folding real proteins and comparing. Two -differences are expected, both small: +The API and outputs are identical (`from_pretrained`, `fold()`, output keys, `type(model)`), and almost every step matches the single-GPU result. +Two differences are expected, both small: - **LM dropout** picks a different (still valid) random pattern than the single-GPU run, because the sharded table can't cheaply reproduce the exact full-table draw. @@ -216,29 +111,72 @@ differences are expected, both small: - **Rounding** differs at about 1e-3, because numbers are summed across GPUs in a different order. -See `fold-cp_caveats.md` for how to tell these harmless differences from a real bug. - -## Requirements the wrapped model adds +Single-GPU kernel backends such as `"fused"` and `"cuequivariance"` are ignored by the CP-wrapped stages; those stages use the distributed CP implementations instead. -- `torch.distributed` set up via `DistributedManager` with a **square** number of GPUs - (1, 4, 9, 16 …). -- `num_diffusion_samples == 1` (the distributed diffusion path). -- `comm="gather"` (exact) or `comm="ring"` (cheaper at scale) for the MSA encoder; - `tp_esmc=True` needs ESM-C's `ffn_hidden=6912` to divide evenly by the number of - GPUs (4/9/16 all work). +## Larger example -## Minimal usage +If you have a large 12-chain complex like [5xgo](https://www.rcsb.org/structure/5XGO), attempting to fold it on a single H100 80GB will fail with OOM -```python -from transformers.models.esmfold2.distributed import DistributedManager, wrap_model_with_cp -from transformers.models.esmfold2.modeling_esmfold2 import ESMFold2Model +```bash +python will_fail.py # expected to OOM +``` -# number of GPUs must be a perfect square; launch with torchrun --nproc-per-node=

-DistributedManager.initialize(OrderedDict([("dp", 1), ("cp", (n, n))]), - device_type="cuda", backend="nccl") -dm = DistributedManager() -model = ESMFold2Model.from_pretrained("biohub/ESMFold2", esmc_precision="bf16").cuda().eval() +Context parallelism sharding will split the memory and allow this protein to be folded on 4x H100 SXM GPUs. +Launch with torchrun on a perfect-square number of GPUs: -wrap_model_with_cp(model, dm, comm="ring", tp_esmc=True) # rewires model in place -# still an ESMFold2Model; fold() / outputs unchanged, now spread across the GPUs. +```bash +PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True torchrun --nproc-per-node=4 cuequivariance_cp.py ``` + +## How context parallelism works + +A protein is a chain of building blocks called residues (`L` = how many). +To predict its shape, ESMFold2 keeps a big matrix with one cell for every pair of residues. +This is the pair representation (or just `z`), and it's what +dominates memory, because `L` residues means `L x L` cells: + +- 500 residues → 250,000 cells +- 2,000 residues → 4,000,000 cells (16x bigger) + +This means that the memory requirement scales quadratically with the input size. + +Context parallelism (CP) shards tensors (vectors and matrices) across GPUs. +More GPUs means more memory for each tensor, allowing for larger inputs. + +CP arranges the GPUs into a square grid so the GPU count must be a perfect square (e.g. 1, 4, 9, 16, …). +Each GPU process is a **rank**, and the total count is the **world size**. +The `L x L` matrix is split into blocks owned by each rank with a [PyTorch DTensor](https://docs.pytorch.org/docs/2.12/distributed.tensor.html). + +The model weights are kept replicated, where each GPU has a copy. +Only the big activations get sharded in this method so per-GPU memory drops as GPUs are added to the inference pool. + +![memory usage](fast_runtime_and_vram.png) + +### Stage-by-stage: what runs where + +The base model runs every stage at full length `L` on every GPU, with the pair table +full-size. The table below walks that same pipeline; **a ✅ means +`wrap_model_with_cp` splits that stage across the grid** (everything else stays +replicated). + +| Stage (base ESMFold2Model.forward) | Split across GPUs? | What wrapping does | +|---|---|---| +| `inputs_embedder` (atom features) | — (replicated) | unchanged; its cost grows only **linearly** with size (it looks at a small window of atoms at a time), so it stays a small, fixed floor | +| ESM-C 6B language model → embeddings | ✅ MLP only, if `tp_esmc=True` | splits ESM-C's big MLP across GPUs; the rest stays replicated; ESM-C is moved to CPU right after it's used, to free room | +| `language_model` → `lm_z` (`LxL`) | ✅ | `_cp_language_model` builds this `LxL` table already sharded (the full thing is never assembled on any GPU) | +| pair init (`z_init`, rel_pos, token_bonds, initial `z`, pair mask) | ✅ | `_cp_pair_init` builds each `LxL` table one block per GPU — no full copy anywhere. | +| recycle loop (`_run_one_loop`) | ✅ | `_cp_recycle_engine` keeps the pair table sharded the whole time — it never gathers the full table between passes | +| `parcae_readout` + `parcae_coda` | ✅ | runs on each GPU's block plus a shared trunk | +| `distogram_head(z + zᵀ)` | ✅ | works on the sharded table and gathers only the small result | +| `structure_head.sample` (diffusion → 3D coords) | ✅ (if `wrap_structure`) | runs the diffusion with the pair table kept sharded | +| `confidence_head` (pLDDT/PAE/PDE/pTM/ipTM scores) | ✅ | every `LxL` input (pair, rel_pos, token_bonds, distance bins, mask) stays sharded — nothing full-size is rebuilt; only the small final scores are gathered | + +### Summary: what shrinks, what doesn't + +- **Grows with the `LxL` pair table → now sharded:** pair init, recycle loop, + parcae, distogram, structure, confidence. Each is built one block per GPU and never + assembled full, so **adding GPUs raises the longest protein you can fold**. +- **Grows only with `L` (atom-level work):** `inputs_embedder` — a slow-growing, + replicated floor. +- **Roughly fixed:** ESM-C's weights (split by `tp_esmc`); ESM-C's own activations are + the remaining floor. diff --git a/cookbook/the_bionemo_contessa/single-gpu.md b/cookbook/the_bionemo_contessa/single-gpu.md index 37a71e2f..49d74105 100644 --- a/cookbook/the_bionemo_contessa/single-gpu.md +++ b/cookbook/the_bionemo_contessa/single-gpu.md @@ -1,143 +1,180 @@ # Single-GPU ESMFold2 — kernel backends +The reference implementation in pure PyTorch is accurate but not the fastest way to run ESMFold2. +Most of the single-GPU runtime sits in a few repeated pair-representation operations, and there are alternate **kernel backends** that keep the same weights and outputs while swapping in faster implementations of those hot paths. +The result is the same ESMFold2 model, but with different speed, warm-up, and dependency trade-offs. + ESMFold2 runs the expensive Pairformer/attention math through a selectable **kernel backend**, chosen with: ```python +model = ESMFold2Model.from_pretrained("biohub/ESMFold2").cuda().eval() model.set_kernel_backend(None | "fused" | "cuequivariance") ``` -This fans out to every module that runs Pairformer-style blocks (`folding_trunk`, +This propagates out to every module that runs Pairformer-style blocks (`folding_trunk`, `lm_encoder`, `parcae_coda`, `confidence_head`, `structure_head`). It only swaps the -*implementation* of a few hot ops — the **triangle multiplicative update** (the -dominant `L×L` Pairformer op), the transition FFN (LayerNorm+SwiGLU), the -dropout-residual, and the attention pair-bias. Weights are untouched and the three -backends are numerically equivalent to bf16 rounding (same pLDDT/pTM). They trade -**speed, memory, first-call compilation, and dependencies** — not accuracy. +*implementation* of a few hot ops. Weights are untouched and the three +backends are numerically equivalent to bf16 rounding (same pLDDT/pTM). > This page covers the single-GPU backends. For multi-GPU context parallelism see > `fold-cp.md` (`wrap_model_with_cp`), which is an orthogonal choice. +## Table of contents + +- [The three backends](#the-three-backends) +- [None (default experience)](#none-default-experience) +- [cuEquivariance](#cuequivariance) +- [Fused](#fused) +- [Performance summary](#performance-summary) +- [Decision guide](#decision-guide) + ## The three backends -| | `None` | `"fused"` | `"cuequivariance"` | +| | None | fused | cuequivariance | |---|---|---|---| -| Implementation | pure PyTorch | vendored **Triton** kernels | **cuEquivariance** kernel | -| Ops accelerated | none (reference) | tri-mul **+ LN+SwiGLU + dropout-residual + pair-bias** | **tri-mul only** (rest = reference) | +| Implementation | PyTorch | Triton kernels | [cuEquivariance](https://github.com/nvidia/cuequivariance) | +| Accelerated Operations | none (reference) | tri-mul + LN+SwiGLU + dropout-residual + pair-bias | tri-mul | | Extra dependency | none | `triton>=3` | `cuequivariance-torch` (CUDA-matched build) | -| First-call compilation | none | **yes — Triton JIT + autotune, per shape** | **no — ships precompiled kernels** | -| Steady-state speed | slowest (reference) | **fastest** | fast (tri-mul only) | -| Missing-dependency behavior | n/a | **silent no-op** → reference path | **raises** at `set_kernel_backend` | -| Runtime fallback | n/a | per-op reference fallback | logs + falls back to chunked einsum if the kernel throws | +| Training / autograd | yes | inference-only | yes | +| First-call compilation | none | yes — Triton JIT + autotune | no — precompiled kernels | + +## None (default experience) + +### Dependencies + +None beyond this ESM package + +### Usage -## What to install +```python +model = ESMFold2Model.from_pretrained("biohub/ESMFold2").cuda().eval() +model.set_kernel_backend(None) # optional +``` + +### Performance + +Fold a 644-residue protein using the default backend with [esmfold2-none.py](esmfold2-none.py) on a single H100 SXM + +```shell +python esmfold2-none.py + +/usr/local/lib/python3.12/dist-packages/torch/jit/_script.py:1487: DeprecationWarning: `torch.jit.script` is deprecated. Please switch to `torch.compile` or `torch.export`. + warnings.warn( +🚨 No checkpoint found for ESMCForSequenceClassification.forward. Please add a `checkpoint` arg to `auto_docstring` or add one in ESMCConfig's docstring +🚨 No checkpoint found for ESMCForTokenClassification.forward. Please add a `checkpoint` arg to `auto_docstring` or add one in ESMCConfig's docstring +Loading checkpoint shards: 100%|█████████████████████████████████| 6/6 [00:00<00:00, 151.51it/s] +Loading CCD dictionary from /root/.cache/huggingface/hub/models--biohub--ESMFold2/snapshots/1ebf0e3481a5184eb6171d40615c79e384b48796/ccd.pkl +pLDDT mean: 0.751, pTM: 0.458, ipTM: 0.096 +Elapsed: 58.02 sec +Max VRAM: 19682.0 MB +``` -Optional extras are declared on the `esm` package: +## cuEquivariance + +[cuEquivariance](https://github.com/nvidia/cuequivariance) is NVIDIA’s precompiled CUDA kernel backend for ESMFold2’s triangle-multiplication hot path, giving a large single-GPU speedup without Triton JIT or accuracy changes. + +### Dependencies + +* `cuequivariance-torch` +* Depending on your CUDA version + * `cuequivariance-ops-torch-cu13` + * `cuequivariance-ops-torch-cu12` + +Install the CUDA-matched extra: ```bash -pip install "esm[fast]" # -> triton>=3,<4 (the "fused" backend) -pip install "esm[cueq12]" # -> cuequivariance build for CUDA 12 (stub — fill in) -pip install "esm[cueq13]" # -> cuequivariance build for CUDA 13 (stub — fill in) +pip install "esm[cueq12]" # CUDA 12 build +``` +or +```bash +pip install "esm[cueq13]" # CUDA 13 build ``` -- **`None`** needs nothing — it's always available and is the reference/fallback. -- **`"fused"`** needs **Triton 3** (`esm[fast]`). It's GPU-only (Triton JIT-compiles - to PTX) and inference-only (falls back to reference under autograd). If Triton is - not importable, `set_kernel_backend("fused")` installs nothing and silently runs - the reference path — so if `"fused"` is unexpectedly slow, check that Triton - imported. -- **`"cuequivariance"`** needs `cuequivariance-torch` matched to your CUDA toolkit - (`esm[cueq12]` / `esm[cueq13]` — currently stubs, fill in the right build). If it's - not installed, `set_kernel_backend("cuequivariance")` **raises** (unlike `"fused"`, - which degrades silently). +You can figure out which one you need with `nvidia-smi` -## Performance (measured, L=1168, single H100, **steady-state after warm-up**) +```shell +$ nvidia-smi -| backend | elapsed | pLDDT | note | -|---|---|---|---| -| `None` | 216.8 s | 0.793 | reference; ~7× slower | -| `"fused"` | 28.9 s | 0.793 | fastest steady-state | -| `"cuequivariance"` | 36.3 s | 0.793 | ~6× over reference; tri-mul only | - -Identical pLDDT confirms the backends are numerically equivalent. `"fused"` edges -out `"cuequivariance"` because it accelerates the *whole* block (FFN + dropout + -tri-mul), whereas `"cuequivariance"` only replaces the tri-mul and leaves the rest -on the reference path. - -## Memory - -Peak VRAM is **roughly the same across all three backends** at a given length — the -model dtype (bf16) dominates, and the kernel choice is a second-order effect: - -| backend | peak VRAM (L=1168) | -|---|---| -| `None` | ~48.3 GB | -| `"cuequivariance"` | ~48.3 GB | -| `"fused"` | ~48.4 GB (marginally higher — Triton autotune scratch) | - -So **pick the backend for speed and compilation behavior, not memory.** (To reduce -memory at a given length you want context parallelism — `fold-cp.md` — not a -different kernel backend.) - -## The compilation trade-off - -There are **two** distinct one-time costs on the first fold(s) — don't conflate them -(measured with `esmc_jit_bench.py`): - -1. **A process-global first-fold cost (~10 s), paid once by *any* backend.** The very - first fold in a process pays cuDNN/cuBLAS algorithm selection, flash-attn init, - allocator growth, and the first ESM-C / atom-encoder / diffusion run. This is - **not** a backend property — whichever backend you run first absorbs it. Both - `"fused"` and `"cuequivariance"` pay it once; `None` too. - -2. **A backend-specific kernel init on first use (~2 s), paid once by *both* - backends.** The first fold with a given backend either JIT-compiles the Triton - kernel set (`"fused"`) or loads the precompiled cuEquivariance kernels - (`"cuequivariance"`). Measured ~2.2 s for each. - -3. **A per-new-sequence-length cost — this is the only place the backends differ:** - - **`"fused"` (Triton)** recompiles its shape-specialized kernels for each new - length → **~0.5 s** per new length. - - **`"cuequivariance"`** is precompiled / shape-independent → **~0 s** (~0.2 s). - - **`None`** compiles nothing. - -Measured with `esmc_jit_bench.py` (cost = first-fold minus warm, after the global -warm-up), two lengths: - -| backend | L=772 (first use) | L=1024 (new length) | -|---|---|---| -| `"cuequivariance"` | ~2.2 s (one-time init) | **~0.2 s** | -| `"fused"` | ~2.2 s (one-time init) | **~0.5 s** | - -So the earlier worry that fused pays "seconds-to-minutes per shape" was wrong: the -big cost is the shared ~10 s global first fold, both backends then pay a ~2 s -one-time init, and fused's *extra* per-new-length compile is only ~0.5 s (cueq ~0). -The steady-state timings in the first table exclude all of this (measured after -warm-up). - -- **`apply_torch_compile()` does not stack with the Triton kernels** — call - `set_kernel_backend(None)` before compiling. (torch.compile adds its *own* compile - cost with the same warm-up caveat.) +Thu Jul 16 13:03:23 2026 ++---------------------------------------------------------------------------------------+ +| NVIDIA-SMI 535.216.03 Driver Version: 535.216.03 CUDA Version: 13.2 | +|-----------------------------------------+----------------------+----------------------+ +``` + +In this case, the CUDA Version is 13.2, so you would need to install `esm[cueq13]`. + +### Usage + +```python +model = ESMFold2Model.from_pretrained("biohub/ESMFold2").cuda().eval() +model.set_kernel_backend("cuequivariance") +``` + +### Performance + +Fold a 644-residue protein using the cuEquivariance backend with [esmfold2-cueq.py](esmfold2-cueq.py) on a single H100 SXM + +```shell +python esmfold2-cueq.py + +pLDDT mean: 0.751, pTM: 0.459, ipTM: 0.096 +Elapsed: 19.90 sec +Max VRAM: 19680.1 MB +``` + +## Fused + +The "fused" backend uses Triton, a Python-based language for writing custom GPU kernels, to fuse several ESMFold2 hot-path operations into fewer CUDA launches for the fastest single-GPU inference while preserving the same model weights and outputs. + +### Dependencies + +The "fused" backend needs Triton 3. It's GPU-only (Triton JIT-compiles to PTX) and **inference-only** (falls back to reference under autograd). + +```bash +pip install "esm[fused]" # triton>=3,<4 +``` + +> If Triton is not importable, `set_kernel_backend("fused")` silently runs the reference path. Check that Triton can be imported if `"fused"` is unexpectedly slow. + +### Usage + +```python +model = ESMFold2Model.from_pretrained("biohub/ESMFold2").cuda().eval() +model.set_kernel_backend("fused") +``` + +### Performance + +Fold a 644-residue protein using the "fused" backend with [esmfold2-fused.py](esmfold2-fused.py) on a single H100 SXM + +```shell +python esmfold2-fused.py + +pLDDT mean: 0.750, pTM: 0.458, ipTM: 0.096 +Elapsed: 16.56 sec +Max VRAM: 19795.1 MB +``` + +## Performance summary + +These results fold the same 644-residue `7ysz` input used in the per-backend +examples above. + +| backend | elapsed | max VRAM | pLDDT | pTM | ipTM | speedup vs `None` | +|---|---:|---:|---:|---:|---:|---:| +| `None` | 58.02 s | 19682.0 MB | 0.751 | 0.458 | 0.096 | 1.0× | +| "cuequivariance" | 19.90 s | 19680.1 MB | 0.751 | 0.459 | 0.096 | 2.9× | +| "fused" | 16.56 s | 19795.1 MB | 0.750 | 0.458 | 0.096 | 3.5× | ## Decision guide -- **Default / fastest throughput →** `"fused"` (`esm[fast]`). Fastest steady state, - and the incremental per-new-length compile is only ~0.5 s — cheap even for one-shot - or variable-length workloads once the global first fold + ~2 s init are paid. -- **No Triton available, or you want zero per-shape compile (e.g. huge length - variety) →** `"cuequivariance"` (`esm[cueq12]`/`esm[cueq13]`) — precompiled, ~0 s - compile, steady state a bit slower than fused. -- **No extra deps, portability, or a bit-exact reference for debugging →** `None`. - -> Tip: whatever backend you pick, do **one throwaway warm-up fold** at startup to pay -> the ~10 s global cost off the critical path (that's what the benchmark's warm-up -> fold does). - -## Gotchas recap - -- `"fused"` missing Triton → silent reference fallback (looks like `None` speed). -- `"cuequivariance"` missing the package → raises immediately. -- `"cuequivariance"` has a *runtime* safety net: if the kernel throws (odd - shape/dtype), it logs a warning and uses the chunked einsum for that call. -- `"fused"` is inference-only and GPU-only; under autograd or on CPU it falls back. -- Backends are **not additive** on the tri-mul — `set_kernel_backend` selects one path. +If you want: + +- **Fastest throughput →** `"fused"`. + - Fastest steady state, and the incremental per-new-length compile is only ~0.5 s +- **Huge length variety →** `"cuequivariance"` + — precompiled, steady state a bit slower than fused. +- **Bit-exact reference for debugging →** `None`. + +> Tip: For any benchmarking, include a warm-up fold at startup to pay the ~10s global cost diff --git a/pyproject.toml b/pyproject.toml index c4bd0a6f..a3bb3da0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ [project.optional-dependencies] # ESMFold2 "fused" kernel backend (set_kernel_backend("fused")) # inference kernels for tri-mul / LN+SwiGLU / dropout-residual. -fast = ["triton>=3,<4"] +fused = ["triton>=3,<4"] # ESMFold2 context-parallel ESM-C tensor parallelism (wrap_model_with_cp(tp_esmc=True)). fold-cp = ["transformer-engine[pytorch]>=2,<3"] # "cuequivariance" kernel backend — pick the build matching your CUDA toolkit.