lhallee commited on
Commit
8b82883
·
verified ·
1 Parent(s): eb90d52

Upload modeling_esm3.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. modeling_esm3.py +1647 -0
modeling_esm3.py ADDED
@@ -0,0 +1,1647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Hugging Face compatible ESM3 wrapper.
5
+
6
+ This module keeps Biohub's ESM3 implementation as the execution core and adds
7
+ the FastPLMs conventions around it: AutoModel loading, sequence-only
8
+ `input_ids` forwarding, and direct multimodal track arguments.
9
+ """
10
+
11
+ import sys
12
+ import math
13
+ import functools
14
+ import importlib
15
+ import os
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Dict, List, Optional, Union
19
+
20
+ import einops
21
+ import torch
22
+ import torch.nn as nn
23
+ import torch.nn.functional as F
24
+ from einops import rearrange
25
+ from huggingface_hub import snapshot_download
26
+ from tokenizers import Tokenizer
27
+ from tokenizers.models import BPE
28
+ from tokenizers.processors import TemplateProcessing
29
+ from transformers import PreTrainedModel, PreTrainedTokenizerFast, PretrainedConfig
30
+ from transformers.modeling_outputs import ModelOutput
31
+
32
+
33
+ ESM3_OPEN_SMALL = "esm3_sm_open_v1"
34
+ ESM3_OPEN_SMALL_ALIASES = {
35
+ "ESM3_small",
36
+ "esm3_small",
37
+ "esm3_sm_open_v1",
38
+ "esm3-open-2024-03",
39
+ "esm3-sm-open-v1",
40
+ "esm3-open",
41
+ }
42
+
43
+ SEQUENCE_BOS_TOKEN = 0
44
+ SEQUENCE_PAD_TOKEN = 1
45
+ SEQUENCE_EOS_TOKEN = 2
46
+ SEQUENCE_CHAINBREAK_TOKEN = 31
47
+ SEQUENCE_MASK_TOKEN = 32
48
+
49
+ VQVAE_CODEBOOK_SIZE = 4096
50
+ STRUCTURE_MASK_TOKEN = VQVAE_CODEBOOK_SIZE
51
+ STRUCTURE_EOS_TOKEN = VQVAE_CODEBOOK_SIZE + 1
52
+ STRUCTURE_BOS_TOKEN = VQVAE_CODEBOOK_SIZE + 2
53
+ STRUCTURE_PAD_TOKEN = VQVAE_CODEBOOK_SIZE + 3
54
+ STRUCTURE_CHAINBREAK_TOKEN = VQVAE_CODEBOOK_SIZE + 4
55
+
56
+ SASA_PAD_TOKEN = 0
57
+ SS8_PAD_TOKEN = 0
58
+ INTERPRO_PAD_TOKEN = 0
59
+ RESIDUE_PAD_TOKEN = 0
60
+ MAX_RESIDUE_ANNOTATIONS = 16
61
+ FUNCTION_TOKENS_DEPTH = 8
62
+
63
+ SEQUENCE_VOCAB = [
64
+ "<cls>",
65
+ "<pad>",
66
+ "<eos>",
67
+ "<unk>",
68
+ "L",
69
+ "A",
70
+ "G",
71
+ "V",
72
+ "S",
73
+ "E",
74
+ "R",
75
+ "T",
76
+ "I",
77
+ "D",
78
+ "P",
79
+ "K",
80
+ "Q",
81
+ "N",
82
+ "F",
83
+ "Y",
84
+ "M",
85
+ "H",
86
+ "W",
87
+ "C",
88
+ "X",
89
+ "B",
90
+ "U",
91
+ "Z",
92
+ "O",
93
+ ".",
94
+ "-",
95
+ "|",
96
+ "<mask>",
97
+ ]
98
+
99
+ _SUPPORTED_ATTENTION_BACKENDS = ("sdpa",)
100
+
101
+ _ESM3_CHECKPOINT_SPECS = {
102
+ ESM3_OPEN_SMALL: {
103
+ "repo_id": "biohub/esm3-sm-open-v1",
104
+ "hidden_size": 1536,
105
+ "num_attention_heads": 24,
106
+ "num_vector_heads": 256,
107
+ "num_hidden_layers": 48,
108
+ },
109
+ }
110
+
111
+
112
+ class FastESM3Config(PretrainedConfig):
113
+ model_type = "fast_esm3"
114
+
115
+ def __init__(
116
+ self,
117
+ vocab_size: int = 64,
118
+ hidden_size: int = 1536,
119
+ num_attention_heads: int = 24,
120
+ num_vector_heads: int = 256,
121
+ num_hidden_layers: int = 48,
122
+ initializer_range: float = 0.02,
123
+ attn_backend: str = "sdpa",
124
+ model_name: str = ESM3_OPEN_SMALL,
125
+ **kwargs,
126
+ ):
127
+ super().__init__(**kwargs)
128
+ assert hidden_size % FUNCTION_TOKENS_DEPTH == 0
129
+ assert hidden_size % num_attention_heads == 0
130
+ self.vocab_size = vocab_size
131
+ self.hidden_size = hidden_size
132
+ self.num_attention_heads = num_attention_heads
133
+ self.num_vector_heads = num_vector_heads
134
+ self.num_hidden_layers = num_hidden_layers
135
+ self.initializer_range = initializer_range
136
+ self.attn_backend = attn_backend
137
+ self.model_name = _resolve_esm3_checkpoint_key(model_name)
138
+ self.tie_word_embeddings = False
139
+
140
+
141
+ @dataclass
142
+ class FastESM3Output(ModelOutput):
143
+ loss: Optional[torch.Tensor] = None
144
+ logits: Optional[torch.Tensor] = None
145
+ last_hidden_state: Optional[torch.Tensor] = None
146
+ sequence_logits: Optional[torch.Tensor] = None
147
+ structure_logits: Optional[torch.Tensor] = None
148
+ secondary_structure_logits: Optional[torch.Tensor] = None
149
+ sasa_logits: Optional[torch.Tensor] = None
150
+ function_logits: Optional[torch.Tensor] = None
151
+ residue_logits: Optional[torch.Tensor] = None
152
+ embeddings: Optional[torch.Tensor] = None
153
+ hidden_states: Optional[tuple[torch.Tensor, ...]] = None
154
+ attentions: Optional[tuple[torch.Tensor, ...]] = None
155
+
156
+
157
+ class EsmSequenceTokenizer(PreTrainedTokenizerFast):
158
+ model_input_names = ["input_ids", "attention_mask"]
159
+
160
+ def __init__(
161
+ self,
162
+ unk_token: str = "<unk>",
163
+ cls_token: str = "<cls>",
164
+ pad_token: str = "<pad>",
165
+ mask_token: str = "<mask>",
166
+ eos_token: str = "<eos>",
167
+ chain_break_token: str = "|",
168
+ **kwargs,
169
+ ):
170
+ token_to_id = {token: index for index, token in enumerate(SEQUENCE_VOCAB)}
171
+ bpe = BPE(token_to_id, merges=[], unk_token=unk_token)
172
+ tokenizer = Tokenizer(bpe)
173
+ special_tokens = [
174
+ cls_token,
175
+ pad_token,
176
+ mask_token,
177
+ eos_token,
178
+ chain_break_token,
179
+ ]
180
+ self.cb_token = chain_break_token
181
+ tokenizer.add_special_tokens(special_tokens)
182
+ tokenizer.post_processor = TemplateProcessing(
183
+ single="<cls> $A <eos>",
184
+ pair="<cls>:0 $A:0 <eos>:0 $B:1 <eos>:1",
185
+ special_tokens=[
186
+ ("<cls>", tokenizer.token_to_id("<cls>")),
187
+ ("<eos>", tokenizer.token_to_id("<eos>")),
188
+ ],
189
+ )
190
+ super().__init__(
191
+ tokenizer_object=tokenizer,
192
+ unk_token=unk_token,
193
+ cls_token=cls_token,
194
+ pad_token=pad_token,
195
+ mask_token=mask_token,
196
+ eos_token=eos_token,
197
+ additional_special_tokens=[chain_break_token],
198
+ **kwargs,
199
+ )
200
+
201
+ @property
202
+ def bos_token(self) -> str:
203
+ return self.cls_token
204
+
205
+ @property
206
+ def bos_token_id(self) -> int:
207
+ return self.cls_token_id
208
+
209
+ @property
210
+ def chain_break_token(self) -> str:
211
+ return self.cb_token
212
+
213
+ @property
214
+ def chain_break_token_id(self) -> int:
215
+ token_id = self.convert_tokens_to_ids(self.chain_break_token)
216
+ assert isinstance(token_id, int)
217
+ return token_id
218
+
219
+ @property
220
+ def all_token_ids(self) -> list[int]:
221
+ return list(range(self.vocab_size))
222
+
223
+ @property
224
+ def special_token_ids(self) -> list[int]:
225
+ return self.all_special_ids
226
+
227
+
228
+ @dataclass
229
+ class FastESM3TokenizerCollection:
230
+ sequence: EsmSequenceTokenizer
231
+ structure: Optional[object] = None
232
+ secondary_structure: Optional[object] = None
233
+ sasa: Optional[object] = None
234
+ function: Optional[object] = None
235
+ residue_annotations: Optional[object] = None
236
+
237
+
238
+ def rbf(values: torch.Tensor, v_min: float, v_max: float, n_bins: int = 16) -> torch.Tensor:
239
+ centers = torch.linspace(
240
+ v_min,
241
+ v_max,
242
+ n_bins,
243
+ device=values.device,
244
+ dtype=values.dtype,
245
+ )
246
+ centers = centers.view([1] * len(values.shape) + [-1])
247
+ std = (v_max - v_min) / n_bins
248
+ z = (values.unsqueeze(-1) - centers) / std
249
+ return torch.exp(-(z**2))
250
+
251
+
252
+ def RegressionHead(
253
+ d_model: int,
254
+ output_dim: int,
255
+ hidden_dim: Optional[int] = None,
256
+ ) -> nn.Module:
257
+ hidden_dim = hidden_dim if hidden_dim is not None else d_model
258
+ return nn.Sequential(
259
+ nn.Linear(d_model, hidden_dim),
260
+ nn.GELU(),
261
+ nn.LayerNorm(hidden_dim),
262
+ nn.Linear(hidden_dim, output_dim),
263
+ )
264
+
265
+
266
+ def rotate_half(x: torch.Tensor, interleaved: bool = False) -> torch.Tensor:
267
+ if not interleaved:
268
+ x1, x2 = x.chunk(2, dim=-1)
269
+ return torch.cat((-x2, x1), dim=-1)
270
+ x1, x2 = x[..., ::2], x[..., 1::2]
271
+ return rearrange(
272
+ torch.stack((-x2, x1), dim=-1),
273
+ "... d two -> ... (d two)",
274
+ two=2,
275
+ )
276
+
277
+
278
+ def apply_rotary_emb_torch(
279
+ x: torch.Tensor,
280
+ cos: torch.Tensor,
281
+ sin: torch.Tensor,
282
+ interleaved: bool = False,
283
+ ) -> torch.Tensor:
284
+ ro_dim = cos.shape[-1] * 2
285
+ assert ro_dim <= x.shape[-1]
286
+ seqlen = x.size(1)
287
+ cos = cos[:seqlen]
288
+ sin = sin[:seqlen]
289
+ cos = einops.repeat(cos, "s d -> s 1 (2 d)")
290
+ sin = einops.repeat(sin, "s d -> s 1 (2 d)")
291
+ return torch.cat(
292
+ [
293
+ x[..., :ro_dim] * cos + rotate_half(x[..., :ro_dim], interleaved) * sin,
294
+ x[..., ro_dim:],
295
+ ],
296
+ dim=-1,
297
+ )
298
+
299
+
300
+ class RotaryEmbedding(nn.Module):
301
+ def __init__(
302
+ self,
303
+ dim: int,
304
+ base: float = 10000.0,
305
+ interleaved: bool = False,
306
+ scale_base: Optional[float] = None,
307
+ scaling_factor: float = 1.0,
308
+ pos_idx_in_fp32: bool = True,
309
+ device: Optional[torch.device] = None,
310
+ ):
311
+ super().__init__()
312
+ self.dim = dim
313
+ self.base = float(base)
314
+ self.pos_idx_in_fp32 = pos_idx_in_fp32
315
+ self.interleaved = interleaved
316
+ self.scale_base = scale_base
317
+ self.scaling_factor = scaling_factor
318
+ self.device = device
319
+ self._seq_len_cached = 0
320
+ self._cos_cached = None
321
+ self._sin_cached = None
322
+ self.reset_parameters()
323
+
324
+ def reset_parameters(self) -> None:
325
+ inv_freq = self._compute_inv_freq(self.device)
326
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
327
+ arange = torch.arange(0, self.dim, 2, device=self.device, dtype=torch.float32)
328
+ scale = (
329
+ (arange + 0.4 * self.dim) / (1.4 * self.dim)
330
+ if self.scale_base is not None
331
+ else None
332
+ )
333
+ self.register_buffer("scale", scale)
334
+
335
+ def _compute_inv_freq(self, device: Optional[torch.device] = None) -> torch.Tensor:
336
+ return 1 / (
337
+ self.base
338
+ ** (
339
+ torch.arange(0, self.dim, 2, device=device, dtype=torch.float32)
340
+ / self.dim
341
+ )
342
+ )
343
+
344
+ def _update_cos_sin_cache(
345
+ self,
346
+ seqlen: int,
347
+ device: Optional[torch.device] = None,
348
+ dtype: Optional[torch.dtype] = None,
349
+ ) -> None:
350
+ if (
351
+ seqlen > self._seq_len_cached
352
+ or self._cos_cached is None
353
+ or self._cos_cached.device != device
354
+ or self._cos_cached.dtype != dtype
355
+ or (self.training and self._cos_cached.is_inference())
356
+ ):
357
+ self._seq_len_cached = seqlen
358
+ if self.pos_idx_in_fp32:
359
+ t = torch.arange(seqlen, device=device, dtype=torch.float32)
360
+ t /= self.scaling_factor
361
+ inv_freq = self.inv_freq.to(torch.float32)
362
+ else:
363
+ t = torch.arange(seqlen, device=device, dtype=self.inv_freq.dtype)
364
+ t /= self.scaling_factor
365
+ inv_freq = self.inv_freq
366
+ freqs = torch.outer(t, inv_freq)
367
+
368
+ if self.scale is None:
369
+ self._cos_cached = torch.cos(freqs).to(dtype)
370
+ self._sin_cached = torch.sin(freqs).to(dtype)
371
+ else:
372
+ raise NotImplementedError("Scaled rotary embeddings are not used by ESM3.")
373
+
374
+ def forward(
375
+ self,
376
+ q: torch.Tensor,
377
+ k: torch.Tensor,
378
+ seqlen_offset: int = 0,
379
+ ) -> tuple[torch.Tensor, torch.Tensor]:
380
+ self._update_cos_sin_cache(
381
+ q.shape[1] + seqlen_offset,
382
+ device=q.device,
383
+ dtype=q.dtype,
384
+ )
385
+ assert self._cos_cached is not None
386
+ assert self._sin_cached is not None
387
+ return (
388
+ apply_rotary_emb_torch(
389
+ q,
390
+ self._cos_cached[seqlen_offset:],
391
+ self._sin_cached[seqlen_offset:],
392
+ self.interleaved,
393
+ ),
394
+ apply_rotary_emb_torch(
395
+ k,
396
+ self._cos_cached[seqlen_offset:],
397
+ self._sin_cached[seqlen_offset:],
398
+ self.interleaved,
399
+ ),
400
+ )
401
+
402
+
403
+ def fp32_autocast_context(device_type: str):
404
+ if device_type == "cuda":
405
+ return torch.autocast(device_type="cuda", enabled=False)
406
+ return torch.autocast(device_type=device_type, enabled=False)
407
+
408
+
409
+ class RotationMatrix:
410
+ def __init__(self, rots: torch.Tensor):
411
+ if rots.shape[-1] == 9:
412
+ rots = rots.unflatten(-1, (3, 3))
413
+ assert rots.shape[-1] == 3
414
+ assert rots.shape[-2] == 3
415
+ self._rots = rots.to(torch.float32)
416
+
417
+ @classmethod
418
+ def identity(cls, shape: tuple[int, ...], **tensor_kwargs) -> "RotationMatrix":
419
+ rots = torch.eye(3, **tensor_kwargs)
420
+ rots = rots.view(*[1 for _ in range(len(shape))], 3, 3)
421
+ rots = rots.expand(*shape, -1, -1)
422
+ return cls(rots)
423
+
424
+ def __getitem__(self, idx) -> "RotationMatrix":
425
+ indices = (idx,) if isinstance(idx, int) or idx is None else tuple(idx)
426
+ return RotationMatrix(self._rots[indices + (slice(None), slice(None))])
427
+
428
+ @property
429
+ def shape(self) -> torch.Size:
430
+ return self._rots.shape[:-2]
431
+
432
+ @property
433
+ def tensor(self) -> torch.Tensor:
434
+ return self._rots.flatten(-2)
435
+
436
+ @property
437
+ def device(self) -> torch.device:
438
+ return self._rots.device
439
+
440
+ def as_matrix(self) -> "RotationMatrix":
441
+ return self
442
+
443
+ def apply(self, p: torch.Tensor) -> torch.Tensor:
444
+ with fp32_autocast_context(self.device.type):
445
+ p = p.to(self._rots.dtype)
446
+ if self._rots.shape[-3] == 1:
447
+ return p @ self._rots.transpose(-1, -2).squeeze(-3)
448
+ return torch.einsum("...ij,...j", self._rots, p)
449
+
450
+ def invert(self) -> "RotationMatrix":
451
+ return RotationMatrix(self._rots.transpose(-1, -2))
452
+
453
+ @staticmethod
454
+ def from_graham_schmidt(
455
+ x_axis: torch.Tensor,
456
+ xy_plane: torch.Tensor,
457
+ eps: float = 1e-12,
458
+ ) -> "RotationMatrix":
459
+ with fp32_autocast_context(x_axis.device.type):
460
+ e1 = xy_plane
461
+ denom = torch.sqrt((x_axis**2).sum(dim=-1, keepdim=True) + eps)
462
+ x_axis = x_axis / denom
463
+ dot = (x_axis * e1).sum(dim=-1, keepdim=True)
464
+ e1 = e1 - x_axis * dot
465
+ denom = torch.sqrt((e1**2).sum(dim=-1, keepdim=True) + eps)
466
+ e1 = e1 / denom
467
+ e2 = torch.cross(x_axis, e1, dim=-1)
468
+ return RotationMatrix(torch.stack([x_axis, e1, e2], dim=-1))
469
+
470
+
471
+ @dataclass(frozen=True)
472
+ class Affine3D:
473
+ trans: torch.Tensor
474
+ rot: RotationMatrix
475
+
476
+ def __post_init__(self) -> None:
477
+ assert self.trans.shape[:-1] == self.rot.shape
478
+
479
+ def __getitem__(self, idx) -> "Affine3D":
480
+ indices = (idx,) if isinstance(idx, int) or idx is None else tuple(idx)
481
+ return Affine3D(
482
+ trans=self.trans[indices + (slice(None),)],
483
+ rot=self.rot[idx],
484
+ )
485
+
486
+ @property
487
+ def shape(self) -> torch.Size:
488
+ return self.trans.shape[:-1]
489
+
490
+ @property
491
+ def dtype(self) -> torch.dtype:
492
+ return self.trans.dtype
493
+
494
+ @property
495
+ def device(self) -> torch.device:
496
+ return self.trans.device
497
+
498
+ @property
499
+ def tensor(self) -> torch.Tensor:
500
+ return torch.cat([self.rot.tensor, self.trans], dim=-1)
501
+
502
+ def as_matrix(self) -> "Affine3D":
503
+ return Affine3D(trans=self.trans, rot=self.rot.as_matrix())
504
+
505
+ def apply(self, p: torch.Tensor) -> torch.Tensor:
506
+ return self.rot.apply(p) + self.trans
507
+
508
+ @staticmethod
509
+ def from_tensor(t: torch.Tensor) -> "Affine3D":
510
+ match t.shape[-1]:
511
+ case 12:
512
+ trans = t[..., -3:]
513
+ rot = RotationMatrix(t[..., :-3].unflatten(-1, (3, 3)))
514
+ case _:
515
+ raise RuntimeError(
516
+ f"Cannot detect rotation format from {t.shape[-1] - 3}-d flat vector"
517
+ )
518
+ return Affine3D(trans, rot)
519
+
520
+ @staticmethod
521
+ def from_graham_schmidt(
522
+ neg_x_axis: torch.Tensor,
523
+ origin: torch.Tensor,
524
+ xy_plane: torch.Tensor,
525
+ eps: float = 1e-10,
526
+ ) -> "Affine3D":
527
+ x_axis = origin - neg_x_axis
528
+ xy_plane = xy_plane - origin
529
+ return Affine3D(
530
+ trans=origin,
531
+ rot=RotationMatrix.from_graham_schmidt(x_axis, xy_plane, eps),
532
+ )
533
+
534
+
535
+ def build_affine3d_from_coordinates(coords: torch.Tensor) -> tuple[Affine3D, torch.Tensor]:
536
+ max_supported_distance = 1e6
537
+ coord_mask = torch.all(
538
+ torch.all(torch.isfinite(coords) & (coords < max_supported_distance), dim=-1),
539
+ dim=-1,
540
+ )
541
+
542
+ def atom3_to_backbone_affine(bb_positions: torch.Tensor) -> Affine3D:
543
+ n_atom, ca_atom, c_atom = bb_positions.unbind(dim=-2)
544
+ return Affine3D.from_graham_schmidt(c_atom, ca_atom, n_atom)
545
+
546
+ coords = coords.clone().float()
547
+ coords[~coord_mask] = 0
548
+ average_per_n_ca_c = coords.masked_fill(~coord_mask[..., None, None], 0).sum(1) / (
549
+ coord_mask.sum(-1)[..., None, None] + 1e-8
550
+ )
551
+ affine_from_average = atom3_to_backbone_affine(
552
+ average_per_n_ca_c.float()
553
+ ).as_matrix()
554
+
555
+ batch_size, seq_len, _, _ = coords.shape
556
+ affine_rot_mats = affine_from_average.rot.tensor[..., None, :].expand(
557
+ batch_size,
558
+ seq_len,
559
+ 9,
560
+ )
561
+ affine_trans = affine_from_average.trans[..., None, :].expand(batch_size, seq_len, 3)
562
+ identity_rot = RotationMatrix.identity(
563
+ (batch_size, seq_len),
564
+ dtype=torch.float32,
565
+ device=coords.device,
566
+ requires_grad=False,
567
+ )
568
+ affine_rot_mats = affine_rot_mats.where(
569
+ coord_mask.any(-1)[..., None, None],
570
+ identity_rot.tensor,
571
+ )
572
+ black_hole_affine = Affine3D(affine_trans, RotationMatrix(affine_rot_mats))
573
+
574
+ affine = atom3_to_backbone_affine(coords.float())
575
+ affine = Affine3D.from_tensor(
576
+ affine.tensor.where(coord_mask[..., None], black_hole_affine.tensor)
577
+ )
578
+ return affine, coord_mask
579
+
580
+
581
+ class MultiHeadAttention(nn.Module):
582
+ def __init__(
583
+ self,
584
+ d_model: int,
585
+ n_heads: int,
586
+ bias: bool = False,
587
+ qk_layernorm: bool = True,
588
+ ):
589
+ super().__init__()
590
+ self.d_model = d_model
591
+ self.n_heads = n_heads
592
+ self.d_head = self.d_model // self.n_heads
593
+ self.layernorm_qkv = nn.Sequential(
594
+ nn.LayerNorm(d_model),
595
+ nn.Linear(d_model, d_model * 3, bias=bias),
596
+ )
597
+ self.out_proj = nn.Linear(d_model, d_model, bias=bias)
598
+ if qk_layernorm:
599
+ self.q_ln = nn.LayerNorm(d_model, bias=bias)
600
+ self.k_ln = nn.LayerNorm(d_model, bias=bias)
601
+ else:
602
+ self.q_ln = nn.Identity()
603
+ self.k_ln = nn.Identity()
604
+ self.rotary = RotaryEmbedding(d_model // n_heads)
605
+
606
+ def _apply_rotary(
607
+ self,
608
+ q: torch.Tensor,
609
+ k: torch.Tensor,
610
+ ) -> tuple[torch.Tensor, torch.Tensor]:
611
+ q = q.unflatten(-1, (self.n_heads, self.d_head))
612
+ k = k.unflatten(-1, (self.n_heads, self.d_head))
613
+ q, k = self.rotary(q, k)
614
+ q = q.flatten(-2, -1)
615
+ k = k.flatten(-2, -1)
616
+ return q, k
617
+
618
+ def forward(
619
+ self,
620
+ x: torch.Tensor,
621
+ seq_id: Optional[torch.Tensor],
622
+ output_attentions: bool = False,
623
+ ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
624
+ qkv = self.layernorm_qkv(x)
625
+ query, key, value = torch.chunk(qkv, 3, dim=-1)
626
+ query = self.q_ln(query).to(query.dtype)
627
+ key = self.k_ln(key).to(query.dtype)
628
+ query, key = self._apply_rotary(query, key)
629
+
630
+ reshaper = functools.partial(
631
+ einops.rearrange,
632
+ pattern="b s (h d) -> b h s d",
633
+ h=self.n_heads,
634
+ )
635
+ query, key, value = map(reshaper, (query, key, value))
636
+
637
+ if seq_id is not None:
638
+ mask = seq_id.unsqueeze(-1) == seq_id.unsqueeze(-2)
639
+ mask = mask.unsqueeze(1)
640
+ else:
641
+ mask = None
642
+
643
+ if output_attentions:
644
+ scale = self.d_head**-0.5
645
+ attn_scores = torch.einsum("bhld,bhsd->bhls", query, key) * scale
646
+ if mask is not None:
647
+ attn_scores = attn_scores.masked_fill(~mask, float("-inf"))
648
+ attn_weights = torch.softmax(attn_scores, dim=-1)
649
+ context = torch.einsum("bhls,bhsd->bhld", attn_weights, value)
650
+ else:
651
+ attn_weights = None
652
+ if mask is None:
653
+ context = F.scaled_dot_product_attention(query, key, value)
654
+ else:
655
+ context = F.scaled_dot_product_attention(query, key, value, mask)
656
+
657
+ context = einops.rearrange(context, "b h s d -> b s (h d)")
658
+ return self.out_proj(context), attn_weights
659
+
660
+
661
+ class GeometricReasoningOriginalImpl(nn.Module):
662
+ def __init__(
663
+ self,
664
+ c_s: int,
665
+ v_heads: int,
666
+ num_vector_messages: int = 1,
667
+ mask_and_zero_frameless: bool = True,
668
+ bias: bool = False,
669
+ ):
670
+ super().__init__()
671
+ self.c_s = c_s
672
+ self.v_heads = v_heads
673
+ self.num_vector_messages = num_vector_messages
674
+ self.mask_and_zero_frameless = mask_and_zero_frameless
675
+ self.s_norm = nn.LayerNorm(c_s, bias=bias)
676
+ dim_proj = 4 * self.v_heads * 3 + self.v_heads * 3 * self.num_vector_messages
677
+ self.proj = nn.Linear(c_s, dim_proj, bias=bias)
678
+ channels_out = self.v_heads * 3 * self.num_vector_messages
679
+ self.out_proj = nn.Linear(channels_out, c_s, bias=bias)
680
+ self.distance_scale_per_head = nn.Parameter(torch.zeros((self.v_heads)))
681
+ self.rotation_scale_per_head = nn.Parameter(torch.zeros((self.v_heads)))
682
+
683
+ def forward(
684
+ self,
685
+ s: torch.Tensor,
686
+ affine: Affine3D,
687
+ affine_mask: torch.Tensor,
688
+ sequence_id: Optional[torch.Tensor],
689
+ chain_id: torch.Tensor,
690
+ ) -> torch.Tensor:
691
+ if sequence_id is None:
692
+ sequence_id = torch.zeros_like(s[..., 0], dtype=torch.int64)
693
+ attn_bias = sequence_id.unsqueeze(-1) == sequence_id.unsqueeze(-2)
694
+ attn_bias = attn_bias.unsqueeze(1).float()
695
+ attn_bias = attn_bias.masked_fill(
696
+ ~affine_mask[:, None, None, :],
697
+ torch.finfo(attn_bias.dtype).min,
698
+ )
699
+ chain_id_mask = chain_id.unsqueeze(1) != chain_id.unsqueeze(2)
700
+ attn_bias = attn_bias.masked_fill(
701
+ chain_id_mask.unsqueeze(1),
702
+ torch.finfo(s.dtype).min,
703
+ )
704
+
705
+ ns = self.s_norm(s)
706
+ vec_rot, vec_dist = self.proj(ns).split(
707
+ [
708
+ self.v_heads * 2 * 3 + self.v_heads * 3 * self.num_vector_messages,
709
+ self.v_heads * 2 * 3,
710
+ ],
711
+ dim=-1,
712
+ )
713
+
714
+ query_rot, key_rot, value = (
715
+ affine.rot[..., None]
716
+ .apply(rearrange(vec_rot, "... (h c) -> ... h c", c=3))
717
+ .split(
718
+ [self.v_heads, self.v_heads, self.v_heads * self.num_vector_messages],
719
+ dim=-2,
720
+ )
721
+ )
722
+ query_dist, key_dist = (
723
+ affine[..., None]
724
+ .apply(rearrange(vec_dist, "... (h c) -> ... h c", c=3))
725
+ .chunk(2, dim=-2)
726
+ )
727
+
728
+ query_dist = rearrange(query_dist, "b s h d -> b h s 1 d")
729
+ key_dist = rearrange(key_dist, "b s h d -> b h 1 s d")
730
+ query_rot = rearrange(query_rot, "b s h d -> b h s d")
731
+ key_rot = rearrange(key_rot, "b s h d -> b h d s")
732
+ value = rearrange(
733
+ value,
734
+ "b s (h m) d -> b h s (m d)",
735
+ m=self.num_vector_messages,
736
+ )
737
+
738
+ distance_term = (query_dist - key_dist).norm(dim=-1) / math.sqrt(3)
739
+ rotation_term = query_rot.matmul(key_rot) / math.sqrt(3)
740
+ distance_term_weight = rearrange(
741
+ F.softplus(self.distance_scale_per_head),
742
+ "h -> h 1 1",
743
+ )
744
+ rotation_term_weight = rearrange(
745
+ F.softplus(self.rotation_scale_per_head),
746
+ "h -> h 1 1",
747
+ )
748
+ attn_weight = (
749
+ rotation_term * rotation_term_weight - distance_term * distance_term_weight
750
+ )
751
+
752
+ s_q = attn_weight.size(2)
753
+ s_k = attn_weight.size(3)
754
+ offset_q = max(0, attn_bias.size(2) - s_q)
755
+ offset_k = max(0, attn_bias.size(3) - s_k)
756
+ attn_bias = attn_bias[:, :, offset_q:, offset_k:]
757
+ attn_weight = torch.softmax(attn_weight + attn_bias, dim=-1)
758
+
759
+ attn_out = attn_weight.matmul(value)
760
+ attn_out = (
761
+ affine.rot[..., None]
762
+ .invert()
763
+ .apply(
764
+ rearrange(
765
+ attn_out,
766
+ "b h s (m d) -> b s (h m) d",
767
+ m=self.num_vector_messages,
768
+ )
769
+ )
770
+ )
771
+ attn_out = rearrange(
772
+ attn_out,
773
+ "b s (h m) d -> b s (h m d)",
774
+ m=self.num_vector_messages,
775
+ )
776
+ if self.mask_and_zero_frameless:
777
+ attn_out = attn_out.masked_fill(~affine_mask[..., None], 0.0)
778
+ attn_out = attn_out.to(self.out_proj.weight.dtype)
779
+ return self.out_proj(attn_out)
780
+
781
+
782
+ def swiglu_correction_fn(expansion_ratio: float, d_model: int) -> int:
783
+ return int(((expansion_ratio * d_model) + 255) // 256 * 256)
784
+
785
+
786
+ class SwiGLU(nn.Module):
787
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
788
+ x1, x2 = x.chunk(2, dim=-1)
789
+ return F.silu(x1) * x2
790
+
791
+
792
+ def swiglu_ln_ffn(d_model: int, expansion_ratio: float, bias: bool) -> nn.Module:
793
+ return nn.Sequential(
794
+ nn.LayerNorm(d_model),
795
+ nn.Linear(
796
+ d_model,
797
+ swiglu_correction_fn(expansion_ratio, d_model) * 2,
798
+ bias=bias,
799
+ ),
800
+ SwiGLU(),
801
+ nn.Linear(swiglu_correction_fn(expansion_ratio, d_model), d_model, bias=bias),
802
+ )
803
+
804
+
805
+ def gelu_ln_ffn(d_model: int, expansion_ratio: float, bias: bool) -> nn.Module:
806
+ hidden_dim = int(expansion_ratio * d_model)
807
+ return nn.Sequential(
808
+ nn.LayerNorm(d_model),
809
+ nn.Linear(d_model, hidden_dim, bias=bias),
810
+ nn.GELU(),
811
+ nn.Linear(hidden_dim, d_model, bias=bias),
812
+ )
813
+
814
+
815
+ class UnifiedTransformerBlock(nn.Module):
816
+ def __init__(
817
+ self,
818
+ d_model: int,
819
+ n_heads: int,
820
+ use_geom_attn: bool = False,
821
+ use_plain_attn: bool = True,
822
+ v_heads: Optional[int] = None,
823
+ bias: bool = False,
824
+ expansion_ratio: float = 4.0,
825
+ residue_scaling_factor: float = 1.0,
826
+ mask_and_zero_frameless: bool = False,
827
+ qk_layernorm: bool = True,
828
+ ffn_type: str = "swiglu",
829
+ ):
830
+ super().__init__()
831
+ self.use_plain_attn = use_plain_attn
832
+ if self.use_plain_attn:
833
+ self.attn = MultiHeadAttention(
834
+ d_model,
835
+ n_heads,
836
+ bias,
837
+ qk_layernorm=qk_layernorm,
838
+ )
839
+ self.use_geom_attn = use_geom_attn
840
+ if self.use_geom_attn:
841
+ assert v_heads is not None
842
+ self.geom_attn = GeometricReasoningOriginalImpl(
843
+ c_s=d_model,
844
+ v_heads=v_heads,
845
+ bias=bias,
846
+ mask_and_zero_frameless=mask_and_zero_frameless,
847
+ )
848
+ if ffn_type == "swiglu":
849
+ self.ffn = swiglu_ln_ffn(d_model, expansion_ratio, bias)
850
+ elif ffn_type == "gelu":
851
+ self.ffn = gelu_ln_ffn(d_model, expansion_ratio, bias)
852
+ else:
853
+ raise ValueError(f"Unknown ffn_type: {ffn_type}")
854
+ self.scaling_factor = residue_scaling_factor
855
+
856
+ def forward(
857
+ self,
858
+ x: torch.Tensor,
859
+ sequence_id: Optional[torch.Tensor],
860
+ frames: Affine3D,
861
+ frames_mask: torch.Tensor,
862
+ chain_id: torch.Tensor,
863
+ output_attentions: bool = False,
864
+ ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
865
+ attn_weights = None
866
+ if self.use_plain_attn:
867
+ r1, attn_weights = self.attn(
868
+ x,
869
+ sequence_id,
870
+ output_attentions=output_attentions,
871
+ )
872
+ x = x + r1 / self.scaling_factor
873
+
874
+ if self.use_geom_attn:
875
+ r2 = self.geom_attn(x, frames, frames_mask, sequence_id, chain_id)
876
+ x = x + r2 / self.scaling_factor
877
+
878
+ r3 = self.ffn(x) / self.scaling_factor
879
+ x = x + r3
880
+ return x, attn_weights
881
+
882
+
883
+ class TransformerStack(nn.Module):
884
+ def __init__(
885
+ self,
886
+ d_model: int,
887
+ n_heads: int,
888
+ v_heads: Optional[int],
889
+ n_layers: int,
890
+ n_layers_geom: int = 1,
891
+ scale_residue: bool = True,
892
+ mask_and_zero_frameless: bool = False,
893
+ bias: bool = False,
894
+ qk_layernorm: bool = True,
895
+ ffn_type: str = "swiglu",
896
+ expansion_ratio: float = 8 / 3,
897
+ ):
898
+ super().__init__()
899
+ self.blocks = nn.ModuleList(
900
+ [
901
+ UnifiedTransformerBlock(
902
+ d_model,
903
+ n_heads,
904
+ v_heads=v_heads,
905
+ use_geom_attn=index < n_layers_geom,
906
+ residue_scaling_factor=(
907
+ math.sqrt(n_layers / 36) if scale_residue else 1.0
908
+ ),
909
+ expansion_ratio=expansion_ratio,
910
+ mask_and_zero_frameless=mask_and_zero_frameless,
911
+ bias=bias,
912
+ qk_layernorm=qk_layernorm,
913
+ ffn_type=ffn_type,
914
+ )
915
+ for index in range(n_layers)
916
+ ]
917
+ )
918
+ self.norm = nn.LayerNorm(d_model, bias=False)
919
+
920
+ def forward(
921
+ self,
922
+ x: torch.Tensor,
923
+ sequence_id: Optional[torch.Tensor] = None,
924
+ affine: Optional[Affine3D] = None,
925
+ affine_mask: Optional[torch.Tensor] = None,
926
+ chain_id: Optional[torch.Tensor] = None,
927
+ output_attentions: bool = False,
928
+ ) -> tuple[
929
+ torch.Tensor,
930
+ torch.Tensor,
931
+ tuple[torch.Tensor, ...],
932
+ Optional[tuple[torch.Tensor, ...]],
933
+ ]:
934
+ *batch_dims, _ = x.shape
935
+ if chain_id is None:
936
+ chain_id = torch.ones(size=batch_dims, dtype=torch.int64, device=x.device)
937
+ assert affine is not None
938
+ assert affine_mask is not None
939
+ all_hidden_states = []
940
+ all_attentions = []
941
+ for block in self.blocks:
942
+ x, attn_weights = block(
943
+ x,
944
+ sequence_id,
945
+ affine,
946
+ affine_mask,
947
+ chain_id,
948
+ output_attentions=output_attentions,
949
+ )
950
+ all_hidden_states.append(x)
951
+ if output_attentions and attn_weights is not None:
952
+ all_attentions.append(attn_weights)
953
+ hidden_states = tuple(all_hidden_states)
954
+ attentions = tuple(all_attentions) if output_attentions else None
955
+ return self.norm(x), x, hidden_states, attentions
956
+
957
+
958
+ class EncodeInputs(nn.Module):
959
+ def __init__(self, d_model: int):
960
+ super().__init__()
961
+ self.sequence_embed = nn.Embedding(64, d_model)
962
+ self.plddt_projection = nn.Linear(16, d_model)
963
+ self.structure_per_res_plddt_projection = nn.Linear(16, d_model)
964
+ self.structure_tokens_embed = nn.Embedding(4096 + 5, d_model)
965
+ self.ss8_embed = nn.Embedding(8 + 3, d_model)
966
+ self.sasa_embed = nn.Embedding(16 + 3, d_model)
967
+ self.function_embed = nn.ModuleList(
968
+ [nn.Embedding(260, d_model // 8, padding_idx=0) for _ in range(8)]
969
+ )
970
+ self.residue_embed = nn.EmbeddingBag(1478, d_model, mode="sum", padding_idx=0)
971
+
972
+ def forward(
973
+ self,
974
+ sequence_tokens: torch.Tensor,
975
+ structure_tokens: torch.Tensor,
976
+ average_plddt: torch.Tensor,
977
+ per_res_plddt: torch.Tensor,
978
+ ss8_tokens: torch.Tensor,
979
+ sasa_tokens: torch.Tensor,
980
+ function_tokens: torch.Tensor,
981
+ residue_annotation_tokens: torch.Tensor,
982
+ ) -> torch.Tensor:
983
+ sequence_embed = self.sequence_embed(sequence_tokens)
984
+ rbf_16_fn = functools.partial(rbf, v_min=0.0, v_max=1.0, n_bins=16)
985
+ plddt_embed = self.plddt_projection(
986
+ rbf_16_fn(average_plddt).to(self.plddt_projection.weight.dtype)
987
+ )
988
+ structure_per_res_plddt = self.structure_per_res_plddt_projection(
989
+ rbf_16_fn(per_res_plddt).to(
990
+ self.structure_per_res_plddt_projection.weight.dtype
991
+ )
992
+ )
993
+ structure_embed = self.structure_tokens_embed(structure_tokens)
994
+ ss8_embed = self.ss8_embed(ss8_tokens)
995
+ sasa_embed = self.sasa_embed(sasa_tokens)
996
+ function_embed = torch.cat(
997
+ [
998
+ embed_fn(funcs)
999
+ for embed_fn, funcs in zip(
1000
+ self.function_embed,
1001
+ function_tokens.unbind(-1),
1002
+ )
1003
+ ],
1004
+ -1,
1005
+ )
1006
+
1007
+ batch_size, seq_len, num_annotations = residue_annotation_tokens.shape
1008
+ residue_embed = self.residue_embed(
1009
+ rearrange(
1010
+ residue_annotation_tokens,
1011
+ "b l n -> (b l) n",
1012
+ b=batch_size,
1013
+ l=seq_len,
1014
+ n=num_annotations,
1015
+ )
1016
+ )
1017
+ residue_embed = rearrange(
1018
+ residue_embed,
1019
+ "(b l) d -> b l d",
1020
+ b=batch_size,
1021
+ l=seq_len,
1022
+ )
1023
+
1024
+ return (
1025
+ sequence_embed
1026
+ + plddt_embed
1027
+ + structure_per_res_plddt
1028
+ + structure_embed
1029
+ + ss8_embed
1030
+ + sasa_embed
1031
+ + function_embed
1032
+ + residue_embed
1033
+ )
1034
+
1035
+
1036
+ @dataclass
1037
+ class ESM3CoreOutput:
1038
+ sequence_logits: torch.Tensor
1039
+ structure_logits: torch.Tensor
1040
+ secondary_structure_logits: torch.Tensor
1041
+ sasa_logits: torch.Tensor
1042
+ function_logits: torch.Tensor
1043
+ residue_logits: torch.Tensor
1044
+ embeddings: torch.Tensor
1045
+ hidden_states: tuple[torch.Tensor, ...]
1046
+ attentions: Optional[tuple[torch.Tensor, ...]] = None
1047
+
1048
+
1049
+ class OutputHeads(nn.Module):
1050
+ def __init__(self, d_model: int):
1051
+ super().__init__()
1052
+ self.sequence_head = RegressionHead(d_model, 64)
1053
+ self.structure_head = RegressionHead(d_model, 4096)
1054
+ self.ss8_head = RegressionHead(d_model, 8 + 3)
1055
+ self.sasa_head = RegressionHead(d_model, 16 + 3)
1056
+ self.function_head = RegressionHead(d_model, 260 * 8)
1057
+ self.residue_head = RegressionHead(d_model, 1478)
1058
+
1059
+ def forward(
1060
+ self,
1061
+ x: torch.Tensor,
1062
+ embed: torch.Tensor,
1063
+ hidden_states: tuple[torch.Tensor, ...],
1064
+ attentions: Optional[tuple[torch.Tensor, ...]] = None,
1065
+ ) -> ESM3CoreOutput:
1066
+ function_logits = self.function_head(x)
1067
+ function_logits = rearrange(function_logits, "... (k v) -> ... k v", k=8)
1068
+ return ESM3CoreOutput(
1069
+ sequence_logits=self.sequence_head(x),
1070
+ structure_logits=self.structure_head(x),
1071
+ secondary_structure_logits=self.ss8_head(x),
1072
+ sasa_logits=self.sasa_head(x),
1073
+ function_logits=function_logits,
1074
+ residue_logits=self.residue_head(x),
1075
+ embeddings=embed,
1076
+ hidden_states=hidden_states,
1077
+ attentions=attentions,
1078
+ )
1079
+
1080
+
1081
+ class ESM3Core(nn.Module):
1082
+ def __init__(
1083
+ self,
1084
+ d_model: int,
1085
+ n_heads: int,
1086
+ v_heads: int,
1087
+ n_layers: int,
1088
+ tokenizers: FastESM3TokenizerCollection,
1089
+ ):
1090
+ super().__init__()
1091
+ self.encoder = EncodeInputs(d_model)
1092
+ self.transformer = TransformerStack(
1093
+ d_model,
1094
+ n_heads,
1095
+ v_heads,
1096
+ n_layers,
1097
+ mask_and_zero_frameless=True,
1098
+ )
1099
+ self.output_heads = OutputHeads(d_model)
1100
+ self.tokenizers = tokenizers
1101
+
1102
+ def forward(
1103
+ self,
1104
+ *,
1105
+ sequence_tokens: Optional[torch.Tensor] = None,
1106
+ structure_tokens: Optional[torch.Tensor] = None,
1107
+ ss8_tokens: Optional[torch.Tensor] = None,
1108
+ sasa_tokens: Optional[torch.Tensor] = None,
1109
+ function_tokens: Optional[torch.Tensor] = None,
1110
+ residue_annotation_tokens: Optional[torch.Tensor] = None,
1111
+ average_plddt: Optional[torch.Tensor] = None,
1112
+ per_res_plddt: Optional[torch.Tensor] = None,
1113
+ structure_coords: Optional[torch.Tensor] = None,
1114
+ chain_id: Optional[torch.Tensor] = None,
1115
+ sequence_id: Optional[torch.Tensor] = None,
1116
+ output_attentions: Optional[bool] = None,
1117
+ ) -> ESM3CoreOutput:
1118
+ output_attentions = bool(output_attentions)
1119
+ present_inputs = [
1120
+ sequence_tokens,
1121
+ structure_tokens,
1122
+ ss8_tokens,
1123
+ sasa_tokens,
1124
+ structure_coords,
1125
+ function_tokens,
1126
+ residue_annotation_tokens,
1127
+ ]
1128
+ try:
1129
+ seq_len, device = next(
1130
+ (x.shape[1], x.device) for x in present_inputs if x is not None
1131
+ )
1132
+ except StopIteration:
1133
+ raise ValueError("At least one of the inputs must be non-None")
1134
+
1135
+ def defaults(x: Optional[torch.Tensor], token: int) -> torch.Tensor:
1136
+ if x is None:
1137
+ return torch.full(
1138
+ (1, seq_len),
1139
+ token,
1140
+ dtype=torch.long,
1141
+ device=device,
1142
+ )
1143
+ return x
1144
+
1145
+ sequence_tokens = defaults(sequence_tokens, self.tokenizers.sequence.mask_token_id)
1146
+ ss8_tokens = defaults(ss8_tokens, SS8_PAD_TOKEN)
1147
+ sasa_tokens = defaults(sasa_tokens, SASA_PAD_TOKEN)
1148
+ average_plddt = defaults(average_plddt, 1).float()
1149
+ per_res_plddt = defaults(per_res_plddt, 0).float()
1150
+ chain_id = defaults(chain_id, 0)
1151
+
1152
+ if residue_annotation_tokens is None:
1153
+ residue_annotation_tokens = torch.full(
1154
+ (1, seq_len, MAX_RESIDUE_ANNOTATIONS),
1155
+ RESIDUE_PAD_TOKEN,
1156
+ dtype=torch.long,
1157
+ device=device,
1158
+ )
1159
+ if function_tokens is None:
1160
+ function_tokens = torch.full(
1161
+ (1, seq_len, FUNCTION_TOKENS_DEPTH),
1162
+ INTERPRO_PAD_TOKEN,
1163
+ dtype=torch.long,
1164
+ device=device,
1165
+ )
1166
+ if structure_coords is None:
1167
+ structure_coords = torch.full(
1168
+ (1, seq_len, 3, 3),
1169
+ float("nan"),
1170
+ dtype=torch.float,
1171
+ device=device,
1172
+ )
1173
+
1174
+ structure_coords = structure_coords[..., :3, :]
1175
+ affine, affine_mask = build_affine3d_from_coordinates(structure_coords)
1176
+
1177
+ structure_tokens = defaults(structure_tokens, STRUCTURE_MASK_TOKEN)
1178
+ structure_tokens = (
1179
+ structure_tokens.masked_fill(structure_tokens == -1, STRUCTURE_MASK_TOKEN)
1180
+ .masked_fill(sequence_tokens == SEQUENCE_BOS_TOKEN, STRUCTURE_BOS_TOKEN)
1181
+ .masked_fill(sequence_tokens == SEQUENCE_PAD_TOKEN, STRUCTURE_PAD_TOKEN)
1182
+ .masked_fill(sequence_tokens == SEQUENCE_EOS_TOKEN, STRUCTURE_EOS_TOKEN)
1183
+ .masked_fill(
1184
+ sequence_tokens == SEQUENCE_CHAINBREAK_TOKEN,
1185
+ STRUCTURE_CHAINBREAK_TOKEN,
1186
+ )
1187
+ )
1188
+
1189
+ x = self.encoder(
1190
+ sequence_tokens,
1191
+ structure_tokens,
1192
+ average_plddt,
1193
+ per_res_plddt,
1194
+ ss8_tokens,
1195
+ sasa_tokens,
1196
+ function_tokens,
1197
+ residue_annotation_tokens,
1198
+ )
1199
+ x, embedding, hidden_states, attentions = self.transformer(
1200
+ x,
1201
+ sequence_id,
1202
+ affine,
1203
+ affine_mask,
1204
+ chain_id,
1205
+ output_attentions=output_attentions,
1206
+ )
1207
+ return self.output_heads(
1208
+ x,
1209
+ embedding,
1210
+ hidden_states=hidden_states,
1211
+ attentions=attentions,
1212
+ )
1213
+
1214
+
1215
+ def _resolve_esm3_checkpoint_key(model_name: str) -> str:
1216
+ if model_name in ESM3_OPEN_SMALL_ALIASES:
1217
+ return ESM3_OPEN_SMALL
1218
+ raise ValueError(
1219
+ f"Unsupported ESM3 checkpoint {model_name}. "
1220
+ f"Supported names: {sorted(ESM3_OPEN_SMALL_ALIASES)}"
1221
+ )
1222
+
1223
+
1224
+ def parse_fasta(fasta_path: str) -> list[str]:
1225
+ assert os.path.exists(fasta_path), f"FASTA file does not exist: {fasta_path}"
1226
+ sequences = []
1227
+ current_seq = []
1228
+ with open(fasta_path, "r", encoding="utf-8") as handle:
1229
+ for line in handle:
1230
+ stripped = line.strip()
1231
+ if len(stripped) == 0:
1232
+ continue
1233
+ if stripped.startswith(">"):
1234
+ if len(current_seq) > 0:
1235
+ sequences.append("".join(current_seq))
1236
+ current_seq = []
1237
+ else:
1238
+ current_seq.append(stripped)
1239
+ if len(current_seq) > 0:
1240
+ sequences.append("".join(current_seq))
1241
+ return sequences
1242
+
1243
+
1244
+ def _ensure_official_esm_on_path() -> None:
1245
+ for parent in Path(__file__).resolve().parents:
1246
+ candidate = parent / "official" / "esm"
1247
+ if (candidate / "esm" / "models" / "esm3.py").exists():
1248
+ candidate_str = str(candidate)
1249
+ if candidate_str not in sys.path:
1250
+ sys.path.insert(0, candidate_str)
1251
+ return
1252
+
1253
+
1254
+ def _make_structure_encoder(device: Union[torch.device, str]) -> nn.Module:
1255
+ _ensure_official_esm_on_path()
1256
+ pretrained = importlib.import_module("esm.pretrained")
1257
+ return pretrained.ESM3_structure_encoder_v0(device)
1258
+
1259
+
1260
+ def _make_structure_decoder(device: Union[torch.device, str]) -> nn.Module:
1261
+ _ensure_official_esm_on_path()
1262
+ pretrained = importlib.import_module("esm.pretrained")
1263
+ return pretrained.ESM3_structure_decoder_v0(device)
1264
+
1265
+
1266
+ def _make_function_decoder(device: Union[torch.device, str]) -> nn.Module:
1267
+ _ensure_official_esm_on_path()
1268
+ pretrained = importlib.import_module("esm.pretrained")
1269
+ return pretrained.ESM3_function_decoder_v0(device)
1270
+
1271
+
1272
+ def _build_official_esm3(config: FastESM3Config) -> nn.Module:
1273
+ return ESM3Core(
1274
+ d_model=config.hidden_size,
1275
+ n_heads=config.num_attention_heads,
1276
+ v_heads=config.num_vector_heads,
1277
+ n_layers=config.num_hidden_layers,
1278
+ tokenizers=FastESM3TokenizerCollection(sequence=EsmSequenceTokenizer()),
1279
+ )
1280
+
1281
+
1282
+ class FastESM3PreTrainedModel(PreTrainedModel):
1283
+ config_class = FastESM3Config
1284
+ base_model_prefix = "esm3"
1285
+ main_input_name = "input_ids"
1286
+ supports_gradient_checkpointing = False
1287
+ all_tied_weights_keys = {}
1288
+
1289
+ @classmethod
1290
+ def is_remote_code(cls) -> bool:
1291
+ return True
1292
+
1293
+ def _init_weights(self, module: nn.Module) -> None:
1294
+ for parameter in module.parameters(recurse=False):
1295
+ if "_is_hf_initialized" in parameter.__dict__ and parameter.__dict__["_is_hf_initialized"]:
1296
+ return
1297
+
1298
+ if isinstance(module, nn.Linear):
1299
+ nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
1300
+ if module.bias is not None:
1301
+ nn.init.zeros_(module.bias)
1302
+ elif isinstance(module, nn.Embedding):
1303
+ nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
1304
+ if module.padding_idx is not None:
1305
+ with torch.no_grad():
1306
+ module.weight[module.padding_idx].zero_()
1307
+ elif isinstance(module, nn.LayerNorm):
1308
+ if module.bias is not None:
1309
+ nn.init.zeros_(module.bias)
1310
+ nn.init.ones_(module.weight)
1311
+
1312
+ @property
1313
+ def attn_backend(self) -> str:
1314
+ return self.config.attn_backend
1315
+
1316
+ @attn_backend.setter
1317
+ def attn_backend(self, backend: str) -> None:
1318
+ if backend not in _SUPPORTED_ATTENTION_BACKENDS:
1319
+ raise ValueError(
1320
+ f"ESM3 currently supports only {_SUPPORTED_ATTENTION_BACKENDS}; got {backend}."
1321
+ )
1322
+ self.config.attn_backend = backend
1323
+
1324
+ @classmethod
1325
+ def from_pretrained_esm(
1326
+ cls,
1327
+ model_name: str = ESM3_OPEN_SMALL,
1328
+ device: Union[torch.device, str] = "cpu",
1329
+ dtype: Optional[torch.dtype] = None,
1330
+ ) -> "FastESM3Model":
1331
+ key = _resolve_esm3_checkpoint_key(model_name)
1332
+ spec = _ESM3_CHECKPOINT_SPECS[key]
1333
+ config = FastESM3Config(
1334
+ hidden_size=spec["hidden_size"],
1335
+ num_attention_heads=spec["num_attention_heads"],
1336
+ num_vector_heads=spec["num_vector_heads"],
1337
+ num_hidden_layers=spec["num_hidden_layers"],
1338
+ model_name=key,
1339
+ )
1340
+ model = FastESM3Model(config)
1341
+ checkpoint_root = Path(
1342
+ snapshot_download(
1343
+ repo_id=spec["repo_id"],
1344
+ allow_patterns=["data/weights/esm3_sm_open_v1.pth"],
1345
+ )
1346
+ )
1347
+ state_dict = torch.load(
1348
+ checkpoint_root / "data" / "weights" / "esm3_sm_open_v1.pth",
1349
+ map_location=torch.device(device),
1350
+ )
1351
+ load_result = model.esm3.load_state_dict(state_dict, strict=True)
1352
+ assert len(load_result.missing_keys) == 0, load_result.missing_keys
1353
+ assert len(load_result.unexpected_keys) == 0, load_result.unexpected_keys
1354
+ model = model.to(device)
1355
+ if dtype is not None:
1356
+ model = model.to(dtype=dtype)
1357
+ model.eval()
1358
+ return model
1359
+
1360
+
1361
+ class FastESM3Model(FastESM3PreTrainedModel):
1362
+ config_class = FastESM3Config
1363
+
1364
+ def __init__(self, config: FastESM3Config, **kwargs):
1365
+ super().__init__(config, **kwargs)
1366
+ self.tokenizer = EsmSequenceTokenizer()
1367
+ self.esm3 = _build_official_esm3(config)
1368
+ self.__dict__["_official_sdk_model"] = None
1369
+
1370
+ @property
1371
+ def device(self) -> torch.device:
1372
+ return next(self.parameters()).device
1373
+
1374
+ @property
1375
+ def raw_model(self) -> nn.Module:
1376
+ return self.esm3
1377
+
1378
+ def _get_official_sdk_model(self) -> nn.Module:
1379
+ cached_model = self.__dict__["_official_sdk_model"]
1380
+ if cached_model is not None:
1381
+ return cached_model
1382
+ _ensure_official_esm_on_path()
1383
+ esm3_module = importlib.import_module("esm.models.esm3")
1384
+ tokenization = importlib.import_module("esm.tokenization")
1385
+
1386
+ sdk_model = esm3_module.ESM3(
1387
+ d_model=self.config.hidden_size,
1388
+ n_heads=self.config.num_attention_heads,
1389
+ v_heads=self.config.num_vector_heads,
1390
+ n_layers=self.config.num_hidden_layers,
1391
+ structure_encoder_fn=_make_structure_encoder,
1392
+ structure_decoder_fn=_make_structure_decoder,
1393
+ function_decoder_fn=_make_function_decoder,
1394
+ tokenizers=tokenization.get_esm3_model_tokenizers(self.config.model_name),
1395
+ )
1396
+ load_result = sdk_model.load_state_dict(self.esm3.state_dict(), strict=True)
1397
+ assert len(load_result.missing_keys) == 0, load_result.missing_keys
1398
+ assert len(load_result.unexpected_keys) == 0, load_result.unexpected_keys
1399
+ dtype = next(self.esm3.parameters()).dtype
1400
+ sdk_model = sdk_model.to(self.device).to(dtype=dtype).eval()
1401
+ self.__dict__["_official_sdk_model"] = sdk_model
1402
+ return sdk_model
1403
+
1404
+ def get_input_embeddings(self) -> nn.Module:
1405
+ return self.esm3.encoder.sequence_embed
1406
+
1407
+ def set_input_embeddings(self, value: nn.Module) -> None:
1408
+ self.esm3.encoder.sequence_embed = value
1409
+
1410
+ def tokenize_sequences(
1411
+ self,
1412
+ sequences: Union[str, list[str]],
1413
+ padding: bool = True,
1414
+ return_tensors: str = "pt",
1415
+ device: Optional[Union[torch.device, str]] = None,
1416
+ add_special_tokens: bool = True,
1417
+ ) -> dict[str, torch.Tensor]:
1418
+ tokenized = self.tokenizer(
1419
+ sequences,
1420
+ padding=padding,
1421
+ return_tensors=return_tensors,
1422
+ add_special_tokens=add_special_tokens,
1423
+ )
1424
+ if device is None:
1425
+ return tokenized
1426
+ return {name: tensor.to(device) for name, tensor in tokenized.items()}
1427
+
1428
+ def forward_sequence(
1429
+ self,
1430
+ sequences: Union[str, list[str]],
1431
+ device: Optional[Union[torch.device, str]] = None,
1432
+ **kwargs,
1433
+ ) -> FastESM3Output:
1434
+ if device is None:
1435
+ device = self.device
1436
+ tokenized = self.tokenize_sequences(sequences, device=device)
1437
+ return self(**tokenized, **kwargs)
1438
+
1439
+ def _embed(
1440
+ self,
1441
+ input_ids: torch.Tensor,
1442
+ attention_mask: Optional[torch.Tensor] = None,
1443
+ **kwargs,
1444
+ ) -> torch.Tensor:
1445
+ return self(
1446
+ input_ids=input_ids,
1447
+ attention_mask=attention_mask,
1448
+ **kwargs,
1449
+ ).last_hidden_state
1450
+
1451
+ def _pool_embeddings(
1452
+ self,
1453
+ embeddings: torch.Tensor,
1454
+ attention_mask: torch.Tensor,
1455
+ pooling_types: list[str],
1456
+ ) -> torch.Tensor:
1457
+ pooled = []
1458
+ mask = attention_mask.to(dtype=embeddings.dtype).unsqueeze(-1)
1459
+ for pooling_type in pooling_types:
1460
+ if pooling_type == "mean":
1461
+ pooled.append((embeddings * mask).sum(dim=1) / mask.sum(dim=1))
1462
+ elif pooling_type == "cls":
1463
+ pooled.append(embeddings[:, 0, :])
1464
+ elif pooling_type == "max":
1465
+ bool_mask = attention_mask.unsqueeze(-1).bool()
1466
+ pooled.append(
1467
+ embeddings.masked_fill(~bool_mask, float("-inf")).max(dim=1).values
1468
+ )
1469
+ else:
1470
+ raise ValueError(
1471
+ f"Unsupported ESM3 pooling type {pooling_type}. "
1472
+ "Supported values are 'mean', 'cls', and 'max'."
1473
+ )
1474
+ return torch.cat(pooled, dim=-1)
1475
+
1476
+ def embed_dataset(
1477
+ self,
1478
+ sequences: Optional[List[str]] = None,
1479
+ tokenizer: Optional[PreTrainedTokenizerFast] = None,
1480
+ batch_size: int = 2,
1481
+ max_len: int = 512,
1482
+ truncate: bool = True,
1483
+ full_embeddings: bool = False,
1484
+ embed_dtype: torch.dtype = torch.float32,
1485
+ pooling_types: List[str] = ["mean"],
1486
+ num_workers: int = 0,
1487
+ sql: bool = False,
1488
+ save: bool = True,
1489
+ sql_db_path: str = "embeddings.db",
1490
+ save_path: str = "embeddings.pth",
1491
+ fasta_path: Optional[str] = None,
1492
+ padding: str = "longest",
1493
+ **kwargs,
1494
+ ) -> Dict[str, torch.Tensor]:
1495
+ del num_workers, sql_db_path
1496
+ assert not sql, "ESM3 embed_dataset currently supports .pth saves, not SQLite."
1497
+ if tokenizer is None:
1498
+ tokenizer = self.tokenizer
1499
+ if fasta_path is not None:
1500
+ fasta_sequences = parse_fasta(fasta_path)
1501
+ sequences = list(sequences or []) + fasta_sequences
1502
+ assert sequences is not None and len(sequences) > 0, (
1503
+ "Must provide at least one sequence via `sequences` or `fasta_path`."
1504
+ )
1505
+
1506
+ unique_sequences = []
1507
+ seen_sequences = set()
1508
+ for sequence in sequences:
1509
+ prepared_sequence = sequence[:max_len] if truncate else sequence
1510
+ if prepared_sequence not in seen_sequences:
1511
+ unique_sequences.append(prepared_sequence)
1512
+ seen_sequences.add(prepared_sequence)
1513
+ unique_sequences = sorted(unique_sequences, key=len, reverse=True)
1514
+
1515
+ embeddings_by_sequence: Dict[str, torch.Tensor] = {}
1516
+ was_training = self.training
1517
+ self.eval()
1518
+ for batch_start in range(0, len(unique_sequences), batch_size):
1519
+ batch_sequences = unique_sequences[batch_start : batch_start + batch_size]
1520
+ tokenized = tokenizer(
1521
+ batch_sequences,
1522
+ padding=padding,
1523
+ truncation=truncate,
1524
+ max_length=max_len + 2,
1525
+ return_tensors="pt",
1526
+ )
1527
+ tokenized = {
1528
+ name: tensor.to(self.device) for name, tensor in tokenized.items()
1529
+ }
1530
+ with torch.inference_mode():
1531
+ residue_embeddings = self._embed(**tokenized, **kwargs)
1532
+ attention_mask = tokenized["attention_mask"]
1533
+ if full_embeddings:
1534
+ batch_embeddings = residue_embeddings.to(embed_dtype).cpu()
1535
+ for sequence, embedding, mask in zip(
1536
+ batch_sequences,
1537
+ batch_embeddings,
1538
+ attention_mask.cpu(),
1539
+ ):
1540
+ embeddings_by_sequence[sequence] = embedding[mask.bool()]
1541
+ else:
1542
+ pooled_embeddings = self._pool_embeddings(
1543
+ residue_embeddings,
1544
+ attention_mask,
1545
+ pooling_types,
1546
+ )
1547
+ pooled_embeddings = pooled_embeddings.to(embed_dtype).cpu()
1548
+ for sequence, embedding in zip(batch_sequences, pooled_embeddings):
1549
+ embeddings_by_sequence[sequence] = embedding
1550
+
1551
+ if was_training:
1552
+ self.train()
1553
+ if save:
1554
+ torch.save(embeddings_by_sequence, save_path)
1555
+ return embeddings_by_sequence
1556
+
1557
+ def encode(self, input):
1558
+ return self._get_official_sdk_model().encode(input)
1559
+
1560
+ def decode(self, input):
1561
+ return self._get_official_sdk_model().decode(input)
1562
+
1563
+ def generate(self, input, config):
1564
+ return self._get_official_sdk_model().generate(input, config)
1565
+
1566
+ def batch_generate(self, inputs, configs):
1567
+ return self._get_official_sdk_model().batch_generate(inputs, configs)
1568
+
1569
+ def forward_and_sample(self, input, sampling_configuration):
1570
+ return self._get_official_sdk_model().forward_and_sample(
1571
+ input,
1572
+ sampling_configuration,
1573
+ )
1574
+
1575
+ def logits(self, input=None, config=None, **kwargs):
1576
+ if input is None:
1577
+ return self.forward(**kwargs)
1578
+ if isinstance(input, torch.Tensor):
1579
+ return self.forward(sequence_tokens=input, **kwargs)
1580
+ if config is None:
1581
+ return self._get_official_sdk_model().logits(input)
1582
+ return self._get_official_sdk_model().logits(input, config)
1583
+
1584
+ def forward(
1585
+ self,
1586
+ input_ids: Optional[torch.Tensor] = None,
1587
+ attention_mask: Optional[torch.Tensor] = None,
1588
+ sequence_tokens: Optional[torch.Tensor] = None,
1589
+ structure_tokens: Optional[torch.Tensor] = None,
1590
+ ss8_tokens: Optional[torch.Tensor] = None,
1591
+ sasa_tokens: Optional[torch.Tensor] = None,
1592
+ function_tokens: Optional[torch.Tensor] = None,
1593
+ residue_annotation_tokens: Optional[torch.Tensor] = None,
1594
+ average_plddt: Optional[torch.Tensor] = None,
1595
+ per_res_plddt: Optional[torch.Tensor] = None,
1596
+ structure_coords: Optional[torch.Tensor] = None,
1597
+ chain_id: Optional[torch.Tensor] = None,
1598
+ sequence_id: Optional[torch.Tensor] = None,
1599
+ labels: Optional[torch.Tensor] = None,
1600
+ output_attentions: Optional[bool] = None,
1601
+ output_hidden_states: Optional[bool] = None,
1602
+ return_dict: Optional[bool] = None,
1603
+ **kwargs,
1604
+ ) -> FastESM3Output:
1605
+ del output_hidden_states, return_dict, kwargs
1606
+ if sequence_tokens is None:
1607
+ sequence_tokens = input_ids
1608
+ if sequence_id is None and attention_mask is not None:
1609
+ sequence_id = attention_mask.to(dtype=torch.bool)
1610
+
1611
+ output = self.esm3(
1612
+ sequence_tokens=sequence_tokens,
1613
+ structure_tokens=structure_tokens,
1614
+ ss8_tokens=ss8_tokens,
1615
+ sasa_tokens=sasa_tokens,
1616
+ function_tokens=function_tokens,
1617
+ residue_annotation_tokens=residue_annotation_tokens,
1618
+ average_plddt=average_plddt,
1619
+ per_res_plddt=per_res_plddt,
1620
+ structure_coords=structure_coords,
1621
+ chain_id=chain_id,
1622
+ sequence_id=sequence_id,
1623
+ output_attentions=output_attentions,
1624
+ )
1625
+
1626
+ loss = None
1627
+ if labels is not None:
1628
+ loss = F.cross_entropy(
1629
+ output.sequence_logits.view(-1, output.sequence_logits.shape[-1]),
1630
+ labels.view(-1),
1631
+ ignore_index=-100,
1632
+ )
1633
+
1634
+ return FastESM3Output(
1635
+ loss=loss,
1636
+ logits=output.sequence_logits,
1637
+ last_hidden_state=output.embeddings,
1638
+ sequence_logits=output.sequence_logits,
1639
+ structure_logits=output.structure_logits,
1640
+ secondary_structure_logits=output.secondary_structure_logits,
1641
+ sasa_logits=output.sasa_logits,
1642
+ function_logits=output.function_logits,
1643
+ residue_logits=output.residue_logits,
1644
+ embeddings=output.embeddings,
1645
+ hidden_states=output.hidden_states,
1646
+ attentions=output.attentions,
1647
+ )