ArushBuilds commited on
Commit
64803df
Β·
1 Parent(s): 2735668

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +561 -464
app.py CHANGED
@@ -1,558 +1,655 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
 
3
  import os
4
- os.environ["KERAS_BACKEND"] = "jax"
5
-
6
- import numpy as np
7
- import jax
8
- import keras
9
- import gradio as gr
10
  import time
 
11
  from pathlib import Path
12
 
13
- from veylon_model import create_llm
14
- from tokenizer import TokenizerWrapper
15
- from config import (
16
- CONTEXT,
17
- vocab_size,
18
- D_MODEL,
19
- numberoflayers,
20
- numberofheads,
21
- d_Latent,
22
- ffn_mult,
23
- num_kv_heads,
24
- swa_window,
25
- )
26
-
27
- # ============================================================
28
- # Initialize (runs once)
29
- # ============================================================
30
-
31
- keras.mixed_precision.set_global_policy("mixed_bfloat16")
32
 
33
- print(f"Backend: {keras.backend.backend()}")
34
- print(f"JAX devices: {jax.devices()}")
 
35
 
36
- # Load tokenizer
37
- tokenizer = TokenizerWrapper("tokenizer.model")
38
- assert tokenizer.vocab_size == vocab_size, (
39
- f"Tokenizer vocab ({tokenizer.vocab_size}) != config vocab ({vocab_size})"
40
- )
41
- print(f"βœ“ Tokenizer loaded: {tokenizer.vocab_size} vocab")
42
-
43
- # Build model
44
- print("Building model...")
45
- model = create_llm(
46
- vocab_size=vocab_size,
47
- d_model=D_MODEL,
48
- n_layers=numberoflayers,
49
- n_heads=numberofheads,
50
- d_latent=d_Latent,
51
- ffn_mult=ffn_mult,
52
- max_seq_len=CONTEXT,
53
- use_moe=False,
54
- num_kv_heads=num_kv_heads,
55
- swa_window=swa_window,
56
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- # Warmup
59
- dummy = np.zeros((1, CONTEXT), dtype=np.int32)
60
- _ = model(dummy, training=False)
61
- print("βœ“ Model built successfully")
62
-
63
- # Load weights
64
- WEIGHTS_PATH = "veylon_final.weights.h5"
65
- if Path(WEIGHTS_PATH).exists():
66
- print(f"Loading weights from: {WEIGHTS_PATH}")
67
- model.load_weights(WEIGHTS_PATH)
68
- print("βœ“ Weights loaded successfully")
69
- else:
70
- print(f"WARNING: {WEIGHTS_PATH} not found. Using untrained model.")
71
-
72
- print(f"βœ“ Model params: {model.count_params():,}\n")
73
-
74
- # ============================================================
75
- # Sampling
76
- # ============================================================
77
-
78
- def sample_from_logits(
79
- logits_row: np.ndarray,
80
- temperature: float = 0.7,
81
- min_p: float = 0.05,
82
- ) -> int:
83
- """
84
- Min-P sampling (replaces top-k). Top-k/top-p keep a FIXED-size or
85
- fixed-cumulative-mass candidate pool open regardless of how confident
86
- the model actually is -- on a small/uncertain model, that means when
87
- it doesn't know what comes next, top-k still hands it 50 candidates
88
- to choose from, many of them garbage. Min-P instead sets a threshold
89
- RELATIVE to the top token's probability: confident predictions
90
- collapse the pool to 1-2 tokens, uncertain predictions keep it wide.
91
- Expects and returns a 1D array (single vocab-length row), not (1, vocab).
92
- """
93
- logits_row = np.array(logits_row, dtype=np.float32, copy=True)
94
 
95
- if temperature > 0:
96
- logits_row = logits_row / float(max(temperature, 1e-8))
97
 
98
- row = logits_row - np.max(logits_row) # numerical stability
99
- probs = np.exp(row)
100
- probs = probs / probs.sum()
 
 
 
101
 
102
- if min_p > 0:
103
- threshold = min_p * probs.max()
104
- mask = probs >= threshold
105
- if not mask.any(): # degenerate guard -- never zero out everything
106
- mask[np.argmax(probs)] = True
107
- probs = np.where(mask, probs, 0.0)
108
- probs = probs / probs.sum()
109
 
110
- return int(np.random.choice(len(probs), p=probs))
111
 
112
 
113
- def apply_repetition_controls(
114
- logits_row: np.ndarray,
115
- generated_ids: list,
116
- no_repeat_ngram_size: int = 3,
117
- repetition_penalty: float = 1.2,
118
- ) -> np.ndarray:
119
- """
120
- Applied BEFORE sample_from_logits, on the model's own newly-generated
121
- tokens only (never the user's prompt -- penalizing someone for the
122
- model reusing words from THEIR prompt would be wrong, this is about
123
- stopping the model's own loops).
124
-
125
- 1. repetition_penalty (soft): divides a positive logit for an
126
- already-seen token, MULTIPLIES a negative one -- always pushes the
127
- token down, regardless of logit sign (a naive "divide by penalty"
128
- for a negative logit would actually push it UP, backwards).
129
- 2. no_repeat_ngram_size (hard): if continuing with a candidate token
130
- would recreate an n-gram that's already appeared in this
131
- generation, that candidate is set to -inf. This is what actually
132
- kills infinite loops -- it's a hard constraint, not a nudge.
133
- """
134
- logits_row = logits_row.copy()
135
-
136
- if repetition_penalty and repetition_penalty != 1.0 and generated_ids:
137
- for tok_id in set(generated_ids):
138
- if logits_row[tok_id] > 0:
139
- logits_row[tok_id] /= repetition_penalty
140
- else:
141
- logits_row[tok_id] *= repetition_penalty
142
-
143
- n = no_repeat_ngram_size
144
- if n and n > 0 and len(generated_ids) >= n - 1:
145
- prefix = tuple(generated_ids[-(n - 1):]) if n > 1 else tuple()
146
- banned = set()
147
- for i in range(len(generated_ids) - n + 1):
148
- if tuple(generated_ids[i:i + n - 1]) == prefix:
149
- banned.add(generated_ids[i + n - 1])
150
- for tok_id in banned:
151
- logits_row[tok_id] = -np.inf
152
-
153
- return logits_row
154
-
155
-
156
- # Few-shot "anchoring": Veylon is a CONTINUATION-only base model, never
157
- # instruction-tuned -- it has no concept of "Input:"/"Output:" turns, so
158
- # priming it with instruction-style examples (as generic small-LM advice
159
- # often suggests) would push it OUT of its training distribution, not
160
- # into it. What anchoring means correctly here is different: prepend a
161
- # short, clean, well-formed piece of narrative prose so the model's
162
- # recent-attention window is dominated by coherent English *before*
163
- # generation starts, nudging it away from degenerating into word salad.
164
- # This consumes some of the context budget, so it's short and toggleable.
165
- ANCHOR_TEXT = (
166
- "The old lighthouse stood at the edge of the cliff, its light "
167
- "sweeping slowly across the dark water. Every night, the keeper "
168
- "climbed the narrow stairs to check the lamp, and every night the "
169
- "sea answered with the steady sound of waves against the rocks.\n\n"
170
  )
171
 
172
- # ============================================================
173
- # Streaming generation with live telemetry
174
- # ============================================================
 
 
175
 
176
  def respond(
177
  message: str,
178
  history: list,
179
- max_new_tokens: int = 96,
180
- temperature: float = 0.7,
181
- min_p: float = 0.05,
182
- no_repeat_ngram_size: int = 3,
183
- repetition_penalty: float = 1.2,
184
- use_anchor: bool = True,
 
185
  ):
186
- """
187
- gr.ChatInterface-compatible streaming generator. Yields incrementally
188
- as tokens are produced, and appends a real (measured, not decorative)
189
- telemetry line -- token count / time-to-first-token / tokens-per-
190
- second -- under the reply, matching the "signature element" of this
191
- UI: honest, live numbers about what the model is actually doing.
192
-
193
- IMPORTANT, honest by design: `history` is accepted (gr.ChatInterface
194
- requires the signature) and IS shown to the person as an ongoing
195
- conversation, but is deliberately NOT concatenated into the prompt
196
- fed to the model. Arya is a continuation-only base model, never
197
- instruction-tuned on multi-turn User/Assistant-style dialogue at
198
- scale -- feeding it a growing instruction-formatted transcript would
199
- push it further out of its training distribution with every turn,
200
- not closer to coherence. Each message is generated as its own fresh
201
- continuation (with the anchor primer). This is stated plainly in the
202
- UI caption rather than left as a surprise.
203
- """
204
- try:
205
- full_prompt = (ANCHOR_TEXT + message) if use_anchor else message
 
 
206
 
207
- tokens = tokenizer.encode(full_prompt, add_bos=True, add_eos=False)
208
- if len(tokens) == 0:
209
- tokens = [tokenizer.bos_id if hasattr(tokenizer, "bos_id") else 1]
210
- tokens = tokens[-CONTEXT:]
211
 
212
- generated_ids: list = []
213
- t_start = time.perf_counter()
214
- ttft = None
 
 
215
 
216
- prompt_ids = np.array([tokens], dtype=np.int32)
217
- logits, cache_k, cache_v = model.generate_step(
218
- prompt_ids, cache_k=None, cache_v=None, cache_pos=0,
219
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
- logits_row = np.array(logits[0, -1, :], dtype=np.float32, copy=True)
222
- logits_row = apply_repetition_controls(
223
- logits_row, generated_ids, no_repeat_ngram_size, repetition_penalty
224
- )
225
- next_token = sample_from_logits(logits_row, temperature=temperature, min_p=min_p)
226
- tokens.append(next_token)
227
- generated_ids.append(next_token)
228
- ttft = time.perf_counter() - t_start
229
-
230
- partial_text = tokenizer.decode(generated_ids)
231
- yield _with_telemetry(partial_text, len(generated_ids), ttft, t_start)
232
-
233
- if next_token != tokenizer.eos_id and len(tokens) < CONTEXT:
234
- cache_pos = len(prompt_ids[0])
235
-
236
- for _ in range(max_new_tokens - 1):
237
- next_input = np.array([[next_token]], dtype=np.int32)
238
- logits, cache_k, cache_v = model.generate_step(
239
- next_input, cache_k=cache_k, cache_v=cache_v, cache_pos=cache_pos,
240
- )
241
- cache_pos += 1
242
-
243
- logits_row = np.array(logits[0, -1, :], dtype=np.float32, copy=True)
244
- logits_row = apply_repetition_controls(
245
- logits_row, generated_ids, no_repeat_ngram_size, repetition_penalty
246
- )
247
- next_token = sample_from_logits(logits_row, temperature=temperature, min_p=min_p)
248
- tokens.append(next_token)
249
- generated_ids.append(next_token)
250
-
251
- if next_token == tokenizer.eos_id:
252
- break
253
-
254
- partial_text = tokenizer.decode(generated_ids)
255
- yield _with_telemetry(partial_text, len(generated_ids), ttft, t_start)
256
-
257
- if len(tokens) >= CONTEXT:
258
- break
259
-
260
- except Exception as e:
261
- yield f"Error: {str(e)}"
262
-
263
-
264
- def _with_telemetry(text: str, n_tok: int, ttft: float, t_start: float) -> str:
265
- elapsed = max(time.perf_counter() - t_start, 1e-6)
266
- tok_per_s = n_tok / elapsed
267
- telemetry = (
268
- f"\n\n<sub style='font-family:monospace;color:var(--body-text-color-subdued)'>"
269
- f"{n_tok} tok Β· TTFT {ttft * 1000:.0f} ms Β· {tok_per_s:.1f} tok/s</sub>"
270
- )
271
- return text + telemetry
272
 
 
273
 
274
- # ============================================================
275
- # Gradio UI β€” Arya
276
- # ============================================================
277
- # NOTE on "gr.server()": there is no such function in Gradio. gr.Blocks
278
- # and gr.ChatInterface are ALREADY built on FastAPI/Uvicorn internally --
279
- # demo.launch() below is what actually starts that server. Same
280
- # clarification as the earlier "use FastAPI for speed" question: the web
281
- # framework was never the bottleneck for a custom JAX model like this.
282
 
283
- ARYA_CSS = """
284
  :root {
285
- --arya-bg: #100C1A;
286
- --arya-bg-2: #17111F;
287
- --arya-panel: #1D1730;
288
- --arya-panel-2: #271F42;
289
- --arya-accent: #E8A33D;
290
- --arya-accent-soft: #F0BE73;
291
- --arya-accent-2: #5FBFA8;
292
- --arya-indigo: #5B67D9;
293
- --arya-text: #F3EEE3;
294
- --arya-text-dim: #948BAA;
295
- --arya-hairline: #2A2440;
296
-
297
- /* Retheme Gradio's OWN documented CSS variables (verified against the
298
- installed theme's actual variable names) instead of guessing at
299
- hashed/scoped internal component classes -- this cascades correctly
300
- into sliders, checkboxes, accordions, buttons, etc. automatically,
301
- and won't silently stop working on a Gradio version bump the way
302
- hand-guessed internal class names would. */
303
- --body-background-fill: var(--arya-bg);
304
- --background-fill-primary: var(--arya-bg);
305
- --background-fill-secondary: var(--arya-panel);
306
- --block-background-fill: var(--arya-panel);
307
- --block-border-color: var(--arya-hairline);
308
- --block-title-text-color: var(--arya-text-dim);
309
- --block-label-text-color: var(--arya-text-dim);
310
- --body-text-color: var(--arya-text);
311
- --body-text-color-subdued: var(--arya-text-dim);
312
- --border-color-primary: var(--arya-hairline);
313
- --border-color-accent: var(--arya-accent);
314
- --color-accent: var(--arya-accent);
315
- --color-accent-soft: color-mix(in srgb, var(--arya-accent) 20%, transparent);
316
- --input-background-fill: var(--arya-panel-2);
317
- --input-border-color: var(--arya-hairline);
318
- --input-border-color-focus: var(--arya-accent);
319
- --input-placeholder-color: var(--arya-text-dim);
320
- --slider-color: var(--arya-accent);
321
- --checkbox-background-color-selected: var(--arya-accent);
322
- --checkbox-border-color-selected: var(--arya-accent);
323
- --checkbox-label-background-fill-selected: color-mix(in srgb, var(--arya-accent) 18%, var(--arya-panel));
324
- --button-primary-background-fill: linear-gradient(135deg, var(--arya-accent), var(--arya-accent-soft));
325
- --button-primary-background-fill-hover: var(--arya-accent-soft);
326
- --button-primary-text-color: #201306;
327
- --button-secondary-background-fill: var(--arya-panel-2);
328
- --button-secondary-border-color: var(--arya-hairline);
329
- --button-secondary-background-fill-hover: var(--arya-panel);
330
- --link-text-color: var(--arya-accent-2);
331
- }
332
-
333
- * { scrollbar-width: thin; scrollbar-color: var(--arya-panel-2) transparent; }
334
- ::-webkit-scrollbar { width: 8px; height: 8px; }
335
- ::-webkit-scrollbar-thumb { background: var(--arya-panel-2); border-radius: 8px; }
336
- ::-webkit-scrollbar-thumb:hover { background: var(--arya-hairline); }
 
 
 
 
 
 
337
 
338
  .gradio-container {
339
- background:
340
- radial-gradient(ellipse 900px 400px at 15% -10%, color-mix(in srgb, var(--arya-accent) 10%, transparent), transparent 60%),
341
- radial-gradient(ellipse 700px 400px at 100% 0%, color-mix(in srgb, var(--arya-indigo) 12%, transparent), transparent 55%),
342
- var(--arya-bg) !important;
343
- font-family: 'Inter', system-ui, sans-serif !important;
344
- max-width: 880px !important;
345
  }
346
 
347
- /* ── Topbar ── */
348
- #arya-topbar {
349
  display: flex;
350
  align-items: center;
 
 
 
351
  gap: 12px;
352
- padding: 18px 4px 16px 4px;
353
- border-bottom: 1px solid var(--arya-hairline);
354
- margin-bottom: 10px;
355
- }
356
- #arya-mark {
357
- width: 34px; height: 34px; flex-shrink: 0;
358
- display: flex; align-items: center; justify-content: center;
359
- border-radius: 10px;
360
- background: linear-gradient(135deg, var(--arya-accent), var(--arya-indigo));
361
- box-shadow: 0 2px 14px color-mix(in srgb, var(--arya-accent) 35%, transparent);
362
- }
363
- #arya-mark svg { width: 19px; height: 19px; }
364
- #arya-title-block { display: flex; flex-direction: column; line-height: 1.15; }
365
- #arya-title {
366
- font-family: 'IBM Plex Mono', monospace;
367
  font-weight: 700;
368
- font-size: 1.18rem;
369
- letter-spacing: 0.14em;
370
- color: var(--arya-text);
 
 
 
 
371
  }
372
- #arya-subtitle {
373
- font-family: 'IBM Plex Mono', monospace;
374
- font-size: 0.66rem;
375
- letter-spacing: 0.06em;
376
- color: var(--arya-text-dim);
377
  }
378
- #arya-status-chip {
379
- font-family: 'IBM Plex Mono', monospace;
380
  font-size: 0.72rem;
381
  font-weight: 500;
382
- color: var(--arya-accent-2);
383
- display: flex; align-items: center; gap: 6px;
384
- padding: 5px 10px;
 
385
  border-radius: 999px;
386
- background: color-mix(in srgb, var(--arya-accent-2) 12%, transparent);
387
- border: 1px solid color-mix(in srgb, var(--arya-accent-2) 30%, transparent);
388
- margin-left: 14px;
389
- }
390
- #arya-status-chip::before {
391
- content: "";
392
- width: 6px; height: 6px; border-radius: 50%;
393
- background: var(--arya-accent-2);
394
- box-shadow: 0 0 8px var(--arya-accent-2);
395
- animation: arya-pulse 2s ease-in-out infinite;
396
- }
397
- @keyframes arya-pulse {
398
- 0%, 100% { opacity: 1; transform: scale(1); }
399
- 50% { opacity: 0.5; transform: scale(0.8); }
400
- }
401
- #arya-specs {
402
  margin-left: auto;
403
- display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end;
 
 
404
  }
405
- .arya-chip {
406
- font-family: 'IBM Plex Mono', monospace;
407
- font-size: 0.66rem;
408
- letter-spacing: 0.03em;
409
- color: var(--arya-text-dim);
410
- padding: 4px 9px;
411
- border-radius: 6px;
412
- background: var(--arya-panel);
413
- border: 1px solid var(--arya-hairline);
414
- white-space: nowrap;
415
  }
 
416
 
417
- /* ── Chat transcript (verified real Gradio 6 Chatbot classes, not guessed:
418
- .user-row / .bot-row / .message-bubble-border confirmed present in the
419
- installed package's compiled component CSS) ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  .user-row {
421
- background: linear-gradient(135deg, var(--arya-accent), var(--arya-accent-soft)) !important;
422
- color: #201306 !important;
423
  }
424
- .user-row * { color: #201306 !important; }
 
 
425
  .bot-row {
426
- background: var(--arya-panel) !important;
427
- border: 1px solid var(--arya-hairline) !important;
 
428
  }
 
429
  .message-bubble-border {
430
- border-color: var(--arya-hairline) !important;
431
- box-shadow: 0 4px 18px rgba(0,0,0,0.25);
432
- animation: arya-rise 0.25s ease-out;
433
- }
434
- @keyframes arya-rise {
435
- from { opacity: 0; transform: translateY(6px); }
436
- to { opacity: 1; transform: translateY(0); }
437
  }
 
 
438
  .bot-row code, .bot-row pre {
439
  font-family: 'IBM Plex Mono', monospace !important;
440
- background: var(--arya-bg-2) !important;
441
- border: 1px solid var(--arya-hairline) !important;
442
  border-radius: 6px !important;
 
443
  }
444
 
445
- /* ── Input row ── */
446
- textarea, input[type="text"] {
447
- transition: border-color 0.15s ease, box-shadow 0.15s ease !important;
 
 
 
 
 
 
 
448
  }
449
- textarea:focus, input[type="text"]:focus {
450
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--arya-accent) 22%, transparent) !important;
 
 
451
  }
 
 
452
  button.primary {
453
- transition: transform 0.12s ease, box-shadow 0.12s ease !important;
454
- box-shadow: 0 2px 12px color-mix(in srgb, var(--arya-accent) 30%, transparent) !important;
 
 
 
 
 
 
 
 
 
 
455
  }
456
- button.primary:hover { transform: translateY(-1px) scale(1.03); }
457
 
458
- /* ── Settings accordion & example chips: themed via the CSS variables
459
- above, no internal class names needed ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  .example {
461
- transition: transform 0.12s ease, border-color 0.12s ease !important;
 
 
 
 
 
 
 
462
  }
463
  .example:hover {
464
- transform: translateY(-2px);
465
- border-color: var(--arya-accent) !important;
 
466
  }
467
 
468
- #arya-caption {
469
- font-family: 'IBM Plex Mono', monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  font-size: 0.68rem;
471
- color: var(--arya-text-dim);
472
- text-align: center;
473
- padding: 16px 20px 6px 20px;
474
- letter-spacing: 0.03em;
475
- border-top: 1px solid var(--arya-hairline);
476
- margin-top: 12px;
 
 
 
 
 
 
 
 
477
  }
478
- """
479
-
480
- ARYA_HEAD = """
481
- <link rel="preconnect" href="https://fonts.googleapis.com">
482
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;700&display=swap" rel="stylesheet">
483
- """
484
 
485
- # Simple geometric monogram, not a decorative flourish -- the gradient fill
486
- # (accent -> indigo) matches #arya-mark's CSS background, single mark
487
- # rather than a busier logo, keeps it legible at 19px.
488
- ARYA_MARK_SVG = """
489
- <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
490
- <path d="M12 3L21 20H16.5L12 11L7.5 20H3L12 3Z" fill="#100C1A"/>
491
- </svg>
492
  """
493
 
494
 
495
  def main():
496
- spec_chips = [
497
- f"{model.count_params() / 1e6:.1f}M PARAMS",
498
- f"{numberoflayers}L",
499
- f"CTX {CONTEXT}",
500
- f"GQA {numberofheads}/{num_kv_heads}",
501
- ]
502
- spec_html = "".join(f'<span class="arya-chip">{c}</span>' for c in spec_chips)
503
-
504
- with gr.Blocks(title="Arya") as demo:
505
-
506
- gr.HTML(f"""
507
- <div id="arya-topbar">
508
- <div id="arya-mark">{ARYA_MARK_SVG}</div>
509
- <div id="arya-title-block">
510
- <span id="arya-title">ARYA</span>
511
- <span id="arya-subtitle">FROM-SCRATCH SMALL LM</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  </div>
513
- <span id="arya-status-chip">READY</span>
514
- <div id="arya-specs">{spec_html}</div>
515
  </div>
516
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  gr.ChatInterface(
519
  fn=respond,
520
  additional_inputs=[
521
- gr.Slider(10, 256, value=96, step=10, label="Max tokens"),
522
- gr.Slider(0.1, 2.0, value=0.7, step=0.1, label="Temperature"),
523
- gr.Slider(0.0, 0.3, value=0.05, step=0.01, label="Min-P",
524
- info="Replaces top-k. Higher = stricter."),
525
- gr.Slider(0, 6, value=3, step=1, label="No-repeat n-gram size",
526
- info="Hard-blocks repeating any phrase this long. 0 disables."),
527
- gr.Slider(1.0, 1.5, value=1.2, step=0.05, label="Repetition penalty"),
 
 
 
 
 
 
 
 
 
528
  gr.Checkbox(value=True, label="Narrative anchor",
529
- info="Primes the model with clean prose before your prompt."),
530
  ],
531
- additional_inputs_accordion=gr.Accordion("Settings", open=False),
 
 
 
 
 
 
 
 
 
 
 
532
  examples=[
533
- ["Once upon a time, in a village by the mountains,"],
534
- ["The scientist looked at the data and realized"],
535
- ["Explain photosynthesis simply."],
 
536
  ],
537
- chatbot=gr.Chatbot(height=460, show_label=False),
538
- textbox=gr.Textbox(placeholder="Ask Arya anything...", show_label=False),
539
  )
540
 
541
- gr.HTML("""
542
- <div id="arya-caption">
543
- CONTINUATION MODEL, NOT INSTRUCTION-TUNED β€” EACH REPLY IS A FRESH
544
- GENERATION, NOT A REMEMBERED CONVERSATION Β· RUNS FULLY ON YOUR HARDWARE
545
- </div>
546
- """)
547
 
548
- # css/head/theme live on launch(), not Blocks(), as of Gradio 6 --
549
- # verified against the actually-installed version rather than assumed.
550
  demo.launch(
551
- share=True, server_name="0.0.0.0", server_port=7860,
552
- css=ARYA_CSS, head=ARYA_HEAD,
553
- theme=gr.themes.Base(primary_hue=gr.themes.colors.orange,
554
- neutral_hue=gr.themes.colors.slate),
 
 
 
 
 
555
  )
556
 
 
557
  if __name__ == "__main__":
558
  main()
 
1
+ """
2
+ app.py β€” Pragya by Arush Kumar Β· Hugging Face Spaces (PyTorch backend)
3
+
4
+ Expects alongside app.py:
5
+ model.py – GPT / GPTConfig (training codebase, unchanged)
6
+ inference.py – generate(), load_model(), sampling helpers
7
+ tokenizer.py – TokenizerWrapper
8
+ tokenizer.model – SentencePiece vocab file
9
+ checkpoint.pt – trained checkpoint (from train.py)
10
+
11
+ Environment variables (Space Settings β†’ Variables):
12
+ CHECKPOINT_PATH path to local checkpoint (default ./checkpoint.pt)
13
+ CHECKPOINT_REPO HF Hub repo id (e.g. yourname/pragya)
14
+ CHECKPOINT_FILE filename inside Hub repo (default checkpoint.pt)
15
+ TOKENIZER_PATH path to tokenizer.model (default ./tokenizer.model)
16
+ MAX_TOKENS_CAP hard cap on slider (default 512)
17
+ DEFAULT_MAX_TOKENS default slider value (default 128)
18
+ """
19
+
20
  from __future__ import annotations
21
 
22
+ import contextlib
23
  import os
24
+ import sys
 
 
 
 
 
25
  import time
26
+ import threading
27
  from pathlib import Path
28
 
29
+ import torch
30
+ import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ _HERE = Path(__file__).resolve().parent
33
+ if str(_HERE) not in sys.path:
34
+ sys.path.insert(0, str(_HERE))
35
 
36
+ from inference import ( # noqa: E402
37
+ load_model, pick_device, _resolve_dtype,
38
+ _apply_repetition_penalty, _block_repeated_ngrams,
39
+ _top_k_filter, _top_p_filter,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  )
41
+ from tokenizer import TokenizerWrapper # noqa: E402
42
+
43
+ # ── env config ───────────────────────────────────────────────────────────────
44
+ _CHECKPOINT_LOCAL = os.environ.get("CHECKPOINT_PATH", str(_HERE / "checkpoint.pt"))
45
+ _HF_REPO = os.environ.get("CHECKPOINT_REPO", "")
46
+ _HF_FILE = os.environ.get("CHECKPOINT_FILE", "pragya.pt")
47
+ _TOKENIZER = os.environ.get("TOKENIZER_PATH", str(_HERE / "tokenizer.model"))
48
+ _MAX_TOKENS_CAP = int(os.environ.get("MAX_TOKENS_CAP", "512"))
49
+ _DEFAULT_TOKENS = int(os.environ.get("DEFAULT_MAX_TOKENS", "128"))
50
+
51
+
52
+ def _resolve_checkpoint() -> str:
53
+ if _HF_REPO and _HF_FILE:
54
+ try:
55
+ from huggingface_hub import hf_hub_download
56
+ print(f"[App] Pulling {_HF_REPO}/{_HF_FILE} from HF Hub …", flush=True)
57
+ return hf_hub_download(repo_id=_HF_REPO, filename=_HF_FILE)
58
+ except Exception as exc:
59
+ print(f"[App] Hub download failed ({exc}), trying local.", flush=True)
60
+ if Path(_CHECKPOINT_LOCAL).exists():
61
+ return _CHECKPOINT_LOCAL
62
+ raise FileNotFoundError(
63
+ f"No checkpoint at {_CHECKPOINT_LOCAL}. "
64
+ "Set CHECKPOINT_REPO + CHECKPOINT_FILE to pull from HF Hub."
65
+ )
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ # ── one-time model init ───────────────────────────────────────────────────────
69
+ print("[App] Initialising …", flush=True)
70
 
71
+ _device = pick_device(None)
72
+ _cast_dtype = _resolve_dtype("auto", _device)
73
+ _ckpt_path = _resolve_checkpoint()
74
+ _model, _cfg = load_model(_ckpt_path, _device)
75
+ _model.eval()
76
+ _tokenizer = TokenizerWrapper(_TOKENIZER)
77
 
78
+ _n_params_str = f"{_model.get_num_params() / 1e6:.1f}M"
79
+ _ctx_len = _cfg.block_size
80
+ _pattern = getattr(_cfg, "pattern", "dense").upper()
81
+ _n_layer = getattr(_cfg, "n_layer", "?")
82
+ _n_head = getattr(_cfg, "n_head", "?")
83
+ _kv_heads = getattr(_cfg, "num_kv_heads", _n_head)
84
+ _gen_lock = threading.Lock()
85
 
86
+ print(f"[App] Ready β€” {_n_params_str} params ctx={_ctx_len} device={_device}", flush=True)
87
 
88
 
89
+ # ── generation ────────────────────────────────────────────────────────────────
90
+ _ANCHOR = (
91
+ "The old lighthouse stood at the edge of the cliff, its beam sweeping "
92
+ "slowly across the dark water. Every night the keeper climbed the narrow "
93
+ "stairs to check the lamp, and every night the sea answered with the steady "
94
+ "sound of waves breaking against the rocks far below.\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  )
96
 
97
+ def _cast_ctx():
98
+ if _cast_dtype is not None and _device.type in ("cuda", "cpu"):
99
+ return torch.autocast(device_type=_device.type, dtype=_cast_dtype)
100
+ return contextlib.nullcontext()
101
+
102
 
103
  def respond(
104
  message: str,
105
  history: list,
106
+ max_new_tokens: int,
107
+ temperature: float,
108
+ top_k: int,
109
+ top_p: float,
110
+ repetition_penalty: float,
111
+ no_repeat_ngram_size: int,
112
+ use_anchor: bool,
113
  ):
114
+ if not message.strip():
115
+ yield "Please enter a prompt."
116
+ return
117
+
118
+ prompt = (_ANCHOR + message) if use_anchor else message
119
+ prompt_ids = _tokenizer.encode(prompt, add_bos=True, add_eos=False)
120
+ if len(prompt_ids) >= _ctx_len:
121
+ prompt_ids = prompt_ids[-(_ctx_len - 1):]
122
+
123
+ generated_ids : list[int] = []
124
+ eos_id = getattr(_tokenizer, "eos_id", None)
125
+ t_start = time.perf_counter()
126
+ ttft : float | None = None
127
+
128
+ def _stats(n: int) -> str:
129
+ elapsed = max(time.perf_counter() - t_start, 1e-6)
130
+ ttft_ms = (ttft * 1000) if ttft is not None else 0.0
131
+ return (
132
+ f"\n\n<span style='font-size:11px;font-family:\"IBM Plex Mono\",monospace;"
133
+ f"color:#9ca3af;letter-spacing:0.03em'>"
134
+ f"{n} tokens Β· TTFT {ttft_ms:.0f} ms Β· {n/elapsed:.1f} tok/s</span>"
135
+ )
136
 
137
+ idx = torch.tensor([prompt_ids], dtype=torch.long, device=_device)
 
 
 
138
 
139
+ with _gen_lock, torch.no_grad(), _cast_ctx():
140
+ for _ in range(max_new_tokens):
141
+ idx_cond = idx if idx.size(1) <= _ctx_len else idx[:, -_ctx_len:]
142
+ logits, _ = _model(idx_cond)
143
+ logits = logits[:, -1, :].float()
144
 
145
+ row = logits[0:1]
146
+ row = _apply_repetition_penalty(row, generated_ids, repetition_penalty)
147
+ row = _block_repeated_ngrams(row, generated_ids, no_repeat_ngram_size)
148
+ row = row / max(temperature, 1e-5)
149
+ if top_k > 0:
150
+ row = _top_k_filter(row, top_k)
151
+ if top_p < 1.0:
152
+ row = _top_p_filter(row, top_p)
153
+
154
+ probs = torch.softmax(row, dim=-1)
155
+ next_id = torch.multinomial(probs, num_samples=1).item()
156
+
157
+ if ttft is None:
158
+ ttft = time.perf_counter() - t_start
159
+
160
+ generated_ids.append(next_id)
161
+ idx = torch.cat([idx, torch.tensor([[next_id]], device=_device)], dim=1)
162
+
163
+ if eos_id is not None and next_id == eos_id:
164
+ break
165
+
166
+ full = _tokenizer.decode(generated_ids)
167
+ yield full + _stats(len(generated_ids))
168
+
169
+ final = _tokenizer.decode(generated_ids) or "(no output)"
170
+ yield final + _stats(len(generated_ids))
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
+ # ── UI ────────────────────────────────────────────────────────────────────────
174
 
175
+ CSS = """
176
+ /* ══════════════════════════════════════════════════════
177
+ Sarvam-style: warm white Β· near-black Β· saffron accent
178
+ Inter (body) + IBM Plex Mono (code / telemetry)
179
+ ══════════════════════════════════════════════════════ */
180
+
181
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
 
182
 
 
183
  :root {
184
+ /* surface */
185
+ --s-bg: #fafaf9;
186
+ --s-white: #ffffff;
187
+ --s-warm-50: #f5f5f4;
188
+ --s-warm-100: #e7e5e4;
189
+ --s-warm-200: #d6d3d1;
190
+
191
+ /* text */
192
+ --s-ink: #0a0a0a;
193
+ --s-ink-2: #292524;
194
+ --s-muted: #78716c;
195
+ --s-faint: #a8a29e;
196
+
197
+ /* accent β€” saffron */
198
+ --s-saf: #ea580c;
199
+ --s-saf-hover: #c2410c;
200
+ --s-saf-light: #fff7ed;
201
+ --s-saf-mid: #fed7aa;
202
+
203
+ /* Gradio token overrides */
204
+ --body-background-fill: var(--s-bg);
205
+ --background-fill-primary: var(--s-white);
206
+ --background-fill-secondary: var(--s-warm-50);
207
+ --block-background-fill: var(--s-white);
208
+ --block-border-color: var(--s-warm-100);
209
+ --block-title-text-color: var(--s-muted);
210
+ --block-label-text-color: var(--s-muted);
211
+ --body-text-color: var(--s-ink);
212
+ --body-text-color-subdued: var(--s-muted);
213
+ --border-color-primary: var(--s-warm-100);
214
+ --border-color-accent: var(--s-saf);
215
+ --color-accent: var(--s-saf);
216
+ --input-background-fill: var(--s-white);
217
+ --input-border-color: var(--s-warm-200);
218
+ --input-border-color-focus: var(--s-saf);
219
+ --input-placeholder-color: var(--s-faint);
220
+ --slider-color: var(--s-saf);
221
+ --checkbox-background-color-selected: var(--s-saf);
222
+ --checkbox-border-color-selected: var(--s-saf);
223
+ --checkbox-label-background-fill-selected:
224
+ color-mix(in srgb, var(--s-saf) 8%, var(--s-white));
225
+ --button-primary-background-fill: var(--s-saf);
226
+ --button-primary-background-fill-hover: var(--s-saf-hover);
227
+ --button-primary-text-color: #ffffff;
228
+ --button-secondary-background-fill: var(--s-white);
229
+ --button-secondary-border-color: var(--s-warm-200);
230
+ --button-secondary-background-fill-hover: var(--s-warm-50);
231
+ --link-text-color: var(--s-saf);
232
+ }
233
+
234
+ /* ── Base ──────────────────────────────────────────────── */
235
+ * { box-sizing: border-box; }
236
+
237
+ body, .gradio-container {
238
+ font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
239
+ background: var(--s-bg) !important;
240
+ color: var(--s-ink) !important;
241
+ }
242
 
243
  .gradio-container {
244
+ max-width: 820px !important;
245
+ margin: 0 auto !important;
246
+ padding: 0 20px !important;
 
 
 
247
  }
248
 
249
+ /* ── Nav bar ──────────────────────────────────────────── */
250
+ #s-nav {
251
  display: flex;
252
  align-items: center;
253
+ padding: 18px 0 16px;
254
+ border-bottom: 1px solid var(--s-warm-100);
255
+ margin-bottom: 0;
256
  gap: 12px;
257
+ }
258
+ #s-wordmark {
259
+ font-family: 'Inter', sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
260
  font-weight: 700;
261
+ font-size: 1.05rem;
262
+ letter-spacing: -0.02em;
263
+ color: var(--s-ink);
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 8px;
267
+ text-decoration: none;
268
  }
269
+ #s-wordmark-dot {
270
+ width: 8px; height: 8px; border-radius: 50%;
271
+ background: var(--s-saf);
272
+ flex-shrink: 0;
 
273
  }
274
+ #s-nav-tag {
 
275
  font-size: 0.72rem;
276
  font-weight: 500;
277
+ color: var(--s-saf);
278
+ background: var(--s-saf-light);
279
+ border: 1px solid var(--s-saf-mid);
280
+ padding: 3px 9px;
281
  border-radius: 999px;
282
+ letter-spacing: 0.01em;
283
+ }
284
+ #s-nav-right {
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  margin-left: auto;
286
+ display: flex;
287
+ align-items: center;
288
+ gap: 20px;
289
  }
290
+ .s-nav-link {
291
+ font-size: 0.82rem;
292
+ color: var(--s-muted);
293
+ text-decoration: none;
294
+ font-weight: 500;
295
+ transition: color 0.12s;
 
 
 
 
296
  }
297
+ .s-nav-link:hover { color: var(--s-ink); }
298
 
299
+ /* ── Hero ─────────────────────────────────────────────── */
300
+ #s-hero {
301
+ padding: 56px 0 40px;
302
+ border-bottom: 1px solid var(--s-warm-100);
303
+ }
304
+ #s-hero-eyebrow {
305
+ font-size: 0.78rem;
306
+ font-weight: 600;
307
+ letter-spacing: 0.06em;
308
+ text-transform: uppercase;
309
+ color: var(--s-saf);
310
+ margin-bottom: 16px;
311
+ }
312
+ #s-hero h1 {
313
+ font-size: clamp(2rem, 5vw, 2.8rem);
314
+ font-weight: 700;
315
+ letter-spacing: -0.03em;
316
+ line-height: 1.12;
317
+ color: var(--s-ink);
318
+ margin: 0 0 16px;
319
+ max-width: 560px;
320
+ }
321
+ #s-hero-desc {
322
+ font-size: 1rem;
323
+ color: var(--s-muted);
324
+ line-height: 1.65;
325
+ max-width: 480px;
326
+ margin-bottom: 28px;
327
+ font-weight: 400;
328
+ }
329
+ #s-stat-row {
330
+ display: flex;
331
+ gap: 32px;
332
+ flex-wrap: wrap;
333
+ }
334
+ .s-stat {
335
+ display: flex;
336
+ flex-direction: column;
337
+ gap: 2px;
338
+ }
339
+ .s-stat-val {
340
+ font-size: 1.1rem;
341
+ font-weight: 700;
342
+ letter-spacing: -0.02em;
343
+ color: var(--s-ink);
344
+ font-variant-numeric: tabular-nums;
345
+ }
346
+ .s-stat-lbl {
347
+ font-size: 0.72rem;
348
+ color: var(--s-faint);
349
+ font-weight: 500;
350
+ text-transform: uppercase;
351
+ letter-spacing: 0.05em;
352
+ }
353
+
354
+ /* ── Section label ────────────────────────────────────── */
355
+ .s-section-label {
356
+ font-size: 0.72rem;
357
+ font-weight: 600;
358
+ letter-spacing: 0.08em;
359
+ text-transform: uppercase;
360
+ color: var(--s-faint);
361
+ padding: 28px 0 12px;
362
+ border-bottom: none;
363
+ }
364
+
365
+ /* ── Chat bubbles ─────────────────────────────────────── */
366
+ /* User: saffron-tinted */
367
  .user-row {
368
+ background: var(--s-saf) !important;
369
+ border-radius: 12px 12px 2px 12px !important;
370
  }
371
+ .user-row * { color: #fff !important; }
372
+
373
+ /* Bot: white card with warm border */
374
  .bot-row {
375
+ background: var(--s-white) !important;
376
+ border: 1px solid var(--s-warm-100) !important;
377
+ border-radius: 12px 12px 12px 2px !important;
378
  }
379
+
380
  .message-bubble-border {
381
+ box-shadow: 0 1px 3px rgba(0,0,0,0.06) !important;
 
 
 
 
 
 
382
  }
383
+
384
+ /* Code in bot replies */
385
  .bot-row code, .bot-row pre {
386
  font-family: 'IBM Plex Mono', monospace !important;
387
+ background: var(--s-warm-50) !important;
388
+ border: 1px solid var(--s-warm-100) !important;
389
  border-radius: 6px !important;
390
+ font-size: 0.82rem !important;
391
  }
392
 
393
+ /* ── Textbox & send button ────────────────────────────── */
394
+ textarea {
395
+ font-family: 'Inter', sans-serif !important;
396
+ font-size: 0.95rem !important;
397
+ border-radius: 8px !important;
398
+ border: 1.5px solid var(--s-warm-200) !important;
399
+ transition: border-color 0.15s, box-shadow 0.15s !important;
400
+ resize: none !important;
401
+ background: var(--s-white) !important;
402
+ color: var(--s-ink) !important;
403
  }
404
+ textarea:focus {
405
+ border-color: var(--s-saf) !important;
406
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--s-saf) 12%, transparent) !important;
407
+ outline: none !important;
408
  }
409
+ textarea::placeholder { color: var(--s-faint) !important; }
410
+
411
  button.primary {
412
+ background: var(--s-saf) !important;
413
+ border: none !important;
414
+ border-radius: 8px !important;
415
+ font-weight: 600 !important;
416
+ font-size: 0.88rem !important;
417
+ letter-spacing: 0.01em !important;
418
+ color: #fff !important;
419
+ transition: background 0.12s, transform 0.1s !important;
420
+ }
421
+ button.primary:hover {
422
+ background: var(--s-saf-hover) !important;
423
+ transform: translateY(-1px) !important;
424
  }
 
425
 
426
+ /* ── Settings accordion ───────────────────────────────── */
427
+ .accordion {
428
+ border: 1px solid var(--s-warm-100) !important;
429
+ border-radius: 10px !important;
430
+ background: var(--s-white) !important;
431
+ overflow: hidden;
432
+ }
433
+ .accordion > .label-wrap {
434
+ padding: 12px 16px !important;
435
+ font-size: 0.85rem !important;
436
+ font-weight: 600 !important;
437
+ color: var(--s-ink-2) !important;
438
+ background: var(--s-warm-50) !important;
439
+ border-bottom: 1px solid var(--s-warm-100) !important;
440
+ }
441
+
442
+ /* Sliders */
443
+ input[type="range"] { accent-color: var(--s-saf) !important; }
444
+
445
+ /* ── Example chips ────────────────────────────────────── */
446
  .example {
447
+ font-size: 0.82rem !important;
448
+ font-weight: 500 !important;
449
+ border: 1px solid var(--s-warm-200) !important;
450
+ border-radius: 6px !important;
451
+ color: var(--s-ink-2) !important;
452
+ background: var(--s-white) !important;
453
+ padding: 6px 12px !important;
454
+ transition: border-color 0.12s, color 0.12s, background 0.12s !important;
455
  }
456
  .example:hover {
457
+ border-color: var(--s-saf) !important;
458
+ color: var(--s-saf) !important;
459
+ background: var(--s-saf-light) !important;
460
  }
461
 
462
+ /* ── Footer ───────────────────────────────────────────── */
463
+ #s-footer {
464
+ border-top: 1px solid var(--s-warm-100);
465
+ padding: 28px 0 20px;
466
+ display: flex;
467
+ justify-content: space-between;
468
+ align-items: flex-start;
469
+ flex-wrap: wrap;
470
+ gap: 16px;
471
+ margin-top: 16px;
472
+ }
473
+ #s-footer-left {
474
+ display: flex;
475
+ flex-direction: column;
476
+ gap: 5px;
477
+ }
478
+ #s-footer-brand {
479
+ font-weight: 700;
480
+ font-size: 0.88rem;
481
+ color: var(--s-ink);
482
+ letter-spacing: -0.01em;
483
+ }
484
+ #s-footer-copy {
485
+ font-size: 0.72rem;
486
+ color: var(--s-faint);
487
+ }
488
+ #s-footer-chips {
489
+ display: flex;
490
+ gap: 6px;
491
+ flex-wrap: wrap;
492
+ align-items: center;
493
+ }
494
+ .s-badge {
495
  font-size: 0.68rem;
496
+ font-weight: 500;
497
+ font-family: 'IBM Plex Mono', monospace;
498
+ color: var(--s-muted);
499
+ background: var(--s-warm-50);
500
+ border: 1px solid var(--s-warm-100);
501
+ padding: 3px 8px;
502
+ border-radius: 4px;
503
+ letter-spacing: 0.02em;
504
+ white-space: nowrap;
505
+ }
506
+ .s-badge-accent {
507
+ color: var(--s-saf);
508
+ background: var(--s-saf-light);
509
+ border-color: var(--s-saf-mid);
510
  }
 
 
 
 
 
 
511
 
512
+ /* ── Misc fixes ───────────────────────────────────────── */
513
+ * { scrollbar-width: thin; scrollbar-color: var(--s-warm-200) transparent; }
514
+ ::-webkit-scrollbar { width: 5px; }
515
+ ::-webkit-scrollbar-thumb { background: var(--s-warm-200); border-radius: 4px; }
 
 
 
516
  """
517
 
518
 
519
  def main():
520
+ # Build runtime stats for the hero section
521
+ device_label = str(_device).upper()
522
+ dtype_label = str(_cast_dtype).replace("torch.", "").upper() if _cast_dtype else "FP32"
523
+ layer_str = str(_n_layer)
524
+ head_str = f"{_n_head}/{_kv_heads}"
525
+
526
+ header_html = f"""
527
+ <div id="s-nav">
528
+ <span id="s-wordmark">
529
+ <span id="s-wordmark-dot"></span>
530
+ Pragya
531
+ </span>
532
+ <span id="s-nav-tag">by Arush Kumar</span>
533
+ <div id="s-nav-right">
534
+ <span class="s-nav-link">PyTorch</span>
535
+ <span class="s-nav-link">{device_label}</span>
536
+ </div>
537
+ </div>
538
+
539
+ <div id="s-hero">
540
+ <div id="s-hero-eyebrow">Arush Kumar Β· From-scratch language model</div>
541
+ <h1>Pragya β€” a model<br>built from the ground up.</h1>
542
+ <p id="s-hero-desc">
543
+ A custom GPT-style architecture with Mixture-of-Depths routing,
544
+ Multi-Token Prediction heads, and YaRN RoPE context scaling.
545
+ Every weight trained from scratch in PyTorch. Type a prompt and let it continue.
546
+ </p>
547
+ <div id="s-stat-row">
548
+ <div class="s-stat">
549
+ <span class="s-stat-val">{_n_params_str}</span>
550
+ <span class="s-stat-lbl">Parameters</span>
551
+ </div>
552
+ <div class="s-stat">
553
+ <span class="s-stat-val">{_ctx_len:,}</span>
554
+ <span class="s-stat-lbl">Context length</span>
555
+ </div>
556
+ <div class="s-stat">
557
+ <span class="s-stat-val">{layer_str}L Β· {head_str}H</span>
558
+ <span class="s-stat-lbl">Depth Β· GQA heads</span>
559
+ </div>
560
+ <div class="s-stat">
561
+ <span class="s-stat-val">{_pattern}</span>
562
+ <span class="s-stat-lbl">Attention pattern</span>
563
+ </div>
564
+ <div class="s-stat">
565
+ <span class="s-stat-val">{dtype_label}</span>
566
+ <span class="s-stat-lbl">Inference dtype</span>
567
  </div>
 
 
568
  </div>
569
+ </div>
570
+ """
571
+
572
+ footer_html = f"""
573
+ <div id="s-footer">
574
+ <div id="s-footer-left">
575
+ <span id="s-footer-brand">Pragya &nbsp;Β·&nbsp; <span style="font-weight:400;color:var(--s-muted)">Arush Kumar</span></span>
576
+ <span id="s-footer-copy">
577
+ Continuation model Β· conversation history is shown in UI but not fed back to the model Β·
578
+ TTFT and tok/s are live-measured each generation
579
+ </span>
580
+ </div>
581
+ <div id="s-footer-chips">
582
+ <span class="s-badge s-badge-accent">{_n_params_str} params</span>
583
+ <span class="s-badge">{device_label}</span>
584
+ <span class="s-badge">{dtype_label}</span>
585
+ <span class="s-badge">ctx {_ctx_len}</span>
586
+ <span class="s-badge">{_pattern}</span>
587
+ </div>
588
+ </div>
589
+ """
590
+
591
+ with gr.Blocks(title="Pragya by Arush Kumar") as demo:
592
+ gr.HTML(header_html)
593
+
594
+ gr.HTML('<div class="s-section-label">Try it</div>')
595
 
596
  gr.ChatInterface(
597
  fn=respond,
598
  additional_inputs=[
599
+ gr.Slider(16, _MAX_TOKENS_CAP, value=_DEFAULT_TOKENS, step=8,
600
+ label="Max new tokens"),
601
+ gr.Slider(0.1, 2.0, value=0.8, step=0.05,
602
+ label="Temperature"),
603
+ gr.Slider(0, 200, value=50, step=5,
604
+ label="Top-k",
605
+ info="0 disables top-k filtering."),
606
+ gr.Slider(0.5, 1.0, value=1.0, step=0.01,
607
+ label="Top-p (nucleus)",
608
+ info="1.0 disables nucleus sampling."),
609
+ gr.Slider(1.0, 2.0, value=1.3, step=0.05,
610
+ label="Repetition penalty",
611
+ info="Count-scaled: penalty^occurrences. 1.0 = off."),
612
+ gr.Slider(0, 6, value=3, step=1,
613
+ label="No-repeat n-gram",
614
+ info="Hard-bans any n-gram of this length from repeating. 0 = off."),
615
  gr.Checkbox(value=True, label="Narrative anchor",
616
+ info="Prepends a short coherent prose passage to prime the context."),
617
  ],
618
+ additional_inputs_accordion=gr.Accordion("Generation settings", open=False),
619
+ chatbot=gr.Chatbot(
620
+ height=440,
621
+ show_label=False,
622
+ render_markdown=True,
623
+ bubble_full_width=False,
624
+ ),
625
+ textbox=gr.Textbox(
626
+ placeholder="Start a sentence and let Pragya complete it …",
627
+ show_label=False,
628
+ lines=2,
629
+ ),
630
  examples=[
631
+ ["The scientist stared at the anomalous readings and realized"],
632
+ ["Once upon a time, in a village at the foot of a mountain,"],
633
+ ["The rules of the game were simple, but nobody had read them."],
634
+ ["Deep in the forest, where the trees grew so tall they blocked the sky,"],
635
  ],
636
+ cache_examples=False,
 
637
  )
638
 
639
+ gr.HTML(footer_html)
 
 
 
 
 
640
 
 
 
641
  demo.launch(
642
+ server_name="0.0.0.0",
643
+ server_port=7860,
644
+ share=False,
645
+ css=CSS,
646
+ theme=gr.themes.Base(
647
+ primary_hue=gr.themes.colors.orange,
648
+ neutral_hue=gr.themes.colors.stone,
649
+ font=gr.themes.GoogleFont("Inter"),
650
+ ),
651
  )
652
 
653
+
654
  if __name__ == "__main__":
655
  main()