Instructions to use Synthyra/ESMFold2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Synthyra/ESMFold2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="Synthyra/ESMFold2", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("Synthyra/ESMFold2", trust_remote_code=True) model = AutoModel.from_pretrained("Synthyra/ESMFold2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Update FastPLMs runtime and model cards
Browse files- README.md +44 -42
- fastplms/models.toml +37 -1
- fastplms/models/esm_plusplus/modeling_esm_plusplus.py +2 -0
- fastplms/models/esmfold2/configuration_esmfold2.py +336 -326
- fastplms/models/esmfold2/esmfold2_mmcif_parsing.py +1 -1
- fastplms/models/esmfold2/esmfold2_output.py +9 -5
- fastplms/models/esmfold2/esmfold2_processor.py +43 -12
- fastplms/models/esmfold2/modeling_esmfold2.py +0 -0
- fastplms/models/esmfold2/modeling_esmfold2_common.py +12 -1
- fastplms/models/esmfold2/modeling_esmfold2_experimental.py +79 -21
- fastplms/registry.py +0 -0
- fastplms_bundle.py +0 -0
- modeling_fastplms.py +1 -1
README.md
CHANGED
|
@@ -10,6 +10,47 @@ tags:
|
|
| 10 |
|
| 11 |
# ESMFold2
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
## Model overview
|
| 14 |
|
| 15 |
`Synthyra/ESMFold2` packages the `biohub/ESMFold2` checkpoint with the FastPLMs
|
|
@@ -41,30 +82,13 @@ This model requires Python 3.11-3.14, PyTorch 2.13, and Transformers 5.13.
|
|
| 41 |
|
| 42 |
The artifact requirements include the structure dependencies.
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
macOS structure runs are not release evidence.
|
| 47 |
|
| 48 |
The Hub quick start needs network access for the first download. For an
|
| 49 |
air-gapped run, build the manifest-pinned local artifact first and use the
|
| 50 |
offline example.
|
| 51 |
|
| 52 |
-
## Quick start
|
| 53 |
-
|
| 54 |
-
```python
|
| 55 |
-
from transformers import AutoModel
|
| 56 |
-
|
| 57 |
-
model_id = "Synthyra/ESMFold2"
|
| 58 |
-
model = AutoModel.from_pretrained(
|
| 59 |
-
model_id,
|
| 60 |
-
trust_remote_code=True,
|
| 61 |
-
attn_implementation="sdpa",
|
| 62 |
-
).eval()
|
| 63 |
-
```
|
| 64 |
-
|
| 65 |
-
For offline validation, replace `model_id` with the manifest-built
|
| 66 |
-
`dist/hub/ESMFold2` path. Pass `local_files_only=True`.
|
| 67 |
-
|
| 68 |
## Attention backends
|
| 69 |
|
| 70 |
The quick start uses `sdpa`.
|
|
@@ -166,28 +190,6 @@ cif_text = model.result_to_cif(result)
|
|
| 166 |
print(result.ptm, result.plddt.mean().item())
|
| 167 |
```
|
| 168 |
|
| 169 |
-
No target structure is required. For complexes, construct the input from the
|
| 170 |
-
types exposed by the loaded artifact:
|
| 171 |
-
|
| 172 |
-
```python
|
| 173 |
-
types = model.input_types
|
| 174 |
-
complex_input = types.StructurePredictionInput(
|
| 175 |
-
sequences=[
|
| 176 |
-
types.ProteinInput(id="A", sequence="MSTNPKPQRKTKRNT"),
|
| 177 |
-
types.ProteinInput(id="B", sequence="MKTIIALSYIFCLVFA"),
|
| 178 |
-
types.DNAInput(id="C", sequence="ATGC"),
|
| 179 |
-
types.LigandInput(id="L", smiles="O"),
|
| 180 |
-
]
|
| 181 |
-
)
|
| 182 |
-
complex_result = model.fold(
|
| 183 |
-
complex_input,
|
| 184 |
-
num_loops=1,
|
| 185 |
-
num_sampling_steps=200,
|
| 186 |
-
seed=7,
|
| 187 |
-
)
|
| 188 |
-
print(complex_result.ptm, complex_result.plddt.mean().item())
|
| 189 |
-
```
|
| 190 |
-
|
| 191 |
The typed interface also supports RNA, protein MSAs, modifications, and covalent
|
| 192 |
bonds. The public schema recognizes `PocketConditioning` and
|
| 193 |
`DistogramConditioning`, but the pinned official forward consumes neither. Its
|
|
@@ -296,7 +298,7 @@ adapter modules are excluded from checkpoint state. It is not a generic
|
|
| 296 |
- Redistributable: `true`
|
| 297 |
- Complete weight publication required: `false`
|
| 298 |
|
| 299 |
-
## Validation and
|
| 300 |
|
| 301 |
FastPLMs pins the checkpoint, upstream source revisions, state transformation,
|
| 302 |
and required files in `models.toml`. Built artifacts record exact source
|
|
|
|
| 10 |
|
| 11 |
# ESMFold2
|
| 12 |
|
| 13 |
+
## Quick start
|
| 14 |
+
|
| 15 |
+
Load the published model, fold two protein chains together, and write an mmCIF
|
| 16 |
+
file. The example omits `num_sampling_steps` and uses the model default.
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from transformers import AutoModel
|
| 23 |
+
|
| 24 |
+
model = AutoModel.from_pretrained(
|
| 25 |
+
"Synthyra/ESMFold2",
|
| 26 |
+
trust_remote_code=True,
|
| 27 |
+
dtype=torch.float32,
|
| 28 |
+
device_map="cuda",
|
| 29 |
+
esmc_precision="bf16",
|
| 30 |
+
attn_implementation="sdpa",
|
| 31 |
+
).eval()
|
| 32 |
+
model.set_chunk_size(32)
|
| 33 |
+
|
| 34 |
+
types = model.input_types
|
| 35 |
+
complex_input = types.StructurePredictionInput(
|
| 36 |
+
sequences=[
|
| 37 |
+
types.ProteinInput(id="A", sequence="MSTNPKPQRKTKRNT"),
|
| 38 |
+
types.ProteinInput(id="B", sequence="MKTIIALSYIFCLVFA"),
|
| 39 |
+
]
|
| 40 |
+
)
|
| 41 |
+
with torch.inference_mode():
|
| 42 |
+
result = model.fold(
|
| 43 |
+
complex_input,
|
| 44 |
+
num_loops=3,
|
| 45 |
+
num_diffusion_samples=1,
|
| 46 |
+
seed=17,
|
| 47 |
+
verbose=True,
|
| 48 |
+
)
|
| 49 |
+
Path("complex.cif").write_text(model.result_to_cif(result), encoding="utf-8")
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Set `verbose=False` to silence the folding progress display. This variant has an enabled confidence head and returns confidence fields.
|
| 53 |
+
|
| 54 |
## Model overview
|
| 55 |
|
| 56 |
`Synthyra/ESMFold2` packages the `biohub/ESMFold2` checkpoint with the FastPLMs
|
|
|
|
| 82 |
|
| 83 |
The artifact requirements include the structure dependencies.
|
| 84 |
|
| 85 |
+
Validation runs in Docker on any compatible CUDA device. Record the container,
|
| 86 |
+
hardware, precision, and inputs; no GPU product or workstation is required.
|
|
|
|
| 87 |
|
| 88 |
The Hub quick start needs network access for the first download. For an
|
| 89 |
air-gapped run, build the manifest-pinned local artifact first and use the
|
| 90 |
offline example.
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
## Attention backends
|
| 93 |
|
| 94 |
The quick start uses `sdpa`.
|
|
|
|
| 190 |
print(result.ptm, result.plddt.mean().item())
|
| 191 |
```
|
| 192 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
The typed interface also supports RNA, protein MSAs, modifications, and covalent
|
| 194 |
bonds. The public schema recognizes `PocketConditioning` and
|
| 195 |
`DistogramConditioning`, but the pinned official forward consumes neither. Its
|
|
|
|
| 298 |
- Redistributable: `true`
|
| 299 |
- Complete weight publication required: `false`
|
| 300 |
|
| 301 |
+
## Validation and sources
|
| 302 |
|
| 303 |
FastPLMs pins the checkpoint, upstream source revisions, state transformation,
|
| 304 |
and required files in `models.toml`. Built artifacts record exact source
|
fastplms/models.toml
CHANGED
|
@@ -392,7 +392,7 @@ checkpoint_license = "MIT"
|
|
| 392 |
hub_license = "mit"
|
| 393 |
weights_publication_allowed = true
|
| 394 |
state_transform = "identity"
|
| 395 |
-
conversion_provenance = "Input: each pinned Biohub ESMFold2 checkpoint and its separately pinned ESMC checkpoint. Transformation: apply identity to preserve the folding checkpoint exactly, load its parameters in FP32 for CUDA BF16-autocast execution, retain canonical BF16 ESMC weights, and optionally rebuild exactly 80 ESMC attention output projections as transient Transformer Engine linears. Output: the corresponding pinned Synthyra ESMFold2 checkpoint plus its declared ESMC precision policy. Validation: release parity covers exact canonical state, learned projection, prepared features, and seeded BF16 folding; experimental FP8 validation covers strict unavailable-device behavior,
|
| 396 |
representative = "esmfold2"
|
| 397 |
documentation = "docs/esmfold2.md"
|
| 398 |
test_tiers = ["check", "compliance", "structure", "feature", "artifact", "benchmark"]
|
|
@@ -1237,3 +1237,39 @@ official_files = [
|
|
| 1237 |
"model.safetensors=sha256:4e903b740ad6ad704ec60881bfd593e0d6c874a630ffa0f0838276e0b665088f",
|
| 1238 |
]
|
| 1239 |
auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2_experimental.ESMFold2ExperimentalModel", AutoModelForSequenceClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForTokenClassification" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
hub_license = "mit"
|
| 393 |
weights_publication_allowed = true
|
| 394 |
state_transform = "identity"
|
| 395 |
+
conversion_provenance = "Input: each pinned Biohub ESMFold2 checkpoint and its separately pinned ESMC checkpoint. Transformation: apply identity to preserve the folding checkpoint exactly, load its parameters in FP32 for CUDA BF16-autocast execution, retain canonical BF16 ESMC weights, and optionally rebuild exactly 80 ESMC attention output projections as transient Transformer Engine linears. Output: the corresponding pinned Synthyra ESMFold2 checkpoint plus its declared ESMC precision policy. Validation: release parity covers exact canonical state, learned projection, prepared features, and seeded BF16 folding; experimental FP8 validation covers strict unavailable-device behavior, the four 6B-backbone variants, and three BF16-to-FP8 reload cycles on the standard variant. Limitation: only the six manifest-listed ESMFold2 variants are supported; FP8 is experimental, applies only to inference-time ESMC execution, and requires direct CUDA loading with Transformer Engine availability."
|
| 396 |
representative = "esmfold2"
|
| 397 |
documentation = "docs/esmfold2.md"
|
| 398 |
test_tiers = ["check", "compliance", "structure", "feature", "artifact", "benchmark"]
|
|
|
|
| 1237 |
"model.safetensors=sha256:4e903b740ad6ad704ec60881bfd593e0d6c874a630ffa0f0838276e0b665088f",
|
| 1238 |
]
|
| 1239 |
auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2_experimental.ESMFold2ExperimentalModel", AutoModelForSequenceClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForTokenClassification" }
|
| 1240 |
+
|
| 1241 |
+
[[models]]
|
| 1242 |
+
id = "esmfold2_300"
|
| 1243 |
+
family = "esmfold2"
|
| 1244 |
+
size_category = "structure"
|
| 1245 |
+
generation_contract = "not_applicable"
|
| 1246 |
+
msa_conditioning = false
|
| 1247 |
+
publication_status = "published"
|
| 1248 |
+
fast_repo = "Synthyra/ESMFold2-300"
|
| 1249 |
+
fast_revision = "a38a62ae930d157484b331c2bf4241684573adba"
|
| 1250 |
+
fast_files = ["config.json=git-sha1:47ec20cf8b234c3b41d6f3ae1bdfe95d4eb4849e", "model.safetensors=sha256:44d6797c5efebf24753d502b40950e0874871c96ceea14f2d7f7e39cebac67fd"]
|
| 1251 |
+
official_repo = "biohub/ESMFold2-Experimental-Fast-base300M-step1500k"
|
| 1252 |
+
official_revision = "21531e59002c9205284715e28ee802dafb430637"
|
| 1253 |
+
official_files = ["config.json=git-sha1:8c9a04fe22b0e5fca77bc4e2861a12c9494ef4d4", "model.safetensors=sha256:44d6797c5efebf24753d502b40950e0874871c96ceea14f2d7f7e39cebac67fd"]
|
| 1254 |
+
notes = "Experimental Fast checkpoint with a frozen 300M ESM++ backbone, tensor-exact in BF16 with the pinned step-1500000 source, 24 folding blocks, no MSA conditioning, and no confidence head. BF16 execution uses FP32 folding parameters with CUDA autocast. FP8 is unsupported. Docker BF16 inference validation passed on the compact Protein G case."
|
| 1255 |
+
auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2_experimental.ESMFold2ExperimentalModel", AutoModelForSequenceClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForTokenClassification" }
|
| 1256 |
+
backbone_model = "esmc_small"
|
| 1257 |
+
backbone = { repo = "biohub/ESMC-300M-1500000", revision = "56803b6378b82e16c3b24aac49d1fce4445540b7", files = ["config.json=git-sha1:7fe728a0eb3fb81b24491d6cc1de816bf7797c27", "model.safetensors=sha256:8bd6cacf9b5a92d51954b64b20407f1f9f564a7e4849b8663470784d2a8b7ed2", "tokenizer.json=git-sha1:81c797f56768b22dec0301fa771f018b7e43e98c", "tokenizer_config.json=git-sha1:f49f57b24a1c93bd544974811e8ecbd61b7fae89", "special_tokens_map.json=git-sha1:c907ee1dc19b24241749b32d665c291c7e6e8e4b"] }
|
| 1258 |
+
|
| 1259 |
+
[[models]]
|
| 1260 |
+
id = "esmfold2_600"
|
| 1261 |
+
family = "esmfold2"
|
| 1262 |
+
size_category = "structure"
|
| 1263 |
+
generation_contract = "not_applicable"
|
| 1264 |
+
msa_conditioning = false
|
| 1265 |
+
publication_status = "published"
|
| 1266 |
+
fast_repo = "Synthyra/ESMFold2-600"
|
| 1267 |
+
fast_revision = "71c67d0b2b73dc245ea7c3cc0d0476439a882d08"
|
| 1268 |
+
fast_files = ["config.json=git-sha1:8e271837cbdada96c4974c8e543f84065e0f06f1", "model.safetensors=sha256:11a53c1b4700b6c62a5a464fc3ec7076160c19e8584157f88e13275116cfd602"]
|
| 1269 |
+
official_repo = "biohub/ESMFold2-Experimental-Fast-base600M-step1500k"
|
| 1270 |
+
official_revision = "15cf2d6648692f6c17cee1297d8a285476fffa9b"
|
| 1271 |
+
official_files = ["config.json=git-sha1:95517e555f17a1eca4b68866c89033a1f6916a5d", "model.safetensors=sha256:11a53c1b4700b6c62a5a464fc3ec7076160c19e8584157f88e13275116cfd602"]
|
| 1272 |
+
notes = "Experimental Fast checkpoint with a frozen 600M ESM++ backbone, tensor-exact in BF16 with the pinned step-1500000 source, 24 folding blocks, no MSA conditioning, and no confidence head. BF16 execution uses FP32 folding parameters with CUDA autocast. FP8 is unsupported. Configuration, weight identities, and artifact reload are verified; this model is not inference-validated."
|
| 1273 |
+
auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2_experimental.ESMFold2ExperimentalModel", AutoModelForSequenceClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ExperimentalForTokenClassification" }
|
| 1274 |
+
backbone_model = "esmc_large"
|
| 1275 |
+
backbone = { repo = "biohub/ESMC-600M-1500000", revision = "21af9cc429af76ebda6c48074fb624db4735aaaf", files = ["config.json=git-sha1:ec29f6009b21d710f64bf1c058f3a9710833d692", "model.safetensors=sha256:d6869f5ae0f11e5dc829b195e062e87cfcc2f851a08a5edbaf5d1083ae7f76cc", "tokenizer.json=git-sha1:81c797f56768b22dec0301fa771f018b7e43e98c", "tokenizer_config.json=git-sha1:f49f57b24a1c93bd544974811e8ecbd61b7fae89", "special_tokens_map.json=git-sha1:c907ee1dc19b24241749b32d665c291c7e6e8e4b"] }
|
fastplms/models/esm_plusplus/modeling_esm_plusplus.py
CHANGED
|
@@ -1076,6 +1076,8 @@ class PreTrainedESMplusplusModel(FastPLMsAttentionMixin, PreTrainedModel):
|
|
| 1076 |
all other operations and all SAE weights remain BF16.
|
| 1077 |
"""
|
| 1078 |
|
|
|
|
|
|
|
| 1079 |
if self._esmc_fp8:
|
| 1080 |
return self._esmc_precision_status
|
| 1081 |
if self.training:
|
|
|
|
| 1076 |
all other operations and all SAE weights remain BF16.
|
| 1077 |
"""
|
| 1078 |
|
| 1079 |
+
if self.config.hidden_size != 2560 or self.config.num_hidden_layers != 80:
|
| 1080 |
+
raise ValueError("FP8 is supported only for the ESMC-6B backbone.")
|
| 1081 |
if self._esmc_fp8:
|
| 1082 |
return self._esmc_precision_status
|
| 1083 |
if self.training:
|
fastplms/models/esmfold2/configuration_esmfold2.py
CHANGED
|
@@ -1,326 +1,336 @@
|
|
| 1 |
-
# Copyright 2026 Biohub. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
"""Configuration schema for release and experimental ESMFold2 checkpoints."""
|
| 16 |
-
|
| 17 |
-
from __future__ import annotations
|
| 18 |
-
|
| 19 |
-
from dataclasses import asdict, dataclass, field
|
| 20 |
-
from typing import Any, TypeVar, cast
|
| 21 |
-
|
| 22 |
-
from transformers.configuration_utils import PretrainedConfig
|
| 23 |
-
|
| 24 |
-
from fastplms.attention import canonical_checkpoint_attention_backend
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
_ESMC_ATTENTION_IMPLEMENTATIONS = frozenset({"eager", "flex_attention", "sdpa"})
|
| 28 |
-
_ESMC_PRECISIONS = frozenset({"auto", "bf16", "fp32", "fp8"})
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
def _esmc_backbone_checkpoint_ids() -> tuple[str, str]:
|
| 32 |
-
"""Return the manifest-pinned official and FastPLMs ESMC repositories."""
|
| 33 |
-
|
| 34 |
-
from fastplms.registry import RegistryError, get_model_registry
|
| 35 |
-
|
| 36 |
-
registry = get_model_registry()
|
| 37 |
-
family = registry.families["esmfold2"]
|
| 38 |
-
if family.backbone_model is None:
|
| 39 |
-
raise RegistryError("families.esmfold2 must declare backbone_model.")
|
| 40 |
-
backbone = registry[family.backbone_model]
|
| 41 |
-
return backbone.official.repo_id, backbone.fast.repo_id
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
def normalize_esmc_id(esmc_id: str) -> str:
|
| 45 |
-
"""Resolve an official ESMC identifier to its FastPLMs checkpoint mirror."""
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
n_layers: int = field(default=4)
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
@dataclass
|
| 207 |
-
class
|
| 208 |
-
"""Release-model
|
| 209 |
-
|
| 210 |
-
enabled: bool = field(default=True)
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
("
|
| 229 |
-
("
|
| 230 |
-
)
|
| 231 |
-
|
| 232 |
-
("
|
| 233 |
-
("
|
| 234 |
-
("
|
| 235 |
-
("
|
| 236 |
-
("
|
| 237 |
-
("
|
| 238 |
-
("
|
| 239 |
-
)
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
""
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
)
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
self.
|
| 268 |
-
self.
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
)
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
if
|
| 289 |
-
raise
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
)
|
| 293 |
-
self.
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
)
|
| 304 |
-
|
| 305 |
-
self.
|
| 306 |
-
self.
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Biohub. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""Configuration schema for release and experimental ESMFold2 checkpoints."""
|
| 16 |
+
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
from dataclasses import asdict, dataclass, field
|
| 20 |
+
from typing import Any, TypeVar, cast
|
| 21 |
+
|
| 22 |
+
from transformers.configuration_utils import PretrainedConfig
|
| 23 |
+
|
| 24 |
+
from fastplms.attention import canonical_checkpoint_attention_backend
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
_ESMC_ATTENTION_IMPLEMENTATIONS = frozenset({"eager", "flex_attention", "sdpa"})
|
| 28 |
+
_ESMC_PRECISIONS = frozenset({"auto", "bf16", "fp32", "fp8"})
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _esmc_backbone_checkpoint_ids() -> tuple[str, str]:
|
| 32 |
+
"""Return the manifest-pinned official and FastPLMs ESMC repositories."""
|
| 33 |
+
|
| 34 |
+
from fastplms.registry import RegistryError, get_model_registry
|
| 35 |
+
|
| 36 |
+
registry = get_model_registry()
|
| 37 |
+
family = registry.families["esmfold2"]
|
| 38 |
+
if family.backbone_model is None:
|
| 39 |
+
raise RegistryError("families.esmfold2 must declare backbone_model.")
|
| 40 |
+
backbone = registry[family.backbone_model]
|
| 41 |
+
return backbone.official.repo_id, backbone.fast.repo_id
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def normalize_esmc_id(esmc_id: str) -> str:
|
| 45 |
+
"""Resolve an official ESMC identifier to its FastPLMs checkpoint mirror."""
|
| 46 |
+
|
| 47 |
+
from fastplms.registry import get_model_registry
|
| 48 |
+
|
| 49 |
+
official_repo, fast_repo = _esmc_backbone_checkpoint_ids()
|
| 50 |
+
if esmc_id == official_repo:
|
| 51 |
+
return fast_repo
|
| 52 |
+
registry = get_model_registry()
|
| 53 |
+
for spec in registry.by_family("esmfold2"):
|
| 54 |
+
if spec.backbone is not None and spec.backbone_model is not None:
|
| 55 |
+
backbone = registry[spec.backbone_model]
|
| 56 |
+
if esmc_id in {spec.backbone.repo_id, backbone.official.repo_id}:
|
| 57 |
+
return backbone.fast.repo_id
|
| 58 |
+
return esmc_id
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def normalize_esmc_attention_implementation(
|
| 62 |
+
implementation: str | dict[str, str] | None,
|
| 63 |
+
) -> str | None:
|
| 64 |
+
"""Validate the ESMC backend and translate the historical ``flex`` name."""
|
| 65 |
+
|
| 66 |
+
if isinstance(implementation, dict):
|
| 67 |
+
if tuple(implementation) != ("",):
|
| 68 |
+
raise ValueError(
|
| 69 |
+
"ESMFold2 has one ESMC attention backbone; use a string or {'': implementation}."
|
| 70 |
+
)
|
| 71 |
+
implementation = implementation[""]
|
| 72 |
+
canonical = canonical_checkpoint_attention_backend(implementation)
|
| 73 |
+
if canonical is not None and canonical not in _ESMC_ATTENTION_IMPLEMENTATIONS:
|
| 74 |
+
expected = sorted(_ESMC_ATTENTION_IMPLEMENTATIONS)
|
| 75 |
+
raise ValueError(
|
| 76 |
+
f"Unsupported ESMFold2 attention implementation {canonical!r}; "
|
| 77 |
+
f"expected one of {expected}."
|
| 78 |
+
)
|
| 79 |
+
return canonical
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
NestedConfig = TypeVar("NestedConfig")
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _nested_config(value: Any, config_type: type[NestedConfig]) -> NestedConfig:
|
| 86 |
+
if isinstance(value, config_type):
|
| 87 |
+
return value
|
| 88 |
+
return config_type(**value) if isinstance(value, dict) else config_type()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _coerce_nested_field(
|
| 92 |
+
value: NestedConfig | dict[str, Any], config_type: type[NestedConfig]
|
| 93 |
+
) -> NestedConfig:
|
| 94 |
+
"""Convert serialized nested dictionaries while retaining supplied objects."""
|
| 95 |
+
|
| 96 |
+
return config_type(**value) if isinstance(value, dict) else value
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@dataclass
|
| 100 |
+
class AtomAttentionConfig:
|
| 101 |
+
"""Sliding-window atom attention and three-dimensional RoPE settings."""
|
| 102 |
+
|
| 103 |
+
d_atom: int = field(default=128)
|
| 104 |
+
d_token: int = field(default=768)
|
| 105 |
+
n_blocks: int = field(default=3)
|
| 106 |
+
n_heads: int = field(default=4)
|
| 107 |
+
swa_window_size: int = field(default=128)
|
| 108 |
+
expansion_ratio: int = field(default=2)
|
| 109 |
+
spatial_rope_base_frequency: float = field(default=20.0)
|
| 110 |
+
n_spatial_rope_pairs_per_axis: int = field(default=2)
|
| 111 |
+
n_uid_rope_pairs: int = field(default=10)
|
| 112 |
+
uid_rope_base_frequency: float = field(default=10000.0)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@dataclass
|
| 116 |
+
class DiffusionModuleConfig:
|
| 117 |
+
"""Dimensions and depth of the coordinate diffusion network."""
|
| 118 |
+
|
| 119 |
+
sigma_data: float = field(default=16.0)
|
| 120 |
+
c_atom: int = field(default=128)
|
| 121 |
+
c_token: int = field(default=768)
|
| 122 |
+
c_z: int = field(default=256)
|
| 123 |
+
c_s_inputs: int = field(default=451)
|
| 124 |
+
fourier_dim: int = field(default=256)
|
| 125 |
+
relpos_r_max: int = field(default=32)
|
| 126 |
+
relpos_s_max: int = field(default=2)
|
| 127 |
+
atom_num_blocks: int = field(default=3)
|
| 128 |
+
atom_num_heads: int = field(default=4)
|
| 129 |
+
token_num_blocks: int = field(default=12)
|
| 130 |
+
token_num_heads: int = field(default=16)
|
| 131 |
+
transition_multiplier: int = field(default=2)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@dataclass
|
| 135 |
+
class FoldingTrunkConfig:
|
| 136 |
+
"""Iterative pair/single trunk dimensions."""
|
| 137 |
+
|
| 138 |
+
n_layers: int = field(default=24)
|
| 139 |
+
n_heads: int = field(default=8)
|
| 140 |
+
dropout: float = field(default=0.0)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
@dataclass
|
| 144 |
+
class InputsEmbedderConfig:
|
| 145 |
+
"""Input feature width and atom encoder settings."""
|
| 146 |
+
|
| 147 |
+
d_inputs: int = field(default=451)
|
| 148 |
+
atom_encoder: AtomAttentionConfig = field(default_factory=AtomAttentionConfig)
|
| 149 |
+
|
| 150 |
+
def __post_init__(self) -> None:
|
| 151 |
+
self.atom_encoder = _coerce_nested_field(self.atom_encoder, AtomAttentionConfig)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@dataclass
|
| 155 |
+
class DiffusionStructureHeadConfig:
|
| 156 |
+
"""Training and inference schedules for coordinate denoising."""
|
| 157 |
+
|
| 158 |
+
diffusion_module: DiffusionModuleConfig = field(default_factory=DiffusionModuleConfig)
|
| 159 |
+
distogram_bins: int = field(default=128)
|
| 160 |
+
train_noise_log_mean: float = field(default=-1.2)
|
| 161 |
+
train_noise_log_std: float = field(default=1.5)
|
| 162 |
+
gamma_0: float = field(default=0.605)
|
| 163 |
+
gamma_min: float = field(default=1.107)
|
| 164 |
+
noise_scale: float = field(default=0.0)
|
| 165 |
+
step_scale: float = field(default=1.0)
|
| 166 |
+
inference_s_max: float = field(default=160.0)
|
| 167 |
+
inference_s_min: float = field(default=4e-4)
|
| 168 |
+
inference_p: float = field(default=8.0)
|
| 169 |
+
inference_num_steps: int = field(default=68)
|
| 170 |
+
|
| 171 |
+
def __post_init__(self) -> None:
|
| 172 |
+
self.diffusion_module = _coerce_nested_field(self.diffusion_module, DiffusionModuleConfig)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
@dataclass
|
| 176 |
+
class ConfidenceHeadConfig:
|
| 177 |
+
"""Confidence-bin definitions and the compact confidence trunk."""
|
| 178 |
+
|
| 179 |
+
enabled: bool = field(default=True)
|
| 180 |
+
num_plddt_bins: int = field(default=50)
|
| 181 |
+
num_pde_bins: int = field(default=64)
|
| 182 |
+
num_pae_bins: int = field(default=64)
|
| 183 |
+
min_dist: float = field(default=2.0)
|
| 184 |
+
max_dist: float = field(default=52.0)
|
| 185 |
+
distogram_bins: int = field(default=128)
|
| 186 |
+
folding_trunk: FoldingTrunkConfig = field(
|
| 187 |
+
default_factory=lambda: FoldingTrunkConfig(n_layers=4)
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
def __post_init__(self) -> None:
|
| 191 |
+
self.folding_trunk = _coerce_nested_field(self.folding_trunk, FoldingTrunkConfig)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
@dataclass
|
| 195 |
+
class MSAEncoderConfig:
|
| 196 |
+
"""Optional multiple-sequence-alignment encoder settings."""
|
| 197 |
+
|
| 198 |
+
enabled: bool = field(default=False)
|
| 199 |
+
d_msa: int = field(default=128)
|
| 200 |
+
d_hidden: int = field(default=32)
|
| 201 |
+
n_layers: int = field(default=4)
|
| 202 |
+
n_heads_msa: int = field(default=8)
|
| 203 |
+
msa_head_width: int = field(default=32)
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
@dataclass
|
| 207 |
+
class LMEncoderConfig:
|
| 208 |
+
"""Release-model pair encoder derived from language-model states."""
|
| 209 |
+
|
| 210 |
+
enabled: bool = field(default=True)
|
| 211 |
+
n_layers: int = field(default=4)
|
| 212 |
+
lm_dropout: float = field(default=0.25)
|
| 213 |
+
per_loop_lm_dropout: bool = field(default=True)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
@dataclass
|
| 217 |
+
class ParcaeConfig:
|
| 218 |
+
"""Release-model diffusion-loop scheduler settings."""
|
| 219 |
+
|
| 220 |
+
enabled: bool = field(default=True)
|
| 221 |
+
poisson_mean: float = field(default=3.0)
|
| 222 |
+
min_steps: int = field(default=1)
|
| 223 |
+
max_steps: int | None = field(default=6)
|
| 224 |
+
coda_n_layers: int = field(default=2)
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
_SCALAR_DEFAULTS: tuple[tuple[str, Any], ...] = (
|
| 228 |
+
("d_single", 384),
|
| 229 |
+
("d_pair", 256),
|
| 230 |
+
("n_relative_residx_bins", 32),
|
| 231 |
+
("n_relative_chain_bins", 2),
|
| 232 |
+
("num_loops", 10),
|
| 233 |
+
("num_diffusion_samples", 8),
|
| 234 |
+
("disable_msa_features", False),
|
| 235 |
+
("lm_dropout", 0.0),
|
| 236 |
+
("force_lm_dropout_during_inference", False),
|
| 237 |
+
("lm_mask_pct", 0.0),
|
| 238 |
+
("lm_d_model", 2560),
|
| 239 |
+
("lm_num_layers", 80),
|
| 240 |
+
)
|
| 241 |
+
_NESTED_CONFIGS = (
|
| 242 |
+
("inputs", InputsEmbedderConfig),
|
| 243 |
+
("folding_trunk", FoldingTrunkConfig),
|
| 244 |
+
("structure_head", DiffusionStructureHeadConfig),
|
| 245 |
+
("confidence_head", ConfidenceHeadConfig),
|
| 246 |
+
("msa_encoder", MSAEncoderConfig),
|
| 247 |
+
("parcae", ParcaeConfig),
|
| 248 |
+
("lm_encoder", LMEncoderConfig),
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
class ESMFold2Config(PretrainedConfig):
|
| 253 |
+
"""Serializable ESMFold2 architecture, runtime, and precision settings."""
|
| 254 |
+
|
| 255 |
+
model_type = "esmfold2"
|
| 256 |
+
has_no_defaults_at_init = True
|
| 257 |
+
|
| 258 |
+
def __init__(self, **kwargs: Any) -> None:
|
| 259 |
+
legacy_backend = normalize_esmc_attention_implementation(kwargs.get("esmc_attn_backend"))
|
| 260 |
+
requested_backend = normalize_esmc_attention_implementation(
|
| 261 |
+
kwargs.get("attn_implementation")
|
| 262 |
+
)
|
| 263 |
+
resolved_backend = requested_backend or legacy_backend
|
| 264 |
+
kwargs["attn_implementation"] = resolved_backend
|
| 265 |
+
super().__init__(**kwargs)
|
| 266 |
+
|
| 267 |
+
self.type = kwargs.get("type", "release")
|
| 268 |
+
if self.type not in {"experimental", "release"}:
|
| 269 |
+
raise ValueError(
|
| 270 |
+
f"ESMFold2Config.type must be 'release' or 'experimental', got {self.type!r}"
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
for name, default in _SCALAR_DEFAULTS:
|
| 274 |
+
setattr(self, name, kwargs.get(name, default))
|
| 275 |
+
|
| 276 |
+
_official_esmc_repo, default_esmc_repo = _esmc_backbone_checkpoint_ids()
|
| 277 |
+
self.esmc_id = normalize_esmc_id(kwargs.get("esmc_id", default_esmc_repo))
|
| 278 |
+
self.esmc_attn_backend = resolved_backend
|
| 279 |
+
self.esmc_precision = str(kwargs.get("esmc_precision", "auto"))
|
| 280 |
+
if self.esmc_precision not in _ESMC_PRECISIONS:
|
| 281 |
+
raise ValueError(
|
| 282 |
+
"esmc_precision must be 'auto', 'bf16', 'fp32', or 'fp8', "
|
| 283 |
+
f"got {self.esmc_precision!r}."
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
for name, config_type in _NESTED_CONFIGS:
|
| 287 |
+
setattr(self, name, _nested_config(kwargs.get(name), config_type))
|
| 288 |
+
if not isinstance(self.msa_encoder.enabled, bool):
|
| 289 |
+
raise TypeError("msa_encoder.enabled must be a boolean.")
|
| 290 |
+
declared_msa_conditioning = kwargs.get("msa_conditioning")
|
| 291 |
+
if "msa_conditioning" in kwargs and not isinstance(declared_msa_conditioning, bool):
|
| 292 |
+
raise TypeError("msa_conditioning must be a boolean when provided.")
|
| 293 |
+
self.msa_conditioning = (
|
| 294 |
+
self.msa_encoder.enabled
|
| 295 |
+
if "msa_conditioning" not in kwargs
|
| 296 |
+
else declared_msa_conditioning
|
| 297 |
+
)
|
| 298 |
+
if self.msa_conditioning != self.msa_encoder.enabled:
|
| 299 |
+
raise ValueError(
|
| 300 |
+
"msa_conditioning must match msa_encoder.enabled; received "
|
| 301 |
+
f"{self.msa_conditioning!r} and {self.msa_encoder.enabled!r}."
|
| 302 |
+
)
|
| 303 |
+
self.msa_encoder_overwrite = bool(kwargs.get("msa_encoder_overwrite", True))
|
| 304 |
+
|
| 305 |
+
self.classifier_train_scope = str(kwargs.get("classifier_train_scope", "probe"))
|
| 306 |
+
if self.classifier_train_scope not in {"probe", "projection"}:
|
| 307 |
+
raise ValueError(
|
| 308 |
+
"classifier_train_scope must be 'probe' or 'projection', "
|
| 309 |
+
f"got {self.classifier_train_scope!r}."
|
| 310 |
+
)
|
| 311 |
+
self.classifier_probe_hidden_size = int(
|
| 312 |
+
kwargs.get("classifier_probe_hidden_size", 512)
|
| 313 |
+
)
|
| 314 |
+
self.classifier_probe_num_heads = int(kwargs.get("classifier_probe_num_heads", 4))
|
| 315 |
+
self.classifier_probe_dropout = float(kwargs.get("classifier_probe_dropout", 0.1))
|
| 316 |
+
self.classifier_hidden_size = int(kwargs.get("classifier_hidden_size", 4096))
|
| 317 |
+
self.classifier_dropout = float(kwargs.get("classifier_dropout", 0.2))
|
| 318 |
+
self.classifier_pooling_types = list(
|
| 319 |
+
kwargs.get("classifier_pooling_types", ["mean"])
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
def to_dict(self) -> dict[str, Any]:
|
| 323 |
+
output = cast(dict[str, Any], super().to_dict())
|
| 324 |
+
for name, _config_type in _NESTED_CONFIGS:
|
| 325 |
+
output[name] = asdict(getattr(self, name))
|
| 326 |
+
return output
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
__all__ = [
|
| 330 |
+
"ESMFold2Config",
|
| 331 |
+
"LMEncoderConfig",
|
| 332 |
+
"MSAEncoderConfig",
|
| 333 |
+
"ParcaeConfig",
|
| 334 |
+
"normalize_esmc_attention_implementation",
|
| 335 |
+
"normalize_esmc_id",
|
| 336 |
+
]
|
fastplms/models/esmfold2/esmfold2_mmcif_parsing.py
CHANGED
|
@@ -58,7 +58,7 @@ def round_mmcif_columns(cif_file: CIFFile) -> None:
|
|
| 58 |
original = atom_site[name]
|
| 59 |
values = original.as_array(np.float64)
|
| 60 |
strings = np.asarray(
|
| 61 |
-
[f"{value:.{decimals}f}" for value in values],
|
| 62 |
dtype=np.str_,
|
| 63 |
)
|
| 64 |
atom_site[name] = CIFColumn(
|
|
|
|
| 58 |
original = atom_site[name]
|
| 59 |
values = original.as_array(np.float64)
|
| 60 |
strings = np.asarray(
|
| 61 |
+
["?" if not np.isfinite(value) else f"{value:.{decimals}f}" for value in values],
|
| 62 |
dtype=np.str_,
|
| 63 |
)
|
| 64 |
atom_site[name] = CIFColumn(
|
fastplms/models/esmfold2/esmfold2_output.py
CHANGED
|
@@ -79,7 +79,7 @@ class _ComplexRecords:
|
|
| 79 |
|
| 80 |
def build_molecular_complex_from_features(
|
| 81 |
coords: torch.Tensor,
|
| 82 |
-
plddt: torch.Tensor,
|
| 83 |
atom_mask: torch.Tensor,
|
| 84 |
ref_element: torch.Tensor,
|
| 85 |
ref_atom_name_chars: torch.Tensor,
|
|
@@ -96,7 +96,7 @@ def build_molecular_complex_from_features(
|
|
| 96 |
X = coords.float().cpu().numpy()
|
| 97 |
atom_names = ref_atom_name_chars.cpu().numpy()
|
| 98 |
elements = ref_element.cpu().numpy()
|
| 99 |
-
confidence = plddt.float().cpu().numpy()
|
| 100 |
records = _ComplexRecords()
|
| 101 |
|
| 102 |
def decode_atoms(tokens: Iterable[Any]):
|
|
@@ -117,8 +117,8 @@ def build_molecular_complex_from_features(
|
|
| 117 |
if is_nonpolymer:
|
| 118 |
mean_confidence = (
|
| 119 |
float(np.mean([confidence[token.token_index] for token in chain.tokens]))
|
| 120 |
-
if chain.tokens
|
| 121 |
-
else
|
| 122 |
)
|
| 123 |
records.add_token(
|
| 124 |
residue_name=chain.tokens[0].residue_name if chain.tokens else "LIG",
|
|
@@ -135,7 +135,11 @@ def build_molecular_complex_from_features(
|
|
| 135 |
records.add_token(
|
| 136 |
residue_name=residue_tokens[0].residue_name,
|
| 137 |
asym_id=chain.asym_id,
|
| 138 |
-
plddt=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
atoms=decode_atoms(residue_tokens),
|
| 140 |
hetero=False,
|
| 141 |
)
|
|
|
|
| 79 |
|
| 80 |
def build_molecular_complex_from_features(
|
| 81 |
coords: torch.Tensor,
|
| 82 |
+
plddt: torch.Tensor | None,
|
| 83 |
atom_mask: torch.Tensor,
|
| 84 |
ref_element: torch.Tensor,
|
| 85 |
ref_atom_name_chars: torch.Tensor,
|
|
|
|
| 96 |
X = coords.float().cpu().numpy()
|
| 97 |
atom_names = ref_atom_name_chars.cpu().numpy()
|
| 98 |
elements = ref_element.cpu().numpy()
|
| 99 |
+
confidence = None if plddt is None else plddt.float().cpu().numpy()
|
| 100 |
records = _ComplexRecords()
|
| 101 |
|
| 102 |
def decode_atoms(tokens: Iterable[Any]):
|
|
|
|
| 117 |
if is_nonpolymer:
|
| 118 |
mean_confidence = (
|
| 119 |
float(np.mean([confidence[token.token_index] for token in chain.tokens]))
|
| 120 |
+
if confidence is not None and chain.tokens
|
| 121 |
+
else float("nan")
|
| 122 |
)
|
| 123 |
records.add_token(
|
| 124 |
residue_name=chain.tokens[0].residue_name if chain.tokens else "LIG",
|
|
|
|
| 135 |
records.add_token(
|
| 136 |
residue_name=residue_tokens[0].residue_name,
|
| 137 |
asym_id=chain.asym_id,
|
| 138 |
+
plddt=(
|
| 139 |
+
float(np.mean([confidence[token.token_index] for token in residue_tokens]))
|
| 140 |
+
if confidence is not None
|
| 141 |
+
else float("nan")
|
| 142 |
+
),
|
| 143 |
atoms=decode_atoms(residue_tokens),
|
| 144 |
hetero=False,
|
| 145 |
)
|
fastplms/models/esmfold2/esmfold2_processor.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing import Any
|
|
| 10 |
import numpy as np
|
| 11 |
import torch
|
| 12 |
from torch import Tensor
|
|
|
|
| 13 |
|
| 14 |
from .esmfold2_conformers import load_ccd
|
| 15 |
from .esmfold2_molecular_complex import MolecularComplexResult
|
|
@@ -240,15 +241,19 @@ class ESMFold2InputBuilder:
|
|
| 240 |
|
| 241 |
def _decode_sample(
|
| 242 |
self,
|
| 243 |
-
output: Mapping[str, Tensor],
|
| 244 |
features: dict[str, Tensor],
|
| 245 |
chain_infos: list[ChainInfo],
|
| 246 |
sample: int,
|
| 247 |
complex_id: str,
|
| 248 |
) -> MolecularComplexResult:
|
| 249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
molecular_complex = build_molecular_complex_from_features(
|
| 251 |
-
coords=
|
| 252 |
plddt=plddt,
|
| 253 |
atom_mask=features["atom_attention_mask"][0],
|
| 254 |
ref_element=features["ref_element"][0],
|
|
@@ -269,7 +274,7 @@ class ESMFold2InputBuilder:
|
|
| 269 |
iptm = output.get("iptm")
|
| 270 |
return MolecularComplexResult(
|
| 271 |
complex=molecular_complex,
|
| 272 |
-
plddt=plddt.detach().cpu(),
|
| 273 |
ptm=float(ptm[sample].item()) if ptm is not None else None,
|
| 274 |
iptm=float(iptm[sample].item()) if iptm is not None else None,
|
| 275 |
pae=sample_tensor("pae"),
|
|
@@ -281,16 +286,19 @@ class ESMFold2InputBuilder:
|
|
| 281 |
|
| 282 |
def decode(
|
| 283 |
self,
|
| 284 |
-
output: Mapping[str, Tensor],
|
| 285 |
features: dict[str, Tensor],
|
| 286 |
chain_infos: list[ChainInfo],
|
| 287 |
*,
|
| 288 |
num_diffusion_samples: int = 1,
|
| 289 |
complex_id: str = "pred",
|
| 290 |
) -> MolecularComplexResult | list[MolecularComplexResult]:
|
|
|
|
|
|
|
|
|
|
| 291 |
results = [
|
| 292 |
self._decode_sample(output, features, chain_infos, sample, complex_id)
|
| 293 |
-
for sample in range(
|
| 294 |
]
|
| 295 |
return results[0] if num_diffusion_samples == 1 and len(results) == 1 else results
|
| 296 |
|
|
@@ -308,13 +316,24 @@ class ESMFold2InputBuilder:
|
|
| 308 |
max_inference_sigma: int | None = None,
|
| 309 |
early_exit: bool = False,
|
| 310 |
complex_id: str = "pred",
|
|
|
|
| 311 |
) -> MolecularComplexResult | list[MolecularComplexResult]:
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
overrides = _sampler_overrides(noise_scale, step_scale, max_inference_sigma)
|
| 319 |
with torch.no_grad(), seed_context(seed):
|
| 320 |
output = model(
|
|
@@ -324,8 +343,20 @@ class ESMFold2InputBuilder:
|
|
| 324 |
num_diffusion_samples=num_diffusion_samples,
|
| 325 |
early_exit=early_exit,
|
| 326 |
return_dict=True,
|
|
|
|
| 327 |
**overrides,
|
| 328 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
return self.decode(
|
| 330 |
output,
|
| 331 |
features,
|
|
|
|
| 10 |
import numpy as np
|
| 11 |
import torch
|
| 12 |
from torch import Tensor
|
| 13 |
+
from tqdm.auto import tqdm
|
| 14 |
|
| 15 |
from .esmfold2_conformers import load_ccd
|
| 16 |
from .esmfold2_molecular_complex import MolecularComplexResult
|
|
|
|
| 241 |
|
| 242 |
def _decode_sample(
|
| 243 |
self,
|
| 244 |
+
output: Mapping[str, Tensor | None],
|
| 245 |
features: dict[str, Tensor],
|
| 246 |
chain_infos: list[ChainInfo],
|
| 247 |
sample: int,
|
| 248 |
complex_id: str,
|
| 249 |
) -> MolecularComplexResult:
|
| 250 |
+
plddt_output = output.get("plddt")
|
| 251 |
+
plddt = None if plddt_output is None else plddt_output[sample]
|
| 252 |
+
coords_output = output.get("sample_atom_coords")
|
| 253 |
+
if coords_output is None:
|
| 254 |
+
raise ValueError("ESMFold2 output omitted sample_atom_coords.")
|
| 255 |
molecular_complex = build_molecular_complex_from_features(
|
| 256 |
+
coords=coords_output[sample],
|
| 257 |
plddt=plddt,
|
| 258 |
atom_mask=features["atom_attention_mask"][0],
|
| 259 |
ref_element=features["ref_element"][0],
|
|
|
|
| 274 |
iptm = output.get("iptm")
|
| 275 |
return MolecularComplexResult(
|
| 276 |
complex=molecular_complex,
|
| 277 |
+
plddt=None if plddt is None else plddt.detach().cpu(),
|
| 278 |
ptm=float(ptm[sample].item()) if ptm is not None else None,
|
| 279 |
iptm=float(iptm[sample].item()) if iptm is not None else None,
|
| 280 |
pae=sample_tensor("pae"),
|
|
|
|
| 286 |
|
| 287 |
def decode(
|
| 288 |
self,
|
| 289 |
+
output: Mapping[str, Tensor | None],
|
| 290 |
features: dict[str, Tensor],
|
| 291 |
chain_infos: list[ChainInfo],
|
| 292 |
*,
|
| 293 |
num_diffusion_samples: int = 1,
|
| 294 |
complex_id: str = "pred",
|
| 295 |
) -> MolecularComplexResult | list[MolecularComplexResult]:
|
| 296 |
+
coords_output = output.get("sample_atom_coords")
|
| 297 |
+
if coords_output is None:
|
| 298 |
+
raise ValueError("ESMFold2 output omitted sample_atom_coords.")
|
| 299 |
results = [
|
| 300 |
self._decode_sample(output, features, chain_infos, sample, complex_id)
|
| 301 |
+
for sample in range(coords_output.shape[0])
|
| 302 |
]
|
| 303 |
return results[0] if num_diffusion_samples == 1 and len(results) == 1 else results
|
| 304 |
|
|
|
|
| 316 |
max_inference_sigma: int | None = None,
|
| 317 |
early_exit: bool = False,
|
| 318 |
complex_id: str = "pred",
|
| 319 |
+
verbose: bool = False,
|
| 320 |
) -> MolecularComplexResult | list[MolecularComplexResult]:
|
| 321 |
+
if verbose:
|
| 322 |
+
with tqdm(total=1, desc="ESMFold2 typed features", unit="stage") as progress:
|
| 323 |
+
features, chain_infos = self.prepare_model_input(
|
| 324 |
+
model,
|
| 325 |
+
input,
|
| 326 |
+
seed=seed,
|
| 327 |
+
device=model.device,
|
| 328 |
+
)
|
| 329 |
+
progress.update()
|
| 330 |
+
else:
|
| 331 |
+
features, chain_infos = self.prepare_model_input(
|
| 332 |
+
model,
|
| 333 |
+
input,
|
| 334 |
+
seed=seed,
|
| 335 |
+
device=model.device,
|
| 336 |
+
)
|
| 337 |
overrides = _sampler_overrides(noise_scale, step_scale, max_inference_sigma)
|
| 338 |
with torch.no_grad(), seed_context(seed):
|
| 339 |
output = model(
|
|
|
|
| 343 |
num_diffusion_samples=num_diffusion_samples,
|
| 344 |
early_exit=early_exit,
|
| 345 |
return_dict=True,
|
| 346 |
+
verbose=verbose,
|
| 347 |
**overrides,
|
| 348 |
)
|
| 349 |
+
if verbose:
|
| 350 |
+
with tqdm(total=1, desc="ESMFold2 decode", unit="stage") as progress:
|
| 351 |
+
result = self.decode(
|
| 352 |
+
output,
|
| 353 |
+
features,
|
| 354 |
+
chain_infos,
|
| 355 |
+
num_diffusion_samples=num_diffusion_samples,
|
| 356 |
+
complex_id=complex_id,
|
| 357 |
+
)
|
| 358 |
+
progress.update()
|
| 359 |
+
return result
|
| 360 |
return self.decode(
|
| 361 |
output,
|
| 362 |
features,
|
fastplms/models/esmfold2/modeling_esmfold2.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
fastplms/models/esmfold2/modeling_esmfold2_common.py
CHANGED
|
@@ -19,6 +19,7 @@ import torch.nn as nn
|
|
| 19 |
import torch.nn.functional as F
|
| 20 |
from torch import Tensor
|
| 21 |
from torch.utils.checkpoint import checkpoint
|
|
|
|
| 22 |
|
| 23 |
from .configuration_esmfold2 import ESMFold2Config
|
| 24 |
from .reproducibility import seed_context
|
|
@@ -2615,6 +2616,7 @@ class DiffusionStructureHead(nn.Module):
|
|
| 2615 |
return_atom_repr: bool = False,
|
| 2616 |
use_inference_cache: bool = True,
|
| 2617 |
denoising_early_exit_rmsd: float | None = None,
|
|
|
|
| 2618 |
) -> dict[str, Tensor | None]:
|
| 2619 |
"""Diffusion sampling (Algorithm 18).
|
| 2620 |
|
|
@@ -2656,7 +2658,16 @@ class DiffusionStructureHead(nn.Module):
|
|
| 2656 |
step_pairs = list(zip(schedule[:-1], schedule[1:], gammas[1:], strict=True))
|
| 2657 |
num_steps = len(step_pairs)
|
| 2658 |
|
| 2659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2660 |
x, x_denoised_prev = self._center_random_augmentation(
|
| 2661 |
x, atom_mask, second_coords=x_denoised_prev
|
| 2662 |
)
|
|
|
|
| 19 |
import torch.nn.functional as F
|
| 20 |
from torch import Tensor
|
| 21 |
from torch.utils.checkpoint import checkpoint
|
| 22 |
+
from tqdm.auto import tqdm
|
| 23 |
|
| 24 |
from .configuration_esmfold2 import ESMFold2Config
|
| 25 |
from .reproducibility import seed_context
|
|
|
|
| 2616 |
return_atom_repr: bool = False,
|
| 2617 |
use_inference_cache: bool = True,
|
| 2618 |
denoising_early_exit_rmsd: float | None = None,
|
| 2619 |
+
verbose: bool = False,
|
| 2620 |
) -> dict[str, Tensor | None]:
|
| 2621 |
"""Diffusion sampling (Algorithm 18).
|
| 2622 |
|
|
|
|
| 2658 |
step_pairs = list(zip(schedule[:-1], schedule[1:], gammas[1:], strict=True))
|
| 2659 |
num_steps = len(step_pairs)
|
| 2660 |
|
| 2661 |
+
step_iterator = step_pairs
|
| 2662 |
+
if verbose:
|
| 2663 |
+
step_iterator = tqdm(
|
| 2664 |
+
step_pairs,
|
| 2665 |
+
total=num_steps if denoising_early_exit_rmsd is None else None,
|
| 2666 |
+
desc="ESMFold2 diffusion",
|
| 2667 |
+
unit="step",
|
| 2668 |
+
)
|
| 2669 |
+
|
| 2670 |
+
for step_idx, (sigma_tm, sigma_t, gamma) in enumerate(step_iterator):
|
| 2671 |
x, x_denoised_prev = self._center_random_augmentation(
|
| 2672 |
x, atom_mask, second_coords=x_denoised_prev
|
| 2673 |
)
|
fastplms/models/esmfold2/modeling_esmfold2_experimental.py
CHANGED
|
@@ -9,6 +9,7 @@ re-injection and a different confidence/MSA stack.
|
|
| 9 |
from __future__ import annotations
|
| 10 |
|
| 11 |
import gc
|
|
|
|
| 12 |
from pathlib import Path
|
| 13 |
from typing import Any, ClassVar, cast
|
| 14 |
|
|
@@ -16,6 +17,7 @@ import torch
|
|
| 16 |
import torch.nn as nn
|
| 17 |
import torch.nn.functional as F
|
| 18 |
from torch import Tensor
|
|
|
|
| 19 |
from transformers.modeling_utils import PreTrainedModel
|
| 20 |
|
| 21 |
from .attention import ESMFold2AttentionMixin
|
|
@@ -569,9 +571,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 569 |
precision=precision,
|
| 570 |
device=device,
|
| 571 |
local_files_only=(
|
| 572 |
-
self._esmc_local_files_only
|
| 573 |
-
if local_files_only is None
|
| 574 |
-
else local_files_only
|
| 575 |
),
|
| 576 |
)
|
| 577 |
|
|
@@ -627,6 +627,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 627 |
residue_index: Tensor,
|
| 628 |
mol_type: Tensor,
|
| 629 |
tok_mask: Tensor,
|
|
|
|
| 630 |
) -> Tensor:
|
| 631 |
if self._esmc_fp8 and torch.is_grad_enabled():
|
| 632 |
_reload_esmc_bf16_for_gradients(
|
|
@@ -640,6 +641,19 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 640 |
raise RuntimeError("ESMFold2 language-model features require load_esmc=True.")
|
| 641 |
pad_to = 16 if self._esmc_fp8 else None
|
| 642 |
with _lm_precision_context(self._esmc_precision_status.resolved, self.device):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
return compute_lm_hidden_states(
|
| 644 |
self._esmc,
|
| 645 |
input_ids,
|
|
@@ -696,6 +710,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 696 |
frames_idx: Tensor | None = None,
|
| 697 |
disto_cond: Tensor | None = None,
|
| 698 |
disto_cond_mask: Tensor | None = None,
|
|
|
|
| 699 |
) -> ESMFold2Output | tuple[Any, ...]:
|
| 700 |
output_hidden_states, return_dict = _resolve_structure_output_controls(
|
| 701 |
self.config,
|
|
@@ -796,7 +811,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 796 |
|
| 797 |
if lm_hidden_states is None and input_ids is not None and self._esmc is not None:
|
| 798 |
lm_hidden_states = self._compute_lm_hidden_states(
|
| 799 |
-
input_ids, asym_id, residue_index, mol_type, tok_mask
|
| 800 |
)
|
| 801 |
if lm_hidden_states is not None:
|
| 802 |
lm_dropout = (
|
|
@@ -845,7 +860,16 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 845 |
z = torch.zeros_like(z_init)
|
| 846 |
prev_pair: Tensor | None = None
|
| 847 |
prev_disto_probs: Tensor | None = None
|
| 848 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 849 |
z = z_init + self.pair_loop_proj(z)
|
| 850 |
if msa_kwargs is not None and self.msa_encoder is not None:
|
| 851 |
z = z + self.msa_encoder(x_pair=z, **msa_kwargs).to(z.dtype)
|
|
@@ -899,6 +923,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 899 |
step_scale=step_scale,
|
| 900 |
return_atom_repr=False,
|
| 901 |
denoising_early_exit_rmsd=(0.10 if early_exit else None),
|
|
|
|
| 902 |
)
|
| 903 |
sample_coords = structure_output["sample_atom_coords"]
|
| 904 |
if sample_coords is None:
|
|
@@ -924,21 +949,45 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 924 |
"sample_atom_coords": sample_coords,
|
| 925 |
"representative_atom_coords": representative_atom_coords,
|
| 926 |
}
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 942 |
output.update(confidence_output)
|
| 943 |
output["atom_pad_mask"] = atm_mask.unsqueeze(0) if atm_mask.dim() == 1 else atm_mask
|
| 944 |
output["residue_index"] = residue_index
|
|
@@ -981,7 +1030,12 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 981 |
raise ValueError(
|
| 982 |
"infer_protein always returns a mapping; return_dict=False is invalid."
|
| 983 |
)
|
| 984 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 985 |
if not self.config.msa_conditioning:
|
| 986 |
for name in MSA_CONDITIONING_INPUT_NAMES:
|
| 987 |
features.pop(name, None)
|
|
@@ -1011,6 +1065,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 1011 |
max_inference_sigma: int | None = None,
|
| 1012 |
early_exit: bool = False,
|
| 1013 |
complex_id: str = "pred",
|
|
|
|
| 1014 |
):
|
| 1015 |
return self.input_builder.fold(
|
| 1016 |
self,
|
|
@@ -1024,6 +1079,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 1024 |
max_inference_sigma=max_inference_sigma,
|
| 1025 |
early_exit=early_exit,
|
| 1026 |
complex_id=complex_id,
|
|
|
|
| 1027 |
)
|
| 1028 |
|
| 1029 |
def fold_protein(
|
|
@@ -1036,6 +1092,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 1036 |
num_diffusion_samples: int = 1,
|
| 1037 |
seed: int | None = None,
|
| 1038 |
complex_id: str = "pred",
|
|
|
|
| 1039 |
):
|
| 1040 |
from .esmfold2_types import ProteinInput, StructurePredictionInput
|
| 1041 |
|
|
@@ -1047,6 +1104,7 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
|
|
| 1047 |
num_diffusion_samples=num_diffusion_samples,
|
| 1048 |
seed=seed,
|
| 1049 |
complex_id=complex_id,
|
|
|
|
| 1050 |
)
|
| 1051 |
|
| 1052 |
@staticmethod
|
|
|
|
| 9 |
from __future__ import annotations
|
| 10 |
|
| 11 |
import gc
|
| 12 |
+
from collections.abc import Mapping
|
| 13 |
from pathlib import Path
|
| 14 |
from typing import Any, ClassVar, cast
|
| 15 |
|
|
|
|
| 17 |
import torch.nn as nn
|
| 18 |
import torch.nn.functional as F
|
| 19 |
from torch import Tensor
|
| 20 |
+
from tqdm.auto import tqdm
|
| 21 |
from transformers.modeling_utils import PreTrainedModel
|
| 22 |
|
| 23 |
from .attention import ESMFold2AttentionMixin
|
|
|
|
| 571 |
precision=precision,
|
| 572 |
device=device,
|
| 573 |
local_files_only=(
|
| 574 |
+
self._esmc_local_files_only if local_files_only is None else local_files_only
|
|
|
|
|
|
|
| 575 |
),
|
| 576 |
)
|
| 577 |
|
|
|
|
| 627 |
residue_index: Tensor,
|
| 628 |
mol_type: Tensor,
|
| 629 |
tok_mask: Tensor,
|
| 630 |
+
verbose: bool = False,
|
| 631 |
) -> Tensor:
|
| 632 |
if self._esmc_fp8 and torch.is_grad_enabled():
|
| 633 |
_reload_esmc_bf16_for_gradients(
|
|
|
|
| 641 |
raise RuntimeError("ESMFold2 language-model features require load_esmc=True.")
|
| 642 |
pad_to = 16 if self._esmc_fp8 else None
|
| 643 |
with _lm_precision_context(self._esmc_precision_status.resolved, self.device):
|
| 644 |
+
if verbose:
|
| 645 |
+
with tqdm(total=1, desc="ESMFold2 backbone", unit="stage") as progress:
|
| 646 |
+
result = compute_lm_hidden_states(
|
| 647 |
+
self._esmc,
|
| 648 |
+
input_ids,
|
| 649 |
+
asym_id,
|
| 650 |
+
residue_index,
|
| 651 |
+
mol_type,
|
| 652 |
+
tok_mask,
|
| 653 |
+
pad_to_multiple=pad_to,
|
| 654 |
+
)
|
| 655 |
+
progress.update()
|
| 656 |
+
return result
|
| 657 |
return compute_lm_hidden_states(
|
| 658 |
self._esmc,
|
| 659 |
input_ids,
|
|
|
|
| 710 |
frames_idx: Tensor | None = None,
|
| 711 |
disto_cond: Tensor | None = None,
|
| 712 |
disto_cond_mask: Tensor | None = None,
|
| 713 |
+
verbose: bool = False,
|
| 714 |
) -> ESMFold2Output | tuple[Any, ...]:
|
| 715 |
output_hidden_states, return_dict = _resolve_structure_output_controls(
|
| 716 |
self.config,
|
|
|
|
| 811 |
|
| 812 |
if lm_hidden_states is None and input_ids is not None and self._esmc is not None:
|
| 813 |
lm_hidden_states = self._compute_lm_hidden_states(
|
| 814 |
+
input_ids, asym_id, residue_index, mol_type, tok_mask, verbose=verbose
|
| 815 |
)
|
| 816 |
if lm_hidden_states is not None:
|
| 817 |
lm_dropout = (
|
|
|
|
| 860 |
z = torch.zeros_like(z_init)
|
| 861 |
prev_pair: Tensor | None = None
|
| 862 |
prev_disto_probs: Tensor | None = None
|
| 863 |
+
loop_iterator = range(n_loops + 1)
|
| 864 |
+
if verbose:
|
| 865 |
+
loop_iterator = tqdm(
|
| 866 |
+
loop_iterator,
|
| 867 |
+
total=n_loops + 1 if not early_exit else None,
|
| 868 |
+
desc="ESMFold2 recycling",
|
| 869 |
+
unit="loop",
|
| 870 |
+
)
|
| 871 |
+
|
| 872 |
+
for loop_num in loop_iterator:
|
| 873 |
z = z_init + self.pair_loop_proj(z)
|
| 874 |
if msa_kwargs is not None and self.msa_encoder is not None:
|
| 875 |
z = z + self.msa_encoder(x_pair=z, **msa_kwargs).to(z.dtype)
|
|
|
|
| 923 |
step_scale=step_scale,
|
| 924 |
return_atom_repr=False,
|
| 925 |
denoising_early_exit_rmsd=(0.10 if early_exit else None),
|
| 926 |
+
verbose=verbose,
|
| 927 |
)
|
| 928 |
sample_coords = structure_output["sample_atom_coords"]
|
| 929 |
if sample_coords is None:
|
|
|
|
| 949 |
"sample_atom_coords": sample_coords,
|
| 950 |
"representative_atom_coords": representative_atom_coords,
|
| 951 |
}
|
| 952 |
+
confidence_config = self.config.confidence_head
|
| 953 |
+
confidence_enabled = (
|
| 954 |
+
confidence_config.get("enabled", True)
|
| 955 |
+
if isinstance(confidence_config, Mapping)
|
| 956 |
+
else getattr(confidence_config, "enabled", True)
|
| 957 |
+
)
|
| 958 |
+
if calculate_confidence and confidence_enabled and self.confidence_head is not None:
|
| 959 |
+
if verbose:
|
| 960 |
+
with tqdm(total=1, desc="ESMFold2 confidence", unit="stage") as progress:
|
| 961 |
+
confidence_output = self.confidence_head(
|
| 962 |
+
s_inputs=x_inputs.detach(),
|
| 963 |
+
z=z.detach().float(),
|
| 964 |
+
x_pred=sample_coords.detach(),
|
| 965 |
+
distogram_atom_idx=distogram_atom_idx,
|
| 966 |
+
token_attention_mask=tok_mask,
|
| 967 |
+
atom_to_token=atom_to_token,
|
| 968 |
+
atom_attention_mask=atm_mask,
|
| 969 |
+
asym_id=asym_id,
|
| 970 |
+
mol_type=mol_type,
|
| 971 |
+
num_diffusion_samples=n_samples,
|
| 972 |
+
relative_position_encoding=relative_position_encoding.detach(),
|
| 973 |
+
token_bonds_encoding=token_bonds_encoding.detach(),
|
| 974 |
+
)
|
| 975 |
+
progress.update()
|
| 976 |
+
else:
|
| 977 |
+
confidence_output = self.confidence_head(
|
| 978 |
+
s_inputs=x_inputs.detach(),
|
| 979 |
+
z=z.detach().float(),
|
| 980 |
+
x_pred=sample_coords.detach(),
|
| 981 |
+
distogram_atom_idx=distogram_atom_idx,
|
| 982 |
+
token_attention_mask=tok_mask,
|
| 983 |
+
atom_to_token=atom_to_token,
|
| 984 |
+
atom_attention_mask=atm_mask,
|
| 985 |
+
asym_id=asym_id,
|
| 986 |
+
mol_type=mol_type,
|
| 987 |
+
num_diffusion_samples=n_samples,
|
| 988 |
+
relative_position_encoding=relative_position_encoding.detach(),
|
| 989 |
+
token_bonds_encoding=token_bonds_encoding.detach(),
|
| 990 |
+
)
|
| 991 |
output.update(confidence_output)
|
| 992 |
output["atom_pad_mask"] = atm_mask.unsqueeze(0) if atm_mask.dim() == 1 else atm_mask
|
| 993 |
output["residue_index"] = residue_index
|
|
|
|
| 1030 |
raise ValueError(
|
| 1031 |
"infer_protein always returns a mapping; return_dict=False is invalid."
|
| 1032 |
)
|
| 1033 |
+
if forward_kwargs.get("verbose", False):
|
| 1034 |
+
with tqdm(total=1, desc="ESMFold2 features", unit="stage") as progress:
|
| 1035 |
+
features = prepare_protein_features(seq)
|
| 1036 |
+
progress.update()
|
| 1037 |
+
else:
|
| 1038 |
+
features = prepare_protein_features(seq)
|
| 1039 |
if not self.config.msa_conditioning:
|
| 1040 |
for name in MSA_CONDITIONING_INPUT_NAMES:
|
| 1041 |
features.pop(name, None)
|
|
|
|
| 1065 |
max_inference_sigma: int | None = None,
|
| 1066 |
early_exit: bool = False,
|
| 1067 |
complex_id: str = "pred",
|
| 1068 |
+
verbose: bool = False,
|
| 1069 |
):
|
| 1070 |
return self.input_builder.fold(
|
| 1071 |
self,
|
|
|
|
| 1079 |
max_inference_sigma=max_inference_sigma,
|
| 1080 |
early_exit=early_exit,
|
| 1081 |
complex_id=complex_id,
|
| 1082 |
+
verbose=verbose,
|
| 1083 |
)
|
| 1084 |
|
| 1085 |
def fold_protein(
|
|
|
|
| 1092 |
num_diffusion_samples: int = 1,
|
| 1093 |
seed: int | None = None,
|
| 1094 |
complex_id: str = "pred",
|
| 1095 |
+
verbose: bool = False,
|
| 1096 |
):
|
| 1097 |
from .esmfold2_types import ProteinInput, StructurePredictionInput
|
| 1098 |
|
|
|
|
| 1104 |
num_diffusion_samples=num_diffusion_samples,
|
| 1105 |
seed=seed,
|
| 1106 |
complex_id=complex_id,
|
| 1107 |
+
verbose=verbose,
|
| 1108 |
)
|
| 1109 |
|
| 1110 |
@staticmethod
|
fastplms/registry.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
fastplms_bundle.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
modeling_fastplms.py
CHANGED
|
@@ -13,7 +13,7 @@ from zipfile import ZIP_DEFLATED, ZipFile
|
|
| 13 |
|
| 14 |
from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
|
| 15 |
|
| 16 |
-
if RUNTIME_HASH != "
|
| 17 |
raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
|
| 18 |
|
| 19 |
_RUNTIME_TEMPORARIES = []
|
|
|
|
| 13 |
|
| 14 |
from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
|
| 15 |
|
| 16 |
+
if RUNTIME_HASH != "4f6a9d59126e9bee9e65b30d55161d648473a058b6aed36727f2ae2cd99d44d7":
|
| 17 |
raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
|
| 18 |
|
| 19 |
_RUNTIME_TEMPORARIES = []
|