lhallee commited on
Commit
bf62188
·
verified ·
1 Parent(s): 8cbb1a4

Update FastPLMs runtime and model cards

Browse files
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
- The release contract requires a CUDA device. The current validated target is
45
- the exact NVIDIA GH200 on Linux aarch64. Linux x86-64, CPU-only, Windows, and
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 provenance
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, all four variants, and three BF16-to-FP8 reload cycles on the standard variant. Limitation: only the four 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,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
- official_repo, fast_repo = _esmc_backbone_checkpoint_ids()
48
- return fast_repo if esmc_id == official_repo else esmc_id
49
-
50
-
51
- def normalize_esmc_attention_implementation(
52
- implementation: str | dict[str, str] | None,
53
- ) -> str | None:
54
- """Validate the ESMC backend and translate the historical ``flex`` name."""
55
-
56
- if isinstance(implementation, dict):
57
- if tuple(implementation) != ("",):
58
- raise ValueError(
59
- "ESMFold2 has one ESMC attention backbone; use a string or {'': implementation}."
60
- )
61
- implementation = implementation[""]
62
- canonical = canonical_checkpoint_attention_backend(implementation)
63
- if canonical is not None and canonical not in _ESMC_ATTENTION_IMPLEMENTATIONS:
64
- expected = sorted(_ESMC_ATTENTION_IMPLEMENTATIONS)
65
- raise ValueError(
66
- f"Unsupported ESMFold2 attention implementation {canonical!r}; "
67
- f"expected one of {expected}."
68
- )
69
- return canonical
70
-
71
-
72
- NestedConfig = TypeVar("NestedConfig")
73
-
74
-
75
- def _nested_config(value: Any, config_type: type[NestedConfig]) -> NestedConfig:
76
- if isinstance(value, config_type):
77
- return value
78
- return config_type(**value) if isinstance(value, dict) else config_type()
79
-
80
-
81
- def _coerce_nested_field(
82
- value: NestedConfig | dict[str, Any], config_type: type[NestedConfig]
83
- ) -> NestedConfig:
84
- """Convert serialized nested dictionaries while retaining supplied objects."""
85
-
86
- return config_type(**value) if isinstance(value, dict) else value
87
-
88
-
89
- @dataclass
90
- class AtomAttentionConfig:
91
- """Sliding-window atom attention and three-dimensional RoPE settings."""
92
-
93
- d_atom: int = field(default=128)
94
- d_token: int = field(default=768)
95
- n_blocks: int = field(default=3)
96
- n_heads: int = field(default=4)
97
- swa_window_size: int = field(default=128)
98
- expansion_ratio: int = field(default=2)
99
- spatial_rope_base_frequency: float = field(default=20.0)
100
- n_spatial_rope_pairs_per_axis: int = field(default=2)
101
- n_uid_rope_pairs: int = field(default=10)
102
- uid_rope_base_frequency: float = field(default=10000.0)
103
-
104
-
105
- @dataclass
106
- class DiffusionModuleConfig:
107
- """Dimensions and depth of the coordinate diffusion network."""
108
-
109
- sigma_data: float = field(default=16.0)
110
- c_atom: int = field(default=128)
111
- c_token: int = field(default=768)
112
- c_z: int = field(default=256)
113
- c_s_inputs: int = field(default=451)
114
- fourier_dim: int = field(default=256)
115
- relpos_r_max: int = field(default=32)
116
- relpos_s_max: int = field(default=2)
117
- atom_num_blocks: int = field(default=3)
118
- atom_num_heads: int = field(default=4)
119
- token_num_blocks: int = field(default=12)
120
- token_num_heads: int = field(default=16)
121
- transition_multiplier: int = field(default=2)
122
-
123
-
124
- @dataclass
125
- class FoldingTrunkConfig:
126
- """Iterative pair/single trunk dimensions."""
127
-
128
- n_layers: int = field(default=24)
129
- n_heads: int = field(default=8)
130
- dropout: float = field(default=0.0)
131
-
132
-
133
- @dataclass
134
- class InputsEmbedderConfig:
135
- """Input feature width and atom encoder settings."""
136
-
137
- d_inputs: int = field(default=451)
138
- atom_encoder: AtomAttentionConfig = field(default_factory=AtomAttentionConfig)
139
-
140
- def __post_init__(self) -> None:
141
- self.atom_encoder = _coerce_nested_field(self.atom_encoder, AtomAttentionConfig)
142
-
143
-
144
- @dataclass
145
- class DiffusionStructureHeadConfig:
146
- """Training and inference schedules for coordinate denoising."""
147
-
148
- diffusion_module: DiffusionModuleConfig = field(default_factory=DiffusionModuleConfig)
149
- distogram_bins: int = field(default=128)
150
- train_noise_log_mean: float = field(default=-1.2)
151
- train_noise_log_std: float = field(default=1.5)
152
- gamma_0: float = field(default=0.605)
153
- gamma_min: float = field(default=1.107)
154
- noise_scale: float = field(default=0.0)
155
- step_scale: float = field(default=1.0)
156
- inference_s_max: float = field(default=160.0)
157
- inference_s_min: float = field(default=4e-4)
158
- inference_p: float = field(default=8.0)
159
- inference_num_steps: int = field(default=68)
160
-
161
- def __post_init__(self) -> None:
162
- self.diffusion_module = _coerce_nested_field(self.diffusion_module, DiffusionModuleConfig)
163
-
164
-
165
- @dataclass
166
- class ConfidenceHeadConfig:
167
- """Confidence-bin definitions and the compact confidence trunk."""
168
-
169
- enabled: bool = field(default=True)
170
- num_plddt_bins: int = field(default=50)
171
- num_pde_bins: int = field(default=64)
172
- num_pae_bins: int = field(default=64)
173
- min_dist: float = field(default=2.0)
174
- max_dist: float = field(default=52.0)
175
- distogram_bins: int = field(default=128)
176
- folding_trunk: FoldingTrunkConfig = field(
177
- default_factory=lambda: FoldingTrunkConfig(n_layers=4)
178
- )
179
-
180
- def __post_init__(self) -> None:
181
- self.folding_trunk = _coerce_nested_field(self.folding_trunk, FoldingTrunkConfig)
182
-
183
-
184
- @dataclass
185
- class MSAEncoderConfig:
186
- """Optional multiple-sequence-alignment encoder settings."""
187
-
188
- enabled: bool = field(default=False)
189
- d_msa: int = field(default=128)
190
- d_hidden: int = field(default=32)
191
- n_layers: int = field(default=4)
192
- n_heads_msa: int = field(default=8)
193
- msa_head_width: int = field(default=32)
194
-
195
-
196
- @dataclass
197
- class LMEncoderConfig:
198
- """Release-model pair encoder derived from language-model states."""
199
-
200
- enabled: bool = field(default=True)
201
- n_layers: int = field(default=4)
202
- lm_dropout: float = field(default=0.25)
203
- per_loop_lm_dropout: bool = field(default=True)
204
-
205
-
206
- @dataclass
207
- class ParcaeConfig:
208
- """Release-model diffusion-loop scheduler settings."""
209
-
210
- enabled: bool = field(default=True)
211
- poisson_mean: float = field(default=3.0)
212
- min_steps: int = field(default=1)
213
- max_steps: int | None = field(default=6)
214
- coda_n_layers: int = field(default=2)
215
-
216
-
217
- _SCALAR_DEFAULTS: tuple[tuple[str, Any], ...] = (
218
- ("d_single", 384),
219
- ("d_pair", 256),
220
- ("n_relative_residx_bins", 32),
221
- ("n_relative_chain_bins", 2),
222
- ("num_loops", 10),
223
- ("num_diffusion_samples", 8),
224
- ("disable_msa_features", False),
225
- ("lm_dropout", 0.0),
226
- ("force_lm_dropout_during_inference", False),
227
- ("lm_mask_pct", 0.0),
228
- ("lm_d_model", 2560),
229
- ("lm_num_layers", 80),
230
- )
231
- _NESTED_CONFIGS = (
232
- ("inputs", InputsEmbedderConfig),
233
- ("folding_trunk", FoldingTrunkConfig),
234
- ("structure_head", DiffusionStructureHeadConfig),
235
- ("confidence_head", ConfidenceHeadConfig),
236
- ("msa_encoder", MSAEncoderConfig),
237
- ("parcae", ParcaeConfig),
238
- ("lm_encoder", LMEncoderConfig),
239
- )
240
-
241
-
242
- class ESMFold2Config(PretrainedConfig):
243
- """Serializable ESMFold2 architecture, runtime, and precision settings."""
244
-
245
- model_type = "esmfold2"
246
- has_no_defaults_at_init = True
247
-
248
- def __init__(self, **kwargs: Any) -> None:
249
- legacy_backend = normalize_esmc_attention_implementation(kwargs.get("esmc_attn_backend"))
250
- requested_backend = normalize_esmc_attention_implementation(
251
- kwargs.get("attn_implementation")
252
- )
253
- resolved_backend = requested_backend or legacy_backend
254
- kwargs["attn_implementation"] = resolved_backend
255
- super().__init__(**kwargs)
256
-
257
- self.type = kwargs.get("type", "release")
258
- if self.type not in {"experimental", "release"}:
259
- raise ValueError(
260
- f"ESMFold2Config.type must be 'release' or 'experimental', got {self.type!r}"
261
- )
262
-
263
- for name, default in _SCALAR_DEFAULTS:
264
- setattr(self, name, kwargs.get(name, default))
265
-
266
- _official_esmc_repo, default_esmc_repo = _esmc_backbone_checkpoint_ids()
267
- self.esmc_id = normalize_esmc_id(kwargs.get("esmc_id", default_esmc_repo))
268
- self.esmc_attn_backend = resolved_backend
269
- self.esmc_precision = str(kwargs.get("esmc_precision", "auto"))
270
- if self.esmc_precision not in _ESMC_PRECISIONS:
271
- raise ValueError(
272
- "esmc_precision must be 'auto', 'bf16', 'fp32', or 'fp8', "
273
- f"got {self.esmc_precision!r}."
274
- )
275
-
276
- for name, config_type in _NESTED_CONFIGS:
277
- setattr(self, name, _nested_config(kwargs.get(name), config_type))
278
- if not isinstance(self.msa_encoder.enabled, bool):
279
- raise TypeError("msa_encoder.enabled must be a boolean.")
280
- declared_msa_conditioning = kwargs.get("msa_conditioning")
281
- if "msa_conditioning" in kwargs and not isinstance(declared_msa_conditioning, bool):
282
- raise TypeError("msa_conditioning must be a boolean when provided.")
283
- self.msa_conditioning = (
284
- self.msa_encoder.enabled
285
- if "msa_conditioning" not in kwargs
286
- else declared_msa_conditioning
287
- )
288
- if self.msa_conditioning != self.msa_encoder.enabled:
289
- raise ValueError(
290
- "msa_conditioning must match msa_encoder.enabled; received "
291
- f"{self.msa_conditioning!r} and {self.msa_encoder.enabled!r}."
292
- )
293
- self.msa_encoder_overwrite = bool(kwargs.get("msa_encoder_overwrite", True))
294
-
295
- self.classifier_train_scope = str(kwargs.get("classifier_train_scope", "probe"))
296
- if self.classifier_train_scope not in {"probe", "projection"}:
297
- raise ValueError(
298
- "classifier_train_scope must be 'probe' or 'projection', "
299
- f"got {self.classifier_train_scope!r}."
300
- )
301
- self.classifier_probe_hidden_size = int(
302
- kwargs.get("classifier_probe_hidden_size", 512)
303
- )
304
- self.classifier_probe_num_heads = int(kwargs.get("classifier_probe_num_heads", 4))
305
- self.classifier_probe_dropout = float(kwargs.get("classifier_probe_dropout", 0.1))
306
- self.classifier_hidden_size = int(kwargs.get("classifier_hidden_size", 4096))
307
- self.classifier_dropout = float(kwargs.get("classifier_dropout", 0.2))
308
- self.classifier_pooling_types = list(
309
- kwargs.get("classifier_pooling_types", ["mean"])
310
- )
311
-
312
- def to_dict(self) -> dict[str, Any]:
313
- output = cast(dict[str, Any], super().to_dict())
314
- for name, _config_type in _NESTED_CONFIGS:
315
- output[name] = asdict(getattr(self, name))
316
- return output
317
-
318
-
319
- __all__ = [
320
- "ESMFold2Config",
321
- "LMEncoderConfig",
322
- "MSAEncoderConfig",
323
- "ParcaeConfig",
324
- "normalize_esmc_attention_implementation",
325
- "normalize_esmc_id",
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 0.0
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=float(np.mean([confidence[token.token_index] for token in residue_tokens])),
 
 
 
 
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
- plddt = output["plddt"][sample]
 
 
 
 
250
  molecular_complex = build_molecular_complex_from_features(
251
- coords=output["sample_atom_coords"][sample],
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(output["sample_atom_coords"].shape[0])
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
- features, chain_infos = self.prepare_model_input(
313
- model,
314
- input,
315
- seed=seed,
316
- device=model.device,
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
- for step_idx, (sigma_tm, sigma_t, gamma) in enumerate(step_pairs):
 
 
 
 
 
 
 
 
 
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
- for loop_num in range(n_loops + 1):
 
 
 
 
 
 
 
 
 
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
- if calculate_confidence and self.confidence_head is not None:
928
- confidence_output = self.confidence_head(
929
- s_inputs=x_inputs.detach(),
930
- z=z.detach().float(),
931
- x_pred=sample_coords.detach(),
932
- distogram_atom_idx=distogram_atom_idx,
933
- token_attention_mask=tok_mask,
934
- atom_to_token=atom_to_token,
935
- atom_attention_mask=atm_mask,
936
- asym_id=asym_id,
937
- mol_type=mol_type,
938
- num_diffusion_samples=n_samples,
939
- relative_position_encoding=relative_position_encoding.detach(),
940
- token_bonds_encoding=token_bonds_encoding.detach(),
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
- features = prepare_protein_features(seq)
 
 
 
 
 
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 != "c76f023da79d5db93093291f4f3858a0e6cde4d9b4fa61ade779cf941f2175da":
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 = []