FlameF0X commited on
Commit
dd08deb
Β·
verified Β·
1 Parent(s): a0d112e

Upload 2 files

Browse files
Files changed (2) hide show
  1. configuration_fwkv.py +44 -0
  2. modeling_fwkv.py +345 -0
configuration_fwkv.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+
4
+ class FWKVConfig(PretrainedConfig):
5
+ model_type = "fwkv"
6
+
7
+ def __init__(
8
+ self,
9
+ vocab_size=50257,
10
+ d_model=768,
11
+ d_emb=384,
12
+ n_layers=13,
13
+ ffn_mult=4,
14
+ head_size=64,
15
+ wkv_floor=0.1,
16
+ wkv_ceil=0.999,
17
+ tau_min=2.0,
18
+ tau_max=1000.0,
19
+ seq_len=1024,
20
+ ce_chunk=512,
21
+ arch_version=2,
22
+ tie_word_embeddings=True,
23
+ **kwargs,
24
+ ):
25
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
26
+ self.vocab_size = vocab_size
27
+ self.d_model = d_model
28
+ self.d_emb = d_emb
29
+ self.n_layers = n_layers
30
+ self.ffn_mult = ffn_mult
31
+ self.head_size = head_size
32
+ self.wkv_floor = wkv_floor
33
+ self.wkv_ceil = wkv_ceil
34
+ self.tau_min = tau_min
35
+ self.tau_max = tau_max
36
+ self.seq_len = seq_len
37
+ self.ce_chunk = ce_chunk
38
+ self.arch_version = arch_version
39
+
40
+ # ─── Standard HF aliases ───
41
+ self.num_hidden_layers = n_layers
42
+ self.hidden_size = d_model
43
+ self.num_attention_heads = 1 # dummy, not used by FWKV
44
+ self.num_key_value_heads = 1 # dummy
modeling_fwkv.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modeling_fwkv.py
2
+ import math
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ from transformers import PreTrainedModel, AutoModel, AutoConfig
8
+ from transformers.modeling_outputs import CausalLMOutputWithPast
9
+ from transformers.generation import GenerationMixin
10
+ from transformers.cache_utils import Cache # needed to detect Cache objects
11
+ from .configuration_fwkv import FWKVConfig
12
+
13
+
14
+ # ════════════════════════════════════════════════════════════════════════════
15
+ # utility functions
16
+ # ════════════════════════════════════════════════════════════════════════════
17
+ def parallel_scan_decay(a: torch.Tensor, W: torch.Tensor) -> torch.Tensor:
18
+ """REFERENCE implementation (Hillis-Steele). y_t = a_t + W * y_{t-1}.
19
+ Kept for parity/testing with decay_scan; not used at inference time."""
20
+ W = W.to(dtype=a.dtype)
21
+ val = a
22
+ T = a.shape[1]
23
+ d = 1
24
+ while d < T:
25
+ shifted = F.pad(val[:, :-d, :], (0, 0, d, 0))
26
+ val = val + (W ** d) * shifted
27
+ d *= 2
28
+ return val
29
+
30
+
31
+ def decay_scan(a: torch.Tensor, W: torch.Tensor, state: torch.Tensor | None = None) -> torch.Tensor:
32
+ """y_t = a_t + W * y_{t-1}, y_{-1} = state, as a causal FFT convolution in fp32.
33
+
34
+ a: (B, T, d), W: (d,) with 0 < W < 1, state: (B, d) or None.
35
+ Matches the training-time implementation exactly (same math as
36
+ parallel_scan_decay, but computed as an FFT convolution for speed).
37
+ """
38
+ with torch.autocast(device_type=a.device.type, enabled=False):
39
+ a = a.float()
40
+ W = W.float()
41
+ B, T, d = a.shape
42
+ if state is not None:
43
+ first = a[:, :1] + W.view(1, 1, d) * state.float().unsqueeze(1)
44
+ a = torch.cat([first, a[:, 1:]], dim=1)
45
+ if T == 1:
46
+ return a
47
+ n = 1 << (2 * T - 1).bit_length() # >= 2T-1, avoids circular wrap
48
+ t = torch.arange(T, device=a.device, dtype=a.dtype)
49
+ K = torch.exp(t.unsqueeze(1) * torch.log(W).unsqueeze(0)) # (T, d) = W^t
50
+ Fa = torch.fft.rfft(a, n=n, dim=1)
51
+ Fk = torch.fft.rfft(K, n=n, dim=0)
52
+ y = torch.fft.irfft(Fa * Fk.unsqueeze(0), n=n, dim=1)[:, :T]
53
+ return y.contiguous()
54
+
55
+
56
+ def chunked_cross_entropy(x, weight, labels, chunk_size=512, ignore_index=-100):
57
+ x_2d = x.reshape(-1, x.size(-1))
58
+ labels_1d = labels.reshape(-1)
59
+ N = x_2d.size(0)
60
+ total_loss = x_2d.new_zeros((), dtype=torch.float32)
61
+ total_count = labels_1d.new_zeros(())
62
+ for start in range(0, N, chunk_size):
63
+ end = min(start + chunk_size, N)
64
+ label_chunk = labels_1d[start:end]
65
+ count = (label_chunk != ignore_index).sum()
66
+ if count == 0:
67
+ continue
68
+ logits_chunk = F.linear(x_2d[start:end], weight)
69
+ loss_chunk = F.cross_entropy(logits_chunk.float(), label_chunk, ignore_index=ignore_index, reduction="sum")
70
+ total_loss = total_loss + loss_chunk
71
+ total_count = total_count + count
72
+ return total_loss / total_count.clamp(min=1)
73
+
74
+
75
+ # ════════════════════════════════════════════════════════════════════════════
76
+ # modules β€” mirror the trained Myosotis v2 / FWKVBlock exactly
77
+ # ════════════════════════════════════════════════════════════════════════════
78
+ class FactorizedTiedHead(nn.Module):
79
+ def __init__(self, vocab_size, d_model, d_emb):
80
+ super().__init__()
81
+ self.d_model = d_model
82
+ self.d_emb = d_emb
83
+ self.weight = nn.Parameter(torch.empty(vocab_size, d_emb))
84
+ self.proj = nn.Linear(d_emb, d_model, bias=False)
85
+ nn.init.normal_(self.weight, std=0.02)
86
+ nn.init.normal_(self.proj.weight, std=0.02)
87
+
88
+ def embed(self, input_ids):
89
+ return self.proj(F.embedding(input_ids, self.weight))
90
+
91
+ # kept as an alias so either calling convention works
92
+ def forward(self, input_ids):
93
+ return self.embed(input_ids)
94
+
95
+ def to_emb_space(self, x):
96
+ return F.linear(x, self.proj.weight.t())
97
+
98
+ def logits(self, x_emb):
99
+ return F.linear(x_emb, self.weight)
100
+
101
+ def resize_vocab(self, new_vocab_size):
102
+ old = self.weight.shape[0]
103
+ if new_vocab_size == old:
104
+ return
105
+ new_w = torch.empty(new_vocab_size, self.d_emb, device=self.weight.device, dtype=self.weight.dtype)
106
+ nn.init.normal_(new_w, std=0.02)
107
+ new_w[:old] = self.weight.data
108
+ self.weight = nn.Parameter(new_w)
109
+
110
+
111
+ class FWKVBlock(nn.Module):
112
+ """Pre-norm block: token-shifted WKV mixer + token-shifted FFN.
113
+
114
+ Per-layer recurrent state = (wkv_state, shift_state_timemix, shift_state_chanmix),
115
+ each (B, d). Must match the training-time block bit-for-bit (module names and
116
+ math), since checkpoints are loaded by state_dict key.
117
+ """
118
+
119
+ def __init__(self, d, layer_id, n_layers, ffn_mult=4, floor=0.1, ceil=0.999,
120
+ head_size=64, tau_min=2.0, tau_max=1000.0):
121
+ super().__init__()
122
+ assert d % head_size == 0, "d_model must be divisible by head_size"
123
+ self.d = d
124
+ self.floor = floor
125
+ self.ceil = ceil
126
+
127
+ self.ln1 = nn.LayerNorm(d)
128
+ self.ln2 = nn.LayerNorm(d)
129
+ self.proj_k = nn.Linear(d, d, bias=False)
130
+ self.proj_v = nn.Linear(d, d, bias=False)
131
+ self.proj_r = nn.Linear(d, d, bias=False)
132
+ self.proj_out = nn.Linear(d, d, bias=False)
133
+ self.proj_out._is_out_proj = True
134
+ self.ln_x = nn.GroupNorm(d // head_size, d) # per-head norm on mixer output
135
+ self.ffn = nn.Sequential(
136
+ nn.Linear(d, ffn_mult * d, bias=False),
137
+ nn.GELU(),
138
+ nn.Linear(ffn_mult * d, d, bias=False),
139
+ )
140
+ self.ffn[2]._is_out_proj = True
141
+
142
+ # Decay: channels span timescales tau in [tau_min, tau_max] (W = 1 - 1/tau).
143
+ tau = torch.exp(torch.linspace(math.log(tau_min), math.log(tau_max), d))
144
+ W0 = (1.0 - 1.0 / tau).clamp(floor + 1e-3, ceil - 1e-4)
145
+ p = (W0 - floor) / (ceil - floor)
146
+ self.w = nn.Parameter(torch.log(p / (1.0 - p)))
147
+
148
+ # Token-shift mix coefficients (RWKV-4 style init).
149
+ ratio_1_to_almost0 = 1.0 - (layer_id / n_layers)
150
+ ratio_0_to_1 = layer_id / max(1, n_layers - 1)
151
+ ramp = torch.arange(d, dtype=torch.float32) / d
152
+ self.mu_k = nn.Parameter(ramp ** ratio_1_to_almost0)
153
+ self.mu_v = nn.Parameter(ramp ** ratio_1_to_almost0 + 0.3 * ratio_0_to_1)
154
+ self.mu_r = nn.Parameter(ramp ** (0.5 * ratio_1_to_almost0))
155
+ self.mu_c = nn.Parameter(ramp ** ratio_1_to_almost0)
156
+
157
+ @property
158
+ def W(self):
159
+ return self.floor + (self.ceil - self.floor) * torch.sigmoid(self.w)
160
+
161
+ def forward(self, x, state=None):
162
+ B, T, d = x.shape
163
+ if state is None:
164
+ wkv_s = None
165
+ sh_tm = x.new_zeros(B, d)
166
+ sh_cm = x.new_zeros(B, d)
167
+ else:
168
+ wkv_s, sh_tm, sh_cm = state
169
+
170
+ # ── time mix (WKV) ────────────────────────────────────────────────
171
+ h = self.ln1(x)
172
+ hp = torch.cat([sh_tm.unsqueeze(1).to(h.dtype), h[:, :-1]], dim=1) # x_{t-1}
173
+ xk = h * self.mu_k + hp * (1 - self.mu_k)
174
+ xv = h * self.mu_v + hp * (1 - self.mu_v)
175
+ xr = h * self.mu_r + hp * (1 - self.mu_r)
176
+ k = self.proj_k(xk)
177
+ v = self.proj_v(xv)
178
+ r = torch.sigmoid(self.proj_r(xr))
179
+
180
+ W = self.W
181
+ gain = torch.sqrt(1.0 - W * W) # unit stationary variance per channel
182
+ a = k.float() * v.float() * gain
183
+ y = decay_scan(a, W, wkv_s) # (B, T, d) fp32
184
+ new_wkv = y[:, -1].detach()
185
+ yn = self.ln_x(y.reshape(B * T, d)).reshape(B, T, d)
186
+ x = x + self.proj_out(r * yn.to(r.dtype))
187
+
188
+ # ── channel mix (FFN) ────────────────────────────────────────────
189
+ h2 = self.ln2(x)
190
+ h2p = torch.cat([sh_cm.unsqueeze(1).to(h2.dtype), h2[:, :-1]], dim=1)
191
+ xc = h2 * self.mu_c + h2p * (1 - self.mu_c)
192
+ x = x + self.ffn(xc)
193
+
194
+ new_state = (new_wkv, h[:, -1].detach(), h2[:, -1].detach())
195
+ return x, new_state
196
+
197
+
198
+ # ════════════════════════════════════════════════════════════════════════════
199
+ # HF wrapper
200
+ # ════════════════════════════════════════════════════════════════════════════
201
+ class FWKVPreTrainedModel(PreTrainedModel):
202
+ config_class = FWKVConfig
203
+ base_model_prefix = "model"
204
+ supports_gradient_checkpointing = False
205
+
206
+ def _init_weights(self, module):
207
+ if isinstance(module, nn.Linear):
208
+ std = 0.02
209
+ if getattr(module, "_is_out_proj", False):
210
+ std = 0.02 / math.sqrt(2 * self.config.n_layers)
211
+ nn.init.normal_(module.weight, mean=0.0, std=std)
212
+ if module.bias is not None:
213
+ nn.init.zeros_(module.bias)
214
+ elif isinstance(module, (nn.LayerNorm, nn.GroupNorm)):
215
+ nn.init.ones_(module.weight)
216
+ nn.init.zeros_(module.bias)
217
+ elif isinstance(module, FactorizedTiedHead):
218
+ pass
219
+
220
+
221
+ class FWKVLanguageModel(FWKVPreTrainedModel, GenerationMixin):
222
+ # This model does NOT use the standard KV-Cache machinery (DynamicCache/
223
+ # StaticCache). Without this, newer transformers versions (4.45+/5.x)
224
+ # auto-wrap past_key_values in a Cache object during generate(), which this
225
+ # custom recurrent-state architecture can't consume.
226
+ _supports_cache_class = False
227
+ _supports_static_cache = False
228
+ _supports_quantized_cache = False
229
+
230
+ def __init__(self, config):
231
+ super().__init__(config)
232
+ self.config = config
233
+ self.amp_dtype = None # optionally set by a training/inference driver
234
+ self.shared = FactorizedTiedHead(config.vocab_size, config.d_model, config.d_emb)
235
+ self.ln0 = nn.LayerNorm(config.d_model)
236
+ self.blocks = nn.ModuleList([
237
+ FWKVBlock(config.d_model, i, config.n_layers, config.ffn_mult,
238
+ config.wkv_floor, config.wkv_ceil, config.head_size,
239
+ config.tau_min, config.tau_max)
240
+ for i in range(config.n_layers)
241
+ ])
242
+ self.norm = nn.LayerNorm(config.d_model)
243
+ self.post_init()
244
+
245
+ def get_input_embeddings(self):
246
+ return self.shared
247
+
248
+ def set_input_embeddings(self, value):
249
+ self.shared = value
250
+
251
+ def resize_vocab(self, new_vocab_size):
252
+ self.shared.resize_vocab(new_vocab_size)
253
+ self.config.vocab_size = new_vocab_size
254
+
255
+ def count_params(self):
256
+ return sum(p.numel() for p in self.parameters())
257
+
258
+ def forward(
259
+ self,
260
+ input_ids=None,
261
+ past_key_values=None,
262
+ attention_mask=None,
263
+ labels=None,
264
+ use_cache=True,
265
+ **kwargs
266
+ ):
267
+ if input_ids is None:
268
+ raise ValueError("input_ids must be provided")
269
+ if not isinstance(input_ids, torch.Tensor):
270
+ input_ids = torch.tensor(input_ids)
271
+ if input_ids.dtype != torch.long:
272
+ input_ids = input_ids.long()
273
+ if input_ids.dim() == 1:
274
+ input_ids = input_ids.unsqueeze(0)
275
+
276
+ x = self.ln0(self.shared.embed(input_ids))
277
+
278
+ # If transformers handed us a Cache object (DynamicCache, etc.) instead
279
+ # of our custom list/tuple of per-layer state tensors, treat it as
280
+ # "no state yet" rather than trying to iterate/index into it.
281
+ if past_key_values is None or isinstance(past_key_values, Cache):
282
+ states_in = [None] * len(self.blocks)
283
+ else:
284
+ states_in = past_key_values
285
+
286
+ states_out = []
287
+ for block, state in zip(self.blocks, states_in):
288
+ x, new_state = block(x, state)
289
+ states_out.append(new_state)
290
+
291
+ x = self.norm(x)
292
+ x_emb = self.shared.to_emb_space(x)
293
+
294
+ loss = None
295
+ logits = None
296
+ if labels is not None:
297
+ if not isinstance(labels, torch.Tensor):
298
+ labels = torch.tensor(labels)
299
+ if labels.dtype != torch.long:
300
+ labels = labels.long()
301
+ loss = chunked_cross_entropy(
302
+ x_emb, self.shared.weight, labels,
303
+ chunk_size=self.config.ce_chunk, ignore_index=-100
304
+ )
305
+ else:
306
+ logits = self.shared.logits(x_emb)
307
+
308
+ past_key_values_out = tuple(states_out) if use_cache else None
309
+ return CausalLMOutputWithPast(
310
+ loss=loss,
311
+ logits=logits,
312
+ past_key_values=past_key_values_out,
313
+ )
314
+
315
+ def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs):
316
+ if past_key_values is not None:
317
+ input_ids = input_ids[:, -1:] if input_ids.dim() > 1 else input_ids[-1:]
318
+
319
+ # Don't coerce a Cache object into a tuple β€” pass it through as-is,
320
+ # forward() knows how to treat it as "empty" state.
321
+ if past_key_values is not None and not isinstance(past_key_values, (tuple, Cache)):
322
+ past_key_values = tuple(past_key_values)
323
+
324
+ return {
325
+ "input_ids": input_ids,
326
+ "past_key_values": past_key_values,
327
+ "use_cache": True,
328
+ }
329
+
330
+ def _reorder_cache(self, past_key_values, beam_idx):
331
+ if past_key_values is None or isinstance(past_key_values, Cache):
332
+ return past_key_values
333
+ reordered = []
334
+ for state in past_key_values:
335
+ if state is None:
336
+ reordered.append(None)
337
+ continue
338
+ # each per-layer state is itself a 3-tuple (wkv_state, sh_tm, sh_cm)
339
+ reordered.append(tuple(s.index_select(0, beam_idx) for s in state))
340
+ return tuple(reordered)
341
+
342
+
343
+ # Register the model with AutoModel
344
+ AutoConfig.register("fwkv", FWKVConfig)
345
+ AutoModel.register(FWKVConfig, FWKVLanguageModel)