File size: 8,037 Bytes
5e23e7e 693561f 38a6be8 7319988 693561f 2b52af6 da0d943 693561f da0d943 693561f 5e23e7e 693561f 5e23e7e 693561f 5e23e7e 693561f 7e2bbde 693561f 2b52af6 693561f 38a6be8 693561f 7319988 693561f 38a6be8 693561f 98a8b03 693561f 38a6be8 693561f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 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 209 210 211 212 213 | 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
# ===== 0. CONFIG (override in Space Settings β Variables and secrets) =====
LLAMA_TAG = os.environ.get("LLAMA_TAG", "") # pin e.g. "b8407"; "" = latest
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")) # internal only
API_KEY = os.environ.get("LLAMA_API_KEY", "") # SET THIS as a Space secret!
SYSTEM_PROMPT = os.environ.get("SYSTEM_PROMPT", "You are a helpful AI assistant.")
# ===== 1. HOLD THE ZEROGPU HARDWARE (~5s of quota, once, at boot) =====
@spaces.GPU(duration=5)
def _hold_zerogpu():
print("[zerogpu] host claimed; GPU slice released, CPU+RAM stay ours.")
return True
_hold_zerogpu()
# ===== 2. READ THE "DYNAMIC" CPU/RAM WE ACTUALLY GOT (cgroup truth) =====
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")
# ===== 3. FETCH PREBUILT llama.cpp (seconds, zero compilation) =====
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():
# llama.cpp Linux binaries are now distributed as .tar.gz (not .zip)
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)
# Extract tar.gz archive
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
# ===== 4. MODEL =====
print(f"[model] downloading {MODEL_REPO}/{MODEL_FILE}")
model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
# ===== 5. LAUNCH llama-server, SIZED TO THE DETECTED HARDWARE =====
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), # every core the cgroup gives us
"-c", str(CTX_SIZE),
"-np", str(PARALLEL), # real concurrent slots, no global lock
"-fa", # flash attention β much smaller KV cache
"--cache-reuse", "256", # KV reuse across chat turns
]
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()
# ===== 6. GRADIO UI (same backend as the API) =====
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()
# ===== 7. PUBLIC /v1 PROXY β the supported way, no monkey-patch =====
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="/") # mounted LAST so /v1/* wins
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "7860"))) |