lhallee commited on
Commit
77a9c3b
·
verified ·
1 Parent(s): e1f5b73

Update FastPLMs files

Browse files
README.md CHANGED
@@ -14,15 +14,16 @@ This checkpoint contains the FastPLMs `ESMFold2` implementation.
14
 
15
  Accepted inputs are raw amino-acid sequences or typed molecular-complex
16
  specifications; low-level forward accepts prepared feature tensors.
17
- Supported Transformers entry points are `AutoConfig`, `AutoModel`.
 
18
 
19
  ## Capabilities
20
 
21
  | Feature | Status |
22
  | --- | --- |
23
- | Sequence classification | Unavailable: no advertised AutoClass |
24
- | Token classification | Unavailable: no advertised AutoClass |
25
- | PEFT fine-tuning | Supported pattern: attach LoRA to the pretrained model |
26
  | Embeddings | Special: ESMC state mixture to 256-wide residue embeddings |
27
  | Test-time training | Special: opt-in folding TTT on the ESMC backbone |
28
  | Attention variants | Supported: `eager`, `sdpa`, `flex_attention` |
@@ -72,6 +73,43 @@ materialize attention tensors. The configured backend does not change.
72
  This family declares the `compliance` tier. Release evidence identifies the
73
  checkpoint, backend, dtype, hardware, inputs, and reference revision.
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  ## PEFT fine-tuning
76
 
77
  Install the training dependencies. Then attach LoRA to the loaded checkpoint:
@@ -81,20 +119,22 @@ python -m pip install "datasets>=4.8,<5" "peft>=0.19,<0.20"
81
  ```
82
 
83
  ```python
84
- from peft import LoraConfig, get_peft_model
85
 
86
  peft_model = get_peft_model(
87
- model,
88
  LoraConfig(
 
89
  r=8,
90
  lora_alpha=16,
91
  target_modules="all-linear",
 
92
  ),
93
  )
94
  ```
95
 
96
- This checkpoint has no advertised classifier. Supply the task objective and
97
- preserve any new head through `modules_to_save`.
98
  All FastPLMs checkpoints follow the Transformers `PreTrainedModel` contract and
99
  can use PEFT. The ESM2-specific shipped CLI is an example, not a
100
  support boundary. Record the target modules, base revision, data identity, and
@@ -149,13 +189,15 @@ complex_result = model.fold(
149
  print(complex_result.ptm, complex_result.plddt.mean().item())
150
  ```
151
 
152
- The typed interface also supports RNA, modifications, covalent bonds, and
153
- distogram conditioning. Protein MSA inputs are not supported by this Fast
154
- checkpoint; every protein chain must use `msa=None`. The public schema recognizes
155
- `PocketConditioning`, but the pinned official runtime discards it and hard-codes
156
- a zero pocket feature. FastPLMs therefore rejects non-null pocket conditioning
157
- instead of silently ignoring it. Prepared `ref_pos` values are component
158
- reference geometries created during featurization, not target coordinates.
 
 
159
  Predicted coordinates and confidence scores are outputs and do not establish
160
  biochemical activity.
161
 
@@ -208,19 +250,15 @@ and
208
  [release evidence manifest](https://github.com/Synthyra/FastPLMs/blob/main/docs/generated/capability_evidence.md).
209
 
210
 
211
- ## Hash-pinned CCD runtime asset
212
 
213
  Structure preparation requires `ccd.pkl` from
214
- `biohub/ESMFold2`. The manifest pins
215
- its 417,306,584-byte size and SHA-256
216
- `9ff44b1927c6b9198e38ffe0928706827a09a350c15530beeeabebfa88038fc5`
217
- under MIT terms. This is a trusted-deserialization boundary: FastPLMs only
218
- allows the exact manifest repository/revision snapshot link to resolve within
219
- that repository's contained blob directory; user-supplied asset and `cache_dir`
220
- symlinks are rejected. The loader creates a private temporary snapshot, verifies
221
- its size and SHA-256, and unpickles only that loader-owned snapshot, closing
222
- path-replacement and in-place source-write races. Offline execution requires the
223
- exact cache object and never downloads a replacement.
224
 
225
  ## Optional folding TTT
226
 
@@ -247,8 +285,8 @@ adapter modules are excluded from checkpoint state. It is not a generic
247
  ## Runtime contract
248
 
249
  - Public input: Raw amino-acid sequences or typed molecular-complex specifications; low-level forward accepts prepared feature tensors
250
- - Advertised AutoClasses: `AutoConfig`, `AutoModel`
251
- - AutoClass weight status: `AutoConfig` = `FastPLMs extension`, `AutoModel` = `pretrained`
252
  - Attention implementations: `eager`, `sdpa`, `flex_attention`
253
  - Precision policies: `auto`, `fp32`, `bf16`, `fp8` (experimental)
254
  - BF16 execution: `fp32_parameters_autocast`
@@ -262,8 +300,8 @@ adapter modules are excluded from checkpoint state. It is not a generic
262
  ## Release record
263
 
264
  - FastPLMs weights: `Synthyra/ESMFold2-Fast`
265
- - Runtime revision: recorded in the built artifact and published commit
266
- - Source-tree and runtime-bundle SHA-256: recorded in the source record
267
  - Official checkpoint: `biohub/ESMFold2-Fast`
268
  - Artifact source: `fast`
269
  - State transform: `identity`
 
14
 
15
  Accepted inputs are raw amino-acid sequences or typed molecular-complex
16
  specifications; low-level forward accepts prepared feature tensors.
17
+ Supported Transformers entry points are `AutoConfig`, `AutoModel`,
18
+ `AutoModelForSequenceClassification`, `AutoModelForTokenClassification`.
19
 
20
  ## Capabilities
21
 
22
  | Feature | Status |
23
  | --- | --- |
24
+ | Sequence classification | Supported: base weights with an untrained task head |
25
+ | Token classification | Supported: base weights with an untrained task head |
26
+ | PEFT fine-tuning | Supported pattern: preserve the separately trained `classifier` |
27
  | Embeddings | Special: ESMC state mixture to 256-wide residue embeddings |
28
  | Test-time training | Special: opt-in folding TTT on the ESMC backbone |
29
  | Attention variants | Supported: `eager`, `sdpa`, `flex_attention` |
 
73
  This family declares the `compliance` tier. Release evidence identifies the
74
  checkpoint, backend, dtype, hardware, inputs, and reference revision.
75
 
76
+ ## Downstream prediction
77
+
78
+ The sequence and token prediction AutoClasses use the checkpoint backbone and
79
+ create a new, untrained `classifier`. Sequence labels have shape `(b,)`.
80
+ Residue labels have shape `(b, l)` and use `-100` outside biological positions.
81
+ The folding trunk is skipped. The classifier uses the checkpoint's learned pLM
82
+ state mixture and projection, followed by one trainable transformer probe.
83
+
84
+ ```python
85
+ import torch
86
+ from transformers import (
87
+ AutoModelForSequenceClassification,
88
+ AutoModelForTokenClassification,
89
+ )
90
+
91
+ model_id = "Synthyra/ESMFold2-Fast"
92
+ sequence_model = AutoModelForSequenceClassification.from_pretrained(
93
+ model_id, num_labels=2, trust_remote_code=True
94
+ ).eval()
95
+ token_model = AutoModelForTokenClassification.from_pretrained(
96
+ model_id, num_labels=3, trust_remote_code=True
97
+ ).eval()
98
+ sequences = ["MSTNPKPQRKTKRNT", "MKTIIALSYIFCLVFA"]
99
+ batch = sequence_model.prepare_classifier_inputs(sequences)
100
+ biological = batch["attention_mask"].bool()
101
+
102
+ sequence_labels = torch.zeros(len(sequences), dtype=torch.long)
103
+ token_labels = torch.full_like(batch["input_ids"], -100)
104
+ token_labels[biological] = 0
105
+
106
+ with torch.inference_mode():
107
+ sequence_output = sequence_model(**batch, labels=sequence_labels)
108
+ token_output = token_model(**batch, labels=token_labels)
109
+ print(sequence_output.logits.shape) # (b, 2)
110
+ print(token_output.logits.shape) # (b, l, 3)
111
+ ```
112
+
113
  ## PEFT fine-tuning
114
 
115
  Install the training dependencies. Then attach LoRA to the loaded checkpoint:
 
119
  ```
120
 
121
  ```python
122
+ from peft import LoraConfig, TaskType, get_peft_model
123
 
124
  peft_model = get_peft_model(
125
+ sequence_model,
126
  LoraConfig(
127
+ task_type=TaskType.SEQ_CLS,
128
  r=8,
129
  lora_alpha=16,
130
  target_modules="all-linear",
131
+ modules_to_save=["classifier"],
132
  ),
133
  )
134
  ```
135
 
136
+ This checkpoint advertises a classification head. Save the separately trained
137
+ `classifier` with the adapter.
138
  All FastPLMs checkpoints follow the Transformers `PreTrainedModel` contract and
139
  can use PEFT. The ESM2-specific shipped CLI is an example, not a
140
  support boundary. Record the target modules, base revision, data identity, and
 
189
  print(complex_result.ptm, complex_result.plddt.mean().item())
190
  ```
191
 
192
+ The typed interface also supports RNA, modifications, and covalent bonds.
193
+ Protein MSA inputs are not supported by this Fast checkpoint; every protein
194
+ chain must use `msa=None`. The public schema recognizes `PocketConditioning` and
195
+ `DistogramConditioning`, but the pinned official forward consumes neither. Its
196
+ feature builder hard-codes a zero pocket feature and constructs distogram tensors
197
+ that the released model ignores. FastPLMs therefore rejects non-null pocket and
198
+ distogram conditioning instead of silently ignoring scientific inputs. Prepared
199
+ `ref_pos` values are component reference geometries created during featurization,
200
+ not target coordinates.
201
  Predicted coordinates and confidence scores are outputs and do not establish
202
  biochemical activity.
203
 
 
250
  [release evidence manifest](https://github.com/Synthyra/FastPLMs/blob/main/docs/generated/capability_evidence.md).
251
 
252
 
253
+ ## Verified CCD runtime asset
254
 
255
  Structure preparation requires `ccd.pkl` from
256
+ `biohub/ESMFold2`. The manifest pins its repository, revision, size, content
257
+ identity, and MIT terms. This is a trusted-deserialization boundary. FastPLMs
258
+ accepts only the pinned snapshot link inside the repository blob directory and
259
+ rejects user-supplied asset and `cache_dir` symlinks. The loader verifies a
260
+ private temporary snapshot before deserialization. Offline execution requires
261
+ the exact cached object and never downloads a replacement.
 
 
 
 
262
 
263
  ## Optional folding TTT
264
 
 
285
  ## Runtime contract
286
 
287
  - Public input: Raw amino-acid sequences or typed molecular-complex specifications; low-level forward accepts prepared feature tensors
288
+ - Advertised AutoClasses: `AutoConfig`, `AutoModel`, `AutoModelForSequenceClassification`, `AutoModelForTokenClassification`
289
+ - AutoClass weight status: `AutoConfig` = `FastPLMs extension`, `AutoModel` = `pretrained`, `AutoModelForSequenceClassification` = `base weights + untrained task head`, `AutoModelForTokenClassification` = `base weights + untrained task head`
290
  - Attention implementations: `eager`, `sdpa`, `flex_attention`
291
  - Precision policies: `auto`, `fp32`, `bf16`, `fp8` (experimental)
292
  - BF16 execution: `fp32_parameters_autocast`
 
300
  ## Release record
301
 
302
  - FastPLMs weights: `Synthyra/ESMFold2-Fast`
303
+ - Runtime revision: recorded separately in the built artifact and published commit
304
+ - Runtime source identities: recorded in `source-record.json`
305
  - Official checkpoint: `biohub/ESMFold2-Fast`
306
  - Artifact source: `fast`
307
  - State transform: `identity`
fastplms/models.toml CHANGED
@@ -199,7 +199,7 @@ representative = "esmc_small"
199
  documentation = "docs/models.md#esm-and-esmc"
200
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
201
  runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/esm_plusplus", "models/ttt.py"]
202
- auto_map = { AutoConfig = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusConfig", AutoModel = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusModel", AutoModelForMaskedLM = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusForMaskedLM" }
203
 
204
  [families.esm3]
205
  architecture = "ESM3"
@@ -223,7 +223,7 @@ representative = "esm3_small"
223
  documentation = "docs/models.md#esm3"
224
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
225
  runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/esm3", "models/ttt.py"]
226
- auto_map = { AutoConfig = "fastplms.models.esm3.modeling_esm3.FastESM3Config", AutoModel = "fastplms.models.esm3.modeling_esm3.FastESM3Model" }
227
 
228
  [families.e1]
229
  architecture = "E1"
@@ -370,8 +370,8 @@ conversion_provenance = "Input: the pinned native Meta ESMFold checkpoint plus i
370
  representative = "esmfold"
371
  documentation = "docs/models.md#esmfold"
372
  test_tiers = ["check", "compliance", "structure", "feature", "artifact", "benchmark"]
373
- runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/_esm_rotary.py", "models/esmfold"]
374
- auto_map = { AutoConfig = "fastplms.models.esmfold.modeling_fast_esmfold.FastEsmFoldConfig", AutoModel = "fastplms.models.esmfold.modeling_fast_esmfold.FastEsmForProteinFolding" }
375
 
376
  [families.esmfold2]
377
  architecture = "ESMFold2"
@@ -396,8 +396,8 @@ conversion_provenance = "Input: each pinned Biohub ESMFold2 checkpoint and its s
396
  representative = "esmfold2"
397
  documentation = "docs/esmfold2.md"
398
  test_tiers = ["check", "compliance", "structure", "feature", "artifact", "benchmark"]
399
- runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/esmfold2", "models/esm_plusplus", "models/ttt.py"]
400
- auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2.ESMFold2Model" }
401
 
402
  [[models]]
403
  id = "esm2_8m"
@@ -1215,7 +1215,7 @@ official_files = [
1215
  "config.json=git-sha1:79ed0dc0f867b8f09bfa004d6f77397c2ab9b38d",
1216
  "model.safetensors=sha256:01358c317428d38535e3db513cab177336fc0f7fab0d84002e64b7741d5181b3",
1217
  ]
1218
- auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2_experimental.ESMFold2ExperimentalModel" }
1219
 
1220
  [[models]]
1221
  id = "esmfold2_experimental_fast_cutoff2025"
@@ -1236,4 +1236,4 @@ official_files = [
1236
  "config.json=git-sha1:0333d68ddb12ed2f066741dcb801142f466c0a2c",
1237
  "model.safetensors=sha256:4e903b740ad6ad704ec60881bfd593e0d6c874a630ffa0f0838276e0b665088f",
1238
  ]
1239
- auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2_experimental.ESMFold2ExperimentalModel" }
 
199
  documentation = "docs/models.md#esm-and-esmc"
200
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
201
  runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/esm_plusplus", "models/ttt.py"]
202
+ auto_map = { AutoConfig = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusConfig", AutoModel = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusModel", AutoModelForMaskedLM = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusForMaskedLM", AutoModelForSequenceClassification = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esm_plusplus.modeling_esm_plusplus.ESMplusplusForTokenClassification" }
203
 
204
  [families.esm3]
205
  architecture = "ESM3"
 
223
  documentation = "docs/models.md#esm3"
224
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
225
  runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/esm3", "models/ttt.py"]
226
+ auto_map = { AutoConfig = "fastplms.models.esm3.modeling_esm3.FastESM3Config", AutoModel = "fastplms.models.esm3.modeling_esm3.FastESM3Model", AutoModelForSequenceClassification = "fastplms.models.esm3.modeling_esm3.FastESM3ForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esm3.modeling_esm3.FastESM3ForTokenClassification" }
227
 
228
  [families.e1]
229
  architecture = "E1"
 
370
  representative = "esmfold"
371
  documentation = "docs/models.md#esmfold"
372
  test_tiers = ["check", "compliance", "structure", "feature", "artifact", "benchmark"]
373
+ runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/_esm_rotary.py", "models/classification_probe.py", "models/esmfold"]
374
+ auto_map = { AutoConfig = "fastplms.models.esmfold.modeling_fast_esmfold.FastEsmFoldConfig", AutoModel = "fastplms.models.esmfold.modeling_fast_esmfold.FastEsmForProteinFolding", AutoModelForSequenceClassification = "fastplms.models.esmfold.modeling_fast_esmfold.FastEsmForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esmfold.modeling_fast_esmfold.FastEsmForTokenClassification" }
375
 
376
  [families.esmfold2]
377
  architecture = "ESMFold2"
 
396
  representative = "esmfold2"
397
  documentation = "docs/esmfold2.md"
398
  test_tiers = ["check", "compliance", "structure", "feature", "artifact", "benchmark"]
399
+ runtime_paths = ["__init__.py", "registry.py", "runtime.py", "models.toml", "models/__init__.py", "attention", "embeddings", "models/classification_probe.py", "models/_esm_rotary.py", "models/esmfold2", "models/esm_plusplus", "models/ttt.py"]
400
+ auto_map = { AutoConfig = "fastplms.models.esmfold2.configuration_esmfold2.ESMFold2Config", AutoModel = "fastplms.models.esmfold2.modeling_esmfold2.ESMFold2Model", AutoModelForSequenceClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ForSequenceClassification", AutoModelForTokenClassification = "fastplms.models.esmfold2.modeling_esmfold2_classification.ESMFold2ForTokenClassification" }
401
 
402
  [[models]]
403
  id = "esm2_8m"
 
1215
  "config.json=git-sha1:79ed0dc0f867b8f09bfa004d6f77397c2ab9b38d",
1216
  "model.safetensors=sha256:01358c317428d38535e3db513cab177336fc0f7fab0d84002e64b7741d5181b3",
1217
  ]
1218
+ 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" }
1219
 
1220
  [[models]]
1221
  id = "esmfold2_experimental_fast_cutoff2025"
 
1236
  "config.json=git-sha1:0333d68ddb12ed2f066741dcb801142f466c0a2c",
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" }
fastplms/models/_esm_rotary.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Stable ESM rotary embeddings independent of Transformers internals.
2
+
3
+ Transformers 5 changed both the name and call contract of its private ESM
4
+ rotary helper. FastPLMs checkpoints use the earlier two-tensor contract, so
5
+ the small mathematical primitive lives here instead of importing a private
6
+ Transformers implementation.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import torch
12
+ from torch import nn
13
+
14
+
15
+ def _rotate_half(tensor: torch.Tensor) -> torch.Tensor:
16
+ """Rotate the final dimension of X by 90 degrees in paired subspaces."""
17
+
18
+ # tensor: (..., d)
19
+ first, second = tensor.chunk(2, dim=-1) # (..., d / 2), (..., d / 2)
20
+ return torch.cat((-second, first), dim=-1) # (..., d)
21
+
22
+
23
+ def apply_rotary_pos_emb(
24
+ tensor: torch.Tensor,
25
+ cos: torch.Tensor,
26
+ sin: torch.Tensor,
27
+ ) -> torch.Tensor:
28
+ """Apply cached rotary factors to X with shape ``(b, h, l, d)``."""
29
+
30
+ # tensor: (b, h, l, d); cos, sin: (1, 1, l_cache, d)
31
+ cos = cos[:, :, : tensor.shape[-2], :] # (1, 1, l, d)
32
+ sin = sin[:, :, : tensor.shape[-2], :] # (1, 1, l, d)
33
+ return tensor * cos + _rotate_half(tensor) * sin # (b, h, l, d)
34
+
35
+
36
+ class RotaryEmbedding(nn.Module):
37
+ """Apply rotary position embeddings to query and key tensors."""
38
+
39
+ inv_freq: torch.Tensor
40
+
41
+ def __init__(self, dim: int) -> None:
42
+ super().__init__()
43
+ frequencies = 1.0 / ( # (d / 2,)
44
+ 10_000 ** (torch.arange(0, dim, 2, dtype=torch.int64).float() / dim)
45
+ )
46
+ # Keep this persistent to preserve the historical checkpoint schema.
47
+ self.register_buffer("inv_freq", frequencies)
48
+ self._seq_len_cached: int | None = None
49
+ self._cos_cached: torch.Tensor | None = None
50
+ self._sin_cached: torch.Tensor | None = None
51
+
52
+ def _update_cos_sin_tables(
53
+ self,
54
+ tensor: torch.Tensor,
55
+ seq_dimension: int = 2,
56
+ ) -> tuple[torch.Tensor, torch.Tensor]:
57
+ # tensor: (..., l, d)
58
+ seq_len = tensor.shape[seq_dimension]
59
+ cache_stale = (
60
+ self._cos_cached is None
61
+ or self._sin_cached is None
62
+ or self._seq_len_cached != seq_len
63
+ or self._cos_cached.device != tensor.device
64
+ )
65
+ if cache_stale:
66
+ self._seq_len_cached = seq_len
67
+ positions = torch.arange(seq_len, device=tensor.device).type_as( # (l,)
68
+ self.inv_freq
69
+ )
70
+ angles = torch.outer(positions, self.inv_freq) # (l, d / 2)
71
+ angles = torch.cat((angles, angles), dim=-1).to(tensor.device) # (l, d)
72
+ self._cos_cached = angles.cos()[None, None, :, :] # (1, 1, l, d)
73
+ self._sin_cached = angles.sin()[None, None, :, :] # (1, 1, l, d)
74
+
75
+ assert self._cos_cached is not None
76
+ assert self._sin_cached is not None
77
+ return self._cos_cached, self._sin_cached # (1, 1, l, d), (1, 1, l, d)
78
+
79
+ def forward(
80
+ self,
81
+ query: torch.Tensor,
82
+ key: torch.Tensor,
83
+ ) -> tuple[torch.Tensor, torch.Tensor]:
84
+ # query, key: (b, h, l, d)
85
+ cos, sin = self._update_cos_sin_tables( # (1, 1, l, d), (1, 1, l, d)
86
+ key,
87
+ seq_dimension=-2,
88
+ )
89
+ return (
90
+ apply_rotary_pos_emb(query, cos, sin).to(dtype=query.dtype), # (b, h, l, d)
91
+ apply_rotary_pos_emb(key, cos, sin).to(dtype=key.dtype), # (b, h, l, d)
92
+ )
fastplms/models/classification_probe.py ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared transformer probes for residue and sequence prediction tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from typing import Any
7
+
8
+ import torch
9
+ from torch import nn
10
+ from torch.nn import functional as F
11
+ from transformers.modeling_outputs import (
12
+ BaseModelOutput,
13
+ SequenceClassifierOutput,
14
+ TokenClassifierOutput,
15
+ )
16
+
17
+ try:
18
+ from fastplms.attention import (
19
+ AttentionBackend,
20
+ _get_flex_attention_fn,
21
+ flex_attention,
22
+ get_attention_mask,
23
+ resolve_attention_backend,
24
+ )
25
+ from fastplms.embeddings.pooling import Pooler
26
+ from fastplms.models._esm_rotary import RotaryEmbedding
27
+ except ModuleNotFoundError as error:
28
+ _COMPOSITE_REQUIRED_NAMES = (
29
+ "AttentionBackend",
30
+ "Pooler",
31
+ "RotaryEmbedding",
32
+ "_get_flex_attention_fn",
33
+ "flex_attention",
34
+ "get_attention_mask",
35
+ "resolve_attention_backend",
36
+ )
37
+ if error.name != "fastplms" or any(
38
+ name not in globals() for name in _COMPOSITE_REQUIRED_NAMES
39
+ ):
40
+ raise
41
+ # Flat Hub composites define every shared symbol above this source.
42
+
43
+
44
+ _SUPPORTED_BACKENDS = frozenset(
45
+ {
46
+ AttentionBackend.EAGER,
47
+ AttentionBackend.SDPA,
48
+ AttentionBackend.FLEX_ATTENTION,
49
+ }
50
+ )
51
+ _SUPPORTED_PROBLEM_TYPES = frozenset(
52
+ {
53
+ "regression",
54
+ "single_label_classification",
55
+ "multi_label_classification",
56
+ }
57
+ )
58
+ _UNSUPPORTED_POOLING = frozenset({"cls", "parti"})
59
+
60
+
61
+ def _config_value(config: Any, name: str, default: Any) -> Any:
62
+ value = getattr(config, name, None)
63
+ return default if value is None else value
64
+
65
+
66
+ def _attention_backend(config: Any) -> AttentionBackend:
67
+ requested = getattr(config, "_attn_implementation", None)
68
+ if requested is None:
69
+ requested = getattr(config, "attn_backend", "sdpa")
70
+ backend = resolve_attention_backend(requested)
71
+ if backend not in _SUPPORTED_BACKENDS:
72
+ expected = ", ".join(sorted(item.value for item in _SUPPORTED_BACKENDS))
73
+ raise ValueError(
74
+ f"Classification probes support only {expected}; received {backend.value!r}."
75
+ )
76
+ return backend
77
+
78
+
79
+ def resolve_problem_type(
80
+ config: Any,
81
+ labels: torch.Tensor,
82
+ *,
83
+ num_labels: int,
84
+ ) -> str:
85
+ """Resolve and persist the standard Transformers classification problem type."""
86
+
87
+ problem_type = getattr(config, "problem_type", None)
88
+ if problem_type is None:
89
+ if num_labels == 1:
90
+ problem_type = "regression"
91
+ elif labels.dtype in {torch.long, torch.int}:
92
+ problem_type = "single_label_classification"
93
+ else:
94
+ problem_type = "multi_label_classification"
95
+ config.problem_type = problem_type
96
+ if problem_type not in _SUPPORTED_PROBLEM_TYPES:
97
+ raise ValueError(
98
+ f"Unsupported problem_type {problem_type!r}; expected one of "
99
+ f"{sorted(_SUPPORTED_PROBLEM_TYPES)}."
100
+ )
101
+ return problem_type
102
+
103
+
104
+ def sequence_classification_loss(
105
+ logits: torch.Tensor,
106
+ labels: torch.Tensor,
107
+ *,
108
+ problem_type: str,
109
+ num_labels: int,
110
+ ) -> torch.Tensor:
111
+ """Compute a Hugging Face-compatible sequence task loss."""
112
+
113
+ labels = labels.to(logits.device)
114
+ if problem_type == "regression":
115
+ if num_labels == 1:
116
+ return F.mse_loss(logits.squeeze(-1), labels.squeeze(-1).to(logits.dtype))
117
+ return F.mse_loss(logits, labels.to(logits.dtype))
118
+ if problem_type == "single_label_classification":
119
+ return F.cross_entropy(logits.reshape(-1, num_labels), labels.reshape(-1).long())
120
+ if problem_type == "multi_label_classification":
121
+ return F.binary_cross_entropy_with_logits(logits, labels.to(logits.dtype))
122
+ raise ValueError(f"Unsupported problem_type {problem_type!r}.")
123
+
124
+
125
+ def _masked_elementwise_loss(
126
+ losses: torch.Tensor,
127
+ labels: torch.Tensor,
128
+ ) -> torch.Tensor:
129
+ valid = labels.ne(-100)
130
+ if not bool(valid.any()):
131
+ return losses.sum() * 0
132
+ return losses.masked_select(valid).mean()
133
+
134
+
135
+ def token_classification_loss(
136
+ logits: torch.Tensor,
137
+ labels: torch.Tensor,
138
+ *,
139
+ problem_type: str,
140
+ num_labels: int,
141
+ ) -> torch.Tensor:
142
+ """Compute a token task loss, excluding every label element equal to ``-100``."""
143
+
144
+ labels = labels.to(logits.device)
145
+ if problem_type == "regression":
146
+ targets = labels.to(logits.dtype)
147
+ if num_labels == 1 and targets.ndim == logits.ndim - 1:
148
+ targets = targets.unsqueeze(-1)
149
+ if targets.shape != logits.shape:
150
+ raise ValueError(
151
+ "Token regression labels must match logits, except that the final "
152
+ "singleton dimension may be omitted when num_labels=1."
153
+ )
154
+ return _masked_elementwise_loss(F.mse_loss(logits, targets, reduction="none"), targets)
155
+ if problem_type == "single_label_classification":
156
+ if not bool(labels.ne(-100).any()):
157
+ return logits.sum() * 0
158
+ return F.cross_entropy(
159
+ logits.reshape(-1, num_labels),
160
+ labels.reshape(-1).long(),
161
+ ignore_index=-100,
162
+ )
163
+ if problem_type == "multi_label_classification":
164
+ if labels.shape != logits.shape:
165
+ raise ValueError("Multilabel token labels must have the same shape as logits.")
166
+ losses = F.binary_cross_entropy_with_logits(
167
+ logits,
168
+ labels.to(logits.dtype),
169
+ reduction="none",
170
+ )
171
+ return _masked_elementwise_loss(losses, labels)
172
+ raise ValueError(f"Unsupported problem_type {problem_type!r}.")
173
+
174
+
175
+ class SwiGLU(nn.Module):
176
+ """SwiGLU activation used by the Protify-aligned feed-forward layer."""
177
+
178
+ def forward(self, inputs: torch.Tensor) -> torch.Tensor:
179
+ gate, values = inputs.chunk(2, dim=-1)
180
+ return F.silu(gate) * values
181
+
182
+
183
+ class ProbeSelfAttention(nn.Module):
184
+ """Four-head RoPE self-attention with explicit, fail-closed dispatch."""
185
+
186
+ def __init__(
187
+ self,
188
+ hidden_size: int,
189
+ num_heads: int,
190
+ dropout: float,
191
+ backend: AttentionBackend,
192
+ use_bias: bool,
193
+ ) -> None:
194
+ super().__init__()
195
+ if hidden_size % num_heads:
196
+ raise ValueError("classifier_probe_hidden_size must be divisible by its head count.")
197
+ self.hidden_size = hidden_size
198
+ self.num_heads = num_heads
199
+ self.head_size = hidden_size // num_heads
200
+ self.dropout = dropout
201
+ self.backend = backend
202
+ self.qkv = nn.Linear(hidden_size, 3 * hidden_size, bias=use_bias)
203
+ self.output = nn.Linear(hidden_size, hidden_size, bias=use_bias)
204
+ self.rotary = RotaryEmbedding(self.head_size)
205
+
206
+ def _reshape(self, tensor: torch.Tensor) -> torch.Tensor:
207
+ batch_size, sequence_length, _ = tensor.shape
208
+ return tensor.view(
209
+ batch_size,
210
+ sequence_length,
211
+ self.num_heads,
212
+ self.head_size,
213
+ ).transpose(1, 2)
214
+
215
+ def forward(
216
+ self,
217
+ hidden_states: torch.Tensor,
218
+ *,
219
+ attention_mask: torch.Tensor | None,
220
+ output_attentions: bool,
221
+ ) -> tuple[torch.Tensor, torch.Tensor | None]:
222
+ batch_size, sequence_length, _ = hidden_states.shape
223
+ query, key, value = self.qkv(hidden_states).chunk(3, dim=-1)
224
+ query = self._reshape(query)
225
+ key = self._reshape(key)
226
+ value = self._reshape(value)
227
+ query, key = self.rotary(query, key)
228
+ if output_attentions and self.backend != AttentionBackend.EAGER:
229
+ raise ValueError(
230
+ f"output_attentions=True is unavailable for {self.backend.value!r}; "
231
+ "select 'eager' explicitly."
232
+ )
233
+ _, attention_mask_4d, flex_block_mask = get_attention_mask(
234
+ self.backend,
235
+ batch_size,
236
+ sequence_length,
237
+ hidden_states.device,
238
+ attention_mask,
239
+ hidden_states.dtype,
240
+ )
241
+ dropout = self.dropout if self.training else 0.0
242
+ attention_weights = None
243
+ if self.backend == AttentionBackend.EAGER:
244
+ scores = query @ key.transpose(-2, -1) / math.sqrt(self.head_size)
245
+ if attention_mask_4d is not None:
246
+ scores = scores.masked_fill(~attention_mask_4d, float("-inf"))
247
+ attention_weights = scores.softmax(dim=-1)
248
+ context = F.dropout(attention_weights, p=dropout, training=self.training) @ value
249
+ elif self.backend == AttentionBackend.SDPA:
250
+ context = F.scaled_dot_product_attention(
251
+ query,
252
+ key,
253
+ value,
254
+ attn_mask=attention_mask_4d,
255
+ dropout_p=dropout,
256
+ )
257
+ elif self.backend == AttentionBackend.FLEX_ATTENTION:
258
+ if flex_attention is None:
259
+ raise RuntimeError("'flex_attention' was requested but is unavailable.")
260
+ flex_fn = _get_flex_attention_fn(
261
+ device=query.device,
262
+ dtype=query.dtype,
263
+ shape=tuple(query.shape),
264
+ mask_semantics="padding",
265
+ )
266
+ if flex_fn is None:
267
+ raise RuntimeError("'flex_attention' was requested but is unavailable.")
268
+ context = flex_fn(
269
+ query,
270
+ key,
271
+ value,
272
+ block_mask=flex_block_mask,
273
+ scale=1.0 / math.sqrt(self.head_size),
274
+ kernel_options={"PRESCALE_QK": True, "BLOCK_N": 32},
275
+ )
276
+ else:
277
+ raise AssertionError(f"Unhandled attention backend {self.backend.value!r}.")
278
+ context = context.transpose(1, 2).contiguous().view(
279
+ batch_size,
280
+ sequence_length,
281
+ self.hidden_size,
282
+ )
283
+ return self.output(context), attention_weights
284
+
285
+
286
+ class ProteinTransformerProbe(nn.Module):
287
+ """Project residue embeddings and refine them with exactly one pre-LN block."""
288
+
289
+ def __init__(self, config: Any, input_size: int) -> None:
290
+ super().__init__()
291
+ hidden_size = int(_config_value(config, "classifier_probe_hidden_size", 512))
292
+ num_heads = int(_config_value(config, "classifier_probe_num_heads", 4))
293
+ dropout = float(_config_value(config, "classifier_probe_dropout", 0.1))
294
+ use_bias = bool(
295
+ _config_value(
296
+ config,
297
+ "classifier_use_bias",
298
+ _config_value(config, "use_bias", False),
299
+ )
300
+ )
301
+ if hidden_size != 512 or num_heads != 4 or hidden_size // num_heads != 128:
302
+ raise ValueError(
303
+ "The folding classification probe requires a 512-wide projection with "
304
+ "four 128-wide attention heads."
305
+ )
306
+ self.hidden_size = hidden_size
307
+ self.input_norm = nn.LayerNorm(input_size)
308
+ self.input_projection = nn.Linear(input_size, hidden_size, bias=use_bias)
309
+ self.attention_norm = nn.LayerNorm(hidden_size)
310
+ self.attention = ProbeSelfAttention(
311
+ hidden_size,
312
+ num_heads,
313
+ dropout,
314
+ _attention_backend(config),
315
+ use_bias,
316
+ )
317
+ intermediate_size = int(math.ceil((8 / 3) * hidden_size / 256) * 256)
318
+ self.feed_forward_norm = nn.LayerNorm(hidden_size)
319
+ self.feed_forward = nn.Sequential(
320
+ nn.Linear(hidden_size, 2 * intermediate_size, bias=use_bias),
321
+ SwiGLU(),
322
+ nn.Dropout(dropout),
323
+ nn.Linear(intermediate_size, hidden_size, bias=use_bias),
324
+ )
325
+ self.residual_dropout = nn.Dropout(dropout)
326
+
327
+ @property
328
+ def attn_backend(self) -> str:
329
+ return self.attention.backend.value
330
+
331
+ def forward(
332
+ self,
333
+ embeddings: torch.Tensor,
334
+ attention_mask: torch.Tensor | None = None,
335
+ *,
336
+ output_attentions: bool = False,
337
+ output_hidden_states: bool = False,
338
+ return_dict: bool = True,
339
+ ) -> BaseModelOutput | tuple[torch.Tensor, ...]:
340
+ if embeddings.ndim != 3:
341
+ raise ValueError("embeddings must have shape (batch, residue, channel).")
342
+ embeddings = embeddings.to(dtype=self.input_projection.weight.dtype)
343
+ hidden_states = self.input_projection(self.input_norm(embeddings))
344
+ attention_output, attention_weights = self.attention(
345
+ self.attention_norm(hidden_states),
346
+ attention_mask=attention_mask,
347
+ output_attentions=output_attentions,
348
+ )
349
+ hidden_states = hidden_states + self.residual_dropout(attention_output)
350
+ hidden_states = hidden_states + self.residual_dropout(
351
+ self.feed_forward(self.feed_forward_norm(hidden_states))
352
+ )
353
+ output = BaseModelOutput(
354
+ last_hidden_state=hidden_states,
355
+ hidden_states=(hidden_states,) if output_hidden_states else None,
356
+ attentions=(attention_weights,) if output_attentions else None,
357
+ )
358
+ return output if return_dict else output.to_tuple()
359
+
360
+
361
+ class _ClassificationProbe(nn.Module):
362
+ def __init__(self, config: Any, input_size: int, *, sequence_task: bool) -> None:
363
+ super().__init__()
364
+ self.config = config
365
+ self.num_labels = int(_config_value(config, "num_labels", 2))
366
+ self.transformer = ProteinTransformerProbe(config, input_size)
367
+ self.sequence_task = sequence_task
368
+ pooling_types = _config_value(config, "classifier_pooling_types", ["mean"])
369
+ self.pooler = Pooler(pooling_types) if sequence_task else None
370
+ if self.pooler is not None:
371
+ unsupported = sorted(set(self.pooler.names) & _UNSUPPORTED_POOLING)
372
+ if unsupported:
373
+ raise ValueError(
374
+ "Classification probes consume residue-only representations and do not "
375
+ f"support pooling operation(s) {unsupported}."
376
+ )
377
+ hidden_size = self.transformer.hidden_size
378
+ classifier_input = hidden_size * (len(self.pooler.names) if self.pooler else 1)
379
+ classifier_hidden = int(_config_value(config, "classifier_hidden_size", 4096))
380
+ classifier_dropout = float(_config_value(config, "classifier_dropout", 0.2))
381
+ use_bias = bool(
382
+ _config_value(
383
+ config,
384
+ "classifier_use_bias",
385
+ _config_value(config, "use_bias", False),
386
+ )
387
+ )
388
+ projection_size = int(math.ceil((2 * self.num_labels) / 256) * 256)
389
+ classifier_layers: list[nn.Module] = [
390
+ nn.LayerNorm(classifier_input),
391
+ nn.Linear(classifier_input, classifier_hidden, bias=use_bias),
392
+ nn.ReLU(),
393
+ nn.Dropout(classifier_dropout),
394
+ nn.Linear(classifier_hidden, projection_size, bias=use_bias),
395
+ nn.ReLU(),
396
+ nn.Dropout(classifier_dropout),
397
+ ]
398
+ if not sequence_task:
399
+ classifier_layers.extend(
400
+ [
401
+ nn.Linear(projection_size, projection_size, bias=use_bias),
402
+ nn.ReLU(),
403
+ ]
404
+ )
405
+ classifier_layers.append(nn.Linear(projection_size, self.num_labels, bias=use_bias))
406
+ self.classifier = nn.Sequential(*classifier_layers)
407
+
408
+ def _forward_transformer(
409
+ self,
410
+ embeddings: torch.Tensor,
411
+ attention_mask: torch.Tensor | None,
412
+ output_attentions: bool | None,
413
+ output_hidden_states: bool | None,
414
+ ) -> BaseModelOutput:
415
+ output_attentions = (
416
+ bool(output_attentions)
417
+ if output_attentions is not None
418
+ else bool(getattr(self.config, "output_attentions", False))
419
+ )
420
+ output_hidden_states = (
421
+ bool(output_hidden_states)
422
+ if output_hidden_states is not None
423
+ else bool(getattr(self.config, "output_hidden_states", False))
424
+ )
425
+ return self.transformer(
426
+ embeddings,
427
+ attention_mask,
428
+ output_attentions=output_attentions,
429
+ output_hidden_states=output_hidden_states,
430
+ return_dict=True,
431
+ )
432
+
433
+
434
+ class SequenceClassificationProbe(_ClassificationProbe):
435
+ """Protify-style sequence classifier over externally supplied residue embeddings."""
436
+
437
+ def __init__(self, config: Any, input_size: int) -> None:
438
+ super().__init__(config, input_size, sequence_task=True)
439
+
440
+ def forward(
441
+ self,
442
+ embeddings: torch.Tensor,
443
+ attention_mask: torch.Tensor | None = None,
444
+ labels: torch.Tensor | None = None,
445
+ output_attentions: bool | None = None,
446
+ output_hidden_states: bool | None = None,
447
+ return_dict: bool | None = None,
448
+ ) -> SequenceClassifierOutput | tuple[torch.Tensor, ...]:
449
+ if attention_mask is None:
450
+ attention_mask = torch.ones(
451
+ embeddings.shape[:2],
452
+ device=embeddings.device,
453
+ dtype=torch.bool,
454
+ )
455
+ outputs = self._forward_transformer(
456
+ embeddings,
457
+ attention_mask,
458
+ output_attentions,
459
+ output_hidden_states,
460
+ )
461
+ if self.pooler is None:
462
+ raise AssertionError("Sequence classification requires a configured pooler.")
463
+ pooled = self.pooler(outputs.last_hidden_state, attention_mask)
464
+ logits = self.classifier(pooled)
465
+ loss = None
466
+ if labels is not None:
467
+ problem_type = resolve_problem_type(self.config, labels, num_labels=self.num_labels)
468
+ loss = sequence_classification_loss(
469
+ logits,
470
+ labels,
471
+ problem_type=problem_type,
472
+ num_labels=self.num_labels,
473
+ )
474
+ result = SequenceClassifierOutput(
475
+ loss=loss,
476
+ logits=logits,
477
+ hidden_states=outputs.hidden_states,
478
+ attentions=outputs.attentions,
479
+ )
480
+ use_return_dict = (
481
+ bool(return_dict)
482
+ if return_dict is not None
483
+ else bool(getattr(self.config, "use_return_dict", True))
484
+ )
485
+ return result if use_return_dict else result.to_tuple()
486
+
487
+
488
+ class TokenClassificationProbe(_ClassificationProbe):
489
+ """Protify-style residue classifier or regressor over supplied embeddings."""
490
+
491
+ def __init__(self, config: Any, input_size: int) -> None:
492
+ super().__init__(config, input_size, sequence_task=False)
493
+
494
+ def forward(
495
+ self,
496
+ embeddings: torch.Tensor,
497
+ attention_mask: torch.Tensor | None = None,
498
+ labels: torch.Tensor | None = None,
499
+ output_attentions: bool | None = None,
500
+ output_hidden_states: bool | None = None,
501
+ return_dict: bool | None = None,
502
+ ) -> TokenClassifierOutput | tuple[torch.Tensor, ...]:
503
+ outputs = self._forward_transformer(
504
+ embeddings,
505
+ attention_mask,
506
+ output_attentions,
507
+ output_hidden_states,
508
+ )
509
+ logits = self.classifier(outputs.last_hidden_state)
510
+ loss = None
511
+ if labels is not None:
512
+ problem_type = resolve_problem_type(self.config, labels, num_labels=self.num_labels)
513
+ loss = token_classification_loss(
514
+ logits,
515
+ labels,
516
+ problem_type=problem_type,
517
+ num_labels=self.num_labels,
518
+ )
519
+ result = TokenClassifierOutput(
520
+ loss=loss,
521
+ logits=logits,
522
+ hidden_states=outputs.hidden_states,
523
+ attentions=outputs.attentions,
524
+ )
525
+ use_return_dict = (
526
+ bool(return_dict)
527
+ if return_dict is not None
528
+ else bool(getattr(self.config, "use_return_dict", True))
529
+ )
530
+ return result if use_return_dict else result.to_tuple()
531
+
532
+
533
+ __all__ = [
534
+ "ProbeSelfAttention",
535
+ "ProteinTransformerProbe",
536
+ "SequenceClassificationProbe",
537
+ "SwiGLU",
538
+ "TokenClassificationProbe",
539
+ "resolve_problem_type",
540
+ "sequence_classification_loss",
541
+ "token_classification_loss",
542
+ ]
fastplms/models/esmfold2/__init__.py CHANGED
@@ -9,6 +9,12 @@ if TYPE_CHECKING:
9
  from .configuration_esmfold2 import ESMFold2Config as ESMFold2Config
10
  from .modeling_esmfold2 import ESMFold2Model as ESMFold2Model
11
  from .modeling_esmfold2 import ESMFold2Output as ESMFold2Output
 
 
 
 
 
 
12
  from .modeling_esmfold2_experimental import (
13
  ESMFold2ExperimentalModel as ESMFold2ExperimentalModel,
14
  )
@@ -17,6 +23,10 @@ if TYPE_CHECKING:
17
  _EXPORT_MODULES = {
18
  "ESMFold2Config": ".configuration_esmfold2",
19
  "ESMFold2ExperimentalModel": ".modeling_esmfold2_experimental",
 
 
 
 
20
  "ESMFold2Model": ".modeling_esmfold2",
21
  "ESMFold2Output": ".modeling_esmfold2",
22
  "seed_context": ".reproducibility",
 
9
  from .configuration_esmfold2 import ESMFold2Config as ESMFold2Config
10
  from .modeling_esmfold2 import ESMFold2Model as ESMFold2Model
11
  from .modeling_esmfold2 import ESMFold2Output as ESMFold2Output
12
+ from .modeling_esmfold2_classification import (
13
+ ESMFold2ExperimentalForSequenceClassification as ESMFold2ExperimentalForSequenceClassification,
14
+ ESMFold2ExperimentalForTokenClassification as ESMFold2ExperimentalForTokenClassification,
15
+ ESMFold2ForSequenceClassification as ESMFold2ForSequenceClassification,
16
+ ESMFold2ForTokenClassification as ESMFold2ForTokenClassification,
17
+ )
18
  from .modeling_esmfold2_experimental import (
19
  ESMFold2ExperimentalModel as ESMFold2ExperimentalModel,
20
  )
 
23
  _EXPORT_MODULES = {
24
  "ESMFold2Config": ".configuration_esmfold2",
25
  "ESMFold2ExperimentalModel": ".modeling_esmfold2_experimental",
26
+ "ESMFold2ExperimentalForSequenceClassification": ".modeling_esmfold2_classification",
27
+ "ESMFold2ExperimentalForTokenClassification": ".modeling_esmfold2_classification",
28
+ "ESMFold2ForSequenceClassification": ".modeling_esmfold2_classification",
29
+ "ESMFold2ForTokenClassification": ".modeling_esmfold2_classification",
30
  "ESMFold2Model": ".modeling_esmfold2",
31
  "ESMFold2Output": ".modeling_esmfold2",
32
  "seed_context": ".reproducibility",
fastplms/models/esmfold2/configuration_esmfold2.py CHANGED
@@ -289,6 +289,23 @@ class ESMFold2Config(PretrainedConfig):
289
  )
290
  self.msa_encoder_overwrite = bool(kwargs.get("msa_encoder_overwrite", True))
291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  def to_dict(self) -> dict[str, Any]:
293
  output = cast(dict[str, Any], super().to_dict())
294
  for name, _config_type in _NESTED_CONFIGS:
 
289
  )
290
  self.msa_encoder_overwrite = bool(kwargs.get("msa_encoder_overwrite", True))
291
 
292
+ self.classifier_train_scope = str(kwargs.get("classifier_train_scope", "probe"))
293
+ if self.classifier_train_scope not in {"probe", "projection"}:
294
+ raise ValueError(
295
+ "classifier_train_scope must be 'probe' or 'projection', "
296
+ f"got {self.classifier_train_scope!r}."
297
+ )
298
+ self.classifier_probe_hidden_size = int(
299
+ kwargs.get("classifier_probe_hidden_size", 512)
300
+ )
301
+ self.classifier_probe_num_heads = int(kwargs.get("classifier_probe_num_heads", 4))
302
+ self.classifier_probe_dropout = float(kwargs.get("classifier_probe_dropout", 0.1))
303
+ self.classifier_hidden_size = int(kwargs.get("classifier_hidden_size", 4096))
304
+ self.classifier_dropout = float(kwargs.get("classifier_dropout", 0.2))
305
+ self.classifier_pooling_types = list(
306
+ kwargs.get("classifier_pooling_types", ["mean"])
307
+ )
308
+
309
  def to_dict(self) -> dict[str, Any]:
310
  output = cast(dict[str, Any], super().to_dict())
311
  for name, _config_type in _NESTED_CONFIGS:
fastplms/models/esmfold2/esmfold2_processor.py CHANGED
@@ -123,6 +123,12 @@ def clean_esmfold2_input(input: StructurePredictionInput) -> StructurePrediction
123
  "the published ESMFold2 feature pipeline drops it. FastPLMs refuses this "
124
  "input instead of silently emitting an all-zero pocket feature."
125
  )
 
 
 
 
 
 
126
 
127
  cleaned: list[Any] = []
128
  for item in input.sequences:
 
123
  "the published ESMFold2 feature pipeline drops it. FastPLMs refuses this "
124
  "input instead of silently emitting an all-zero pocket feature."
125
  )
126
+ if input.distogram_conditioning is not None:
127
+ raise NotImplementedError(
128
+ "ESMFold2 distogram conditioning is present in the upstream input schema but "
129
+ "the published ESMFold2 forward does not consume it. FastPLMs refuses this "
130
+ "input instead of silently ignoring the supplied distogram."
131
+ )
132
 
133
  cleaned: list[Any] = []
134
  for item in input.sequences:
fastplms/models/esmfold2/modeling_esmfold2.py CHANGED
@@ -74,6 +74,7 @@ from .modeling_esmfold2_common import (
74
  maybe_subsample_msa,
75
  validate_kernel_backend,
76
  validate_msa_conditioning_inputs,
 
77
  )
78
 
79
  _ESMC_FP8_LINEAR_SUFFIX = ".attn.out_proj"
@@ -1417,6 +1418,12 @@ class ESMFold2Model(
1417
  output_attentions: bool | None = None,
1418
  output_hidden_states: bool | None = None,
1419
  return_dict: bool | None = None,
 
 
 
 
 
 
1420
  ) -> ESMFold2Output | tuple[Any, ...]:
1421
  output_hidden_states, return_dict = _resolve_structure_output_controls(
1422
  self.config,
@@ -1432,6 +1439,12 @@ class ESMFold2Model(
1432
  deletion_value=deletion_value,
1433
  deletion_mean=deletion_mean,
1434
  )
 
 
 
 
 
 
1435
  tok_mask = token_attention_mask
1436
  atm_mask = atom_attention_mask
1437
  disto_idx = distogram_atom_idx
 
74
  maybe_subsample_msa,
75
  validate_kernel_backend,
76
  validate_msa_conditioning_inputs,
77
+ validate_prepared_auxiliary_inputs,
78
  )
79
 
80
  _ESMC_FP8_LINEAR_SUFFIX = ".attn.out_proj"
 
1418
  output_attentions: bool | None = None,
1419
  output_hidden_states: bool | None = None,
1420
  return_dict: bool | None = None,
1421
+ pocket_feature: Tensor | None = None,
1422
+ gt_coords: Tensor | None = None,
1423
+ is_resolved: Tensor | None = None,
1424
+ frames_idx: Tensor | None = None,
1425
+ disto_cond: Tensor | None = None,
1426
+ disto_cond_mask: Tensor | None = None,
1427
  ) -> ESMFold2Output | tuple[Any, ...]:
1428
  output_hidden_states, return_dict = _resolve_structure_output_controls(
1429
  self.config,
 
1439
  deletion_value=deletion_value,
1440
  deletion_mean=deletion_mean,
1441
  )
1442
+ validate_prepared_auxiliary_inputs(
1443
+ pocket_feature=pocket_feature,
1444
+ disto_cond=disto_cond,
1445
+ disto_cond_mask=disto_cond_mask,
1446
+ )
1447
+ del gt_coords, is_resolved, frames_idx
1448
  tok_mask = token_attention_mask
1449
  atm_mask = atom_attention_mask
1450
  disto_idx = distogram_atom_idx
fastplms/models/esmfold2/modeling_esmfold2_classification.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Sequence and residue prediction heads for ESMFold2 checkpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ from torch import Tensor
10
+
11
+ from ..classification_probe import SequenceClassificationProbe, TokenClassificationProbe
12
+ from .configuration_esmfold2 import ESMFold2Config
13
+ from .embedding import _TOKEN_TO_ID, _VALID_RESIDUES, _encode_single_chain
14
+ from .esmfold2_constants_esm3 import SEQUENCE_PAD_TOKEN
15
+ from .modeling_esmfold2 import ESMFold2Model
16
+ from .modeling_esmfold2_experimental import ESMFold2ExperimentalModel
17
+
18
+
19
+ ClassifierTrainScope = Literal["probe", "projection"]
20
+ _VALID_RESIDUE_IDS = frozenset(_TOKEN_TO_ID[residue] for residue in _VALID_RESIDUES)
21
+
22
+
23
+ class _ESMFold2ClassificationMixin:
24
+ """Run a task probe on the checkpoint-owned ESMC sequence projection."""
25
+
26
+ _classifier_config_type = ""
27
+ _keys_to_ignore_on_load_unexpected: list[str] = [r"\._extra_state$"]
28
+
29
+ def _initialize_classifier(self, classifier: nn.Module) -> None:
30
+ self.requires_grad_(False)
31
+ self.classifier = classifier
32
+ self.set_classifier_train_scope(self.config.classifier_train_scope)
33
+
34
+ def set_classifier_train_scope(self, scope: ClassifierTrainScope) -> None:
35
+ """Select whether fine-tuning updates only the probe or its input projection too."""
36
+
37
+ if scope not in {"probe", "projection"}:
38
+ raise ValueError(
39
+ "classifier_train_scope must be 'probe' or 'projection', "
40
+ f"got {scope!r}."
41
+ )
42
+ self.config.classifier_train_scope = scope
43
+ self.requires_grad_(False)
44
+ self.classifier.requires_grad_(True)
45
+ if scope == "projection":
46
+ self.language_model.base_z_combine.requires_grad_(True)
47
+ self.language_model.base_z_linear.requires_grad_(True)
48
+ if self._esmc is not None:
49
+ self._esmc.requires_grad_(False)
50
+
51
+ def load_esmc(self, *args: Any, **kwargs: Any) -> None:
52
+ super().load_esmc(*args, **kwargs)
53
+ if self._esmc is None:
54
+ raise RuntimeError("ESMFold2 ESMC loading completed without a backbone.")
55
+ self._esmc.requires_grad_(False)
56
+
57
+ def train(self, mode: bool = True):
58
+ super().train(mode)
59
+ if self._esmc is not None:
60
+ self._esmc.eval()
61
+ return self
62
+
63
+ @classmethod
64
+ def from_pretrained(cls, pretrained_model_name_or_path, *model_args: Any, **kwargs: Any):
65
+ if "config" not in kwargs:
66
+ kwargs["config"] = ESMFold2Config.from_pretrained(
67
+ pretrained_model_name_or_path, **kwargs
68
+ )
69
+ config = kwargs["config"]
70
+ if not isinstance(config, ESMFold2Config):
71
+ raise TypeError("ESMFold2 classifiers require an ESMFold2Config.")
72
+ if config.type != cls._classifier_config_type:
73
+ raise ValueError(
74
+ f"{cls.__name__} requires config.type={cls._classifier_config_type!r}, "
75
+ f"got {config.type!r}."
76
+ )
77
+ loaded = super().from_pretrained(
78
+ pretrained_model_name_or_path, *model_args, **kwargs
79
+ )
80
+ model = loaded[0] if isinstance(loaded, tuple) else loaded
81
+ model.set_classifier_train_scope(model.config.classifier_train_scope)
82
+ return loaded
83
+
84
+ def prepare_classifier_inputs(
85
+ self, sequence_or_sequences: str | list[str] | tuple[str, ...]
86
+ ) -> dict[str, Tensor]:
87
+ """Encode one or more ungapped single-chain proteins without special tokens."""
88
+
89
+ sequences = (
90
+ [sequence_or_sequences]
91
+ if isinstance(sequence_or_sequences, str)
92
+ else list(sequence_or_sequences)
93
+ )
94
+ if not sequences:
95
+ raise ValueError("prepare_classifier_inputs requires at least one sequence.")
96
+ encoded = [_encode_single_chain(sequence) for sequence in sequences]
97
+ sequence_length = max(map(len, encoded))
98
+ input_ids = torch.full(
99
+ (len(encoded), sequence_length),
100
+ SEQUENCE_PAD_TOKEN,
101
+ dtype=torch.long,
102
+ device=self.device,
103
+ )
104
+ attention_mask = torch.zeros_like(input_ids, dtype=torch.bool)
105
+ for batch_index, token_ids in enumerate(encoded):
106
+ length = len(token_ids)
107
+ input_ids[batch_index, :length] = torch.tensor(
108
+ token_ids, dtype=torch.long, device=self.device
109
+ )
110
+ attention_mask[batch_index, :length] = True
111
+ return {"input_ids": input_ids, "attention_mask": attention_mask}
112
+
113
+ def _classifier_embeddings(
114
+ self, input_ids: Tensor, attention_mask: Tensor | None
115
+ ) -> tuple[Tensor, Tensor]:
116
+ if input_ids.ndim != 2:
117
+ raise ValueError(
118
+ "ESMFold2 classifier input_ids must have shape (batch, residue), "
119
+ f"got {tuple(input_ids.shape)}."
120
+ )
121
+ if attention_mask is None:
122
+ attention_mask = input_ids.ne(SEQUENCE_PAD_TOKEN)
123
+ elif attention_mask.shape != input_ids.shape:
124
+ raise ValueError(
125
+ "ESMFold2 classifier attention_mask must match input_ids, got "
126
+ f"{tuple(attention_mask.shape)} and {tuple(input_ids.shape)}."
127
+ )
128
+ residue_mask = attention_mask.to(device=input_ids.device, dtype=torch.bool)
129
+ if not residue_mask.any(dim=1).all():
130
+ raise ValueError("Every ESMFold2 classifier input must contain a protein residue.")
131
+ if input_ids.masked_select(residue_mask).eq(SEQUENCE_PAD_TOKEN).any():
132
+ raise ValueError("ESMFold2 classifier padding tokens cannot be attended residues.")
133
+ residue_ids = input_ids.masked_select(residue_mask)
134
+ valid_residue_ids = torch.tensor(
135
+ sorted(_VALID_RESIDUE_IDS), dtype=input_ids.dtype, device=input_ids.device
136
+ )
137
+ if not torch.isin(residue_ids, valid_residue_ids).all():
138
+ raise ValueError(
139
+ "ESMFold2 classifiers accept residue-only single-chain protein inputs."
140
+ )
141
+
142
+ batch_size, sequence_length = input_ids.shape
143
+ residue_index = torch.arange(sequence_length, device=input_ids.device).expand(
144
+ batch_size, -1
145
+ )
146
+ asym_id = torch.zeros_like(input_ids)
147
+ mol_type = torch.zeros_like(input_ids)
148
+ with torch.no_grad():
149
+ hidden_states = self._compute_lm_hidden_states(
150
+ input_ids,
151
+ asym_id,
152
+ residue_index,
153
+ mol_type,
154
+ residue_mask,
155
+ )
156
+ embeddings = self.project_esmc_hidden_states(hidden_states, residue_mask)
157
+ return embeddings, residue_mask
158
+
159
+ def _classifier_forward(
160
+ self,
161
+ input_ids: Tensor,
162
+ attention_mask: Tensor | None = None,
163
+ labels: Tensor | None = None,
164
+ output_attentions: bool | None = None,
165
+ output_hidden_states: bool | None = None,
166
+ return_dict: bool | None = None,
167
+ ):
168
+ embeddings, residue_mask = self._classifier_embeddings(input_ids, attention_mask)
169
+ return self.classifier(
170
+ embeddings,
171
+ attention_mask=residue_mask,
172
+ labels=labels,
173
+ output_attentions=output_attentions,
174
+ output_hidden_states=output_hidden_states,
175
+ return_dict=return_dict,
176
+ )
177
+
178
+
179
+ class _ESMFold2SequenceClassificationMixin(_ESMFold2ClassificationMixin):
180
+ def __init__(self, config: ESMFold2Config) -> None:
181
+ super().__init__(config)
182
+ self._initialize_classifier(SequenceClassificationProbe(config, config.d_pair))
183
+
184
+ forward = _ESMFold2ClassificationMixin._classifier_forward
185
+
186
+
187
+ class _ESMFold2TokenClassificationMixin(_ESMFold2ClassificationMixin):
188
+ def __init__(self, config: ESMFold2Config) -> None:
189
+ super().__init__(config)
190
+ self._initialize_classifier(TokenClassificationProbe(config, config.d_pair))
191
+
192
+ forward = _ESMFold2ClassificationMixin._classifier_forward
193
+
194
+
195
+ class ESMFold2ForSequenceClassification(
196
+ _ESMFold2SequenceClassificationMixin, ESMFold2Model
197
+ ):
198
+ """Released ESMFold2 with a sequence classification or regression probe."""
199
+
200
+ _classifier_config_type = "release"
201
+
202
+
203
+ class ESMFold2ForTokenClassification(_ESMFold2TokenClassificationMixin, ESMFold2Model):
204
+ """Released ESMFold2 with a residue classification or regression probe."""
205
+
206
+ _classifier_config_type = "release"
207
+
208
+
209
+ class ESMFold2ExperimentalForSequenceClassification(
210
+ _ESMFold2SequenceClassificationMixin, ESMFold2ExperimentalModel
211
+ ):
212
+ """Experimental ESMFold2 with a sequence classification or regression probe."""
213
+
214
+ _classifier_config_type = "experimental"
215
+
216
+
217
+ class ESMFold2ExperimentalForTokenClassification(
218
+ _ESMFold2TokenClassificationMixin, ESMFold2ExperimentalModel
219
+ ):
220
+ """Experimental ESMFold2 with a residue classification or regression probe."""
221
+
222
+ _classifier_config_type = "experimental"
223
+
224
+
225
+ __all__ = [
226
+ "ESMFold2ExperimentalForSequenceClassification",
227
+ "ESMFold2ExperimentalForTokenClassification",
228
+ "ESMFold2ForSequenceClassification",
229
+ "ESMFold2ForTokenClassification",
230
+ ]
fastplms/models/esmfold2/modeling_esmfold2_common.py CHANGED
@@ -57,6 +57,14 @@ MSA_CONDITIONING_INPUT_NAMES = (
57
  "deletion_value",
58
  "deletion_mean",
59
  )
 
 
 
 
 
 
 
 
60
 
61
 
62
  def validate_kernel_backend(backend: str | None) -> None:
@@ -105,6 +113,29 @@ def validate_msa_conditioning_inputs(
105
  )
106
 
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  def _fused_active(module: nn.Module, tensor: Tensor) -> bool:
109
  """Return whether an optional fused implementation can handle this call."""
110
  return (
 
57
  "deletion_value",
58
  "deletion_mean",
59
  )
60
+ PREPARED_AUXILIARY_INPUT_NAMES = (
61
+ "pocket_feature",
62
+ "gt_coords",
63
+ "is_resolved",
64
+ "frames_idx",
65
+ "disto_cond",
66
+ "disto_cond_mask",
67
+ )
68
 
69
 
70
  def validate_kernel_backend(backend: str | None) -> None:
 
113
  )
114
 
115
 
116
+ def validate_prepared_auxiliary_inputs(
117
+ *,
118
+ pocket_feature: Tensor | None,
119
+ disto_cond: Tensor | None,
120
+ disto_cond_mask: Tensor | None,
121
+ ) -> None:
122
+ """Accept inert prepared fields and reject unsupported conditioning."""
123
+
124
+ if pocket_feature is not None and torch.any(pocket_feature != 0).item():
125
+ raise NotImplementedError(
126
+ "The published ESMFold2 forward does not consume pocket conditioning; "
127
+ "nonzero pocket_feature values are unsupported."
128
+ )
129
+ distogram_is_active = (disto_cond is not None and torch.any(disto_cond != 0).item()) or (
130
+ disto_cond_mask is not None and torch.any(disto_cond_mask).item()
131
+ )
132
+ if distogram_is_active:
133
+ raise NotImplementedError(
134
+ "The published ESMFold2 forward does not consume distogram conditioning; "
135
+ "nonzero disto_cond or disto_cond_mask values are unsupported."
136
+ )
137
+
138
+
139
  def _fused_active(module: nn.Module, tensor: Tensor) -> bool:
140
  """Return whether an optional fused implementation can handle this call."""
141
  return (
fastplms/models/esmfold2/modeling_esmfold2_experimental.py CHANGED
@@ -59,6 +59,7 @@ from .modeling_esmfold2_common import (
59
  gather_token_to_atom,
60
  validate_kernel_backend,
61
  validate_msa_conditioning_inputs,
 
62
  )
63
 
64
  _EPS = 1e-5
@@ -689,6 +690,12 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
689
  output_attentions: bool | None = None,
690
  output_hidden_states: bool | None = None,
691
  return_dict: bool | None = None,
 
 
 
 
 
 
692
  ) -> ESMFold2Output | tuple[Any, ...]:
693
  output_hidden_states, return_dict = _resolve_structure_output_controls(
694
  self.config,
@@ -704,6 +711,12 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
704
  deletion_value=deletion_value,
705
  deletion_mean=deletion_mean,
706
  )
 
 
 
 
 
 
707
  tok_mask = token_attention_mask
708
  atm_mask = atom_attention_mask
709
  n_loops = num_loops if num_loops is not None else self.config.num_loops
 
59
  gather_token_to_atom,
60
  validate_kernel_backend,
61
  validate_msa_conditioning_inputs,
62
+ validate_prepared_auxiliary_inputs,
63
  )
64
 
65
  _EPS = 1e-5
 
690
  output_attentions: bool | None = None,
691
  output_hidden_states: bool | None = None,
692
  return_dict: bool | None = None,
693
+ pocket_feature: Tensor | None = None,
694
+ gt_coords: Tensor | None = None,
695
+ is_resolved: Tensor | None = None,
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,
 
711
  deletion_value=deletion_value,
712
  deletion_mean=deletion_mean,
713
  )
714
+ validate_prepared_auxiliary_inputs(
715
+ pocket_feature=pocket_feature,
716
+ disto_cond=disto_cond,
717
+ disto_cond_mask=disto_cond_mask,
718
+ )
719
+ del gt_coords, is_resolved, frames_idx
720
  tok_mask = token_attention_mask
721
  atm_mask = atom_attention_mask
722
  n_loops = num_loops if num_loops is not None else self.config.num_loops
fastplms_bundle.py CHANGED
The diff for this file is too large to render. See raw diff
 
modeling_fastplms.py CHANGED
@@ -8,11 +8,12 @@ import sys
8
  import tempfile
9
  from io import BytesIO
10
  from pathlib import Path
 
11
  from zipfile import ZIP_DEFLATED, ZipFile
12
 
13
  from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
14
 
15
- if RUNTIME_HASH != "5f557b57da6c13e8b26b2a144ab0983b9621d853a3141a3a52e912d2de26289a":
16
  raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
17
 
18
  _RUNTIME_TEMPORARIES = []
@@ -179,9 +180,14 @@ def _install_runtime():
179
  return package
180
 
181
  _install_runtime()
182
- _module_181 = _import_without_bytecode("fastplms.models.esmfold2.configuration_esmfold2")
183
- ESMFold2Config = _module_181.ESMFold2Config
184
  ESMFold2Config.__module__ = __name__
185
- _module_184 = _import_without_bytecode("fastplms.models.esmfold2.modeling_esmfold2")
186
- ESMFold2Model = _module_184.ESMFold2Model
187
  ESMFold2Model.__module__ = __name__
 
 
 
 
 
 
8
  import tempfile
9
  from io import BytesIO
10
  from pathlib import Path
11
+ from typing import ClassVar
12
  from zipfile import ZIP_DEFLATED, ZipFile
13
 
14
  from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
15
 
16
+ if RUNTIME_HASH != "dacab314c14443a90eb43e0dfaeca56233336bb0f0ca617756b8353e4f5e3fc2":
17
  raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
18
 
19
  _RUNTIME_TEMPORARIES = []
 
180
  return package
181
 
182
  _install_runtime()
183
+ _module_182 = _import_without_bytecode("fastplms.models.esmfold2.configuration_esmfold2")
184
+ ESMFold2Config = _module_182.ESMFold2Config
185
  ESMFold2Config.__module__ = __name__
186
+ _module_185 = _import_without_bytecode("fastplms.models.esmfold2.modeling_esmfold2")
187
+ ESMFold2Model = _module_185.ESMFold2Model
188
  ESMFold2Model.__module__ = __name__
189
+ _module_188 = _import_without_bytecode("fastplms.models.esmfold2.modeling_esmfold2_classification")
190
+ ESMFold2ForSequenceClassification = _module_188.ESMFold2ForSequenceClassification
191
+ ESMFold2ForSequenceClassification.__module__ = __name__
192
+ ESMFold2ForTokenClassification = _module_188.ESMFold2ForTokenClassification
193
+ ESMFold2ForTokenClassification.__module__ = __name__