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,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class YaRNConfig(BaseModel):
|
|
9
|
+
"""NTK-by-parts RoPE frequency scaling (Peng et al., 2023).
|
|
10
|
+
|
|
11
|
+
Extends RoPE to longer contexts by scaling low-frequency dimensions
|
|
12
|
+
linearly while leaving high-frequency dimensions unchanged. The
|
|
13
|
+
inv_freq buffer is modified once at construction time so forward()
|
|
14
|
+
stays export-traceable.
|
|
15
|
+
|
|
16
|
+
scale: target_max_seq_len / original_max_seq_len
|
|
17
|
+
original_max_seq_len: context length the base model was trained on
|
|
18
|
+
beta_fast: high-freq threshold (wavelength = original / beta_fast)
|
|
19
|
+
beta_slow: low-freq threshold (wavelength = original / beta_slow)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
scale: float = Field(gt=1.0)
|
|
23
|
+
original_max_seq_len: int = Field(gt=0)
|
|
24
|
+
beta_fast: float = Field(default=32.0, gt=0.0)
|
|
25
|
+
beta_slow: float = Field(default=1.0, gt=0.0)
|
|
26
|
+
|
|
27
|
+
@model_validator(mode="after")
|
|
28
|
+
def _check_beta_ordering(self) -> YaRNConfig:
|
|
29
|
+
if self.beta_slow >= self.beta_fast:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"beta_slow ({self.beta_slow}) must be less than beta_fast ({self.beta_fast}). "
|
|
32
|
+
"Swapped values invert the high/low-frequency partition and silently break scaling."
|
|
33
|
+
)
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RoPE1DConfig(BaseModel):
|
|
38
|
+
kind: Literal["rope1d"] = "rope1d"
|
|
39
|
+
dim_head: int = Field(gt=0)
|
|
40
|
+
base: int = Field(default=10_000, gt=0)
|
|
41
|
+
yarn: YaRNConfig | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RoPE2DConfig(BaseModel):
|
|
45
|
+
kind: Literal["rope2d"] = "rope2d"
|
|
46
|
+
dim_head: int = Field(gt=0)
|
|
47
|
+
base: int = Field(default=10_000, gt=0)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class NoPosEncodingConfig(BaseModel):
|
|
51
|
+
kind: Literal["none"] = "none"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class LearnedPosEncodingConfig(BaseModel):
|
|
55
|
+
"""Config for learned absolute position embeddings (Vaswani et al., 2017)."""
|
|
56
|
+
|
|
57
|
+
kind: Literal["learned"] = "learned"
|
|
58
|
+
dim_head: int = Field(gt=0)
|
|
59
|
+
max_seq_len: int = Field(gt=0)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
PosEncodingConfig = Annotated[
|
|
63
|
+
RoPE1DConfig | RoPE2DConfig | NoPosEncodingConfig | LearnedPosEncodingConfig,
|
|
64
|
+
Field(discriminator="kind"),
|
|
65
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from stackformers.positional.config import (
|
|
4
|
+
LearnedPosEncodingConfig,
|
|
5
|
+
NoPosEncodingConfig,
|
|
6
|
+
PosEncodingConfig,
|
|
7
|
+
RoPE1DConfig,
|
|
8
|
+
RoPE2DConfig,
|
|
9
|
+
)
|
|
10
|
+
from stackformers.positional.learned import LearnedPosEncoding
|
|
11
|
+
from stackformers.positional.none import NoPosEncoding
|
|
12
|
+
from stackformers.positional.protocols import PosEncoding
|
|
13
|
+
from stackformers.positional.rope1d import RotaryEmbedding1D
|
|
14
|
+
from stackformers.positional.rope2d import RotaryEmbedding2D
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_pos_encoding(config: PosEncodingConfig) -> PosEncoding:
|
|
18
|
+
match config:
|
|
19
|
+
case RoPE1DConfig():
|
|
20
|
+
return RotaryEmbedding1D(config)
|
|
21
|
+
case RoPE2DConfig():
|
|
22
|
+
return RotaryEmbedding2D(config)
|
|
23
|
+
case NoPosEncodingConfig():
|
|
24
|
+
return NoPosEncoding(config)
|
|
25
|
+
case LearnedPosEncodingConfig():
|
|
26
|
+
return LearnedPosEncoding(config)
|
|
27
|
+
case _:
|
|
28
|
+
raise AssertionError(f"Unhandled pos encoding config: {type(config)}")
|
|
@@ -0,0 +1,49 @@
|
|
|
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.positional.config import LearnedPosEncodingConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LearnedPosEncoding(nn.Module):
|
|
11
|
+
"""Learned absolute position embeddings added to query and key tensors.
|
|
12
|
+
|
|
13
|
+
Each position index is mapped to a learned dh-dimensional vector, which is
|
|
14
|
+
added to q and k after the head projection. Requires integer position indices
|
|
15
|
+
in positions[..., 0]; out-of-range indices raise an embedding error.
|
|
16
|
+
|
|
17
|
+
Section 3.5 of Vaswani et al., 2017 describes the learned variant alongside
|
|
18
|
+
sinusoidal encodings — https://arxiv.org/abs/1706.03762
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, config: LearnedPosEncodingConfig) -> None:
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.emb = nn.Embedding(config.max_seq_len, config.dim_head)
|
|
24
|
+
nn.init.normal_(self.emb.weight, std=0.02)
|
|
25
|
+
|
|
26
|
+
def _encode(
|
|
27
|
+
self, q: Tensor, k: Tensor, q_positions: Tensor, k_positions: Tensor
|
|
28
|
+
) -> tuple[Tensor, Tensor]:
|
|
29
|
+
q_idx = q_positions[..., 0].long()
|
|
30
|
+
k_idx = k_positions[..., 0].long()
|
|
31
|
+
return q + self.emb(q_idx).unsqueeze(1), k + self.emb(k_idx).unsqueeze(1)
|
|
32
|
+
|
|
33
|
+
def forward_padded(
|
|
34
|
+
self,
|
|
35
|
+
q: Float[Tensor, "b h n dh"],
|
|
36
|
+
k: Float[Tensor, "b h s dh"],
|
|
37
|
+
q_positions: Float[Tensor, "b n c"],
|
|
38
|
+
k_positions: Float[Tensor, "b s c"],
|
|
39
|
+
) -> tuple[Float[Tensor, "b h n dh"], Float[Tensor, "b h s dh"]]:
|
|
40
|
+
return self._encode(q, k, q_positions, k_positions)
|
|
41
|
+
|
|
42
|
+
def forward_packed(
|
|
43
|
+
self,
|
|
44
|
+
q: Float[Tensor, "nt h dh"],
|
|
45
|
+
k: Float[Tensor, "nt h dh"],
|
|
46
|
+
q_positions: Float[Tensor, "nt c"],
|
|
47
|
+
k_positions: Float[Tensor, "nt c"],
|
|
48
|
+
) -> tuple[Float[Tensor, "nt h dh"], Float[Tensor, "nt h dh"]]:
|
|
49
|
+
return self._encode(q, k, q_positions, k_positions)
|
|
@@ -0,0 +1,32 @@
|
|
|
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.positional.config import NoPosEncodingConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NoPosEncoding(nn.Module):
|
|
11
|
+
"""Null object for PosEncoding — passes q, k unchanged regardless of layout."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, _config: NoPosEncodingConfig = NoPosEncodingConfig()) -> None:
|
|
14
|
+
super().__init__()
|
|
15
|
+
|
|
16
|
+
def forward_padded(
|
|
17
|
+
self,
|
|
18
|
+
q: Float[Tensor, "b h n dh"],
|
|
19
|
+
k: Float[Tensor, "b h s dh"],
|
|
20
|
+
q_positions: Float[Tensor, "b n c"],
|
|
21
|
+
k_positions: Float[Tensor, "b s c"],
|
|
22
|
+
) -> tuple[Float[Tensor, "b h n dh"], Float[Tensor, "b h s dh"]]:
|
|
23
|
+
return q, k
|
|
24
|
+
|
|
25
|
+
def forward_packed(
|
|
26
|
+
self,
|
|
27
|
+
q: Float[Tensor, "nt h dh"],
|
|
28
|
+
k: Float[Tensor, "nt h dh"],
|
|
29
|
+
q_positions: Float[Tensor, "nt c"],
|
|
30
|
+
k_positions: Float[Tensor, "nt c"],
|
|
31
|
+
) -> tuple[Float[Tensor, "nt h dh"], Float[Tensor, "nt h dh"]]:
|
|
32
|
+
return q, k
|
|
@@ -0,0 +1,32 @@
|
|
|
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 PosEncoding(Protocol):
|
|
11
|
+
"""Apply positional encoding to projected query and key tensors.
|
|
12
|
+
|
|
13
|
+
Separate methods per layout so implementations contain no dispatch logic
|
|
14
|
+
and callers (which already know their layout) pick the right path directly.
|
|
15
|
+
Null implementation: NoPosEncoding.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def forward_padded(
|
|
19
|
+
self,
|
|
20
|
+
q: Float[Tensor, "b h n dh"],
|
|
21
|
+
k: Float[Tensor, "b h s dh"],
|
|
22
|
+
q_positions: Float[Tensor, "b n c"],
|
|
23
|
+
k_positions: Float[Tensor, "b s c"],
|
|
24
|
+
) -> tuple[Float[Tensor, "b h n dh"], Float[Tensor, "b h s dh"]]: ...
|
|
25
|
+
|
|
26
|
+
def forward_packed(
|
|
27
|
+
self,
|
|
28
|
+
q: Float[Tensor, "nt h dh"],
|
|
29
|
+
k: Float[Tensor, "nt h dh"],
|
|
30
|
+
q_positions: Float[Tensor, "nt c"],
|
|
31
|
+
k_positions: Float[Tensor, "nt c"],
|
|
32
|
+
) -> tuple[Float[Tensor, "nt h dh"], Float[Tensor, "nt h dh"]]: ...
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
import torch.nn as nn
|
|
7
|
+
from jaxtyping import Float
|
|
8
|
+
from torch import Tensor
|
|
9
|
+
|
|
10
|
+
from stackformers.positional.config import RoPE1DConfig, YaRNConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _rotate_half(x: Tensor) -> Tensor:
|
|
14
|
+
"""Split x into [x1 | x2] halves and return [-x2 | x1]."""
|
|
15
|
+
half = x.shape[-1] // 2
|
|
16
|
+
x1, x2 = x[..., :half], x[..., half:]
|
|
17
|
+
return torch.cat((-x2, x1), dim=-1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _apply_rope_padded_unbatched(t: Tensor, freqs: Tensor) -> Tensor:
|
|
21
|
+
"""t: b h n dh, freqs: n dh — (n dh) broadcasts over (b h n dh) without unsqueeze."""
|
|
22
|
+
cos = freqs.cos()
|
|
23
|
+
sin = freqs.sin()
|
|
24
|
+
return t * cos + _rotate_half(t) * sin
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _apply_rope(t: Tensor, freqs: Tensor) -> Tensor:
|
|
28
|
+
"""Works for padded (b h n dh)+(b n dh) and packed (nt h dh)+(nt dh).
|
|
29
|
+
|
|
30
|
+
unsqueeze(1) inserts the head dim in both cases.
|
|
31
|
+
Cast to t.dtype so float32 freqs don't upcast a float16 input.
|
|
32
|
+
"""
|
|
33
|
+
cos = freqs.cos().to(dtype=t.dtype).unsqueeze(1)
|
|
34
|
+
sin = freqs.sin().to(dtype=t.dtype).unsqueeze(1)
|
|
35
|
+
return t * cos + _rotate_half(t) * sin
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _yarn_inv_freq(
|
|
39
|
+
inv_freq: Tensor,
|
|
40
|
+
cfg: YaRNConfig,
|
|
41
|
+
) -> Tensor:
|
|
42
|
+
"""Return YaRN-scaled inv_freq (Peng et al., 2023, NTK-by-parts)."""
|
|
43
|
+
wavelen = 2.0 * math.pi / inv_freq
|
|
44
|
+
low_wavelen = cfg.original_max_seq_len / cfg.beta_slow
|
|
45
|
+
high_wavelen = cfg.original_max_seq_len / cfg.beta_fast
|
|
46
|
+
smooth = (
|
|
47
|
+
(cfg.original_max_seq_len / wavelen - cfg.beta_slow) / (cfg.beta_fast - cfg.beta_slow)
|
|
48
|
+
).clamp(0.0, 1.0)
|
|
49
|
+
scaled = inv_freq / cfg.scale
|
|
50
|
+
blended = smooth * inv_freq + (1.0 - smooth) * scaled
|
|
51
|
+
return torch.where(
|
|
52
|
+
wavelen < high_wavelen, inv_freq, torch.where(wavelen > low_wavelen, scaled, blended)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RotaryEmbedding1D(nn.Module):
|
|
57
|
+
"""1-D Rotary Position Embedding (Su et al., 2021).
|
|
58
|
+
|
|
59
|
+
Optionally accepts YaRNConfig for extended context via NTK-by-parts scaling.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, config: RoPE1DConfig) -> None:
|
|
63
|
+
super().__init__()
|
|
64
|
+
assert config.dim_head % 2 == 0, "dim_head must be even for RoPE"
|
|
65
|
+
inv_freq = 1.0 / (
|
|
66
|
+
config.base ** (torch.arange(0, config.dim_head, 2).float() / config.dim_head)
|
|
67
|
+
)
|
|
68
|
+
if config.yarn is not None:
|
|
69
|
+
inv_freq = _yarn_inv_freq(inv_freq, config.yarn)
|
|
70
|
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
|
71
|
+
|
|
72
|
+
@torch.no_grad()
|
|
73
|
+
def _freqs_from_positions(self, positions: Tensor) -> Tensor:
|
|
74
|
+
"""positions: (..., c) → freqs: (..., dh), uses first coordinate only.
|
|
75
|
+
|
|
76
|
+
float32 cast ensures half-precision inputs don't lose precision in the outer product.
|
|
77
|
+
"""
|
|
78
|
+
inv: Tensor = self.inv_freq # type: ignore[assignment]
|
|
79
|
+
pos = positions[..., 0].to(dtype=torch.float32)
|
|
80
|
+
freqs = pos.unsqueeze(-1) * inv.float()
|
|
81
|
+
return torch.cat([freqs, freqs], dim=-1)
|
|
82
|
+
|
|
83
|
+
def _encode(
|
|
84
|
+
self,
|
|
85
|
+
q: Tensor,
|
|
86
|
+
k: Tensor,
|
|
87
|
+
q_positions: Tensor,
|
|
88
|
+
k_positions: Tensor,
|
|
89
|
+
) -> tuple[Tensor, Tensor]:
|
|
90
|
+
return (
|
|
91
|
+
_apply_rope(q, self._freqs_from_positions(q_positions)),
|
|
92
|
+
_apply_rope(k, self._freqs_from_positions(k_positions)),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def forward_padded(
|
|
96
|
+
self,
|
|
97
|
+
q: Float[Tensor, "b h n dh"],
|
|
98
|
+
k: Float[Tensor, "b h s dh"],
|
|
99
|
+
q_positions: Float[Tensor, "b n c"],
|
|
100
|
+
k_positions: Float[Tensor, "b s c"],
|
|
101
|
+
) -> tuple[Float[Tensor, "b h n dh"], Float[Tensor, "b h s dh"]]:
|
|
102
|
+
return self._encode(q, k, q_positions, k_positions)
|
|
103
|
+
|
|
104
|
+
def forward_packed(
|
|
105
|
+
self,
|
|
106
|
+
q: Float[Tensor, "nt h dh"],
|
|
107
|
+
k: Float[Tensor, "nt h dh"],
|
|
108
|
+
q_positions: Float[Tensor, "nt c"],
|
|
109
|
+
k_positions: Float[Tensor, "nt c"],
|
|
110
|
+
) -> tuple[Float[Tensor, "nt h dh"], Float[Tensor, "nt h dh"]]:
|
|
111
|
+
return self._encode(q, k, q_positions, k_positions)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
from jaxtyping import Float
|
|
6
|
+
from torch import Tensor
|
|
7
|
+
|
|
8
|
+
from stackformers.positional.config import RoPE2DConfig
|
|
9
|
+
from stackformers.positional.rope1d import _apply_rope
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RotaryEmbedding2D(nn.Module):
|
|
13
|
+
"""2-D Rotary Position Embedding for (row, col) positions.
|
|
14
|
+
|
|
15
|
+
Conforms to PosEncoding: positions must have c=2 (row, col per token).
|
|
16
|
+
Splits dim_head evenly: first half encodes row, second half column.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: RoPE2DConfig) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
assert config.dim_head % 4 == 0, "dim_head must be divisible by 4 for 2-D RoPE"
|
|
22
|
+
half_dh = config.dim_head // 2
|
|
23
|
+
inv_freq = 1.0 / (config.base ** (torch.arange(0, half_dh, 2).float() / half_dh))
|
|
24
|
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
|
25
|
+
|
|
26
|
+
@torch.no_grad()
|
|
27
|
+
def _freqs_from_positions(self, positions: Tensor) -> Tensor:
|
|
28
|
+
"""positions: (..., 2) → freqs: (..., dh)"""
|
|
29
|
+
inv: Tensor = self.inv_freq # type: ignore[assignment]
|
|
30
|
+
row = positions[..., 0].to(dtype=inv.dtype).unsqueeze(-1) * inv # (..., dh//4)
|
|
31
|
+
col = positions[..., 1].to(dtype=inv.dtype).unsqueeze(-1) * inv
|
|
32
|
+
half = lambda f: torch.cat([f, f], dim=-1) # noqa: E731
|
|
33
|
+
return torch.cat([half(row), half(col)], dim=-1) # (..., dh)
|
|
34
|
+
|
|
35
|
+
def _encode(
|
|
36
|
+
self, q: Tensor, k: Tensor, q_positions: Tensor, k_positions: Tensor
|
|
37
|
+
) -> tuple[Tensor, Tensor]:
|
|
38
|
+
return (
|
|
39
|
+
_apply_rope(q, self._freqs_from_positions(q_positions)),
|
|
40
|
+
_apply_rope(k, self._freqs_from_positions(k_positions)),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def forward_padded(
|
|
44
|
+
self,
|
|
45
|
+
q: Float[Tensor, "b h n dh"],
|
|
46
|
+
k: Float[Tensor, "b h s dh"],
|
|
47
|
+
q_positions: Float[Tensor, "b n c"],
|
|
48
|
+
k_positions: Float[Tensor, "b s c"],
|
|
49
|
+
) -> tuple[Float[Tensor, "b h n dh"], Float[Tensor, "b h s dh"]]:
|
|
50
|
+
return self._encode(q, k, q_positions, k_positions)
|
|
51
|
+
|
|
52
|
+
def forward_packed(
|
|
53
|
+
self,
|
|
54
|
+
q: Float[Tensor, "nt h dh"],
|
|
55
|
+
k: Float[Tensor, "nt h dh"],
|
|
56
|
+
q_positions: Float[Tensor, "nt c"],
|
|
57
|
+
k_positions: Float[Tensor, "nt c"],
|
|
58
|
+
) -> tuple[Float[Tensor, "nt h dh"], Float[Tensor, "nt h dh"]]:
|
|
59
|
+
return self._encode(q, k, q_positions, k_positions)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# presets
|
|
2
|
+
|
|
3
|
+
Opinionated `nn.Module` subclasses that wire up the building blocks with fixed structural choices. The caller controls all hyperparameters through a typed config — nothing is hardcoded.
|
|
4
|
+
|
|
5
|
+
For custom wiring, use the building blocks directly.
|
|
6
|
+
|
|
7
|
+
## Class hierarchy
|
|
8
|
+
|
|
9
|
+
Each preset is split into two levels:
|
|
10
|
+
|
|
11
|
+
- **`*Base[C]`** — abstract generic class parameterised over any config type `C`. Defines `__init__` (wires the stack) and `forward`, declares `build_layers` and `build_norm` as abstract. No assumptions about `C`.
|
|
12
|
+
- **`*`** (concrete) — extends `*Base[ConcreteConfig]`, fills in all `build_*` defaults, and exposes additional overridable hooks (`build_pos_encoding`, `build_attn_bias`, `build_ff`, …).
|
|
13
|
+
|
|
14
|
+
## Presets
|
|
15
|
+
|
|
16
|
+
**`TransformerEncoder`** — self-attn → ff per layer. Set `causal=True` in `attn` for GPT-style autoregressive decoding. Set `window_size` for sliding-window (O(n·w)) attention.
|
|
17
|
+
|
|
18
|
+
**`TransformerDecoder`** — causal self-attn → cross-attn → ff per layer. Cross-attention always uses no positional encoding.
|
|
19
|
+
|
|
20
|
+
**`CrossAttender`** — cross-attn → ff per layer. `x` is a fixed set of queries with no self-attention. Useful for Perceiver-style or slot-attention architectures.
|
|
21
|
+
|
|
22
|
+
All presets accept `PaddedInput` or `PackedInput` interchangeably.
|
|
23
|
+
|
|
24
|
+
## Customising a collaborator (subclass the concrete preset)
|
|
25
|
+
|
|
26
|
+
Override one or more `build_*` methods; the rest use their defaults:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
class ALiBiEncoder(TransformerEncoder):
|
|
30
|
+
def build_attn_bias(self, _: TransformerEncoderConfig) -> AttnBias:
|
|
31
|
+
return ALiBi(self.config.attn.heads)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Using a fully custom config (subclass the abstract base)
|
|
35
|
+
|
|
36
|
+
Implement `build_layers` and `build_norm`; the base `__init__` calls them and nothing else:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
class MyConfig(BaseModel):
|
|
40
|
+
dim: int
|
|
41
|
+
heads: int
|
|
42
|
+
num_layers: int
|
|
43
|
+
|
|
44
|
+
class MyEncoder(TransformerEncoderBase[MyConfig]):
|
|
45
|
+
def build_layers(self, config: MyConfig) -> list[TransformerLayer]:
|
|
46
|
+
pos = MyRoPE(config.dim // config.heads)
|
|
47
|
+
return [
|
|
48
|
+
TransformerLayer(
|
|
49
|
+
self_attn=SelfAttention(
|
|
50
|
+
SelfAttentionConfig(dim=config.dim, heads=config.heads,
|
|
51
|
+
dim_head=config.dim // config.heads),
|
|
52
|
+
pos_encoding=pos,
|
|
53
|
+
),
|
|
54
|
+
ff=SwiGLU(SwiGLUConfig(dim=config.dim, mult=4)),
|
|
55
|
+
norm_attn=nn.RMSNorm(config.dim),
|
|
56
|
+
norm_ff=nn.RMSNorm(config.dim),
|
|
57
|
+
)
|
|
58
|
+
for _ in range(config.num_layers)
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
def build_norm(self, config: MyConfig) -> Norm:
|
|
62
|
+
return nn.RMSNorm(config.dim)
|
|
63
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from stackformers.feedforward.factory import build_ff
|
|
2
|
+
from stackformers.norm.factory import NormConfig, build_norm
|
|
3
|
+
from stackformers.positional.factory import build_pos_encoding
|
|
4
|
+
from stackformers.presets.cross_attender import (
|
|
5
|
+
CrossAttender,
|
|
6
|
+
CrossAttenderConfig,
|
|
7
|
+
plain_cross_attender_config,
|
|
8
|
+
)
|
|
9
|
+
from stackformers.presets.decoder import (
|
|
10
|
+
TransformerDecoder,
|
|
11
|
+
TransformerDecoderConfig,
|
|
12
|
+
plain_decoder_config,
|
|
13
|
+
)
|
|
14
|
+
from stackformers.presets.encoder import (
|
|
15
|
+
TransformerEncoder,
|
|
16
|
+
TransformerEncoderConfig,
|
|
17
|
+
plain_encoder_config,
|
|
18
|
+
windowed_encoder_config,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# builders
|
|
23
|
+
"NormConfig",
|
|
24
|
+
"build_norm",
|
|
25
|
+
"build_ff",
|
|
26
|
+
"build_pos_encoding",
|
|
27
|
+
# preset config factories
|
|
28
|
+
"plain_encoder_config",
|
|
29
|
+
"windowed_encoder_config",
|
|
30
|
+
"plain_decoder_config",
|
|
31
|
+
"plain_cross_attender_config",
|
|
32
|
+
# presets
|
|
33
|
+
"TransformerEncoderConfig",
|
|
34
|
+
"TransformerEncoder",
|
|
35
|
+
"TransformerDecoderConfig",
|
|
36
|
+
"TransformerDecoder",
|
|
37
|
+
"CrossAttenderConfig",
|
|
38
|
+
"CrossAttender",
|
|
39
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
import torch.nn as nn
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
from torch import Tensor
|
|
9
|
+
|
|
10
|
+
from stackformers.attention.config import CrossAttentionConfig
|
|
11
|
+
from stackformers.attention.cross_attn import CrossAttention
|
|
12
|
+
from stackformers.cross_attender import CrossAttenderLayer, CrossAttenderStack
|
|
13
|
+
from stackformers.feedforward.config import FeedForwardConfig, SwiGLUConfig
|
|
14
|
+
from stackformers.feedforward.factory import build_ff
|
|
15
|
+
from stackformers.feedforward.protocols import FeedForward
|
|
16
|
+
from stackformers.norm.config import RMSNormConfig
|
|
17
|
+
from stackformers.norm.factory import NormConfig, build_norm
|
|
18
|
+
from stackformers.norm.protocols import Norm
|
|
19
|
+
from stackformers.positional.config import NoPosEncodingConfig, PosEncodingConfig
|
|
20
|
+
from stackformers.positional.factory import build_pos_encoding
|
|
21
|
+
from stackformers.positional.protocols import PosEncoding
|
|
22
|
+
from stackformers.sequence import SequenceInput
|
|
23
|
+
|
|
24
|
+
C = TypeVar("C")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CrossAttenderBase(nn.Module, Generic[C], ABC):
|
|
28
|
+
"""Abstract cross-attender: queries from x attend to context, no self-attention.
|
|
29
|
+
|
|
30
|
+
Subclass with any config type C. Implement build_layers and build_norm;
|
|
31
|
+
__init__ wires them into a CrossAttenderStack and nothing else.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, config: C) -> None:
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.config = config
|
|
37
|
+
self._stack = CrossAttenderStack(
|
|
38
|
+
layers=self.build_layers(config),
|
|
39
|
+
final_norm=self.build_norm(config),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def build_layers(self, config: C) -> list[CrossAttenderLayer]: ...
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def build_norm(self, config: C) -> Norm: ...
|
|
47
|
+
|
|
48
|
+
def forward(self, x_input: SequenceInput, ctx_input: SequenceInput) -> Tensor:
|
|
49
|
+
return self._stack(x_input, ctx_input)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CrossAttenderConfig(BaseModel):
|
|
53
|
+
attn: CrossAttentionConfig
|
|
54
|
+
ff: FeedForwardConfig
|
|
55
|
+
norm: NormConfig
|
|
56
|
+
pos_encoding: PosEncodingConfig = NoPosEncodingConfig()
|
|
57
|
+
num_layers: int = Field(gt=0)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def plain_cross_attender_config(
|
|
61
|
+
dim: int,
|
|
62
|
+
heads: int,
|
|
63
|
+
num_layers: int,
|
|
64
|
+
*,
|
|
65
|
+
ff_mult: float = 4.0,
|
|
66
|
+
dropout: float = 0.0,
|
|
67
|
+
) -> CrossAttenderConfig:
|
|
68
|
+
"""Global SDPA cross-attender with RMSNorm and SwiGLU FF, no positional encoding.
|
|
69
|
+
|
|
70
|
+
Pass PaddedInput for inference, PackedInput for training — same model.
|
|
71
|
+
"""
|
|
72
|
+
dim_head = dim // heads
|
|
73
|
+
return CrossAttenderConfig(
|
|
74
|
+
attn=CrossAttentionConfig(dim=dim, heads=heads, dim_head=dim_head, dropout=dropout),
|
|
75
|
+
ff=SwiGLUConfig(dim=dim, mult=ff_mult, dropout=dropout),
|
|
76
|
+
norm=RMSNormConfig(dim=dim),
|
|
77
|
+
num_layers=num_layers,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CrossAttender(CrossAttenderBase[CrossAttenderConfig]):
|
|
82
|
+
"""Concrete cross-attender for CrossAttenderConfig.
|
|
83
|
+
|
|
84
|
+
Pass PaddedInput for inference, PackedInput for training.
|
|
85
|
+
Override build_pos_encoding, build_ff, or build_norm to customise individual
|
|
86
|
+
collaborators while keeping the rest of the defaults.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def build_layers(self, config: CrossAttenderConfig) -> list[CrossAttenderLayer]:
|
|
90
|
+
pos = self.build_pos_encoding(config)
|
|
91
|
+
return [
|
|
92
|
+
CrossAttenderLayer(
|
|
93
|
+
cross_attn=CrossAttention(config=config.attn, pos_encoding=pos),
|
|
94
|
+
ff=self.build_ff(config),
|
|
95
|
+
norm_cross=build_norm(config.norm),
|
|
96
|
+
norm_ff=build_norm(config.norm),
|
|
97
|
+
)
|
|
98
|
+
for _ in range(config.num_layers)
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def build_pos_encoding(self, config: CrossAttenderConfig) -> PosEncoding:
|
|
102
|
+
return build_pos_encoding(config.pos_encoding)
|
|
103
|
+
|
|
104
|
+
def build_ff(self, config: CrossAttenderConfig) -> FeedForward:
|
|
105
|
+
return build_ff(config.ff)
|
|
106
|
+
|
|
107
|
+
def build_norm(self, config: CrossAttenderConfig) -> Norm:
|
|
108
|
+
return build_norm(config.norm)
|