lhallee commited on
Commit
996dc22
·
verified ·
1 Parent(s): ee7f2fd

Update FastPLMs files

Browse files
README.md CHANGED
@@ -10,7 +10,7 @@ tags:
10
 
11
  # Synthyra/ESMplusplus_large
12
 
13
- This checkpoint packages the FastPLMs `ESMC` implementation.
14
 
15
  Accepted inputs are amino-acid sequences tokenized to residue IDs.
16
  Supported Transformers entry points are `AutoConfig`, `AutoModel`,
@@ -28,9 +28,7 @@ Supported Transformers entry points are `AutoConfig`, `AutoModel`,
28
  | Attention variants | Special: SDPA fidelity path; alternate backends have explicit bands |
29
  | Compliance | Declared: exact release evidence is required |
30
 
31
- A supported interface is not a pretrained downstream predictor. Classification
32
- heads start untrained, and declared compliance metadata is not a claim that an
33
- arbitrary local build passed its release gate.
34
 
35
  ## Install and platform requirements
36
 
@@ -41,12 +39,12 @@ python -m pip install -r \
41
  "https://huggingface.co/Synthyra/ESMplusplus_large/resolve/main/requirements.txt"
42
  ```
43
 
44
- The FastPLMs implementation itself is embedded in the model repository and loaded
45
- by Transformers through `trust_remote_code=True`.
46
 
47
- Python 3.11-3.14, PyTorch 2.13, and Transformers 5.13 are required. The artifact requirements include the direct FlashAttention loader dependency. FlashAttention also requires compatible CUDA hardware and BF16 execution. The Hub quick start below requires network
48
- access on first download. For an air-gapped run, first build the manifest-pinned
49
- local artifact and use the offline form shown in the example.
50
 
51
  ## Quick start
52
 
@@ -62,23 +60,23 @@ model = AutoModel.from_pretrained(
62
  ```
63
 
64
  For offline validation, replace `model_id` with the manifest-built
65
- `dist/hub/ESMplusplus_large` path and pass `local_files_only=True`.
66
 
67
  ## Attention and compliance
68
 
69
  The quick start selects `sdpa` explicitly. Declared variants are `eager`, `sdpa`, `flex_attention`, `flash_attention_2`,
70
- `flash_attention_3`. An unavailable requested backend raises instead of
71
- silently switching implementations.
72
- `output_attentions=True` may use the documented, one-call eager fallback solely
73
- to materialize attention tensors; the configured backend remains unchanged.
74
 
75
- This family declares the `compliance` tier. Release evidence binds the exact
76
  checkpoint, backend, dtype, hardware, inputs, and reference revision.
77
 
78
  ## Tokenization and forward inference
79
 
80
- Load the tokenizer from the same artifact as the model. Padding is represented
81
- explicitly by the attention mask:
82
 
83
  ```python
84
  import torch
@@ -103,8 +101,8 @@ print(output.last_hidden_state.shape)
103
 
104
  ## Dataset embeddings
105
 
106
- The shared embedding mixin preserves input order and biological-position
107
- masking. It accepts sequences, identified records, mappings, or a FASTA path:
108
 
109
  ```python
110
  pooled = model.embed_dataset(
@@ -121,12 +119,12 @@ print(residues[0].tensor.shape) # (l, d)
121
  ```
122
 
123
  Set `output` and `format="safetensors"` or `"sqlite"` for transactional,
124
- bounded-memory persistence. Resume verifies input order, model state, tokenizer
125
- policy, backend, dtype, and pooling configuration before appending.
126
 
127
  ## PEFT fine-tuning
128
 
129
- Install the direct training dependencies, then attach LoRA to the loaded checkpoint:
130
 
131
  ```bash
132
  python -m pip install "datasets>=4.8,<5" "peft>=0.19,<0.20"
@@ -145,17 +143,17 @@ peft_model = get_peft_model(
145
  )
146
  ```
147
 
148
- This checkpoint has no advertised classifier. Supply the task-specific
149
- objective and preserve any new head through `modules_to_save`.
150
  All FastPLMs checkpoints follow the Transformers `PreTrainedModel` contract and
151
- can be adapted with PEFT. The ESM2-specific shipped CLI is an example, not a
152
  support boundary. Record the target modules, base revision, data identity, and
153
  trainable parameter scope.
154
 
155
  ## Test-time training
156
 
157
  TTT samples masked views of one protein and updates only injected low-rank
158
- adapters. Base checkpoint weights remain frozen:
159
 
160
  ```python
161
  from transformers import AutoModelForMaskedLM
@@ -173,28 +171,85 @@ ttt_model.ttt_reset()
173
  print(metrics)
174
  ```
175
 
176
- Persisted adapters retain their deterministic reset state. TTT adds latency
177
- and memory, can worsen an output, and does not establish biological function.
178
 
179
  ## ESMC behavior
180
 
181
- This artifact exposes the Biohub ESMC sequence encoder and masked-language-model
182
- head through Transformers. It is also the language-model family used by
183
- ESMFold2. SDPA is the default and the recommended choice for highest numerical
184
- fidelity. Flex Attention and FlashAttention 3 are supported, non-experimental
185
- backends, but their BF16 arithmetic may be numerically divergent from SDPA.
186
- Those deviations produce diagnostic warnings rather than strict parity
187
- failures; dispatch integrity, masks, finite outputs, shapes, and catastrophic
188
- biological disagreement remain hard gates.
189
 
190
  The current GH200/aarch64 release environment validates eager, SDPA, and Flex.
191
- Flash requests fail closed because compatible locked kernels are unavailable
192
- on this platform.
193
 
194
- When `sequence_id` is supplied, it is authoritative for ESMC attention grouping
195
- and padding, and `attention_mask` is ignored. Values greater than or equal to
196
- zero are valid sequence-group IDs; `-1` denotes padding. Omit `sequence_id` to
197
- use `attention_mask` as the padding contract.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  | Backend | Support | Measurement status |
200
  | --- | --- | --- |
@@ -217,7 +272,7 @@ and
217
  - Advertised AutoClasses: `AutoConfig`, `AutoModel`, `AutoModelForMaskedLM`
218
  - AutoClass weight status: `AutoConfig` = `FastPLMs extension`, `AutoModel` = `pretrained`, `AutoModelForMaskedLM` = `pretrained`
219
  - Attention implementations: `eager`, `sdpa`, `flex_attention`, `flash_attention_2`, `flash_attention_3`
220
- - Precision policies: `default`
221
  - BF16 execution: `static_parameters`
222
  - Generation contract: `not_applicable`
223
  - Artifact dependency set: `core`
@@ -229,8 +284,8 @@ and
229
  ## Release record
230
 
231
  - FastPLMs weights: `Synthyra/ESMplusplus_large`
232
- - Runtime revision: recorded separately in the built artifact and published commit
233
- - Source-tree and runtime-bundle SHA-256: recorded in `provenance.json`
234
  - Official checkpoint: `biohub/ESMC-600M`
235
  - Artifact source: `fast`
236
  - State transform: `esmc_to_fastplms_v1`
@@ -238,19 +293,17 @@ and
238
  - Release tiers: `check`, `compliance`, `feature`, `artifact`, `benchmark`
239
  - Unresolved required file identities: `0`
240
 
241
- `provenance.json` records exact file identities, conversion, source revisions,
242
- legal texts, schema, and attestations. A nonzero unresolved count blocks release.
243
 
244
  ## Validation boundary
245
 
246
- Declared tiers compare applicable configuration, tokenizer behavior, state,
247
- and representative inference with the pinned reference. Metadata alone does
248
- not claim a build passed, a backend is faster, or an output is biologically
249
- valid.
250
 
251
  ## License
252
 
253
  Checkpoint terms: MIT. The Hub model-card identifier is
254
- `mit`. Applicable source licenses, notices, attribution,
255
- and conversion records are distributed with the local artifact. Review them
256
- before use.
 
10
 
11
  # Synthyra/ESMplusplus_large
12
 
13
+ This checkpoint contains the FastPLMs `ESMC` implementation.
14
 
15
  Accepted inputs are amino-acid sequences tokenized to residue IDs.
16
  Supported Transformers entry points are `AutoConfig`, `AutoModel`,
 
28
  | Attention variants | Special: SDPA fidelity path; alternate backends have explicit bands |
29
  | Compliance | Declared: exact release evidence is required |
30
 
31
+ A supported interface is not a pretrained downstream predictor. Classification heads start untrained. Compliance metadata does not show that a local build passed its release gate.
 
 
32
 
33
  ## Install and platform requirements
34
 
 
39
  "https://huggingface.co/Synthyra/ESMplusplus_large/resolve/main/requirements.txt"
40
  ```
41
 
42
+ The FastPLMs implementation itself is embedded in the model repository.
43
+ Transformers loads it through `trust_remote_code=True`.
44
 
45
+ This model requires Python 3.11-3.14, PyTorch 2.13, and Transformers 5.13. The artifact requirements include the FlashAttention loader dependency. FlashAttention also requires compatible CUDA hardware and BF16 execution. The Hub quick start needs network access for
46
+ the first download. For an air-gapped run, build the manifest-pinned local
47
+ artifact first and use the offline example.
48
 
49
  ## Quick start
50
 
 
60
  ```
61
 
62
  For offline validation, replace `model_id` with the manifest-built
63
+ `dist/hub/ESMplusplus_large` path. Pass `local_files_only=True`.
64
 
65
  ## Attention and compliance
66
 
67
  The quick start selects `sdpa` explicitly. Declared variants are `eager`, `sdpa`, `flex_attention`, `flash_attention_2`,
68
+ `flash_attention_3`. An unavailable requested backend raises. It does not
69
+ silently change implementation.
70
+ `output_attentions=True` can use the documented one-call eager fallback to
71
+ materialize attention tensors. The configured backend does not change.
72
 
73
+ This family declares the `compliance` tier. Release evidence identifies the
74
  checkpoint, backend, dtype, hardware, inputs, and reference revision.
75
 
76
  ## Tokenization and forward inference
77
 
78
+ Load the tokenizer from the same artifact as the model. The attention mask
79
+ shows padding explicitly:
80
 
81
  ```python
82
  import torch
 
101
 
102
  ## Dataset embeddings
103
 
104
+ The shared embedding mixin keeps input order and biological-position masking.
105
+ It accepts sequences, identified records, mappings, or a FASTA path:
106
 
107
  ```python
108
  pooled = model.embed_dataset(
 
119
  ```
120
 
121
  Set `output` and `format="safetensors"` or `"sqlite"` for transactional,
122
+ bounded-memory storage. Resume checks input order, model state, tokenizer
123
+ policy, backend, dtype, and pooling configuration before it appends data.
124
 
125
  ## PEFT fine-tuning
126
 
127
+ Install the training dependencies. Then attach LoRA to the loaded checkpoint:
128
 
129
  ```bash
130
  python -m pip install "datasets>=4.8,<5" "peft>=0.19,<0.20"
 
143
  )
144
  ```
145
 
146
+ This checkpoint has no advertised classifier. Supply the task objective and
147
+ preserve any new head through `modules_to_save`.
148
  All FastPLMs checkpoints follow the Transformers `PreTrainedModel` contract and
149
+ can use PEFT. The ESM2-specific shipped CLI is an example, not a
150
  support boundary. Record the target modules, base revision, data identity, and
151
  trainable parameter scope.
152
 
153
  ## Test-time training
154
 
155
  TTT samples masked views of one protein and updates only injected low-rank
156
+ adapters. Base checkpoint weights stay frozen:
157
 
158
  ```python
159
  from transformers import AutoModelForMaskedLM
 
171
  print(metrics)
172
  ```
173
 
174
+ Saved adapters retain their deterministic reset state. TTT adds latency and
175
+ memory, can worsen an output, and does not show biological function.
176
 
177
  ## ESMC behavior
178
 
179
+ This artifact provides the Biohub ESMC sequence encoder and masked-language-
180
+ model head through Transformers. ESMFold2 also uses this language-model family.
181
+ SDPA is the default and gives the highest numerical fidelity. Flex Attention and
182
+ FlashAttention 3 are supported non-experimental backends. Their BF16 arithmetic
183
+ can differ numerically from SDPA. These differences give diagnostic warnings,
184
+ not strict-parity failures. Dispatch, masks, finite outputs, shapes, and large
185
+ biological disagreements remain hard gates.
 
186
 
187
  The current GH200/aarch64 release environment validates eager, SDPA, and Flex.
188
+ Flash requests raise because compatible locked kernels are unavailable on this
189
+ platform.
190
 
191
+ When `sequence_id` is supplied, it controls ESMC attention groups and padding.
192
+ `attention_mask` is ignored. Values greater than or equal to zero are valid
193
+ sequence-group IDs. `-1` marks padding. Omit `sequence_id` to use
194
+ `attention_mask` for padding.
195
+
196
+ ### Hidden-state sparse autoencoders
197
+
198
+ ESM++ supports hidden-state SAEs from the official
199
+ [Biohub ESMC SAE collection](https://huggingface.co/collections/biohub/esmc-saes-for-hidden-states-all-layers).
200
+ Select an SAE for this ESMC scale. Load only required layers. Then attach them
201
+ to the model:
202
+
203
+ ```python
204
+ import torch
205
+ from transformers import AutoModel
206
+
207
+ sae = AutoModel.from_pretrained("biohub/ESMC-600M-sae-layer27-k64-codebook65536", device=model.device)
208
+ sae.initialize_layers([27])
209
+ model.add_sae_models([sae.layers["27"]])
210
+
211
+ with torch.inference_mode():
212
+ output = model(**batch, normalize_sae=True)
213
+
214
+ features = output.sae_outputs["layer27"]
215
+ print(features.shape, features.layout) # (valid_token_count, codebook_dim), sparse COO
216
+ ```
217
+
218
+ SAEs run after you attach them. Use `compute_sae=False` to skip SAE work.
219
+ Outputs are detached sparse tensors with keys such as `layer{N}`. They omit
220
+ padding. The model uses `sequence_id`, then `attention_mask`, for padding.
221
+ `normalize_sae=True` uses Biohub `(features / max) * idf` normalization. SAE
222
+ computation requires `input_ids`. It rejects mask tokens because Biohub trained
223
+ the SAEs with unmasked sequences. This interface supports hidden-state SAEs
224
+ only, not MLP-output SAEs. FastPLMs does not copy SAE weights or add SAE
225
+ checkpoints to its model manifest.
226
+
227
+ ### Experimental FP8 inference
228
+
229
+ The default uses checkpoint BF16 behavior. FP8 is an explicit experimental
230
+ inference option for every ESM++ scale:
231
+
232
+ ```python
233
+ import torch
234
+ from transformers import AutoModel
235
+
236
+ fp8_model = AutoModel.from_pretrained(
237
+ "Synthyra/ESMplusplus_large",
238
+ trust_remote_code=True,
239
+ dtype=torch.bfloat16,
240
+ ).cuda().eval()
241
+ fp8_model.enable_fp8()
242
+ print(fp8_model.esmc_precision_status)
243
+
244
+ with torch.inference_mode():
245
+ fp8_output = fp8_model(**{name: value.cuda() for name, value in batch.items()})
246
+ ```
247
+
248
+ FP8 forward calls require `torch.inference_mode()`. The model pads the sequence
249
+ dimension to a multiple of 16. Transformer Engine converts supported linear
250
+ layers. The call fails if the dependency, compatible CUDA hardware, or complete
251
+ conversion set is unavailable. It does not silently use BF16. FP8 does not
252
+ claim numerical parity.
253
 
254
  | Backend | Support | Measurement status |
255
  | --- | --- | --- |
 
272
  - Advertised AutoClasses: `AutoConfig`, `AutoModel`, `AutoModelForMaskedLM`
273
  - AutoClass weight status: `AutoConfig` = `FastPLMs extension`, `AutoModel` = `pretrained`, `AutoModelForMaskedLM` = `pretrained`
274
  - Attention implementations: `eager`, `sdpa`, `flex_attention`, `flash_attention_2`, `flash_attention_3`
275
+ - Precision policies: `default`, `fp8` (experimental)
276
  - BF16 execution: `static_parameters`
277
  - Generation contract: `not_applicable`
278
  - Artifact dependency set: `core`
 
284
  ## Release record
285
 
286
  - FastPLMs weights: `Synthyra/ESMplusplus_large`
287
+ - Runtime revision: recorded in the built artifact and published commit
288
+ - Source-tree and runtime-bundle SHA-256: recorded in the source record
289
  - Official checkpoint: `biohub/ESMC-600M`
290
  - Artifact source: `fast`
291
  - State transform: `esmc_to_fastplms_v1`
 
293
  - Release tiers: `check`, `compliance`, `feature`, `artifact`, `benchmark`
294
  - Unresolved required file identities: `0`
295
 
296
+ The source record records exact file identities, conversion, source revisions,
297
+ legal texts, schema, and attestations. A nonzero unresolved count blocks a release.
298
 
299
  ## Validation boundary
300
 
301
+ Declared tiers compare configuration, tokenizer behavior, state, and
302
+ representative inference with the pinned reference. Metadata does not show that
303
+ a build passed, that a backend is faster, or that an output is biologically valid.
 
304
 
305
  ## License
306
 
307
  Checkpoint terms: MIT. The Hub model-card identifier is
308
+ `mit`. The local artifact contains applicable source
309
+ licenses, notices, attribution, and conversion records. Review them before use.
 
THIRD_PARTY_NOTICES.md CHANGED
@@ -46,7 +46,7 @@ explicitly defines the repository release as including pretrained DPLM1 and
46
  DPLM2 weights, and the same revision carries the complete
47
  [Apache-2.0 license](https://github.com/bytedance/dplm/blob/8a2e15e53416b4536f03f79ad1f6f6a9cbd5e19d/LICENSE).
48
  FastPLMs records both checkpoint families as Apache-2.0 and distributes the
49
- verbatim license plus `LICENSES/dplm/PROVENANCE.md`. Converted weights retain
50
  those terms and remain subject to the ordinary artifact and publication gates.
51
 
52
  ## Biohub
@@ -80,7 +80,7 @@ TorchMetrics, Lightning Utilities, and NVIDIA DLLogger. Their exact versions or
80
  revision are pinned in `docker/constraints/esmfold.txt`; OpenFold imports them
81
  eagerly, and FastPLMs production code does not depend on them. DLLogger's exact
82
  source identity and installed-license handling are recorded in
83
- `LICENSES/dllogger/PROVENANCE.md`.
84
 
85
  ## ProteinTTT
86
 
@@ -93,7 +93,7 @@ revision-specific provenance are under `LICENSES/protein-ttt/`.
93
  For every supported family, `src/fastplms/models.toml` records an immutable
94
  official checkpoint revision, an immutable FastPLMs checkpoint revision, file
95
  digests, a named state transformation, and a mechanism-level conversion record.
96
- Generated artifacts reproduce that record in `provenance.json`. A release or
97
  artifact build must fail when a required file identity, legal text, attribution
98
  notice, modified-file notice, upstream revision, or conversion record is absent
99
  or differs from its manifest digest.
 
46
  DPLM2 weights, and the same revision carries the complete
47
  [Apache-2.0 license](https://github.com/bytedance/dplm/blob/8a2e15e53416b4536f03f79ad1f6f6a9cbd5e19d/LICENSE).
48
  FastPLMs records both checkpoint families as Apache-2.0 and distributes the
49
+ verbatim license plus `LICENSES/dplm/SOURCE_RECORD.md`. Converted weights retain
50
  those terms and remain subject to the ordinary artifact and publication gates.
51
 
52
  ## Biohub
 
80
  revision are pinned in `docker/constraints/esmfold.txt`; OpenFold imports them
81
  eagerly, and FastPLMs production code does not depend on them. DLLogger's exact
82
  source identity and installed-license handling are recorded in
83
+ `LICENSES/dllogger/SOURCE_RECORD.md`.
84
 
85
  ## ProteinTTT
86
 
 
93
  For every supported family, `src/fastplms/models.toml` records an immutable
94
  official checkpoint revision, an immutable FastPLMs checkpoint revision, file
95
  digests, a named state transformation, and a mechanism-level conversion record.
96
+ Generated artifacts reproduce that record in `source-record.json`. A release or
97
  artifact build must fail when a required file identity, legal text, attribution
98
  notice, modified-file notice, upstream revision, or conversion record is absent
99
  or differs from its manifest digest.
fastplms/models.toml CHANGED
@@ -88,7 +88,7 @@ license_files = ["LICENSE"]
88
  license_digests = ["LICENSE=sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"]
89
  distribution_files = [
90
  "LICENSE=sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
91
- "PROVENANCE.md=sha256:a659f74be9073cf1ad2d2f7071531ca56959b421f111152cf4c41184ace5970e",
92
  ]
93
 
94
  [[upstreams]]
@@ -122,7 +122,7 @@ license_files = ["LICENSE"]
122
  license_digests = ["LICENSE=sha256:da6d3703ed11cbe42bd212c725957c98da23cbff1998c05fa4b3d976d1a58e93"]
123
  distribution_files = [
124
  "LICENSE=sha256:da6d3703ed11cbe42bd212c725957c98da23cbff1998c05fa4b3d976d1a58e93",
125
- "PROVENANCE.md=sha256:950adb94daf15e646ddf226dacfe2a8e77801aa0793e439a9a3490a48eb666e7",
126
  ]
127
 
128
  [[upstreams]]
@@ -136,7 +136,7 @@ license_digests = ["LICENSE=sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c1
136
  distribution_files = [
137
  "LICENSE=sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
138
  "MODIFICATIONS.md=sha256:fd6f0aa1086a0c996cf967b326d18e965660cda0ad5c7f36a3474a8490720da3",
139
- "PROVENANCE.md=sha256:48c903db43a217a3126afaefbac60b7ddac7efda2dfcc0cbff0bffc7d6c30081",
140
  ]
141
 
142
  [[upstreams]]
@@ -149,7 +149,7 @@ license_files = ["LICENSE"]
149
  license_digests = ["LICENSE=sha256:bb01e7d5554f9e2e117172e56551452f68a7818df7bc8e71cd7a776a1d4ba3df"]
150
  distribution_files = [
151
  "LICENSE=sha256:bb01e7d5554f9e2e117172e56551452f68a7818df7bc8e71cd7a776a1d4ba3df",
152
- "PROVENANCE.md=sha256:dc641c37353c2efd50ccbdb316ca4aae495ec02c1563e0e15bac92f75fc482e5",
153
  ]
154
 
155
  [families.esm2]
@@ -187,7 +187,8 @@ reference_adapter = "tests.parity.support.reference_adapters.esm_plusplus"
187
  attention = ["eager", "sdpa", "flex_attention", "flash_attention_2", "flash_attention_3"]
188
  dtypes = ["float32", "bfloat16"]
189
  bf16_execution = "static_parameters"
190
- precisions = ["default"]
 
191
  vram_tier = "sequence"
192
  checkpoint_license = "MIT"
193
  hub_license = "mit"
@@ -267,7 +268,7 @@ checkpoint_license = "Apache-2.0"
267
  hub_license = "apache-2.0"
268
  weights_publication_allowed = true
269
  state_transform = "dplm_to_fastplms_v1"
270
- conversion_provenance = "Input: the pinned official DPLM1 checkpoint. Transformation: apply dplm_to_fastplms_v1, omitting the unused absolute-position table for rotary checkpoints and materializing the tied input/output embedding values as independent tensors. Output: the pinned Synthyra DPLM checkpoint. Validation: release parity compares exact state identity after the declared transform, tokenizer behavior, generation, and inference. License basis: the pinned ByteDance DPLM Apache-2.0 LICENSE and README explicitly scope the repository release to the pretrained DPLM1 and DPLM2 weights; immutable evidence is recorded in LICENSES/dplm/PROVENANCE.md. Limitation: redistribution remains subject to Apache-2.0 and the pinned source record; no broader rights are inferred."
271
  representative = "dplm_150m"
272
  documentation = "docs/models.md#dplm"
273
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
@@ -291,7 +292,7 @@ checkpoint_license = "Apache-2.0"
291
  hub_license = "apache-2.0"
292
  weights_publication_allowed = true
293
  state_transform = "dplm2_to_fastplms_v1"
294
- conversion_provenance = "Input: the pinned official DPLM2 checkpoint. Transformation: apply dplm2_to_fastplms_v1, retaining the independent language-model head and trained encoder contact head while omitting the unused absolute-position table for rotary checkpoints. Output: the pinned Synthyra DPLM2 checkpoint. Validation: release parity compares exact keys and values after the declared omission, non-aliasing, tokenizer behavior, generation, and inference. License basis: the pinned ByteDance DPLM Apache-2.0 LICENSE and README explicitly scope the repository release to the pretrained DPLM1 and DPLM2 weights; immutable evidence is recorded in LICENSES/dplm/PROVENANCE.md. Limitation: no head exception is permitted by this source record, and redistribution remains subject to Apache-2.0."
295
  representative = "dplm2_150m"
296
  documentation = "docs/models.md#dplm2"
297
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
 
88
  license_digests = ["LICENSE=sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"]
89
  distribution_files = [
90
  "LICENSE=sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
91
+ "SOURCE_RECORD.md=sha256:a659f74be9073cf1ad2d2f7071531ca56959b421f111152cf4c41184ace5970e",
92
  ]
93
 
94
  [[upstreams]]
 
122
  license_digests = ["LICENSE=sha256:da6d3703ed11cbe42bd212c725957c98da23cbff1998c05fa4b3d976d1a58e93"]
123
  distribution_files = [
124
  "LICENSE=sha256:da6d3703ed11cbe42bd212c725957c98da23cbff1998c05fa4b3d976d1a58e93",
125
+ "SOURCE_RECORD.md=sha256:950adb94daf15e646ddf226dacfe2a8e77801aa0793e439a9a3490a48eb666e7",
126
  ]
127
 
128
  [[upstreams]]
 
136
  distribution_files = [
137
  "LICENSE=sha256:cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
138
  "MODIFICATIONS.md=sha256:fd6f0aa1086a0c996cf967b326d18e965660cda0ad5c7f36a3474a8490720da3",
139
+ "SOURCE_RECORD.md=sha256:48c903db43a217a3126afaefbac60b7ddac7efda2dfcc0cbff0bffc7d6c30081",
140
  ]
141
 
142
  [[upstreams]]
 
149
  license_digests = ["LICENSE=sha256:bb01e7d5554f9e2e117172e56551452f68a7818df7bc8e71cd7a776a1d4ba3df"]
150
  distribution_files = [
151
  "LICENSE=sha256:bb01e7d5554f9e2e117172e56551452f68a7818df7bc8e71cd7a776a1d4ba3df",
152
+ "SOURCE_RECORD.md=sha256:dc641c37353c2efd50ccbdb316ca4aae495ec02c1563e0e15bac92f75fc482e5",
153
  ]
154
 
155
  [families.esm2]
 
187
  attention = ["eager", "sdpa", "flex_attention", "flash_attention_2", "flash_attention_3"]
188
  dtypes = ["float32", "bfloat16"]
189
  bf16_execution = "static_parameters"
190
+ precisions = ["default", "fp8"]
191
+ experimental_precisions = ["fp8"]
192
  vram_tier = "sequence"
193
  checkpoint_license = "MIT"
194
  hub_license = "mit"
 
268
  hub_license = "apache-2.0"
269
  weights_publication_allowed = true
270
  state_transform = "dplm_to_fastplms_v1"
271
+ conversion_provenance = "Input: the pinned official DPLM1 checkpoint. Transformation: apply dplm_to_fastplms_v1, omitting the unused absolute-position table for rotary checkpoints and materializing the tied input/output embedding values as independent tensors. Output: the pinned Synthyra DPLM checkpoint. Validation: release parity compares exact state identity after the declared transform, tokenizer behavior, generation, and inference. License basis: the pinned ByteDance DPLM Apache-2.0 LICENSE and README explicitly scope the repository release to the pretrained DPLM1 and DPLM2 weights; immutable evidence is recorded in LICENSES/dplm/SOURCE_RECORD.md. Limitation: redistribution remains subject to Apache-2.0 and the pinned source record; no broader rights are inferred."
272
  representative = "dplm_150m"
273
  documentation = "docs/models.md#dplm"
274
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
 
292
  hub_license = "apache-2.0"
293
  weights_publication_allowed = true
294
  state_transform = "dplm2_to_fastplms_v1"
295
+ conversion_provenance = "Input: the pinned official DPLM2 checkpoint. Transformation: apply dplm2_to_fastplms_v1, retaining the independent language-model head and trained encoder contact head while omitting the unused absolute-position table for rotary checkpoints. Output: the pinned Synthyra DPLM2 checkpoint. Validation: release parity compares exact keys and values after the declared omission, non-aliasing, tokenizer behavior, generation, and inference. License basis: the pinned ByteDance DPLM Apache-2.0 LICENSE and README explicitly scope the repository release to the pretrained DPLM1 and DPLM2 weights; immutable evidence is recorded in LICENSES/dplm/SOURCE_RECORD.md. Limitation: no head exception is permitted by this source record, and redistribution remains subject to Apache-2.0."
296
  representative = "dplm2_150m"
297
  documentation = "docs/models.md#dplm2"
298
  test_tiers = ["check", "compliance", "feature", "artifact", "benchmark"]
fastplms/models/esm_plusplus/modeling_esm_plusplus.py CHANGED
@@ -2,13 +2,17 @@
2
 
3
  from __future__ import annotations
4
 
 
 
5
  import math
 
 
 
 
 
6
  import torch
7
  import torch.nn as nn
8
  import torch.nn.functional as F
9
- from dataclasses import dataclass
10
- from functools import partial
11
- from typing import ClassVar
12
  from einops import rearrange
13
  from tokenizers import Tokenizer
14
  from tokenizers.models import BPE
@@ -61,6 +65,151 @@ except ModuleNotFoundError as error:
61
  # Legacy flat Hub composites define every shared symbol above this block.
62
 
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  class ESMplusplusConfig(PretrainedConfig):
65
  """Configuration class for ESM++ model.
66
 
@@ -616,6 +765,8 @@ class TransformerOutput(ModelOutput):
616
  hidden_states: tuple[torch.Tensor] | None = None
617
  attentions: tuple[torch.Tensor] | None = None
618
  s_max: tuple[list[torch.Tensor], ...] | None = None
 
 
619
 
620
 
621
  @dataclass
@@ -624,6 +775,7 @@ class ESMplusplusOutput(MaskedLMOutput):
624
 
625
  s_max: tuple[list[torch.Tensor], ...] | None = None
626
  last_hidden_state: torch.Tensor | None = None
 
627
 
628
 
629
  @dataclass
@@ -631,6 +783,7 @@ class ESMplusplusSequenceClassifierOutput(SequenceClassifierOutput):
631
  """Sequence-classification output with optional attention diagnostics."""
632
 
633
  s_max: tuple[list[torch.Tensor], ...] | None = None
 
634
 
635
 
636
  @dataclass
@@ -638,6 +791,7 @@ class ESMplusplusTokenClassifierOutput(TokenClassifierOutput):
638
  """Token-classification output with optional attention diagnostics."""
639
 
640
  s_max: tuple[list[torch.Tensor], ...] | None = None
 
641
 
642
 
643
  class TransformerStack(nn.Module):
@@ -688,63 +842,30 @@ class TransformerStack(nn.Module):
688
  output_attentions: bool | None = False,
689
  output_s_max: bool | None = False,
690
  esmfold2_hidden_states: bool = False,
 
691
  ) -> TransformerOutput:
692
  # x: (b, l, d); attention_mask, sequence_id: (b, l)
693
  hidden_states = () if output_hidden_states else None
694
  attentions = () if output_attentions else None
695
  full_s_max = () if output_s_max else None
 
 
696
  # Match the pinned Biohub Transformers contract: a supplied sequence_id
697
  # is authoritative and must encode padding as -1. attention_mask is
698
  # ignored in that mode rather than intersected with the chain mask.
699
- if sequence_id is None and attention_mask is not None:
700
- expected_shape = (x.shape[0], x.shape[1])
701
- if attention_mask.ndim != 2 or tuple(attention_mask.shape) != expected_shape:
702
- raise ValueError(
703
- f"attention_mask must have shape {expected_shape}; "
704
- f"received {tuple(attention_mask.shape)}."
705
- )
706
- attention_mask = attention_mask.to(device=x.device, dtype=torch.bool)
707
- if not bool(attention_mask.any(dim=1).all()):
708
- raise ValueError("attention_mask must keep at least one valid key per batch row.")
709
- effective_backend = resolve_attention_backend_for_call(
710
- self.attention_backend,
711
- output_attentions=bool(output_attentions),
712
- )
713
-
714
- if sequence_id is None and attention_mask is not None:
715
- attention_mask_2d, attention_mask_4d, flex_block_mask = (
716
- self._sequence_id_attention_masks(
717
- sequence_id=attention_mask.to(device=x.device, dtype=torch.bool),
718
- batch_size=x.shape[0],
719
- seq_len=x.shape[1],
720
- device=x.device,
721
- dtype=x.dtype,
722
- effective_backend=effective_backend,
723
- )
724
- )
725
- elif sequence_id is None:
726
- attention_mask_2d, attention_mask_4d, flex_block_mask = get_attention_mask(
727
- effective_backend=effective_backend,
728
  batch_size=x.shape[0],
729
  seq_len=x.shape[1],
730
  device=x.device,
731
- attention_mask=attention_mask,
732
  dtype=x.dtype,
733
- mask_semantics="padding",
734
- )
735
- else:
736
- attention_mask_2d, attention_mask_4d, flex_block_mask = (
737
- self._sequence_id_attention_masks(
738
- sequence_id=sequence_id,
739
- batch_size=x.shape[0],
740
- seq_len=x.shape[1],
741
- device=x.device,
742
- dtype=x.dtype,
743
- effective_backend=effective_backend,
744
- )
745
  )
 
746
 
747
- for block in self.blocks:
748
  if output_hidden_states:
749
  if hidden_states is None:
750
  raise RuntimeError(
@@ -754,6 +875,8 @@ class TransformerStack(nn.Module):
754
  # by the final normalized state. This gives n_layers + 1 states
755
  # and, for ESMC-6B, the 81-state order consumed by ESMFold2.
756
  hidden_states += (x,)
 
 
757
  if self.gradient_checkpointing and self.training:
758
  x, attn_weights, s_max = self._gradient_checkpointing_func(
759
  block.__call__,
@@ -782,52 +905,85 @@ class TransformerStack(nn.Module):
782
  last_hidden_state = self.norm(x)
783
  if output_hidden_states:
784
  hidden_states += (last_hidden_state,)
 
 
 
785
 
786
  return TransformerOutput(
787
  last_hidden_state=last_hidden_state,
788
  hidden_states=hidden_states,
789
  attentions=attentions,
790
  s_max=full_s_max,
 
791
  )
792
 
793
- def _sequence_id_attention_masks(
 
794
  self,
795
- sequence_id: torch.Tensor,
 
796
  batch_size: int,
797
  seq_len: int,
798
  device: torch.device,
799
  dtype: torch.dtype | None = None,
800
  effective_backend: AttentionBackend | None = None,
 
801
  ) -> tuple[torch.Tensor | None, torch.Tensor | None, BlockMask | None]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
802
  expected_shape = (batch_size, seq_len)
803
- if sequence_id.ndim != 2 or tuple(sequence_id.shape) != expected_shape:
804
  raise ValueError(
805
- f"sequence_id must have shape {expected_shape}; "
806
- f"received {tuple(sequence_id.shape)}."
807
  )
808
- if sequence_id.device != device:
809
- sequence_id = sequence_id.to(device=device)
810
- backend = (
811
- self.attention_backend
812
- if effective_backend is None
813
- else resolve_attention_backend(effective_backend)
814
  )
815
- if sequence_id.dtype == torch.bool:
816
- attention_mask_2d = sequence_id
 
 
817
  # Biohub's boolean single-chain form groups biological positions
818
  # together and padding positions together. Padding queries remain
819
  # finite without allowing their states to enter residue attention.
820
- attention_mask_4d = sequence_id[:, None, :, None] == sequence_id[:, None, None, :]
 
 
821
  else:
822
- attention_mask_2d = sequence_id != -1
823
- attention_mask_4d = (sequence_id.unsqueeze(-1) == sequence_id.unsqueeze(-2)).unsqueeze(
824
- 1
 
 
 
 
825
  )
826
- if not bool(attention_mask_2d.any(dim=1).all()):
827
- raise ValueError("attention_mask must keep at least one valid key per batch row.")
 
828
 
829
  if backend.is_flash:
830
- if sequence_id.dtype != torch.bool:
831
  raise ValueError(
832
  "ESM++ FlashAttention only supports boolean sequence_id padding masks. "
833
  "Use eager, sdpa, or flex_attention for chain-aware integer sequence_id "
@@ -836,22 +992,22 @@ class TransformerStack(nn.Module):
836
  return attention_mask_2d, attention_mask_4d, None
837
 
838
  if backend == AttentionBackend.FLEX:
839
- if sequence_id.dtype == torch.bool:
840
 
841
  def mask_mod(batch_idx, head_idx, q_idx, kv_idx):
842
  del head_idx
843
- return sequence_id[batch_idx, q_idx] == sequence_id[batch_idx, kv_idx]
844
 
845
  else:
846
 
847
  def mask_mod(batch_idx, head_idx, q_idx, kv_idx):
848
  del head_idx
849
- q_id = sequence_id[batch_idx, q_idx]
850
- kv_id = sequence_id[batch_idx, kv_idx]
851
  return q_id == kv_id
852
 
853
  flex_block_mask = _get_flex_block_mask(
854
- mask_pattern=sequence_id,
855
  batch_size=batch_size,
856
  query_length=seq_len,
857
  key_value_length=seq_len,
@@ -859,7 +1015,7 @@ class TransformerStack(nn.Module):
859
  dtype=dtype,
860
  mask_semantics=(
861
  "boolean_sequence_id"
862
- if sequence_id.dtype == torch.bool
863
  else "integer_sequence_id"
864
  ),
865
  mask_mod=mask_mod,
@@ -889,6 +1045,237 @@ class PreTrainedESMplusplusModel(FastPLMsAttentionMixin, PreTrainedModel):
889
  "flash_attention_3",
890
  )
891
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  @property
893
  def tokenizer(self) -> EsmSequenceTokenizer:
894
  """Construct the sequence tokenizer only when a raw-sequence API needs it."""
@@ -1033,6 +1420,8 @@ class ESMplusplusModel(PreTrainedESMplusplusModel, EmbeddingMixin):
1033
  output_s_max: bool | None = False,
1034
  esmfold2_hidden_states: bool = False,
1035
  return_dict: bool | None = None,
 
 
1036
  ) -> TransformerOutput | tuple[torch.Tensor, ...]:
1037
  """Run ESMC inference with the pinned Biohub mask precedence.
1038
 
@@ -1056,25 +1445,47 @@ class ESMplusplusModel(PreTrainedESMplusplusModel, EmbeddingMixin):
1056
  )
1057
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1058
 
 
 
 
1059
  if attention_mask is None and sequence_id is None and input_ids is not None:
1060
  attention_mask = input_ids.ne(self.config.pad_token_id)
 
 
 
 
 
 
1061
 
1062
  x = self.embed(input_ids) if inputs_embeds is None else inputs_embeds
1063
 
1064
- transformer_output = self.transformer(
1065
- x=x,
1066
- attention_mask=attention_mask,
1067
- sequence_id=sequence_id,
1068
- output_hidden_states=output_hidden_states,
1069
- output_attentions=output_attentions,
1070
- output_s_max=output_s_max,
1071
- esmfold2_hidden_states=esmfold2_hidden_states,
 
 
 
 
 
 
 
 
 
 
 
1072
  )
 
1073
  result = TransformerOutput(
1074
  last_hidden_state=transformer_output.last_hidden_state,
1075
  hidden_states=transformer_output.hidden_states,
1076
  attentions=transformer_output.attentions,
1077
  s_max=transformer_output.s_max,
 
1078
  )
1079
  return result if return_dict else result.to_tuple()
1080
 
@@ -1158,6 +1569,8 @@ class ESMplusplusForMaskedLM(
1158
  esmfold2_hidden_states: bool = False,
1159
  return_dict: bool | None = None,
1160
  compute_logits: bool = True,
 
 
1161
  ) -> ESMplusplusOutput | tuple[torch.Tensor, ...]:
1162
  if input_ids is None and inputs_embeds is None:
1163
  raise ValueError("You have to specify either input_ids or inputs_embeds")
@@ -1174,20 +1587,41 @@ class ESMplusplusForMaskedLM(
1174
  else self.config.output_hidden_states
1175
  )
1176
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
 
 
 
1177
  if attention_mask is None and sequence_id is None and input_ids is not None:
1178
  attention_mask = input_ids.ne(self.config.pad_token_id)
 
 
 
 
 
 
1179
 
1180
  x = self.embed(input_ids) if inputs_embeds is None else inputs_embeds
1181
 
1182
- output = self.transformer(
1183
- x=x,
1184
- attention_mask=attention_mask,
1185
- sequence_id=sequence_id,
1186
- output_hidden_states=output_hidden_states,
1187
- output_attentions=output_attentions,
1188
- output_s_max=output_s_max,
1189
- esmfold2_hidden_states=esmfold2_hidden_states,
 
 
 
 
 
 
 
 
 
 
 
1190
  )
 
1191
 
1192
  last_hidden_state = output.last_hidden_state
1193
  logits = self.sequence_head(last_hidden_state) if compute_logits else None
@@ -1205,6 +1639,7 @@ class ESMplusplusForMaskedLM(
1205
  attentions=output.attentions,
1206
  s_max=output.s_max,
1207
  last_hidden_state=last_hidden_state,
 
1208
  )
1209
  return result if return_dict else result.to_tuple()
1210
 
@@ -1280,6 +1715,8 @@ class ESMplusplusForSequenceClassification(ESMplusplusForMaskedLM, EmbeddingMixi
1280
  output_hidden_states: bool | None = None,
1281
  output_s_max: bool | None = False,
1282
  return_dict: bool | None = None,
 
 
1283
  ) -> ESMplusplusSequenceClassifierOutput | tuple[torch.Tensor, ...]:
1284
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1285
  pooling_mask = attention_mask
@@ -1310,6 +1747,8 @@ class ESMplusplusForSequenceClassification(ESMplusplusForMaskedLM, EmbeddingMixi
1310
  output_s_max=output_s_max,
1311
  return_dict=True,
1312
  compute_logits=False,
 
 
1313
  )
1314
 
1315
  last_hidden_state = output.last_hidden_state
@@ -1345,6 +1784,7 @@ class ESMplusplusForSequenceClassification(ESMplusplusForMaskedLM, EmbeddingMixi
1345
  hidden_states=output.hidden_states,
1346
  attentions=output.attentions,
1347
  s_max=output.s_max,
 
1348
  )
1349
  return result if return_dict else result.to_tuple()
1350
 
@@ -1399,6 +1839,8 @@ class ESMplusplusForTokenClassification(ESMplusplusForMaskedLM, EmbeddingMixin):
1399
  output_hidden_states: bool | None = None,
1400
  output_s_max: bool | None = False,
1401
  return_dict: bool | None = None,
 
 
1402
  ) -> ESMplusplusTokenClassifierOutput | tuple[torch.Tensor, ...]:
1403
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1404
  output = super().forward(
@@ -1412,6 +1854,8 @@ class ESMplusplusForTokenClassification(ESMplusplusForMaskedLM, EmbeddingMixin):
1412
  output_s_max=output_s_max,
1413
  return_dict=True,
1414
  compute_logits=False,
 
 
1415
  )
1416
 
1417
  last_hidden_state = output.last_hidden_state
@@ -1427,6 +1871,7 @@ class ESMplusplusForTokenClassification(ESMplusplusForMaskedLM, EmbeddingMixin):
1427
  hidden_states=output.hidden_states,
1428
  attentions=output.attentions,
1429
  s_max=output.s_max,
 
1430
  )
1431
  return result if return_dict else result.to_tuple()
1432
 
 
2
 
3
  from __future__ import annotations
4
 
5
+ import importlib
6
+ import importlib.metadata
7
  import math
8
+ from contextlib import contextmanager
9
+ from dataclasses import asdict, dataclass
10
+ from functools import partial
11
+ from typing import Any, ClassVar
12
+
13
  import torch
14
  import torch.nn as nn
15
  import torch.nn.functional as F
 
 
 
16
  from einops import rearrange
17
  from tokenizers import Tokenizer
18
  from tokenizers.models import BPE
 
65
  # Legacy flat Hub composites define every shared symbol above this block.
66
 
67
 
68
+ _ESMC_FP8_LINEAR_SUFFIX = ".attn.out_proj"
69
+ _ESMC_FP8_ALIGNMENT = 16
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class ESMplusplusFP8Status:
74
+ """Resolved state of the explicit Transformer Engine ESMC FP8 path."""
75
+
76
+ enabled: bool
77
+ reason: str
78
+ device: str
79
+ transformer_engine_version: str | None
80
+ converted_projections: int
81
+
82
+ def as_dict(self) -> dict[str, str | int | bool | None]:
83
+ return asdict(self)
84
+
85
+
86
+ def _transformer_engine_version() -> str | None:
87
+ try:
88
+ return importlib.metadata.version("transformer-engine")
89
+ except importlib.metadata.PackageNotFoundError:
90
+ return None
91
+
92
+
93
+ def _load_transformer_engine() -> tuple[Any, Any]:
94
+ """Load Transformer Engine lazily so ordinary ESM++ imports stay portable."""
95
+
96
+ try:
97
+ te = importlib.import_module("transformer_engine.pytorch")
98
+ recipe = importlib.import_module("transformer_engine.common.recipe")
99
+ except (ImportError, OSError, RuntimeError) as error:
100
+ raise RuntimeError(
101
+ f"Transformer Engine could not be imported: {type(error).__name__}: {error}"
102
+ ) from error
103
+ if not hasattr(recipe, "Float8CurrentScaling"):
104
+ raise RuntimeError(
105
+ "Transformer Engine does not expose Float8CurrentScaling, which is "
106
+ "required by the validated ESMC FP8 path."
107
+ )
108
+ return te, recipe
109
+
110
+
111
+ def _te_fp8_capability(device: torch.device) -> tuple[bool, str]:
112
+ """Return whether the strict Transformer Engine FP8 path can run."""
113
+
114
+ if device.type != "cuda":
115
+ return False, "FP8 requires ESM++ on a CUDA device."
116
+ if not torch.cuda.is_available():
117
+ return False, "CUDA is unavailable."
118
+ try:
119
+ major, minor = torch.cuda.get_device_capability(device)
120
+ except (AssertionError, RuntimeError, ValueError) as error:
121
+ return False, f"CUDA capability query failed: {error}"
122
+ if not (major >= 9 or (major == 8 and minor >= 9)):
123
+ return False, f"CUDA capability {major}.{minor} does not support FP8."
124
+ try:
125
+ te, _ = _load_transformer_engine()
126
+ except RuntimeError as error:
127
+ return False, str(error)
128
+
129
+ probe = getattr(te, "is_fp8_available", None)
130
+ if probe is None:
131
+ try:
132
+ probe = importlib.import_module("transformer_engine.pytorch.fp8").is_fp8_available
133
+ except (ImportError, AttributeError, OSError, RuntimeError) as error:
134
+ return False, f"Transformer Engine has no usable FP8 probe: {error}"
135
+ try:
136
+ try:
137
+ result = probe(return_reason=True)
138
+ except TypeError:
139
+ result = probe()
140
+ except (OSError, RuntimeError) as error:
141
+ return False, f"Transformer Engine FP8 probe failed: {error}"
142
+ if isinstance(result, tuple):
143
+ available = bool(result[0])
144
+ detail = str(result[1]) if len(result) > 1 and result[1] else ""
145
+ else:
146
+ available = bool(result)
147
+ detail = ""
148
+ if not available:
149
+ return False, detail or "Transformer Engine reports FP8 unavailable."
150
+ return True, "Transformer Engine reports FP8 availability."
151
+
152
+
153
+ def _convert_esmc_attention_outputs_to_te(
154
+ module: nn.Module,
155
+ *,
156
+ expected_projections: int,
157
+ ) -> tuple[str, ...]:
158
+ """Replace exactly one attention output projection per ESMC block."""
159
+
160
+ targets = [
161
+ (path, child)
162
+ for path, child in module.named_modules()
163
+ if isinstance(child, nn.Linear) and path.endswith(_ESMC_FP8_LINEAR_SUFFIX)
164
+ ]
165
+ if len(targets) != expected_projections:
166
+ raise RuntimeError(
167
+ "ESMC FP8 conversion expected exactly "
168
+ f"{expected_projections} attention output projections, found {len(targets)}."
169
+ )
170
+
171
+ te, _ = _load_transformer_engine()
172
+ modules = dict(module.named_modules())
173
+ converted: list[str] = []
174
+ for path, child in targets:
175
+ owner_path, name = path.rsplit(".", 1)
176
+ owner = modules[owner_path]
177
+ replacement = te.Linear(
178
+ child.in_features,
179
+ child.out_features,
180
+ bias=child.bias is not None,
181
+ params_dtype=child.weight.dtype,
182
+ device=child.weight.device,
183
+ )
184
+ with torch.no_grad():
185
+ replacement.weight.copy_(child.weight)
186
+ if child.bias is not None:
187
+ replacement.bias.copy_(child.bias)
188
+ replacement.eval().requires_grad_(False)
189
+ setattr(owner, name, replacement)
190
+ converted.append(path)
191
+ return tuple(converted)
192
+
193
+
194
+ @contextmanager
195
+ def _esmplusplus_fp8_context(enabled: bool, device: torch.device):
196
+ """Enter the validated BF16-storage, Transformer Engine FP8 context."""
197
+
198
+ if not enabled:
199
+ yield
200
+ return
201
+ if torch.is_grad_enabled():
202
+ raise RuntimeError("ESM++ FP8 is inference-only; use torch.inference_mode() or no_grad().")
203
+ te, recipe = _load_transformer_engine()
204
+ fp8_recipe = recipe.Float8CurrentScaling(
205
+ use_power_2_scales=False,
206
+ fp8_format=recipe.Format.HYBRID,
207
+ )
208
+ with torch.autocast(device_type=device.type, dtype=torch.bfloat16):
209
+ with te.autocast(enabled=True, recipe=fp8_recipe):
210
+ yield
211
+
212
+
213
  class ESMplusplusConfig(PretrainedConfig):
214
  """Configuration class for ESM++ model.
215
 
 
765
  hidden_states: tuple[torch.Tensor] | None = None
766
  attentions: tuple[torch.Tensor] | None = None
767
  s_max: tuple[list[torch.Tensor], ...] | None = None
768
+ sae_outputs: dict[str, torch.Tensor] | None = None
769
+ sae_hidden_states: dict[int, torch.Tensor] | None = None
770
 
771
 
772
  @dataclass
 
775
 
776
  s_max: tuple[list[torch.Tensor], ...] | None = None
777
  last_hidden_state: torch.Tensor | None = None
778
+ sae_outputs: dict[str, torch.Tensor] | None = None
779
 
780
 
781
  @dataclass
 
783
  """Sequence-classification output with optional attention diagnostics."""
784
 
785
  s_max: tuple[list[torch.Tensor], ...] | None = None
786
+ sae_outputs: dict[str, torch.Tensor] | None = None
787
 
788
 
789
  @dataclass
 
791
  """Token-classification output with optional attention diagnostics."""
792
 
793
  s_max: tuple[list[torch.Tensor], ...] | None = None
794
+ sae_outputs: dict[str, torch.Tensor] | None = None
795
 
796
 
797
  class TransformerStack(nn.Module):
 
842
  output_attentions: bool | None = False,
843
  output_s_max: bool | None = False,
844
  esmfold2_hidden_states: bool = False,
845
+ sae_layers: tuple[int, ...] = (),
846
  ) -> TransformerOutput:
847
  # x: (b, l, d); attention_mask, sequence_id: (b, l)
848
  hidden_states = () if output_hidden_states else None
849
  attentions = () if output_attentions else None
850
  full_s_max = () if output_s_max else None
851
+ sae_layer_set = set(sae_layers)
852
+ sae_hidden_states = {} if sae_layer_set else None
853
  # Match the pinned Biohub Transformers contract: a supplied sequence_id
854
  # is authoritative and must encode padding as -1. attention_mask is
855
  # ignored in that mode rather than intersected with the chain mask.
856
+ attention_mask_2d, attention_mask_4d, flex_block_mask = (
857
+ self._prepare_attention_masks(
858
+ attention_mask=attention_mask,
859
+ sequence_id=sequence_id,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
  batch_size=x.shape[0],
861
  seq_len=x.shape[1],
862
  device=x.device,
 
863
  dtype=x.dtype,
864
+ output_attentions=bool(output_attentions),
 
 
 
 
 
 
 
 
 
 
 
865
  )
866
+ )
867
 
868
+ for layer_index, block in enumerate(self.blocks):
869
  if output_hidden_states:
870
  if hidden_states is None:
871
  raise RuntimeError(
 
875
  # by the final normalized state. This gives n_layers + 1 states
876
  # and, for ESMC-6B, the 81-state order consumed by ESMFold2.
877
  hidden_states += (x,)
878
+ if sae_hidden_states is not None and layer_index in sae_layer_set:
879
+ sae_hidden_states[layer_index] = x
880
  if self.gradient_checkpointing and self.training:
881
  x, attn_weights, s_max = self._gradient_checkpointing_func(
882
  block.__call__,
 
905
  last_hidden_state = self.norm(x)
906
  if output_hidden_states:
907
  hidden_states += (last_hidden_state,)
908
+ final_layer_index = len(self.blocks)
909
+ if sae_hidden_states is not None and final_layer_index in sae_layer_set:
910
+ sae_hidden_states[final_layer_index] = last_hidden_state
911
 
912
  return TransformerOutput(
913
  last_hidden_state=last_hidden_state,
914
  hidden_states=hidden_states,
915
  attentions=attentions,
916
  s_max=full_s_max,
917
+ sae_hidden_states=sae_hidden_states,
918
  )
919
 
920
+ @torch.compiler.disable
921
+ def _prepare_attention_masks(
922
  self,
923
+ attention_mask: torch.Tensor | None,
924
+ sequence_id: torch.Tensor | None,
925
  batch_size: int,
926
  seq_len: int,
927
  device: torch.device,
928
  dtype: torch.dtype | None = None,
929
  effective_backend: AttentionBackend | None = None,
930
+ output_attentions: bool = False,
931
  ) -> tuple[torch.Tensor | None, torch.Tensor | None, BlockMask | None]:
932
+ mask_name = "sequence_id" if sequence_id is not None else "attention_mask"
933
+ mask_pattern = sequence_id if sequence_id is not None else attention_mask
934
+ if mask_pattern is None:
935
+ backend = resolve_attention_backend_for_call(
936
+ self.attention_backend,
937
+ output_attentions=output_attentions,
938
+ )
939
+ return get_attention_mask(
940
+ effective_backend=backend,
941
+ batch_size=batch_size,
942
+ seq_len=seq_len,
943
+ device=device,
944
+ attention_mask=None,
945
+ dtype=dtype,
946
+ mask_semantics="padding",
947
+ )
948
+
949
  expected_shape = (batch_size, seq_len)
950
+ if mask_pattern.ndim != 2 or tuple(mask_pattern.shape) != expected_shape:
951
  raise ValueError(
952
+ f"{mask_name} must have shape {expected_shape}; "
953
+ f"received {tuple(mask_pattern.shape)}."
954
  )
955
+ if mask_pattern.device != device:
956
+ mask_pattern = mask_pattern.to(device=device)
957
+ if sequence_id is None:
958
+ mask_pattern = mask_pattern.to(dtype=torch.bool)
959
+ attention_mask_2d = (
960
+ mask_pattern if mask_pattern.dtype == torch.bool else mask_pattern != -1
961
  )
962
+ if not bool(attention_mask_2d.any(dim=1).all()):
963
+ raise ValueError("attention_mask must keep at least one valid key per batch row.")
964
+
965
+ if mask_pattern.dtype == torch.bool:
966
  # Biohub's boolean single-chain form groups biological positions
967
  # together and padding positions together. Padding queries remain
968
  # finite without allowing their states to enter residue attention.
969
+ attention_mask_4d = (
970
+ mask_pattern[:, None, :, None] == mask_pattern[:, None, None, :]
971
+ )
972
  else:
973
+ attention_mask_4d = (
974
+ mask_pattern.unsqueeze(-1) == mask_pattern.unsqueeze(-2)
975
+ ).unsqueeze(1)
976
+ backend = (
977
+ resolve_attention_backend_for_call(
978
+ self.attention_backend,
979
+ output_attentions=output_attentions,
980
  )
981
+ if effective_backend is None
982
+ else resolve_attention_backend(effective_backend)
983
+ )
984
 
985
  if backend.is_flash:
986
+ if mask_pattern.dtype != torch.bool:
987
  raise ValueError(
988
  "ESM++ FlashAttention only supports boolean sequence_id padding masks. "
989
  "Use eager, sdpa, or flex_attention for chain-aware integer sequence_id "
 
992
  return attention_mask_2d, attention_mask_4d, None
993
 
994
  if backend == AttentionBackend.FLEX:
995
+ if mask_pattern.dtype == torch.bool:
996
 
997
  def mask_mod(batch_idx, head_idx, q_idx, kv_idx):
998
  del head_idx
999
+ return mask_pattern[batch_idx, q_idx] == mask_pattern[batch_idx, kv_idx]
1000
 
1001
  else:
1002
 
1003
  def mask_mod(batch_idx, head_idx, q_idx, kv_idx):
1004
  del head_idx
1005
+ q_id = mask_pattern[batch_idx, q_idx]
1006
+ kv_id = mask_pattern[batch_idx, kv_idx]
1007
  return q_id == kv_id
1008
 
1009
  flex_block_mask = _get_flex_block_mask(
1010
+ mask_pattern=mask_pattern,
1011
  batch_size=batch_size,
1012
  query_length=seq_len,
1013
  key_value_length=seq_len,
 
1015
  dtype=dtype,
1016
  mask_semantics=(
1017
  "boolean_sequence_id"
1018
+ if mask_pattern.dtype == torch.bool
1019
  else "integer_sequence_id"
1020
  ),
1021
  mask_mod=mask_mod,
 
1045
  "flash_attention_3",
1046
  )
1047
 
1048
+ def __init__(self, config: ESMplusplusConfig, *args: object, **kwargs: object) -> None:
1049
+ super().__init__(config, *args, **kwargs)
1050
+ self._sae_models = nn.ModuleDict()
1051
+ self._esmc_fp8 = False
1052
+ self._esmc_fp8_module_paths: tuple[str, ...] = ()
1053
+ self._esmc_precision_status = ESMplusplusFP8Status(
1054
+ enabled=False,
1055
+ reason="FP8 has not been enabled; canonical checkpoint precision is unchanged.",
1056
+ device="cpu",
1057
+ transformer_engine_version=_transformer_engine_version(),
1058
+ converted_projections=0,
1059
+ )
1060
+
1061
+ @property
1062
+ def esmc_precision_status(self) -> ESMplusplusFP8Status:
1063
+ """Return the explicit FP8 conversion status for this ESM++ instance."""
1064
+
1065
+ return self._esmc_precision_status
1066
+
1067
+ def enable_fp8(self) -> ESMplusplusFP8Status:
1068
+ """Enable the strict inference-only Transformer Engine FP8 path.
1069
+
1070
+ Canonical parameters must already be BF16 on one supported CUDA device.
1071
+ Exactly one attention output projection per transformer block is replaced;
1072
+ all other operations and all SAE weights remain BF16.
1073
+ """
1074
+
1075
+ if self._esmc_fp8:
1076
+ return self._esmc_precision_status
1077
+ if self.training:
1078
+ raise RuntimeError("ESM++ FP8 is inference-only; call eval() before enable_fp8().")
1079
+ parameter_devices = {parameter.device for parameter in self.parameters()}
1080
+ if len(parameter_devices) != 1:
1081
+ raise RuntimeError(
1082
+ "ESM++ FP8 requires every parameter on one CUDA device; sharded device maps "
1083
+ f"are unsupported, found {sorted(map(str, parameter_devices))}."
1084
+ )
1085
+ device = next(iter(parameter_devices), self.device)
1086
+ available, reason = _te_fp8_capability(device)
1087
+ if not available:
1088
+ raise RuntimeError(f"ESM++ FP8 is unavailable: {reason}")
1089
+ non_bf16 = [
1090
+ name
1091
+ for name, parameter in self.named_parameters()
1092
+ if parameter.is_floating_point() and parameter.dtype != torch.bfloat16
1093
+ ]
1094
+ if non_bf16:
1095
+ examples = ", ".join(non_bf16[:3])
1096
+ raise RuntimeError(
1097
+ "ESM++ FP8 requires canonical BF16 parameters before conversion; "
1098
+ f"found {len(non_bf16)} non-BF16 parameters (for example: {examples})."
1099
+ )
1100
+ paths = _convert_esmc_attention_outputs_to_te(
1101
+ self,
1102
+ expected_projections=self.config.num_hidden_layers,
1103
+ )
1104
+ self._esmc_fp8 = True
1105
+ self._esmc_fp8_module_paths = paths
1106
+ self._esmc_precision_status = ESMplusplusFP8Status(
1107
+ enabled=True,
1108
+ reason=(
1109
+ f"{reason} Converted {len(paths)} attention output projections; "
1110
+ "canonical checkpoint and SAE weights remain BF16."
1111
+ ),
1112
+ device=str(device),
1113
+ transformer_engine_version=_transformer_engine_version(),
1114
+ converted_projections=len(paths),
1115
+ )
1116
+ return self._esmc_precision_status
1117
+
1118
+ def add_sae_models(self, sae_models: list[nn.Module]) -> None:
1119
+ """Attach official Biohub hidden-state SAE layers to this ESM++ model."""
1120
+
1121
+ for sae_model in sae_models:
1122
+ if not isinstance(sae_model, nn.Module):
1123
+ raise TypeError(
1124
+ "Each SAE must be an nn.Module obtained from an official Biohub "
1125
+ "ESMCSAEModel.layers entry."
1126
+ )
1127
+ layer = getattr(sae_model, "layer", None)
1128
+ if isinstance(layer, bool) or not isinstance(layer, int):
1129
+ raise TypeError("Each SAE layer must expose an integer .layer attribute.")
1130
+ if not 0 <= layer <= self.config.num_hidden_layers:
1131
+ raise ValueError(
1132
+ f"SAE target layer {layer} is outside the ESM++ hidden-state range "
1133
+ f"0..{self.config.num_hidden_layers}."
1134
+ )
1135
+ params = getattr(sae_model, "params", None)
1136
+ d_model = getattr(params, "d_model", None)
1137
+ if d_model != self.config.hidden_size:
1138
+ raise ValueError(
1139
+ f"SAE layer {layer} expects d_model={d_model!r}, but this ESM++ "
1140
+ f"checkpoint has hidden_size={self.config.hidden_size}."
1141
+ )
1142
+ if not callable(getattr(sae_model, "get_sae_output", None)):
1143
+ raise TypeError("Each SAE layer must expose get_sae_output(layer_states, token_mask).")
1144
+ for name in ("idf", "max"):
1145
+ if not isinstance(getattr(sae_model, name, None), torch.Tensor):
1146
+ raise TypeError(f"Each SAE layer must expose a tensor {name!r} buffer.")
1147
+ key = f"layer{layer}"
1148
+ if key in self._sae_models:
1149
+ raise ValueError(
1150
+ f"An SAE is already registered at {key!r}; only one SAE per layer "
1151
+ "can be active."
1152
+ )
1153
+ self._sae_models[key] = sae_model
1154
+
1155
+ def _prepare_sae_forward(
1156
+ self,
1157
+ *,
1158
+ compute_sae: bool,
1159
+ input_ids: torch.Tensor | None,
1160
+ attention_mask: torch.Tensor | None,
1161
+ sequence_id: torch.Tensor | None,
1162
+ ) -> tuple[tuple[int, ...], torch.Tensor | None]:
1163
+ if not compute_sae or not self._sae_models:
1164
+ return (), None
1165
+ if input_ids is None:
1166
+ raise ValueError(
1167
+ "SAE computation requires input_ids so masked-token inputs can be rejected."
1168
+ )
1169
+ if torch.any(input_ids == self.config.mask_token_id):
1170
+ raise ValueError("SAE inputs must not contain mask tokens; SAEs were trained unmasked.")
1171
+ if sequence_id is not None:
1172
+ token_mask = sequence_id >= 0
1173
+ elif attention_mask is not None:
1174
+ token_mask = attention_mask.to(dtype=torch.bool)
1175
+ else:
1176
+ token_mask = input_ids != self.config.pad_token_id
1177
+ layers = tuple(sorted(int(name.removeprefix("layer")) for name in self._sae_models))
1178
+ return layers, token_mask
1179
+
1180
+ def _get_sae_outputs(
1181
+ self,
1182
+ hidden_states: dict[int, torch.Tensor] | None,
1183
+ token_mask: torch.Tensor | None,
1184
+ *,
1185
+ normalize_sae: bool,
1186
+ ) -> dict[str, torch.Tensor] | None:
1187
+ if not self._sae_models:
1188
+ return None
1189
+ if hidden_states is None or token_mask is None:
1190
+ raise RuntimeError("SAE hidden-state collection was not initialized.")
1191
+ outputs: dict[str, torch.Tensor] = {}
1192
+ for key, sae_model in self._sae_models.items():
1193
+ layer = int(key.removeprefix("layer"))
1194
+ if layer not in hidden_states:
1195
+ raise RuntimeError(f"ESM++ did not collect the requested SAE layer {layer}.")
1196
+ sae_output = sae_model.get_sae_output(hidden_states[layer].clone(), token_mask)
1197
+ features = getattr(sae_output, "feature_magnitudes", None)
1198
+ if not isinstance(features, torch.Tensor):
1199
+ raise TypeError("SAE get_sae_output must return tensor feature_magnitudes.")
1200
+ features = features.detach()
1201
+ if normalize_sae:
1202
+ features = (features / sae_model.max) * sae_model.idf
1203
+ outputs[key] = features.to_sparse()
1204
+ return outputs
1205
+
1206
+ def _pad_fp8_inputs(
1207
+ self,
1208
+ input_ids: torch.Tensor | None,
1209
+ attention_mask: torch.Tensor | None,
1210
+ sequence_id: torch.Tensor | None,
1211
+ inputs_embeds: torch.Tensor | None,
1212
+ ) -> tuple[
1213
+ torch.Tensor | None,
1214
+ torch.Tensor | None,
1215
+ torch.Tensor | None,
1216
+ torch.Tensor | None,
1217
+ int | None,
1218
+ ]:
1219
+ if not self._esmc_fp8:
1220
+ return input_ids, attention_mask, sequence_id, inputs_embeds, None
1221
+ source = input_ids if input_ids is not None else inputs_embeds
1222
+ if source is None:
1223
+ return input_ids, attention_mask, sequence_id, inputs_embeds, None
1224
+ sequence_length = source.shape[1]
1225
+ padded_length = (
1226
+ (sequence_length + _ESMC_FP8_ALIGNMENT - 1) // _ESMC_FP8_ALIGNMENT
1227
+ ) * _ESMC_FP8_ALIGNMENT
1228
+ padding = padded_length - sequence_length
1229
+ if padding == 0:
1230
+ return input_ids, attention_mask, sequence_id, inputs_embeds, None
1231
+ if input_ids is not None:
1232
+ input_ids = F.pad(input_ids, (0, padding), value=self.config.pad_token_id)
1233
+ if inputs_embeds is not None:
1234
+ inputs_embeds = F.pad(inputs_embeds, (0, 0, 0, padding), value=0.0)
1235
+ if sequence_id is not None:
1236
+ sequence_id = F.pad(sequence_id.to(dtype=torch.long), (0, padding), value=-1)
1237
+ else:
1238
+ if attention_mask is None:
1239
+ attention_mask = (
1240
+ source != self.config.pad_token_id
1241
+ if input_ids is not None
1242
+ else torch.ones(
1243
+ source.shape[:2],
1244
+ dtype=torch.bool,
1245
+ device=source.device,
1246
+ )
1247
+ )
1248
+ attention_mask = F.pad(attention_mask, (0, padding), value=0)
1249
+ return input_ids, attention_mask, sequence_id, inputs_embeds, sequence_length
1250
+
1251
+ @staticmethod
1252
+ def _trim_transformer_output(
1253
+ output: TransformerOutput,
1254
+ sequence_length: int | None,
1255
+ ) -> TransformerOutput:
1256
+ if sequence_length is None:
1257
+ return output
1258
+ hidden_states = (
1259
+ tuple(state[:, :sequence_length] for state in output.hidden_states)
1260
+ if output.hidden_states is not None
1261
+ else None
1262
+ )
1263
+ attentions = (
1264
+ tuple(
1265
+ attention[..., :sequence_length, :sequence_length]
1266
+ for attention in output.attentions
1267
+ )
1268
+ if output.attentions is not None
1269
+ else None
1270
+ )
1271
+ return TransformerOutput(
1272
+ last_hidden_state=output.last_hidden_state[:, :sequence_length],
1273
+ hidden_states=hidden_states,
1274
+ attentions=attentions,
1275
+ s_max=output.s_max,
1276
+ sae_hidden_states=output.sae_hidden_states,
1277
+ )
1278
+
1279
  @property
1280
  def tokenizer(self) -> EsmSequenceTokenizer:
1281
  """Construct the sequence tokenizer only when a raw-sequence API needs it."""
 
1420
  output_s_max: bool | None = False,
1421
  esmfold2_hidden_states: bool = False,
1422
  return_dict: bool | None = None,
1423
+ compute_sae: bool = True,
1424
+ normalize_sae: bool = False,
1425
  ) -> TransformerOutput | tuple[torch.Tensor, ...]:
1426
  """Run ESMC inference with the pinned Biohub mask precedence.
1427
 
 
1445
  )
1446
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1447
 
1448
+ input_ids, attention_mask, sequence_id, inputs_embeds, original_length = (
1449
+ self._pad_fp8_inputs(input_ids, attention_mask, sequence_id, inputs_embeds)
1450
+ )
1451
  if attention_mask is None and sequence_id is None and input_ids is not None:
1452
  attention_mask = input_ids.ne(self.config.pad_token_id)
1453
+ sae_layers, sae_token_mask = self._prepare_sae_forward(
1454
+ compute_sae=compute_sae,
1455
+ input_ids=input_ids,
1456
+ attention_mask=attention_mask,
1457
+ sequence_id=sequence_id,
1458
+ )
1459
 
1460
  x = self.embed(input_ids) if inputs_embeds is None else inputs_embeds
1461
 
1462
+ with _esmplusplus_fp8_context(self._esmc_fp8, self.device):
1463
+ transformer_output = self.transformer(
1464
+ x=x,
1465
+ attention_mask=attention_mask,
1466
+ sequence_id=sequence_id,
1467
+ output_hidden_states=output_hidden_states,
1468
+ output_attentions=output_attentions,
1469
+ output_s_max=output_s_max,
1470
+ esmfold2_hidden_states=esmfold2_hidden_states,
1471
+ sae_layers=sae_layers,
1472
+ )
1473
+ sae_outputs = (
1474
+ self._get_sae_outputs(
1475
+ transformer_output.sae_hidden_states,
1476
+ sae_token_mask,
1477
+ normalize_sae=normalize_sae,
1478
+ )
1479
+ if sae_layers
1480
+ else None
1481
  )
1482
+ transformer_output = self._trim_transformer_output(transformer_output, original_length)
1483
  result = TransformerOutput(
1484
  last_hidden_state=transformer_output.last_hidden_state,
1485
  hidden_states=transformer_output.hidden_states,
1486
  attentions=transformer_output.attentions,
1487
  s_max=transformer_output.s_max,
1488
+ sae_outputs=sae_outputs,
1489
  )
1490
  return result if return_dict else result.to_tuple()
1491
 
 
1569
  esmfold2_hidden_states: bool = False,
1570
  return_dict: bool | None = None,
1571
  compute_logits: bool = True,
1572
+ compute_sae: bool = True,
1573
+ normalize_sae: bool = False,
1574
  ) -> ESMplusplusOutput | tuple[torch.Tensor, ...]:
1575
  if input_ids is None and inputs_embeds is None:
1576
  raise ValueError("You have to specify either input_ids or inputs_embeds")
 
1587
  else self.config.output_hidden_states
1588
  )
1589
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1590
+ input_ids, attention_mask, sequence_id, inputs_embeds, original_length = (
1591
+ self._pad_fp8_inputs(input_ids, attention_mask, sequence_id, inputs_embeds)
1592
+ )
1593
  if attention_mask is None and sequence_id is None and input_ids is not None:
1594
  attention_mask = input_ids.ne(self.config.pad_token_id)
1595
+ sae_layers, sae_token_mask = self._prepare_sae_forward(
1596
+ compute_sae=compute_sae,
1597
+ input_ids=input_ids,
1598
+ attention_mask=attention_mask,
1599
+ sequence_id=sequence_id,
1600
+ )
1601
 
1602
  x = self.embed(input_ids) if inputs_embeds is None else inputs_embeds
1603
 
1604
+ with _esmplusplus_fp8_context(self._esmc_fp8, self.device):
1605
+ output = self.transformer(
1606
+ x=x,
1607
+ attention_mask=attention_mask,
1608
+ sequence_id=sequence_id,
1609
+ output_hidden_states=output_hidden_states,
1610
+ output_attentions=output_attentions,
1611
+ output_s_max=output_s_max,
1612
+ esmfold2_hidden_states=esmfold2_hidden_states,
1613
+ sae_layers=sae_layers,
1614
+ )
1615
+ sae_outputs = (
1616
+ self._get_sae_outputs(
1617
+ output.sae_hidden_states,
1618
+ sae_token_mask,
1619
+ normalize_sae=normalize_sae,
1620
+ )
1621
+ if sae_layers
1622
+ else None
1623
  )
1624
+ output = self._trim_transformer_output(output, original_length)
1625
 
1626
  last_hidden_state = output.last_hidden_state
1627
  logits = self.sequence_head(last_hidden_state) if compute_logits else None
 
1639
  attentions=output.attentions,
1640
  s_max=output.s_max,
1641
  last_hidden_state=last_hidden_state,
1642
+ sae_outputs=sae_outputs,
1643
  )
1644
  return result if return_dict else result.to_tuple()
1645
 
 
1715
  output_hidden_states: bool | None = None,
1716
  output_s_max: bool | None = False,
1717
  return_dict: bool | None = None,
1718
+ compute_sae: bool = True,
1719
+ normalize_sae: bool = False,
1720
  ) -> ESMplusplusSequenceClassifierOutput | tuple[torch.Tensor, ...]:
1721
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1722
  pooling_mask = attention_mask
 
1747
  output_s_max=output_s_max,
1748
  return_dict=True,
1749
  compute_logits=False,
1750
+ compute_sae=compute_sae,
1751
+ normalize_sae=normalize_sae,
1752
  )
1753
 
1754
  last_hidden_state = output.last_hidden_state
 
1784
  hidden_states=output.hidden_states,
1785
  attentions=output.attentions,
1786
  s_max=output.s_max,
1787
+ sae_outputs=output.sae_outputs,
1788
  )
1789
  return result if return_dict else result.to_tuple()
1790
 
 
1839
  output_hidden_states: bool | None = None,
1840
  output_s_max: bool | None = False,
1841
  return_dict: bool | None = None,
1842
+ compute_sae: bool = True,
1843
+ normalize_sae: bool = False,
1844
  ) -> ESMplusplusTokenClassifierOutput | tuple[torch.Tensor, ...]:
1845
  return_dict = return_dict if return_dict is not None else self.config.use_return_dict
1846
  output = super().forward(
 
1854
  output_s_max=output_s_max,
1855
  return_dict=True,
1856
  compute_logits=False,
1857
+ compute_sae=compute_sae,
1858
+ normalize_sae=normalize_sae,
1859
  )
1860
 
1861
  last_hidden_state = output.last_hidden_state
 
1871
  hidden_states=output.hidden_states,
1872
  attentions=output.attentions,
1873
  s_max=output.s_max,
1874
+ sae_outputs=output.sae_outputs,
1875
  )
1876
  return result if return_dict else result.to_tuple()
1877
 
fastplms_bundle.py CHANGED
The diff for this file is too large to render. See raw diff
 
modeling_fastplms.py CHANGED
@@ -12,7 +12,7 @@ from zipfile import ZIP_DEFLATED, ZipFile
12
 
13
  from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
14
 
15
- if RUNTIME_HASH != "d73f544a7623d12eff2ba407e2d7e86417b7d731e36a2931515930eae7d7e8e5":
16
  raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
17
 
18
  _RUNTIME_TEMPORARIES = []
 
12
 
13
  from .fastplms_bundle import RUNTIME_DATA, RUNTIME_HASH
14
 
15
+ if RUNTIME_HASH != "23133ece4b4c336a3e782b316afd5bcf7367b522de2827cb6153885c1ffe1e70":
16
  raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
17
 
18
  _RUNTIME_TEMPORARIES = []