AbstractPhil commited on
Commit
915a71f
·
verified ·
1 Parent(s): 98ec4ab

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -173
app.py CHANGED
@@ -1,10 +1,7 @@
1
  # app.py
2
- # --------------------------------------------------------------------------------------------------
3
- # Gradio app for Beeper
4
- # - Loads released safetensors + tokenizer from Hugging Face
5
- # - Auto-sizes pentachora banks to match checkpoints (across Beeper v1..v4)
6
- # - Generation uses same knobs & penalties as training script
7
- # --------------------------------------------------------------------------------------------------
8
  import gradio as gr
9
  import torch
10
  from tokenizers import Tokenizer
@@ -13,9 +10,6 @@ from safetensors.torch import load_file as load_safetensors
13
 
14
  from beeper_model import BeeperRoseGPT, generate, prepare_model_for_state_dict
15
 
16
- # ----------------------------
17
- # 🔧 Model versions configuration
18
- # ----------------------------
19
  MODEL_VERSIONS = {
20
  "Beeper v4 (Advanced)": {
21
  "repo_id": "AbstractPhil/beeper-rose-v4",
@@ -39,7 +33,6 @@ MODEL_VERSIONS = {
39
  },
40
  }
41
 
42
- # Base configuration (matches training defaults)
43
  CONFIG = {
44
  "context": 512,
45
  "vocab_size": 8192,
@@ -56,196 +49,182 @@ CONFIG = {
56
  "resid_dropout": 0.1,
57
  "dropout": 0.0,
58
  "grad_checkpoint": False,
59
- # tokenizer_path not needed here; we load tokenizer.json from the HF repo
 
 
 
 
 
 
 
60
  }
61
 
62
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
63
-
64
- # Globals (kept simple for a single process Gradio app)
65
  infer: BeeperRoseGPT | None = None
66
  tok: Tokenizer | None = None
67
  current_version: str | None = None
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  def load_model_version(version_name: str) -> str:
71
- """
72
- Download the checkpoint and tokenizer, build model, ensure pentachora sizes match,
73
- then strictly load weights. Robust to v1/v2 (no pentas) and v3/v4 (with pentas).
74
- """
75
- global infer, tok, current_version
76
-
77
  if current_version == version_name and infer is not None and tok is not None:
78
  return f"Already loaded: {version_name}"
79
 
80
- version_info = MODEL_VERSIONS[version_name]
81
-
82
  try:
83
- # Download artifacts
84
- model_file = hf_hub_download(
85
- repo_id=version_info["repo_id"],
86
- filename=version_info["model_file"]
87
- )
88
- tokenizer_file = hf_hub_download(
89
- repo_id=version_info["repo_id"],
90
- filename="tokenizer.json"
91
- )
92
-
93
- # Load state dict on CPU, inspect pentachora shapes if present
94
- state_dict = load_safetensors(model_file, device="cpu")
95
 
96
- # Build model & pre-create pentachora if needed
97
  m = BeeperRoseGPT(CONFIG).to(device)
98
- prepare_model_for_state_dict(m, state_dict, device=device)
99
 
100
- # Try strict load first; if shapes drift (rare), fallback to non-strict
101
  try:
102
- missing, unexpected = m.load_state_dict(state_dict, strict=True)
103
- # PyTorch returns NamedTuple; report counts
104
  _msg = f"strict load ok | missing={len(missing)} unexpected={len(unexpected)}"
105
  except Exception as e:
106
- _msg = f"strict load failed ({e}); trying non-strict"
107
- # Non-strict load for very old snapshots
108
- m.load_state_dict(state_dict, strict=False)
109
 
110
  m.eval()
111
-
112
- # Tokenizer
113
  t = Tokenizer.from_file(tokenizer_file)
114
 
115
- # Swap globals
116
- infer, tok = m, t
117
- current_version = version_name
118
- return f"Successfully loaded: {version_name} ({_msg})"
 
 
 
119
 
 
120
  except Exception as e:
121
- infer = None
122
- tok = None
123
- current_version = None
124
  return f"Error loading {version_name}: {str(e)}"
125
 
126
-
127
- # Load default on startup — prefer v4, fallback to v3
128
  try:
129
- load_status = load_model_version("Beeper v4 (Advanced)")
130
- if "Error" in load_status:
131
- print(f"v4 not ready yet: {load_status}")
132
- load_status = load_model_version("Beeper v3 (Multi-Concept)")
133
- except Exception as _:
134
- load_status = load_model_version("Beeper v3 (Multi-Concept)")
135
- print(load_status)
136
-
137
-
138
- # ----------------------------
139
- # 💬 Chat wrapper
140
- # ----------------------------
141
- def beeper_reply(
142
- message: str,
143
- history: list[tuple[str, str]] | None,
144
- model_version: str,
145
- temperature: float | None,
146
- top_k: int | None,
147
- top_p: float | None,
148
- max_new_tokens: int = 80
149
- ) -> str:
150
  global infer, tok, current_version
151
-
152
- # Hot-swap versions if the dropdown changed
153
  if model_version != current_version:
154
- status = load_model_version(model_version)
155
- if "Error" in status:
156
- return f"⚠️ {status}"
157
 
158
  if infer is None or tok is None:
159
  return "⚠️ Model not loaded. Please select a version and try again."
160
 
161
- # Light prompting heuristics (consistent with your example)
162
- m = message.strip()
163
- if "?" in m:
164
- prompt = f"Q: {m}\nA:"
165
- elif m.lower() in {"hi", "hello", "hey"}:
166
- prompt = 'The little robot said hello. She said, "'
167
- elif "story" in m.lower():
168
- prompt = "Once upon a time, there was a robot. "
169
- else:
170
- prompt = m + ". "
171
-
172
- # Generate
173
- text = generate(
174
- model=infer,
175
- tok=tok,
176
- cfg=CONFIG,
177
- prompt=prompt,
178
  max_new_tokens=int(max_new_tokens),
179
  temperature=float(temperature) if temperature is not None else None,
180
  top_k=int(top_k) if top_k is not None else None,
181
  top_p=float(top_p) if top_p is not None else None,
182
- repetition_penalty=1.10,
183
- presence_penalty=0.8,
184
- frequency_penalty=0.1,
185
- device=device,
186
- detokenize=True,
187
  )
188
 
189
- # Strip prompt echoes & artifacts
190
- if text.startswith(prompt):
191
- text = text[len(prompt):]
192
- text = text.replace("Q:", "").replace("A:", "")
193
-
194
- lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
195
- if lines:
196
- text = lines[0]
197
-
198
- # If user message echoed at head, trim after first occurrence
199
- head = m[:20].lower()
200
- if text.lower().startswith(head):
201
- idx = text.lower().find(head)
202
- text = text[idx + len(head):].strip() or text
203
-
204
- for artifact in ("User:", "Beeper:", "U ser:", "Beep er:", "User ", "Beeper "):
205
- text = text.replace(artifact, "")
206
 
207
- text = text.strip()
208
- if not text or len(text) < 3:
209
- text = "I like robots and stories!"
210
-
211
- if text[-1:] not in ".!?”\"'":
212
- text += "."
213
-
214
- return text[:200]
215
-
216
-
217
- # ----------------------------
218
- # 🖼️ Interface
219
- # ----------------------------
220
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
221
- gr.Markdown(
222
- """
223
- # 🤖 Beeper — A Rose-based Tiny Language Model
224
- Hello! I'm Beeper, a small language model trained with love and care. Please be patient with me — I'm still learning! 💕
225
- """
226
- )
227
 
228
  with gr.Row():
229
  with gr.Column(scale=3):
230
  model_dropdown = gr.Dropdown(
231
  choices=list(MODEL_VERSIONS.keys()),
232
- value="Beeper v3 (Multi-Concept)", # safer default
233
- label="Select Beeper Version",
234
- info="Choose which version of Beeper to chat with",
235
  )
236
  with gr.Column(scale=7):
237
  version_info = gr.Markdown("**Current:** " + MODEL_VERSIONS["Beeper v3 (Multi-Concept)"]["description"])
238
 
239
- def update_version_info(version_name: str):
240
- return f"**Current:** {MODEL_VERSIONS[version_name]['description']}"
241
-
242
- model_dropdown.change(
243
- fn=update_version_info,
244
- inputs=[model_dropdown],
245
- outputs=[version_info],
246
- )
247
-
248
- chatbot = gr.Chatbot(label="Chat with Beeper", height=400)
249
  msg = gr.Textbox(label="Message", placeholder="Type your message here...")
250
 
251
  with gr.Row():
@@ -262,34 +241,38 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
262
  submit = gr.Button("Send", variant="primary")
263
  clear = gr.Button("Clear")
264
 
265
- gr.Examples(
266
- examples=[
267
- ["Hello Beeper! How are you today?"],
268
- ["Can you tell me a story about a robot?"],
269
- ["What do you like to do for fun?"],
270
- ["What makes you happy?"],
271
- ["Tell me about your dreams"],
272
- ],
273
- inputs=msg,
 
 
 
 
 
 
 
274
  )
275
 
276
- def respond(message, chat_history, model_version, temperature, top_k, top_p, max_new_tokens):
277
- if chat_history is None:
278
- chat_history = []
279
- response = beeper_reply(message, chat_history, model_version, temperature, top_k, top_p, max_new_tokens)
280
- chat_history.append((message, response))
 
281
  return "", chat_history
282
 
283
- msg.submit(
284
- respond,
285
- [msg, chatbot, model_dropdown, temperature_slider, top_k_slider, top_p_slider, max_new_tokens_slider],
286
- [msg, chatbot],
287
- )
288
- submit.click(
289
- respond,
290
- [msg, chatbot, model_dropdown, temperature_slider, top_k_slider, top_p_slider, max_new_tokens_slider],
291
- [msg, chatbot],
292
- )
293
  clear.click(lambda: None, None, chatbot, queue=False)
294
 
295
  if __name__ == "__main__":
 
1
  # app.py
2
+ # Gradio app exposing full Corpus (coarse) and Capoera (topic/mood) selections
3
+
4
+ import json
 
 
 
5
  import gradio as gr
6
  import torch
7
  from tokenizers import Tokenizer
 
10
 
11
  from beeper_model import BeeperRoseGPT, generate, prepare_model_for_state_dict
12
 
 
 
 
13
  MODEL_VERSIONS = {
14
  "Beeper v4 (Advanced)": {
15
  "repo_id": "AbstractPhil/beeper-rose-v4",
 
33
  },
34
  }
35
 
 
36
  CONFIG = {
37
  "context": 512,
38
  "vocab_size": 8192,
 
49
  "resid_dropout": 0.1,
50
  "dropout": 0.0,
51
  "grad_checkpoint": False,
52
+ "runtime_pentachora": {
53
+ "enable": True,
54
+ "pool": "mean",
55
+ "temp": 0.10,
56
+ "coarse_alpha": 0.25,
57
+ "topic_alpha": 0.15,
58
+ "mood_alpha": 0.10,
59
+ },
60
  }
61
 
62
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
 
63
  infer: BeeperRoseGPT | None = None
64
  tok: Tokenizer | None = None
65
  current_version: str | None = None
66
 
67
+ # Metadata for selectors
68
+ CORPUS_CHOICES: list[str] = []
69
+ CORPUS_INDEX: dict[str, int] = {}
70
+ TOPIC_CHOICES: list[str] = []
71
+ MOOD_CHOICES: list[str] = []
72
+
73
+ def _mood_labels(mood_bins: int) -> list[str]:
74
+ center = mood_bins // 2
75
+ labels = []
76
+ for i in range(mood_bins):
77
+ v = i - center
78
+ name = { -3:"Very Negative", -2:"Negative", -1:"Slightly Negative",
79
+ 0:"Neutral", 1:"Slightly Positive", 2:"Positive", 3:"Very Positive" }.get(v, f"Valence {v:+d}")
80
+ labels.append(f"{i} ({name} {v:+d})")
81
+ return labels
82
+
83
+ def _build_choices_from_config(repo_id: str, coarse_C: int, topic_C: int, mood_C: int):
84
+ global CORPUS_CHOICES, CORPUS_INDEX, TOPIC_CHOICES, MOOD_CHOICES
85
+ CORPUS_CHOICES, CORPUS_INDEX = [], {}
86
+ # Try to load training config.json (exported alongside weights)
87
+ names = []
88
+ try:
89
+ cfg_path = hf_hub_download(repo_id, "config.json")
90
+ with open(cfg_path, "r", encoding="utf-8") as f:
91
+ train_cfg = json.load(f)
92
+ alive = train_cfg.get("_alive_entries")
93
+ if isinstance(alive, list) and all(isinstance(e, dict) for e in alive):
94
+ names = [str(e.get("name", f"Class {i}")) for i, e in enumerate(alive)]
95
+ elif isinstance(train_cfg.get("corpus"), list):
96
+ # fallback: use corpus list if length matches bank size
97
+ maybe = [str(e.get("name", f"Class {i}")) for i, e in enumerate(train_cfg["corpus"])]
98
+ if len(maybe) == coarse_C:
99
+ names = maybe
100
+ except Exception:
101
+ names = []
102
+
103
+ if len(names) != coarse_C:
104
+ names = [f"Class {i}" for i in range(coarse_C)]
105
+
106
+ CORPUS_CHOICES = names
107
+ CORPUS_INDEX = {name: i for i, name in enumerate(names)}
108
+ TOPIC_CHOICES = [str(i) for i in range(topic_C)]
109
+ MOOD_CHOICES = _mood_labels(mood_C)
110
 
111
  def load_model_version(version_name: str) -> str:
112
+ global infer, tok, current_version, CORPUS_CHOICES, TOPIC_CHOICES, MOOD_CHOICES
 
 
 
 
 
113
  if current_version == version_name and infer is not None and tok is not None:
114
  return f"Already loaded: {version_name}"
115
 
116
+ info = MODEL_VERSIONS[version_name]
 
117
  try:
118
+ model_file = hf_hub_download(info["repo_id"], info["model_file"])
119
+ tokenizer_file = hf_hub_download(info["repo_id"], "tokenizer.json")
 
 
 
 
 
 
 
 
 
 
120
 
121
+ state = load_safetensors(model_file, device="cpu")
122
  m = BeeperRoseGPT(CONFIG).to(device)
123
+ prepare_model_for_state_dict(m, state, device=device)
124
 
 
125
  try:
126
+ missing, unexpected = m.load_state_dict(state, strict=True)
 
127
  _msg = f"strict load ok | missing={len(missing)} unexpected={len(unexpected)}"
128
  except Exception as e:
129
+ _msg = f"strict load failed ({e}); non-strict fallback"
130
+ m.load_state_dict(state, strict=False)
 
131
 
132
  m.eval()
 
 
133
  t = Tokenizer.from_file(tokenizer_file)
134
 
135
+ infer, tok, current_version = m, t, version_name
136
+
137
+ # Build UI choices from bank sizes + training config (for names)
138
+ coarse_C = infer.penta_coarse.size(0) if infer.penta_coarse is not None else 0
139
+ topic_C = infer.penta_medium.size(0) if infer.penta_medium is not None else 512
140
+ mood_C = infer.penta_fine.size(0) if infer.penta_fine is not None else 7
141
+ _build_choices_from_config(info["repo_id"], coarse_C, topic_C, mood_C)
142
 
143
+ return f"Successfully loaded: {version_name} ({_msg})"
144
  except Exception as e:
145
+ infer = None; tok = None; current_version = None
146
+ CORPUS_CHOICES, TOPIC_CHOICES, MOOD_CHOICES = [], [], []
 
147
  return f"Error loading {version_name}: {str(e)}"
148
 
149
+ # Initial load: prefer v4, fallback to v3
 
150
  try:
151
+ status = load_model_version("Beeper v4 (Advanced)")
152
+ if "Error" in status:
153
+ print(status)
154
+ status = load_model_version("Beeper v3 (Multi-Concept)")
155
+ except Exception:
156
+ status = load_model_version("Beeper v3 (Multi-Concept)")
157
+ print(status)
158
+
159
+ def _parse_selected_indices(values: list[str] | None, mapping: dict[str,int] | None = None) -> list[int] | None:
160
+ if not values: return None
161
+ if mapping is None:
162
+ return [int(v.split()[0]) if isinstance(v, str) else int(v) for v in values]
163
+ return [mapping[v] for v in values if v in mapping]
164
+
165
+ def beeper_reply(message, history, model_version, temperature, top_k, top_p, max_new_tokens,
166
+ corpus_selected, topic_selected, mood_selected):
 
 
 
 
 
167
  global infer, tok, current_version
 
 
168
  if model_version != current_version:
169
+ s = load_model_version(model_version)
170
+ if "Error" in s:
171
+ return f"⚠️ {s}"
172
 
173
  if infer is None or tok is None:
174
  return "⚠️ Model not loaded. Please select a version and try again."
175
 
176
+ # Build runtime pull config with user selections
177
+ rt = dict(CONFIG.get("runtime_pentachora", {}))
178
+ # Convert selections -> index lists
179
+ rt["coarse_select"] = _parse_selected_indices(corpus_selected, CORPUS_INDEX) # names -> indices
180
+ rt["topic_select"] = _parse_selected_indices(topic_selected, None) # numeric strings -> ints
181
+ rt["mood_select"] = _parse_selected_indices(mood_selected, None) # numeric strings -> ints
182
+
183
+ m = (message or "").strip()
184
+ if "?" in m: prompt = f"Q: {m}\nA:"
185
+ elif m.lower() in {"hi","hello","hey"}: prompt = 'The little robot said hello. She said, "'
186
+ elif "story" in m.lower(): prompt = "Once upon a time, there was a robot. "
187
+ else: prompt = m + ". "
188
+
189
+ out = generate(
190
+ model=infer, tok=tok, cfg=CONFIG, prompt=prompt,
 
 
191
  max_new_tokens=int(max_new_tokens),
192
  temperature=float(temperature) if temperature is not None else None,
193
  top_k=int(top_k) if top_k is not None else None,
194
  top_p=float(top_p) if top_p is not None else None,
195
+ repetition_penalty=1.10, presence_penalty=0.8, frequency_penalty=0.1,
196
+ device=device, detokenize=True, runtime_cfg=rt,
 
 
 
197
  )
198
 
199
+ if out.startswith(prompt): out = out[len(prompt):]
200
+ out = out.replace("Q:","").replace("A:","").strip()
201
+ if out and out[-1] not in ".!?”\"'": out += "."
202
+ return out[:200]
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ # ---------------- UI ----------------
 
 
 
 
 
 
 
 
 
 
 
 
205
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
206
+ gr.Markdown("# 🤖 Beeper — Corpus & Capoera–aware Chat")
 
 
 
 
 
207
 
208
  with gr.Row():
209
  with gr.Column(scale=3):
210
  model_dropdown = gr.Dropdown(
211
  choices=list(MODEL_VERSIONS.keys()),
212
+ value="Beeper v3 (Multi-Concept)",
213
+ label="Select Beeper Version"
 
214
  )
215
  with gr.Column(scale=7):
216
  version_info = gr.Markdown("**Current:** " + MODEL_VERSIONS["Beeper v3 (Multi-Concept)"]["description"])
217
 
218
+ # Runtime pentachora selectors
219
+ with gr.Row():
220
+ with gr.Column():
221
+ corpus_select = gr.Dropdown(choices=CORPUS_CHOICES, multiselect=True, label="Corpus (Coarse classes)")
222
+ with gr.Column():
223
+ topic_select = gr.Dropdown(choices=TOPIC_CHOICES, multiselect=True, label="Capoera Topics (IDs)")
224
+ with gr.Column():
225
+ mood_select = gr.Dropdown(choices=MOOD_CHOICES, multiselect=True, label="Capoera Moods (valence)")
226
+
227
+ chatbot = gr.Chatbot(label="Chat with Beeper", height=420)
228
  msg = gr.Textbox(label="Message", placeholder="Type your message here...")
229
 
230
  with gr.Row():
 
241
  submit = gr.Button("Send", variant="primary")
242
  clear = gr.Button("Clear")
243
 
244
+ # On version change: load model + update selectors
245
+ def on_change_version(version_name: str):
246
+ status = load_model_version(version_name)
247
+ info = f"**Current:** {MODEL_VERSIONS[version_name]['description']} \n{status}"
248
+ # refresh selector choices
249
+ return (
250
+ info,
251
+ gr.update(choices=CORPUS_CHOICES, value=[]),
252
+ gr.update(choices=TOPIC_CHOICES, value=[]),
253
+ gr.update(choices=MOOD_CHOICES, value=[]),
254
+ )
255
+
256
+ model_dropdown.change(
257
+ on_change_version,
258
+ inputs=[model_dropdown],
259
+ outputs=[version_info, corpus_select, topic_select, mood_select],
260
  )
261
 
262
+ def respond(message, chat_history, model_version, temperature, top_k, top_p, max_new_tokens,
263
+ corpus_selected, topic_selected, mood_selected):
264
+ if chat_history is None: chat_history = []
265
+ resp = beeper_reply(message, chat_history, model_version, temperature, top_k, top_p, max_new_tokens,
266
+ corpus_selected, topic_selected, mood_selected)
267
+ chat_history.append((message, resp))
268
  return "", chat_history
269
 
270
+ inputs_all = [msg, chatbot, model_dropdown, temperature_slider, top_k_slider, top_p_slider, max_new_tokens_slider,
271
+ corpus_select, topic_select, mood_select]
272
+ outputs_all = [msg, chatbot]
273
+
274
+ msg.submit(respond, inputs_all, outputs_all)
275
+ submit.click(respond, inputs_all, outputs_all)
 
 
 
 
276
  clear.click(lambda: None, None, chatbot, queue=False)
277
 
278
  if __name__ == "__main__":