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.
Files changed (46) hide show
  1. stackformers/__init__.py +136 -0
  2. stackformers/attention/README.md +21 -0
  3. stackformers/attention/__init__.py +16 -0
  4. stackformers/attention/bias.py +12 -0
  5. stackformers/attention/config.py +145 -0
  6. stackformers/attention/cross_attn.py +77 -0
  7. stackformers/attention/ops.py +167 -0
  8. stackformers/attention/protocols.py +44 -0
  9. stackformers/attention/self_attn.py +88 -0
  10. stackformers/config.py +26 -0
  11. stackformers/cross_attender.py +46 -0
  12. stackformers/decoder.py +57 -0
  13. stackformers/encoder.py +26 -0
  14. stackformers/feedforward/README.md +11 -0
  15. stackformers/feedforward/__init__.py +5 -0
  16. stackformers/feedforward/config.py +68 -0
  17. stackformers/feedforward/factory.py +24 -0
  18. stackformers/feedforward/geglu.py +35 -0
  19. stackformers/feedforward/protocols.py +16 -0
  20. stackformers/feedforward/relu_squared.py +28 -0
  21. stackformers/feedforward/swiglu.py +34 -0
  22. stackformers/layers.py +31 -0
  23. stackformers/norm/README.md +11 -0
  24. stackformers/norm/__init__.py +3 -0
  25. stackformers/norm/config.py +20 -0
  26. stackformers/norm/factory.py +18 -0
  27. stackformers/norm/protocols.py +16 -0
  28. stackformers/positional/README.md +13 -0
  29. stackformers/positional/__init__.py +23 -0
  30. stackformers/positional/config.py +65 -0
  31. stackformers/positional/factory.py +28 -0
  32. stackformers/positional/learned.py +49 -0
  33. stackformers/positional/none.py +32 -0
  34. stackformers/positional/protocols.py +32 -0
  35. stackformers/positional/rope1d.py +111 -0
  36. stackformers/positional/rope2d.py +59 -0
  37. stackformers/presets/README.md +63 -0
  38. stackformers/presets/__init__.py +39 -0
  39. stackformers/presets/cross_attender.py +108 -0
  40. stackformers/presets/decoder.py +126 -0
  41. stackformers/presets/encoder.py +150 -0
  42. stackformers/sequence.py +158 -0
  43. stackformers-3.8.0.dist-info/METADATA +204 -0
  44. stackformers-3.8.0.dist-info/RECORD +46 -0
  45. stackformers-3.8.0.dist-info/WHEEL +4 -0
  46. 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)
@@ -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)
@@ -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,5 @@
1
+ from stackformers.feedforward.geglu import GEGLU
2
+ from stackformers.feedforward.relu_squared import ReluSquaredFF
3
+ from stackformers.feedforward.swiglu import SwiGLU
4
+
5
+ __all__ = ["GEGLU", "ReluSquaredFF", "SwiGLU"]
@@ -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,3 @@
1
+ from stackformers.norm.config import LayerNormConfig, RMSNormConfig
2
+
3
+ __all__ = ["LayerNormConfig", "RMSNormConfig"]
@@ -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
+ ]