SeaWolf-AI commited on
Commit
e77d0bf
·
verified ·
1 Parent(s): 4df8486

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
Files changed (5) hide show
  1. Dockerfile +4 -1
  2. README.md +8 -34
  3. app.py +95 -61
  4. index.html +160 -43
  5. 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
- N_CTX=4096 \
 
 
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-35B CPU Chat
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
- - FINAL-Bench/Darwin-36B-KR
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-35B · CPU chat
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 FastAPI frontend proxying to a local llama.cpp server.
3
- Backend is upstream llama.cpp (prebuilt release, supports POCKET's qwen35moe arch).
4
- Uses the native /completion endpoint with a manually-built ChatML prompt (Qwen-family),
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
- has_system = any(m.get("role") == "system" for m in messages)
21
- if not has_system:
22
- parts.append("<|im_start|>system\nYou are POCKET, a helpful assistant running on-device.<|im_end|>\n")
23
  for m in messages:
24
- role = m.get("role", "user")
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
- @app.get("/api/status")
32
- async def status():
 
 
 
33
  try:
34
  async with httpx.AsyncClient(timeout=3.0) as c:
35
- r = await c.get(BACKEND + "/health")
36
- if r.status_code == 200:
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 JSONResponse({"state": "loading",
41
- "detail": "downloading & loading POCKET-35B… (first boot can take a few minutes)"})
42
 
43
 
44
- @app.post("/api/chat")
45
- async def chat(req: Request):
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(body.get("temperature", 0.7)),
51
  "top_p": 0.95,
52
- "n_predict": int(body.get("max_tokens", 512)),
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 with httpx.AsyncClient(timeout=None) as c:
63
- async with c.stream("POST", BACKEND + "/completion", json=payload) as r:
64
- if r.status_code != 200:
65
- detail = (await r.aread()).decode("utf-8", "ignore")[:200]
66
- yield "data: " + json.dumps({"error": "backend %s: %s" % (r.status_code, detail)}) + "\n\n"
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 "data: " + json.dumps({"error": str(e)[:200]}) + "\n\n"
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:920px; 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,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
- <div class="card">
76
- <div class="chat" id="chat"><div class="empty" id="empty">Ask anything. This runs on CPU — first reply may take a moment.</div></div>
77
- <div class="ex" id="ex">
78
- <button>Explain mixture-of-experts in one line.</button>
79
- <button>Write a haiku about running AI with no GPU.</button>
80
- <button>한국어로 자기소개를 문장으로 해줘.</button>
 
 
 
 
 
 
 
81
  </div>
82
- <div class="bar">
83
- <input id="q" placeholder="Type a message…" autocomplete="off"/>
84
- <button id="send" disabled>Send</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  </div>
86
  </div>
87
 
88
- <div class="card about">
89
- <h3>What is POCKET?</h3>
90
- <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>
91
- <table>
92
- <tr><th>Build</th><th>Size</th><th>Runs on</th><th>Best for</th></tr>
93
- <tr><td>POCKET-35B Q4_K_M</td><td>21 GB</td><td>PC 32 GB</td><td>top quality</td></tr>
94
- <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>
95
- <tr><td>POCKET-KR / EN</td><td>~5 GB</td><td>iPhone / Android</td><td>on your phone</td></tr>
96
- </table>
97
- <div class="links">
98
- <a href="https://huggingface.co/collections/FINAL-Bench/pocket-models-6a618ee5d23eafb7e185a5c6">📚 POCKET collection</a>
99
- <a href="https://huggingface.co/FINAL-Bench/POCKET-35B-GGUF">POCKET-35B-GGUF</a>
100
- <a href="https://huggingface.co/FINAL-Bench/POCKET-KR-MLX">POCKET-KR (iPhone)</a>
101
- <a href="https://huggingface.co/FINAL-Bench/POCKET-EN-GGUF">POCKET-EN</a>
102
- <a href="https://huggingface.co/FINAL-Bench/Darwin-36B-Opus">🧬 Darwin-36B-Opus</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 history=[], busy=false;
 
 
 
 
 
 
 
 
 
 
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
- send.disabled = s.state!=='ready' || busy;
118
- if(s.state!=='ready') setTimeout(poll,2500);
 
 
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) Bring the FastAPI frontend up first, so the page + description render immediately
5
- # (chat stays disabled until the backend reports healthy).
6
  uvicorn app:app --host 0.0.0.0 --port "${PORT}" &
7
 
8
- # 2) Download the GGUF (13 GB first cold boot can take a few minutes).
9
- echo "[start] downloading ${MODEL_FILE} from ${MODEL_REPO} …"
10
- MODEL_PATH=$(python -c "import os;from huggingface_hub import hf_hub_download;print(hf_hub_download(os.environ['MODEL_REPO'],os.environ['MODEL_FILE']))")
11
- echo "[start] model ready at ${MODEL_PATH}"
 
12
 
13
- # 3) Start the inference backend: upstream llama.cpp master (supports qwen35moe),
14
- # CPU only, model's own chat template via --jinja.
15
- exec llama-server -m "${MODEL_PATH}" \
16
- --host 127.0.0.1 --port 8080 \
17
- -c "${N_CTX}" -t "$(nproc)" \
18
- --jinja --no-webui
 
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