FlameF0X commited on
Commit
596989c
Β·
verified Β·
1 Parent(s): 3e02de2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +372 -0
app.py ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ import gradio as gr
6
+ from transformers import (
7
+ AutoTokenizer,
8
+ PretrainedConfig,
9
+ PreTrainedModel,
10
+ GenerationMixin,
11
+ )
12
+ from transformers.modeling_outputs import CausalLMOutputWithPast
13
+
14
+ # -------------------------------------------------------------------------
15
+ # Model components (must match the saved checkpoint exactly)
16
+ # -------------------------------------------------------------------------
17
+
18
+ class FWKVConfig(PretrainedConfig):
19
+ model_type = "fwkv"
20
+
21
+ def __init__(
22
+ self,
23
+ d_model: int = 512,
24
+ d_emb: int = 128,
25
+ n_layers: int = 14,
26
+ ffn_mult: int = 4,
27
+ vocab_size: int = 50257,
28
+ seq_len: int = 1024, # trained with 1024
29
+ wkv_floor: float = 0.1,
30
+ tie_word_embeddings: bool = True,
31
+ **kwargs,
32
+ ):
33
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
34
+ self.d_model = d_model
35
+ self.d_emb = d_emb
36
+ self.n_layers = n_layers
37
+ self.ffn_mult = ffn_mult
38
+ self.vocab_size = vocab_size
39
+ self.seq_len = seq_len
40
+ self.wkv_floor = wkv_floor
41
+
42
+
43
+ # ── ROSA (exact copy from training) ─────────────────────────────────────
44
+ def rosa(x: list[int]) -> list[int]:
45
+ """Causal copy‑signal predictor; returns y[i] = token after longest
46
+ repeating suffix ending at i, or -1 if none."""
47
+ n = len(x)
48
+ if n == 0:
49
+ return []
50
+ y = [-1] * n
51
+ s = 2 * n + 2
52
+ trans = [None] * s
53
+ link = [-1] * s
54
+ length = [0] * s
55
+ last_end = [-1] * s
56
+ trans[0] = {}
57
+ last = 0
58
+ size = 1
59
+
60
+ for i, t in enumerate(x):
61
+ cur = size; size += 1
62
+ trans[cur] = {}
63
+ length[cur] = length[last] + 1
64
+ p = last
65
+ while p != -1 and t not in trans[p]:
66
+ trans[p][t] = cur
67
+ p = link[p]
68
+ if p == -1:
69
+ link[cur] = 0
70
+ else:
71
+ q = trans[p][t]
72
+ if length[p] + 1 == length[q]:
73
+ link[cur] = q
74
+ else:
75
+ clone = size; size += 1
76
+ trans[clone] = trans[q].copy()
77
+ length[clone] = length[p] + 1
78
+ link[clone] = link[q]
79
+ last_end[clone] = last_end[q]
80
+ while p != -1 and trans[p][t] == q:
81
+ trans[p][t] = clone
82
+ p = link[p]
83
+ link[q] = clone
84
+ link[cur] = clone
85
+ last = cur
86
+
87
+ v = cur
88
+ pred = -1
89
+ while v != -1:
90
+ if length[v] > 0 and last_end[v] >= 0:
91
+ pred = x[last_end[v] + 1]
92
+ break
93
+ v = link[v]
94
+ y[i] = pred
95
+
96
+ v = last
97
+ while v != -1 and last_end[v] < i:
98
+ last_end[v] = i
99
+ v = link[v]
100
+ return y
101
+
102
+
103
+ # ── Vectorised parallel scan ─────────────────────────────────────────────
104
+ def parallel_scan_decay(a: torch.Tensor, W: torch.Tensor) -> torch.Tensor:
105
+ """Hillis–Steele inclusive scan with constant per‑channel decay."""
106
+ W = W.to(dtype=a.dtype) # keep precision
107
+ val = a
108
+ T = a.shape[1]
109
+ d = 1
110
+ while d < T:
111
+ shifted = F.pad(val[:, :-d, :], (0, 0, d, 0))
112
+ val = val + (W ** d) * shifted
113
+ d *= 2
114
+ return val
115
+
116
+
117
+ # ── Factorised tied head ─────────────────────────────────────────────────
118
+ class FactorizedTiedHead(nn.Module):
119
+ def __init__(self, vocab_size: int, d_model: int, d_emb: int):
120
+ super().__init__()
121
+ self.d_model = d_model
122
+ self.d_emb = d_emb
123
+ self.weight = nn.Parameter(torch.empty(vocab_size, d_emb))
124
+ self.proj = nn.Linear(d_emb, d_model, bias=False)
125
+
126
+ def embed(self, input_ids):
127
+ return self.proj(F.embedding(input_ids, self.weight))
128
+
129
+ def to_emb_space(self, x):
130
+ return F.linear(x, self.proj.weight.t())
131
+
132
+ def logits(self, x_emb):
133
+ return F.linear(x_emb, self.weight)
134
+
135
+
136
+ # ── FWKV Block (with parallel scan) ──────────────────────────────────────
137
+ class FWKVBlock(nn.Module):
138
+ def __init__(self, d: int, ffn_mult: int = 4, floor: float = 0.1):
139
+ super().__init__()
140
+ self.floor = floor
141
+ self.proj_k = nn.Linear(d, d, bias=False)
142
+ self.proj_v = nn.Linear(d, d, bias=False)
143
+ self.proj_r = nn.Linear(d, d, bias=False)
144
+ self.proj_out = nn.Linear(d, d, bias=False)
145
+ self.w = nn.Parameter(torch.ones(d) * 2.0)
146
+ self.ffn = nn.Sequential(
147
+ nn.Linear(d, ffn_mult * d, bias=False),
148
+ nn.GELU(),
149
+ nn.Linear(ffn_mult * d, d, bias=False),
150
+ )
151
+ self.norm_wkv = nn.LayerNorm(d)
152
+ self.norm_ffn = nn.LayerNorm(d)
153
+
154
+ @property
155
+ def W(self):
156
+ return torch.clamp(torch.sigmoid(self.w), min=self.floor)
157
+
158
+ def forward(self, x, state=None):
159
+ B, T, d = x.shape
160
+ W = self.W
161
+ k = self.proj_k(x)
162
+ v = self.proj_v(x)
163
+ r = torch.sigmoid(self.proj_r(x))
164
+
165
+ a = k * v
166
+ if state is not None:
167
+ a = a.clone()
168
+ a[:, 0] = a[:, 0] + W * state
169
+
170
+ wkv_out = parallel_scan_decay(a, W)
171
+ new_state = wkv_out[:, -1].detach()
172
+
173
+ x = self.norm_wkv(x + self.proj_out(r * wkv_out))
174
+ x = self.norm_ffn(x + self.ffn(x))
175
+ return x, new_state
176
+
177
+
178
+ # ── Full Language Model ──────────────────────────────────────────────────
179
+ class FWKVLanguageModel(PreTrainedModel, GenerationMixin):
180
+ config_class = FWKVConfig
181
+
182
+ def __init__(self, config):
183
+ super().__init__(config)
184
+ self.shared = FactorizedTiedHead(config.vocab_size, config.d_model, config.d_emb)
185
+ self.rosa_emb = nn.Embedding(config.vocab_size + 1, config.d_emb, padding_idx=0)
186
+ self.blocks = nn.ModuleList([
187
+ FWKVBlock(config.d_model, config.ffn_mult, config.wkv_floor)
188
+ for _ in range(config.n_layers)
189
+ ])
190
+ self.norm = nn.LayerNorm(config.d_model)
191
+ self.post_init()
192
+
193
+ def get_input_embeddings(self):
194
+ return self.shared.weight
195
+
196
+ def forward(
197
+ self,
198
+ input_ids,
199
+ rosa_ids=None,
200
+ past_key_values=None,
201
+ labels=None,
202
+ use_cache=True,
203
+ **kwargs,
204
+ ):
205
+ if rosa_ids is None:
206
+ # fallback: compute on the fly (expensive, but safe)
207
+ rows = [rosa(row.tolist()) for row in input_ids.detach().cpu()]
208
+ rosa_ids = torch.tensor(rows, device=input_ids.device, dtype=torch.long)
209
+
210
+ x = self.shared.embed(input_ids)
211
+ rosa_idx = (rosa_ids + 1).clamp(min=0)
212
+ x = x + self.shared.proj(self.rosa_emb(rosa_idx))
213
+
214
+ states_in = past_key_values or [None] * len(self.blocks)
215
+ states_out = []
216
+ for block, state in zip(self.blocks, states_in):
217
+ x, new_state = block(x, state)
218
+ states_out.append(new_state)
219
+
220
+ x = self.norm(x)
221
+ x_emb = self.shared.to_emb_space(x)
222
+ logits = self.shared.logits(x_emb)
223
+
224
+ return CausalLMOutputWithPast(
225
+ loss=None,
226
+ logits=logits,
227
+ past_key_values=states_out if use_cache else None,
228
+ )
229
+
230
+ def prepare_inputs_for_generation(self, input_ids, past_key_values=None,
231
+ rosa_ids=None, **kwargs):
232
+ if past_key_values is not None:
233
+ input_ids = input_ids[:, -1:]
234
+ if rosa_ids is not None:
235
+ rosa_ids = rosa_ids[:, -1:]
236
+ return {"input_ids": input_ids, "rosa_ids": rosa_ids,
237
+ "past_key_values": past_key_values, "use_cache": True}
238
+
239
+
240
+ # -------------------------------------------------------------------------
241
+ # Loading & Chat helpers
242
+ # -------------------------------------------------------------------------
243
+
244
+ USER_TOKEN = "<|user|>"
245
+ ASSISTANT_TOKEN = "<|assistant|>"
246
+
247
+ def load_model():
248
+ device = "cuda" if torch.cuda.is_available() else "cpu"
249
+ print(f"Loading FWKV-ROSA from Hub on {device} ...")
250
+ try:
251
+ model = FWKVLanguageModel.from_pretrained("FlameF0X/FWKV-ROSA")
252
+ model = model.to(device)
253
+ model.eval()
254
+ tokenizer = AutoTokenizer.from_pretrained("FlameF0X/FWKV-ROSA")
255
+ status = "FWKV-ROSA chat model ready!"
256
+ except Exception as e:
257
+ model, tokenizer = None, None
258
+ status = f"Error loading model: {e}"
259
+ print(status)
260
+ return model, tokenizer, status
261
+
262
+
263
+ model, tokenizer, load_status = load_model()
264
+
265
+ @torch.no_grad()
266
+ def generate_reply(ids: list[int], max_new_tokens=150, temperature=0.8, top_k=50):
267
+ """Autoregressive generation with ROSA updates, returning full sequence."""
268
+ device = next(model.parameters()).device
269
+ eos_id = tokenizer.eos_token_id
270
+
271
+ # initial forward over the whole prompt
272
+ inp = torch.tensor([ids], device=device)
273
+ rosa_ids = torch.tensor([rosa(ids)], device=device)
274
+ out = model(input_ids=inp, rosa_ids=rosa_ids, use_cache=True)
275
+ states = out.past_key_values
276
+ logits = out.logits[0, -1]
277
+ generated = list(ids)
278
+
279
+ for _ in range(max_new_tokens):
280
+ scaled = logits / max(temperature, 1e-5)
281
+ if top_k and top_k < scaled.size(-1):
282
+ kth = torch.topk(scaled, top_k).values[-1]
283
+ scaled[scaled < kth] = float('-inf')
284
+ probs = torch.softmax(scaled, dim=-1)
285
+ next_token = torch.multinomial(probs, 1).item()
286
+ generated.append(next_token)
287
+ if next_token == eos_id:
288
+ break
289
+
290
+ # ROSA for the extended sequence, use the last prediction
291
+ next_rosa = rosa(generated)[-1]
292
+ step_inp = torch.tensor([[next_token]], device=device)
293
+ step_rosa = torch.tensor([[next_rosa]], device=device)
294
+ out = model(input_ids=step_inp, rosa_ids=step_rosa,
295
+ past_key_values=states, use_cache=True)
296
+ states = out.past_key_values
297
+ logits = out.logits[0, -1]
298
+
299
+ return generated
300
+
301
+
302
+ def chat_function(message, history):
303
+ """Gradio ChatInterface callback. history is a list of (user, assistant) pairs."""
304
+ # Build the full conversation in the chat template
305
+ messages = []
306
+ for user_msg, asst_msg in history:
307
+ messages.append({"role": "user", "content": user_msg})
308
+ messages.append({"role": "assistant", "content": asst_msg})
309
+ messages.append({"role": "user", "content": message})
310
+
311
+ # Convert to token IDs
312
+ user_id = tokenizer.convert_tokens_to_ids(USER_TOKEN)
313
+ asst_id = tokenizer.convert_tokens_to_ids(ASSISTANT_TOKEN)
314
+ eos_id = tokenizer.eos_token_id
315
+ ids = []
316
+ for turn in messages:
317
+ role = turn["role"]
318
+ content = turn["content"]
319
+ content_ids = tokenizer.encode(" " + content)
320
+ if role == "user":
321
+ ids += [user_id] + content_ids
322
+ elif role == "assistant":
323
+ ids += [asst_id] + content_ids + [eos_id]
324
+
325
+ # Truncate from the left if needed
326
+ max_len = model.config.seq_len
327
+ if len(ids) > max_len:
328
+ ids = ids[-max_len:]
329
+
330
+ # Cue the assistant to start replying
331
+ ids.append(asst_id)
332
+
333
+ # Generate the assistant's reply
334
+ full_gen = generate_reply(ids, max_new_tokens=150, temperature=0.8, top_k=50)
335
+
336
+ # Extract only the new assistant tokens (after the last asst_id)
337
+ # Find the position of the last asst_id and take everything after it
338
+ assistant_start = len(ids) - 1 # index of the asst_id we just appended
339
+ reply_ids = full_gen[assistant_start + 1:] # skip the asst_id itself
340
+ reply = tokenizer.decode(reply_ids, skip_special_tokens=True).strip()
341
+ return reply
342
+
343
+
344
+ # ── Gradio UI ────────────────────────────────────────────────────────────
345
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
346
+ gr.Markdown(f"""
347
+ # ⚑ FWKV-ROSA Chat
348
+ **Model:** [FlameF0X/FWKV-ROSA](https://huggingface.co/FlameF0X/FWKV-ROSA)
349
+ *{load_status}*
350
+
351
+ This is a 56M‑parameter recurrent LM trained with the RWKV‑8 ROSA
352
+ copy‑signal mechanism. It uses the chat template:
353
+
354
+ `<|user|> message <|assistant|> reply <eos>`
355
+
356
+ You can chat naturally; the model will remember recent context up to
357
+ {model.config.seq_len if model else 1024} tokens.
358
+ """)
359
+
360
+ chatbot = gr.ChatInterface(
361
+ fn=chat_function,
362
+ title="",
363
+ description="",
364
+ examples=[
365
+ "Explain how a linear recurrent network can still copy long‑range patterns.",
366
+ "Write a short poem about a fox discovering a hidden library.",
367
+ ],
368
+ theme="soft",
369
+ )
370
+
371
+ if __name__ == "__main__":
372
+ demo.launch()