| import os |
| import fnmatch |
| import urllib.request |
| import json |
| import time |
| import threading |
| import asyncio |
| import multiprocessing as mp |
| from concurrent.futures import ProcessPoolExecutor |
| from huggingface_hub import hf_hub_download, HfApi |
| import uvicorn |
| from fastapi import FastAPI, Request, HTTPException |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.responses import JSONResponse, StreamingResponse |
| from pydantic import BaseModel |
|
|
| |
| |
| |
| |
| CURATED_MODELS = { |
| "deepseek": { |
| "id": "deepseek", |
| "role": "reasoning", |
| "label": "DeepSeek-R1-Distill Qwen 7B", |
| "emoji": "π§ ", |
| "category": "Reasoning / Coding", |
| "repo": "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF", |
| "file": "*Q4_K_M.gguf", |
| "ram_gb": 4.8, |
| "n_ctx": 8192, |
| "description": "Distilled DeepSeek reasoning model β punches far above its size. Best for logic, math, coding.", |
| }, |
| "wizardvicuna": { |
| "id": "wizardvicuna", |
| "role": "uncensored-alt", |
| "label": "Wizard-Vicuna 7B Uncensored", |
| "emoji": "π§", |
| "category": "Uncensored", |
| "repo": "TheBloke/Wizard-Vicuna-7B-Uncensored-GGUF", |
| "file": "*Q4_K_M.gguf", |
| "ram_gb": 4.8, |
| "n_ctx": 4096, |
| "description": "Classic uncensored Llama-2 7B β reliable, well-tested, same size class as the other curated models so it stays within the free CPU tier's time budget.", |
| }, |
| "dolphin": { |
| "id": "dolphin", |
| "role": "uncensored", |
| "label": "Dolphin 2.9.3 Mistral 7B", |
| "emoji": "π¬", |
| "category": "Uncensored", |
| |
| |
| |
| |
| |
| "repo": "bartowski/dolphin-2.9.3-mistral-7B-32k-GGUF", |
| "file": "*Q4_K_M.gguf", |
| "ram_gb": 4.8, |
| "n_ctx": 8192, |
| "description": "Eric Hartford's Dolphin series β gold standard uncensored model. Follows all instructions without refusal.", |
| }, |
| "qwen": { |
| "id": "qwen", |
| "role": "general", |
| "label": "Qwen 2.5 7B Instruct", |
| "emoji": "β‘", |
| "category": "Best General", |
| "repo": "bartowski/Qwen2.5-7B-Instruct-GGUF", |
| "file": "*Q4_K_M.gguf", |
| "ram_gb": 4.8, |
| "n_ctx": 8192, |
| "description": "Alibaba's flagship 7B model β extremely capable, multilingual, fast. Best all-rounder for daily use.", |
| }, |
| |
| |
| |
| |
| |
| |
| |
| |
| "qwen3thinking": { |
| "id": "qwen3thinking", |
| "role": "reasoning-alt", |
| "label": "Qwen3 4B Thinking (2507)", |
| "emoji": "π€", |
| "category": "Reasoning / Coding", |
| "repo": "unsloth/Qwen3-4B-Thinking-2507-GGUF", |
| "file": "*Q4_K_M.gguf", |
| "ram_gb": 2.8, |
| "n_ctx": 8192, |
| "description": "Purpose-built reasoning/thinking model, newer generation than DeepSeek-R1-Distill-Qwen-7B and roughly half the size β faster on CPU with comparable reasoning quality.", |
| }, |
| |
| |
| |
| |
| "dolphin3": { |
| "id": "dolphin3", |
| "role": "uncensored", |
| "label": "Dolphin 3.0 Llama 3.2 3B", |
| "emoji": "π¬", |
| "category": "Uncensored", |
| "repo": "bartowski/Dolphin3.0-Llama3.2-3B-GGUF", |
| "file": "*Q4_K_M.gguf", |
| "ram_gb": 2.2, |
| "n_ctx": 8192, |
| "description": "Official Dolphin 3.0 release from the cognitivecomputations/dphn team β newer generation, less than half the size of Dolphin 2.9.3, follows instructions without refusal.", |
| }, |
| } |
| ROLE_TO_MODEL_ID = {spec["role"]: mid for mid, spec in CURATED_MODELS.items()} |
|
|
| |
| |
| |
| ROLE_SEARCH = { |
| "reasoning": {"query": "reasoning instruct gguf", "prefer": ["r1", "reasoning", "qwq", "thinking", "o1"]}, |
| "uncensored": {"query": "uncensored abliterated gguf", "prefer": ["uncensored", "abliterated", "dolphin"]}, |
| "general": {"query": "instruct chat gguf", "prefer": []}, |
| } |
| |
| |
| _SIZE_BLOCKLIST = ("70b", "72b", "65b", "34b", "32b", "30b", "27b", "24b", "22b", "20b", "14b", "13b", "405b") |
|
|
| |
| |
| |
| SPACE_ROLE = os.environ.get("SPACE_ROLE", "general").strip().lower() |
| if SPACE_ROLE not in ROLE_TO_MODEL_ID: |
| print(f"[Config] Unknown SPACE_ROLE '{SPACE_ROLE}', defaulting to 'general'") |
| SPACE_ROLE = "general" |
|
|
| CONFIG_URL = os.environ.get("CONFIG_URL", "") |
| MODEL_ID = os.environ.get("MODEL_ID", ROLE_TO_MODEL_ID[SPACE_ROLE]) |
| MODEL_REPO = os.environ.get("MODEL_REPO", "") |
| MODEL_FILE = os.environ.get("MODEL_FILE", "") |
| N_CTX = int(os.environ.get("N_CTX", "8192")) |
| N_THREADS = int(os.environ.get("N_THREADS", "4")) |
| API_KEY = os.environ.get("API_KEY", "") |
| MODEL_SOURCE = "fallback" |
| LAST_REFRESHED = None |
|
|
| |
| if CONFIG_URL: |
| print(f"[Config] Fetching cluster config from: {CONFIG_URL}") |
| try: |
| with urllib.request.urlopen(urllib.request.Request(CONFIG_URL), timeout=10) as r: |
| cfg = json.loads(r.read().decode()) |
| MODEL_ID = cfg.get("MODEL_ID", cfg.get("model_id", MODEL_ID)) |
| MODEL_REPO = cfg.get("MODEL_REPO", cfg.get("model_repo", MODEL_REPO)) |
| MODEL_FILE = cfg.get("MODEL_FILE", cfg.get("model_file", MODEL_FILE)) |
| if "N_CTX" in cfg or "n_ctx" in cfg: N_CTX = int(cfg.get("N_CTX", cfg.get("n_ctx", N_CTX))) |
| if "N_THREADS" in cfg or "n_threads" in cfg: N_THREADS = int(cfg.get("N_THREADS", cfg.get("n_threads", N_THREADS))) |
| API_KEY = cfg.get("API_KEY", cfg.get("api_key", API_KEY)) |
| print("[Config] Cluster config synced.") |
| except Exception as e: |
| print(f"[Config] Fetch failed ({e}), using local env vars.") |
|
|
| |
| def resolve_model_spec(model_id, repo_override="", file_override=""): |
| if repo_override: |
| return repo_override, file_override or "*.gguf", N_CTX |
| spec = CURATED_MODELS.get(model_id, CURATED_MODELS[ROLE_TO_MODEL_ID[SPACE_ROLE]]) |
| return spec["repo"], spec["file"], spec.get("n_ctx", N_CTX) |
|
|
| ACTIVE_MODEL_ID = MODEL_ID if MODEL_ID in CURATED_MODELS else ROLE_TO_MODEL_ID[SPACE_ROLE] |
| active_repo, active_file, active_ctx = resolve_model_spec(ACTIVE_MODEL_ID, MODEL_REPO, MODEL_FILE) |
|
|
| |
| def download_model(repo, file_pattern): |
| try: |
| if "*" not in file_pattern: |
| return hf_hub_download(repo_id=repo, filename=file_pattern) |
| except Exception as e: |
| print(f"[Download] Direct download failed: {e}, searchingβ¦") |
| api = HfApi() |
| files = list(api.list_repo_files(repo_id=repo)) |
| matches = [f for f in files if fnmatch.fnmatch(f.lower(), file_pattern.lower())] |
| if not matches: |
| matches = [f for f in files if f.endswith(".gguf")] |
| if not matches: |
| raise FileNotFoundError(f"No GGUF found in {repo}") |
| print(f"[Download] Selected: {matches[0]}") |
| return hf_hub_download(repo_id=repo, filename=matches[0]) |
|
|
| |
| def discover_best_model_for_role(role): |
| """Search the Hub for the most-downloaded GGUF model matching this role. |
| Returns (repo, file, label) or None if nothing usable is found β callers |
| must leave the currently-loaded model running on None rather than clobber it.""" |
| spec = ROLE_SEARCH.get(role, ROLE_SEARCH["general"]) |
| api = HfApi() |
| try: |
| candidates = list(api.list_models(search=spec["query"], sort="downloads", direction=-1, limit=25)) |
| except Exception as e: |
| print(f"[Discover] Hub search failed: {e}") |
| return None |
|
|
| def score(m): |
| name = m.id.lower() |
| return sum(1 for kw in spec["prefer"] if kw in name) |
| candidates.sort(key=score, reverse=True) |
|
|
| for m in candidates: |
| name = m.id.lower() |
| if any(b in name for b in _SIZE_BLOCKLIST): |
| continue |
| try: |
| files = list(api.list_repo_files(m.id)) |
| except Exception: |
| continue |
| match = next((f for f in files if fnmatch.fnmatch(f.lower(), "*q4_k_m.gguf")), None) |
| if not match: |
| match = next((f for f in files if f.lower().endswith(".gguf") and "q4" in f.lower()), None) |
| if not match: |
| continue |
| label = m.id.split("/")[-1] |
| print(f"[Discover] Role '{role}' -> {m.id} / {match}") |
| return m.id, match, label |
|
|
| print(f"[Discover] No suitable model found for role '{role}'") |
| return None |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| PUBLIC_PORT = 7860 |
| _mp_ctx = mp.get_context("spawn") |
|
|
| |
| _worker_llama = None |
|
|
| def _init_worker(model_path, ctx, n_threads): |
| global _worker_llama |
| from llama_cpp import Llama |
| print(f"[Worker] Loading {model_path}") |
| _worker_llama = Llama(model_path=model_path, n_ctx=ctx, n_threads=n_threads, verbose=False) |
| print(f"[Worker] Ready: {model_path}") |
|
|
| def _worker_generate(messages, max_tokens, temperature): |
| return _worker_llama.create_chat_completion( |
| messages=messages, max_tokens=max_tokens, temperature=temperature, stream=False |
| ) |
|
|
| def _worker_noop(): |
| |
| |
| return True |
|
|
| class WorkerBusy(Exception): |
| pass |
|
|
| class _Worker: |
| """One persistent worker process holding the loaded model. Recreated on model switch.""" |
| def __init__(self): |
| self.executor = None |
| self.ready = threading.Event() |
| self.model_path = "" |
| self.ctx = N_CTX |
| self.lock = threading.Lock() |
| self.queue_lock = asyncio.Lock() |
|
|
| def start(self, model_path, ctx): |
| self.ready.clear() |
| self.ctx = ctx |
| old = self.executor |
| self.executor = ProcessPoolExecutor( |
| max_workers=1, mp_context=_mp_ctx, |
| initializer=_init_worker, initargs=(model_path, ctx, N_THREADS), |
| ) |
| self.model_path = model_path |
| if old: |
| old.shutdown(wait=False, cancel_futures=True) |
|
|
| |
| |
| |
| def _mark_ready(): |
| try: |
| self.executor.submit(_worker_noop).result() |
| self.ready.set() |
| print(f"[Worker] Model ready: {model_path}") |
| except Exception as e: |
| print(f"[Worker] Failed to become ready: {e}") |
|
|
| threading.Thread(target=_mark_ready, daemon=True).start() |
|
|
| async def generate(self, messages, max_tokens, temperature): |
| |
| |
| |
| |
| |
| |
| |
| try: |
| await asyncio.wait_for(self.queue_lock.acquire(), timeout=100) |
| except asyncio.TimeoutError: |
| raise WorkerBusy("Server has a long queue right now β please wait and try again.") |
| try: |
| loop = asyncio.get_running_loop() |
| with self.lock: |
| executor = self.executor |
| try: |
| |
| |
| |
| |
| |
| |
| |
| |
| return await asyncio.wait_for( |
| loop.run_in_executor(executor, _worker_generate, messages, max_tokens, temperature), |
| timeout=90, |
| ) |
| except asyncio.TimeoutError: |
| print("[Worker] Generation exceeded 90s β killing and restarting the worker process") |
| self.start(self.model_path, self.ctx) |
| raise RuntimeError("Generation timed out (prompt may be too long for this model's context window) β the worker was restarted, try again with a shorter message.") |
| finally: |
| self.queue_lock.release() |
|
|
| worker = _Worker() |
|
|
| |
| proxy = FastAPI(title="JollyDay LLM Space β Multi-Model") |
|
|
| proxy.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| def _check_api_key(request: Request): |
| if not API_KEY: |
| return |
| got = request.headers.get("authorization", "") |
| if got != f"Bearer {API_KEY}": |
| raise HTTPException(status_code=401, detail="Invalid API key") |
|
|
| |
|
|
| @proxy.get("/jolly/health") |
| async def jolly_health(): |
| return JSONResponse({ |
| "status": "ready" if worker.ready.is_set() else "loading", |
| "role": SPACE_ROLE, |
| "model_id": ACTIVE_MODEL_ID, |
| "model_file": os.path.basename(worker.model_path), |
| "model_source": MODEL_SOURCE, |
| "n_ctx": N_CTX, |
| "n_threads": N_THREADS, |
| }) |
|
|
| @proxy.get("/jolly/info") |
| async def jolly_info(): |
| spec = CURATED_MODELS.get(ACTIVE_MODEL_ID, {}) |
| return JSONResponse({ |
| "role": SPACE_ROLE, |
| "model_id": ACTIVE_MODEL_ID, |
| "model_repo": active_repo, |
| "model_file": active_file, |
| "model_path": os.path.basename(worker.model_path), |
| "model_source": MODEL_SOURCE, |
| "last_refreshed": LAST_REFRESHED, |
| "label": spec.get("label", ""), |
| "n_ctx": N_CTX, |
| "n_threads": N_THREADS, |
| "api_key_set": bool(API_KEY), |
| "config_url": CONFIG_URL, |
| "server_ready": worker.ready.is_set(), |
| }) |
|
|
| @proxy.get("/jolly/models") |
| async def jolly_models(): |
| return JSONResponse({ |
| "role": SPACE_ROLE, |
| "active_id": ACTIVE_MODEL_ID, |
| "models": list(CURATED_MODELS.values()), |
| }) |
|
|
| @proxy.get("/jolly/active") |
| async def jolly_active(): |
| spec = CURATED_MODELS.get(ACTIVE_MODEL_ID, {}) |
| return JSONResponse({ |
| "id": ACTIVE_MODEL_ID, |
| "role": SPACE_ROLE, |
| "label": spec.get("label", os.path.basename(worker.model_path)), |
| "emoji": spec.get("emoji", "π€"), |
| "category": spec.get("category", ""), |
| "description": spec.get("description", ""), |
| "model_file": os.path.basename(worker.model_path), |
| "model_source": MODEL_SOURCE, |
| "server_ready": worker.ready.is_set(), |
| }) |
|
|
| @proxy.get("/jolly/wake") |
| async def jolly_wake(): |
| """Lightweight wake endpoint β just touching this URL wakes a sleeping HF Space.""" |
| spec = CURATED_MODELS.get(ACTIVE_MODEL_ID, {}) |
| return JSONResponse({ |
| "status": "ready" if worker.ready.is_set() else "loading", |
| "model_id": ACTIVE_MODEL_ID, |
| "label": spec.get("label", ACTIVE_MODEL_ID), |
| "emoji": spec.get("emoji", "π€"), |
| }) |
|
|
| class SwitchRequest(BaseModel): |
| model_id: str |
|
|
| @proxy.post("/jolly/switch") |
| async def jolly_switch(req: SwitchRequest): |
| global ACTIVE_MODEL_ID, active_repo, active_file, active_ctx, MODEL_SOURCE |
|
|
| model_id = req.model_id.lower().strip() |
| if model_id not in CURATED_MODELS: |
| raise HTTPException( |
| status_code=400, |
| detail=f"Unknown model_id '{model_id}'. Valid options: {list(CURATED_MODELS.keys())}" |
| ) |
|
|
| if model_id == ACTIVE_MODEL_ID and worker.ready.is_set(): |
| spec = CURATED_MODELS[model_id] |
| return JSONResponse({"status": "already_active", "model_id": model_id, "label": spec["label"]}) |
|
|
| spec = CURATED_MODELS[model_id] |
| ACTIVE_MODEL_ID = model_id |
| active_repo = spec["repo"] |
| active_file = spec["file"] |
| active_ctx = spec.get("n_ctx", N_CTX) |
| MODEL_SOURCE = "fallback" |
|
|
| def _switch(): |
| try: |
| path = download_model(active_repo, active_file) |
| worker.start(path, active_ctx) |
| except Exception as e: |
| print(f"[Switch] ERROR: {e}") |
|
|
| threading.Thread(target=_switch, daemon=True).start() |
|
|
| return JSONResponse({ |
| "status": "switching", |
| "model_id": model_id, |
| "label": spec["label"], |
| "emoji": spec["emoji"], |
| "description": spec["description"], |
| "note": "Model is downloading/loading. Poll /jolly/health until status is 'ready'.", |
| }) |
|
|
| @proxy.post("/jolly/refresh-model") |
| async def jolly_refresh_model(): |
| """Manual 'update models' button: re-query the Hub for the current best |
| model for this Space's role (SPACE_ROLE) and hot-swap to it if found. |
| Leaves the running model untouched if the search comes up empty.""" |
| global ACTIVE_MODEL_ID, active_repo, active_file, active_ctx, MODEL_SOURCE, LAST_REFRESHED |
|
|
| found = discover_best_model_for_role(SPACE_ROLE) |
| if not found: |
| return JSONResponse({ |
| "status": "no_change", |
| "role": SPACE_ROLE, |
| "detail": "No suitable Hub model found for this role β keeping the current model.", |
| "active_model_id": ACTIVE_MODEL_ID, |
| }) |
|
|
| repo, file, label = found |
| ACTIVE_MODEL_ID = f"hub:{label}" |
| active_repo = repo |
| active_file = file |
| active_ctx = N_CTX |
| MODEL_SOURCE = "hub-discovered" |
| LAST_REFRESHED = time.time() |
|
|
| def _switch(): |
| try: |
| path = download_model(active_repo, active_file) |
| worker.start(path, active_ctx) |
| except Exception as e: |
| print(f"[Refresh] ERROR: {e}") |
|
|
| threading.Thread(target=_switch, daemon=True).start() |
|
|
| return JSONResponse({ |
| "status": "switching", |
| "role": SPACE_ROLE, |
| "model_repo": repo, |
| "model_file": file, |
| "label": label, |
| "note": "Discovered from Hub search. Poll /jolly/health until status is 'ready'.", |
| }) |
|
|
| |
| |
| |
| |
| |
|
|
| @proxy.get("/v1/models") |
| async def list_models(request: Request): |
| _check_api_key(request) |
| return JSONResponse({ |
| "object": "list", |
| "data": [{"id": ACTIVE_MODEL_ID, "object": "model", "owned_by": "me"}], |
| }) |
|
|
| @proxy.post("/v1/chat/completions") |
| async def chat_completions(request: Request): |
| _check_api_key(request) |
| if not worker.ready.is_set(): |
| return JSONResponse( |
| {"error": "Model is loading, please retry in a moment.", "status": "loading"}, |
| status_code=503, |
| ) |
| try: |
| body = await request.json() |
| except Exception: |
| return JSONResponse({"error": "Invalid JSON body"}, status_code=400) |
|
|
| messages = body.get("messages", []) |
| |
| |
| |
| |
| |
| |
| |
| max_tokens = min(int(body.get("max_tokens", 80) or 80), 80) |
| temperature = body.get("temperature", 0.7) |
| stream = bool(body.get("stream", False)) |
|
|
| try: |
| result = await worker.generate(messages, max_tokens, temperature) |
| except WorkerBusy as e: |
| return JSONResponse({"error": str(e), "status": "busy"}, status_code=429) |
| except Exception as e: |
| return JSONResponse({"error": str(e)}, status_code=500) |
|
|
| if stream: |
| async def _gen(): |
| content = result.get("choices", [{}])[0].get("message", {}).get("content", "") |
| chunk = { |
| "id": result.get("id", "chatcmpl"), "object": "chat.completion.chunk", |
| "choices": [{"index": 0, "delta": {"content": content}, "finish_reason": None}], |
| } |
| yield f"data: {json.dumps(chunk)}\n\n" |
| yield "data: [DONE]\n\n" |
| return StreamingResponse(_gen(), media_type="text/event-stream") |
|
|
| return JSONResponse(result) |
|
|
| |
| |
| |
| |
| |
| if __name__ == "__main__": |
| print(f"[Boot] Space role : {SPACE_ROLE}") |
| print(f"[Boot] Model ID : {ACTIVE_MODEL_ID}") |
| print(f"[Boot] Model repo : {active_repo}") |
| print(f"[Boot] Model file : {active_file}") |
| print(f"[Boot] Context : {active_ctx} Threads: {N_THREADS}") |
| print(f"[Boot] API key set: {'yes' if API_KEY else 'no'}") |
|
|
| try: |
| boot_path = download_model(active_repo, active_file) |
| except Exception as e: |
| print(f"[WARN] {e} β falling back to Qwen 2.5 1.5B") |
| boot_path = hf_hub_download( |
| repo_id="Qwen/Qwen2.5-1.5B-Instruct-GGUF", |
| filename="qwen2.5-1.5b-instruct-q4_k_m.gguf" |
| ) |
|
|
| worker.start(boot_path, active_ctx) |
|
|
| print(f"[Proxy] Starting on :{PUBLIC_PORT}β¦") |
| uvicorn.run(proxy, host="0.0.0.0", port=PUBLIC_PORT, log_level="info") |
|
|