Equimo 0.1.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.
equimo/layers/ffn.py ADDED
@@ -0,0 +1,184 @@
1
+ from typing import Callable
2
+
3
+ import equinox as eqx
4
+ import jax
5
+ import jax.numpy as jnp
6
+ import jax.random as jr
7
+ from jaxtyping import Array, Float, PRNGKeyArray
8
+
9
+ from equimo.layers.dropout import Dropout
10
+
11
+
12
+ class Mlp(eqx.Module):
13
+ """Multi-layer Perceptron (MLP) module with dropout.
14
+
15
+ A standard MLP implementation with two fully connected layers, activation function,
16
+ and dropout for regularization. The architecture follows:
17
+ input -> fc1 -> activation -> dropout1 -> fc2 -> dropout2 -> output
18
+
19
+ Attributes:
20
+ fc1: First linear layer
21
+ fc2: Second linear layer
22
+ drop1: Dropout after first layer
23
+ drop2: Dropout after second layer
24
+ act_layer: Activation function
25
+ """
26
+
27
+ fc1: eqx.nn.Linear
28
+ fc2: eqx.nn.Linear
29
+ drop1: Dropout
30
+ drop2: Dropout
31
+ act_layer: Callable
32
+
33
+ def __init__(
34
+ self,
35
+ in_features: int,
36
+ *,
37
+ key: PRNGKeyArray,
38
+ out_features: int | None = None,
39
+ hidden_features: int | None = None,
40
+ act_layer: Callable = jax.nn.gelu,
41
+ dropout_rate: float = 0.0,
42
+ bias: bool = True,
43
+ **kwargs,
44
+ ):
45
+ """Initialize the MLP.
46
+
47
+ Args:
48
+ in_features: Number of input features
49
+ key: PRNG key for initialization
50
+ out_features: Number of output features (default: same as in_features)
51
+ hidden_features: Number of hidden features (default: same as in_features)
52
+ act_layer: Activation function (default: gelu)
53
+ dropout_rate: Dropout probability (default: 0.0)
54
+ bias: Whether to include bias in linear layers (default: True)
55
+ **kwargs: Additional arguments
56
+ """
57
+ key_fc1, key_fc2 = jr.split(key, 2)
58
+
59
+ self.act_layer = act_layer
60
+
61
+ hidden_features = hidden_features or in_features
62
+ out_features = out_features or in_features
63
+
64
+ self.fc1 = eqx.nn.Linear(
65
+ in_features, hidden_features, use_bias=bias, key=key_fc1
66
+ )
67
+ self.fc2 = eqx.nn.Linear(
68
+ hidden_features, out_features, use_bias=bias, key=key_fc2
69
+ )
70
+
71
+ self.drop1 = Dropout(dropout_rate)
72
+ self.drop2 = Dropout(dropout_rate)
73
+
74
+ def __call__(
75
+ self,
76
+ x: Float[Array, "seqlen dim"],
77
+ enable_dropout: bool,
78
+ key: PRNGKeyArray,
79
+ ) -> Float[Array, "seqlen dim"]:
80
+ key_dr1, key_dr2 = jr.split(key, 2)
81
+
82
+ x = self.drop1(
83
+ self.act_layer(jax.vmap(self.fc1)(x)),
84
+ inference=not enable_dropout,
85
+ key=key_dr1,
86
+ )
87
+ x = self.drop2(
88
+ jax.vmap(self.fc2)(x),
89
+ inference=not enable_dropout,
90
+ key=key_dr2,
91
+ )
92
+
93
+ return x
94
+
95
+
96
+ class SwiGlu(eqx.Module):
97
+ """SwiGLU activation module with dropout.
98
+
99
+ Implements the SwiGLU (Swish-Gated Linear Unit) activation function with dropout,
100
+ as described in "GLU Variants Improve Transformer" paper [1]. The architecture uses
101
+ a gating mechanism where the input is transformed by two parallel paths and
102
+ combined multiplicatively.
103
+
104
+ The computation flow is:
105
+ 1. Joint projection to higher dimension (w12)
106
+ 2. Split into two paths
107
+ 3. Apply SiLU to first path and multiply with second path
108
+ 4. Project back to original dimension (w3)
109
+
110
+ Attributes:
111
+ w12: Joint projection layer for both paths
112
+ w3: Final projection layer
113
+ drop1: Dropout after gating
114
+ drop2: Dropout after final projection
115
+
116
+ References:
117
+ [1]: https://arxiv.org/pdf/2002.05202
118
+ """
119
+
120
+ w12: eqx.nn.Linear
121
+ w3: eqx.nn.Linear
122
+ drop1: Dropout
123
+ drop2: Dropout
124
+
125
+ def __init__(
126
+ self,
127
+ in_features: int,
128
+ *,
129
+ key: PRNGKeyArray,
130
+ out_features: int | None = None,
131
+ hidden_features: int | None = None,
132
+ dropout_rate: float = 0.0,
133
+ bias: bool = True,
134
+ **kwargs,
135
+ ):
136
+ """Initialize the SwiGLU module.
137
+
138
+ Args:
139
+ in_features: Number of input features
140
+ key: PRNG key for initialization
141
+ out_features: Number of output features (default: same as in_features)
142
+ hidden_features: Size of hidden dimension (default: same as in_features)
143
+ dropout_rate: Dropout probability (default: 0.0)
144
+ bias: Whether to include bias in linear layers (default: True)
145
+ **kwargs: Additional arguments
146
+ """
147
+ key_fc1, key_fc2 = jr.split(key, 2)
148
+
149
+ hidden_features = hidden_features or in_features
150
+ out_features = out_features or in_features
151
+
152
+ self.w12 = eqx.nn.Linear(
153
+ in_features, hidden_features, use_bias=bias, key=key_fc1
154
+ )
155
+ self.w3 = eqx.nn.Linear(
156
+ hidden_features // 2, out_features, use_bias=bias, key=key_fc2
157
+ )
158
+
159
+ self.drop1 = Dropout(dropout_rate)
160
+ self.drop2 = Dropout(dropout_rate)
161
+
162
+ def __call__(
163
+ self,
164
+ x: Float[Array, "seqlen dim"],
165
+ enable_dropout: bool,
166
+ key: PRNGKeyArray,
167
+ ) -> Float[Array, "seqlen dim"]:
168
+ key_dr1, key_dr2 = jr.split(key, 2)
169
+
170
+ x12 = jax.vmap(self.w12)(x)
171
+ x1, x2 = jnp.split(x12, 2, axis=-1)
172
+ x = self.drop1(
173
+ jax.nn.silu(x1) * x2,
174
+ inference=not enable_dropout,
175
+ key=key_dr1,
176
+ )
177
+
178
+ x = self.drop2(
179
+ jax.vmap(self.w3)(x),
180
+ inference=not enable_dropout,
181
+ key=key_dr2,
182
+ )
183
+
184
+ return x
@@ -0,0 +1,68 @@
1
+ import equinox as eqx
2
+ from jaxtyping import Array, Float, PRNGKeyArray
3
+
4
+ from equimo.layers.dropout import DropPathAdd
5
+
6
+
7
+ class Residual(eqx.Module):
8
+ """A wrapper module that adds a residual connection with optional drop path.
9
+
10
+ This module wraps any other module and adds a residual (skip) connection around it.
11
+ It also includes drop path regularization which stochastically drops the residual
12
+ path during training. The computation flow is:
13
+ input -> [main branch: module] + [residual branch: identity with drop path] -> output
14
+
15
+ Attributes:
16
+ module: The module to wrap with a residual connection
17
+ drop_path: DropPath module for residual connection regularization
18
+ """
19
+
20
+ module: eqx.Module
21
+ drop_path: DropPathAdd
22
+
23
+ def __init__(
24
+ self,
25
+ module: eqx.Module,
26
+ drop_path: float = 0,
27
+ ):
28
+ """Initialize the Residual wrapper.
29
+
30
+ Args:
31
+ module: The module to wrap with a residual connection
32
+ drop_path: Drop path rate (probability of dropping the residual connection)
33
+ (default: 0)
34
+ """
35
+ self.module = module
36
+ self.drop_path = DropPathAdd(drop_path)
37
+
38
+ def __call__(
39
+ self,
40
+ x: Float[Array, "..."],
41
+ enable_dropout: bool,
42
+ key: PRNGKeyArray,
43
+ pass_args: bool = False,
44
+ ) -> Float[Array, "..."]:
45
+ """Forward pass of the residual block.
46
+
47
+ Args:
48
+ x: Input tensor of any shape
49
+ enable_dropout: Whether to enable dropout during training
50
+ key: PRNG key for randomness
51
+ pass_args: Whether to pass enable_dropout and key to the wrapped module
52
+ (default: False)
53
+
54
+ Returns:
55
+ Output tensor with same shape as input, combining the module output
56
+ with the residual connection through drop path
57
+ """
58
+ if pass_args:
59
+ x2 = self.module(x, enable_dropout=enable_dropout, key=key)
60
+ else:
61
+ x2 = self.module(x)
62
+
63
+ return self.drop_path(
64
+ x,
65
+ x2,
66
+ inference=not enable_dropout,
67
+ key=key,
68
+ )
equimo/layers/mamba.py ADDED
@@ -0,0 +1,169 @@
1
+ import math
2
+ from typing import List, Tuple
3
+
4
+ import equinox as eqx
5
+ import jax
6
+ import jax.numpy as jnp
7
+ import jax.random as jr
8
+ from einops import rearrange
9
+ from jaxtyping import Array, Float, PRNGKeyArray
10
+
11
+ from equimo.layers.norm import RMSNormGated
12
+ from equimo.ops.scan import non_causal_linear_attn
13
+
14
+
15
+ class Mamba2Mixer(eqx.Module):
16
+ """Mamba2 Mixer.
17
+
18
+ This class implements the a Mamba2 Mixer using State Space Duality (SSD),
19
+ from Mamba2 [1]. Also supports implementation details from Visual State Space
20
+ Duality (VSSD) [2].
21
+
22
+ Attributes:
23
+ in_proj (eqx.nn.Linear): Input projection layer.
24
+ conv (eqx.nn.Conv): Convolutional layer for processing input.
25
+ dt_bias (eqx.nn.Param): Bias for delta time.
26
+ A_log (eqx.nn.Param): Logarithm of the state transition matrix A.
27
+ D (eqx.nn.Param): Direct feedthrough matrix D.
28
+ norm (eqx.nn.RMSNorm): Root Mean Square Layer Normalization.
29
+ out_proj (eqx.nn.Linear): Output projection layer.
30
+
31
+ Args:
32
+ config (MambaConfig): Configuration object for the Mamba2VisionMixer.
33
+ rngs (nnx.Rngs): Random number generators for parameter initialization.
34
+
35
+ Notes:
36
+ This implementation is heavily based on wlln/scratch.
37
+
38
+ References:
39
+ [1] Transformers are SSMs: Generalized Models and Efficient Algorithms Through Structured State Space Duality
40
+ (https://arxiv.org/abs/2401.04054)
41
+ [2] VSSD: Vision Mamba with Non-Causal State Space Duality
42
+ (https://arxiv.org/abs/2407.18559)
43
+ """
44
+
45
+ d_inner: int = eqx.field(static=True)
46
+ n_heads: int = eqx.field(static=True)
47
+ head_dim: int = eqx.field(static=True)
48
+ n_groups: int = eqx.field(static=True)
49
+ indices_xBC: List[int] = eqx.field(static=True)
50
+
51
+ in_proj: eqx.nn.Linear
52
+ conv: eqx.nn.Conv
53
+ dt_bias: Float[Array, "n_heads"]
54
+ A_log: Float[Array, "n_heads"]
55
+ D: Float[Array, "n_heads"]
56
+ norm: eqx.nn.LayerNorm
57
+ out_proj: eqx.nn.Linear
58
+
59
+ def __init__(
60
+ self,
61
+ d_model: int,
62
+ *,
63
+ key: PRNGKeyArray,
64
+ expand: int = 2,
65
+ n_groups: int = 1,
66
+ head_dim: int = 64,
67
+ d_state: int = 128,
68
+ d_conv: int = 4,
69
+ dt_min: float = 0.001,
70
+ dt_max: float = 0.1,
71
+ dt_init_floor: float = 1e-4,
72
+ A_init_range: Tuple[int, int] = (1, 16),
73
+ use_bias: bool = False,
74
+ conv_bias: bool = True,
75
+ **kwargs,
76
+ ):
77
+ key_inproj, key_outproj, key_conv, key_randvals, key_a = jr.split(key, 5)
78
+ self.d_inner = int(d_model * expand)
79
+ if self.d_inner % head_dim != 0:
80
+ raise ValueError("`d_inner` must be a multiple of `head_dim`.")
81
+ self.n_heads = self.d_inner // head_dim
82
+ self.head_dim = head_dim
83
+ self.n_groups = n_groups
84
+ self.indices_xBC = [self.d_inner, self.d_inner + n_groups * d_state]
85
+
86
+ d_in_proj = 2 * self.d_inner + 2 * n_groups * d_state + self.n_heads
87
+ self.in_proj = eqx.nn.Linear(
88
+ d_model,
89
+ d_in_proj,
90
+ use_bias=use_bias,
91
+ key=key_inproj,
92
+ )
93
+
94
+ conv_dim = self.d_inner + 2 * n_groups * d_state
95
+ self.conv = eqx.nn.Conv(
96
+ num_spatial_dims=1,
97
+ in_channels=conv_dim,
98
+ out_channels=conv_dim,
99
+ kernel_size=d_conv,
100
+ groups=conv_dim,
101
+ padding="SAME",
102
+ use_bias=conv_bias,
103
+ key=key_conv,
104
+ )
105
+
106
+ rand_vals = jr.uniform(key_randvals, (self.n_heads,))
107
+ dt = jnp.exp(
108
+ rand_vals * (math.log(dt_max) - math.log(dt_min)) + math.log(dt_min)
109
+ )
110
+ dt = jnp.clip(dt, a_min=dt_init_floor)
111
+ self.dt_bias = dt + jnp.log(-jnp.expm1(-dt))
112
+
113
+ A_min, A_max = A_init_range
114
+ A = jr.uniform(key_a, (self.n_heads,), minval=A_min, maxval=A_max)
115
+ self.A_log = jnp.log(A)
116
+
117
+ self.D = jnp.ones(self.n_heads)
118
+
119
+ self.norm = eqx.nn.LayerNorm(self.d_inner)
120
+ self.out_proj = eqx.nn.Linear(
121
+ self.d_inner, d_model, use_bias=use_bias, key=key_outproj
122
+ )
123
+
124
+ def __call__(
125
+ self,
126
+ x: Float[Array, "seqlen dim"],
127
+ enable_dropout: bool,
128
+ key: PRNGKeyArray,
129
+ ) -> Float[Array, "seqlen dim"]:
130
+ A = -jnp.exp(self.A_log)
131
+ zxbcdt = jax.vmap(self.in_proj)(x)
132
+
133
+ z, xbc, dt = jnp.split(
134
+ zxbcdt,
135
+ [self.d_inner, zxbcdt.shape[-1] - self.n_heads],
136
+ axis=-1,
137
+ )
138
+
139
+ dt = jax.nn.softplus(dt + self.dt_bias)
140
+
141
+ # Pad or truncate the xbc tensor to match the conv kernel size
142
+ # xbc_rearranged = rearrange(xbc, "b l d -> b d l")
143
+ # conv_state = pad_or_truncate_to_length(xbc_rearranged, self.config.d_conv)
144
+
145
+ # apply 1d convolution and silu activation
146
+ xbc_conv = rearrange(self.conv(rearrange(xbc, "s d -> d s")), "d s -> s d")
147
+ xbc_silu = jax.nn.silu(xbc_conv[: x.shape[0], :])
148
+
149
+ # split the conv state into the conv kernel and the conv state
150
+ x, B, C = jnp.split(xbc_silu, self.indices_xBC, axis=-1)
151
+
152
+ x = rearrange(x, "l (h p) -> l h p", p=self.head_dim)
153
+
154
+ y = non_causal_linear_attn(
155
+ x, dt=dt, A=A, B=B, C=C, D=self.D, n_groups=self.n_groups
156
+ )
157
+
158
+ y = rearrange(y, "l h p -> l (h p)")
159
+
160
+ # apply the output projection
161
+ if isinstance(self.norm, RMSNormGated):
162
+ y = self.norm(y, jax.nn.silu(z))
163
+ else:
164
+ # Should be LayerNorm
165
+ y = jax.vmap(self.norm)(y) * z
166
+
167
+ y = jax.vmap(self.out_proj)(y)
168
+
169
+ return y
equimo/layers/norm.py ADDED
@@ -0,0 +1,87 @@
1
+ from typing import Optional
2
+
3
+ import equinox as eqx
4
+ import jax.numpy as jnp
5
+ from jax import lax
6
+ from jaxtyping import Array, Float
7
+
8
+
9
+ class RMSNormGated(eqx.Module):
10
+ """Root Mean Square (RMS) Normalization with optional gating.
11
+
12
+ Implements RMS normalization with learnable scale parameters and optional
13
+ gating mechanism. RMS norm is similar to Layer Norm but only normalizes by
14
+ the root mean square, without centering the mean.
15
+
16
+ Attributes:
17
+ w: Learnable scale parameter vector of size dim
18
+ """
19
+
20
+ w: Float[Array, "dim"]
21
+
22
+ def __init__(self, d: int):
23
+ """Initialize RMSNormGated.
24
+
25
+ Args:
26
+ d: Dimension of the input features
27
+ """
28
+ self.w = jnp.ones(d)
29
+
30
+ def __call__(
31
+ self,
32
+ x: Float[Array, "dim"],
33
+ z: Optional[Float[Array, "dim"]] = None,
34
+ ) -> Float[Array, "dim"]:
35
+ """Apply RMS normalization with optional gating.
36
+
37
+ Args:
38
+ x: Input tensor of shape (dim,)
39
+ z: Optional gating tensor of shape (dim,)
40
+
41
+ Returns:
42
+ Normalized tensor of same shape as input
43
+ """
44
+ if z is not None:
45
+ x *= z
46
+
47
+ y = x.astype(jnp.float32)
48
+ norm = y * lax.rsqrt(jnp.mean(y * y, -1, keepdims=True) + 1e-5)
49
+
50
+ return self.w * norm.astype(x.dtype)
51
+
52
+
53
+ class LayerScale(eqx.Module):
54
+ """Layer scaling module for stabilizing deep networks.
55
+
56
+ Implements learnable scaling factors for each feature dimension, initialized
57
+ to a small value. This helps stabilize training in deep networks by initially
58
+ dampening the contribution of each layer.
59
+
60
+ Attributes:
61
+ init_values: Initial scale value (static)
62
+ gamma: Learnable scale parameters of size dim
63
+ """
64
+
65
+ init_values: float = eqx.field(static=True)
66
+ gamma: Optional[Float[Array, "dim"]]
67
+
68
+ def __init__(self, dim: int, init_values: float):
69
+ """Initialize LayerScale.
70
+
71
+ Args:
72
+ dim: Dimension of the input features
73
+ init_values: Initial value for all scaling factors
74
+ """
75
+ self.init_values = init_values
76
+ self.gamma = jnp.repeat(self.init_values, dim)
77
+
78
+ def __call__(self, x: Float[Array, "dim"]):
79
+ """Apply layer scaling to input tensor.
80
+
81
+ Args:
82
+ x: Input tensor of shape (dim,)
83
+
84
+ Returns:
85
+ Scaled tensor of same shape as input
86
+ """
87
+ return x * self.gamma