Maxilicious20 commited on
Commit
160f110
·
verified ·
1 Parent(s): 48d63bb

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -495
app.py DELETED
@@ -1,495 +0,0 @@
1
- """Velqora-160M-Instruct — Hugging Face Gradio + ZeroGPU chat demo.
2
-
3
- Model sources:
4
- 1) Maxilicious20/Velqora-160M-Instruct (HF safetensors, when available)
5
- 2) Maxilicious20/Velqora-160M-Instruct-GGUF (F16 fallback via Transformers)
6
-
7
- The model is intentionally experimental. The 'context off' setting prevents
8
- previous chat messages from being included in the prompt; the UI still shows them.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import logging
14
- import os
15
- from typing import Any
16
-
17
- # IMPORTANT: ZeroGPU patches CUDA handling. Import it BEFORE torch/transformers.
18
- # Do NOT initialize or move the model inside the @spaces.GPU call.
19
- import spaces
20
- import torch
21
- import gradio as gr
22
- from huggingface_hub import hf_hub_download, list_repo_files
23
- from transformers import AutoModelForCausalLM, AutoTokenizer, PreTrainedTokenizerFast, LlamaConfig, LlamaForCausalLM
24
-
25
-
26
- LOGGER = logging.getLogger("velqora-chat")
27
- logging.basicConfig(level=logging.INFO)
28
-
29
- MODEL_REPO = "Maxilicious20/Velqora-160M-Instruct"
30
- GGUF_REPO = "Maxilicious20/Velqora-160M-Instruct-GGUF"
31
- CONTEXT_LENGTH = 2048
32
-
33
- # The temperature and top-p defaults reflect our earlier llama.cpp test command.
34
- # Other defaults are conservative choices, NOT empirically proven optima.
35
- DEFAULT_TEMPERATURE = 0.4
36
- DEFAULT_TOP_P = 0.9
37
- DEFAULT_TOP_K = 40
38
- DEFAULT_REPETITION_PENALTY = 1.10
39
- DEFAULT_MAX_NEW_TOKENS = 64
40
- DEFAULT_CONTEXT = False
41
- DEFAULT_CONTEXT_TURNS = 3
42
-
43
- SPECIAL_TOKENS = {
44
- "<|unk|>": 0,
45
- "<|pad|>": 1,
46
- "<|bos|>": 2,
47
- "<|eos|>": 3,
48
- "<|im_start|>": 4,
49
- "<|im_end|>": 5,
50
- "<|eot_id|>": 6,
51
- }
52
-
53
-
54
- POPUP_JS = r"""() => {
55
- if (document.getElementById('velqora-warning-overlay')) return;
56
- const overlay = document.createElement('div');
57
- overlay.id = 'velqora-warning-overlay';
58
- overlay.innerHTML = `
59
- <div class="velqora-warning-card" role="alertdialog"
60
- aria-modal="true" aria-labelledby="velqora-warning-title"
61
- aria-describedby="velqora-warning-description">
62
- <div class="velqora-warning-symbol">!</div>
63
- <h2 id="velqora-warning-title">Before you chat</h2>
64
- <p id="velqora-warning-description">
65
- <strong>We recommend keeping conversation context disabled.</strong>
66
- Velqora-160M often interprets previous messages incorrectly.
67
- This is a small experimental model: answers may be inaccurate,
68
- and responses can contain repetitive phrases or loops.
69
- </p>
70
- <p class="velqora-warning-footnote">Please verify important information independently.</p>
71
- <button id="velqora-warning-close" disabled aria-disabled="true">
72
- Continue (5s)
73
- </button>
74
- </div>`;
75
- document.body.appendChild(overlay);
76
- const close = document.getElementById('velqora-warning-close');
77
- let remaining = 5;
78
- const timer = setInterval(() => {
79
- remaining -= 1;
80
- if (remaining <= 0) {
81
- clearInterval(timer);
82
- close.disabled = false;
83
- close.setAttribute('aria-disabled', 'false');
84
- close.textContent = 'I understand — continue';
85
- close.focus();
86
- } else {
87
- close.textContent = `Continue (${remaining}s)`;
88
- }
89
- }, 1000);
90
- close.addEventListener('click', () => {
91
- if (close.disabled) return;
92
- overlay.remove();
93
- });
94
- }"""
95
-
96
-
97
- CSS = r"""
98
- .gradio-container { max-width: 1080px !important; margin: 0 auto; }
99
- #velqora-top { padding: 20px 0 8px; }
100
- #velqora-title { font-size: 2rem; font-weight: 800; letter-spacing: -.035em; }
101
- #velqora-subtitle { color: #64748b; margin-top: 0; }
102
- #velqora-chat { border-radius: 16px; }
103
- #velqora-warning-overlay {
104
- position: fixed; z-index: 99999; inset: 0;
105
- background: rgba(9, 17, 32, 0.64);
106
- display: flex; justify-content: center; align-items: center;
107
- padding: 20px; backdrop-filter: blur(5px);
108
- }
109
- .velqora-warning-card {
110
- width: min(100%, 510px); background: #fff; color: #192233;
111
- border-radius: 22px; padding: 30px;
112
- box-shadow: 0 24px 90px rgba(0,0,0,.28);
113
- font-family: system-ui, -apple-system, sans-serif;
114
- }
115
- .velqora-warning-symbol {
116
- display: flex; align-items: center; justify-content: center;
117
- width: 48px; height: 48px; margin-bottom: 12px;
118
- color: #8a5100; background: #fff1d5;
119
- border-radius: 13px; font-weight: 800; font-size: 26px;
120
- }
121
- .velqora-warning-card h2 { font-size: 24px; font-weight: 750; margin: 0 0 12px; }
122
- .velqora-warning-card p { font-size: 15px; line-height: 1.7; margin: 0 0 12px; }
123
- .velqora-warning-footnote { color: #64748b; }
124
- #velqora-warning-close {
125
- width: 100%; background: #1865ca; color: #fff; border: none;
126
- padding: 13px; border-radius: 12px; font-size: 15px; font-weight: 700;
127
- cursor: pointer; margin-top: 12px;
128
- }
129
- #velqora-warning-close:disabled {
130
- background: #b4c3d8; cursor: not-allowed;
131
- }
132
- #velqora-warning-close:not(:disabled):hover { background: #1155ae; }
133
- """
134
-
135
-
136
- def load_tokenizer() -> Any:
137
- """Load the original ByteLevel BPE tokenizer; never substitute a foreign tokenizer."""
138
- try:
139
- tokenizer = AutoTokenizer.from_pretrained(MODEL_REPO, use_fast=True)
140
- LOGGER.info("Tokenizer loaded via AutoTokenizer from %s", MODEL_REPO)
141
- except Exception as exc:
142
- LOGGER.warning("AutoTokenizer could not load (%s); using tokenizer.json", exc)
143
- tokenizer_path = hf_hub_download(repo_id=MODEL_REPO, filename="tokenizer.json")
144
- tokenizer = PreTrainedTokenizerFast(
145
- tokenizer_file=tokenizer_path,
146
- unk_token="<|unk|>",
147
- pad_token="<|pad|>",
148
- bos_token="<|bos|>",
149
- eos_token="<|eot_id|>",
150
- additional_special_tokens=["<|im_start|>", "<|im_end|>", "<|eos|>"],
151
- )
152
-
153
- # Assert exact tokenizer special-token IDs from the native training format.
154
- actual = {token: tokenizer.convert_tokens_to_ids(token) for token in SPECIAL_TOKENS}
155
- mismatches = {token: (SPECIAL_TOKENS[token], actual[token])
156
- for token in SPECIAL_TOKENS if actual[token] != SPECIAL_TOKENS[token]}
157
- if mismatches:
158
- raise RuntimeError(
159
- "Velqora tokenizer does not match the original special-token IDs: "
160
- f"{mismatches}. Refusing to run inference with a different tokenizer."
161
- )
162
- return tokenizer
163
-
164
-
165
- def load_model() -> tuple[Any, str, str]:
166
- """Load actual Velqora weights, preferring HF safetensors and then F16 GGUF."""
167
- device = "cuda" if torch.cuda.is_available() else "cpu"
168
- dtype = torch.float16 if device == "cuda" else torch.float32
169
- failures: list[str] = []
170
-
171
- try:
172
- files = list_repo_files(MODEL_REPO)
173
- except Exception as exc:
174
- LOGGER.warning("Cannot list original model repo: %s", exc)
175
- files = []
176
- failures.append(f"Original repository listing failed: {exc}")
177
-
178
- hf_weights = any(
179
- file == "model.safetensors" or file.endswith(".safetensors")
180
- or file == "pytorch_model.bin" or file.endswith(".bin")
181
- for file in files
182
- )
183
- if hf_weights:
184
- try:
185
- model = AutoModelForCausalLM.from_pretrained(
186
- MODEL_REPO, dtype=dtype, trust_remote_code=False,
187
- low_cpu_mem_usage=False,
188
- )
189
- model.eval().to(device)
190
- LOGGER.info("Loaded original Transformers weights from %s", MODEL_REPO)
191
- return model, device, "Original Transformers checkpoint"
192
- except Exception as exc:
193
- LOGGER.exception("Loading HF checkpoint failed")
194
- failures.append(f"Transformers checkpoint failed: {exc}")
195
- else:
196
- LOGGER.info("No standard HF weights found in %s; attempting GGUF fallback", MODEL_REPO)
197
-
198
- try:
199
- gguf_files = set(list_repo_files(GGUF_REPO))
200
- preferred = [
201
- "Velqora-160M-Instruct-F16.gguf",
202
- "Velqora-160M-Instruct-Q8_0.gguf",
203
- "Velqora-160M-Instruct-Q4_K_M.gguf",
204
- ]
205
- gguf_file = next((name for name in preferred if name in gguf_files), None)
206
- if gguf_file is None:
207
- raise FileNotFoundError(
208
- "No supported GGUF checkpoint was found in the GGUF repository. "
209
- "Expected F16, Q8_0 or Q4_K_M."
210
- )
211
- # Transformers supports loading Llama-architecture GGUF checkpoints as
212
- # ordinary PyTorch models (dequantized as required). Needs the 'gguf' package.
213
- # The GGUF repository may not contain config.json. Supply the
214
- # known architecture explicitly rather than trying to infer it from
215
- # a non-HF native config in the original model repository.
216
- config = LlamaConfig(
217
- vocab_size=32000,
218
- hidden_size=1024,
219
- intermediate_size=2560,
220
- num_hidden_layers=12,
221
- num_attention_heads=16,
222
- num_key_value_heads=4,
223
- max_position_embeddings=CONTEXT_LENGTH,
224
- rope_theta=10000.0,
225
- rms_norm_eps=1e-5,
226
- hidden_act="silu",
227
- tie_word_embeddings=True,
228
- bos_token_id=2,
229
- eos_token_id=6,
230
- pad_token_id=1,
231
- )
232
- model = LlamaForCausalLM.from_pretrained(
233
- GGUF_REPO,
234
- gguf_file=gguf_file,
235
- config=config,
236
- dtype=dtype,
237
- low_cpu_mem_usage=False,
238
- )
239
- model.eval().to(device)
240
- LOGGER.info("Loaded %s from %s", gguf_file, GGUF_REPO)
241
- return model, device, f"GGUF fallback ({gguf_file})"
242
- except Exception as exc:
243
- LOGGER.exception("Loading GGUF fallback failed")
244
- failures.append(f"GGUF fallback failed: {exc}")
245
-
246
- raise RuntimeError(
247
- "Velqora-160M could not be loaded from its public repositories. "
248
- "If the original repo contains only model.pt, upload the verified "
249
- "Llama-compatible model.safetensors + config.json export, or ensure "
250
- "the F16 GGUF file exists in the GGUF repo. Details: " + " | ".join(failures)
251
- )
252
-
253
-
254
- TOKENIZER = load_tokenizer()
255
- MODEL, DEVICE, MODEL_SOURCE = load_model()
256
-
257
-
258
- def clean_user_text(text: str) -> str:
259
- """Prevent pasted chat delimiters from injecting fake model roles."""
260
- cleaned = (text or "").strip()
261
- for token in SPECIAL_TOKENS:
262
- cleaned = cleaned.replace(token, f"[{token[2:-2]}]")
263
- return cleaned
264
-
265
-
266
- def encode_prompt(text: str) -> list[int]:
267
- """Encode a chat prompt without silently adding BOS/EOS tokens."""
268
- return TOKENIZER.encode(text, add_special_tokens=False)
269
-
270
-
271
- def make_prompt(history: list[dict[str, str]], max_new_tokens: int,
272
- use_context: bool, context_turns: int) -> list[int]:
273
- """Build a prompt fitting the 2048-token limit, preferring the latest question."""
274
- if not history or history[-1].get("role") != "user":
275
- raise ValueError("The latest chat message must be from the user.")
276
-
277
- usable = CONTEXT_LENGTH - int(max_new_tokens) - 8
278
- if usable < 40:
279
- raise ValueError("Requested generation length leaves too little prompt space.")
280
-
281
- prefix = "<|im_start|>user\n"
282
- suffix = "<|im_end|><|im_start|>assistant\n"
283
- latest = clean_user_text(str(history[-1].get("content") or ""))
284
- if not latest:
285
- raise ValueError("Message must not be empty.")
286
-
287
- latest_ids = encode_prompt(latest)
288
- framing = len(encode_prompt(prefix + suffix))
289
- if len(latest_ids) + framing > usable:
290
- latest_ids = latest_ids[-(usable - framing):]
291
- latest = TOKENIZER.decode(latest_ids, skip_special_tokens=False)
292
-
293
- current = prefix + latest + suffix
294
- current_ids = encode_prompt(current)
295
-
296
- if not use_context:
297
- return current_ids
298
-
299
- previous = history[:-1]
300
- pairs: list[tuple[str, str]] = []
301
- i = 0
302
- while i + 1 < len(previous):
303
- user = previous[i]
304
- assistant = previous[i + 1]
305
- if user.get("role") == "user" and assistant.get("role") == "assistant":
306
- user_text = clean_user_text(str(user.get("content") or ""))
307
- assistant_text = clean_user_text(str(assistant.get("content") or ""))
308
- if user_text and assistant_text:
309
- pairs.append((user_text, assistant_text))
310
- i += 2
311
- else:
312
- i += 1
313
-
314
- selected: list[str] = []
315
- for question, answer in reversed(pairs[-int(context_turns):]):
316
- pair = (
317
- f"<|im_start|>user\n{question}<|im_end|>"
318
- f"<|im_start|>assistant\n{answer}<|eot_id|>"
319
- )
320
- proposed = pair + "".join(selected) + current
321
- proposed_ids = encode_prompt(proposed)
322
- if len(proposed_ids) <= usable:
323
- selected.insert(0, pair)
324
- # Discard older turns if the prompt would exceed the context window.
325
-
326
- return encode_prompt("".join(selected) + current)
327
-
328
-
329
- def submit_user(message: str, history: list[dict[str, str]] | None):
330
- """Append a user's message to the visible chat history."""
331
- history = list(history or [])
332
- text = (message or "").strip()
333
- if not text:
334
- return message, history
335
- if history and history[-1].get("role") == "user":
336
- return message, history
337
- return "", history + [{"role": "user", "content": text[:12000]}]
338
-
339
-
340
- def generation_duration(history, temperature, top_p, top_k,
341
- repetition_penalty, max_new_tokens, use_context, context_turns):
342
- """Reserve proportional ZeroGPU time without booking too long for short replies."""
343
- return max(35, min(120, 15 + int(max_new_tokens) * 0.5))
344
-
345
-
346
- @spaces.GPU(duration=generation_duration)
347
- def generate_reply(history: list[dict[str, str]], temperature: float,
348
- top_p: float, top_k: int, repetition_penalty: float,
349
- max_new_tokens: int, use_context: bool,
350
- context_turns: int) -> list[dict[str, str]]:
351
- """Generate a Velqora response, optionally including recent conversation turns."""
352
- history = list(history or [])
353
- if not history or history[-1].get("role") != "user":
354
- return history
355
-
356
- try:
357
- max_new_tokens = int(max_new_tokens)
358
- input_ids = make_prompt(history, max_new_tokens, bool(use_context),
359
- int(context_turns))
360
- ids = torch.tensor([input_ids], dtype=torch.long, device=DEVICE)
361
- attention_mask = torch.ones_like(ids)
362
- stop_ids = [SPECIAL_TOKENS["<|eot_id|>"],
363
- SPECIAL_TOKENS["<|eos|>"],
364
- SPECIAL_TOKENS["<|im_end|>"]]
365
- with torch.inference_mode():
366
- output = MODEL.generate(
367
- input_ids=ids,
368
- attention_mask=attention_mask,
369
- max_new_tokens=max_new_tokens,
370
- do_sample=True,
371
- temperature=float(temperature),
372
- top_p=float(top_p),
373
- top_k=int(top_k),
374
- repetition_penalty=float(repetition_penalty),
375
- eos_token_id=stop_ids,
376
- pad_token_id=SPECIAL_TOKENS["<|pad|>"],
377
- use_cache=True,
378
- )
379
- new_tokens = output[0, len(input_ids):].tolist()
380
- answer = TOKENIZER.decode(new_tokens, skip_special_tokens=False)
381
- # Trim trailing delimiters and any generated spurious role transition.
382
- for marker in ("<|eot_id|>", "<|eos|>", "<|im_end|>", "<|im_start|>"):
383
- answer = answer.split(marker, 1)[0]
384
- answer = answer.strip() or "(No text generated. Try again or adjust the settings.)"
385
- return history + [{"role": "assistant", "content": answer}]
386
- except Exception as exc:
387
- LOGGER.exception("Generation failed")
388
- return history + [{
389
- "role": "assistant",
390
- "content": f"⚠️ Generation failed: `{type(exc).__name__}`. "
391
- "Check the Space logs for details.",
392
- }]
393
-
394
-
395
- def reset_settings():
396
- """Restore conservative defaults; chat history remains unchanged."""
397
- return (DEFAULT_TEMPERATURE, DEFAULT_TOP_P, DEFAULT_TOP_K,
398
- DEFAULT_REPETITION_PENALTY, DEFAULT_MAX_NEW_TOKENS,
399
- DEFAULT_CONTEXT, DEFAULT_CONTEXT_TURNS)
400
-
401
-
402
- with gr.Blocks(title="Velqora-160M Chat", css=CSS, js=POPUP_JS) as demo:
403
- gr.Markdown(
404
- "# Velqora-160M-Instruct\n"
405
- "**Experimental small language model · 158.6M parameters · 2K context**\n\n"
406
- "The model can make factual mistakes, misread context or repeat phrases. "
407
- "Do not rely on it for important decisions.\n\n"
408
- f"[Model](https://huggingface.co/{MODEL_REPO}) · "
409
- f"[GGUF](https://huggingface.co/{GGUF_REPO}) · "
410
- "**Knowledge cutoff: not documented**",
411
- elem_id="velqora-top",
412
- )
413
-
414
- with gr.Row():
415
- with gr.Column(scale=7, min_width=310):
416
- chatbot = gr.Chatbot(
417
- label="Velqora Chat",
418
- height=510,
419
- elem_id="velqora-chat",
420
- buttons=["copy", "copy_all"],
421
- )
422
- user_input = gr.Textbox(
423
- label="Your message",
424
- placeholder="Type a short question (English is recommended)...",
425
- lines=2,
426
- max_lines=5,
427
- )
428
- with gr.Row():
429
- send = gr.Button("Send", variant="primary", scale=4)
430
- clear = gr.Button("Clear chat", scale=1)
431
-
432
- with gr.Column(scale=3, min_width=250):
433
- gr.Markdown("### Generation settings")
434
- temperature = gr.Slider(0.1, 1.5, value=DEFAULT_TEMPERATURE,
435
- step=0.05, label="Temperature")
436
- top_p = gr.Slider(0.1, 1.0, value=DEFAULT_TOP_P,
437
- step=0.05, label="Top-p")
438
- top_k = gr.Slider(1, 100, value=DEFAULT_TOP_K,
439
- step=1, label="Top-k")
440
- repetition_penalty = gr.Slider(
441
- 1.0, 1.5, value=DEFAULT_REPETITION_PENALTY,
442
- step=0.05, label="Repetition penalty")
443
- max_new_tokens = gr.Slider(
444
- 16, 256, value=DEFAULT_MAX_NEW_TOKENS,
445
- step=16, label="Maximum new tokens")
446
- with gr.Accordion("Conversation context (experimental)", open=True):
447
- context_enabled = gr.Checkbox(
448
- value=DEFAULT_CONTEXT,
449
- label="Include previous turns in the prompt",
450
- info="OFF is recommended. Prior context is often misunderstood.",
451
- )
452
- context_turns = gr.Slider(
453
- 1, 6, value=DEFAULT_CONTEXT_TURNS,
454
- step=1, label="Maximum previous turns")
455
- reset = gr.Button("Reset settings")
456
- gr.Markdown(
457
- "**Recommended defaults:** temperature 0.4 / top-p 0.9 / "
458
- "context OFF. These are conservative experimental settings, "
459
- "not independently optimized benchmark winners.\n\n"
460
- "Responses consume ZeroGPU quota and may wait in a queue."
461
- )
462
-
463
- settings = [temperature, top_p, top_k, repetition_penalty,
464
- max_new_tokens, context_enabled, context_turns]
465
-
466
- submit_event = send.click(
467
- fn=submit_user, inputs=[user_input, chatbot],
468
- outputs=[user_input, chatbot], queue=False,
469
- )
470
- submit_event.then(
471
- fn=generate_reply, inputs=[chatbot, *settings],
472
- outputs=chatbot, concurrency_limit=1,
473
- )
474
- enter_event = user_input.submit(
475
- fn=submit_user, inputs=[user_input, chatbot],
476
- outputs=[user_input, chatbot], queue=False,
477
- )
478
- enter_event.then(
479
- fn=generate_reply, inputs=[chatbot, *settings],
480
- outputs=chatbot, concurrency_limit=1,
481
- )
482
- clear.click(lambda: ([], ""), inputs=None,
483
- outputs=[chatbot, user_input], queue=False)
484
- reset.click(reset_settings, inputs=None, outputs=settings, queue=False)
485
-
486
- gr.Markdown(
487
- "<small>Model source: " + MODEL_SOURCE + ". "
488
- "Original training data cutoff is unknown; no calendar date is claimed.</small>"
489
- )
490
-
491
-
492
- demo.queue(default_concurrency_limit=1)
493
-
494
- if __name__ == "__main__":
495
- demo.launch()