FlameF0X commited on
Commit
5a8cb25
·
verified ·
1 Parent(s): a8bdb80

Create modeling_fwkv.py

Browse files
Files changed (1) hide show
  1. modeling_fwkv.py +294 -0
modeling_fwkv.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FWKV-ROSA: a from-scratch RWKV-style recurrent LM (~56M params) with ROSA copy signal.
3
+ Self-contained model definition for Hugging Face `transformers`.
4
+ """
5
+ import math
6
+ from typing import Optional, Tuple, List
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+ from transformers import (
12
+ PretrainedConfig,
13
+ PreTrainedModel,
14
+ GenerationMixin,
15
+ )
16
+ from transformers.modeling_outputs import CausalLMOutputWithPast
17
+
18
+
19
+ # -------------------------------------------------------------------------
20
+ # 1. ROSA (Rapid Online Suffix Automaton)
21
+ # -------------------------------------------------------------------------
22
+ def rosa(x: List[int]) -> List[int]:
23
+ """
24
+ y[i] = x[j+1] where x[j-m:j] == x[i-m:i] for the largest match length m,
25
+ over all j < i; ties broken by largest j. Returns -1 if no prior occurrence.
26
+ """
27
+ n = len(x)
28
+ if n == 0:
29
+ return []
30
+ y = [-1] * n
31
+ s = 2 * n + 2
32
+ trans = [None] * s
33
+ link = [-1] * s
34
+ length = [0] * s
35
+ last_end = [-1] * s
36
+ trans[0] = {}
37
+ last = 0
38
+ size = 1
39
+
40
+ for i, t in enumerate(x):
41
+ cur = size
42
+ size += 1
43
+ trans[cur] = {}
44
+ length[cur] = length[last] + 1
45
+ p = last
46
+ while p != -1 and t not in trans[p]:
47
+ trans[p][t] = cur
48
+ p = link[p]
49
+ if p == -1:
50
+ link[cur] = 0
51
+ else:
52
+ q = trans[p][t]
53
+ if length[p] + 1 == length[q]:
54
+ link[cur] = q
55
+ else:
56
+ clone = size
57
+ size += 1
58
+ trans[clone] = trans[q].copy()
59
+ length[clone] = length[p] + 1
60
+ link[clone] = link[q]
61
+ last_end[clone] = last_end[q]
62
+ while p != -1 and trans[p][t] == q:
63
+ trans[p][t] = clone
64
+ p = link[p]
65
+ link[q] = clone
66
+ link[cur] = clone
67
+ last = cur
68
+
69
+ v = cur
70
+ pred = -1
71
+ while v != -1:
72
+ if length[v] > 0 and last_end[v] >= 0:
73
+ pred = x[last_end[v] + 1]
74
+ break
75
+ v = link[v]
76
+ y[i] = pred
77
+
78
+ v = last
79
+ while v != -1 and last_end[v] < i:
80
+ last_end[v] = i
81
+ v = link[v]
82
+ return y
83
+
84
+
85
+ # -------------------------------------------------------------------------
86
+ # 2. Parallel scan (exact, vectorised)
87
+ # -------------------------------------------------------------------------
88
+ def parallel_scan_decay(a: torch.Tensor, W: torch.Tensor) -> torch.Tensor:
89
+ """Hillis-Steele inclusive scan with constant per-channel decay W."""
90
+ W = W.to(dtype=a.dtype)
91
+ val = a
92
+ T = a.shape[1]
93
+ d = 1
94
+ while d < T:
95
+ shifted = F.pad(val[:, :-d, :], (0, 0, d, 0))
96
+ val = val + (W ** d) * shifted
97
+ d *= 2
98
+ return val
99
+
100
+
101
+ # -------------------------------------------------------------------------
102
+ # 3. Config
103
+ # -------------------------------------------------------------------------
104
+ class FWKVConfig(PretrainedConfig):
105
+ model_type = "fwkv"
106
+
107
+ def __init__(
108
+ self,
109
+ d_model: int = 512,
110
+ d_emb: int = 128,
111
+ n_layers: int = 14,
112
+ ffn_mult: int = 4,
113
+ vocab_size: int = 50257,
114
+ seq_len: int = 1024,
115
+ wkv_floor: float = 0.1,
116
+ tie_word_embeddings: bool = True,
117
+ **kwargs,
118
+ ):
119
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
120
+ self.d_model = d_model
121
+ self.d_emb = d_emb
122
+ self.n_layers = n_layers
123
+ self.ffn_mult = ffn_mult
124
+ self.vocab_size = vocab_size
125
+ self.seq_len = seq_len
126
+ self.wkv_floor = wkv_floor
127
+
128
+
129
+ # -------------------------------------------------------------------------
130
+ # 4. Factorised tied embedding/head
131
+ # -------------------------------------------------------------------------
132
+ class FactorizedTiedHead(nn.Module):
133
+ def __init__(self, vocab_size: int, d_model: int, d_emb: int):
134
+ super().__init__()
135
+ self.d_model = d_model
136
+ self.d_emb = d_emb
137
+ self.weight = nn.Parameter(torch.empty(vocab_size, d_emb))
138
+ self.proj = nn.Linear(d_emb, d_model, bias=False)
139
+
140
+ def embed(self, input_ids):
141
+ return self.proj(F.embedding(input_ids, self.weight))
142
+
143
+ def to_emb_space(self, x):
144
+ return F.linear(x, self.proj.weight.t())
145
+
146
+ def logits(self, x_emb):
147
+ return F.linear(x_emb, self.weight)
148
+
149
+
150
+ # -------------------------------------------------------------------------
151
+ # 5. FWKV Block
152
+ # -------------------------------------------------------------------------
153
+ class FWKVBlock(nn.Module):
154
+ def __init__(self, d: int, ffn_mult: int = 4, floor: float = 0.1):
155
+ super().__init__()
156
+ self.floor = floor
157
+ self.proj_k = nn.Linear(d, d, bias=False)
158
+ self.proj_v = nn.Linear(d, d, bias=False)
159
+ self.proj_r = nn.Linear(d, d, bias=False)
160
+ self.proj_out = nn.Linear(d, d, bias=False)
161
+ self.w = nn.Parameter(torch.ones(d) * 2.0)
162
+ self.ffn = nn.Sequential(
163
+ nn.Linear(d, ffn_mult * d, bias=False),
164
+ nn.GELU(),
165
+ nn.Linear(ffn_mult * d, d, bias=False),
166
+ )
167
+ self.norm_wkv = nn.LayerNorm(d)
168
+ self.norm_ffn = nn.LayerNorm(d)
169
+
170
+ @property
171
+ def W(self):
172
+ return torch.clamp(torch.sigmoid(self.w), min=self.floor)
173
+
174
+ def forward(self, x, state=None):
175
+ B, T, d = x.shape
176
+ W = self.W
177
+ k = self.proj_k(x)
178
+ v = self.proj_v(x)
179
+ r = torch.sigmoid(self.proj_r(x))
180
+
181
+ a = k * v
182
+ if state is not None:
183
+ a = a.clone()
184
+ a[:, 0] = a[:, 0] + W * state
185
+
186
+ wkv_out = parallel_scan_decay(a, W)
187
+ new_state = wkv_out[:, -1].detach()
188
+
189
+ x = self.norm_wkv(x + self.proj_out(r * wkv_out))
190
+ x = self.norm_ffn(x + self.ffn(x))
191
+ return x, new_state
192
+
193
+
194
+ # -------------------------------------------------------------------------
195
+ # 6. Full Language Model
196
+ # -------------------------------------------------------------------------
197
+ class FWKVLanguageModel(PreTrainedModel, GenerationMixin):
198
+ config_class = FWKVConfig
199
+ base_model_prefix = "fwkv"
200
+ supports_gradient_checkpointing = True
201
+
202
+ def __init__(self, config: FWKVConfig):
203
+ super().__init__(config)
204
+ self.shared = FactorizedTiedHead(config.vocab_size, config.d_model, config.d_emb)
205
+ self.rosa_emb = nn.Embedding(config.vocab_size + 1, config.d_emb, padding_idx=0)
206
+ self.blocks = nn.ModuleList([
207
+ FWKVBlock(config.d_model, config.ffn_mult, config.wkv_floor)
208
+ for _ in range(config.n_layers)
209
+ ])
210
+ self.norm = nn.LayerNorm(config.d_model)
211
+ self.post_init()
212
+
213
+ def get_input_embeddings(self):
214
+ return self.shared.weight
215
+
216
+ def set_input_embeddings(self, value):
217
+ self.shared.weight = nn.Parameter(value)
218
+
219
+ def forward(
220
+ self,
221
+ input_ids: torch.LongTensor,
222
+ rosa_ids: Optional[torch.LongTensor] = None,
223
+ past_key_values: Optional[List[Optional[torch.Tensor]]] = None,
224
+ labels: Optional[torch.LongTensor] = None,
225
+ use_cache: bool = True,
226
+ **kwargs,
227
+ ) -> CausalLMOutputWithPast:
228
+ # ROSA IDs: compute if not supplied
229
+ if rosa_ids is None:
230
+ rosa_ids = torch.tensor(
231
+ [rosa(row.tolist()) for row in input_ids.detach().cpu()],
232
+ device=input_ids.device, dtype=torch.long
233
+ )
234
+
235
+ x = self.shared.embed(input_ids) # [B, T, d_model]
236
+ rosa_idx = (rosa_ids + 1).clamp(min=0) # -1 -> 0
237
+ x = x + self.shared.proj(self.rosa_emb(rosa_idx)) # inject ROSA signal
238
+
239
+ # State propagation
240
+ states_in = past_key_values or [None] * len(self.blocks)
241
+ states_out = []
242
+ for block, state in zip(self.blocks, states_in):
243
+ x, new_state = block(x, state)
244
+ states_out.append(new_state)
245
+
246
+ x = self.norm(x)
247
+ x_emb = self.shared.to_emb_space(x) # [B, T, d_emb]
248
+ logits = self.shared.logits(x_emb) # [B, T, vocab]
249
+
250
+ loss = None
251
+ if labels is not None:
252
+ # Shift logits and labels for standard LM loss
253
+ shift_logits = logits[..., :-1, :].contiguous()
254
+ shift_labels = labels[..., 1:].contiguous()
255
+ loss = F.cross_entropy(
256
+ shift_logits.view(-1, shift_logits.size(-1)),
257
+ shift_labels.view(-1),
258
+ ignore_index=-100,
259
+ )
260
+
261
+ return CausalLMOutputWithPast(
262
+ loss=loss,
263
+ logits=logits,
264
+ past_key_values=states_out if use_cache else None,
265
+ )
266
+
267
+ def prepare_inputs_for_generation(
268
+ self,
269
+ input_ids: torch.LongTensor,
270
+ past_key_values: Optional[List[Optional[torch.Tensor]]] = None,
271
+ rosa_ids: Optional[torch.LongTensor] = None,
272
+ **kwargs,
273
+ ):
274
+ if past_key_values is not None:
275
+ # Only keep last token for autoregressive step
276
+ input_ids = input_ids[:, -1:]
277
+ if rosa_ids is not None:
278
+ rosa_ids = rosa_ids[:, -1:]
279
+ return {
280
+ "input_ids": input_ids,
281
+ "rosa_ids": rosa_ids,
282
+ "past_key_values": past_key_values,
283
+ "use_cache": True,
284
+ }
285
+
286
+ def _reorder_cache(self, past_key_values, beam_idx):
287
+ """Reorder past states if beam search is used (unlikely)."""
288
+ reordered_past = []
289
+ for state in past_key_values:
290
+ if state is not None:
291
+ reordered_past.append(state.index_select(0, beam_idx))
292
+ else:
293
+ reordered_past.append(None)
294
+ return reordered_past