chyams Claude Opus 4.5 commited on
Commit
67d4e0c
·
1 Parent(s): dd9f260

Capture decisions: 5 LLM Explorer bug fixes

Browse files

- Dark mode button text synced on page load via demo.load() JS
- Dark mode heading colors: setProperty with !important beats stylesheet
- Entropy NaN fix: filter probs > 0 instead of p + eps
- Merged Top-N + Top-K into single Top-K slider (5-100)
- Show-steps-off highlight: strip leading whitespace before span

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (3) hide show
  1. app.py +40 -29
  2. config.json +2 -2
  3. models.py +11 -11
app.py CHANGED
@@ -76,6 +76,8 @@ h1, h2, h3,
76
  color: #63348d !important;
77
  }
78
  body.dark h1, body.dark h2, body.dark h3,
 
 
79
  .dark .gradio-container h1, .dark .gradio-container h2, .dark .gradio-container h3 {
80
  color: #ded9f4 !important;
81
  }
@@ -146,13 +148,30 @@ body.dark, .dark {
146
  }
147
  """
148
 
149
- # Dark mode toggle JS — toggles class and swaps button text
150
  DARK_MODE_JS = """
151
  () => {
152
  document.body.classList.toggle('dark');
153
  const isDark = document.body.classList.contains('dark');
154
  const el = document.getElementById('dark-mode-btn');
155
  if (el) el.innerText = isDark ? 'Light mode' : 'Dark mode';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
  """
158
 
@@ -250,11 +269,14 @@ def _render_step_html(step_data: dict, prompt: str, prev_generated: str) -> str:
250
  def _render_final_text_html(prompt: str, generated_text: str) -> str:
251
  """Render final text with all generated text highlighted (show-steps OFF mode)."""
252
  generated = generated_text[len(prompt):]
 
 
 
253
 
254
  return f"""
255
  <div style="border:1px solid var(--llm-card-border);border-radius:8px;padding:16px;background:var(--llm-card-bg);">
256
  <div style="font-family:monospace;font-size:16px;line-height:1.6;word-wrap:break-word;">
257
- <span style="color:var(--llm-prompt-color);">{_esc(prompt)}</span><span style="background:var(--llm-highlight-bg);color:var(--llm-highlight-color);font-weight:600;padding:2px 6px;border-radius:4px;">{_esc(generated)}</span>
258
  </div>
259
  </div>"""
260
 
@@ -286,7 +308,7 @@ def _render_tokens_html(tokens: list[tuple[str, int]]) -> str:
286
  # Tab 1: Probability Explorer
287
  # ---------------------------------------------------------------------------
288
 
289
- def explore_probabilities(prompt, temperature, top_n, top_k, steps, show_steps, seed):
290
  """Generate tokens step by step and return formatted HTML."""
291
  if not manager.is_ready():
292
  return f"<p style='color:red;'>{manager.status_message()}</p>"
@@ -296,7 +318,6 @@ def explore_probabilities(prompt, temperature, top_n, top_k, steps, show_steps,
296
  prompt=prompt,
297
  steps=int(steps),
298
  temperature=temperature,
299
- top_n=int(top_n),
300
  top_k=int(top_k),
301
  seed=seed,
302
  show_steps=show_steps,
@@ -353,12 +374,12 @@ def admin_load_model(model_name):
353
  return status, json.dumps(cfg, indent=2)
354
 
355
 
356
- def admin_save_defaults(prompt, temperature, top_n, steps, seed):
357
  """Save default settings."""
358
  manager.update_config(
359
  default_prompt=prompt,
360
  default_temperature=temperature,
361
- default_top_n=int(top_n),
362
  default_steps=int(steps),
363
  default_seed=int(seed),
364
  )
@@ -416,27 +437,14 @@ def create_app():
416
  elem_classes=["param-help"],
417
  )
418
 
419
- t1_top_n = gr.Slider(
420
- label="Top-N display",
421
- minimum=5, maximum=30, step=1,
422
- value=cfg.get("default_top_n", 10),
423
- )
424
- gr.Markdown(
425
- "How many candidate tokens to show in the probability table. "
426
- "Only affects the display, not which token gets selected.",
427
- elem_classes=["param-help"],
428
- )
429
-
430
  t1_top_k = gr.Slider(
431
- label="Top-K sampling",
432
- minimum=0, maximum=100, step=1,
433
- value=0,
434
  )
435
  gr.Markdown(
436
- "Limits which tokens the model can pick from. "
437
- "At 0, the full vocabulary is available. "
438
- "At 40, only the 40 most likely tokens are considered "
439
- "and everything else is ignored.",
440
  elem_classes=["param-help"],
441
  )
442
 
@@ -486,7 +494,7 @@ def create_app():
486
 
487
  t1_generate_btn.click(
488
  fn=explore_probabilities,
489
- inputs=[t1_prompt, t1_temperature, t1_top_n, t1_top_k, t1_steps, t1_show_steps, t1_seed],
490
  outputs=[t1_output],
491
  )
492
 
@@ -553,9 +561,9 @@ def create_app():
553
  label="Default temperature",
554
  value=cfg.get("default_temperature", 0.8),
555
  )
556
- admin_top_n = gr.Number(
557
- label="Default top-n",
558
- value=cfg.get("default_top_n", 10),
559
  precision=0,
560
  )
561
  admin_steps = gr.Number(
@@ -596,10 +604,13 @@ def create_app():
596
  # Save defaults
597
  admin_save_btn.click(
598
  fn=admin_save_defaults,
599
- inputs=[admin_prompt, admin_temp, admin_top_n, admin_steps, admin_seed],
600
  outputs=[admin_save_msg],
601
  )
602
 
 
 
 
603
  return demo
604
 
605
 
 
76
  color: #63348d !important;
77
  }
78
  body.dark h1, body.dark h2, body.dark h3,
79
+ body.dark .gradio-container h1, body.dark .gradio-container h2, body.dark .gradio-container h3,
80
+ .dark h1, .dark h2, .dark h3,
81
  .dark .gradio-container h1, .dark .gradio-container h2, .dark .gradio-container h3 {
82
  color: #ded9f4 !important;
83
  }
 
148
  }
149
  """
150
 
151
+ # Dark mode toggle JS — toggles class and swaps button text + heading colors
152
  DARK_MODE_JS = """
153
  () => {
154
  document.body.classList.toggle('dark');
155
  const isDark = document.body.classList.contains('dark');
156
  const el = document.getElementById('dark-mode-btn');
157
  if (el) el.innerText = isDark ? 'Light mode' : 'Dark mode';
158
+ document.querySelectorAll('h1, h2, h3').forEach(h => {
159
+ h.style.setProperty('color', isDark ? '#ded9f4' : '#63348d', 'important');
160
+ });
161
+ }
162
+ """
163
+
164
+ # Dark mode init JS — sync button text and heading colors on page load
165
+ DARK_MODE_INIT_JS = """
166
+ () => {
167
+ const isDark = document.body.classList.contains('dark');
168
+ const el = document.getElementById('dark-mode-btn');
169
+ if (el) el.innerText = isDark ? 'Light mode' : 'Dark mode';
170
+ if (isDark) {
171
+ document.querySelectorAll('h1, h2, h3').forEach(h => {
172
+ h.style.setProperty('color', '#ded9f4', 'important');
173
+ });
174
+ }
175
  }
176
  """
177
 
 
269
  def _render_final_text_html(prompt: str, generated_text: str) -> str:
270
  """Render final text with all generated text highlighted (show-steps OFF mode)."""
271
  generated = generated_text[len(prompt):]
272
+ escaped = _esc(generated)
273
+ stripped = escaped.lstrip()
274
+ leading = escaped[:len(escaped) - len(stripped)]
275
 
276
  return f"""
277
  <div style="border:1px solid var(--llm-card-border);border-radius:8px;padding:16px;background:var(--llm-card-bg);">
278
  <div style="font-family:monospace;font-size:16px;line-height:1.6;word-wrap:break-word;">
279
+ <span style="color:var(--llm-prompt-color);">{_esc(prompt)}</span>{leading}<span style="background:var(--llm-highlight-bg);color:var(--llm-highlight-color);font-weight:600;padding:2px 6px;border-radius:4px;">{stripped}</span>
280
  </div>
281
  </div>"""
282
 
 
308
  # Tab 1: Probability Explorer
309
  # ---------------------------------------------------------------------------
310
 
311
+ def explore_probabilities(prompt, temperature, top_k, steps, show_steps, seed):
312
  """Generate tokens step by step and return formatted HTML."""
313
  if not manager.is_ready():
314
  return f"<p style='color:red;'>{manager.status_message()}</p>"
 
318
  prompt=prompt,
319
  steps=int(steps),
320
  temperature=temperature,
 
321
  top_k=int(top_k),
322
  seed=seed,
323
  show_steps=show_steps,
 
374
  return status, json.dumps(cfg, indent=2)
375
 
376
 
377
+ def admin_save_defaults(prompt, temperature, top_k, steps, seed):
378
  """Save default settings."""
379
  manager.update_config(
380
  default_prompt=prompt,
381
  default_temperature=temperature,
382
+ default_top_k=int(top_k),
383
  default_steps=int(steps),
384
  default_seed=int(seed),
385
  )
 
437
  elem_classes=["param-help"],
438
  )
439
 
 
 
 
 
 
 
 
 
 
 
 
440
  t1_top_k = gr.Slider(
441
+ label="Top-K",
442
+ minimum=5, maximum=100, step=1,
443
+ value=cfg.get("default_top_k", 10),
444
  )
445
  gr.Markdown(
446
+ "Limits which tokens the model considers and how many "
447
+ "appear in the probability table.",
 
 
448
  elem_classes=["param-help"],
449
  )
450
 
 
494
 
495
  t1_generate_btn.click(
496
  fn=explore_probabilities,
497
+ inputs=[t1_prompt, t1_temperature, t1_top_k, t1_steps, t1_show_steps, t1_seed],
498
  outputs=[t1_output],
499
  )
500
 
 
561
  label="Default temperature",
562
  value=cfg.get("default_temperature", 0.8),
563
  )
564
+ admin_top_k_admin = gr.Number(
565
+ label="Default top-k",
566
+ value=cfg.get("default_top_k", 10),
567
  precision=0,
568
  )
569
  admin_steps = gr.Number(
 
604
  # Save defaults
605
  admin_save_btn.click(
606
  fn=admin_save_defaults,
607
+ inputs=[admin_prompt, admin_temp, admin_top_k_admin, admin_steps, admin_seed],
608
  outputs=[admin_save_msg],
609
  )
610
 
611
+ # Sync dark mode button text on page load
612
+ demo.load(fn=None, js=DARK_MODE_INIT_JS)
613
+
614
  return demo
615
 
616
 
config.json CHANGED
@@ -1,8 +1,8 @@
1
  {
2
- "model": "GPT-OSS-20B",
3
  "default_prompt": "The best thing about Huston-Tillotson University is",
4
  "default_temperature": 0.8,
5
- "default_top_n": 10,
6
  "default_steps": 8,
7
  "default_seed": 42
8
  }
 
1
  {
2
+ "model": "Qwen2.5-3B",
3
  "default_prompt": "The best thing about Huston-Tillotson University is",
4
  "default_temperature": 0.8,
5
+ "default_top_k": 10,
6
  "default_steps": 8,
7
  "default_seed": 42
8
  }
models.py CHANGED
@@ -73,7 +73,7 @@ def _load_config() -> dict:
73
  "model": DEFAULT_MODEL,
74
  "default_prompt": "The best thing about Huston-Tillotson University is",
75
  "default_temperature": 0.8,
76
- "default_top_n": 10,
77
  "default_steps": 8,
78
  "default_seed": 42,
79
  }
@@ -223,8 +223,7 @@ class ModelManager:
223
  @staticmethod
224
  def entropy_bits(probs: torch.Tensor) -> float:
225
  """Shannon entropy in bits."""
226
- eps = 1e-20
227
- p = probs + eps
228
  return float(-torch.sum(p * torch.log2(p)))
229
 
230
  def top_k_table(
@@ -247,13 +246,15 @@ class ModelManager:
247
  prompt: str,
248
  steps: int = 8,
249
  temperature: float = 0.8,
250
- top_n: int = 10,
251
- top_k: int = 0,
252
  seed: int = 42,
253
  show_steps: bool = True,
254
  ) -> list[dict]:
255
  """Generate tokens one at a time, returning per-step data.
256
 
 
 
 
257
  Each step dict contains:
258
  - step: int (1-based)
259
  - text: accumulated text so far
@@ -273,15 +274,14 @@ class ModelManager:
273
  logits = self._get_logits(text)
274
 
275
  # Apply top-k filtering before temperature
276
- if top_k > 0:
277
- top_k_vals, top_k_idxs = torch.topk(logits, k=min(top_k, logits.shape[0]))
278
- mask = torch.full_like(logits, float("-inf"))
279
- mask.scatter_(0, top_k_idxs, top_k_vals)
280
- logits = mask
281
 
282
  probs = self.apply_temperature(logits, temperature)
283
  entropy = self.entropy_bits(probs)
284
- top_tokens = self.top_k_table(probs, k=top_n) if show_steps else []
285
 
286
  # Temperature 0 = greedy (always pick highest probability)
287
  if temperature == 0:
 
73
  "model": DEFAULT_MODEL,
74
  "default_prompt": "The best thing about Huston-Tillotson University is",
75
  "default_temperature": 0.8,
76
+ "default_top_k": 10,
77
  "default_steps": 8,
78
  "default_seed": 42,
79
  }
 
223
  @staticmethod
224
  def entropy_bits(probs: torch.Tensor) -> float:
225
  """Shannon entropy in bits."""
226
+ p = probs[probs > 0]
 
227
  return float(-torch.sum(p * torch.log2(p)))
228
 
229
  def top_k_table(
 
246
  prompt: str,
247
  steps: int = 8,
248
  temperature: float = 0.8,
249
+ top_k: int = 10,
 
250
  seed: int = 42,
251
  show_steps: bool = True,
252
  ) -> list[dict]:
253
  """Generate tokens one at a time, returning per-step data.
254
 
255
+ top_k controls both sampling (only top-k tokens considered) and
256
+ how many tokens appear in the probability table.
257
+
258
  Each step dict contains:
259
  - step: int (1-based)
260
  - text: accumulated text so far
 
274
  logits = self._get_logits(text)
275
 
276
  # Apply top-k filtering before temperature
277
+ top_k_vals, top_k_idxs = torch.topk(logits, k=min(top_k, logits.shape[0]))
278
+ mask = torch.full_like(logits, float("-inf"))
279
+ mask.scatter_(0, top_k_idxs, top_k_vals)
280
+ logits = mask
 
281
 
282
  probs = self.apply_temperature(logits, temperature)
283
  entropy = self.entropy_bits(probs)
284
+ top_tokens = self.top_k_table(probs, k=top_k) if show_steps else []
285
 
286
  # Temperature 0 = greedy (always pick highest probability)
287
  if temperature == 0: