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

Update FastPLMs files

Browse files
LICENSES/protein-ttt/SOURCE_RECORD.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # ProteinTTT provenance
2
+
3
+ FastPLMs uses `anton-bushuiev/ProteinTTT` revision
4
+ `fde2817cd84b936167cc76ccabf31e5c0fe49962` as the official reference for the
5
+ optional protein test-time training workflow. The repository is pinned at
6
+ `vendor/upstream/protein-ttt/` and is not a production dependency or runtime
7
+ image component. The accompanying `LICENSE` is the verbatim MIT text from that
8
+ revision.
README.md CHANGED
@@ -10,7 +10,7 @@ tags:
10
 
11
  # Synthyra/ESMFold2-Fast
12
 
13
- This checkpoint packages the FastPLMs `ESMFold2` implementation.
14
 
15
  Accepted inputs are raw amino-acid sequences or typed molecular-complex
16
  specifications; low-level forward accepts prepared feature tensors.
@@ -28,9 +28,7 @@ Supported Transformers entry points are `AutoConfig`, `AutoModel`.
28
  | Attention variants | Supported: `eager`, `sdpa`, `flex_attention` |
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/ESMFold2-Fast/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 structure dependencies. The published execution contract requires a CUDA device. The current validated release target is the exact NVIDIA GH200 on Linux aarch64; Linux x86-64, CPU-only, Windows, and macOS structure runs are not current release evidence. 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,21 +60,21 @@ model = AutoModel.from_pretrained(
62
  ```
63
 
64
  For offline validation, replace `model_id` with the manifest-built
65
- `dist/hub/ESMFold2-Fast` 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`. An unavailable
70
- requested backend raises instead of silently switching implementations.
71
- `output_attentions=True` may use the documented, one-call eager fallback solely
72
- to materialize attention tensors; the configured backend remains unchanged.
73
 
74
- This family declares the `compliance` tier. Release evidence binds the exact
75
  checkpoint, backend, dtype, hardware, inputs, and reference revision.
76
 
77
  ## PEFT fine-tuning
78
 
79
- Install the direct training dependencies, then attach LoRA to the loaded checkpoint:
80
 
81
  ```bash
82
  python -m pip install "datasets>=4.8,<5" "peft>=0.19,<0.20"
@@ -95,22 +93,21 @@ peft_model = get_peft_model(
95
  )
96
  ```
97
 
98
- This checkpoint has no advertised classifier. Supply the task-specific
99
- objective and preserve any new head through `modules_to_save`.
100
  All FastPLMs checkpoints follow the Transformers `PreTrainedModel` contract and
101
- can be adapted with PEFT. The ESM2-specific shipped CLI is an example, not a
102
  support boundary. Record the target modules, base revision, data identity, and
103
  trainable parameter scope.
104
 
105
  ## Alignment-conditioning contract
106
 
107
- This 24-block Fast checkpoint is inference-optimized for single-sequence
108
- conditioning and was trained without MSA conditioning. It is not
109
- MSA-conditioned and rejects `ProteinInput.msa` and low-level MSA-derived
110
- features. Typed multichain and multimolecule inputs remain supported when
111
- every protein chain uses `msa=None`. Use the corresponding full ESMFold2
112
- checkpoint for MSA-conditioned inference. This follows the official Biohub
113
- architecture description in [Appendix A.2.1](https://biohub.ai/papers/esm_protein.pdf).
114
 
115
 
116
  ## Protein folding
@@ -152,15 +149,13 @@ complex_result = model.fold(
152
  print(complex_result.ptm, complex_result.plddt.mean().item())
153
  ```
154
 
155
- The typed interface also supports RNA, modifications, and covalent bonds.
156
- Protein MSA inputs are not supported by this Fast checkpoint; every protein
157
- chain must use `msa=None`. The public schema recognizes `PocketConditioning` and
158
- `DistogramConditioning`, but the pinned official forward consumes neither. Its
159
- feature builder hard-codes a zero pocket feature and constructs distogram tensors
160
- that the released model ignores. FastPLMs therefore rejects non-null pocket and
161
- distogram conditioning instead of silently ignoring scientific inputs. Prepared
162
- `ref_pos` values are component reference geometries created during featurization,
163
- not target coordinates.
164
  Predicted coordinates and confidence scores are outputs and do not establish
165
  biochemical activity.
166
 
@@ -244,9 +239,9 @@ print(adapted.ttt_metrics)
244
  ```
245
 
246
  Entering a gradient-enabled path reloads canonical BF16 ESMC weights. TTT adds
247
- latency and memory, can worsen a prediction, and does not calibrate confidence
248
- or establish biological validity. Folding TTT is result-scoped: its transient
249
- ESMC adapter modules are excluded from checkpoint state, so it is not a generic
250
  `save_pretrained` adapter-persistence path.
251
 
252
  ## Runtime contract
@@ -267,8 +262,8 @@ ESMC adapter modules are excluded from checkpoint state, so it is not a generic
267
  ## Release record
268
 
269
  - FastPLMs weights: `Synthyra/ESMFold2-Fast`
270
- - Runtime revision: recorded separately in the built artifact and published commit
271
- - Source-tree and runtime-bundle SHA-256: recorded in `provenance.json`
272
  - Official checkpoint: `biohub/ESMFold2-Fast`
273
  - Artifact source: `fast`
274
  - State transform: `identity`
@@ -276,19 +271,17 @@ ESMC adapter modules are excluded from checkpoint state, so it is not a generic
276
  - Release tiers: `check`, `compliance`, `structure`, `feature`, `artifact`, `benchmark`
277
  - Unresolved required file identities: `0`
278
 
279
- `provenance.json` records exact file identities, conversion, source revisions,
280
- legal texts, schema, and attestations. A nonzero unresolved count blocks release.
281
 
282
  ## Validation boundary
283
 
284
- Declared tiers compare applicable configuration, tokenizer behavior, state,
285
- and representative inference with the pinned reference. Metadata alone does
286
- not claim a build passed, a backend is faster, or an output is biologically
287
- valid.
288
 
289
  ## License
290
 
291
  Checkpoint terms: MIT. The Hub model-card identifier is
292
- `mit`. Applicable source licenses, notices, attribution,
293
- and conversion records are distributed with the local artifact. Review them
294
- before use.
 
10
 
11
  # Synthyra/ESMFold2-Fast
12
 
13
+ This checkpoint contains the FastPLMs `ESMFold2` implementation.
14
 
15
  Accepted inputs are raw amino-acid sequences or typed molecular-complex
16
  specifications; low-level forward accepts prepared feature tensors.
 
28
  | Attention variants | Supported: `eager`, `sdpa`, `flex_attention` |
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/ESMFold2-Fast/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 structure dependencies. The release contract requires a CUDA device. The current validated target is the exact NVIDIA GH200 on Linux aarch64. Linux x86-64, CPU-only, Windows, and macOS structure runs are not release evidence. 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/ESMFold2-Fast` 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`. An unavailable
68
+ requested backend raises. It does not silently change implementation.
69
+ `output_attentions=True` can use the documented one-call eager fallback to
70
+ materialize attention tensors. The configured backend does not change.
71
 
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:
78
 
79
  ```bash
80
  python -m pip install "datasets>=4.8,<5" "peft>=0.19,<0.20"
 
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
101
  trainable parameter scope.
102
 
103
  ## Alignment-conditioning contract
104
 
105
+ This 24-block Fast checkpoint is optimized for single-sequence inference. It
106
+ was trained without MSA conditioning. It rejects `ProteinInput.msa` and low-level
107
+ MSA-derived features. Typed multichain and multimolecule inputs remain supported
108
+ when every protein chain uses `msa=None`. Use the full ESMFold2 checkpoint for
109
+ MSA-conditioned inference. This follows the official Biohub architecture
110
+ description in [Appendix A.2.1](https://biohub.ai/papers/esm_protein.pdf).
 
111
 
112
 
113
  ## Protein folding
 
149
  print(complex_result.ptm, complex_result.plddt.mean().item())
150
  ```
151
 
152
+ The typed interface also supports RNA, modifications, covalent bonds, and
153
+ distogram conditioning. Protein MSA inputs are not supported by this Fast
154
+ checkpoint; every protein chain must use `msa=None`. The public schema recognizes
155
+ `PocketConditioning`, but the pinned official runtime discards it and hard-codes
156
+ a zero pocket feature. FastPLMs therefore rejects non-null pocket conditioning
157
+ instead of silently ignoring it. Prepared `ref_pos` values are component
158
+ reference geometries created during featurization, not target coordinates.
 
 
159
  Predicted coordinates and confidence scores are outputs and do not establish
160
  biochemical activity.
161
 
 
239
  ```
240
 
241
  Entering a gradient-enabled path reloads canonical BF16 ESMC weights. TTT adds
242
+ latency and memory and can worsen a prediction. It does not calibrate confidence
243
+ or show biological validity. Folding TTT is result-scoped. Its transient ESMC
244
+ adapter modules are excluded from checkpoint state. It is not a generic
245
  `save_pretrained` adapter-persistence path.
246
 
247
  ## Runtime contract
 
262
  ## Release record
263
 
264
  - FastPLMs weights: `Synthyra/ESMFold2-Fast`
265
+ - Runtime revision: recorded in the built artifact and published commit
266
+ - Source-tree and runtime-bundle SHA-256: recorded in the source record
267
  - Official checkpoint: `biohub/ESMFold2-Fast`
268
  - Artifact source: `fast`
269
  - State transform: `identity`
 
271
  - Release tiers: `check`, `compliance`, `structure`, `feature`, `artifact`, `benchmark`
272
  - Unresolved required file identities: `0`
273
 
274
+ The source record records exact file identities, conversion, source revisions,
275
+ legal texts, schema, and attestations. A nonzero unresolved count blocks a release.
276
 
277
  ## Validation boundary
278
 
279
+ Declared tiers compare configuration, tokenizer behavior, state, and
280
+ representative inference with the pinned reference. Metadata does not show that
281
+ a build passed, that a backend is faster, or that an output is biologically valid.
 
282
 
283
  ## License
284
 
285
  Checkpoint terms: MIT. The Hub model-card identifier is
286
+ `mit`. The local artifact contains applicable source
287
+ 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/models/esmfold2/esmfold2_processor.py CHANGED
@@ -123,12 +123,6 @@ def clean_esmfold2_input(input: StructurePredictionInput) -> StructurePrediction
123
  "the published ESMFold2 feature pipeline drops it. FastPLMs refuses this "
124
  "input instead of silently emitting an all-zero pocket feature."
125
  )
126
- if input.distogram_conditioning is not None:
127
- raise NotImplementedError(
128
- "ESMFold2 distogram conditioning is present in the upstream input schema but "
129
- "the published ESMFold2 forward does not consume it. FastPLMs refuses this "
130
- "input instead of silently ignoring the supplied distogram."
131
- )
132
 
133
  cleaned: list[Any] = []
134
  for item in input.sequences:
 
123
  "the published ESMFold2 feature pipeline drops it. FastPLMs refuses this "
124
  "input instead of silently emitting an all-zero pocket feature."
125
  )
 
 
 
 
 
 
126
 
127
  cleaned: list[Any] = []
128
  for item in input.sequences:
fastplms/models/esmfold2/modeling_esmfold2.py CHANGED
@@ -74,7 +74,6 @@ from .modeling_esmfold2_common import (
74
  maybe_subsample_msa,
75
  validate_kernel_backend,
76
  validate_msa_conditioning_inputs,
77
- validate_prepared_auxiliary_inputs,
78
  )
79
 
80
  _ESMC_FP8_LINEAR_SUFFIX = ".attn.out_proj"
@@ -1418,12 +1417,6 @@ class ESMFold2Model(
1418
  output_attentions: bool | None = None,
1419
  output_hidden_states: bool | None = None,
1420
  return_dict: bool | None = None,
1421
- pocket_feature: Tensor | None = None,
1422
- gt_coords: Tensor | None = None,
1423
- is_resolved: Tensor | None = None,
1424
- frames_idx: Tensor | None = None,
1425
- disto_cond: Tensor | None = None,
1426
- disto_cond_mask: Tensor | None = None,
1427
  ) -> ESMFold2Output | tuple[Any, ...]:
1428
  output_hidden_states, return_dict = _resolve_structure_output_controls(
1429
  self.config,
@@ -1439,12 +1432,6 @@ class ESMFold2Model(
1439
  deletion_value=deletion_value,
1440
  deletion_mean=deletion_mean,
1441
  )
1442
- validate_prepared_auxiliary_inputs(
1443
- pocket_feature=pocket_feature,
1444
- disto_cond=disto_cond,
1445
- disto_cond_mask=disto_cond_mask,
1446
- )
1447
- del gt_coords, is_resolved, frames_idx
1448
  tok_mask = token_attention_mask
1449
  atm_mask = atom_attention_mask
1450
  disto_idx = distogram_atom_idx
 
74
  maybe_subsample_msa,
75
  validate_kernel_backend,
76
  validate_msa_conditioning_inputs,
 
77
  )
78
 
79
  _ESMC_FP8_LINEAR_SUFFIX = ".attn.out_proj"
 
1417
  output_attentions: bool | None = None,
1418
  output_hidden_states: bool | None = None,
1419
  return_dict: bool | None = None,
 
 
 
 
 
 
1420
  ) -> ESMFold2Output | tuple[Any, ...]:
1421
  output_hidden_states, return_dict = _resolve_structure_output_controls(
1422
  self.config,
 
1432
  deletion_value=deletion_value,
1433
  deletion_mean=deletion_mean,
1434
  )
 
 
 
 
 
 
1435
  tok_mask = token_attention_mask
1436
  atm_mask = atom_attention_mask
1437
  disto_idx = distogram_atom_idx
fastplms/models/esmfold2/modeling_esmfold2_common.py CHANGED
@@ -57,14 +57,6 @@ MSA_CONDITIONING_INPUT_NAMES = (
57
  "deletion_value",
58
  "deletion_mean",
59
  )
60
- PREPARED_AUXILIARY_INPUT_NAMES = (
61
- "pocket_feature",
62
- "gt_coords",
63
- "is_resolved",
64
- "frames_idx",
65
- "disto_cond",
66
- "disto_cond_mask",
67
- )
68
 
69
 
70
  def validate_kernel_backend(backend: str | None) -> None:
@@ -113,29 +105,6 @@ def validate_msa_conditioning_inputs(
113
  )
114
 
115
 
116
- def validate_prepared_auxiliary_inputs(
117
- *,
118
- pocket_feature: Tensor | None,
119
- disto_cond: Tensor | None,
120
- disto_cond_mask: Tensor | None,
121
- ) -> None:
122
- """Accept inert upstream features while rejecting unsupported conditioning."""
123
-
124
- if pocket_feature is not None and torch.any(pocket_feature != 0).item():
125
- raise NotImplementedError(
126
- "The published ESMFold2 forward does not consume pocket conditioning; "
127
- "nonzero pocket_feature values are unsupported."
128
- )
129
- distogram_is_active = (disto_cond is not None and torch.any(disto_cond != 0).item()) or (
130
- disto_cond_mask is not None and torch.any(disto_cond_mask).item()
131
- )
132
- if distogram_is_active:
133
- raise NotImplementedError(
134
- "The published ESMFold2 forward does not consume distogram conditioning; "
135
- "nonzero disto_cond or disto_cond_mask values are unsupported."
136
- )
137
-
138
-
139
  def _fused_active(module: nn.Module, tensor: Tensor) -> bool:
140
  """Return whether an optional fused implementation can handle this call."""
141
  return (
@@ -1612,6 +1581,22 @@ class SWAAtomTransformer(nn.Module):
1612
  # ===========================================================================
1613
 
1614
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1615
  class ESMFold2AtomEncoder(nn.Module):
1616
  """Encode atom inputs with normalization and sliding-window attention.
1617
 
@@ -1708,13 +1693,14 @@ class ESMFold2AtomEncoder(nn.Module):
1708
  cos, sin = self.atom_transformer._build_3d_rope(ref_pos, ref_space_uid)
1709
  cos = cos.repeat_interleave(num_diffusion_samples, 0)
1710
  sin = sin.repeat_interleave(num_diffusion_samples, 0)
1711
- mask_exp = atom_attention_mask.repeat_interleave(num_diffusion_samples, 0)
1712
- seqlens = mask_exp.sum(dim=-1, dtype=torch.int32)
1713
- indices = torch.nonzero(mask_exp.flatten(), as_tuple=False).flatten()
1714
- max_seqlen = int(seqlens.max().item())
1715
- cu_seqlens = F.pad(torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0))
 
 
1716
  attention_params = (cos, sin, indices, cu_seqlens, max_seqlen)
1717
- n_tokens = int(atom_to_token.max().item()) + 1
1718
  if layer_cache is not None:
1719
  layer_cache["c_base"] = c_base
1720
  layer_cache["attention_params"] = attention_params
 
57
  "deletion_value",
58
  "deletion_mean",
59
  )
 
 
 
 
 
 
 
 
60
 
61
 
62
  def validate_kernel_backend(backend: str | None) -> None:
 
105
  )
106
 
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  def _fused_active(module: nn.Module, tensor: Tensor) -> bool:
109
  """Return whether an optional fused implementation can handle this call."""
110
  return (
 
1581
  # ===========================================================================
1582
 
1583
 
1584
+ @torch.compiler.disable
1585
+ def _prepare_atom_encoder_metadata(
1586
+ atom_attention_mask: Tensor,
1587
+ atom_to_token: Tensor,
1588
+ num_diffusion_samples: int,
1589
+ ) -> tuple[Tensor, Tensor, Tensor, int, int]:
1590
+ """Prepare mask-derived atom metadata outside compiled diffusion graphs."""
1591
+ mask_exp = atom_attention_mask.repeat_interleave(num_diffusion_samples, 0)
1592
+ seqlens = mask_exp.sum(dim=-1, dtype=torch.int32)
1593
+ indices = torch.nonzero(mask_exp.flatten(), as_tuple=False).flatten()
1594
+ max_seqlen = int(seqlens.max().item())
1595
+ cu_seqlens = F.pad(torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0))
1596
+ n_tokens = int(atom_to_token.max().item()) + 1
1597
+ return mask_exp, indices, cu_seqlens, max_seqlen, n_tokens
1598
+
1599
+
1600
  class ESMFold2AtomEncoder(nn.Module):
1601
  """Encode atom inputs with normalization and sliding-window attention.
1602
 
 
1693
  cos, sin = self.atom_transformer._build_3d_rope(ref_pos, ref_space_uid)
1694
  cos = cos.repeat_interleave(num_diffusion_samples, 0)
1695
  sin = sin.repeat_interleave(num_diffusion_samples, 0)
1696
+ mask_exp, indices, cu_seqlens, max_seqlen, n_tokens = (
1697
+ _prepare_atom_encoder_metadata(
1698
+ atom_attention_mask,
1699
+ atom_to_token,
1700
+ num_diffusion_samples,
1701
+ )
1702
+ )
1703
  attention_params = (cos, sin, indices, cu_seqlens, max_seqlen)
 
1704
  if layer_cache is not None:
1705
  layer_cache["c_base"] = c_base
1706
  layer_cache["attention_params"] = attention_params
fastplms/models/esmfold2/modeling_esmfold2_experimental.py CHANGED
@@ -59,7 +59,6 @@ from .modeling_esmfold2_common import (
59
  gather_token_to_atom,
60
  validate_kernel_backend,
61
  validate_msa_conditioning_inputs,
62
- validate_prepared_auxiliary_inputs,
63
  )
64
 
65
  _EPS = 1e-5
@@ -690,12 +689,6 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
690
  output_attentions: bool | None = None,
691
  output_hidden_states: bool | None = None,
692
  return_dict: bool | None = None,
693
- pocket_feature: Tensor | None = None,
694
- gt_coords: Tensor | None = None,
695
- is_resolved: Tensor | None = None,
696
- frames_idx: Tensor | None = None,
697
- disto_cond: Tensor | None = None,
698
- disto_cond_mask: Tensor | None = None,
699
  ) -> ESMFold2Output | tuple[Any, ...]:
700
  output_hidden_states, return_dict = _resolve_structure_output_controls(
701
  self.config,
@@ -711,12 +704,6 @@ class ESMFold2ExperimentalModel(ESMFold2EmbeddingMixin, ESMFold2AttentionMixin,
711
  deletion_value=deletion_value,
712
  deletion_mean=deletion_mean,
713
  )
714
- validate_prepared_auxiliary_inputs(
715
- pocket_feature=pocket_feature,
716
- disto_cond=disto_cond,
717
- disto_cond_mask=disto_cond_mask,
718
- )
719
- del gt_coords, is_resolved, frames_idx
720
  tok_mask = token_attention_mask
721
  atm_mask = atom_attention_mask
722
  n_loops = num_loops if num_loops is not None else self.config.num_loops
 
59
  gather_token_to_atom,
60
  validate_kernel_backend,
61
  validate_msa_conditioning_inputs,
 
62
  )
63
 
64
  _EPS = 1e-5
 
689
  output_attentions: bool | None = None,
690
  output_hidden_states: bool | None = None,
691
  return_dict: bool | None = None,
 
 
 
 
 
 
692
  ) -> ESMFold2Output | tuple[Any, ...]:
693
  output_hidden_states, return_dict = _resolve_structure_output_controls(
694
  self.config,
 
704
  deletion_value=deletion_value,
705
  deletion_mean=deletion_mean,
706
  )
 
 
 
 
 
 
707
  tok_mask = token_attention_mask
708
  atm_mask = atom_attention_mask
709
  n_loops = num_loops if num_loops is not None else self.config.num_loops
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 != "6e81dad15aa6df4f53633997daf8747aa49a4afbd030a65937a6f3a7d8159273":
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 != "5f557b57da6c13e8b26b2a144ab0983b9621d853a3141a3a52e912d2de26289a":
16
  raise RuntimeError("FastPLMs runtime identity differs from the bridge.")
17
 
18
  _RUNTIME_TEMPORARIES = []