Spaces:
Running
Running
Commit Β·
64803df
1
Parent(s): 2735668
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,558 +1,655 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
import os
|
| 4 |
-
|
| 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 |
-
|
| 14 |
-
|
| 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 |
-
|
| 34 |
-
|
|
|
|
| 35 |
|
| 36 |
-
#
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 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 |
-
|
| 96 |
-
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
|
| 110 |
-
|
| 111 |
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 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 |
-
|
| 174 |
-
|
|
|
|
|
|
|
| 175 |
|
| 176 |
def respond(
|
| 177 |
message: str,
|
| 178 |
history: list,
|
| 179 |
-
max_new_tokens: int
|
| 180 |
-
temperature: float
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
repetition_penalty: float
|
| 184 |
-
|
|
|
|
| 185 |
):
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
| 206 |
|
| 207 |
-
|
| 208 |
-
if len(tokens) == 0:
|
| 209 |
-
tokens = [tokenizer.bos_id if hasattr(tokenizer, "bos_id") else 1]
|
| 210 |
-
tokens = tokens[-CONTEXT:]
|
| 211 |
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
|
|
|
|
|
|
| 215 |
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 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 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
# framework was never the bottleneck for a custom JAX model like this.
|
| 282 |
|
| 283 |
-
ARYA_CSS = """
|
| 284 |
:root {
|
| 285 |
-
|
| 286 |
-
--
|
| 287 |
-
--
|
| 288 |
-
--
|
| 289 |
-
--
|
| 290 |
-
--
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
--
|
| 294 |
-
--
|
| 295 |
-
--
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
--background-fill
|
| 306 |
-
--
|
| 307 |
-
--
|
| 308 |
-
--block-
|
| 309 |
-
--block-
|
| 310 |
-
--
|
| 311 |
-
--
|
| 312 |
-
--
|
| 313 |
-
--
|
| 314 |
-
--color-
|
| 315 |
-
--color-accent
|
| 316 |
-
--
|
| 317 |
-
--input-
|
| 318 |
-
--input-border-color
|
| 319 |
-
--input-
|
| 320 |
-
--
|
| 321 |
-
--
|
| 322 |
-
--checkbox-
|
| 323 |
-
--checkbox-
|
| 324 |
-
--
|
| 325 |
-
|
| 326 |
-
--button-primary-
|
| 327 |
-
--button-
|
| 328 |
-
--button-
|
| 329 |
-
--button-secondary-background-fill
|
| 330 |
-
--
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
.gradio-container {
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
var(--arya-bg) !important;
|
| 343 |
-
font-family: 'Inter', system-ui, sans-serif !important;
|
| 344 |
-
max-width: 880px !important;
|
| 345 |
}
|
| 346 |
|
| 347 |
-
/* ββ
|
| 348 |
-
#
|
| 349 |
display: flex;
|
| 350 |
align-items: center;
|
|
|
|
|
|
|
|
|
|
| 351 |
gap: 12px;
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 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.
|
| 369 |
-
letter-spacing: 0.
|
| 370 |
-
color: var(--
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
}
|
| 372 |
-
#
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
color: var(--arya-text-dim);
|
| 377 |
}
|
| 378 |
-
#
|
| 379 |
-
font-family: 'IBM Plex Mono', monospace;
|
| 380 |
font-size: 0.72rem;
|
| 381 |
font-weight: 500;
|
| 382 |
-
color: var(--
|
| 383 |
-
|
| 384 |
-
|
|
|
|
| 385 |
border-radius: 999px;
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 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;
|
|
|
|
|
|
|
| 404 |
}
|
| 405 |
-
.
|
| 406 |
-
font-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
border-radius: 6px;
|
| 412 |
-
background: var(--arya-panel);
|
| 413 |
-
border: 1px solid var(--arya-hairline);
|
| 414 |
-
white-space: nowrap;
|
| 415 |
}
|
|
|
|
| 416 |
|
| 417 |
-
/* ββ
|
| 418 |
-
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
.user-row {
|
| 421 |
-
background:
|
| 422 |
-
|
| 423 |
}
|
| 424 |
-
.user-row * { color: #
|
|
|
|
|
|
|
| 425 |
.bot-row {
|
| 426 |
-
background: var(--
|
| 427 |
-
border: 1px solid var(--
|
|
|
|
| 428 |
}
|
|
|
|
| 429 |
.message-bubble-border {
|
| 430 |
-
|
| 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(--
|
| 441 |
-
border: 1px solid var(--
|
| 442 |
border-radius: 6px !important;
|
|
|
|
| 443 |
}
|
| 444 |
|
| 445 |
-
/* ββ
|
| 446 |
-
textarea
|
| 447 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
}
|
| 449 |
-
textarea:focus
|
| 450 |
-
|
|
|
|
|
|
|
| 451 |
}
|
|
|
|
|
|
|
| 452 |
button.primary {
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
}
|
| 456 |
-
button.primary:hover { transform: translateY(-1px) scale(1.03); }
|
| 457 |
|
| 458 |
-
/* ββ Settings accordion
|
| 459 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
.example {
|
| 461 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
}
|
| 463 |
.example:hover {
|
| 464 |
-
|
| 465 |
-
|
|
|
|
| 466 |
}
|
| 467 |
|
| 468 |
-
|
| 469 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
font-size: 0.68rem;
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
border
|
| 476 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 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 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
<
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 522 |
-
|
| 523 |
-
gr.Slider(0.
|
| 524 |
-
|
| 525 |
-
gr.Slider(0,
|
| 526 |
-
|
| 527 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
gr.Checkbox(value=True, label="Narrative anchor",
|
| 529 |
-
info="
|
| 530 |
],
|
| 531 |
-
additional_inputs_accordion=gr.Accordion("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
examples=[
|
| 533 |
-
["
|
| 534 |
-
["
|
| 535 |
-
["
|
|
|
|
| 536 |
],
|
| 537 |
-
|
| 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 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 Β· <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()
|