stackformers 3.8.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.
- stackformers/__init__.py +136 -0
- stackformers/attention/README.md +21 -0
- stackformers/attention/__init__.py +16 -0
- stackformers/attention/bias.py +12 -0
- stackformers/attention/config.py +145 -0
- stackformers/attention/cross_attn.py +77 -0
- stackformers/attention/ops.py +167 -0
- stackformers/attention/protocols.py +44 -0
- stackformers/attention/self_attn.py +88 -0
- stackformers/config.py +26 -0
- stackformers/cross_attender.py +46 -0
- stackformers/decoder.py +57 -0
- stackformers/encoder.py +26 -0
- stackformers/feedforward/README.md +11 -0
- stackformers/feedforward/__init__.py +5 -0
- stackformers/feedforward/config.py +68 -0
- stackformers/feedforward/factory.py +24 -0
- stackformers/feedforward/geglu.py +35 -0
- stackformers/feedforward/protocols.py +16 -0
- stackformers/feedforward/relu_squared.py +28 -0
- stackformers/feedforward/swiglu.py +34 -0
- stackformers/layers.py +31 -0
- stackformers/norm/README.md +11 -0
- stackformers/norm/__init__.py +3 -0
- stackformers/norm/config.py +20 -0
- stackformers/norm/factory.py +18 -0
- stackformers/norm/protocols.py +16 -0
- stackformers/positional/README.md +13 -0
- stackformers/positional/__init__.py +23 -0
- stackformers/positional/config.py +65 -0
- stackformers/positional/factory.py +28 -0
- stackformers/positional/learned.py +49 -0
- stackformers/positional/none.py +32 -0
- stackformers/positional/protocols.py +32 -0
- stackformers/positional/rope1d.py +111 -0
- stackformers/positional/rope2d.py +59 -0
- stackformers/presets/README.md +63 -0
- stackformers/presets/__init__.py +39 -0
- stackformers/presets/cross_attender.py +108 -0
- stackformers/presets/decoder.py +126 -0
- stackformers/presets/encoder.py +150 -0
- stackformers/sequence.py +158 -0
- stackformers-3.8.0.dist-info/METADATA +204 -0
- stackformers-3.8.0.dist-info/RECORD +46 -0
- stackformers-3.8.0.dist-info/WHEEL +4 -0
- stackformers-3.8.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from einops import rearrange, repeat
|
|
5
|
+
from torch import Tensor
|
|
6
|
+
|
|
7
|
+
from stackformers.attention.bias import NoAttnBias
|
|
8
|
+
from stackformers.attention.config import SelfAttentionConfig
|
|
9
|
+
from stackformers.attention.ops import packed_attn_or_fallback, padded_sdpa
|
|
10
|
+
from stackformers.attention.protocols import AttnBias
|
|
11
|
+
from stackformers.positional.protocols import PosEncoding
|
|
12
|
+
from stackformers.sequence import (
|
|
13
|
+
PackedInput,
|
|
14
|
+
PackedSequence,
|
|
15
|
+
PaddedInput,
|
|
16
|
+
SequenceInput,
|
|
17
|
+
packed_to_padded,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SelfAttention(nn.Module):
|
|
22
|
+
"""Multi-head self-attention.
|
|
23
|
+
|
|
24
|
+
window_size=None (default) → global attention.
|
|
25
|
+
window_size=w → sliding-window attention with width w.
|
|
26
|
+
|
|
27
|
+
Pass PaddedInput for inference or export, PackedInput for training — same weights.
|
|
28
|
+
Falls back to padded SDPA when varlen_attn is unavailable (CPU, float32, torch.export)
|
|
29
|
+
or when an attention bias is provided.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
config: SelfAttentionConfig,
|
|
35
|
+
pos_encoding: PosEncoding,
|
|
36
|
+
attn_bias: AttnBias = NoAttnBias(),
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.config = config
|
|
40
|
+
h, kv_h, dh = config.heads, config.effective_kv_heads, config.dim_head
|
|
41
|
+
self.to_q = nn.Linear(config.dim, h * dh, bias=False)
|
|
42
|
+
self.to_k = nn.Linear(config.dim, kv_h * dh, bias=False)
|
|
43
|
+
self.to_v = nn.Linear(config.dim, kv_h * dh, bias=False)
|
|
44
|
+
self.to_out = nn.Linear(h * dh, config.dim, bias=False)
|
|
45
|
+
self.dropout = nn.Dropout(config.dropout)
|
|
46
|
+
self.pos_encoding = pos_encoding
|
|
47
|
+
self.attn_bias = attn_bias
|
|
48
|
+
self.q_norm: nn.Module = nn.RMSNorm(dh) if config.qk_norm else nn.Identity()
|
|
49
|
+
self.k_norm: nn.Module = nn.RMSNorm(dh) if config.qk_norm else nn.Identity()
|
|
50
|
+
nn.init.normal_(self.to_out.weight, std=0.02)
|
|
51
|
+
|
|
52
|
+
def _forward_padded(self, input: PaddedInput) -> Tensor:
|
|
53
|
+
cfg = self.config
|
|
54
|
+
h, kv_h, groups = cfg.heads, cfg.effective_kv_heads, cfg.groups
|
|
55
|
+
x = input.x
|
|
56
|
+
q = self.q_norm(rearrange(self.to_q(x), "b n (h d) -> b h n d", h=h))
|
|
57
|
+
k = self.k_norm(rearrange(self.to_k(x), "b n (h d) -> b h n d", h=kv_h))
|
|
58
|
+
v = rearrange(self.to_v(x), "b n (h d) -> b h n d", h=kv_h)
|
|
59
|
+
if groups > 1:
|
|
60
|
+
k = repeat(k, "b h n d -> b (h g) n d", g=groups)
|
|
61
|
+
v = repeat(v, "b h n d -> b (h g) n d", g=groups)
|
|
62
|
+
q, k = self.pos_encoding.forward_padded(q, k, input.abs_positions, input.abs_positions)
|
|
63
|
+
bias = self.attn_bias(input)
|
|
64
|
+
out = padded_sdpa(q, k, v, input.mask, cfg.causal, cfg.window_size, bias)
|
|
65
|
+
return self.dropout(self.to_out(rearrange(out, "b h n d -> b n (h d)")))
|
|
66
|
+
|
|
67
|
+
def _forward_packed(self, input: PackedInput) -> Tensor:
|
|
68
|
+
cfg = self.config
|
|
69
|
+
h, kv_h, groups = cfg.heads, cfg.effective_kv_heads, cfg.groups
|
|
70
|
+
x = input.x
|
|
71
|
+
q = self.q_norm(rearrange(self.to_q(x), "nt (h d) -> nt h d", h=h))
|
|
72
|
+
k = self.k_norm(rearrange(self.to_k(x), "nt (h d) -> nt h d", h=kv_h))
|
|
73
|
+
v = rearrange(self.to_v(x), "nt (h d) -> nt h d", h=kv_h)
|
|
74
|
+
if groups > 1:
|
|
75
|
+
k = repeat(k, "nt h d -> nt (h g) d", g=groups)
|
|
76
|
+
v = repeat(v, "nt h d -> nt (h g) d", g=groups)
|
|
77
|
+
q, k = self.pos_encoding.forward_packed(q, k, input.abs_positions, input.abs_positions)
|
|
78
|
+
bias = self.attn_bias(packed_to_padded(input))
|
|
79
|
+
seq = PackedSequence(cu_seqlens=input.cu_seqlens, max_seqlen=input.max_seqlen)
|
|
80
|
+
out = packed_attn_or_fallback(q, k, v, seq, seq, cfg.causal, cfg.window_size, bias)
|
|
81
|
+
return self.dropout(self.to_out(rearrange(out, "nt h d -> nt (h d)")))
|
|
82
|
+
|
|
83
|
+
def forward(self, input: SequenceInput) -> Tensor:
|
|
84
|
+
match input:
|
|
85
|
+
case PaddedInput():
|
|
86
|
+
return self._forward_padded(input)
|
|
87
|
+
case PackedInput():
|
|
88
|
+
return self._forward_packed(input)
|
stackformers/config.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from stackformers.attention.config import CrossAttentionConfig, SelfAttentionConfig
|
|
6
|
+
from stackformers.feedforward.config import FeedForwardConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LayerConfig(BaseModel):
|
|
10
|
+
attn: SelfAttentionConfig
|
|
11
|
+
ff: FeedForwardConfig
|
|
12
|
+
pre_norm: bool = True
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EncoderConfig(BaseModel):
|
|
16
|
+
layer: LayerConfig
|
|
17
|
+
num_layers: int = Field(gt=0)
|
|
18
|
+
dropout: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DecoderConfig(BaseModel):
|
|
22
|
+
self_attn: SelfAttentionConfig
|
|
23
|
+
cross_attn: CrossAttentionConfig
|
|
24
|
+
ff: FeedForwardConfig
|
|
25
|
+
num_layers: int = Field(gt=0)
|
|
26
|
+
dropout: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from torch import Tensor
|
|
5
|
+
|
|
6
|
+
from stackformers.attention.protocols import CrossAttn
|
|
7
|
+
from stackformers.feedforward.protocols import FeedForward
|
|
8
|
+
from stackformers.norm.protocols import Norm
|
|
9
|
+
from stackformers.sequence import SequenceInput
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CrossAttenderLayer(nn.Module):
|
|
13
|
+
"""Pre-norm layer: cross-attn → feed-forward. No self-attention."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
cross_attn: CrossAttn,
|
|
18
|
+
ff: FeedForward,
|
|
19
|
+
norm_cross: Norm,
|
|
20
|
+
norm_ff: Norm,
|
|
21
|
+
) -> None:
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.cross_attn = cross_attn
|
|
24
|
+
self.ff = ff
|
|
25
|
+
self.norm_cross = norm_cross
|
|
26
|
+
self.norm_ff = norm_ff
|
|
27
|
+
|
|
28
|
+
def forward(self, x_input: SequenceInput, ctx_input: SequenceInput) -> SequenceInput:
|
|
29
|
+
normed = x_input._replace(x=self.norm_cross(x_input.x))
|
|
30
|
+
x = x_input.x + self.cross_attn(normed, ctx_input)
|
|
31
|
+
x = x + self.ff(self.norm_ff(x))
|
|
32
|
+
return x_input._replace(x=x)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CrossAttenderStack(nn.Module):
|
|
36
|
+
"""Stack of CrossAttenderLayers with a final layer norm."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, layers: list[CrossAttenderLayer], final_norm: Norm) -> None:
|
|
39
|
+
super().__init__()
|
|
40
|
+
self.layers = nn.ModuleList(layers)
|
|
41
|
+
self.final_norm = final_norm
|
|
42
|
+
|
|
43
|
+
def forward(self, x_input: SequenceInput, ctx_input: SequenceInput) -> Tensor:
|
|
44
|
+
for layer in self.layers:
|
|
45
|
+
x_input = layer(x_input, ctx_input)
|
|
46
|
+
return self.final_norm(x_input.x)
|
stackformers/decoder.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from torch import Tensor
|
|
5
|
+
|
|
6
|
+
from stackformers.attention.protocols import CrossAttn, SelfAttn
|
|
7
|
+
from stackformers.feedforward.protocols import FeedForward
|
|
8
|
+
from stackformers.norm.protocols import Norm
|
|
9
|
+
from stackformers.sequence import SequenceInput
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DecoderLayer(nn.Module):
|
|
13
|
+
"""Pre-norm decoder layer: causal self-attn → cross-attn → feed-forward."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
self_attn: SelfAttn,
|
|
18
|
+
cross_attn: CrossAttn,
|
|
19
|
+
ff: FeedForward,
|
|
20
|
+
norm_self: Norm,
|
|
21
|
+
norm_cross: Norm,
|
|
22
|
+
norm_ff: Norm,
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.self_attn = self_attn
|
|
26
|
+
self.cross_attn = cross_attn
|
|
27
|
+
self.ff = ff
|
|
28
|
+
self.norm_self = norm_self
|
|
29
|
+
self.norm_cross = norm_cross
|
|
30
|
+
self.norm_ff = norm_ff
|
|
31
|
+
|
|
32
|
+
def forward(self, x_input: SequenceInput, ctx_input: SequenceInput) -> SequenceInput:
|
|
33
|
+
normed_self = x_input._replace(x=self.norm_self(x_input.x))
|
|
34
|
+
x = x_input.x + self.self_attn(normed_self)
|
|
35
|
+
x_input = x_input._replace(x=x)
|
|
36
|
+
normed_cross = x_input._replace(x=self.norm_cross(x_input.x))
|
|
37
|
+
x = x_input.x + self.cross_attn(normed_cross, ctx_input)
|
|
38
|
+
x = x + self.ff(self.norm_ff(x))
|
|
39
|
+
return x_input._replace(x=x)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Decoder(nn.Module):
|
|
43
|
+
"""Stack of DecoderLayers with a final layer norm."""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
layers: list[DecoderLayer],
|
|
48
|
+
final_norm: Norm,
|
|
49
|
+
) -> None:
|
|
50
|
+
super().__init__()
|
|
51
|
+
self.layers = nn.ModuleList(layers)
|
|
52
|
+
self.final_norm = final_norm
|
|
53
|
+
|
|
54
|
+
def forward(self, x_input: SequenceInput, ctx_input: SequenceInput) -> Tensor:
|
|
55
|
+
for layer in self.layers:
|
|
56
|
+
x_input = layer(x_input, ctx_input)
|
|
57
|
+
return self.final_norm(x_input.x)
|
stackformers/encoder.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from torch import Tensor
|
|
5
|
+
|
|
6
|
+
from stackformers.layers import TransformerLayer
|
|
7
|
+
from stackformers.norm.protocols import Norm
|
|
8
|
+
from stackformers.sequence import SequenceInput
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Encoder(nn.Module):
|
|
12
|
+
"""Stack of TransformerLayers with a final layer norm."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
layers: list[TransformerLayer],
|
|
17
|
+
final_norm: Norm,
|
|
18
|
+
) -> None:
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.layers = nn.ModuleList(layers)
|
|
21
|
+
self.final_norm = final_norm
|
|
22
|
+
|
|
23
|
+
def forward(self, input: SequenceInput) -> Tensor:
|
|
24
|
+
for layer in self.layers:
|
|
25
|
+
input = layer(input)
|
|
26
|
+
return self.final_norm(input.x)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# feedforward
|
|
2
|
+
|
|
3
|
+
Token-wise feed-forward sublayers behind the `FeedForward` protocol: `(x: b n d) → b n d`.
|
|
4
|
+
|
|
5
|
+
`SwiGLU` is the only implementation. Its inner dimension is `int(dim * mult * 2/3)`, scaled down so parameter count matches a standard 4× GELU FFN.
|
|
6
|
+
|
|
7
|
+
## Adding a new feed-forward
|
|
8
|
+
|
|
9
|
+
1. Add a config class to `config.py` (or reuse `FeedForwardConfig` if parameterisation is identical).
|
|
10
|
+
2. Implement the class satisfying `FeedForward` structurally.
|
|
11
|
+
3. Add a `case` branch in `factory.py::build_ff`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Annotated, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, model_validator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _FFBase(BaseModel):
|
|
10
|
+
"""Shared geometry and alignment check for gated feed-forward variants."""
|
|
11
|
+
|
|
12
|
+
dim: int = Field(gt=0)
|
|
13
|
+
mult: float = Field(default=4.0, gt=0.0)
|
|
14
|
+
dropout: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
15
|
+
|
|
16
|
+
_ALIGN = 64 # tensor-core alignment for FP16/BF16
|
|
17
|
+
|
|
18
|
+
@model_validator(mode="after")
|
|
19
|
+
def _check_inner_dim_alignment(self) -> _FFBase:
|
|
20
|
+
d = self.inner_dim
|
|
21
|
+
if d % self._ALIGN != 0:
|
|
22
|
+
warnings.warn(
|
|
23
|
+
f"inner_dim={d} is not a multiple of {self._ALIGN}. "
|
|
24
|
+
"Unaligned dimensions reduce GPU throughput on tensor-core hardware. "
|
|
25
|
+
f"Nearest aligned values: {(d // self._ALIGN) * self._ALIGN} or "
|
|
26
|
+
f"{(d // self._ALIGN + 1) * self._ALIGN}. "
|
|
27
|
+
"Adjust dim or mult so that int(dim * mult * 2/3) is a multiple of "
|
|
28
|
+
f"{self._ALIGN}.",
|
|
29
|
+
UserWarning,
|
|
30
|
+
stacklevel=2,
|
|
31
|
+
)
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def inner_dim(self) -> int:
|
|
36
|
+
# Two gate matrices → scale inner dim down to match param count with GELU-4x FFN.
|
|
37
|
+
return int(self.dim * self.mult * 2 / 3)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SwiGLUConfig(_FFBase):
|
|
41
|
+
"""Config for the SwiGLU feed-forward network (Noam Shazeer, 2020)."""
|
|
42
|
+
|
|
43
|
+
kind: Literal["swiglu"] = "swiglu"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class GEGLUConfig(_FFBase):
|
|
47
|
+
"""Config for the GEGLU feed-forward network (Noam Shazeer, 2020)."""
|
|
48
|
+
|
|
49
|
+
kind: Literal["geglu"] = "geglu"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ReluSquaredConfig(_FFBase):
|
|
53
|
+
"""Config for the ReLU² feed-forward network.
|
|
54
|
+
|
|
55
|
+
Non-gated: inner_dim = dim * mult (no 2/3 factor).
|
|
56
|
+
Paper: "Primer: Searching for Efficient Transformers" — https://arxiv.org/abs/2109.08668
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
kind: Literal["relu_squared"] = "relu_squared"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def inner_dim(self) -> int:
|
|
63
|
+
return int(self.dim * self.mult)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
FeedForwardConfig = Annotated[
|
|
67
|
+
SwiGLUConfig | GEGLUConfig | ReluSquaredConfig, Field(discriminator="kind")
|
|
68
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from stackformers.feedforward.config import (
|
|
4
|
+
FeedForwardConfig,
|
|
5
|
+
GEGLUConfig,
|
|
6
|
+
ReluSquaredConfig,
|
|
7
|
+
SwiGLUConfig,
|
|
8
|
+
)
|
|
9
|
+
from stackformers.feedforward.geglu import GEGLU
|
|
10
|
+
from stackformers.feedforward.protocols import FeedForward
|
|
11
|
+
from stackformers.feedforward.relu_squared import ReluSquaredFF
|
|
12
|
+
from stackformers.feedforward.swiglu import SwiGLU
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_ff(config: FeedForwardConfig) -> FeedForward:
|
|
16
|
+
match config:
|
|
17
|
+
case SwiGLUConfig():
|
|
18
|
+
return SwiGLU(config)
|
|
19
|
+
case GEGLUConfig():
|
|
20
|
+
return GEGLU(config)
|
|
21
|
+
case ReluSquaredConfig():
|
|
22
|
+
return ReluSquaredFF(config)
|
|
23
|
+
case _:
|
|
24
|
+
raise AssertionError(f"Unhandled feedforward config: {type(config)}")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from jaxtyping import Float
|
|
5
|
+
from torch import Tensor
|
|
6
|
+
|
|
7
|
+
from stackformers.feedforward.config import GEGLUConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GEGLU(nn.Module):
|
|
11
|
+
"""GEGLU feed-forward network.
|
|
12
|
+
|
|
13
|
+
GLU variant that gates with GELU instead of SiLU (SwiGLU). Empirically
|
|
14
|
+
competitive with SwiGLU; slightly smoother gradient signal near zero.
|
|
15
|
+
inner_dim = 2/3 * dim * mult so total params match a standard 4x GELU FFN.
|
|
16
|
+
|
|
17
|
+
Paper: "GLU Variants Improve Transformers" — https://arxiv.org/abs/2002.05202
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config: GEGLUConfig) -> None:
|
|
21
|
+
super().__init__()
|
|
22
|
+
inner_dim = config.inner_dim
|
|
23
|
+
|
|
24
|
+
self.w1 = nn.Linear(config.dim, inner_dim, bias=False)
|
|
25
|
+
self.w2 = nn.Linear(config.dim, inner_dim, bias=False)
|
|
26
|
+
self.w3 = nn.Linear(inner_dim, config.dim, bias=False)
|
|
27
|
+
self.dropout = nn.Dropout(config.dropout)
|
|
28
|
+
self.act = nn.GELU(approximate="tanh")
|
|
29
|
+
|
|
30
|
+
nn.init.normal_(self.w3.weight, std=0.02)
|
|
31
|
+
|
|
32
|
+
def forward(self, x: Float[Tensor, "b n d"]) -> Float[Tensor, "b n d"]:
|
|
33
|
+
gate = self.act(self.w1(x))
|
|
34
|
+
hidden = gate * self.w2(x)
|
|
35
|
+
return self.w3(self.dropout(hidden))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from jaxtyping import Float
|
|
6
|
+
from torch import Tensor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class FeedForward(Protocol):
|
|
11
|
+
"""Apply a feed-forward transformation to (b, n, d) token embeddings.
|
|
12
|
+
|
|
13
|
+
Implementation: SwiGLU.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __call__(self, x: Float[Tensor, "b n d"]) -> Float[Tensor, "b n d"]: ...
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from jaxtyping import Float
|
|
5
|
+
from torch import Tensor
|
|
6
|
+
|
|
7
|
+
from stackformers.feedforward.config import ReluSquaredConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ReluSquaredFF(nn.Module):
|
|
11
|
+
"""ReLU² feed-forward network.
|
|
12
|
+
|
|
13
|
+
Standard two-matrix FFN (no gating) with ReLU² activation.
|
|
14
|
+
inner_dim = dim * mult; parameter count matches a standard mult-x FFN.
|
|
15
|
+
|
|
16
|
+
Paper: "Primer: Searching for Efficient Transformers" — https://arxiv.org/abs/2109.08668
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: ReluSquaredConfig) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.w1 = nn.Linear(config.dim, config.inner_dim, bias=False)
|
|
22
|
+
self.w2 = nn.Linear(config.inner_dim, config.dim, bias=False)
|
|
23
|
+
self.dropout = nn.Dropout(config.dropout)
|
|
24
|
+
self.act = nn.ReLU()
|
|
25
|
+
|
|
26
|
+
def forward(self, x: Float[Tensor, "b n d"]) -> Float[Tensor, "b n d"]:
|
|
27
|
+
h = self.act(self.w1(x))
|
|
28
|
+
return self.w2(self.dropout(h.square()))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
from jaxtyping import Float
|
|
5
|
+
from torch import Tensor
|
|
6
|
+
|
|
7
|
+
from stackformers.feedforward.config import SwiGLUConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SwiGLU(nn.Module):
|
|
11
|
+
"""SwiGLU feed-forward network.
|
|
12
|
+
|
|
13
|
+
Uses two parallel gate matrices (W1, W2) and one output projection (W3).
|
|
14
|
+
inner_dim = 2/3 * dim * mult so total params match a standard 4x GELU FFN.
|
|
15
|
+
|
|
16
|
+
Paper: "GLU Variants Improve Transformers" — https://arxiv.org/abs/2002.05202
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: SwiGLUConfig) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
inner_dim = config.inner_dim
|
|
22
|
+
|
|
23
|
+
self.w1 = nn.Linear(config.dim, inner_dim, bias=False)
|
|
24
|
+
self.w2 = nn.Linear(config.dim, inner_dim, bias=False)
|
|
25
|
+
self.w3 = nn.Linear(inner_dim, config.dim, bias=False)
|
|
26
|
+
self.dropout = nn.Dropout(config.dropout)
|
|
27
|
+
self.act = nn.SiLU()
|
|
28
|
+
|
|
29
|
+
nn.init.normal_(self.w3.weight, std=0.02)
|
|
30
|
+
|
|
31
|
+
def forward(self, x: Float[Tensor, "b n d"]) -> Float[Tensor, "b n d"]:
|
|
32
|
+
gate = self.act(self.w1(x))
|
|
33
|
+
hidden = gate * self.w2(x)
|
|
34
|
+
return self.w3(self.dropout(hidden))
|
stackformers/layers.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
|
|
5
|
+
from stackformers.attention.protocols import SelfAttn
|
|
6
|
+
from stackformers.feedforward.protocols import FeedForward
|
|
7
|
+
from stackformers.norm.protocols import Norm
|
|
8
|
+
from stackformers.sequence import SequenceInput
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TransformerLayer(nn.Module):
|
|
12
|
+
"""Pre-norm transformer layer: norm → self-attn → residual, norm → ff → residual."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
self_attn: SelfAttn,
|
|
17
|
+
ff: FeedForward,
|
|
18
|
+
norm_attn: Norm,
|
|
19
|
+
norm_ff: Norm,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.self_attn = self_attn
|
|
23
|
+
self.ff = ff
|
|
24
|
+
self.norm_attn = norm_attn
|
|
25
|
+
self.norm_ff = norm_ff
|
|
26
|
+
|
|
27
|
+
def forward(self, input: SequenceInput) -> SequenceInput:
|
|
28
|
+
normed = input._replace(x=self.norm_attn(input.x))
|
|
29
|
+
x = input.x + self.self_attn(normed)
|
|
30
|
+
x = x + self.ff(self.norm_ff(x))
|
|
31
|
+
return input._replace(x=x)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# norm
|
|
2
|
+
|
|
3
|
+
Normalisation layers behind the `Norm` protocol: `(x: b n d) → b n d`.
|
|
4
|
+
|
|
5
|
+
`RMSNorm` is the default — no mean subtraction, no bias, numerically stable on fp16. `LayerNorm` is available for compatibility.
|
|
6
|
+
|
|
7
|
+
## Adding a new norm
|
|
8
|
+
|
|
9
|
+
1. Add a config class with a `kind: Literal[...]` discriminator to `config.py` and include it in the `NormConfig` union.
|
|
10
|
+
2. Implement the class satisfying `Norm` structurally — do not import the protocol.
|
|
11
|
+
3. Add a `case` branch in `factory.py::build_norm`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RMSNormConfig(BaseModel):
|
|
9
|
+
kind: Literal["rms"] = "rms"
|
|
10
|
+
dim: int = Field(gt=0)
|
|
11
|
+
eps: float = Field(default=1e-6, gt=0)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LayerNormConfig(BaseModel):
|
|
15
|
+
kind: Literal["layer_norm"] = "layer_norm"
|
|
16
|
+
dim: int = Field(gt=0)
|
|
17
|
+
eps: float = 1e-5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
NormConfig = Annotated[RMSNormConfig | LayerNormConfig, Field(discriminator="kind")]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch.nn as nn
|
|
4
|
+
|
|
5
|
+
from stackformers.norm.config import LayerNormConfig, NormConfig, RMSNormConfig
|
|
6
|
+
from stackformers.norm.protocols import Norm
|
|
7
|
+
|
|
8
|
+
__all__ = ["NormConfig", "build_norm"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_norm(config: NormConfig) -> Norm:
|
|
12
|
+
match config:
|
|
13
|
+
case RMSNormConfig():
|
|
14
|
+
return nn.RMSNorm(config.dim, eps=config.eps)
|
|
15
|
+
case LayerNormConfig():
|
|
16
|
+
return nn.LayerNorm(config.dim, eps=config.eps)
|
|
17
|
+
case _:
|
|
18
|
+
raise AssertionError(f"Unhandled norm config: {type(config)}")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from jaxtyping import Float
|
|
6
|
+
from torch import Tensor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class Norm(Protocol):
|
|
11
|
+
"""Apply layer normalisation to (b, n, d) tensors.
|
|
12
|
+
|
|
13
|
+
Implementation: RMSNorm.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __call__(self, x: Float[Tensor, "b n d"]) -> Float[Tensor, "b n d"]: ...
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# positional
|
|
2
|
+
|
|
3
|
+
Positional encodings applied to Q and K tensors inside attention — not to the residual stream.
|
|
4
|
+
|
|
5
|
+
`RotaryEmbedding1D` is the standard choice. It supports YaRN context extension via `RoPE1DConfig(yarn=YaRNConfig(...))`. `RotaryEmbedding2D` handles grid inputs by splitting the head dimension into row and column components. `NoPosEncoding` is a null object for cross-attention paths that need no positional information.
|
|
6
|
+
|
|
7
|
+
The `PosEncoding` protocol exposes two methods — `forward_padded` and `forward_packed` — so callers (which already know their layout) pick the right path directly without internal dispatch. All implementations share the same computation via layout-agnostic helpers, since positional encoding is a per-token operation that does not depend on sequence boundaries.
|
|
8
|
+
|
|
9
|
+
## Adding a new encoding
|
|
10
|
+
|
|
11
|
+
1. Add a config class with a `kind: Literal[...]` discriminator to `config.py` and include it in the `PosEncodingConfig` union.
|
|
12
|
+
2. Implement the class satisfying `PosEncoding` (and optionally `PackedPosEncoding`) structurally.
|
|
13
|
+
3. Add a `case` branch in `factory.py::build_pos_encoding`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from stackformers.positional.config import (
|
|
2
|
+
LearnedPosEncodingConfig,
|
|
3
|
+
NoPosEncodingConfig,
|
|
4
|
+
PosEncodingConfig,
|
|
5
|
+
RoPE1DConfig,
|
|
6
|
+
YaRNConfig,
|
|
7
|
+
)
|
|
8
|
+
from stackformers.positional.learned import LearnedPosEncoding
|
|
9
|
+
from stackformers.positional.none import NoPosEncoding
|
|
10
|
+
from stackformers.positional.rope1d import RotaryEmbedding1D
|
|
11
|
+
from stackformers.positional.rope2d import RotaryEmbedding2D
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"LearnedPosEncoding",
|
|
15
|
+
"LearnedPosEncodingConfig",
|
|
16
|
+
"NoPosEncoding",
|
|
17
|
+
"NoPosEncodingConfig",
|
|
18
|
+
"PosEncodingConfig",
|
|
19
|
+
"RoPE1DConfig",
|
|
20
|
+
"RotaryEmbedding1D",
|
|
21
|
+
"RotaryEmbedding2D",
|
|
22
|
+
"YaRNConfig",
|
|
23
|
+
]
|