Spaces:
Sleeping
Sleeping
add Tab2 live A/B vs Bonsai (sequential: Bonsai first, then POCKET); 2nd llama-server for Bonsai Q1_0; README=BONSAI vs POCKET
Browse files- Dockerfile +4 -1
- README.md +8 -34
- app.py +95 -61
- index.html +160 -43
- start.sh +12 -12
Dockerfile
CHANGED
|
@@ -26,9 +26,12 @@ RUN chmod +x start.sh
|
|
| 26 |
|
| 27 |
ENV MODEL_REPO=FINAL-Bench/POCKET-35B-GGUF \
|
| 28 |
MODEL_FILE=POCKET-35B-Q2_K.gguf \
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
HF_HOME=/data/hf \
|
| 31 |
BACKEND=http://127.0.0.1:8080 \
|
|
|
|
| 32 |
PORT=7860
|
| 33 |
|
| 34 |
EXPOSE 7860
|
|
|
|
| 26 |
|
| 27 |
ENV MODEL_REPO=FINAL-Bench/POCKET-35B-GGUF \
|
| 28 |
MODEL_FILE=POCKET-35B-Q2_K.gguf \
|
| 29 |
+
BONSAI_REPO=prism-ml/Bonsai-27B-gguf \
|
| 30 |
+
BONSAI_FILE=Bonsai-27B-Q1_0.gguf \
|
| 31 |
+
N_CTX=2048 \
|
| 32 |
HF_HOME=/data/hf \
|
| 33 |
BACKEND=http://127.0.0.1:8080 \
|
| 34 |
+
BONSAI_BACKEND=http://127.0.0.1:8081 \
|
| 35 |
PORT=7860
|
| 36 |
|
| 37 |
EXPOSE 7860
|
README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
---
|
| 2 |
-
title: POCKET
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: yellow
|
| 5 |
colorTo: green
|
| 6 |
sdk: docker
|
|
@@ -8,43 +8,17 @@ app_port: 7860
|
|
| 8 |
pinned: true
|
| 9 |
license: apache-2.0
|
| 10 |
models:
|
| 11 |
-
- Qwen/Qwen3.6-35B-A3B
|
| 12 |
-
- Qwen/Qwen3.5-35B-A3B
|
| 13 |
- FINAL-Bench/POCKET-35B-GGUF
|
| 14 |
-
- FINAL-Bench/POCKET-KR-GGUF
|
| 15 |
-
- FINAL-Bench/POCKET-EN-GGUF
|
| 16 |
-
- FINAL-Bench/POCKET-KR-MLX
|
| 17 |
- FINAL-Bench/Darwin-36B-Opus
|
| 18 |
-
-
|
| 19 |
-
- FINAL-Bench/Darwin-35B-A3B-Opus
|
| 20 |
-
- FINAL-Bench/Darwin-35B-A3B-Opus-Q8-GGUF
|
| 21 |
-
- FINAL-Bench/Darwin-28B-Opus
|
| 22 |
-
- FINAL-Bench/Darwin-28B-Coder
|
| 23 |
-
- FINAL-Bench/Darwin-28B-Coder-GGUF
|
| 24 |
-
- FINAL-Bench/Darwin-28B-REASON
|
| 25 |
-
- FINAL-Bench/Darwin-28B-KR-Legal
|
| 26 |
-
- FINAL-Bench/Darwin-31B-Opus
|
| 27 |
-
- FINAL-Bench/Darwin-27B-Opus
|
| 28 |
-
- FINAL-Bench/Darwin-60B-DUO
|
| 29 |
-
- FINAL-Bench/Darwin-398B-JGOS
|
| 30 |
-
- FINAL-Bench/Darwin-9B-Opus
|
| 31 |
-
- FINAL-Bench/Darwin-9B-NEG
|
| 32 |
-
- FINAL-Bench/Darwin-9B-MFP4
|
| 33 |
-
- FINAL-Bench/Darwin-4B-Chimera
|
| 34 |
-
- FINAL-Bench/Darwin-4B-David
|
| 35 |
-
- FINAL-Bench/Darwin-4B-Genesis
|
| 36 |
-
- FINAL-Bench/Darwin-TTS-1.7B-Cross
|
| 37 |
-
- FINAL-Bench/Aether-7B-5Attn
|
| 38 |
-
- FINAL-Bench/Aether-7B-5Attn-it
|
| 39 |
-
- FINAL-Bench/Ourbox-35B-JGOS
|
| 40 |
-
- FINAL-Bench/Ourbox-35B-JGOS-GGUF
|
| 41 |
-
- FINAL-Bench/Qwen3.5-35B-A3B-VKAE
|
| 42 |
-
short_description: A 35B model answering on a CPU-only box. No GPU.
|
| 43 |
---
|
| 44 |
|
| 45 |
-
# POCKET
|
| 46 |
|
| 47 |
-
A live demo of **POCKET-35B** (Q2_K, 13 GB) answering on an **upgraded CPU** Space — **no GPU**
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
POCKET is a sparse Mixture-of-Experts model (34.66B total, ~3B active/token) quantized from
|
| 50 |
[Darwin-36B-Opus](https://huggingface.co/FINAL-Bench/Darwin-36B-Opus). It runs on stock
|
|
|
|
| 1 |
---
|
| 2 |
+
title: POCKET vs Bonsai · CPU
|
| 3 |
+
emoji: ⚔️
|
| 4 |
colorFrom: yellow
|
| 5 |
colorTo: green
|
| 6 |
sdk: docker
|
|
|
|
| 8 |
pinned: true
|
| 9 |
license: apache-2.0
|
| 10 |
models:
|
|
|
|
|
|
|
| 11 |
- FINAL-Bench/POCKET-35B-GGUF
|
|
|
|
|
|
|
|
|
|
| 12 |
- FINAL-Bench/Darwin-36B-Opus
|
| 13 |
+
short_description: BONSAI vs POCKET — 35B MoE out-runs the top 1-bit 27B on CPU
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# BONSAI vs POCKET · live A/B on a CPU
|
| 17 |
|
| 18 |
+
A live demo of **POCKET-35B** (Q2_K, 13 GB) answering on an **upgraded CPU** Space — **no GPU** —
|
| 19 |
+
with a second tab that races it **head-to-head against Bonsai-27B** (the most-downloaded 1-bit
|
| 20 |
+
on-device model) on the same box, same stock `llama.cpp`. Bonsai answers first, then POCKET —
|
| 21 |
+
watch the tok/s.
|
| 22 |
|
| 23 |
POCKET is a sparse Mixture-of-Experts model (34.66B total, ~3B active/token) quantized from
|
| 24 |
[Darwin-36B-Opus](https://huggingface.co/FINAL-Bench/Darwin-36B-Opus). It runs on stock
|
app.py
CHANGED
|
@@ -1,98 +1,132 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
-
"""POCKET-35B CPU chat
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
so it does not depend on the embedded chat template. No GPU."""
|
| 6 |
import os, json, time
|
| 7 |
import httpx
|
| 8 |
from fastapi import FastAPI, Request
|
| 9 |
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
|
| 10 |
|
| 11 |
-
BACKEND = os.environ.get("BACKEND", "http://127.0.0.1:8080")
|
|
|
|
| 12 |
HERE = os.path.dirname(__file__)
|
| 13 |
|
| 14 |
app = FastAPI(title="POCKET-35B CPU chat")
|
| 15 |
|
| 16 |
|
| 17 |
def build_chatml(messages):
|
| 18 |
-
"""Qwen-family ChatML prompt."""
|
| 19 |
parts = []
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
parts.append("<|im_start|>system\nYou are POCKET, a helpful assistant running on-device.<|im_end|>\n")
|
| 23 |
for m in messages:
|
| 24 |
-
|
| 25 |
-
content = m.get("content", "")
|
| 26 |
-
parts.append("<|im_start|>%s\n%s<|im_end|>\n" % (role, content))
|
| 27 |
parts.append("<|im_start|>assistant\n")
|
| 28 |
return "".join(parts)
|
| 29 |
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
| 33 |
try:
|
| 34 |
async with httpx.AsyncClient(timeout=3.0) as c:
|
| 35 |
-
r = await c.get(
|
| 36 |
-
|
| 37 |
-
return JSONResponse({"state": "ready", "detail": "POCKET-35B on CPU · ready"})
|
| 38 |
-
return JSONResponse({"state": "loading", "detail": "warming up the model…"})
|
| 39 |
except Exception:
|
| 40 |
-
return
|
| 41 |
-
"detail": "downloading & loading POCKET-35B… (first boot can take a few minutes)"})
|
| 42 |
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
body = await req.json()
|
| 47 |
-
messages = body.get("messages") or [{"role": "user", "content": body.get("prompt", "Hi")}]
|
| 48 |
payload = {
|
| 49 |
"prompt": build_chatml(messages),
|
| 50 |
-
"temperature": float(
|
| 51 |
"top_p": 0.95,
|
| 52 |
-
"n_predict": int(
|
| 53 |
"stream": True,
|
| 54 |
"cache_prompt": True,
|
| 55 |
"stop": ["<|im_end|>", "<|im_start|>"],
|
| 56 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
async def gen():
|
| 59 |
-
n_tok = 0
|
| 60 |
-
t_first = None
|
| 61 |
try:
|
| 62 |
-
async
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
return
|
| 68 |
-
async for line in r.aiter_lines():
|
| 69 |
-
if not line or not line.startswith("data:"):
|
| 70 |
-
continue
|
| 71 |
-
try:
|
| 72 |
-
obj = json.loads(line[5:].strip())
|
| 73 |
-
except Exception:
|
| 74 |
-
continue
|
| 75 |
-
delta = obj.get("content")
|
| 76 |
-
if delta:
|
| 77 |
-
if t_first is None:
|
| 78 |
-
t_first = time.monotonic()
|
| 79 |
-
n_tok += 1
|
| 80 |
-
yield "data: " + json.dumps({"t": delta}) + "\n\n"
|
| 81 |
-
if obj.get("stop"):
|
| 82 |
-
tim = obj.get("timings") or {}
|
| 83 |
-
tok_s = tim.get("predicted_per_second")
|
| 84 |
-
if not tok_s and t_first is not None and n_tok > 0:
|
| 85 |
-
dt = time.monotonic() - t_first
|
| 86 |
-
tok_s = (n_tok / dt) if dt > 0 else 0.0
|
| 87 |
-
yield "data: " + json.dumps({
|
| 88 |
-
"done": True,
|
| 89 |
-
"tok_s": round(tok_s or 0.0, 1),
|
| 90 |
-
"n": int(tim.get("predicted_n") or n_tok),
|
| 91 |
-
}) + "\n\n"
|
| 92 |
-
return
|
| 93 |
-
yield "data: " + json.dumps({"done": True, "tok_s": 0.0, "n": n_tok}) + "\n\n"
|
| 94 |
except Exception as e:
|
| 95 |
-
yield
|
| 96 |
|
| 97 |
return StreamingResponse(gen(), media_type="text/event-stream")
|
| 98 |
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
"""POCKET-35B CPU chat + live A/B vs Bonsai.
|
| 3 |
+
FastAPI frontend proxying to two local llama.cpp servers (upstream prebuilt release,
|
| 4 |
+
supports POCKET's qwen35moe arch). Native /completion + manual ChatML. No GPU."""
|
|
|
|
| 5 |
import os, json, time
|
| 6 |
import httpx
|
| 7 |
from fastapi import FastAPI, Request
|
| 8 |
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
|
| 9 |
|
| 10 |
+
BACKEND = os.environ.get("BACKEND", "http://127.0.0.1:8080") # POCKET
|
| 11 |
+
BONSAI_BACKEND = os.environ.get("BONSAI_BACKEND", "http://127.0.0.1:8081") # Bonsai
|
| 12 |
HERE = os.path.dirname(__file__)
|
| 13 |
|
| 14 |
app = FastAPI(title="POCKET-35B CPU chat")
|
| 15 |
|
| 16 |
|
| 17 |
def build_chatml(messages):
|
| 18 |
+
"""Qwen-family ChatML prompt (works for both POCKET and Bonsai)."""
|
| 19 |
parts = []
|
| 20 |
+
if not any(m.get("role") == "system" for m in messages):
|
| 21 |
+
parts.append("<|im_start|>system\nYou are a helpful assistant running on-device.<|im_end|>\n")
|
|
|
|
| 22 |
for m in messages:
|
| 23 |
+
parts.append("<|im_start|>%s\n%s<|im_end|>\n" % (m.get("role", "user"), m.get("content", "")))
|
|
|
|
|
|
|
| 24 |
parts.append("<|im_start|>assistant\n")
|
| 25 |
return "".join(parts)
|
| 26 |
|
| 27 |
|
| 28 |
+
def _sse(obj):
|
| 29 |
+
return "data: " + json.dumps(obj) + "\n\n"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
async def _health(backend):
|
| 33 |
try:
|
| 34 |
async with httpx.AsyncClient(timeout=3.0) as c:
|
| 35 |
+
r = await c.get(backend + "/health")
|
| 36 |
+
return r.status_code == 200
|
|
|
|
|
|
|
| 37 |
except Exception:
|
| 38 |
+
return False
|
|
|
|
| 39 |
|
| 40 |
|
| 41 |
+
async def run_one(backend, tag, messages, max_tokens, temperature=0.7):
|
| 42 |
+
"""Stream one model's /completion output as SSE, tagged with `tag` (None = untagged)."""
|
|
|
|
|
|
|
| 43 |
payload = {
|
| 44 |
"prompt": build_chatml(messages),
|
| 45 |
+
"temperature": float(temperature),
|
| 46 |
"top_p": 0.95,
|
| 47 |
+
"n_predict": int(max_tokens),
|
| 48 |
"stream": True,
|
| 49 |
"cache_prompt": True,
|
| 50 |
"stop": ["<|im_end|>", "<|im_start|>"],
|
| 51 |
}
|
| 52 |
+
n_tok = 0
|
| 53 |
+
t_first = None
|
| 54 |
+
tagd = {"m": tag} if tag else {}
|
| 55 |
+
async with httpx.AsyncClient(timeout=None) as c:
|
| 56 |
+
async with c.stream("POST", backend + "/completion", json=payload) as r:
|
| 57 |
+
if r.status_code != 200:
|
| 58 |
+
detail = (await r.aread()).decode("utf-8", "ignore")[:200]
|
| 59 |
+
yield _sse(dict(tagd, error="backend %s: %s" % (r.status_code, detail)))
|
| 60 |
+
return
|
| 61 |
+
async for line in r.aiter_lines():
|
| 62 |
+
if not line or not line.startswith("data:"):
|
| 63 |
+
continue
|
| 64 |
+
try:
|
| 65 |
+
obj = json.loads(line[5:].strip())
|
| 66 |
+
except Exception:
|
| 67 |
+
continue
|
| 68 |
+
delta = obj.get("content")
|
| 69 |
+
if delta:
|
| 70 |
+
if t_first is None:
|
| 71 |
+
t_first = time.monotonic()
|
| 72 |
+
n_tok += 1
|
| 73 |
+
yield _sse(dict(tagd, t=delta))
|
| 74 |
+
if obj.get("stop"):
|
| 75 |
+
tim = obj.get("timings") or {}
|
| 76 |
+
tok_s = tim.get("predicted_per_second")
|
| 77 |
+
if not tok_s and t_first is not None and n_tok > 0:
|
| 78 |
+
dt = time.monotonic() - t_first
|
| 79 |
+
tok_s = (n_tok / dt) if dt > 0 else 0.0
|
| 80 |
+
yield _sse(dict(tagd, done=True, tok_s=round(tok_s or 0.0, 1),
|
| 81 |
+
n=int(tim.get("predicted_n") or n_tok)))
|
| 82 |
+
return
|
| 83 |
+
yield _sse(dict(tagd, done=True, tok_s=0.0, n=n_tok))
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@app.get("/api/status")
|
| 87 |
+
async def status():
|
| 88 |
+
pk = await _health(BACKEND)
|
| 89 |
+
bn = await _health(BONSAI_BACKEND)
|
| 90 |
+
if pk:
|
| 91 |
+
detail = "POCKET-35B on CPU · ready" if bn else "POCKET ready · loading Bonsai for the A/B…"
|
| 92 |
+
else:
|
| 93 |
+
detail = "downloading & loading models… (first boot can take a few minutes)"
|
| 94 |
+
return JSONResponse({"state": "ready" if pk else "loading", "detail": detail,
|
| 95 |
+
"pocket": pk, "bonsai": bn})
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
@app.post("/api/chat")
|
| 99 |
+
async def chat(req: Request):
|
| 100 |
+
body = await req.json()
|
| 101 |
+
messages = body.get("messages") or [{"role": "user", "content": body.get("prompt", "Hi")}]
|
| 102 |
+
mt = int(body.get("max_tokens", 512))
|
| 103 |
+
|
| 104 |
+
async def gen():
|
| 105 |
+
try:
|
| 106 |
+
async for chunk in run_one(BACKEND, None, messages, mt, body.get("temperature", 0.7)):
|
| 107 |
+
yield chunk
|
| 108 |
+
except Exception as e:
|
| 109 |
+
yield _sse({"error": str(e)[:200]})
|
| 110 |
+
|
| 111 |
+
return StreamingResponse(gen(), media_type="text/event-stream")
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@app.post("/api/chat_ab")
|
| 115 |
+
async def chat_ab(req: Request):
|
| 116 |
+
"""Sequential race: Bonsai runs first, then POCKET (each gets the full CPU)."""
|
| 117 |
+
body = await req.json()
|
| 118 |
+
messages = body.get("messages") or [{"role": "user", "content": body.get("prompt", "Hi")}]
|
| 119 |
+
mt = int(body.get("max_tokens", 200))
|
| 120 |
|
| 121 |
async def gen():
|
|
|
|
|
|
|
| 122 |
try:
|
| 123 |
+
async for chunk in run_one(BONSAI_BACKEND, "bonsai", messages, mt):
|
| 124 |
+
yield chunk
|
| 125 |
+
async for chunk in run_one(BACKEND, "pocket", messages, mt):
|
| 126 |
+
yield chunk
|
| 127 |
+
yield _sse({"ab_done": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
except Exception as e:
|
| 129 |
+
yield _sse({"error": str(e)[:200]})
|
| 130 |
|
| 131 |
return StreamingResponse(gen(), media_type="text/event-stream")
|
| 132 |
|
index.html
CHANGED
|
@@ -3,14 +3,14 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8"/>
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
| 6 |
-
<title>POCKET-35B · CPU chat</title>
|
| 7 |
<style>
|
| 8 |
:root{ --gold:#f0992a; --teal:#0f9d6e; --ink:#12203a; --muted:#5b6b8f;
|
| 9 |
-
--line:#e7ecf5; --bg:#f7f9fc; --card:#ffffff; }
|
| 10 |
*{ box-sizing:border-box; }
|
| 11 |
body{ margin:0; font-family:'Segoe UI',Helvetica,Arial,sans-serif; color:var(--ink);
|
| 12 |
background:var(--bg); line-height:1.55; }
|
| 13 |
-
.wrap{ max-width:
|
| 14 |
header{ text-align:center; padding:22px 0 8px; }
|
| 15 |
.brand{ font-size:40px; font-weight:800; letter-spacing:1px;
|
| 16 |
background:linear-gradient(90deg,#ffd76a,#f0992a); -webkit-background-clip:text;
|
|
@@ -21,6 +21,10 @@
|
|
| 21 |
.chip{ background:#fff; border:1px solid var(--line); border-radius:20px; padding:6px 14px;
|
| 22 |
font-size:13px; font-weight:600; color:var(--ink); }
|
| 23 |
.chip b{ color:var(--teal); }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
.status{ text-align:center; font-size:13px; color:var(--muted); margin:10px 0; }
|
| 25 |
.dot{ display:inline-block; width:8px; height:8px; border-radius:50%; background:#c9c8c2;
|
| 26 |
margin-right:6px; vertical-align:1px; }
|
|
@@ -37,12 +41,12 @@
|
|
| 37 |
.meta b{ color:var(--teal); font-size:13.5px; }
|
| 38 |
.empty{ color:var(--muted); text-align:center; padding:40px 10px; font-size:14px; }
|
| 39 |
.bar{ display:flex; gap:8px; padding:12px; border-top:1px solid var(--line); }
|
| 40 |
-
#q{ flex:1; border:1px solid var(--line); border-radius:12px; padding:12px 14px; font-size:15px;
|
| 41 |
font-family:inherit; outline:none; }
|
| 42 |
-
#q:focus{ border-color:var(--gold); }
|
| 43 |
-
#send{ background:var(--gold); color:#3a2600; border:none; border-radius:12px; padding:0 20px;
|
| 44 |
font-weight:700; font-size:15px; cursor:pointer; }
|
| 45 |
-
#send:disabled{ background:#e7ecf5; color:#9aa6bd; cursor:not-allowed; }
|
| 46 |
.about{ margin-top:24px; padding:20px 22px; font-size:14.5px; color:#2b3a56; }
|
| 47 |
.about h3{ margin:0 0 8px; font-size:16px; }
|
| 48 |
.about table{ width:100%; border-collapse:collapse; margin:10px 0; font-size:13.5px; }
|
|
@@ -54,6 +58,22 @@
|
|
| 54 |
.ex{ display:flex; gap:8px; flex-wrap:wrap; padding:0 12px 12px; }
|
| 55 |
.ex button{ background:#fff; border:1px solid var(--line); border-radius:10px; padding:7px 12px;
|
| 56 |
font-size:13px; color:var(--muted); cursor:pointer; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
</style>
|
| 58 |
</head>
|
| 59 |
<body>
|
|
@@ -70,69 +90,125 @@
|
|
| 70 |
</div>
|
| 71 |
</header>
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
<div class="status"><span id="dot" class="dot loading"></span><span id="st">starting…</span></div>
|
| 74 |
|
| 75 |
-
<
|
| 76 |
-
|
| 77 |
-
<div class="
|
| 78 |
-
<
|
| 79 |
-
<
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
</div>
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
-
<
|
| 89 |
-
|
| 90 |
-
<
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
<
|
| 95 |
-
<
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
<
|
| 99 |
-
<
|
| 100 |
-
<
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
</div>
|
| 104 |
-
<div class="note">This Space runs the Q2_K build on an upgraded CPU (no GPU). Speed here reflects a shared CPU box — a dedicated mini-PC is faster. Apache-2.0 · a VIDRAFT model family.</div>
|
| 105 |
</div>
|
| 106 |
</div>
|
| 107 |
|
| 108 |
<script>
|
| 109 |
-
const chat=document.getElementById('chat'), empty=document.getElementById('empty');
|
| 110 |
-
const q=document.getElementById('q'), send=document.getElementById('send');
|
| 111 |
const dot=document.getElementById('dot'), st=document.getElementById('st');
|
| 112 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
|
|
|
| 114 |
async function poll(){
|
| 115 |
try{ const r=await fetch('/api/status'); const s=await r.json();
|
| 116 |
st.textContent=s.detail; dot.className='dot '+(s.state==='ready'?'ready':'loading');
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
| 119 |
}catch(e){ setTimeout(poll,3000); }
|
| 120 |
}
|
| 121 |
-
poll();
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
function add(role,text){
|
| 124 |
-
if(empty) empty.remove();
|
| 125 |
const m=document.createElement('div'); m.className='msg '+(role==='user'?'u':'a');
|
| 126 |
const b=document.createElement('div'); b.className='bub'; b.textContent=text; m.appendChild(b);
|
| 127 |
chat.appendChild(m); chat.scrollTop=chat.scrollHeight; return b;
|
| 128 |
}
|
| 129 |
-
|
| 130 |
async function ask(text){
|
| 131 |
if(busy||!text.trim()) return; busy=true; send.disabled=true; q.value='';
|
| 132 |
add('user',text); history.push({role:'user',content:text});
|
| 133 |
const bub=add('assistant','');
|
| 134 |
-
const meta=document.createElement('div'); meta.className='meta';
|
| 135 |
-
meta.textContent='⏳ generating on CPU…'; chat.appendChild(meta);
|
| 136 |
let acc='', nTok=0, t0=0;
|
| 137 |
try{
|
| 138 |
const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},
|
|
@@ -154,10 +230,51 @@ async function ask(text){
|
|
| 154 |
}catch(e){ bub.textContent='⚠️ '+e; }
|
| 155 |
busy=false; send.disabled=false; q.focus();
|
| 156 |
}
|
| 157 |
-
|
| 158 |
send.onclick=()=>ask(q.value);
|
| 159 |
q.addEventListener('keydown',e=>{ if(e.key==='Enter') ask(q.value); });
|
| 160 |
document.getElementById('ex').addEventListener('click',e=>{ if(e.target.tagName==='BUTTON') ask(e.target.textContent); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
</script>
|
| 162 |
</body>
|
| 163 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8"/>
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
| 6 |
+
<title>POCKET-35B · CPU chat & A/B vs Bonsai</title>
|
| 7 |
<style>
|
| 8 |
:root{ --gold:#f0992a; --teal:#0f9d6e; --ink:#12203a; --muted:#5b6b8f;
|
| 9 |
+
--line:#e7ecf5; --bg:#f7f9fc; --card:#ffffff; --bonsai:#7c8aa5; }
|
| 10 |
*{ box-sizing:border-box; }
|
| 11 |
body{ margin:0; font-family:'Segoe UI',Helvetica,Arial,sans-serif; color:var(--ink);
|
| 12 |
background:var(--bg); line-height:1.55; }
|
| 13 |
+
.wrap{ max-width:960px; margin:0 auto; padding:24px 18px 60px; }
|
| 14 |
header{ text-align:center; padding:22px 0 8px; }
|
| 15 |
.brand{ font-size:40px; font-weight:800; letter-spacing:1px;
|
| 16 |
background:linear-gradient(90deg,#ffd76a,#f0992a); -webkit-background-clip:text;
|
|
|
|
| 21 |
.chip{ background:#fff; border:1px solid var(--line); border-radius:20px; padding:6px 14px;
|
| 22 |
font-size:13px; font-weight:600; color:var(--ink); }
|
| 23 |
.chip b{ color:var(--teal); }
|
| 24 |
+
.tabs{ display:flex; gap:8px; justify-content:center; margin:16px 0 4px; }
|
| 25 |
+
.tab{ background:#fff; border:1px solid var(--line); border-radius:12px; padding:9px 18px;
|
| 26 |
+
font-size:14.5px; font-weight:700; color:var(--muted); cursor:pointer; }
|
| 27 |
+
.tab.active{ color:#3a2600; background:linear-gradient(90deg,#ffe6b0,#f7c36a); border-color:#f0992a; }
|
| 28 |
.status{ text-align:center; font-size:13px; color:var(--muted); margin:10px 0; }
|
| 29 |
.dot{ display:inline-block; width:8px; height:8px; border-radius:50%; background:#c9c8c2;
|
| 30 |
margin-right:6px; vertical-align:1px; }
|
|
|
|
| 41 |
.meta b{ color:var(--teal); font-size:13.5px; }
|
| 42 |
.empty{ color:var(--muted); text-align:center; padding:40px 10px; font-size:14px; }
|
| 43 |
.bar{ display:flex; gap:8px; padding:12px; border-top:1px solid var(--line); }
|
| 44 |
+
#q,#abq{ flex:1; border:1px solid var(--line); border-radius:12px; padding:12px 14px; font-size:15px;
|
| 45 |
font-family:inherit; outline:none; }
|
| 46 |
+
#q:focus,#abq:focus{ border-color:var(--gold); }
|
| 47 |
+
#send,#absend{ background:var(--gold); color:#3a2600; border:none; border-radius:12px; padding:0 20px;
|
| 48 |
font-weight:700; font-size:15px; cursor:pointer; }
|
| 49 |
+
#send:disabled,#absend:disabled{ background:#e7ecf5; color:#9aa6bd; cursor:not-allowed; }
|
| 50 |
.about{ margin-top:24px; padding:20px 22px; font-size:14.5px; color:#2b3a56; }
|
| 51 |
.about h3{ margin:0 0 8px; font-size:16px; }
|
| 52 |
.about table{ width:100%; border-collapse:collapse; margin:10px 0; font-size:13.5px; }
|
|
|
|
| 58 |
.ex{ display:flex; gap:8px; flex-wrap:wrap; padding:0 12px 12px; }
|
| 59 |
.ex button{ background:#fff; border:1px solid var(--line); border-radius:10px; padding:7px 12px;
|
| 60 |
font-size:13px; color:var(--muted); cursor:pointer; }
|
| 61 |
+
/* A/B */
|
| 62 |
+
.abhead{ display:grid; grid-template-columns:1fr 1fr; gap:12px; padding:14px 14px 4px; }
|
| 63 |
+
.abcol{ font-size:15px; font-weight:800; }
|
| 64 |
+
.abcol span{ display:block; font-size:12px; font-weight:600; color:var(--muted); margin-top:2px; }
|
| 65 |
+
.abcol.b{ color:var(--bonsai); } .abcol.p{ color:var(--gold); }
|
| 66 |
+
.abgrid{ display:grid; grid-template-columns:1fr 1fr; gap:12px; padding:6px 14px 4px; }
|
| 67 |
+
.abpane{ border:1px solid var(--line); border-radius:12px; padding:12px; min-height:200px;
|
| 68 |
+
max-height:42vh; overflow-y:auto; background:#fbfcfe; }
|
| 69 |
+
.abpane .txt{ font-size:14px; white-space:pre-wrap; color:var(--ink); }
|
| 70 |
+
.abpane .m{ font-size:12.5px; font-weight:700; color:var(--muted); margin-top:8px; }
|
| 71 |
+
.abpane .m b{ font-size:14px; }
|
| 72 |
+
.abpane.b .m b{ color:var(--bonsai); } .abpane.p .m b{ color:var(--teal); }
|
| 73 |
+
.abresult{ text-align:center; font-size:17px; font-weight:800; color:var(--ink);
|
| 74 |
+
padding:12px; min-height:24px; }
|
| 75 |
+
.abresult b{ color:var(--teal); }
|
| 76 |
+
@media (max-width:640px){ .abhead,.abgrid{ grid-template-columns:1fr; } }
|
| 77 |
</style>
|
| 78 |
</head>
|
| 79 |
<body>
|
|
|
|
| 90 |
</div>
|
| 91 |
</header>
|
| 92 |
|
| 93 |
+
<div class="tabs">
|
| 94 |
+
<button class="tab active" data-tab="chat">💬 Chat</button>
|
| 95 |
+
<button class="tab" data-tab="ab">⚔️ POCKET vs Bonsai</button>
|
| 96 |
+
</div>
|
| 97 |
+
|
| 98 |
<div class="status"><span id="dot" class="dot loading"></span><span id="st">starting…</span></div>
|
| 99 |
|
| 100 |
+
<!-- ===== Tab 1: Chat (unchanged) ===== -->
|
| 101 |
+
<div id="panel-chat">
|
| 102 |
+
<div class="card">
|
| 103 |
+
<div class="chat" id="chat"><div class="empty" id="empty">Ask anything. This runs on CPU — first reply may take a moment.</div></div>
|
| 104 |
+
<div class="ex" id="ex">
|
| 105 |
+
<button>Explain mixture-of-experts in one line.</button>
|
| 106 |
+
<button>Write a haiku about running AI with no GPU.</button>
|
| 107 |
+
<button>한국어로 자기소개를 세 문장으로 해줘.</button>
|
| 108 |
+
</div>
|
| 109 |
+
<div class="bar">
|
| 110 |
+
<input id="q" placeholder="Type a message…" autocomplete="off"/>
|
| 111 |
+
<button id="send" disabled>Send</button>
|
| 112 |
+
</div>
|
| 113 |
</div>
|
| 114 |
+
|
| 115 |
+
<div class="card about">
|
| 116 |
+
<h3>What is POCKET?</h3>
|
| 117 |
+
<p>POCKET is a family of on-device builds quantized from <a href="https://huggingface.co/FINAL-Bench/Darwin-36B-Opus">Darwin-36B-Opus</a>, VIDRAFT's flagship. A 34.66B sparse-MoE model that wakes only ~3B parameters per token — so it runs on a phone, and on a PC with <b>no GPU</b>, using the tools you already have. On the same machine and the same stock <code>llama.cpp</code>, it generates <b>2.7× faster on CPU</b> and <b>2.2× faster on GPU</b> than the leading 1-bit 27B, at matched quality.</p>
|
| 118 |
+
<table>
|
| 119 |
+
<tr><th>Build</th><th>Size</th><th>Runs on</th><th>Best for</th></tr>
|
| 120 |
+
<tr><td>POCKET-35B Q4_K_M</td><td>21 GB</td><td>PC 32 GB</td><td>top quality</td></tr>
|
| 121 |
+
<tr><td>POCKET-35B <b>Q2_K</b> ⭐ (this demo)</td><td>13 GB</td><td>mini-PC, no GPU</td><td>daily driver</td></tr>
|
| 122 |
+
<tr><td>POCKET-KR / EN</td><td>~5 GB</td><td>iPhone / Android</td><td>on your phone</td></tr>
|
| 123 |
+
</table>
|
| 124 |
+
<div class="links">
|
| 125 |
+
<a href="https://huggingface.co/collections/FINAL-Bench/pocket-models-6a618ee5d23eafb7e185a5c6">📚 POCKET collection</a>
|
| 126 |
+
<a href="https://huggingface.co/FINAL-Bench/POCKET-35B-GGUF">POCKET-35B-GGUF</a>
|
| 127 |
+
<a href="https://huggingface.co/FINAL-Bench/POCKET-KR-MLX">POCKET-KR (iPhone)</a>
|
| 128 |
+
<a href="https://huggingface.co/FINAL-Bench/POCKET-EN-GGUF">POCKET-EN</a>
|
| 129 |
+
<a href="https://huggingface.co/FINAL-Bench/Darwin-36B-Opus">🧬 Darwin-36B-Opus</a>
|
| 130 |
+
</div>
|
| 131 |
+
<div class="note">This Space runs the Q2_K build on an upgraded CPU (no GPU). Speed here reflects a shared CPU box — a dedicated mini-PC is faster. Apache-2.0 · a VIDRAFT model family.</div>
|
| 132 |
</div>
|
| 133 |
</div>
|
| 134 |
|
| 135 |
+
<!-- ===== Tab 2: POCKET vs Bonsai (A/B) ===== -->
|
| 136 |
+
<div id="panel-ab" style="display:none">
|
| 137 |
+
<div class="card">
|
| 138 |
+
<div class="abhead">
|
| 139 |
+
<div class="abcol b">🌳 Bonsai-27B <span>1-bit dense · runs first</span></div>
|
| 140 |
+
<div class="abcol p">📦 POCKET-35B <span>sparse MoE · runs second</span></div>
|
| 141 |
+
</div>
|
| 142 |
+
<div class="abgrid">
|
| 143 |
+
<div class="abpane b"><div class="txt" id="ab-b-txt"></div><div class="m" id="ab-b-m"></div></div>
|
| 144 |
+
<div class="abpane p"><div class="txt" id="ab-p-txt"></div><div class="m" id="ab-p-m"></div></div>
|
| 145 |
+
</div>
|
| 146 |
+
<div class="abresult" id="ab-result">Type a prompt — Bonsai answers first, then POCKET on the same CPU.</div>
|
| 147 |
+
<div class="ex" id="abex">
|
| 148 |
+
<button>Explain how a rocket reaches orbit.</button>
|
| 149 |
+
<button>Give me 3 tips for better sleep.</button>
|
| 150 |
+
<button>Write a short poem about the sea.</button>
|
| 151 |
+
</div>
|
| 152 |
+
<div class="bar">
|
| 153 |
+
<input id="abq" placeholder="One prompt — both models answer…" autocomplete="off"/>
|
| 154 |
+
<button id="absend" disabled>Race</button>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<div class="card about">
|
| 159 |
+
<h3>What am I looking at?</h3>
|
| 160 |
+
<p>The exact same prompt is sent to <b>Bonsai-27B</b> (the most-downloaded on-device model, a 1-bit dense build) and to <b>POCKET-35B</b> (our sparse Mixture-of-Experts, Q2_K) — on the <b>same CPU-only box, the same stock <code>llama.cpp</code></b>. Bonsai runs first, then POCKET. Watch the <b>tok/s</b> on each side.</p>
|
| 161 |
+
<p>Why POCKET wins on generation: a dense 27B reads all of its weights every token (~3.5 GB); POCKET's MoE wakes only ~3B parameters (~0.7 GB). The weaker the hardware, the wider the gap.</p>
|
| 162 |
+
<div class="note"><b>Honest notes:</b> sequential race, each model gets the full CPU. tok/s is llama.cpp's measured generation speed. On a shared box a dedicated machine is faster. Prompt <i>processing</i> (not shown) is one place Bonsai leads on very long inputs. Quality is a statistical tie. Bonsai is a trademark of its owners; shown here for factual comparison only.</div>
|
| 163 |
+
<div class="links">
|
| 164 |
+
<a href="https://huggingface.co/FINAL-Bench/POCKET-35B-GGUF">📦 POCKET-35B-GGUF</a>
|
| 165 |
+
<a href="https://huggingface.co/collections/FINAL-Bench/pocket-models-6a618ee5d23eafb7e185a5c6">📚 POCKET collection</a>
|
| 166 |
+
<a href="https://huggingface.co/FINAL-Bench/Darwin-36B-Opus">🧬 Darwin-36B-Opus</a>
|
| 167 |
+
</div>
|
| 168 |
</div>
|
|
|
|
| 169 |
</div>
|
| 170 |
</div>
|
| 171 |
|
| 172 |
<script>
|
|
|
|
|
|
|
| 173 |
const dot=document.getElementById('dot'), st=document.getElementById('st');
|
| 174 |
+
let ready={pocket:false,bonsai:false};
|
| 175 |
+
|
| 176 |
+
/* ---- tabs ---- */
|
| 177 |
+
document.querySelector('.tabs').addEventListener('click',e=>{
|
| 178 |
+
if(e.target.tagName!=='BUTTON') return;
|
| 179 |
+
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
|
| 180 |
+
e.target.classList.add('active');
|
| 181 |
+
const t=e.target.dataset.tab;
|
| 182 |
+
document.getElementById('panel-chat').style.display = t==='chat'?'':'none';
|
| 183 |
+
document.getElementById('panel-ab').style.display = t==='ab'?'':'none';
|
| 184 |
+
});
|
| 185 |
|
| 186 |
+
/* ---- status poll ---- */
|
| 187 |
async function poll(){
|
| 188 |
try{ const r=await fetch('/api/status'); const s=await r.json();
|
| 189 |
st.textContent=s.detail; dot.className='dot '+(s.state==='ready'?'ready':'loading');
|
| 190 |
+
ready.pocket=!!s.pocket; ready.bonsai=!!s.bonsai;
|
| 191 |
+
send.disabled = !ready.pocket || busy;
|
| 192 |
+
absend.disabled = !(ready.pocket&&ready.bonsai) || abBusy;
|
| 193 |
+
if(!(ready.pocket&&ready.bonsai)) setTimeout(poll,2500);
|
| 194 |
}catch(e){ setTimeout(poll,3000); }
|
| 195 |
}
|
|
|
|
| 196 |
|
| 197 |
+
/* ---- Tab 1 chat ---- */
|
| 198 |
+
const chat=document.getElementById('chat'), empty=document.getElementById('empty');
|
| 199 |
+
const q=document.getElementById('q'), send=document.getElementById('send');
|
| 200 |
+
let history=[], busy=false;
|
| 201 |
function add(role,text){
|
| 202 |
+
if(empty&&empty.parentNode) empty.remove();
|
| 203 |
const m=document.createElement('div'); m.className='msg '+(role==='user'?'u':'a');
|
| 204 |
const b=document.createElement('div'); b.className='bub'; b.textContent=text; m.appendChild(b);
|
| 205 |
chat.appendChild(m); chat.scrollTop=chat.scrollHeight; return b;
|
| 206 |
}
|
|
|
|
| 207 |
async function ask(text){
|
| 208 |
if(busy||!text.trim()) return; busy=true; send.disabled=true; q.value='';
|
| 209 |
add('user',text); history.push({role:'user',content:text});
|
| 210 |
const bub=add('assistant','');
|
| 211 |
+
const meta=document.createElement('div'); meta.className='meta'; meta.textContent='⏳ generating on CPU…'; chat.appendChild(meta);
|
|
|
|
| 212 |
let acc='', nTok=0, t0=0;
|
| 213 |
try{
|
| 214 |
const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},
|
|
|
|
| 230 |
}catch(e){ bub.textContent='⚠️ '+e; }
|
| 231 |
busy=false; send.disabled=false; q.focus();
|
| 232 |
}
|
|
|
|
| 233 |
send.onclick=()=>ask(q.value);
|
| 234 |
q.addEventListener('keydown',e=>{ if(e.key==='Enter') ask(q.value); });
|
| 235 |
document.getElementById('ex').addEventListener('click',e=>{ if(e.target.tagName==='BUTTON') ask(e.target.textContent); });
|
| 236 |
+
|
| 237 |
+
/* ---- Tab 2 A/B race ---- */
|
| 238 |
+
const absend=document.getElementById('absend'), abq=document.getElementById('abq');
|
| 239 |
+
const abTxt={bonsai:document.getElementById('ab-b-txt'),pocket:document.getElementById('ab-p-txt')};
|
| 240 |
+
const abM={bonsai:document.getElementById('ab-b-m'),pocket:document.getElementById('ab-p-m')};
|
| 241 |
+
const abResult=document.getElementById('ab-result');
|
| 242 |
+
let abBusy=false;
|
| 243 |
+
async function race(text){
|
| 244 |
+
if(abBusy||!text.trim()) return; abBusy=true; absend.disabled=true; abq.value='';
|
| 245 |
+
abTxt.bonsai.textContent=''; abTxt.pocket.textContent='';
|
| 246 |
+
abM.bonsai.innerHTML='⏳ Bonsai generating…'; abM.pocket.innerHTML='⏳ waiting for Bonsai to finish…';
|
| 247 |
+
abResult.textContent='Racing on one CPU…';
|
| 248 |
+
const acc={bonsai:'',pocket:''}, cnt={bonsai:0,pocket:0}, t0={bonsai:0,pocket:0}, spd={bonsai:0,pocket:0};
|
| 249 |
+
try{
|
| 250 |
+
const r=await fetch('/api/chat_ab',{method:'POST',headers:{'Content-Type':'application/json'},
|
| 251 |
+
body:JSON.stringify({messages:[{role:'user',content:text}],max_tokens:200})});
|
| 252 |
+
const rd=r.body.getReader(); const dec=new TextDecoder(); let buf='';
|
| 253 |
+
while(true){ const {done,value}=await rd.read(); if(done) break;
|
| 254 |
+
buf+=dec.decode(value,{stream:true}); const parts=buf.split('\n\n'); buf=parts.pop();
|
| 255 |
+
for(const p of parts){ if(!p.startsWith('data:')) continue;
|
| 256 |
+
const j=JSON.parse(p.slice(5)); const m=j.m;
|
| 257 |
+
if(j.t && m){ if(!t0[m]) t0[m]=performance.now(); cnt[m]++; acc[m]+=j.t; abTxt[m].textContent=acc[m];
|
| 258 |
+
if(cnt[m]>1){ const s=cnt[m]/((performance.now()-t0[m])/1000); abM[m].innerHTML='⚡ <b>'+s.toFixed(1)+'</b> tok/s'; }
|
| 259 |
+
abTxt[m].parentNode.scrollTop=abTxt[m].parentNode.scrollHeight; }
|
| 260 |
+
if(j.done && m){ spd[m]=j.tok_s||0;
|
| 261 |
+
abM[m].innerHTML='⚡ <b>'+(spd[m]?spd[m].toFixed(1):'—')+'</b> tok/s · '+(j.n||cnt[m])+' tokens';
|
| 262 |
+
if(m==='bonsai') abM.pocket.innerHTML='⏳ POCKET generating…'; }
|
| 263 |
+
if(j.error && m){ abTxt[m].textContent='⚠️ '+j.error; }
|
| 264 |
+
if(j.ab_done){
|
| 265 |
+
if(spd.bonsai>0 && spd.pocket>0){ const x=spd.pocket/spd.bonsai;
|
| 266 |
+
abResult.innerHTML='⚡ POCKET was <b>'+x.toFixed(1)+'× faster</b> than Bonsai — same CPU, no GPU';
|
| 267 |
+
} else { abResult.textContent='Done.'; } }
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
}catch(e){ abResult.textContent='⚠️ '+e; }
|
| 271 |
+
abBusy=false; absend.disabled=false; abq.focus();
|
| 272 |
+
}
|
| 273 |
+
absend.onclick=()=>race(abq.value);
|
| 274 |
+
abq.addEventListener('keydown',e=>{ if(e.key==='Enter') race(abq.value); });
|
| 275 |
+
document.getElementById('abex').addEventListener('click',e=>{ if(e.target.tagName==='BUTTON') race(e.target.textContent); });
|
| 276 |
+
|
| 277 |
+
poll();
|
| 278 |
</script>
|
| 279 |
</body>
|
| 280 |
</html>
|
start.sh
CHANGED
|
@@ -1,18 +1,18 @@
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
set -e
|
| 3 |
|
| 4 |
-
# 1)
|
| 5 |
-
# (chat stays disabled until the backend reports healthy).
|
| 6 |
uvicorn app:app --host 0.0.0.0 --port "${PORT}" &
|
| 7 |
|
| 8 |
-
# 2)
|
| 9 |
-
echo "[start] downloading ${MODEL_FILE}
|
| 10 |
-
|
| 11 |
-
echo "[start]
|
|
|
|
| 12 |
|
| 13 |
-
# 3)
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
set -e
|
| 3 |
|
| 4 |
+
# 1) Frontend up first so the page + description render immediately.
|
|
|
|
| 5 |
uvicorn app:app --host 0.0.0.0 --port "${PORT}" &
|
| 6 |
|
| 7 |
+
# 2) POCKET backend (:8080) — powers Tab 1 chat AND the POCKET side of the A/B.
|
| 8 |
+
echo "[start] downloading POCKET ${MODEL_FILE} …"
|
| 9 |
+
PK=$(python -c "import os;from huggingface_hub import hf_hub_download;print(hf_hub_download(os.environ['MODEL_REPO'],os.environ['MODEL_FILE']))")
|
| 10 |
+
echo "[start] POCKET at ${PK}"
|
| 11 |
+
llama-server -m "${PK}" --host 127.0.0.1 --port 8080 -c "${N_CTX}" -t "$(nproc)" --jinja --no-webui &
|
| 12 |
|
| 13 |
+
# 3) Bonsai backend (:8081) — the Bonsai side of the A/B race. Same stock binary.
|
| 14 |
+
echo "[start] downloading Bonsai ${BONSAI_FILE} …"
|
| 15 |
+
BN=$(python -c "import os;from huggingface_hub import hf_hub_download;print(hf_hub_download(os.environ['BONSAI_REPO'],os.environ['BONSAI_FILE']))")
|
| 16 |
+
echo "[start] Bonsai at ${BN}"
|
| 17 |
+
# exec = keep container alive on this process; sequential A/B means no CPU contention.
|
| 18 |
+
exec llama-server -m "${BN}" --host 127.0.0.1 --port 8081 -c "${N_CTX}" -t "$(nproc)" --jinja --no-webui
|