Skip to content

Commit 82ee355

Browse files
authored
oss sync (#347)
1 parent 14d3829 commit 82ee355

56 files changed

Lines changed: 964 additions & 362 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ repos:
2222
args: [ --fix ]
2323
- id: ruff-format # formatter
2424
types_or: [python, jupyter]
25-
- repo: https://github.com/RobertCraigie/pyright-python
26-
rev: v1.1.399
25+
- repo: local
2726
hooks:
28-
- id: pyright
29-
name: pyright
30-
entry: pyright
31-
language: system
32-
types: [python]
33-
pass_filenames: true # For speed, we only check the files that are changed
34-
# Modal-app tutorial: deps (modal, abnumber) and dynamic decorators aren't resolvable in the lint env.
35-
exclude: ^cookbook/tutorials/binder_design\.py$
27+
- id: ty
28+
name: ty
29+
entry: ty check
30+
language: system # ty is a pixi dev dep (pyproject.toml [tool.pixi.feature.dev]); pre-commit runs in the pixi env
31+
# pass_filenames: false — ty checks the whole project (it needs the full module graph and
32+
# is fast enough), and per-file invocation would bypass [tool.ty.src].exclude (astral-sh/ty#269).
33+
pass_filenames: false
34+
always_run: true
35+
require_serial: true
3636
- repo: https://github.com/gitleaks/gitleaks
3737
rev: v8.24.2
3838
hooks:

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# A world model of protein biology: ESMC, ESMFold2, & ESM Atlas
55

66

7-
[ESMC & ESMFold2 Preprint](https://biohub.ai/papers/esm_protein.pdf) &sdot; [Atlas](https://biohub.ai/esm/protein/atlas) &sdot; [Tutorials](https://github.com/Biohub/esm/tree/main/cookbook/tutorials) &sdot; [Slack](https://bit.ly/esm-slack)<br>
7+
[ESMC & ESMFold2 Preprint](https://www.biorxiv.org/content/10.64898/2026.06.03.729735) &sdot; [Atlas](https://biohub.ai/esm/protein/atlas) &sdot; [Tutorials](https://github.com/Biohub/esm/tree/main/cookbook/tutorials) &sdot; [Slack](https://bit.ly/esm-slack)<br>
88
</div>
99

1010
We are releasing a world model for protein biology: a scientific engine for prediction, design, and discovery. Built on the latest generation of Evolutionary Scale Modeling (ESM), this system learns from the protein sequences produced by evolution and uses that knowledge to represent, map, predict, and design proteins across scales — from atomic interactions to evolutionary relationships spanning billions of years. The system includes three artifacts: ESMC, ESMFold2, and ESM Atlas.
@@ -25,7 +25,7 @@ We are releasing a world model for protein biology: a scientific engine for pred
2525
</div>
2626

2727

28-
ESMFold2 is validated in the lab across five therapeutic targets. Inversion of ESMFold2 enables generation of de novo minibinders and antibody-derived scFvs with high hit rates, nanomolar affinities, target specificity, and functional activity. We've released the full protocol from target sequence to ranked binder design in this [notebook](https://github.com/Biohub/esm/blob/main/cookbook/tutorials/binder_design.ipynb). For additional details, please refer to the [preprint](https://biohub.ai/papers/esm_protein.pdf).
28+
ESMFold2 is validated in the lab across five therapeutic targets. Inversion of ESMFold2 enables generation of de novo minibinders and antibody-derived scFvs with high hit rates, nanomolar affinities, target specificity, and functional activity. We've released the full protocol from target sequence to ranked binder design in this [notebook](https://github.com/Biohub/esm/blob/main/cookbook/tutorials/binder_design.ipynb). For additional details, please refer to the [preprint](https://www.biorxiv.org/content/10.64898/2026.06.03.729735).
2929

3030
<div align="center">
3131
<img src="_assets/esmfold2_binder.png" width="60%"/>
@@ -320,7 +320,7 @@ If you use ESM in your work, please cite one of the following:
320320
and Pannu, Jassi and Bachas, Sharrol and Liu, Daniel S.
321321
and Sercu, Tom and Rives, Alexander},
322322
year = {2026},
323-
url = {https://biohub.ai/papers/esm_protein.pdf},
323+
url = {https://www.biorxiv.org/content/10.64898/2026.06.03.729735},
324324
note = {Preprint}
325325
}
326326
```

cookbook/tutorials/binder_design.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"source": [
88
"## [Tutorial](https://github.com/biohub/esm/tree/main/cookbook/tutorials): How to run minibinder + scFv design fully end-to-end.\n",
99
"\n",
10-
"In this notebook we will use [Modal](https://modal.com/) to parallelize binder design and synthesize a selection, using the protocol described in the ESMC and ESMFold2 paper titled [\"Language Modeling Materializes a World Model of Protein Biology\"](https://biohub.ai/papers/esm_protein.pdf).\n",
10+
"In this notebook we will use [Modal](https://modal.com/) to parallelize binder design and synthesize a selection, using the protocol described in the ESMC and ESMFold2 paper titled [\"Language Modeling Materializes a World Model of Protein Biology\"](https://www.biorxiv.org/content/10.64898/2026.06.03.729735).\n",
1111
"\n",
1212
"Biohub used this approach to design minibinders and scFvs against five therapeutically relevant targets — PDGFRB, EGFR, PD-L1, CD45, and CTLA4 — spanning receptor tyrosine kinases, immune checkpoints, and cell-surface phosphatases. Binders exhibit nanomolar affinity, target specificity, and functional activity in laboratory assays.\n",
1313
"\n",

cookbook/tutorials/binder_design.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"""
1010
Code for binder design with ESMFold2 and ESMC.
1111
12-
As described in [Language Modeling Materializes a World Model of Protein Biology](https://biohub.ai/papers/esm_protein.pdf).
12+
As described in [Language Modeling Materializes a World Model of Protein Biology](https://www.biorxiv.org/content/10.64898/2026.06.03.729735).
1313
"""
1414

1515
import logging
@@ -1051,7 +1051,7 @@ def _apply_torch_compile(model: torch.nn.Module) -> None:
10511051
def _maybe_compile_module(module: torch.nn.Module) -> None:
10521052
if not isinstance(module, compile_targets):
10531053
return
1054-
module.forward = torch.compile(module.forward) # pyright: ignore
1054+
module.forward = torch.compile(module.forward) # ty:ignore[invalid-assignment]
10551055

10561056
model.apply(_maybe_compile_module)
10571057

@@ -1206,7 +1206,9 @@ def main(
12061206
app.load(use_scaling_critics)
12071207
run_fn = app.design
12081208
else:
1209-
app = ESMFold2DesignModal(use_scaling_critics=use_scaling_critics)
1209+
app = ESMFold2DesignModal(
1210+
use_scaling_critics=use_scaling_critics # ty:ignore[unknown-argument]
1211+
)
12101212
run_fn = app.design.remote
12111213

12121214
seq, trajectory, results = run_fn(

cookbook/tutorials/embed.ipynb

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"# If you are working in colab, uncomment these lines to install dependencies\n",
2626
"#! pip install esm@git+https://github.com/Biohub/esm.git@main\n",
2727
"#! pip install matplotlib\n",
28-
"#! pip install seaborn"
28+
"#! pip install seaborn\n",
29+
"#! pip install remotezip"
2930
]
3031
},
3132
{
@@ -137,47 +138,32 @@
137138
},
138139
{
139140
"cell_type": "code",
140-
"execution_count": 27,
141+
"execution_count": null,
141142
"metadata": {},
142-
"outputs": [
143-
{
144-
"name": "stdout",
145-
"output_type": "stream",
146-
"text": [
147-
"--2026-05-28 16:44:15-- https://docs.google.com/uc?export=download&id=1SpOkL11MJxIgy99dqufvUNJuCiuhxuyg\n",
148-
"Resolving docs.google.com (docs.google.com)... 142.251.210.78\n",
149-
"Connecting to docs.google.com (docs.google.com)|142.251.210.78|:443... connected.\n",
150-
"HTTP request sent, awaiting response... 303 See Other\n",
151-
"Location: https://drive.usercontent.google.com/download?id=1SpOkL11MJxIgy99dqufvUNJuCiuhxuyg&export=download [following]\n",
152-
"--2026-05-28 16:44:15-- https://drive.usercontent.google.com/download?id=1SpOkL11MJxIgy99dqufvUNJuCiuhxuyg&export=download\n",
153-
"Resolving drive.usercontent.google.com (drive.usercontent.google.com)... 192.178.50.65\n",
154-
"Connecting to drive.usercontent.google.com (drive.usercontent.google.com)|192.178.50.65|:443... connected.\n",
155-
"HTTP request sent, awaiting response... 200 OK\n",
156-
"Length: 43132 (42K) [application/octet-stream]\n",
157-
"Saving to: 'adk.csv'\n",
158-
"\n",
159-
"adk.csv 100%[===================>] 42.12K --.-KB/s in 0.02s \n",
160-
"\n",
161-
"2026-05-28 16:44:16 (2.17 MB/s) - 'adk.csv' saved [43132/43132]\n",
162-
"\n"
163-
]
164-
}
165-
],
143+
"outputs": [],
166144
"source": [
167-
"!wget --no-check-certificate \"https://docs.google.com/uc?export=download&id=1SpOkL11MJxIgy99dqufvUNJuCiuhxuyg\" -O adk.csv"
145+
"# Stream just the one CSV we need out of the 200MB archive, no full download.\n",
146+
"from remotezip import RemoteZip\n",
147+
"\n",
148+
"DATA_URL = \"https://zenodo.org/records/15022271/files/data.zip\"\n",
149+
"MEMBER = \"data/adk_ml_dataset.csv\"\n",
150+
"\n",
151+
"with RemoteZip(DATA_URL) as zf:\n",
152+
" with zf.open(MEMBER) as src, open(\"adk_ml_dataset.csv\", \"wb\") as dst:\n",
153+
" dst.write(src.read())"
168154
]
169155
},
170156
{
171157
"cell_type": "code",
172-
"execution_count": 28,
158+
"execution_count": null,
173159
"metadata": {},
174160
"outputs": [],
175161
"source": [
176162
"import matplotlib.pyplot as plt\n",
177163
"import pandas as pd\n",
178164
"import seaborn as sns\n",
179165
"\n",
180-
"adk_path = \"adk.csv\"\n",
166+
"adk_path = \"adk_ml_dataset.csv\"\n",
181167
"df = pd.read_csv(adk_path)\n",
182168
"df = df[[\"org_name\", \"sequence\", \"lid_type\", \"temperature\"]]\n",
183169
"df = df[df[\"lid_type\"] != \"other\"] # drop one structural class for simplicity"
@@ -231,7 +217,7 @@
231217
},
232218
{
233219
"cell_type": "code",
234-
"execution_count": 32,
220+
"execution_count": null,
235221
"metadata": {},
236222
"outputs": [],
237223
"source": [
@@ -244,7 +230,7 @@
244230
},
245231
{
246232
"cell_type": "code",
247-
"execution_count": 33,
233+
"execution_count": null,
248234
"metadata": {},
249235
"outputs": [],
250236
"source": [
@@ -322,9 +308,9 @@
322308
],
323309
"metadata": {
324310
"kernelspec": {
325-
"display_name": "Pixi (ESM)",
311+
"display_name": "default",
326312
"language": "python",
327-
"name": "pixi-esm"
313+
"name": "python3"
328314
},
329315
"language_info": {
330316
"codemirror_mode": {
@@ -336,7 +322,7 @@
336322
"name": "python",
337323
"nbconvert_exporter": "python",
338324
"pygments_lexer": "ipython3",
339-
"version": "3.12.12"
325+
"version": "3.12.9"
340326
}
341327
},
342328
"nbformat": 4,

cookbook/tutorials/esmc_sae_feature_interpretation.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@
493493
" for pos in top_positions:\n",
494494
" if activations[pos] > 0:\n",
495495
" ax.annotate(\n",
496-
" f\"{sequence[pos]}{pos+1}\",\n",
496+
" f\"{sequence[pos]}{pos + 1}\",\n",
497497
" (pos + 1, activations[pos]),\n",
498498
" textcoords=\"offset points\",\n",
499499
" xytext=(0, 6),\n",
@@ -508,7 +508,7 @@
508508
" top_positions = np.argsort(activations)[::-1][:5]\n",
509509
" for pos in top_positions:\n",
510510
" if activations[pos] > 0:\n",
511-
" print(f\" Position {pos+1} ({sequence[pos]}): {activations[pos]:.3f}\")\n",
511+
" print(f\" Position {pos + 1} ({sequence[pos]}): {activations[pos]:.3f}\")\n",
512512
" print()"
513513
]
514514
},

cookbook/tutorials/gfp_design.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@
432432
"alignment = alignments[0]\n",
433433
"\n",
434434
"identity = align.get_sequence_identity(alignment)\n",
435-
"print(f\"Sequence identity: {100*identity:.2f}%\")\n",
435+
"print(f\"Sequence identity: {100 * identity:.2f}%\")\n",
436436
"\n",
437437
"print(\"\\nSequence alignment:\")\n",
438438
"fig = pl.figure(figsize=(8.0, 4.0))\n",

esm/layers/attention.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
from esm.layers.rotary import RotaryEmbedding, TritonRotaryEmbedding
99

1010
try:
11-
from flash_attn import flash_attn_varlen_qkvpacked_func # type: ignore
11+
from flash_attn import flash_attn_varlen_qkvpacked_func
1212
except (ImportError, RuntimeError):
13-
flash_attn_varlen_qkvpacked_func = None
13+
flash_attn_varlen_qkvpacked_func = None # ty:ignore[invalid-assignment]
1414

1515

1616
class MultiHeadAttention(nn.Module):
@@ -135,7 +135,8 @@ def forward(
135135
)
136136
qkv_N3HD = self.rotary(qkv_N3HD, cu_seqlens, max_seqlen)
137137

138-
context_NHD = flash_attn_varlen_qkvpacked_func( # type: ignore
138+
assert flash_attn_varlen_qkvpacked_func is not None
139+
context_NHD = flash_attn_varlen_qkvpacked_func(
139140
qkv_N3HD, cu_seqlens, max_seqlen, softmax_scale=self.d_head**-0.5
140141
)
141142
context_ND = einops.rearrange(context_NHD, "n h d -> n (h d)")

esm/layers/codebook.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77

88
class EMACodebook(nn.Module):
9+
embeddings: torch.Tensor
10+
N: torch.Tensor
11+
z_avg: torch.Tensor
12+
913
def __init__(
1014
self,
1115
n_codes,
@@ -17,7 +21,7 @@ def __init__(
1721
super().__init__()
1822
self.register_buffer("embeddings", torch.randn(n_codes, embedding_dim))
1923
self.register_buffer("N", torch.zeros(n_codes))
20-
self.register_buffer("z_avg", self.embeddings.data.clone()) # pyright: ignore[reportCallIssue]
24+
self.register_buffer("z_avg", self.embeddings.data.clone())
2125

2226
self.n_codes = n_codes
2327
self.embedding_dim = embedding_dim
@@ -50,9 +54,9 @@ def _init_embeddings(self, z):
5054
_k_rand = y[torch.randperm(y.shape[0])][: self.n_codes]
5155
if dist.is_initialized():
5256
dist.broadcast(_k_rand, 0)
53-
self.embeddings.data.copy_(_k_rand) # pyright: ignore[reportCallIssue]
54-
self.z_avg.data.copy_(_k_rand) # pyright: ignore[reportCallIssue]
55-
self.N.data.copy_(torch.ones(self.n_codes)) # pyright: ignore[reportCallIssue]
57+
self.embeddings.data.copy_(_k_rand)
58+
self.z_avg.data.copy_(_k_rand)
59+
self.N.data.copy_(torch.ones(self.n_codes))
5660

5761
def forward(self, z):
5862
# z: [b, t, c]
@@ -62,17 +66,14 @@ def forward(self, z):
6266
flat_inputs = z.view(-1, self.embedding_dim)
6367
distances = (
6468
(flat_inputs**2).sum(dim=1, keepdim=True)
65-
- 2 * flat_inputs @ self.embeddings.t() # pyright: ignore[reportCallIssue]
66-
+ (self.embeddings.t() ** 2).sum(dim=0, keepdim=True) # pyright: ignore[reportCallIssue]
69+
- 2 * flat_inputs @ self.embeddings.t()
70+
+ (self.embeddings.t() ** 2).sum(dim=0, keepdim=True)
6771
) # [bt, c]
6872

6973
encoding_indices = torch.argmin(distances, dim=1)
7074
encoding_indices = encoding_indices.view(*z.shape[:2]) # [b, t, ncode]
7175

72-
embeddings = F.embedding(
73-
encoding_indices,
74-
self.embeddings, # pyright: ignore[reportArgumentType]
75-
) # [b, t, c] # pyright: ignore[reportArgumentType]
76+
embeddings = F.embedding(encoding_indices, self.embeddings) # [b, t, c]
7677

7778
commitment_loss = 0.25 * F.mse_loss(z, embeddings.detach())
7879

@@ -84,8 +85,8 @@ def forward(self, z):
8485
return embeddings_st, encoding_indices, commitment_loss
8586

8687
def dictionary_lookup(self, encodings):
87-
embeddings = F.embedding(encodings, self.embeddings) # pyright: ignore[reportArgumentType]
88+
embeddings = F.embedding(encodings, self.embeddings)
8889
return embeddings
8990

9091
def soft_codebook_lookup(self, weights: torch.Tensor) -> torch.Tensor:
91-
return weights @ self.embeddings # pyright: ignore[reportOperatorIssue]
92+
return weights @ self.embeddings

esm/layers/rotary.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@
2626
from einops import rearrange, repeat
2727

2828
try:
29-
from flash_attn.ops.triton.rotary import ( # type:ignore
30-
apply_rotary as apply_triton_rotary,
31-
)
29+
from flash_attn.ops.triton.rotary import apply_rotary as apply_triton_rotary
3230
except ImportError:
33-
apply_triton_rotary = None
31+
apply_triton_rotary = None # ty:ignore[invalid-assignment]
3432

3533

3634
def rotate_half(x, interleaved=False):
@@ -169,23 +167,23 @@ def _update_cos_sin_cache(self, seqlen, device=None, dtype=None):
169167
else:
170168
inv_freq = self.inv_freq
171169
else:
172-
t = torch.arange(seqlen, device=device, dtype=self.inv_freq.dtype) # pyright: ignore[reportArgumentType, reportCallIssue]
170+
t = torch.arange(
171+
seqlen, device=device, dtype=self.inv_freq.dtype
172+
) # ty:ignore[no-matching-overload]
173173
t /= self.scaling_factor
174174
inv_freq = self.inv_freq
175175
# Don't do einsum, it converts fp32 to fp16 under AMP
176176
# freqs = torch.einsum("i,j->ij", t, self.inv_freq)
177-
freqs = torch.outer(t, inv_freq) # pyright: ignore[reportArgumentType]
177+
freqs = torch.outer(t, inv_freq) # ty:ignore[invalid-argument-type]
178178

179179
if self.scale is None:
180180
self._cos_cached = torch.cos(freqs).to(dtype)
181181
self._sin_cached = torch.sin(freqs).to(dtype)
182182
else:
183183
power = (
184-
torch.arange( # pyright: ignore[reportCallIssue]
185-
seqlen,
186-
dtype=self.scale.dtype, # pyright: ignore[reportArgumentType]
187-
device=self.scale.device, # pyright: ignore[reportArgumentType]
188-
)
184+
torch.arange(
185+
seqlen, dtype=self.scale.dtype, device=self.scale.device
186+
) # ty:ignore[no-matching-overload]
189187
- seqlen // 2
190188
) / self.scale_base
191189
scale = self.scale.to(device=power.device) ** power.unsqueeze(-1)
@@ -225,7 +223,7 @@ def forward(
225223
self.interleaved,
226224
True, # inplace=True
227225
),
228-
) # type: ignore
226+
)
229227
else:
230228
assert False
231229

0 commit comments

Comments
 (0)