| import os, io, json, time, tarfile, subprocess, urllib.request |
| from pathlib import Path |
|
|
| import gradio as gr |
| import spaces |
| import httpx |
| from huggingface_hub import hf_hub_download |
|
|
| |
| LLAMA_TAG = os.environ.get("LLAMA_TAG", "") |
| MODEL_REPO = os.environ.get("MODEL_REPO", "Abiray/MiniCPM5-1B-GGUF") |
| MODEL_FILE = os.environ.get("MODEL_FILE", "minicpm5-1b-Q6_K.gguf") |
| CTX_SIZE = int(os.environ.get("CTX_SIZE", "8192")) |
| PARALLEL = int(os.environ.get("PARALLEL", "2")) |
| LLAMA_PORT = int(os.environ.get("LLAMA_PORT", "8080")) |
| API_KEY = os.environ.get("LLAMA_API_KEY", "") |
| SYSTEM_PROMPT = os.environ.get("SYSTEM_PROMPT", "You are a helpful AI assistant.") |
|
|
| |
| @spaces.GPU(duration=5) |
| def _hold_zerogpu(): |
| print("[zerogpu] host claimed; GPU slice released, CPU+RAM stay ours.") |
| return True |
|
|
| _hold_zerogpu() |
|
|
| |
| def effective_cpus() -> int: |
| try: |
| quota, period = Path("/sys/fs/cgroup/cpu.max").read_text().split()[:2] |
| if quota != "max": |
| return max(1, int(quota) // int(period)) |
| except Exception: |
| pass |
| try: |
| return len(os.sched_getaffinity(0)) |
| except Exception: |
| return os.cpu_count() or 2 |
|
|
| def memory_limit_gb(): |
| try: |
| v = Path("/sys/fs/cgroup/memory.max").read_text().strip() |
| if v != "max": |
| return round(int(v) / 1e9, 1) |
| except Exception: |
| pass |
| return None |
|
|
| CORES = effective_cpus() |
| print(f"[resources] effective cores: {CORES} | RAM limit: {memory_limit_gb() or '?'} GB") |
|
|
| |
| FALLBACK_TAG = "b8407" |
|
|
| def resolve_tag() -> str: |
| if LLAMA_TAG: |
| return LLAMA_TAG |
| try: |
| req = urllib.request.Request( |
| "https://api.github.com/repos/ggml-org/llama.cpp/releases/latest", |
| headers={"User-Agent": "hf-space"}) |
| with urllib.request.urlopen(req, timeout=10) as r: |
| return json.load(r)["tag_name"] |
| except Exception as e: |
| print(f"[llama.cpp] latest-tag lookup failed ({e}); pinned fallback {FALLBACK_TAG}") |
| return FALLBACK_TAG |
|
|
| def ensure_llama_server() -> Path: |
| tag = resolve_tag() |
| root = Path.home() / ".cache" / "llamacpp" / tag |
| server = root / "build" / "bin" / "llama-server" |
| if not server.exists(): |
| |
| url = (f"https://github.com/ggml-org/llama.cpp/releases/download/" |
| f"{tag}/llama-{tag}-bin-ubuntu-x64.tar.gz") |
| print(f"[llama.cpp] downloading {url}") |
| req = urllib.request.Request(url, headers={"User-Agent": "hf-space"}) |
| with urllib.request.urlopen(req, timeout=180) as r: |
| blob = r.read() |
| root.mkdir(parents=True, exist_ok=True) |
| |
| with tarfile.open(fileobj=io.BytesIO(blob), mode="r:gz") as t: |
| t.extractall(root) |
| for f in (root / "build" / "bin").iterdir(): |
| f.chmod(0o755) |
| return server |
|
|
| |
| print(f"[model] downloading {MODEL_REPO}/{MODEL_FILE}") |
| model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE) |
|
|
| |
| server_bin = ensure_llama_server() |
| UPSTREAM = f"http://127.0.0.1:{LLAMA_PORT}" |
|
|
| env = os.environ.copy() |
| env["LD_LIBRARY_PATH"] = f"{server_bin.parent}:{env.get('LD_LIBRARY_PATH', '')}" |
| env.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1") |
|
|
| cmd = [ |
| str(server_bin), "-m", model_path, |
| "--host", "127.0.0.1", "--port", str(LLAMA_PORT), |
| "-t", str(CORES), "-tb", str(CORES), |
| "-c", str(CTX_SIZE), |
| "-np", str(PARALLEL), |
| "-fa", |
| "--cache-reuse", "256", |
| ] |
| if API_KEY: |
| cmd += ["--api-key", API_KEY] |
|
|
| proc = subprocess.Popen(cmd, env=env) |
|
|
| def wait_ready(timeout=600): |
| t0 = time.time() |
| while time.time() - t0 < timeout: |
| if proc.poll() is not None: |
| raise RuntimeError(f"llama-server died early (code {proc.returncode})") |
| try: |
| if httpx.get(f"{UPSTREAM}/health", timeout=2).status_code == 200: |
| print("[llama.cpp] server ready") |
| return |
| except Exception: |
| pass |
| time.sleep(1) |
| raise TimeoutError("llama-server not ready in time") |
|
|
| wait_ready() |
|
|
| |
| from openai import OpenAI |
| oai = OpenAI(base_url=f"{UPSTREAM}/v1", api_key=API_KEY or "not-needed") |
|
|
| def _normalize(history): |
| msgs = [{"role": "system", "content": SYSTEM_PROMPT}] |
| for item in history: |
| if isinstance(item, dict): |
| msgs.append({"role": item["role"], "content": item["content"]}) |
| else: |
| user, bot = item |
| msgs.append({"role": "user", "content": user}) |
| if bot: |
| msgs.append({"role": "assistant", "content": bot}) |
| return msgs |
|
|
| def chat(message, history): |
| stream = oai.chat.completions.create( |
| model="local", |
| messages=_normalize(history) + [{"role": "user", "content": message}], |
| stream=True, temperature=0.7, max_tokens=1024, |
| ) |
| out = "" |
| for chunk in stream: |
| delta = chunk.choices[0].delta.content |
| if delta: |
| out += delta |
| yield out |
|
|
| with gr.Blocks() as demo: |
| gr.Markdown( |
| f"# β‘ llama.cpp on ZeroGPU's free CPU\n" |
| f"`{MODEL_REPO}` Β· {CORES} threads Β· ctx {CTX_SIZE} Β· {PARALLEL} parallel slots\n\n" |
| f"**External API:** `POST /v1/chat/completions` on this Space's `*.hf.space` URL." |
| ) |
| gr.ChatInterface(fn=chat, examples=["Who are you?", |
| "Write a python script to reverse a string.", "Explain quantum computing."]) |
|
|
| demo = demo.queue() |
|
|
| |
| from fastapi import FastAPI, Request |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.responses import Response, StreamingResponse |
|
|
| app = FastAPI(title="llama-cpp-proxy") |
| app.add_middleware(CORSMiddleware, allow_origins=["*"], |
| allow_methods=["*"], allow_headers=["*"]) |
| _http = httpx.AsyncClient(timeout=httpx.Timeout(None)) |
|
|
| @app.api_route("/v1/{path:path}", methods=["GET", "POST"]) |
| async def v1_proxy(path: str, request: Request): |
| body = await request.body() |
| headers = {k: request.headers[k] for k in ("authorization", "content-type") |
| if k in request.headers} |
| wants_stream = False |
| if body: |
| try: |
| wants_stream = bool(json.loads(body).get("stream")) |
| except Exception: |
| pass |
|
|
| url = f"{UPSTREAM}/v1/{path}" |
| if request.method == "POST" and wants_stream: |
| req = _http.build_request("POST", url, content=body, headers=headers) |
| resp = await _http.send(req, stream=True) |
| async def gen(): |
| try: |
| async for chunk in resp.aiter_raw(): |
| yield chunk |
| finally: |
| await resp.aclose() |
| return StreamingResponse(gen(), status_code=resp.status_code, |
| media_type="text/event-stream") |
|
|
| resp = await _http.request(request.method, url, content=body or None, headers=headers) |
| return Response(content=resp.content, status_code=resp.status_code, |
| media_type=resp.headers.get("content-type", "application/json")) |
|
|
| app = gr.mount_gradio_app(app, demo, path="/") |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "7860"))) |