File size: 5,348 Bytes
b2cb4a0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | #
# For licensing see accompanying LICENSE file.
# Copyright (c) 2025 Apple Inc. Licensed under MIT License.
#
# Started from https://github.com/facebookresearch/esm/tree/main,
# licensed under MIT License, Copyright (c) Meta Platforms, Inc. and affiliates.
from typing import Union
import mlx.core as mx
import mlx.nn as nn
from .simplefold.mlx.esm_modules import (
ContactPredictionHead,
ESM1bLayerNorm,
RobertaLMHead,
TransformerLayer,
)
from onescience.datapipes.esm import Alphabet
def masked_fill_mlx(x, mask, value):
return mx.where(mask, value, x)
class ESM2(nn.Module):
def __init__(
self,
num_layers: int = 33,
embed_dim: int = 1280,
attention_heads: int = 20,
alphabet: Union[Alphabet, str] = "ESM-1b",
token_dropout: bool = True,
):
super().__init__()
self.num_layers = num_layers
self.embed_dim = embed_dim
self.attention_heads = attention_heads
if not isinstance(alphabet, Alphabet):
alphabet = Alphabet.from_architecture(alphabet)
self.alphabet = alphabet
self.alphabet_size = len(alphabet)
self.padding_idx = alphabet.padding_idx
self.mask_idx = alphabet.mask_idx
self.cls_idx = alphabet.cls_idx
self.eos_idx = alphabet.eos_idx
self.prepend_bos = alphabet.prepend_bos
self.append_eos = alphabet.append_eos
self.token_dropout = token_dropout
self._init_submodules()
def _init_submodules(self):
self.embed_scale = 1
self.embed_tokens = mx.zeros((self.alphabet_size, self.embed_dim))
self.layers = [
TransformerLayer(
self.embed_dim,
4 * self.embed_dim,
self.attention_heads,
add_bias_kv=False,
use_esm1b_layer_norm=True,
use_rotary_embeddings=True,
)
for _ in range(self.num_layers)
]
self.contact_head = ContactPredictionHead(
self.num_layers * self.attention_heads,
self.prepend_bos,
self.append_eos,
eos_idx=self.eos_idx,
)
self.emb_layer_norm_after = ESM1bLayerNorm(self.embed_dim)
self.lm_head = RobertaLMHead(
embed_dim=self.embed_dim,
output_dim=self.alphabet_size,
weight=self.embed_tokens,
)
def __call__(
self, tokens, repr_layers=[], need_head_weights=False, return_contacts=False
):
if return_contacts:
need_head_weights = True
assert tokens.ndim == 2
padding_mask = mx.equal(tokens, self.padding_idx) # B, T
x = self.embed_scale * self.embed_tokens[tokens, :]
if self.token_dropout:
x = masked_fill_mlx(x, (tokens == self.mask_idx)[..., None], 0.0)
# x: B x T x C
mask_ratio_train = 0.15 * 0.8
src_lengths = (~padding_mask).sum(axis=-1)
mask_ratio_observed = (tokens == self.mask_idx).sum(axis=-1).astype(
x.dtype
) / src_lengths
x = x * (1 - mask_ratio_train) / (1 - mask_ratio_observed)[:, None, None]
if padding_mask is not None:
x = x * (1 - padding_mask[..., None].astype(x.dtype))
repr_layers = set(repr_layers)
hidden_representations = {}
if 0 in repr_layers:
hidden_representations[0] = x
if need_head_weights:
attn_weights = []
# (B, T, E) => (T, B, E)
x = mx.swapaxes(x, axis1=0, axis2=1)
if not padding_mask.any():
padding_mask = None
for layer_idx, layer in enumerate(self.layers):
x, attn = layer(
x,
self_attn_padding_mask=padding_mask,
need_head_weights=need_head_weights,
)
if (layer_idx + 1) in repr_layers:
hidden_representations[layer_idx + 1] = mx.swapaxes(x, axis1=0, axis2=1)
if need_head_weights:
# (H, B, T, T) => (B, H, T, T)
attn_weights.append(mx.swapaxes(attn, axis1=1, axis2=0))
x = self.emb_layer_norm_after(x)
x = mx.swapaxes(x, axis1=0, axis2=1) # (T, B, E) => (B, T, E)
# last hidden representation should have layer norm applied
if (layer_idx + 1) in repr_layers:
hidden_representations[layer_idx + 1] = x
x = self.lm_head(x)
result = {"logits": x, "representations": hidden_representations}
if need_head_weights:
# attentions: B x L x H x T x T
attentions = mx.stack(attn_weights, axis=1)
if padding_mask is not None:
attention_mask = 1 - padding_mask.astype(attentions.dtype)
attention_mask = (
attention_mask[:, None, ...] * attention_mask[:, :, None, ...]
)
attentions = attentions * attention_mask[:, None, None, :, :]
result["attentions"] = attentions
if return_contacts:
contacts = self.contact_head(tokens, attentions)
result["contacts"] = contacts
return result
def predict_contacts(self, tokens):
return self(tokens, return_contacts=True)["contacts"]
|