stack-attention-pytorch 0.0.1__tar.gz → 0.0.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stack-attention-pytorch
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Implementation of Differentiable Stacks for augmenting Transformers / Attention
5
5
  Project-URL: Homepage, https://pypi.org/project/stack-attention-pytorch/
6
6
  Project-URL: Repository, https://github.com/lucidrains/stack-attention-pytorch
@@ -36,7 +36,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
36
36
  Requires-Python: >=3.10
37
37
  Requires-Dist: einops>=0.8.1
38
38
  Requires-Dist: einx>=0.4.0
39
- Requires-Dist: torch-einops-utils>=0.0.29
39
+ Requires-Dist: torch-einops-utils>=0.1.27
40
40
  Requires-Dist: torch>=2.5
41
41
  Provides-Extra: examples
42
42
  Provides-Extra: test
@@ -72,3 +72,12 @@ For following a line of research that augments attention with a differentiable s
72
72
  url = {https://arxiv.org/abs/2507.15343},
73
73
  }
74
74
  ```
75
+
76
+ ```bibtex
77
+ @inproceedings{joulin2015inferring,
78
+ author = {Armand Joulin and Tom{\'a}{\v{s}} Mikolov},
79
+ title = {Inferring Algorithmic Patterns with Stack-Augmented Recurrent Nets},
80
+ booktitle = {Advances in Neural Information Processing Systems 28 (NIPS 2015)},
81
+ year = {2015}
82
+ }
83
+ ```
@@ -27,3 +27,12 @@ For following a line of research that augments attention with a differentiable s
27
27
  url = {https://arxiv.org/abs/2507.15343},
28
28
  }
29
29
  ```
30
+
31
+ ```bibtex
32
+ @inproceedings{joulin2015inferring,
33
+ author = {Armand Joulin and Tom{\'a}{\v{s}} Mikolov},
34
+ title = {Inferring Algorithmic Patterns with Stack-Augmented Recurrent Nets},
35
+ booktitle = {Advances in Neural Information Processing Systems 28 (NIPS 2015)},
36
+ year = {2015}
37
+ }
38
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stack-attention-pytorch"
3
- version = "0.0.1"
3
+ version = "0.0.2"
4
4
  description = "Implementation of Differentiable Stacks for augmenting Transformers / Attention"
5
5
  authors = [
6
6
  { name = "Phil Wang", email = "lucidrains@gmail.com" }
@@ -27,7 +27,7 @@ dependencies = [
27
27
  "einx>=0.4.0",
28
28
  "einops>=0.8.1",
29
29
  "torch>=2.5",
30
- "torch-einops-utils>=0.0.29",
30
+ "torch-einops-utils>=0.1.27",
31
31
  ]
32
32
 
33
33
  [project.urls]
@@ -0,0 +1,2 @@
1
+
2
+ from stack_attention.stack_trans_layer import StackTransLayer
@@ -0,0 +1,199 @@
1
+ from __future__ import annotations
2
+ from functools import partial
3
+
4
+ import torch
5
+ from torch import cat, nn, Tensor
6
+ from torch.nn import Module, Linear, RMSNorm, Parameter
7
+ import torch.nn.functional as F
8
+
9
+ from torch_einops_utils import pack_with_inverse, pad_left_at_dim, pad_right_at_dim
10
+
11
+ import einx
12
+ from einops import einsum, rearrange, repeat
13
+ from einops.layers.torch import Rearrange
14
+
15
+ # ein equations
16
+
17
+ # b - batch seq
18
+ # h - heads or num stacks
19
+ # s - stack depth
20
+ # d - dim stack
21
+ # a - actions
22
+
23
+ # constants
24
+
25
+ LinearNoBias = partial(Linear, bias = False)
26
+
27
+ # helper functions
28
+
29
+ def exists(v):
30
+ return v is not None
31
+
32
+ def default(v, d):
33
+ return v if exists(v) else d
34
+
35
+ # gumbel
36
+
37
+ def log(t, eps = 1e-20):
38
+ return t.clamp_min(eps).log()
39
+
40
+ def gumbel_noise_like(t):
41
+ return -log(-log(torch.rand_like(t)))
42
+
43
+ def straight_through(src, tgt):
44
+ return src + (tgt - src).detach()
45
+
46
+ def mask_value(t):
47
+ return -torch.finfo(t.dtype).max
48
+
49
+ # classes
50
+
51
+ class StackTransLayer(Module):
52
+ # Kechi Zhang et al. https://arxiv.org/abs/2507.15343
53
+
54
+ def __init__(
55
+ self,
56
+ dim,
57
+ *,
58
+ num_stacks = 4, # heads
59
+ dim_stack = 16, # dim head
60
+ stack_size = 24,
61
+ prenorm = False
62
+ ):
63
+ super().__init__()
64
+
65
+ self.norm = RMSNorm(dim) if prenorm else nn.Identity()
66
+
67
+ # action related
68
+
69
+ num_actions = 3 # (push, pop, noop)
70
+ self.to_action_logits = LinearNoBias(dim, num_stacks * num_actions)
71
+ self.split_action_logits = Rearrange('... (num_stacks num_actions) -> ... num_stacks num_actions', num_actions = num_actions)
72
+ self.num_actions = num_actions
73
+
74
+ # to hidden per stack for maybe push
75
+
76
+ dim_inner = num_stacks * dim_stack
77
+ self.to_stack_inputs = LinearNoBias(dim, dim_inner)
78
+ self.split_num_stacks = Rearrange('... (num_stacks dim_stack) -> ... num_stacks dim_stack', num_stacks = num_stacks)
79
+
80
+ self.num_stacks = num_stacks
81
+ self.stack_size = stack_size
82
+ self.dim_stack = dim_stack
83
+
84
+ # attention based read from stack
85
+
86
+ self.to_read_stack_attn = LinearNoBias(dim_stack, 1)
87
+
88
+ self.null_stack = Parameter(torch.randn(dim_stack) * 1e-2)
89
+
90
+ # combine
91
+
92
+ self.combine = LinearNoBias(dim_inner, dim)
93
+
94
+ def forward(
95
+ self,
96
+ tokens,
97
+ stack_states: tuple[Tensor, Tensor] | None = None,
98
+ action_temperature = 1.,
99
+ stochastic_action = False,
100
+ hard_action = False
101
+ ):
102
+ assert action_temperature > 0.
103
+
104
+ orig, device = tokens, tokens.device
105
+
106
+ # maybe pre norm
107
+
108
+ tokens = self.norm(tokens)
109
+
110
+ tokens, inverse_pack = pack_with_inverse(tokens, '* d')
111
+
112
+ # dimensions
113
+
114
+ batch, num_stacks, stack_size, dim_stack = tokens.shape[0], self.num_stacks, self.stack_size, self.dim_stack
115
+
116
+ # project to stack inputs
117
+
118
+ stack_inputs = self.to_stack_inputs(tokens)
119
+ stack_inputs = self.split_num_stacks(stack_inputs) # (b h d)
120
+
121
+ # the actions per stack per token
122
+
123
+ action_logits = self.to_action_logits(tokens)
124
+ action_logits = self.split_action_logits(action_logits) # (b h 3)
125
+ action_logits = action_logits / action_temperature
126
+
127
+ if stochastic_action:
128
+ action_logits = action_logits + gumbel_noise_like(action_logits)
129
+
130
+ actions = action_logits.softmax(dim = -1)
131
+
132
+ if hard_action:
133
+ soft_actions = actions
134
+ hard_actions = F.one_hot(actions.argmax(dim = -1), self.num_actions)
135
+ actions = straight_through(soft_actions, hard_actions)
136
+
137
+ # handle initial stack state
138
+ # but the transformer could dynamically generate this initial state as well
139
+
140
+ if not exists(stack_states):
141
+ init_stack = torch.zeros((batch, num_stacks, stack_size, dim_stack), device = device)
142
+ init_stack_mask = torch.zeros((batch, num_stacks, stack_size), dtype = torch.bool, device = device)
143
+ stack_states = (init_stack, init_stack_mask)
144
+
145
+ # differentiable stack - eq 3 (applies to mask as well)
146
+
147
+ stack, stack_mask = stack_states
148
+ zeros = torch.zeros_like(stack_inputs)
149
+
150
+ stack_with_inputs, _ = pack_with_inverse((stack_inputs, stack, zeros), 'b h * d')
151
+
152
+ stack_mask_with_input = pad_left_at_dim(stack_mask, 1, value = True, dim = -1)
153
+ stack_mask_with_input = pad_right_at_dim(stack_mask_with_input, 1, value = False, dim = -1)
154
+
155
+ # superposition of all three possibilities, weighted summed by the action
156
+
157
+ pushed_stack = stack_with_inputs[..., :-2, :]
158
+ popped_stack = stack_with_inputs[..., 2:,:]
159
+ noop_stack = stack
160
+
161
+ stack_superpositions = torch.stack((pushed_stack, popped_stack, noop_stack), dim = -1)
162
+
163
+ pushed_mask = stack_mask_with_input[..., :-2]
164
+ popped_mask = stack_mask_with_input[..., 2:]
165
+ noop_mask = stack_mask
166
+
167
+ stack_mask_superpositions = torch.stack((pushed_mask, popped_mask, noop_mask), dim = -1)
168
+
169
+ next_stack = einsum(stack_superpositions, actions, 'b h s d a, b h a -> b h s d')
170
+ next_mask = einsum(stack_mask_superpositions.float(), actions.float(), 'b h s a, b h a -> b h s').bool()
171
+
172
+ next_stack_states = (next_stack, next_mask)
173
+
174
+ # read from the stacks, using their attention like method
175
+ # however, will do some adjustments as it does not look quite right
176
+
177
+ null_stack = repeat(self.null_stack, 'd -> b h 1 d', b = batch, h = num_stacks)
178
+
179
+ next_stack_with_null = cat((next_stack, null_stack), dim = -2)
180
+ next_mask_with_null = pad_right_at_dim(next_mask, 1, value = True)
181
+
182
+ read_stack_attn_logits = self.to_read_stack_attn(next_stack_with_null)
183
+ read_stack_attn_logits = rearrange(read_stack_attn_logits, '... 1 -> ...')
184
+
185
+ read_stack_attn_logits = read_stack_attn_logits.masked_fill(next_mask_with_null, mask_value(next_stack_with_null))
186
+
187
+ read_stack_attn = read_stack_attn_logits.softmax(dim = -1)
188
+
189
+ read_stack_out = einsum(next_stack_with_null, read_stack_attn, 'b h s d, b h s -> b h d')
190
+
191
+ # combine
192
+
193
+ out = rearrange(stack_inputs, 'b h d -> b (h d)')
194
+
195
+ out = self.combine(out)
196
+
197
+ out = inverse_pack(out)
198
+
199
+ return out, next_stack_states
File without changes