Phase-Technologies commited on
Commit
da50d36
Β·
verified Β·
1 Parent(s): 84a19b7

Add model architecture component: pscan.py

Browse files
Files changed (1) hide show
  1. model/pscan.py +181 -0
model/pscan.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Parallel Scan (Blelloch algorithm) in pure PyTorch for ARG selective SSM recurrence.
3
+
4
+ Eliminates all external dependencies (mamba-ssm, selective_scan_cuda).
5
+ Runs in 2*log2(T) parallel steps (11 steps for T=2048) on any GPU.
6
+ 100% mathematically exact equivalence to sequential SSM recurrence.
7
+ """
8
+
9
+ from __future__ import annotations
10
+ import math
11
+ import torch
12
+ import torch.nn.functional as F
13
+
14
+
15
+ def npo2(length: int) -> int:
16
+ """Returns the next power of 2 above length."""
17
+ return 2 ** math.ceil(math.log2(length))
18
+
19
+
20
+ def pad_npo2(x: torch.Tensor) -> torch.Tensor:
21
+ """Pads input length dimension (dim 1) to the next power of 2."""
22
+ length = x.size(1)
23
+ len_p2 = npo2(length)
24
+ if len_p2 == length:
25
+ return x
26
+ pad_tuple = (0, 0, 0, 0, 0, len_p2 - length)
27
+ return F.pad(x, pad_tuple, "constant", 0)
28
+
29
+
30
+ class PScan(torch.autograd.Function):
31
+ """
32
+ Computes the parallel associative scan:
33
+ H[t] = A[t] * H[t-1] + X[t] with H[-1] = 0
34
+ in O(log2(T)) parallel steps via Blelloch tree reduction.
35
+ """
36
+
37
+ @staticmethod
38
+ def pscan(A: torch.Tensor, X: torch.Tensor) -> None:
39
+ # A: (B, D, L, N)
40
+ # X: (B, D, L, N)
41
+ B, D, L, _ = A.size()
42
+ num_steps = int(math.log2(L))
43
+
44
+ # ── Up sweep ──────────────────────────────────────────────────────────
45
+ Aa = A
46
+ Xa = X
47
+ for _ in range(num_steps - 2):
48
+ T = Xa.size(2)
49
+ Aa = Aa.view(B, D, T // 2, 2, -1)
50
+ Xa = Xa.view(B, D, T // 2, 2, -1)
51
+
52
+ Xa[:, :, :, 1].add_(Aa[:, :, :, 1].mul(Xa[:, :, :, 0]))
53
+ Aa[:, :, :, 1].mul_(Aa[:, :, :, 0])
54
+
55
+ Aa = Aa[:, :, :, 1]
56
+ Xa = Xa[:, :, :, 1]
57
+
58
+ if Xa.size(2) == 4:
59
+ Xa[:, :, 1].add_(Aa[:, :, 1].mul(Xa[:, :, 0]))
60
+ Aa[:, :, 1].mul_(Aa[:, :, 0])
61
+ Xa[:, :, 3].add_(Aa[:, :, 3].mul(Xa[:, :, 2] + Aa[:, :, 2].mul(Xa[:, :, 1])))
62
+ elif Xa.size(2) == 2:
63
+ Xa[:, :, 1].add_(Aa[:, :, 1].mul(Xa[:, :, 0]))
64
+ return
65
+ else:
66
+ return
67
+
68
+ # ── Down sweep ────────────────────────────────────────────────────────
69
+ Aa = A[:, :, 2 ** (num_steps - 2) - 1 : L : 2 ** (num_steps - 2)]
70
+ Xa = X[:, :, 2 ** (num_steps - 2) - 1 : L : 2 ** (num_steps - 2)]
71
+ Xa[:, :, 2].add_(Aa[:, :, 2].mul(Xa[:, :, 1]))
72
+ Aa[:, :, 2].mul_(Aa[:, :, 1])
73
+
74
+ for k in range(num_steps - 3, -1, -1):
75
+ stride = 2 ** k
76
+ Aa = A[:, :, stride - 1 : L : stride]
77
+ Xa = X[:, :, stride - 1 : L : stride]
78
+
79
+ T = Xa.size(2)
80
+ Aa = Aa.view(B, D, T // 2, 2, -1)
81
+ Xa = Xa.view(B, D, T // 2, 2, -1)
82
+
83
+ Xa[:, :, 1:, 0].add_(Aa[:, :, 1:, 0].mul(Xa[:, :, :-1, 1]))
84
+ Aa[:, :, 1:, 0].mul_(Aa[:, :, :-1, 1])
85
+
86
+ @staticmethod
87
+ def pscan_rev(A: torch.Tensor, X: torch.Tensor) -> None:
88
+ # Backward scan in reverse time order
89
+ B, D, L, _ = A.size()
90
+ num_steps = int(math.log2(L))
91
+
92
+ # Up sweep
93
+ Aa = A
94
+ Xa = X
95
+ for _ in range(num_steps - 2):
96
+ T = Xa.size(2)
97
+ Aa = Aa.view(B, D, T // 2, 2, -1)
98
+ Xa = Xa.view(B, D, T // 2, 2, -1)
99
+
100
+ Xa[:, :, :, 0].add_(Aa[:, :, :, 0].mul(Xa[:, :, :, 1]))
101
+ Aa[:, :, :, 0].mul_(Aa[:, :, :, 1])
102
+
103
+ Aa = Aa[:, :, :, 0]
104
+ Xa = Xa[:, :, :, 0]
105
+
106
+ if Xa.size(2) == 4:
107
+ Xa[:, :, 2].add_(Aa[:, :, 2].mul(Xa[:, :, 3]))
108
+ Aa[:, :, 2].mul_(Aa[:, :, 3])
109
+ Xa[:, :, 0].add_(Aa[:, :, 0].mul(Xa[:, :, 1].add(Aa[:, :, 1].mul(Xa[:, :, 2]))))
110
+ elif Xa.size(2) == 2:
111
+ Xa[:, :, 0].add_(Aa[:, :, 0].mul(Xa[:, :, 1]))
112
+ return
113
+ else:
114
+ return
115
+
116
+ # Down sweep
117
+ stride_top = 2 ** (num_steps - 2)
118
+ Aa = A[:, :, 0 : L : stride_top]
119
+ Xa = X[:, :, 0 : L : stride_top]
120
+ Xa[:, :, 1].add_(Aa[:, :, 1].mul(Xa[:, :, 2]))
121
+ Aa[:, :, 1].mul_(Aa[:, :, 2])
122
+
123
+ for k in range(num_steps - 3, -1, -1):
124
+ stride = 2 ** k
125
+ Aa = A[:, :, 0 : L : stride]
126
+ Xa = X[:, :, 0 : L : stride]
127
+
128
+ T = Xa.size(2)
129
+ Aa = Aa.view(B, D, T // 2, 2, -1)
130
+ Xa = Xa.view(B, D, T // 2, 2, -1)
131
+
132
+ Xa[:, :, :-1, 1].add_(Aa[:, :, :-1, 1].mul(Xa[:, :, 1:, 0]))
133
+ Aa[:, :, :-1, 1].mul_(Aa[:, :, 1:, 0])
134
+
135
+ @staticmethod
136
+ def forward(ctx, A_in: torch.Tensor, X_in: torch.Tensor) -> torch.Tensor:
137
+ # A_in: (B, L, D, N)
138
+ # X_in: (B, L, D, N)
139
+ L = X_in.size(1)
140
+
141
+ if L == npo2(L):
142
+ A = A_in.clone()
143
+ X = X_in.clone()
144
+ else:
145
+ A = pad_npo2(A_in).clone()
146
+ X = pad_npo2(X_in).clone()
147
+
148
+ A = A.transpose(2, 1).contiguous() # (B, D, L_p2, N)
149
+ X = X.transpose(2, 1).contiguous() # (B, D, L_p2, N)
150
+
151
+ PScan.pscan(A, X)
152
+
153
+ ctx.save_for_backward(A_in, X)
154
+ return X.transpose(2, 1)[:, :L].contiguous()
155
+
156
+ @staticmethod
157
+ def backward(ctx, grad_output_in: torch.Tensor):
158
+ A_in, X = ctx.saved_tensors
159
+ L = grad_output_in.size(1)
160
+
161
+ if L == npo2(L):
162
+ grad_output = grad_output_in.clone()
163
+ else:
164
+ grad_output = pad_npo2(grad_output_in).clone()
165
+ A_in = pad_npo2(A_in)
166
+
167
+ grad_output = grad_output.transpose(2, 1).contiguous() # (B, D, L_p2, N)
168
+ A_in_t = A_in.transpose(2, 1).contiguous() # (B, D, L_p2, N)
169
+
170
+ # Shift A 1 step to the left for adjoint recurrence
171
+ A_shifted = F.pad(A_in_t[:, :, 1:], (0, 0, 0, 1))
172
+
173
+ PScan.pscan_rev(A_shifted, grad_output)
174
+
175
+ grad_A = torch.zeros_like(X)
176
+ grad_A[:, :, 1:].add_(X[:, :, :-1] * grad_output[:, :, 1:])
177
+
178
+ return grad_A.transpose(2, 1)[:, :L].contiguous(), grad_output.transpose(2, 1)[:, :L].contiguous()
179
+
180
+
181
+ pscan = PScan.apply