ArushBuilds commited on
Commit
ef54b8e
·
verified ·
1 Parent(s): 03eca8d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +510 -143
app.py CHANGED
@@ -13,16 +13,24 @@ Environment variables (Space Settings -> Variables):
13
  TOKENIZER_PATH tokenizer.pkl or its folder (default: auto-detected next to app.py)
14
  MAX_TOKENS_CAP slider maximum (default 512)
15
  DEFAULT_MAX_TOKENS slider default (default 128)
 
 
 
 
16
  """
17
  from __future__ import annotations
18
 
19
  import contextlib
 
 
20
  import os
21
  import queue
22
  import sys
 
23
  import threading
24
  import time
25
  from pathlib import Path
 
26
 
27
  import gradio as gr
28
  import torch
@@ -31,14 +39,12 @@ _HERE = Path(__file__).resolve().parent
31
  if str(_HERE) not in sys.path:
32
  sys.path.insert(0, str(_HERE))
33
 
34
- import inspect # noqa: E402
35
-
36
  from inference import generate, load_model, pick_device, _resolve_dtype # noqa: E402
37
 
38
  if "on_text" not in inspect.signature(generate).parameters:
39
  raise RuntimeError("inference.py on this Space is out of date (generate() has no on_text argument). "
40
  "Upload the current inference.py next to app.py.")
41
- from tokenizer import TokenizerWrapper # noqa: E402
42
 
43
  _CHECKPOINT_LOCAL = os.environ.get("CHECKPOINT_PATH", str(_HERE / "pragya.pt"))
44
  _HF_REPO = os.environ.get("CHECKPOINT_REPO", "")
@@ -93,10 +99,33 @@ _dtype = _resolve_dtype("auto", _device) # autocast dtype, None = f
93
  _dtype_lbl = str(_dtype).replace("torch.", "").upper() if _dtype else "FP32"
94
  _device_lbl = str(_device).upper()
95
  _gen_lock = threading.Lock() # one generation at a time on the shared model
 
96
 
97
  print(f"[App] Ready: {_n_params} params, ctx={_ctx_len}, device={_device}", flush=True)
98
 
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  # -- generation ---------------------------------------------------------------
101
  _ANCHOR = (
102
  "The old lighthouse stood at the edge of the cliff, its beam sweeping slowly across the dark "
@@ -105,69 +134,218 @@ _ANCHOR = (
105
  )
106
 
107
 
108
- def _stats_html(n_tokens: int, ttft_ms: float | None, elapsed: float) -> str:
109
- rate = n_tokens / max(elapsed, 1e-6)
110
- ttft = f"{ttft_ms:.0f} ms" if ttft_ms is not None else "-"
111
- return (
112
- "<div class='p-stats'>"
113
- f"{n_tokens} tokens &middot; TTFT {ttft} &middot; {rate:.1f} tok/s"
114
- "</div>"
115
- )
 
 
 
 
 
 
116
 
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  def respond(message, history, max_new_tokens, temperature, top_k, top_p,
119
  repetition_penalty, no_repeat_ngram_size, use_anchor):
120
- if not message or not message.strip():
121
- yield "Please enter a prompt."
 
 
 
 
122
  return
123
 
124
- prompt = (_ANCHOR + message) if use_anchor else message
125
- pieces: queue.Queue = queue.Queue()
126
- error: list[BaseException] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- def worker():
129
  try:
130
- ctx = (torch.autocast(device_type=_device.type, dtype=_dtype)
131
- if _dtype is not None else contextlib.nullcontext())
132
- with _gen_lock, torch.inference_mode(), ctx:
133
- generate(
134
- _model, _tokenizer, prompt, _device,
135
- max_new_tokens=int(max_new_tokens),
136
- temperature=float(temperature),
137
- top_k=int(top_k) if top_k > 0 else None,
138
- top_p=float(top_p) if top_p < 1.0 else None,
139
- repetition_penalty=float(repetition_penalty),
140
- no_repeat_ngram_size=int(no_repeat_ngram_size),
141
- stream=False,
142
- use_cache=True,
143
- on_text=pieces.put,
144
- )
145
- except BaseException as exc: # surfaced in the UI below
146
- error.append(exc)
 
 
 
 
147
  finally:
148
- pieces.put(None) # end-of-stream marker
149
-
150
- t0 = time.perf_counter()
151
- ttft = None
152
- text = ""
153
- threading.Thread(target=worker, daemon=True).start()
154
- while True:
155
- piece = pieces.get()
156
- if piece is None:
157
- break
158
- if ttft is None:
159
- ttft = (time.perf_counter() - t0) * 1000
160
- text += piece
161
- yield text + _stats_html(_count(text), ttft, time.perf_counter() - t0)
162
-
163
- if error:
164
- yield f"Generation failed: {error[0]}"
165
- return
166
- yield (text or "(no output)") + _stats_html(_count(text), ttft, time.perf_counter() - t0)
167
-
168
-
169
- def _count(text: str) -> int:
170
- return len(_tokenizer.encode(text, add_bos=False, add_eos=False)) if text else 0
 
171
 
172
 
173
  # -- UI -----------------------------------------------------------------------
@@ -175,83 +353,198 @@ CSS = """
175
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
176
 
177
  :root {
178
- --p-bg: #fafaf9; --p-card: #ffffff; --p-line: #e7e5e4; --p-line-2: #d6d3d1;
179
- --p-ink: #0a0a0a; --p-ink-2: #292524; --p-muted: #78716c; --p-faint: #a8a29e;
180
- --p-accent: #ea580c; --p-accent-hover: #c2410c; --p-accent-soft: #fff7ed; --p-accent-line: #fed7aa;
 
 
 
181
  }
182
- body, .gradio-container { font-family: 'Inter', system-ui, sans-serif !important; background: var(--p-bg) !important; color: var(--p-ink) !important; }
183
- .gradio-container { max-width: 860px !important; margin: 0 auto !important; padding: 0 20px !important; }
184
- footer { display: none !important; }
185
 
186
- /* header */
187
- .p-nav { display: flex; align-items: center; gap: 12px; padding: 18px 0 16px; border-bottom: 1px solid var(--p-line); }
188
- .p-wordmark { font-weight: 700; font-size: 1.05rem; letter-spacing: -0.02em; display: flex; align-items: center; gap: 8px; }
189
- .p-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--p-accent); }
190
- .p-tag { font-size: 0.72rem; font-weight: 500; color: var(--p-accent); background: var(--p-accent-soft);
191
- border: 1px solid var(--p-accent-line); padding: 3px 9px; border-radius: 999px; }
192
- .p-nav-right { margin-left: auto; display: flex; gap: 14px; font-size: 0.78rem; color: var(--p-muted); font-family: 'IBM Plex Mono', monospace; }
193
-
194
- .p-hero { padding: 48px 0 36px; border-bottom: 1px solid var(--p-line); }
195
- .p-eyebrow { font-size: 0.74rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--p-accent); margin-bottom: 14px; }
196
- .p-hero h1 { font-size: clamp(2rem, 5vw, 2.7rem); font-weight: 700; letter-spacing: -0.03em; line-height: 1.12; margin: 0 0 14px; max-width: 580px; }
197
- .p-lede { font-size: 1rem; color: var(--p-muted); line-height: 1.65; max-width: 520px; margin: 0 0 26px; }
198
- .p-stats { display: flex; gap: 30px; flex-wrap: wrap; }
199
- .p-stat { display: flex; flex-direction: column; gap: 2px; }
200
- .p-stat b { font-size: 1.05rem; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
201
- .p-stat span { font-size: 0.7rem; color: var(--p-faint); font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; }
202
-
203
- .p-section { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--p-faint); padding: 26px 0 10px; }
204
-
205
- /* chat */
206
- .message-row.user-row .message, .message-row.user-row .message * { color: #ffffff !important; }
207
- .message-row.user-row .message { background: var(--p-accent) !important; border-radius: 12px 12px 2px 12px !important; }
208
- .message-row.bot-row .message, .message-row.bot-row .message * { color: #0a0a0a !important; }
209
- .message-row.bot-row .message code { color: #292524 !important; }
210
- .message-row.bot-row .message a { color: var(--p-accent) !important; }
211
- .message-row.bot-row .message { background: var(--p-card) !important; border: 1px solid var(--p-line) !important; border-radius: 12px 12px 12px 2px !important; box-shadow: 0 1px 3px rgba(0,0,0,0.05) !important; }
212
- .bot-row code, .bot-row pre { font-family: 'IBM Plex Mono', monospace !important; background: var(--p-bg) !important; border: 1px solid var(--p-line) !important; border-radius: 6px !important; font-size: 0.82rem !important; }
213
- .p-stats-line { font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; color: var(--p-faint); margin-top: 8px; }
214
-
215
- textarea { font-family: 'Inter', sans-serif !important; font-size: 0.95rem !important; border-radius: 8px !important; border: 1.5px solid var(--p-line-2) !important; background: var(--p-card) !important; }
216
- textarea:focus { border-color: var(--p-accent) !important; box-shadow: 0 0 0 3px rgba(234,88,12,0.12) !important; outline: none !important; }
217
- button.primary { background: var(--p-accent) !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; color: #fff !important; }
218
- button.primary:hover { background: var(--p-accent-hover) !important; }
219
- .accordion { border: 1px solid var(--p-line) !important; border-radius: 10px !important; background: var(--p-card) !important; }
220
- .example { font-size: 0.82rem !important; border: 1px solid var(--p-line-2) !important; border-radius: 6px !important; background: var(--p-card) !important; }
221
- .example:hover { border-color: var(--p-accent) !important; color: var(--p-accent) !important; background: var(--p-accent-soft) !important; }
222
-
223
- /* footer */
224
- .p-footer { border-top: 1px solid var(--p-line); padding: 24px 0 18px; margin-top: 14px; display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 14px; }
225
- .p-footer-brand { font-weight: 700; font-size: 0.88rem; }
226
- .p-footer-copy { font-size: 0.72rem; color: var(--p-faint); margin-top: 4px; max-width: 420px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  .p-badges { display: flex; gap: 6px; flex-wrap: wrap; }
228
- .p-badge { font-size: 0.68rem; font-family: 'IBM Plex Mono', monospace; color: var(--p-muted); background: var(--p-bg); border: 1px solid var(--p-line); padding: 3px 8px; border-radius: 4px; white-space: nowrap; }
 
229
  .p-badge.acc { color: var(--p-accent); background: var(--p-accent-soft); border-color: var(--p-accent-line); }
230
- .p-stats { font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; color: var(--p-faint); margin-top: 10px; }
 
 
 
 
 
231
  """
232
 
 
 
233
 
234
  def _header_html() -> str:
235
  return f"""
236
  <div class="p-nav">
237
  <span class="p-wordmark"><span class="p-dot"></span>Pragya</span>
238
  <span class="p-tag">by Arush Kumar</span>
239
- <div class="p-nav-right"><span>PyTorch</span><span>{_device_lbl}</span></div>
240
  </div>
241
  <div class="p-hero">
242
  <div class="p-eyebrow">From-scratch language model</div>
243
- <h1>A model built from the ground up.</h1>
244
- <p class="p-lede">A GPT-style architecture with KV-cached decoding, grouped-query attention,
245
- and every weight trained from scratch. Type a prompt and watch it continue.</p>
246
  <div class="p-stats">
247
  <div class="p-stat"><b>{_n_params}</b><span>Parameters</span></div>
248
- <div class="p-stat"><b>{_ctx_len:,}</b><span>Context</span></div>
249
- <div class="p-stat"><b>{_n_layer}L &middot; {_n_head}/{_kv_heads}H</b><span>Depth &middot; heads</span></div>
250
  <div class="p-stat"><b>{_pattern}</b><span>Attention</span></div>
251
  <div class="p-stat"><b>{_dtype_lbl}</b><span>Inference dtype</span></div>
252
  </div>
253
  </div>
254
- <div class="p-section">Try it</div>
255
  """
256
 
257
 
@@ -259,54 +552,128 @@ def _footer_html() -> str:
259
  return f"""
260
  <div class="p-footer">
261
  <div>
262
- <div class="p-footer-brand">Pragya &middot; <span style="font-weight:400;color:#78716c">Arush Kumar</span></div>
263
- <div class="p-footer-copy">Continuation model. Earlier turns are shown in the chat but are not sent back to the model.
264
- TTFT and tok/s are measured on each generation.</div>
 
265
  </div>
266
  <div class="p-badges">
267
  <span class="p-badge acc">{_n_params} params</span>
268
  <span class="p-badge">{_device_lbl}</span>
269
  <span class="p-badge">{_dtype_lbl}</span>
270
- <span class="p-badge">ctx {_ctx_len}</span>
271
  <span class="p-badge">{_pattern}</span>
 
272
  </div>
273
  </div>
274
  """
275
 
276
 
277
- def main():
278
- with gr.Blocks(title="Pragya by Arush Kumar") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  gr.HTML(_header_html())
280
- gr.ChatInterface(
281
- fn=respond,
282
- additional_inputs=[
283
- gr.Slider(16, _MAX_TOKENS_CAP, value=_DEFAULT_TOKENS, step=8, label="Max new tokens"),
284
- gr.Slider(0.1, 2.0, value=0.8, step=0.05, label="Temperature"),
285
- gr.Slider(0, 200, value=50, step=5, label="Top-k", info="0 disables top-k."),
286
- gr.Slider(0.5, 1.0, value=1.0, step=0.01, label="Top-p", info="1.0 disables nucleus sampling."),
287
- gr.Slider(1.0, 2.0, value=1.3, step=0.05, label="Repetition penalty",
288
- info="Count-scaled penalty. 1.0 = off."),
289
- gr.Slider(0, 6, value=3, step=1, label="No-repeat n-gram",
290
- info="Bans repeating any n-gram of this length. 0 = off."),
291
- gr.Checkbox(value=False, label="Narrative anchor",
292
- info="Prepend a short prose passage to set the style."),
293
- ],
294
- additional_inputs_accordion=gr.Accordion("Generation settings", open=False),
295
- chatbot=gr.Chatbot(height=440, show_label=False),
296
- textbox=gr.Textbox(placeholder="Start a sentence and let Pragya continue...",
297
- show_label=False, lines=2),
298
- examples=[["Hello"], ["Tell me a story."], ["Create a simple story."], ["Who developed you?"]],
299
- cache_examples=False,
300
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  gr.HTML(_footer_html())
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  demo.queue().launch(
304
  server_name="0.0.0.0",
305
  server_port=int(os.environ.get("PORT", "7860")),
306
  share=False,
307
- css=CSS,
308
- theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, neutral_hue=gr.themes.colors.stone,
309
- font=gr.themes.GoogleFont("Inter")),
310
  )
311
 
312
 
 
13
  TOKENIZER_PATH tokenizer.pkl or its folder (default: auto-detected next to app.py)
14
  MAX_TOKENS_CAP slider maximum (default 512)
15
  DEFAULT_MAX_TOKENS slider default (default 128)
16
+
17
+ UI: custom Blocks chat -- send button, interruptible generation (Stop), quick-start
18
+ chips, and a live stats line (TTFT / tok/s) under the conversation. Works on Gradio
19
+ 4.x and 5.x; uses the messages chat format when available, falls back to tuples.
20
  """
21
  from __future__ import annotations
22
 
23
  import contextlib
24
+ import html as _htmllib
25
+ import inspect
26
  import os
27
  import queue
28
  import sys
29
+ import tempfile
30
  import threading
31
  import time
32
  from pathlib import Path
33
+ from typing import Any
34
 
35
  import gradio as gr
36
  import torch
 
39
  if str(_HERE) not in sys.path:
40
  sys.path.insert(0, str(_HERE))
41
 
 
 
42
  from inference import generate, load_model, pick_device, _resolve_dtype # noqa: E402
43
 
44
  if "on_text" not in inspect.signature(generate).parameters:
45
  raise RuntimeError("inference.py on this Space is out of date (generate() has no on_text argument). "
46
  "Upload the current inference.py next to app.py.")
47
+ from tokenizer import TokenizerWrapper # noqa: E402
48
 
49
  _CHECKPOINT_LOCAL = os.environ.get("CHECKPOINT_PATH", str(_HERE / "pragya.pt"))
50
  _HF_REPO = os.environ.get("CHECKPOINT_REPO", "")
 
99
  _dtype_lbl = str(_dtype).replace("torch.", "").upper() if _dtype else "FP32"
100
  _device_lbl = str(_device).upper()
101
  _gen_lock = threading.Lock() # one generation at a time on the shared model
102
+ _ui_guard = threading.Lock() # one respond() stream at a time in the UI
103
 
104
  print(f"[App] Ready: {_n_params} params, ctx={_ctx_len}, device={_device}", flush=True)
105
 
106
 
107
+ # -- avatars (cosmetic; PIL ships with gradio) --------------------------------
108
+ def _make_avatar(bg: tuple[int, int, int], dot: tuple[int, int, int] | None) -> str:
109
+ from PIL import Image, ImageDraw
110
+ size, r = 256, 46
111
+ img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
112
+ draw = ImageDraw.Draw(img)
113
+ draw.ellipse((6, 6, size - 6, size - 6), fill=(*bg, 255))
114
+ if dot:
115
+ draw.ellipse((size / 2 - r, size / 2 - r, size / 2 + r, size / 2 + r), fill=(*dot, 255))
116
+ path = Path(tempfile.gettempdir()) / f"pragya_avatar_{bg[0]}_{bg[1]}_{bg[2]}_{bool(dot)}.png"
117
+ img.save(path)
118
+ return str(path)
119
+
120
+
121
+ try:
122
+ _AVATARS = (_make_avatar((41, 37, 36), (168, 162, 158)), # user
123
+ _make_avatar((234, 88, 12), (255, 255, 255))) # Pragya
124
+ except Exception as _avatar_exc: # never block startup over avatars
125
+ print(f"[App] Avatars skipped ({_avatar_exc}).", flush=True)
126
+ _AVATARS = None
127
+
128
+
129
  # -- generation ---------------------------------------------------------------
130
  _ANCHOR = (
131
  "The old lighthouse stood at the edge of the cliff, its beam sweeping slowly across the dark "
 
134
  )
135
 
136
 
137
+ class _GenerationStopped(Exception):
138
+ """Raised inside the on_text callback to abort generation mid-stream."""
139
+
140
+
141
+ _stop_lock = threading.Lock()
142
+ _active_stop: threading.Event | None = None
143
+
144
+
145
+ def _new_stop_event() -> threading.Event:
146
+ global _active_stop
147
+ ev = threading.Event()
148
+ with _stop_lock:
149
+ _active_stop = ev
150
+ return ev
151
 
152
 
153
+ def _request_stop() -> None:
154
+ with _stop_lock:
155
+ ev = _active_stop
156
+ if ev is not None:
157
+ ev.set()
158
+
159
+
160
+ def _count(text: str) -> int:
161
+ return len(_tokenizer.encode(text, add_bos=False, add_eos=False)) if text else 0
162
+
163
+
164
+ # -- chat-format helpers (messages format when supported, tuples otherwise) ---
165
+ _MSG_FORMAT: bool = True
166
+
167
+
168
+ def _text(content: Any) -> str:
169
+ if content is None:
170
+ return ""
171
+ if isinstance(content, (list, tuple)):
172
+ parts = []
173
+ for part in content:
174
+ parts.append(str(part.get("text", "")) if isinstance(part, dict) else str(part))
175
+ return "".join(parts)
176
+ return str(content)
177
+
178
+
179
+ def _norm_history(value: Any) -> list[dict[str, str]]:
180
+ """Whatever the Chatbot hands us (message dicts or (user, bot) tuples) -> message dicts."""
181
+ messages: list[dict[str, str]] = []
182
+ for item in value or []:
183
+ if isinstance(item, dict):
184
+ messages.append({"role": str(item.get("role", "assistant")),
185
+ "content": _text(item.get("content"))})
186
+ elif isinstance(item, (list, tuple)) and len(item) == 2:
187
+ user, bot = item
188
+ if user is not None:
189
+ messages.append({"role": "user", "content": _text(user)})
190
+ if bot is not None:
191
+ messages.append({"role": "assistant", "content": _text(bot)})
192
+ return messages
193
+
194
+
195
+ def _chat_value(messages: list[dict[str, str]]) -> Any:
196
+ """Internal message dicts -> a value the Chatbot component understands."""
197
+ if _MSG_FORMAT:
198
+ return messages
199
+ pairs: list[list[Any]] = []
200
+ pending_user: str | None = None
201
+ for message in messages:
202
+ if message["role"] == "user":
203
+ pending_user = message["content"]
204
+ else:
205
+ pairs.append([pending_user or "", message["content"]])
206
+ pending_user = None
207
+ if pending_user is not None:
208
+ pairs.append([pending_user, None])
209
+ return pairs
210
+
211
+
212
+ # -- status line ---------------------------------------------------------------
213
+ def _status_html(kind: str, n_tokens: int = 0, ttft_ms: float | None = None,
214
+ elapsed: float = 0.0, note: str = "") -> str:
215
+ ttft = f"{ttft_ms:,.0f} ms" if ttft_ms is not None else "…"
216
+ rate = (n_tokens / elapsed) if (elapsed > 1e-6 and n_tokens) else 0.0
217
+ rate_s = f"{rate:,.1f} tok/s" if n_tokens else "…"
218
+ if kind == "gen":
219
+ return ("<div class='p-status'><span class='p-live-dot'></span>"
220
+ f"<span class='p-status-text'>Generating · {n_tokens} tokens · TTFT {ttft} · {rate_s}</span>"
221
+ "<span class='p-status-hint'>press Stop to interrupt</span></div>")
222
+ if kind == "done":
223
+ return ("<div class='p-status'><span class='p-ok-dot'></span>"
224
+ f"<span class='p-status-text'>Done · {n_tokens} tokens · TTFT {ttft} · "
225
+ f"{rate:,.1f} tok/s · {elapsed:,.1f} s</span></div>")
226
+ if kind == "stopped":
227
+ return ("<div class='p-status'><span class='p-warn-dot'></span>"
228
+ f"<span class='p-status-text'>Stopped · {n_tokens} tokens generated · "
229
+ f"{elapsed:,.1f} s</span></div>")
230
+ if kind == "error":
231
+ return ("<div class='p-status'><span class='p-err-dot'></span>"
232
+ f"<span class='p-status-text'>Generation failed · "
233
+ f"{_htmllib.escape(note or 'unknown error')}</span></div>")
234
+ if kind == "empty":
235
+ return ("<div class='p-status'><span class='p-warn-dot'></span>"
236
+ "<span class='p-status-text'>The prompt box is empty — type something first.</span></div>")
237
+ return ("<div class='p-status'><span class='p-idle-dot'></span>"
238
+ "<span class='p-status-text'>Ready · Enter ↵ sends · Shift+Enter adds a line</span></div>")
239
+
240
+
241
+ # -- the streaming handler ------------------------------------------------------
242
  def respond(message, history, max_new_tokens, temperature, top_k, top_p,
243
  repetition_penalty, no_repeat_ngram_size, use_anchor):
244
+ """Stream one continuation into the chatbot. Bound to Send, Enter, and the chips."""
245
+ message = (message or "").strip()
246
+ hist = _norm_history(history)
247
+
248
+ if not message:
249
+ yield (_chat_value(hist), "", _status_html("empty"), gr.update(), gr.update())
250
  return
251
 
252
+ if not _ui_guard.acquire(blocking=False):
253
+ raise gr.Error("Pragya is already writing — press Stop or let it finish first.")
254
+
255
+ try:
256
+ prompt = (_ANCHOR + message) if use_anchor else message
257
+ stop_ev = _new_stop_event()
258
+ pieces: queue.Queue = queue.Queue()
259
+ error: list[BaseException] = []
260
+
261
+ def emit(piece: str) -> None:
262
+ if stop_ev.is_set(): # raised inside generate() to abort it
263
+ raise _GenerationStopped()
264
+ pieces.put(piece)
265
+
266
+ def worker() -> None:
267
+ try:
268
+ ctx = (torch.autocast(device_type=_device.type, dtype=_dtype)
269
+ if _dtype is not None else contextlib.nullcontext())
270
+ with _gen_lock, torch.inference_mode(), ctx:
271
+ generate(
272
+ _model, _tokenizer, prompt, _device,
273
+ max_new_tokens=int(max_new_tokens),
274
+ temperature=float(temperature),
275
+ top_k=int(top_k) if top_k > 0 else None,
276
+ top_p=float(top_p) if top_p < 1.0 else None,
277
+ repetition_penalty=float(repetition_penalty),
278
+ no_repeat_ngram_size=int(no_repeat_ngram_size),
279
+ stream=False,
280
+ use_cache=True,
281
+ on_text=emit,
282
+ )
283
+ except _GenerationStopped:
284
+ pass
285
+ except BaseException as exc: # surfaced in the status line below
286
+ error.append(exc)
287
+ finally:
288
+ pieces.put(None) # end-of-stream marker
289
+
290
+ hist.append({"role": "user", "content": message})
291
+ hist.append({"role": "assistant", "content": "…"})
292
+
293
+ t0 = time.perf_counter()
294
+ ttft: float | None = None
295
+ text = ""
296
+ interrupted = False
297
+ threading.Thread(target=worker, daemon=True).start()
298
+
299
+ yield (_chat_value(hist), "", _status_html("gen", 0, None, 0.0),
300
+ gr.update(interactive=False), gr.update(visible=True))
301
 
 
302
  try:
303
+ while True:
304
+ try:
305
+ piece = pieces.get(timeout=0.2)
306
+ except queue.Empty:
307
+ if stop_ev.is_set():
308
+ interrupted = True
309
+ break
310
+ if ttft is None: # still waiting for the first token
311
+ yield (_chat_value(hist), "",
312
+ _status_html("gen", 0, None, time.perf_counter() - t0),
313
+ gr.update(), gr.update())
314
+ continue
315
+ if piece is None:
316
+ break
317
+ if ttft is None:
318
+ ttft = (time.perf_counter() - t0) * 1000.0
319
+ text += piece
320
+ hist[-1]["content"] = text
321
+ yield (_chat_value(hist), "",
322
+ _status_html("gen", _count(text), ttft, time.perf_counter() - t0),
323
+ gr.update(), gr.update())
324
  finally:
325
+ stop_ev.set() # halts the worker if this stream ends early
326
+
327
+ elapsed = time.perf_counter() - t0
328
+ if error:
329
+ note = f"{type(error[0]).__name__}: {error[0]}"
330
+ hist[-1]["content"] = text or "⚠ Generation failed — see the status line below."
331
+ final_status = _status_html("error", note=note)
332
+ else:
333
+ if not text:
334
+ hist[-1]["content"] = "*(stopped before any output)*" if interrupted else "*(no output)*"
335
+ final_status = _status_html("stopped" if interrupted else "done", _count(text), ttft, elapsed)
336
+ yield (_chat_value(hist), "", final_status,
337
+ gr.update(interactive=True), gr.update(visible=False, interactive=False))
338
+ finally:
339
+ _ui_guard.release()
340
+
341
+
342
+ def _clear_chat():
343
+ """Interrupt any running generation, wait for it to wind down, then reset the UI."""
344
+ _request_stop()
345
+ with _ui_guard: # held by respond() while it streams
346
+ pass
347
+ return ([], "", _status_html("idle"),
348
+ gr.update(interactive=True), gr.update(visible=False, interactive=False))
349
 
350
 
351
  # -- UI -----------------------------------------------------------------------
 
353
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
354
 
355
  :root {
356
+ --p-bg: #fafaf9; --p-card: #ffffff;
357
+ --p-line: #e7e5e4; --p-line-2: #d6d3d1;
358
+ --p-ink: #1c1917; --p-ink-2: #292524; --p-muted: #78716c; --p-faint: #a8a29e;
359
+ --p-accent: #ea580c; --p-accent-hover: #c2410c;
360
+ --p-accent-soft: #fff7ed; --p-accent-line: #fed7aa;
361
+ --p-shadow: 0 1px 2px rgba(28,25,23,.04), 0 16px 40px -24px rgba(28,25,23,.25);
362
  }
 
 
 
363
 
364
+ * { box-sizing: border-box; }
365
+ body, .gradio-container {
366
+ font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif !important;
367
+ background: var(--p-bg) !important;
368
+ color: var(--p-ink) !important;
369
+ }
370
+ .gradio-container {
371
+ max-width: 880px !important;
372
+ margin: 0 auto !important;
373
+ padding: 0 20px 48px !important;
374
+ background:
375
+ radial-gradient(900px 320px at 50% -80px, rgba(255,237,213,.9) 0%, rgba(255,237,213,0) 60%),
376
+ var(--p-bg) !important;
377
+ }
378
+ footer { display: none !important; }
379
+ ::selection { background: var(--p-accent-line); }
380
+
381
+ /* ---------- header ---------- */
382
+ .p-nav { display: flex; align-items: center; gap: 12px; padding: 20px 0 16px; border-bottom: 1px solid var(--p-line); }
383
+ .p-wordmark { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 1.05rem; letter-spacing: -.02em; color: var(--p-ink); }
384
+ .p-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--p-accent); box-shadow: 0 0 0 3.5px var(--p-accent-soft); }
385
+ .p-tag { font-size: .72rem; font-weight: 600; color: var(--p-accent); background: var(--p-accent-soft); border: 1px solid var(--p-accent-line); padding: 3px 10px; border-radius: 999px; }
386
+ .p-nav-right { margin-left: auto; display: flex; gap: 16px; font-size: .74rem; color: var(--p-faint); font-family: 'IBM Plex Mono', monospace; }
387
+
388
+ .p-hero { padding: 44px 0 34px; border-bottom: 1px solid var(--p-line); margin-bottom: 6px; }
389
+ .p-eyebrow { font-size: .72rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--p-accent); margin-bottom: 14px; }
390
+ .p-hero h1 { font-size: clamp(2.05rem, 5vw, 2.8rem); font-weight: 700; letter-spacing: -.035em; line-height: 1.1; margin: 0 0 14px; color: var(--p-ink); }
391
+ .p-grad { background: linear-gradient(92deg, #ea580c 0%, #f59e0b 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
392
+ .p-lede { font-size: .98rem; color: var(--p-muted); line-height: 1.65; max-width: 540px; margin: 0 0 28px; }
393
+ .p-stats { display: flex; flex-wrap: wrap; }
394
+ .p-stat { display: flex; flex-direction: column; gap: 3px; padding: 0 26px; border-left: 1px solid var(--p-line); }
395
+ .p-stat:first-child { padding-left: 0; border-left: none; }
396
+ .p-stat b { font-size: 1.02rem; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; color: var(--p-ink); }
397
+ .p-stat span { font-size: .66rem; color: var(--p-faint); font-weight: 600; text-transform: uppercase; letter-spacing: .07em; }
398
+
399
+ .p-section { font-size: .7rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--p-faint); padding: 22px 0 10px; }
400
+
401
+ /* ---------- conversation ---------- */
402
+ .p-chat-head { align-items: center; margin-top: 10px; }
403
+ .p-chat-title { display: flex; align-items: center; gap: 9px; font-weight: 600; font-size: .92rem; color: var(--p-ink); flex-wrap: wrap; }
404
+ .p-chat-sub { font-weight: 400; font-size: .72rem; color: var(--p-faint); }
405
+ .p-clear { border: 1px solid var(--p-line-2) !important; background: var(--p-card) !important; color: var(--p-muted) !important;
406
+ border-radius: 8px !important; font-size: .75rem !important; font-weight: 600 !important; padding: 5px 14px !important;
407
+ transition: all .15s ease !important; }
408
+ .p-clear:hover { border-color: var(--p-accent) !important; color: var(--p-accent) !important; background: var(--p-accent-soft) !important; }
409
+
410
+ .p-chat { border: 1px solid var(--p-line) !important; border-radius: 16px !important;
411
+ background: var(--p-card) !important; box-shadow: var(--p-shadow) !important; }
412
+
413
+ .message-row { padding: 2px 0; }
414
+ .message-row .message, .message.user, .message.assistant {
415
+ font-size: .95rem !important; line-height: 1.62 !important; padding: 10px 15px !important; margin: 2px 0 !important;
416
+ }
417
+ .message-row.user-row .message, .message.user {
418
+ background: linear-gradient(135deg, #f97316, #ea580c) !important;
419
+ border: 1px solid var(--p-accent-hover) !important;
420
+ color: #ffffff !important;
421
+ border-radius: 14px 14px 4px 14px !important;
422
+ box-shadow: 0 2px 8px -2px rgba(194,65,12,.45) !important;
423
+ }
424
+ .message-row.user-row .message *, .message.user * { color: #ffffff !important; }
425
+ .message-row.bot-row .message, .message.assistant {
426
+ background: var(--p-card) !important;
427
+ border: 1px solid var(--p-line) !important;
428
+ color: var(--p-ink) !important;
429
+ border-radius: 14px 14px 14px 4px !important;
430
+ box-shadow: 0 1px 2px rgba(28,25,23,.05) !important;
431
+ }
432
+ .message-row.bot-row .message *, .message.assistant * { color: var(--p-ink) !important; }
433
+ .message-row.bot-row .message a, .message.assistant a { color: var(--p-accent) !important; }
434
+ .message-row.bot-row code, .message-row.bot-row pre,
435
+ .message.assistant code, .message.assistant pre {
436
+ font-family: 'IBM Plex Mono', monospace !important; background: var(--p-bg) !important;
437
+ border: 1px solid var(--p-line) !important; border-radius: 6px !important; font-size: .82rem !important;
438
+ }
439
+ .message-row img { border-radius: 50% !important; object-fit: cover; }
440
+
441
+ .p-chat ::-webkit-scrollbar, .p-chat *::-webkit-scrollbar { width: 7px; height: 7px; }
442
+ .p-chat ::-webkit-scrollbar-thumb, .p-chat *::-webkit-scrollbar-thumb { background: var(--p-line-2); border-radius: 999px; }
443
+ .p-chat ::-webkit-scrollbar-track, .p-chat *::-webkit-scrollbar-track { background: transparent; }
444
+
445
+ /* ---------- status line ---------- */
446
+ #p-status { padding: 10px 4px 0; }
447
+ #p-status .p-status { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; min-height: 20px;
448
+ font-family: 'IBM Plex Mono', monospace; font-size: .72rem; color: var(--p-muted); }
449
+ .p-live-dot, .p-idle-dot, .p-ok-dot, .p-warn-dot, .p-err-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; }
450
+ .p-live-dot { background: var(--p-accent); animation: p-pulse 1.1s ease-in-out infinite; }
451
+ .p-idle-dot { background: var(--p-faint); }
452
+ .p-ok-dot { background: #16a34a; }
453
+ .p-warn-dot { background: #d97706; }
454
+ .p-err-dot { background: #dc2626; }
455
+ @keyframes p-pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .3; transform: scale(.75); } }
456
+ .p-status-hint { color: var(--p-faint); border-left: 1px solid var(--p-line); padding-left: 9px; }
457
+
458
+ /* ---------- composer ---------- */
459
+ .p-input-row { gap: 10px !important; align-items: stretch !important; }
460
+ .p-input textarea {
461
+ font-family: 'Inter', sans-serif !important; font-size: .95rem !important; line-height: 1.55 !important;
462
+ border: 1.5px solid var(--p-line-2) !important; border-radius: 14px !important;
463
+ background: var(--p-card) !important; padding: 13px 16px !important;
464
+ box-shadow: 0 1px 2px rgba(28,25,23,.04) !important;
465
+ transition: border-color .15s ease, box-shadow .15s ease !important;
466
+ }
467
+ .p-input textarea:focus { border-color: var(--p-accent) !important; box-shadow: 0 0 0 4px rgba(234,88,12,.12) !important; }
468
+ .p-input textarea::placeholder { color: var(--p-faint) !important; }
469
+
470
+ .p-input-actions { display: flex !important; flex-direction: column !important; }
471
+ .p-send {
472
+ flex: 1 1 auto !important; min-height: 64px !important;
473
+ background: linear-gradient(135deg, #f97316, #ea580c) !important;
474
+ color: #ffffff !important; border: none !important; border-radius: 14px !important;
475
+ font-weight: 600 !important; font-size: .95rem !important; font-family: 'Inter', sans-serif !important;
476
+ box-shadow: 0 2px 10px -2px rgba(234,88,12,.55) !important;
477
+ transition: box-shadow .15s ease, transform .08s ease !important;
478
+ }
479
+ .p-send:hover:not(:disabled) { box-shadow: 0 4px 16px -4px rgba(234,88,12,.7) !important; transform: translateY(-1px); }
480
+ .p-send:active:not(:disabled) { transform: translateY(0); }
481
+ .p-send:disabled { opacity: .55; cursor: not-allowed; box-shadow: none !important; }
482
+
483
+ .p-stop {
484
+ width: 100% !important; margin-top: 10px !important;
485
+ background: #fef2f2 !important; border: 1px solid #fecaca !important; color: #b91c1c !important;
486
+ border-radius: 12px !important; font-size: .82rem !important; font-weight: 600 !important;
487
+ padding: 8px !important; transition: all .15s ease !important;
488
+ }
489
+ .p-stop:hover { background: #fee2e2 !important; border-color: #fca5a5 !important; }
490
+
491
+ /* ---------- quick starts ---------- */
492
+ .p-chips { gap: 8px !important; flex-wrap: wrap !important; margin-top: 2px; }
493
+ .p-chips .form { min-width: 0 !important; margin: 0 !important; }
494
+ .p-chip {
495
+ border: 1px solid var(--p-line-2) !important; background: var(--p-card) !important; color: var(--p-ink-2) !important;
496
+ border-radius: 999px !important; font-size: .8rem !important; font-weight: 500 !important;
497
+ font-family: 'Inter', sans-serif !important; padding: 6px 15px !important; transition: all .15s ease !important;
498
+ }
499
+ .p-chip:hover { border-color: var(--p-accent) !important; color: var(--p-accent) !important; background: var(--p-accent-soft) !important; }
500
+
501
+ /* ---------- settings ---------- */
502
+ .p-settings { border: 1px solid var(--p-line) !important; border-radius: 14px !important;
503
+ background: var(--p-card) !important; box-shadow: 0 1px 2px rgba(28,25,23,.04) !important; margin-top: 18px !important; }
504
+ .p-settings .label-wrap, .p-settings summary { font-weight: 600 !important; font-size: .88rem !important; }
505
+ input[type="range"] { accent-color: var(--p-accent) !important; }
506
+ input[type="checkbox"] { accent-color: var(--p-accent) !important; }
507
+
508
+ /* ---------- footer ---------- */
509
+ .p-footer { border-top: 1px solid var(--p-line); padding: 26px 0 8px; margin-top: 26px;
510
+ display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 16px; }
511
+ .p-footer-brand { font-weight: 700; font-size: .9rem; color: var(--p-ink); }
512
+ .p-footer-copy { font-size: .74rem; color: var(--p-faint); margin-top: 5px; max-width: 460px; line-height: 1.6; }
513
  .p-badges { display: flex; gap: 6px; flex-wrap: wrap; }
514
+ .p-badge { font-size: .67rem; font-family: 'IBM Plex Mono', monospace; color: var(--p-muted);
515
+ background: var(--p-card); border: 1px solid var(--p-line); padding: 3px 9px; border-radius: 5px; white-space: nowrap; }
516
  .p-badge.acc { color: var(--p-accent); background: var(--p-accent-soft); border-color: var(--p-accent-line); }
517
+
518
+ @media (max-width: 640px) {
519
+ .p-nav-right { display: none; }
520
+ .p-stat { padding: 0 14px !important; }
521
+ .p-hero { padding: 32px 0 24px; }
522
+ }
523
  """
524
 
525
+ _QUICK_STARTS = ("Hello", "Tell me a story.", "Create a simple story.", "Who developed you?")
526
+
527
 
528
  def _header_html() -> str:
529
  return f"""
530
  <div class="p-nav">
531
  <span class="p-wordmark"><span class="p-dot"></span>Pragya</span>
532
  <span class="p-tag">by Arush Kumar</span>
533
+ <div class="p-nav-right"><span>pytorch</span><span>{_device_lbl.lower()}</span><span>{_n_params}</span></div>
534
  </div>
535
  <div class="p-hero">
536
  <div class="p-eyebrow">From-scratch language model</div>
537
+ <h1>A model built <span class="p-grad">from the ground up.</span></h1>
538
+ <p class="p-lede">A GPT-style architecture with KV-cached decoding, grouped-query attention and
539
+ every weight trained from scratch. Type a prompt and watch it continue — streamed token by token.</p>
540
  <div class="p-stats">
541
  <div class="p-stat"><b>{_n_params}</b><span>Parameters</span></div>
542
+ <div class="p-stat"><b>{_ctx_len:,}</b><span>Context window</span></div>
543
+ <div class="p-stat"><b>{_n_layer}L &middot; {_n_head}/{_kv_heads}H</b><span>Depth &middot; Q/KV heads</span></div>
544
  <div class="p-stat"><b>{_pattern}</b><span>Attention</span></div>
545
  <div class="p-stat"><b>{_dtype_lbl}</b><span>Inference dtype</span></div>
546
  </div>
547
  </div>
 
548
  """
549
 
550
 
 
552
  return f"""
553
  <div class="p-footer">
554
  <div>
555
+ <div class="p-footer-brand">Pragya &middot; <span style="font-weight:400;color:var(--p-muted)">Arush Kumar</span></div>
556
+ <div class="p-footer-copy">A continuation model — earlier turns stay on screen but are never re-sent
557
+ to the model. Enter &#8629; sends &middot; Shift+Enter adds a newline &middot; Stop interrupts mid-stream.
558
+ TTFT and throughput are measured on every generation.</div>
559
  </div>
560
  <div class="p-badges">
561
  <span class="p-badge acc">{_n_params} params</span>
562
  <span class="p-badge">{_device_lbl}</span>
563
  <span class="p-badge">{_dtype_lbl}</span>
564
+ <span class="p-badge">ctx {_ctx_len:,}</span>
565
  <span class="p-badge">{_pattern}</span>
566
+ <span class="p-badge">pytorch</span>
567
  </div>
568
  </div>
569
  """
570
 
571
 
572
+ def _make_chatbot() -> gr.Chatbot:
573
+ """Build the Chatbot with whichever chat-value format this Gradio supports."""
574
+ global _MSG_FORMAT
575
+ supported = inspect.signature(gr.Chatbot.__init__).parameters
576
+ kwargs: dict[str, Any] = dict(height=460, show_label=False, elem_classes="p-chat")
577
+ if "type" in supported:
578
+ kwargs["type"] = "messages"
579
+ if "avatar_images" in supported and _AVATARS:
580
+ kwargs["avatar_images"] = _AVATARS
581
+ if "show_copy_button" in supported:
582
+ kwargs["show_copy_button"] = True
583
+ if "bubble_full_width" in supported:
584
+ kwargs["bubble_full_width"] = False
585
+ if "line_breaks" in supported:
586
+ kwargs["line_breaks"] = True
587
+ chatbot = gr.Chatbot(**kwargs)
588
+ _MSG_FORMAT = kwargs.get("type") == "messages"
589
+ return chatbot
590
+
591
+
592
+ def main() -> None:
593
+ with gr.Blocks(
594
+ title="Pragya · by Arush Kumar",
595
+ css=CSS, # NOTE: css/theme belong on Blocks, not launch(), on Gradio 4/5
596
+ theme=gr.themes.Base(
597
+ primary_hue=gr.themes.colors.orange,
598
+ neutral_hue=gr.themes.colors.stone,
599
+ font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
600
+ font_mono=[gr.themes.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace"],
601
+ ),
602
+ ) as demo:
603
  gr.HTML(_header_html())
604
+
605
+ # -- conversation ----------------------------------------------------------
606
+ with gr.Row(elem_classes="p-chat-head"):
607
+ with gr.Column(scale=1, min_width=0):
608
+ gr.HTML("<div class='p-chat-title'><span class='p-dot'></span>Conversation"
609
+ "<span class='p-chat-sub'>continuations only — earlier turns are not re-sent</span></div>")
610
+ clear_btn = gr.Button("Clear", size="sm", scale=0, min_width=0, elem_classes="p-clear")
611
+
612
+ chatbot = _make_chatbot()
613
+ status = gr.HTML(_status_html("idle"), elem_id="p-status")
614
+
615
+ # -- composer (textbox + Send; Stop appears only while generating) ----------
616
+ with gr.Row(elem_classes="p-input-row"):
617
+ msg = gr.Textbox(
618
+ placeholder="Start a sentence and let Pragya continue it…",
619
+ show_label=False, lines=2, max_lines=6, autofocus=True,
620
+ container=False, scale=1, elem_classes="p-input",
621
+ )
622
+ with gr.Column(scale=0, min_width=118, elem_classes="p-input-actions"):
623
+ send_btn = gr.Button("Send", variant="primary", size="lg", elem_classes="p-send")
624
+ stop_btn = gr.Button("Stop generating", variant="stop", visible=False, elem_classes="p-stop")
625
+
626
+ # -- quick starts ------------------------------------------------------------
627
+ gr.HTML("<div class='p-section'>Quick starts</div>")
628
+ chips: list[tuple[str, gr.Button]] = []
629
+ with gr.Row(elem_classes="p-chips"):
630
+ for example in _QUICK_STARTS:
631
+ chips.append((example, gr.Button(example, size="sm", scale=0, min_width=0,
632
+ elem_classes="p-chip")))
633
+
634
+ # -- generation settings (identical knobs/defaults to before) -----------------
635
+ with gr.Accordion("Generation settings", open=False, elem_classes="p-settings"):
636
+ with gr.Row():
637
+ with gr.Column():
638
+ max_new_tokens = gr.Slider(16, _MAX_TOKENS_CAP, value=_DEFAULT_TOKENS, step=8,
639
+ label="Max new tokens")
640
+ temperature = gr.Slider(0.1, 2.0, value=0.8, step=0.05, label="Temperature")
641
+ top_k = gr.Slider(0, 200, value=50, step=5, label="Top-k",
642
+ info="0 disables top-k.")
643
+ repetition_penalty = gr.Slider(1.0, 2.0, value=1.3, step=0.05,
644
+ label="Repetition penalty",
645
+ info="Count-scaled penalty. 1.0 = off.")
646
+ with gr.Column():
647
+ top_p = gr.Slider(0.5, 1.0, value=1.0, step=0.01, label="Top-p",
648
+ info="1.0 disables nucleus sampling.")
649
+ no_repeat_ngram_size = gr.Slider(0, 6, value=3, step=1, label="No-repeat n-gram",
650
+ info="Bans repeating any n-gram of this length. 0 = off.")
651
+ use_anchor = gr.Checkbox(value=False, label="Narrative anchor",
652
+ info="Prepend a short prose passage to set the style.")
653
+
654
  gr.HTML(_footer_html())
655
 
656
+ # -- wiring --------------------------------------------------------------------
657
+ settings = [max_new_tokens, temperature, top_k, top_p,
658
+ repetition_penalty, no_repeat_ngram_size, use_anchor]
659
+ inputs = [msg, chatbot, *settings]
660
+ outputs = [chatbot, msg, status, send_btn, stop_btn]
661
+
662
+ msg.submit(respond, inputs, outputs)
663
+ send_btn.click(respond, inputs, outputs)
664
+ stop_btn.click(_request_stop, None, None, queue=False) # immediate, bypasses the queue
665
+ clear_btn.click(_clear_chat, None, outputs, queue=False)
666
+
667
+ for example, chip in chips:
668
+ def _run_example(history, *setting_values, _example=example):
669
+ yield from respond(_example, history, *setting_values)
670
+ chip.click(_run_example, [chatbot, *settings], outputs)
671
+
672
  demo.queue().launch(
673
  server_name="0.0.0.0",
674
  server_port=int(os.environ.get("PORT", "7860")),
675
  share=False,
676
+ show_error=True,
 
 
677
  )
678
 
679