lhallee commited on
Commit
4a217a9
·
verified ·
1 Parent(s): a7786d7

Update FastPLMs files

Browse files
README.md CHANGED
@@ -14,15 +14,16 @@ This checkpoint contains the FastPLMs `ESMFold` implementation.
14
 
15
  Accepted inputs are raw amino-acid sequences through folding helpers, or
16
  prepared residue 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 | Unavailable for this structure-only checkpoint |
27
  | Test-time training | Unavailable: the checkpoint has no trained MLM head |
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
@@ -132,8 +172,8 @@ folding requests raise.
132
  ## Runtime contract
133
 
134
  - Public input: Raw amino-acid sequences through folding helpers, or prepared residue tensors
135
- - Advertised AutoClasses: `AutoConfig`, `AutoModel`
136
- - AutoClass weight status: `AutoConfig` = `FastPLMs extension`, `AutoModel` = `pretrained`
137
  - Attention implementations: `eager`, `sdpa`, `flex_attention`
138
  - Precision policies: `default`
139
  - BF16 execution: `fp32_parameters_autocast`
@@ -147,8 +187,8 @@ folding requests raise.
147
  ## Release record
148
 
149
  - FastPLMs weights: `Synthyra/FastESMFold`
150
- - Runtime revision: recorded in the built artifact and published commit
151
- - Source-tree and runtime-bundle SHA-256: recorded in the source record
152
  - Official checkpoint: `facebook/esmfold_v1`
153
  - Artifact source: `fast`
154
  - State transform: `esmfold_meta_to_fastplms_v1`
 
14
 
15
  Accepted inputs are raw amino-acid sequences through folding helpers, or
16
  prepared residue 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 | Unavailable for this structure-only checkpoint |
28
  | Test-time training | Unavailable: the checkpoint has no trained MLM head |
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/FastESMFold"
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
 
172
  ## Runtime contract
173
 
174
  - Public input: Raw amino-acid sequences through folding helpers, or prepared residue tensors
175
+ - Advertised AutoClasses: `AutoConfig`, `AutoModel`, `AutoModelForSequenceClassification`, `AutoModelForTokenClassification`
176
+ - AutoClass weight status: `AutoConfig` = `FastPLMs extension`, `AutoModel` = `pretrained`, `AutoModelForSequenceClassification` = `base weights + untrained task head`, `AutoModelForTokenClassification` = `base weights + untrained task head`
177
  - Attention implementations: `eager`, `sdpa`, `flex_attention`
178
  - Precision policies: `default`
179
  - BF16 execution: `fp32_parameters_autocast`
 
187
  ## Release record
188
 
189
  - FastPLMs weights: `Synthyra/FastESMFold`
190
+ - Runtime revision: recorded separately in the built artifact and published commit
191
+ - Runtime source identities: recorded in `source-record.json`
192
  - Official checkpoint: `facebook/esmfold_v1`
193
  - Artifact source: `fast`
194
  - State transform: `esmfold_meta_to_fastplms_v1`
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/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/esmfold/modeling_fast_esmfold.py CHANGED
@@ -37,6 +37,22 @@ from transformers.models.esm.openfold_utils import residue_constants
37
 
38
  from fastplms.models._esm_rotary import RotaryEmbedding
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  # Hub composite artifacts define these shared names earlier in the assembled file.
42
  try:
@@ -531,13 +547,42 @@ class FastEsmBackbone(nn.Module):
531
  class FastEsmFoldConfig(EsmConfig):
532
  model_type = "fast_esmfold"
533
 
534
- def __init__(self, attn_backend: str | None = None, **kwargs: Any) -> None:
 
 
 
 
 
 
 
 
 
 
 
 
535
  # Earlier mirrors serialized an untrained ESMFold-specific TTT policy.
536
  # It is intentionally ignored because the official checkpoint has no
537
  # trained masked-language-model head.
538
  kwargs.pop("ttt_config", None)
539
  super().__init__(**kwargs)
540
  self.attn_backend = attn_backend
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
 
542
 
543
  class FastEsmForProteinFolding(FastPLMsAttentionMixin, EsmForProteinFolding):
@@ -873,3 +918,139 @@ class FastEsmForProteinFolding(FastPLMsAttentionMixin, EsmForProteinFolding):
873
 
874
  del sequence, return_pdb_string
875
  self._ttt_unavailable()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  from fastplms.models._esm_rotary import RotaryEmbedding
39
 
40
+ try:
41
+ from fastplms.models.classification_probe import (
42
+ SequenceClassificationProbe,
43
+ TokenClassificationProbe,
44
+ )
45
+ except ModuleNotFoundError as error:
46
+ _COMPOSITE_CLASSIFIER_NAMES = (
47
+ "SequenceClassificationProbe",
48
+ "TokenClassificationProbe",
49
+ )
50
+ if error.name != "fastplms" or any(
51
+ name not in globals() for name in _COMPOSITE_CLASSIFIER_NAMES
52
+ ):
53
+ raise
54
+ # Hub composites define the shared classifier probes before this source.
55
+
56
 
57
  # Hub composite artifacts define these shared names earlier in the assembled file.
58
  try:
 
547
  class FastEsmFoldConfig(EsmConfig):
548
  model_type = "fast_esmfold"
549
 
550
+ def __init__(
551
+ self,
552
+ attn_backend: str | None = None,
553
+ classifier_train_scope: str = "probe",
554
+ classifier_pooling_types: list[str] | None = None,
555
+ classifier_probe_hidden_size: int = 512,
556
+ classifier_probe_num_heads: int = 4,
557
+ classifier_probe_dropout: float = 0.1,
558
+ classifier_hidden_size: int = 4096,
559
+ classifier_dropout: float = 0.2,
560
+ classifier_use_bias: bool = False,
561
+ **kwargs: Any,
562
+ ) -> None:
563
  # Earlier mirrors serialized an untrained ESMFold-specific TTT policy.
564
  # It is intentionally ignored because the official checkpoint has no
565
  # trained masked-language-model head.
566
  kwargs.pop("ttt_config", None)
567
  super().__init__(**kwargs)
568
  self.attn_backend = attn_backend
569
+ if classifier_train_scope not in {"probe", "projection"}:
570
+ raise ValueError(
571
+ "classifier_train_scope must be 'probe' or 'projection', got "
572
+ f"{classifier_train_scope!r}."
573
+ )
574
+ self.classifier_train_scope = classifier_train_scope
575
+ self.classifier_pooling_types = (
576
+ ["mean"]
577
+ if classifier_pooling_types is None
578
+ else list(classifier_pooling_types)
579
+ )
580
+ self.classifier_probe_hidden_size = classifier_probe_hidden_size
581
+ self.classifier_probe_num_heads = classifier_probe_num_heads
582
+ self.classifier_probe_dropout = classifier_probe_dropout
583
+ self.classifier_hidden_size = classifier_hidden_size
584
+ self.classifier_dropout = classifier_dropout
585
+ self.classifier_use_bias = classifier_use_bias
586
 
587
 
588
  class FastEsmForProteinFolding(FastPLMsAttentionMixin, EsmForProteinFolding):
 
918
 
919
  del sequence, return_pdb_string
920
  self._ttt_unavailable()
921
+
922
+
923
+ class _FastEsmFoldClassificationMixin:
924
+ """Expose ESMFold's checkpoint-trained residue projection to task probes."""
925
+
926
+ _classifier_probe_class: type[nn.Module]
927
+
928
+ def __init__(self, config: FastEsmFoldConfig) -> None:
929
+ super().__init__(config)
930
+ sequence_state_dim = config.esmfold_config.trunk.sequence_state_dim
931
+ self.classifier = self._classifier_probe_class(config, sequence_state_dim)
932
+ self.classifier.apply(self._init_weights)
933
+ self._configure_classifier_train_scope()
934
+
935
+ def _configure_classifier_train_scope(self) -> None:
936
+ """Apply the serialized fine-tuning boundary deterministically."""
937
+
938
+ self.requires_grad_(False)
939
+ self.classifier.requires_grad_(True)
940
+ if self.config.classifier_train_scope == "projection":
941
+ self.esm_s_combine.requires_grad_(True)
942
+ self.esm_s_mlp.requires_grad_(True)
943
+
944
+ @staticmethod
945
+ def prepare_classifier_inputs(
946
+ sequences: str | list[str],
947
+ ) -> dict[str, torch.Tensor]:
948
+ """Encode single-chain proteins as residue-only ESMFold inputs."""
949
+
950
+ sequence_batch = [sequences] if isinstance(sequences, str) else list(sequences)
951
+ if not sequence_batch:
952
+ raise ValueError("At least one protein sequence is required.")
953
+
954
+ supported_residues = residue_constants.restype_order_with_x
955
+ encoded_sequences: list[torch.Tensor] = []
956
+ for sequence in sequence_batch:
957
+ if not isinstance(sequence, str):
958
+ raise TypeError("Each protein sequence must be a string.")
959
+ normalized = sequence.upper()
960
+ if not normalized:
961
+ raise ValueError("Protein sequences must not be empty.")
962
+ invalid = sorted(set(normalized).difference(supported_residues))
963
+ if invalid:
964
+ raise ValueError(
965
+ "ESMFold classifiers accept single-chain proteins containing "
966
+ "the 20 standard amino acids or X; unsupported residues: "
967
+ f"{', '.join(invalid)}."
968
+ )
969
+ encoded_sequences.append(
970
+ torch.tensor(
971
+ [supported_residues[residue] for residue in normalized],
972
+ dtype=torch.int64,
973
+ )
974
+ )
975
+
976
+ input_ids = collate_dense_tensors(encoded_sequences, pad_v=0)
977
+ attention_mask = collate_dense_tensors(
978
+ [torch.ones_like(sequence) for sequence in encoded_sequences],
979
+ pad_v=0,
980
+ )
981
+ return {"input_ids": input_ids, "attention_mask": attention_mask}
982
+
983
+ def _classifier_features(
984
+ self,
985
+ input_ids: torch.Tensor,
986
+ attention_mask: torch.Tensor | None,
987
+ ) -> tuple[torch.Tensor, torch.Tensor]:
988
+ if input_ids.ndim != 2:
989
+ raise ValueError(
990
+ "input_ids must have shape (batch, residue), got "
991
+ f"{tuple(input_ids.shape)}."
992
+ )
993
+ if attention_mask is None:
994
+ attention_mask = torch.ones_like(input_ids)
995
+ elif attention_mask.shape != input_ids.shape:
996
+ raise ValueError(
997
+ "attention_mask must match input_ids, got "
998
+ f"{tuple(attention_mask.shape)} and {tuple(input_ids.shape)}."
999
+ )
1000
+ attention_mask = attention_mask.to(device=input_ids.device)
1001
+ if torch.any(attention_mask.sum(dim=-1) == 0):
1002
+ raise ValueError("Every classifier input must contain at least one residue.")
1003
+
1004
+ esmaa = self.af2_idx_to_esm_idx(input_ids, attention_mask)
1005
+ layer_states = self.compute_language_model_representations(esmaa)
1006
+ layer_states = layer_states.to(self.esm_s_combine.dtype).detach()
1007
+ if self.config.esmfold_config.esm_ablate_sequence:
1008
+ layer_states = layer_states * 0
1009
+ mixed_states = (
1010
+ self.esm_s_combine.softmax(0).unsqueeze(0) @ layer_states
1011
+ ).squeeze(2)
1012
+ residue_embeddings = self.esm_s_mlp(mixed_states)
1013
+ residue_embeddings = residue_embeddings * attention_mask.unsqueeze(-1).to(
1014
+ residue_embeddings.dtype
1015
+ )
1016
+ return residue_embeddings, attention_mask
1017
+
1018
+ def forward(
1019
+ self,
1020
+ input_ids: torch.Tensor,
1021
+ attention_mask: torch.Tensor | None = None,
1022
+ labels: torch.Tensor | None = None,
1023
+ output_attentions: bool | None = None,
1024
+ output_hidden_states: bool | None = None,
1025
+ return_dict: bool | None = None,
1026
+ ) -> Any:
1027
+ residue_embeddings, attention_mask = self._classifier_features(
1028
+ input_ids,
1029
+ attention_mask,
1030
+ )
1031
+ return self.classifier(
1032
+ residue_embeddings,
1033
+ attention_mask=attention_mask,
1034
+ labels=labels,
1035
+ output_attentions=output_attentions,
1036
+ output_hidden_states=output_hidden_states,
1037
+ return_dict=return_dict,
1038
+ )
1039
+
1040
+
1041
+ class FastEsmForSequenceClassification(
1042
+ _FastEsmFoldClassificationMixin,
1043
+ FastEsmForProteinFolding,
1044
+ ):
1045
+ """Sequence classification or regression over ESMFold residue features."""
1046
+
1047
+ _classifier_probe_class = SequenceClassificationProbe
1048
+
1049
+
1050
+ class FastEsmForTokenClassification(
1051
+ _FastEsmFoldClassificationMixin,
1052
+ FastEsmForProteinFolding,
1053
+ ):
1054
+ """Residue classification or regression over ESMFold residue features."""
1055
+
1056
+ _classifier_probe_class = TokenClassificationProbe
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 != "9a0627bd8e6f68ac389ee97690f802284cffc7e7a7a537880aacd8a7ae440cc3":
16
  raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
17
 
18
  _RUNTIME_TEMPORARIES = []
@@ -179,8 +180,12 @@ def _install_runtime():
179
  return package
180
 
181
  _install_runtime()
182
- _module_181 = _import_without_bytecode("fastplms.models.esmfold.modeling_fast_esmfold")
183
- FastEsmFoldConfig = _module_181.FastEsmFoldConfig
184
  FastEsmFoldConfig.__module__ = __name__
185
- FastEsmForProteinFolding = _module_181.FastEsmForProteinFolding
186
  FastEsmForProteinFolding.__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 != "027d8ff36110255e68e1050fa838f94dc3e5d6255611e73b934d543993cd823a":
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.esmfold.modeling_fast_esmfold")
184
+ FastEsmFoldConfig = _module_182.FastEsmFoldConfig
185
  FastEsmFoldConfig.__module__ = __name__
186
+ FastEsmForProteinFolding = _module_182.FastEsmForProteinFolding
187
  FastEsmForProteinFolding.__module__ = __name__
188
+ FastEsmForSequenceClassification = _module_182.FastEsmForSequenceClassification
189
+ FastEsmForSequenceClassification.__module__ = __name__
190
+ FastEsmForTokenClassification = _module_182.FastEsmForTokenClassification
191
+ FastEsmForTokenClassification.__module__ = __name__