esm 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. esm/__init__.py +0 -0
  2. esm/layers/attention.py +70 -0
  3. esm/layers/blocks.py +153 -0
  4. esm/layers/codebook.py +88 -0
  5. esm/layers/ffn.py +29 -0
  6. esm/layers/geom_attention.py +151 -0
  7. esm/layers/regression_head.py +24 -0
  8. esm/layers/rotary.py +221 -0
  9. esm/layers/structure_proj.py +68 -0
  10. esm/layers/transformer_stack.py +94 -0
  11. esm/models/esm3.py +799 -0
  12. esm/models/function_decoder.py +339 -0
  13. esm/models/vqvae.py +450 -0
  14. esm/pretrained.py +95 -0
  15. esm/sdk/api.py +327 -0
  16. esm/tokenization/__init__.py +63 -0
  17. esm/tokenization/function_tokenizer.py +404 -0
  18. esm/tokenization/residue_tokenizer.py +224 -0
  19. esm/tokenization/sasa_tokenizer.py +129 -0
  20. esm/tokenization/sequence_tokenizer.py +68 -0
  21. esm/tokenization/ss_tokenizer.py +109 -0
  22. esm/tokenization/structure_tokenizer.py +63 -0
  23. esm/tokenization/tokenizer_base.py +42 -0
  24. esm/utils/constants/esm3.py +127 -0
  25. esm/utils/constants/models.py +5 -0
  26. esm/utils/constants/physics.py +5 -0
  27. esm/utils/decoding.py +225 -0
  28. esm/utils/encoding.py +241 -0
  29. esm/utils/function/encode_decode.py +187 -0
  30. esm/utils/function/interpro.py +245 -0
  31. esm/utils/function/lsh.py +103 -0
  32. esm/utils/function/tfidf.py +56 -0
  33. esm/utils/generation.py +185 -0
  34. esm/utils/misc.py +256 -0
  35. esm/utils/noise_schedules.py +34 -0
  36. esm/utils/residue_constants.py +81 -0
  37. esm/utils/sampling.py +155 -0
  38. esm/utils/structure/affine3d.py +389 -0
  39. esm/utils/structure/aligner.py +88 -0
  40. esm/utils/structure/lddt.py +98 -0
  41. esm/utils/structure/normalize_coordinates.py +82 -0
  42. esm/utils/structure/predicted_aligned_error.py +108 -0
  43. esm/utils/structure/protein_chain.py +828 -0
  44. esm/utils/structure/protein_structure.py +263 -0
  45. esm/utils/types.py +27 -0
  46. esm-3.0.0.dist-info/LICENSE.md +80 -0
  47. esm-3.0.0.dist-info/METADATA +132 -0
  48. esm-3.0.0.dist-info/RECORD +50 -0
  49. esm-3.0.0.dist-info/WHEEL +5 -0
  50. esm-3.0.0.dist-info/top_level.txt +1 -0
esm/__init__.py ADDED
File without changes
@@ -0,0 +1,70 @@
1
+ import functools
2
+
3
+ import einops
4
+ import torch
5
+ import torch.nn.functional as F
6
+ from torch import nn
7
+
8
+ from esm.layers.rotary import RotaryEmbedding
9
+
10
+
11
+ class MultiHeadAttention(nn.Module):
12
+ def __init__(
13
+ self,
14
+ d_model: int,
15
+ n_heads: int,
16
+ bias: bool = False,
17
+ qk_layernorm: bool = True,
18
+ ):
19
+ super().__init__()
20
+
21
+ self.d_model = d_model
22
+ self.n_heads = n_heads
23
+
24
+ self.d_head = self.d_model // self.n_heads
25
+ self.layernorm_qkv = nn.Sequential(
26
+ nn.LayerNorm(d_model), nn.Linear(d_model, d_model * 3, bias=bias)
27
+ )
28
+ self.out_proj = nn.Linear(d_model, d_model, bias=bias)
29
+
30
+ if qk_layernorm:
31
+ self.q_ln = nn.LayerNorm(d_model, bias=bias)
32
+ self.k_ln = nn.LayerNorm(d_model, bias=bias)
33
+ else:
34
+ self.q_ln = nn.Identity()
35
+ self.k_ln = nn.Identity()
36
+
37
+ self.rotary = RotaryEmbedding(d_model // n_heads)
38
+
39
+ def _apply_rotary(self, q: torch.Tensor, k: torch.Tensor):
40
+ q = q.unflatten(-1, (self.n_heads, self.d_head))
41
+ k = k.unflatten(-1, (self.n_heads, self.d_head))
42
+ q, k = self.rotary(q, k)
43
+ q = q.flatten(-2, -1)
44
+ k = k.flatten(-2, -1)
45
+ return q, k
46
+
47
+ def forward(self, x, seq_id):
48
+ qkv_BLD3 = self.layernorm_qkv(x)
49
+ query_BLD, key_BLD, value_BLD = torch.chunk(qkv_BLD3, 3, dim=-1)
50
+ query_BLD, key_BLD = self.q_ln(query_BLD), self.k_ln(key_BLD)
51
+ query_BLD, key_BLD = self._apply_rotary(query_BLD, key_BLD)
52
+
53
+ n_heads = self.n_heads
54
+ reshaper = functools.partial(
55
+ einops.rearrange, pattern="b s (h d) -> b h s d", h=n_heads
56
+ )
57
+
58
+ query_BHLD, key_BHLD, value_BHLD = map(
59
+ reshaper, (query_BLD, key_BLD, value_BLD)
60
+ )
61
+
62
+ # Where True, enable participation in attention.
63
+ mask_BLL = seq_id.unsqueeze(-1) == seq_id.unsqueeze(-2)
64
+ mask_BHLL = mask_BLL.unsqueeze(1)
65
+
66
+ context_BHLD = F.scaled_dot_product_attention(
67
+ query_BHLD, key_BHLD, value_BHLD, mask_BHLL
68
+ )
69
+ context_BLD = einops.rearrange(context_BHLD, "b h s d -> b s (h d)")
70
+ return self.out_proj(context_BLD)
esm/layers/blocks.py ADDED
@@ -0,0 +1,153 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ from esm.layers.attention import MultiHeadAttention
6
+ from esm.layers.geom_attention import (
7
+ GeometricReasoningOriginalImpl,
8
+ )
9
+ from esm.utils.structure.affine3d import Affine3D
10
+
11
+
12
+ def swiglu_correction_fn(expansion_ratio: float, d_model: int) -> int:
13
+ # set hidden dimesion to nearest multiple of 256 after expansion ratio
14
+ return int(((expansion_ratio * d_model) + 255) // 256 * 256)
15
+
16
+
17
+ class SwiGLU(nn.Module):
18
+ """
19
+ SwiGLU activation function as an nn.Module, allowing it to be used within nn.Sequential.
20
+ This module splits the input tensor along the last dimension and applies the SiLU (Swish)
21
+ activation function to the first half, then multiplies it by the second half.
22
+ """
23
+
24
+ def __init__(self):
25
+ super(SwiGLU, self).__init__()
26
+
27
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
28
+ x1, x2 = x.chunk(2, dim=-1)
29
+ return F.silu(x1) * x2
30
+
31
+
32
+ def swiglu_ln_ffn(d_model: int, expansion_ratio: float, bias: bool):
33
+ return nn.Sequential(
34
+ nn.LayerNorm(d_model),
35
+ nn.Linear(
36
+ d_model, swiglu_correction_fn(expansion_ratio, d_model) * 2, bias=bias
37
+ ),
38
+ SwiGLU(),
39
+ nn.Linear(swiglu_correction_fn(expansion_ratio, d_model), d_model, bias=bias),
40
+ )
41
+
42
+
43
+ def gelu_ln_ffn(d_model: int, expansion_ratio: float, bias: bool):
44
+ hidden_dim = int(expansion_ratio * d_model)
45
+ return nn.Sequential(
46
+ nn.LayerNorm(d_model),
47
+ nn.Linear(d_model, hidden_dim, bias=bias),
48
+ nn.GELU(),
49
+ nn.Linear(hidden_dim, d_model, bias=bias),
50
+ )
51
+
52
+
53
+ class UnifiedTransformerBlock(nn.Module):
54
+ """
55
+ A unified transformer block that can optionally incorporate geometric attention.
56
+
57
+ This class defines a transformer block that can be configured to use geometric attention
58
+ alongside the standard multi-head attention mechanism. It is designed to be a flexible
59
+ component of transformer-based models, allowing for the integration of geometric reasoning.
60
+
61
+ Parameters
62
+ ----------
63
+ d_model : int
64
+ The dimensionality of the input and output features of the transformer block.
65
+ n_heads : int
66
+ The number of attention heads in the multi-head attention mechanism.
67
+ n_layers : int
68
+ The number of layers in the transformer block.
69
+ use_geom_attn : bool, optional
70
+ Whether to use geometric attention in addition to the standard multi-head attention. Defaults to False.
71
+ v_heads : int, optional
72
+ The number of heads to use for the geometric attention mechanism, if enabled. Must be specified if `use_geom_attn` is True.
73
+ """
74
+
75
+ def __init__(
76
+ self,
77
+ d_model: int,
78
+ n_heads: int,
79
+ use_geom_attn: bool = False,
80
+ use_plain_attn: bool = True,
81
+ v_heads: int | None = None,
82
+ bias: bool = False,
83
+ expansion_ratio: float = 4.0,
84
+ residue_scaling_factor: float = 1,
85
+ mask_and_zero_frameless: bool = False,
86
+ qk_layernorm: bool = True,
87
+ ffn_type: str = "swiglu", # swiglu | gelu
88
+ ):
89
+ super().__init__()
90
+ self.use_plain_attn = use_plain_attn
91
+ if self.use_plain_attn:
92
+ self.attn = MultiHeadAttention(
93
+ d_model, n_heads, bias, qk_layernorm=qk_layernorm
94
+ )
95
+ self.use_geom_attn = use_geom_attn
96
+ if self.use_geom_attn:
97
+ if v_heads is None:
98
+ raise ValueError("v_heads must be specified when use_geom_attn is True")
99
+ self.geom_attn = GeometricReasoningOriginalImpl(
100
+ c_s=d_model,
101
+ v_heads=v_heads,
102
+ bias=bias,
103
+ mask_and_zero_frameless=mask_and_zero_frameless,
104
+ )
105
+ if ffn_type == "swiglu":
106
+ self.ffn = swiglu_ln_ffn(d_model, expansion_ratio, bias)
107
+ elif ffn_type == "gelu":
108
+ self.ffn = gelu_ln_ffn(d_model, expansion_ratio, bias)
109
+ else:
110
+ raise ValueError(f"Unknown ffn_type: {ffn_type}")
111
+ self.scaling_factor = residue_scaling_factor
112
+
113
+ def forward(
114
+ self,
115
+ x: torch.Tensor,
116
+ sequence_id: torch.Tensor,
117
+ frames: Affine3D,
118
+ frames_mask: torch.Tensor,
119
+ chain_id: torch.Tensor,
120
+ ) -> torch.Tensor:
121
+ """
122
+ Forward pass for the UnifiedTransformerBlock.
123
+
124
+ Parameters
125
+ ----------
126
+ x : torch.Tensor[float]
127
+ Input tensor to the transformer block, typically the output from the previous layer.
128
+ sequence_id : torch.Tensor[int]
129
+ Tensor containing sequence IDs for each element in the batch, used for attention masking.
130
+ frames : Affine3D
131
+ Affine3D containing geometric frame information for geometric attention.
132
+ frames_mask : torch.Tensor[bool]
133
+ Boolean mask tensor indicating valid frames for geometric attention.
134
+ chain_id : torch.Tensor[int]
135
+ Tensor containing chain IDs for each element, used for attention masking in geometric attention.
136
+
137
+ Returns
138
+ -------
139
+ torch.Tensor[float]
140
+ The output tensor after applying the transformer block operations.
141
+ """
142
+ if self.use_plain_attn:
143
+ r1 = self.attn(x, sequence_id)
144
+ x = x + r1 / self.scaling_factor
145
+
146
+ if self.use_geom_attn:
147
+ r2 = self.geom_attn(x, frames, frames_mask, sequence_id, chain_id)
148
+ x = x + r2 / self.scaling_factor
149
+
150
+ r3 = self.ffn(x) / self.scaling_factor
151
+ x = x + r3
152
+
153
+ return x
esm/layers/codebook.py ADDED
@@ -0,0 +1,88 @@
1
+ import numpy as np
2
+ import torch
3
+ import torch.distributed as dist
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+
8
+ class EMACodebook(nn.Module):
9
+ def __init__(
10
+ self,
11
+ n_codes,
12
+ embedding_dim,
13
+ no_random_restart=True,
14
+ restart_thres=1.0,
15
+ ema_decay=0.99,
16
+ ):
17
+ super().__init__()
18
+ self.register_buffer("embeddings", torch.randn(n_codes, embedding_dim))
19
+ self.register_buffer("N", torch.zeros(n_codes))
20
+ self.register_buffer("z_avg", self.embeddings.data.clone())
21
+
22
+ self.n_codes = n_codes
23
+ self.embedding_dim = embedding_dim
24
+ self._need_init = True
25
+ self.no_random_restart = no_random_restart
26
+ self.restart_thres = restart_thres
27
+ self.freeze_codebook = False
28
+ self.ema_decay = ema_decay
29
+
30
+ def reset_parameters(self):
31
+ # For meta init
32
+ pass
33
+
34
+ def _tile(self, x):
35
+ d, ew = x.shape
36
+ if d < self.n_codes:
37
+ n_repeats = (self.n_codes + d - 1) // d
38
+ std = 0.01 / np.sqrt(ew)
39
+ x = x.repeat(n_repeats, 1)
40
+ x = x + torch.randn_like(x) * std
41
+ return x
42
+
43
+ def _init_embeddings(self, z):
44
+ # z: [b, t, c]
45
+ self._need_init = False
46
+ flat_inputs = z.view(-1, self.embedding_dim)
47
+ y = self._tile(flat_inputs)
48
+
49
+ y.shape[0]
50
+ _k_rand = y[torch.randperm(y.shape[0])][: self.n_codes]
51
+ if dist.is_initialized():
52
+ dist.broadcast(_k_rand, 0)
53
+ self.embeddings.data.copy_(_k_rand)
54
+ self.z_avg.data.copy_(_k_rand)
55
+ self.N.data.copy_(torch.ones(self.n_codes))
56
+
57
+ def forward(self, z):
58
+ # z: [b, t, c]
59
+ if self._need_init and self.training and not self.freeze_codebook:
60
+ self._init_embeddings(z)
61
+ # z is of shape [batch_size, sequence length, channels]
62
+ flat_inputs = z.view(-1, self.embedding_dim)
63
+ distances = (
64
+ (flat_inputs**2).sum(dim=1, keepdim=True)
65
+ - 2 * flat_inputs @ self.embeddings.t()
66
+ + (self.embeddings.t() ** 2).sum(dim=0, keepdim=True)
67
+ ) # [bt, c]
68
+
69
+ encoding_indices = torch.argmin(distances, dim=1)
70
+ encoding_indices = encoding_indices.view(*z.shape[:2]) # [b, t, ncode]
71
+
72
+ embeddings = F.embedding(encoding_indices, self.embeddings) # [b, t, c]
73
+
74
+ commitment_loss = 0.25 * F.mse_loss(z, embeddings.detach())
75
+
76
+ # EMA codebook update
77
+ if self.training and not self.freeze_codebook:
78
+ assert False, "Not implemented"
79
+ embeddings_st = (embeddings - z).detach() + z
80
+
81
+ return embeddings_st, encoding_indices, commitment_loss
82
+
83
+ def dictionary_lookup(self, encodings):
84
+ embeddings = F.embedding(encodings, self.embeddings)
85
+ return embeddings
86
+
87
+ def soft_codebook_lookup(self, weights: torch.Tensor) -> torch.Tensor:
88
+ return weights @ self.embeddings
esm/layers/ffn.py ADDED
@@ -0,0 +1,29 @@
1
+ import torch.nn as nn
2
+ import torch.nn.functional as F
3
+ from torch import Tensor
4
+
5
+ # NOT CURRENTLY USED
6
+
7
+
8
+ class SwiGLU(nn.Module):
9
+ def __init__(self) -> None:
10
+ super().__init__()
11
+
12
+ def forward(self, x: Tensor) -> Tensor:
13
+ x1, x2 = x.chunk(2, dim=-1)
14
+ hidden = F.silu(x1) * x2
15
+ return hidden
16
+
17
+
18
+ class FFN(nn.Module):
19
+ def __init__(self, in_proj, activation, out_proj) -> None:
20
+ super().__init__()
21
+ self.in_proj = in_proj
22
+ self.activation = activation
23
+ self.out_proj = out_proj
24
+
25
+ def forward(self, x: Tensor) -> Tensor:
26
+ x = self.in_proj(x)
27
+ x = self.activation(x)
28
+ x = self.out_proj(x)
29
+ return x
@@ -0,0 +1,151 @@
1
+ from math import sqrt
2
+
3
+ import torch
4
+ from einops import rearrange
5
+ from torch import nn
6
+ from torch.nn import functional as F
7
+
8
+
9
+ class GeometricReasoningOriginalImpl(nn.Module):
10
+ def __init__(
11
+ self,
12
+ c_s: int,
13
+ v_heads: int,
14
+ num_vector_messages: int = 1,
15
+ mask_and_zero_frameless: bool = True,
16
+ divide_residual_by_depth: bool = False,
17
+ bias: bool = False,
18
+ ):
19
+ """Approximate implementation:
20
+
21
+ ATTN(A, v) := (softmax_j A_ij) v_j
22
+ make_rot_vectors(x) := R(i->g) Linear(x).reshape(..., 3)
23
+ make_vectors(x) := T(i->g) Linear(x).reshape(..., 3)
24
+
25
+ v <- make_rot_vectors(x)
26
+ q_dir, k_dir <- make_rot_vectors(x)
27
+ q_dist, k_dist <- make_vectors(x)
28
+
29
+ A_ij <- dot(q_dir_i, k_dir_j) -||q_dist_i - k_dist_j||^2
30
+ x <- x + Linear(T(g->i) ATTN(A, v))
31
+ """
32
+ super().__init__()
33
+ self.c_s = c_s
34
+ self.v_heads = v_heads
35
+ self.num_vector_messages = num_vector_messages
36
+ self.mask_and_zero_frameless = mask_and_zero_frameless
37
+
38
+ self.s_norm = nn.LayerNorm(c_s, bias=bias)
39
+ dim_proj = (
40
+ 4 * self.v_heads * 3 + self.v_heads * 3 * self.num_vector_messages
41
+ ) # 2 x (q, k) * number of heads * (x, y, z) + number of heads * number of vector messages * (x, y, z)
42
+ self.proj = nn.Linear(c_s, dim_proj, bias=bias)
43
+ channels_out = self.v_heads * 3 * self.num_vector_messages
44
+ self.out_proj = nn.Linear(channels_out, c_s, bias=bias)
45
+
46
+ # The basic idea is for some attention heads to pay more or less attention to rotation versus distance,
47
+ # as well as to control the sharpness of the softmax (i.e., should this head only attend to those residues
48
+ # very nearby or should there be shallower dropoff in attention weight?)
49
+ self.distance_scale_per_head = nn.Parameter(torch.zeros((self.v_heads)))
50
+ self.rotation_scale_per_head = nn.Parameter(torch.zeros((self.v_heads)))
51
+
52
+ def forward(self, s, affine, affine_mask, sequence_id, chain_id):
53
+ attn_bias = sequence_id.unsqueeze(-1) == sequence_id.unsqueeze(-2)
54
+ attn_bias = attn_bias.unsqueeze(1).float()
55
+ attn_bias = attn_bias.masked_fill(
56
+ ~affine_mask[:, None, None, :], torch.finfo(attn_bias.dtype).min
57
+ )
58
+ chain_id_mask = chain_id.unsqueeze(1) != chain_id.unsqueeze(2)
59
+ attn_bias = attn_bias.masked_fill(
60
+ chain_id_mask.unsqueeze(1), torch.finfo(s.dtype).min
61
+ )
62
+
63
+ ns = self.s_norm(s)
64
+ vec_rot, vec_dist = self.proj(ns).split(
65
+ [
66
+ self.v_heads * 2 * 3 + self.v_heads * 3 * self.num_vector_messages,
67
+ self.v_heads * 2 * 3,
68
+ ],
69
+ dim=-1,
70
+ )
71
+
72
+ # Rotate the queries and keys for the rotation term. We also rotate the values.
73
+ # NOTE(zeming, thayes): Values are only rotated, not translated. We may wish to change
74
+ # this in the future.
75
+ query_rot, key_rot, value = (
76
+ affine.rot[..., None]
77
+ .apply(rearrange(vec_rot, "... (h c) -> ... h c", c=3))
78
+ .split(
79
+ [
80
+ self.v_heads,
81
+ self.v_heads,
82
+ self.v_heads * self.num_vector_messages,
83
+ ],
84
+ dim=-2,
85
+ )
86
+ )
87
+
88
+ # Rotate and translate the queries and keys for the distance term
89
+ # NOTE(thayes): a simple speedup would be to apply all rotations together, then
90
+ # separately apply the translations.
91
+ query_dist, key_dist = (
92
+ affine[..., None]
93
+ .apply(rearrange(vec_dist, "... (h c) -> ... h c", c=3))
94
+ .chunk(2, dim=-2)
95
+ )
96
+
97
+ query_dist = rearrange(query_dist, "b s h d -> b h s 1 d")
98
+ key_dist = rearrange(key_dist, "b s h d -> b h 1 s d")
99
+ query_rot = rearrange(query_rot, "b s h d -> b h s d")
100
+ key_rot = rearrange(key_rot, "b s h d -> b h d s")
101
+ value = rearrange(
102
+ value, "b s (h m) d -> b h s (m d)", m=self.num_vector_messages
103
+ )
104
+
105
+ distance_term = (query_dist - key_dist).norm(dim=-1) / sqrt(3)
106
+ rotation_term = query_rot.matmul(key_rot) / sqrt(3)
107
+ distance_term_weight = rearrange(
108
+ F.softplus(self.distance_scale_per_head), "h -> h 1 1"
109
+ )
110
+ rotation_term_weight = rearrange(
111
+ F.softplus(self.rotation_scale_per_head), "h -> h 1 1"
112
+ )
113
+
114
+ attn_weight = (
115
+ rotation_term * rotation_term_weight - distance_term * distance_term_weight
116
+ )
117
+
118
+ if attn_bias is not None:
119
+ # we can re-use the attention bias from the transformer layers
120
+ # NOTE(thayes): This attention bias is expected to handle two things:
121
+ # 1. Masking attention on padding tokens
122
+ # 2. Masking cross sequence attention in the case of bin packing
123
+ s_q = attn_weight.size(2)
124
+ s_k = attn_weight.size(3)
125
+ _s_q = max(0, attn_bias.size(2) - s_q)
126
+ _s_k = max(0, attn_bias.size(3) - s_k)
127
+ attn_bias = attn_bias[:, :, _s_q:, _s_k:]
128
+ attn_weight = attn_weight + attn_bias
129
+
130
+ attn_weight = torch.softmax(attn_weight, dim=-1)
131
+
132
+ attn_out = attn_weight.matmul(value)
133
+
134
+ attn_out = (
135
+ affine.rot[..., None]
136
+ .invert()
137
+ .apply(
138
+ rearrange(
139
+ attn_out, "b h s (m d) -> b s (h m) d", m=self.num_vector_messages
140
+ )
141
+ )
142
+ )
143
+
144
+ attn_out = rearrange(
145
+ attn_out, "b s (h m) d -> b s (h m d)", m=self.num_vector_messages
146
+ )
147
+ if self.mask_and_zero_frameless:
148
+ attn_out = attn_out.masked_fill(~affine_mask[..., None], 0.0)
149
+ s = self.out_proj(attn_out)
150
+
151
+ return s
@@ -0,0 +1,24 @@
1
+ import torch.nn as nn
2
+
3
+
4
+ def RegressionHead(
5
+ d_model: int,
6
+ output_dim: int,
7
+ hidden_dim: int | None = None,
8
+ ) -> nn.Module:
9
+ """Single-hidden layer MLP for supervised output.
10
+
11
+ Args:
12
+ d_model: input dimension
13
+ output_dim: dimensionality of the output.
14
+ hidden_dim: optional dimension of hidden layer, defaults to d_model.
15
+ Returns:
16
+ output MLP module.
17
+ """
18
+ hidden_dim = hidden_dim if hidden_dim is not None else d_model
19
+ return nn.Sequential(
20
+ nn.Linear(d_model, hidden_dim),
21
+ nn.GELU(),
22
+ nn.LayerNorm(hidden_dim),
23
+ nn.Linear(hidden_dim, output_dim),
24
+ )