Spaces:
Sleeping
Sleeping
| """Scugnizz Llama-PCS interactive chat (custom PCS decoder, not transformers AutoModel).""" | |
| from __future__ import annotations | |
| import importlib.util | |
| import gc | |
| import json | |
| import os | |
| import shutil | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import gradio as gr | |
| import torch | |
| import torch.nn.functional as F | |
| from huggingface_hub import snapshot_download | |
| from transformers import AutoTokenizer | |
| HUB_REPO = os.environ.get("HUB_REPO", "ProjectScugnizz/scugnizz-llama-pcs") | |
| HUB_PATH = os.environ.get( | |
| "HUB_PATH", | |
| "training-runs/sft-chat-v2-ground-adhere-8b-20260807-053640", | |
| ) | |
| SCRIPT_REPO = os.environ.get("SCRIPT_REPO", "ProjectScugnizz/scugnizz-llama-training") | |
| MODEL_MOUNT = Path(os.environ.get("MODEL_MOUNT", "/models")) | |
| CODE_MOUNT = Path(os.environ.get("CODE_MOUNT", "/code")) | |
| SYSTEM = os.environ.get( | |
| "SYSTEM_PROMPT", | |
| "You are a helpful assistant. Answer clearly and correctly.", | |
| ) | |
| _CODE_FALLBACK = Path("/tmp/scugnizz-code") | |
| _MODEL_FALLBACK = Path("/tmp/scugnizz-model") | |
| _WEIGHT_CACHE = Path("/tmp/scugnizz-weights") | |
| _state = { | |
| "model": None, | |
| "tok": None, | |
| "dev": None, | |
| "error": None, | |
| "status": "cold", | |
| "hub_path": HUB_PATH.strip("/"), | |
| "weights_file": "model_final.pt", | |
| } | |
| def _active_hub_path() -> str: | |
| return (_state.get("hub_path") or HUB_PATH).strip("/") | |
| def _active_weights_file() -> str: | |
| return (_state.get("weights_file") or "model_final.pt").strip() or "model_final.pt" | |
| def _resolve_code_dir() -> Path: | |
| # Hub volume mounts truncate some text files (saw `import time` → `import tim`). | |
| # Trainer is tiny — always pull via Hub API. | |
| snapshot_download( | |
| SCRIPT_REPO, | |
| local_dir=str(_CODE_FALLBACK), | |
| allow_patterns=["scugnizz-llama.py", "sft_data.py"], | |
| ) | |
| print(f"code from Hub → {_CODE_FALLBACK}", flush=True) | |
| return _CODE_FALLBACK | |
| def _resolve_model_dir(hub_path: str | None = None) -> Path: | |
| hub_path = (hub_path or _active_hub_path()).strip("/") | |
| nested = MODEL_MOUNT / hub_path | |
| # Prefer path-specific mount (volume pointed at a run tree). | |
| if (nested / "args.json").is_file() or (nested / "model_final.pt").is_file(): | |
| print(f"model mount nested: {nested}", flush=True) | |
| return nested | |
| # Legacy: Space volume mounts one run at /models root — only if it matches active path. | |
| env_path = os.environ.get("HUB_PATH", "").strip("/") | |
| if hub_path == env_path and (MODEL_MOUNT / "model_final.pt").is_file(): | |
| print(f"model mount: {MODEL_MOUNT}", flush=True) | |
| return MODEL_MOUNT | |
| local = _MODEL_FALLBACK / hub_path | |
| # Re-download if missing weights or args | |
| need = not (local / "args.json").is_file() | |
| wname = _active_weights_file() | |
| if not (local / wname).is_file() and not (local / "model_final.pt").is_file(): | |
| need = True | |
| if need: | |
| print(f"snapshot_download {HUB_REPO}/{hub_path} (~7GB)…", flush=True) | |
| snapshot_download( | |
| HUB_REPO, | |
| local_dir=str(_MODEL_FALLBACK), | |
| allow_patterns=[f"{hub_path}/*", f"{hub_path}/tokenizer/*"], | |
| ) | |
| return local | |
| def _load_train_module(): | |
| code = _resolve_code_dir() | |
| path = code / "scugnizz-llama.py" | |
| spec = importlib.util.spec_from_file_location("scugnizz_train", path) | |
| mod = importlib.util.module_from_spec(spec) | |
| sys.path.insert(0, str(code)) | |
| sys.modules["scugnizz_train"] = mod | |
| spec.loader.exec_module(mod) | |
| return mod | |
| def _load_tokenizer(model_dir: Path, hub_path: str | None = None): | |
| hub_path = (hub_path or _active_hub_path()).strip("/") | |
| tok_dir = model_dir / "tokenizer" | |
| cfg = tok_dir / "tokenizer_config.json" | |
| try: | |
| if cfg.is_file() and cfg.stat().st_size > 1000: | |
| json.loads(cfg.read_text(encoding="utf-8")) | |
| return AutoTokenizer.from_pretrained(str(tok_dir)) | |
| except (OSError, json.JSONDecodeError, ValueError): | |
| pass | |
| print("tokenizer via Hub (mount JSON unreliable)", flush=True) | |
| return AutoTokenizer.from_pretrained(HUB_REPO, subfolder=f"{hub_path}/tokenizer") | |
| def _materialize_weights(src: Path, hub_path: str | None = None, weights_file: str | None = None) -> Path: | |
| """Copy once to local disk — Hub volume / FUSE is slow for torch.load.""" | |
| hub_path = (hub_path or _active_hub_path()).strip("/") | |
| weights_file = weights_file or _active_weights_file() | |
| sz = src.stat().st_size | |
| if sz < 1_000_000_000: | |
| raise RuntimeError(f"weight file too small ({sz} B) at {src} — LFS/mount broken?") | |
| _WEIGHT_CACHE.mkdir(parents=True, exist_ok=True) | |
| safe = hub_path.replace("/", "__") + "__" + weights_file | |
| dest = _WEIGHT_CACHE / safe | |
| if dest.is_file() and dest.stat().st_size == sz: | |
| print(f"weights cache hit {dest} ({sz / 1e9:.2f} GB)", flush=True) | |
| return dest | |
| print(f"copying {sz / 1e9:.2f} GB → {dest} …", flush=True) | |
| t0 = time.perf_counter() | |
| shutil.copyfile(src, dest) | |
| print(f"copy done in {time.perf_counter() - t0:.1f}s", flush=True) | |
| return dest | |
| def _read_json(path: Path) -> dict: | |
| return json.loads(path.read_text(encoding="utf-8")) | |
| def _load_run_args(model_dir: Path, hub_path: str | None = None) -> dict: | |
| hub_path = (hub_path or _active_hub_path()).strip("/") | |
| local = model_dir / "args.json" | |
| try: | |
| if local.is_file() and local.stat().st_size > 50: | |
| return _read_json(local) | |
| except (OSError, json.JSONDecodeError, ValueError): | |
| pass | |
| print("args.json via Hub (mount JSON unreliable)", flush=True) | |
| from huggingface_hub import hf_hub_download | |
| p = hf_hub_download(HUB_REPO, f"{hub_path}/args.json") | |
| return _read_json(Path(p)) | |
| def _load_weights_into_model(model: torch.nn.Module, weight_path: Path, device: str) -> None: | |
| """t4-small has 15GB RAM — mmap + per-tensor copy, no full fp32 duplicate.""" | |
| t_load = time.perf_counter() | |
| weights = torch.load(weight_path, map_location="cpu", weights_only=True, mmap=True) | |
| print(f"torch.load(mmap) {time.perf_counter() - t_load:.1f}s", flush=True) | |
| state = weights["model"] if isinstance(weights, dict) and "model" in weights else weights | |
| own = model.state_dict() | |
| t_copy = time.perf_counter() | |
| with torch.no_grad(): | |
| for k, v in state.items(): | |
| dst = own[k] | |
| if torch.is_floating_point(v): | |
| dst.copy_(v.to(device=dst.device, dtype=dst.dtype, non_blocking=True)) | |
| else: | |
| dst.copy_(v.to(device=dst.device, non_blocking=True)) | |
| if device == "cuda": | |
| torch.cuda.synchronize() | |
| print(f"param copy {time.perf_counter() - t_copy:.1f}s", flush=True) | |
| del weights, state | |
| gc.collect() | |
| def _build_model(mod, cfg, device: str): | |
| # meta init avoids allocating a 7GB fp32 empty model in 15GB RAM | |
| with torch.device("meta"): | |
| model = mod.ScugnizzDecoder(cfg) | |
| model = model.to_empty(device=device) | |
| if device == "cuda": | |
| model = model.half() | |
| # to_empty may split tied weights | |
| model.tok_emb.weight = model.lm_head.weight | |
| return model | |
| def _stop_token_ids(tok): | |
| ids = set() | |
| if getattr(tok, "eos_token_id", None) is not None: | |
| ids.add(int(tok.eos_token_id)) | |
| unk = getattr(tok, "unk_token_id", None) | |
| for s in ("<|eot_id|>", "<|eom_id|>"): | |
| tid = tok.convert_tokens_to_ids(s) | |
| if tid is None or tid < 0: | |
| continue | |
| if unk is not None and tid == unk: | |
| continue | |
| ids.add(int(tid)) | |
| return ids | |
| def unload_model(): | |
| if _state["model"] is not None: | |
| print("unloading model…", flush=True) | |
| _state["model"] = None | |
| _state["tok"] = None | |
| gc.collect() | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| _state["status"] = "cold" | |
| def list_hub_runs(): | |
| """List training-runs/* folders on the weights repo (newest-ish last alphabetically reversed).""" | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| runs = [] | |
| for item in api.list_repo_tree( | |
| HUB_REPO, path_in_repo="training-runs", recursive=False, repo_type="model" | |
| ): | |
| p = getattr(item, "path", "") or "" | |
| if p.startswith("training-runs/"): | |
| runs.append(p) | |
| # Prefer chat SFT near top: sort sft-chat first, then reverse chrono name | |
| def key(p): | |
| name = p.rsplit("/", 1)[-1] | |
| pri = 0 if "sft-chat" in name else 1 | |
| return (pri, name) | |
| runs.sort(key=key, reverse=True) | |
| return runs | |
| def list_weight_files(hub_path: str): | |
| """List *.pt weight files under a run folder.""" | |
| from huggingface_hub import HfApi | |
| hub_path = (hub_path or "").strip("/") | |
| if not hub_path: | |
| return ["model_final.pt"] | |
| api = HfApi() | |
| files = [] | |
| for item in api.list_repo_tree( | |
| HUB_REPO, path_in_repo=hub_path, recursive=False, repo_type="model" | |
| ): | |
| p = getattr(item, "path", "") or "" | |
| name = p.rsplit("/", 1)[-1] | |
| if not name.endswith(".pt"): | |
| continue | |
| size = getattr(item, "size", None) or 0 | |
| if size and size < 1_000_000_000: | |
| continue | |
| files.append(name) | |
| if not files: | |
| files = ["model_final.pt"] | |
| # final first, then pulses by step | |
| def wkey(n): | |
| if n == "model_final.pt": | |
| return (0, 0) | |
| if n.startswith("model_pulse_"): | |
| try: | |
| return (1, -int(n.replace("model_pulse_", "").replace(".pt", ""))) | |
| except ValueError: | |
| return (1, 0) | |
| return (2, n) | |
| files.sort(key=wkey) | |
| return files | |
| def load_model(force: bool = False): | |
| if _state["model"] is not None and not force: | |
| return _state["model"], _state["tok"], _state["dev"] | |
| if force: | |
| unload_model() | |
| hub_path = _active_hub_path() | |
| weights_file = _active_weights_file() | |
| _state["error"] = None | |
| _state["status"] = f"loading {hub_path}/{weights_file}" | |
| t0 = time.perf_counter() | |
| try: | |
| mod = _load_train_module() | |
| model_dir = _resolve_model_dir(hub_path) | |
| run_args = _load_run_args(model_dir, hub_path) | |
| dev = "cuda" if torch.cuda.is_available() else "cpu" | |
| print(f"device={dev} cuda={torch.cuda.is_available()}", flush=True) | |
| tok = _load_tokenizer(model_dir, hub_path) | |
| if tok.pad_token is None: | |
| tok.pad_token = tok.eos_token | |
| cfg = mod.preset_config( | |
| run_args.get("model_size", "1.7b"), | |
| len(tok), | |
| # Pretrain was 4096; chat SFT now 4096 — match pretrain/Gradio window | |
| int(os.environ.get("BLOCK_SIZE", "4096")), | |
| 0.0, | |
| run_args.get("pcs_a", 0.8309193524478643), | |
| run_args.get("pcs_b", 0.0), | |
| ) | |
| cfg.gradient_checkpointing = False | |
| model = _build_model(mod, cfg, dev) | |
| wpath = model_dir / weights_file | |
| if not wpath.is_file(): | |
| # fall back to final if pulse missing locally but listed | |
| alt = model_dir / "model_final.pt" | |
| if weights_file != "model_final.pt" and alt.is_file(): | |
| raise FileNotFoundError(f"{wpath} missing — try Refresh weights or model_final.pt") | |
| raise FileNotFoundError(f"missing weights: {wpath}") | |
| weight_path = _materialize_weights(wpath, hub_path, weights_file) | |
| _load_weights_into_model(model, weight_path, dev) | |
| model.eval() | |
| _state.update(model=model, tok=tok, dev=dev, status="ready") | |
| print(f"model ready in {time.perf_counter() - t0:.1f}s total", flush=True) | |
| return model, tok, dev | |
| except Exception as e: | |
| _state["error"] = str(e) | |
| _state["status"] = f"error: {e}" | |
| print(f"load failed: {e}", flush=True) | |
| raise | |
| def switch_checkpoint(hub_path: str, weights_file: str): | |
| hub_path = (hub_path or "").strip("/") | |
| weights_file = (weights_file or "model_final.pt").strip() or "model_final.pt" | |
| if not hub_path: | |
| return f"**Status:** pick a run · current `{_active_hub_path()}/{_active_weights_file()}`" | |
| same = hub_path == _active_hub_path() and weights_file == _active_weights_file() and _state["model"] is not None | |
| if same: | |
| return f"**Status:** already loaded `{hub_path}/{weights_file}`" | |
| _state["hub_path"] = hub_path | |
| _state["weights_file"] = weights_file | |
| try: | |
| load_model(force=True) | |
| return f"**Status:** ready · `{hub_path}/{weights_file}`" | |
| except Exception as e: | |
| return f"**Status:** error loading `{hub_path}/{weights_file}`: {e}" | |
| def _as_text(content) -> str: | |
| """Normalize Gradio message content (str | list blocks | ChatMessage-like).""" | |
| if content is None: | |
| return "" | |
| if isinstance(content, str): | |
| return content | |
| if isinstance(content, list): | |
| parts = [] | |
| for block in content: | |
| if isinstance(block, str): | |
| parts.append(block) | |
| elif isinstance(block, dict): | |
| parts.append(str(block.get("text") or block.get("content") or "")) | |
| else: | |
| parts.append(str(getattr(block, "text", "") or "")) | |
| return "".join(parts) | |
| return str(content) | |
| def _strip_ctx_footer(text: str) -> str: | |
| marker = "\n\n—\n*ctx " | |
| i = text.rfind(marker) | |
| return text[:i].rstrip() if i >= 0 else text | |
| def history_to_messages(history): | |
| """Gradio 5 history: dicts, ChatMessage objects, or legacy tuples.""" | |
| msgs = [{"role": "system", "content": SYSTEM}] | |
| if not history: | |
| return msgs | |
| for m in history: | |
| if isinstance(m, dict): | |
| role = m.get("role") | |
| content = _as_text(m.get("content")) | |
| elif hasattr(m, "role") and hasattr(m, "content"): | |
| role = getattr(m, "role", None) | |
| content = _as_text(getattr(m, "content", None)) | |
| elif isinstance(m, (list, tuple)) and len(m) == 2: | |
| # legacy [user, assistant] pair | |
| u, a = _as_text(m[0]), _as_text(m[1]) | |
| if u: | |
| msgs.append({"role": "user", "content": u}) | |
| if a: | |
| msgs.append({"role": "assistant", "content": _strip_ctx_footer(a)}) | |
| continue | |
| else: | |
| continue | |
| if role in ("user", "assistant") and content: | |
| if role == "assistant": | |
| content = _strip_ctx_footer(content) | |
| msgs.append({"role": role, "content": content}) | |
| return msgs | |
| def _prompt_ids(tok, msgs, budget: int): | |
| """Fit chat into block_size: drop oldest turns first, then left-trim latest user text.""" | |
| system, rest = msgs[0], msgs[1:] | |
| if not rest: | |
| prompt = tok.apply_chat_template([system], tokenize=False, add_generation_prompt=True) | |
| ids = tok.encode(prompt, add_special_tokens=False) | |
| return prompt, ids[:budget], False | |
| dropped = 0 | |
| for keep_from in range(0, len(rest)): | |
| candidate = [system] + rest[keep_from:] | |
| prompt = tok.apply_chat_template( | |
| candidate, tokenize=False, add_generation_prompt=True | |
| ) | |
| ids = tok.encode(prompt, add_special_tokens=False) | |
| if len(ids) <= budget: | |
| if keep_from: | |
| print(f"context: dropped {keep_from} older turns → {len(ids)} tok", flush=True) | |
| return prompt, ids, bool(keep_from) | |
| dropped = keep_from + 1 | |
| # Latest user message alone still too long — keep its tail (question usually at end) | |
| last = dict(rest[-1]) | |
| content = last.get("content") or "" | |
| lo, hi = 0, len(content) | |
| best_prompt, best_ids = None, None | |
| while lo < hi: | |
| mid = (lo + hi) // 2 | |
| last["content"] = content[mid:] | |
| prompt = tok.apply_chat_template( | |
| [system, last], tokenize=False, add_generation_prompt=True | |
| ) | |
| ids = tok.encode(prompt, add_special_tokens=False) | |
| if len(ids) <= budget: | |
| best_prompt, best_ids = prompt, ids | |
| hi = mid | |
| else: | |
| lo = mid + 1 | |
| if best_ids is None: | |
| last["content"] = content[-500:] | |
| best_prompt = tok.apply_chat_template( | |
| [system, last], tokenize=False, add_generation_prompt=True | |
| ) | |
| best_ids = tok.encode(best_prompt, add_special_tokens=False)[-budget:] | |
| print( | |
| f"context: trimmed latest user (+ dropped {dropped} turns) → {len(best_ids)} tok", | |
| flush=True, | |
| ) | |
| return best_prompt, best_ids, True | |
| def _generate(model, tok, ids, max_new_tokens, temperature, top_k, device): | |
| model.eval() | |
| x = torch.tensor([ids], dtype=torch.long, device=device) | |
| greedy = temperature <= 0.0 | |
| stop_ids = _stop_token_ids(tok) | |
| out_ids = [] | |
| t0 = time.perf_counter() | |
| logits, _, kvs = model(x, use_cache=True) | |
| for _ in range(max_new_tokens): | |
| step_logits = logits[:, -1, :] | |
| if greedy: | |
| next_id = step_logits.argmax(dim=-1, keepdim=True) | |
| else: | |
| step_logits = step_logits / max(temperature, 1e-5) | |
| if top_k > 0: | |
| v, _ = torch.topk(step_logits, min(top_k, step_logits.size(-1))) | |
| step_logits = step_logits.masked_fill(step_logits < v[:, [-1]], float("-inf")) | |
| probs = F.softmax(step_logits, dim=-1) | |
| next_id = torch.multinomial(probs, num_samples=1) | |
| tid = int(next_id.item()) | |
| out_ids.append(tid) | |
| if tid in stop_ids: | |
| break | |
| if len(ids) + len(out_ids) >= model.cfg.block_size: | |
| break | |
| logits, _, kvs = model(next_id, past_kvs=kvs, use_cache=True) | |
| dt = max(time.perf_counter() - t0, 1e-6) | |
| print(f"gen {len(out_ids)} tok in {dt:.2f}s ({len(out_ids) / dt:.1f} tok/s)", flush=True) | |
| return tok.decode(out_ids, skip_special_tokens=True).strip() | |
| def respond(message, history, max_new_tokens, temperature, top_k): | |
| model, tok, dev = load_model() | |
| msgs = history_to_messages(history) | |
| msgs.append({"role": "user", "content": _as_text(message)}) | |
| lengths = [len(m["content"]) for m in msgs] | |
| print( | |
| f"msgs={len(msgs)} roles={[m['role'] for m in msgs]} chars={lengths} " | |
| f"temp={temperature} top_k={top_k} max_new={max_new_tokens}", | |
| flush=True, | |
| ) | |
| for i, m in enumerate(msgs): | |
| print(f"--- msg[{i}] {m['role']} ({len(m['content'])} chars) ---", flush=True) | |
| print(m["content"], flush=True) | |
| budget = model.cfg.block_size - 1 | |
| prompt, ids, truncated = _prompt_ids(tok, msgs, budget) | |
| print( | |
| f"prompt_tokens={len(ids)}/{budget} truncated={truncated}", | |
| flush=True, | |
| ) | |
| print("--- prompt begin ---", flush=True) | |
| print(prompt, flush=True) | |
| print("--- prompt end ---", flush=True) | |
| text = _generate( | |
| model, | |
| tok, | |
| ids, | |
| int(max_new_tokens), | |
| float(temperature), | |
| int(top_k), | |
| dev, | |
| ) | |
| print("--- assistant begin ---", flush=True) | |
| print(text, flush=True) | |
| print("--- assistant end ---", flush=True) | |
| note = f"\n\n—\n*ctx {len(ids)}/{budget} tok*" + (" *(trimmed)*" if truncated else "") | |
| return text + note | |
| def build_ui(): | |
| try: | |
| run_choices = list_hub_runs() | |
| except Exception as e: | |
| print(f"list_hub_runs failed: {e}", flush=True) | |
| run_choices = [_active_hub_path()] | |
| default_run = _active_hub_path() | |
| if default_run not in run_choices: | |
| run_choices = [default_run] + run_choices | |
| try: | |
| weight_choices = list_weight_files(default_run) | |
| except Exception: | |
| weight_choices = ["model_final.pt"] | |
| with gr.Blocks(title="Scugnizz Llama-PCS chat") as demo: | |
| status = gr.Markdown( | |
| f"**Status:** {_state.get('status', 'cold')} · `{_active_hub_path()}/{_active_weights_file()}`" | |
| ) | |
| gr.Markdown( | |
| f"""# Scugnizz Llama-PCS chat | |
| Custom ~1.7B PCS decoder · pick any run under `{HUB_REPO}` | |
| **Context window: 4096 tokens**. Each reply shows `ctx N/4095`. | |
| Switching checkpoints downloads ~7GB the first time — wait for **ready**. | |
| For grounded/open-book answers use **temperature 0–0.3** (default 0.2). | |
| """ | |
| ) | |
| with gr.Accordion("Checkpoint", open=True): | |
| run_dd = gr.Dropdown( | |
| choices=run_choices, | |
| value=default_run, | |
| label=f"Hub run ({HUB_REPO})", | |
| allow_custom_value=True, | |
| ) | |
| weight_dd = gr.Dropdown( | |
| choices=weight_choices, | |
| value=_active_weights_file() | |
| if _active_weights_file() in weight_choices | |
| else weight_choices[0], | |
| label="Weights file", | |
| allow_custom_value=True, | |
| ) | |
| with gr.Row(): | |
| refresh_runs = gr.Button("Refresh list", scale=1) | |
| load_btn = gr.Button("Load checkpoint", variant="primary", scale=2) | |
| def on_run_change(hub_path): | |
| try: | |
| ws = list_weight_files(hub_path) | |
| except Exception as e: | |
| return gr.update(choices=["model_final.pt"], value="model_final.pt"), f"**Status:** weight list error: {e}" | |
| val = "model_final.pt" if "model_final.pt" in ws else ws[0] | |
| return gr.update(choices=ws, value=val), f"**Status:** select weights · `{hub_path}`" | |
| def on_refresh(): | |
| try: | |
| runs = list_hub_runs() | |
| except Exception as e: | |
| return gr.update(), f"**Status:** refresh error: {e}" | |
| cur = _active_hub_path() | |
| if cur not in runs: | |
| runs = [cur] + runs | |
| return gr.update(choices=runs, value=cur), f"**Status:** listed {len(runs)} runs" | |
| run_dd.change(on_run_change, [run_dd], [weight_dd, status]) | |
| refresh_runs.click(on_refresh, None, [run_dd, status]) | |
| load_btn.click(switch_checkpoint, [run_dd, weight_dd], status) | |
| chatbot = gr.Chatbot(height=480, type="messages") | |
| msg = gr.Textbox( | |
| placeholder="Ask something… (for open-book: paste passage, then question at the end)", | |
| scale=1, | |
| ) | |
| with gr.Accordion("Generation", open=False): | |
| max_new = gr.Slider(16, 512, value=128, step=8, label="max new tokens") | |
| temp = gr.Slider(0.0, 1.5, value=0.2, step=0.05, label="temperature (0 = greedy)") | |
| top_k = gr.Slider(0, 200, value=50, step=1, label="top-k (0 = off)") | |
| clear = gr.Button("Clear") | |
| # Single handler — avoids Gradio queue race where .then() saw empty/stale history | |
| # (logs showed prompt_tokens≈48 while user pasted long context). | |
| def chat(user_msg, history, max_new_tokens, temperature, top_k): | |
| history = list(history or []) | |
| user_msg = _as_text(user_msg) | |
| if not user_msg.strip(): | |
| return "", history | |
| prior = history | |
| try: | |
| answer = respond(user_msg, prior, max_new_tokens, temperature, top_k) | |
| except Exception as e: | |
| answer = f"(error loading/generating: {e})" | |
| history = prior + [ | |
| {"role": "user", "content": user_msg}, | |
| {"role": "assistant", "content": answer}, | |
| ] | |
| return "", history | |
| def refresh_status(): | |
| return ( | |
| f"**Status:** {_state.get('status', 'cold')} · " | |
| f"`{_active_hub_path()}/{_active_weights_file()}`" | |
| ) | |
| msg.submit( | |
| chat, [msg, chatbot, max_new, temp, top_k], [msg, chatbot] | |
| ).then(refresh_status, None, status) | |
| clear.click(lambda: [], None, chatbot, queue=False) | |
| demo.load(refresh_status, None, status) | |
| return demo | |
| if __name__ == "__main__": | |
| print("preloading model before serving…", flush=True) | |
| try: | |
| load_model() | |
| except Exception as e: | |
| print(f"preload failed (will retry on first request): {e}", flush=True) | |
| demo = build_ui() | |
| demo.queue().launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.environ.get("PORT", 7860)), | |
| ssr_mode=False, | |
| ) | |