SeaWolf-AI commited on
Commit
5216cd1
·
verified ·
1 Parent(s): ada5a41

Add decision core: ztc_server.py

Browse files
Files changed (1) hide show
  1. ztc_server.py +127 -0
ztc_server.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """POST /v1/systemone server for FINAL-Bench/Darwin-27B-ZTC (System One request shape).
2
+
3
+ ZTC_MODEL=<snapshot path or FINAL-Bench/Darwin-27B-ZTC> PORT=8000 python ztc_server.py
4
+
5
+ Every question is answered with one forward pass of the backbone and the decision readout
6
+ (no generated tokens). Questions from concurrent requests are batched together on the GPU
7
+ (at most ZTC_BATCH questions and ZTC_TOKEN_BUDGET estimated tokens per batch). A question
8
+ longer than ZTC_MAX_LENGTH tokens is refused with HTTP 422 (never truncated).
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import math
14
+ import os
15
+ import queue
16
+ import sys
17
+ import threading
18
+ from concurrent.futures import Future
19
+ from pathlib import Path
20
+
21
+ import torch
22
+ import uvicorn
23
+ from fastapi import FastAPI, HTTPException, Request
24
+
25
+ MODEL = os.getenv("ZTC_MODEL", "FINAL-Bench/Darwin-27B-ZTC")
26
+ MAX_LENGTH = int(os.getenv("ZTC_MAX_LENGTH", "32768"))
27
+ BATCH = int(os.getenv("ZTC_BATCH", "8"))
28
+ TOKEN_BUDGET = int(os.getenv("ZTC_TOKEN_BUDGET", "16000"))
29
+
30
+ path = Path(MODEL)
31
+ if not path.exists():
32
+ from huggingface_hub import snapshot_download
33
+ path = Path(snapshot_download(MODEL, revision=os.getenv("ZTC_REVISION")))
34
+ sys.path.insert(0, str(path))
35
+ from autojev.model import DecisionModel, answer # noqa: E402
36
+
37
+ model = DecisionModel(checkpoint=path).eval()
38
+ NAME = "darwin-27b-ztc"
39
+ Q: "queue.Queue[tuple[dict, Future]]" = queue.Queue()
40
+
41
+
42
+ def estimate(row):
43
+ return len(json.dumps([row["state"], row["question"]], ensure_ascii=False)) // 3 + 192
44
+
45
+
46
+ @torch.inference_mode()
47
+ def probabilities(rows):
48
+ batch = model.prepare(rows, max_length=MAX_LENGTH)
49
+ values = (model(batch) / model.temperature).softmax(-1).cpu().tolist()
50
+ return [v[:c] for v, c in zip(values, batch.counts)]
51
+
52
+
53
+ def run_single(row, future):
54
+ try:
55
+ future.set_result(probabilities([row])[0])
56
+ except ValueError as error:
57
+ future.set_exception(ValueError(str(error)))
58
+ except Exception as error: # out of memory or other runtime failure
59
+ torch.cuda.empty_cache()
60
+ future.set_exception(RuntimeError(str(error)[:300]))
61
+
62
+
63
+ def worker():
64
+ while True:
65
+ items = [Q.get()]
66
+ while len(items) < BATCH * 4:
67
+ try:
68
+ items.append(Q.get(timeout=0.005))
69
+ except queue.Empty:
70
+ break
71
+ items.sort(key=lambda item: estimate(item[0]))
72
+ i = 0
73
+ while i < len(items):
74
+ group = [items[i]]
75
+ tokens = estimate(items[i][0])
76
+ while i + len(group) < len(items) and len(group) < BATCH and tokens + estimate(items[i + len(group)][0]) <= TOKEN_BUDGET:
77
+ tokens += estimate(items[i + len(group)][0])
78
+ group.append(items[i + len(group)])
79
+ i += len(group)
80
+ try:
81
+ for (row, future), values in zip(group, probabilities([g[0] for g in group])):
82
+ future.set_result(values)
83
+ except Exception:
84
+ torch.cuda.empty_cache()
85
+ for row, future in group:
86
+ if not future.done():
87
+ run_single(row, future)
88
+
89
+
90
+ threading.Thread(target=worker, daemon=True).start()
91
+ app = FastAPI(title="Darwin-27B-ZTC System One server")
92
+
93
+
94
+ @app.get("/health")
95
+ def health():
96
+ return {"status": "ready", "model": NAME, "temperature": model.temperature}
97
+
98
+
99
+ @app.post("/v1/systemone")
100
+ async def system_one(request: Request):
101
+ body = await request.json()
102
+ questions = body.get("questions") or {}
103
+ if body.get("images"):
104
+ raise HTTPException(422, "This model is text-only; images are not supported.")
105
+ futures = {}
106
+ for key, question in questions.items():
107
+ if question.get("type") not in ("choice", "noul", "score"):
108
+ raise HTTPException(422, f"Unsupported question type: {question.get('type')}")
109
+ future = Future()
110
+ Q.put(({"state": body.get("state"), "question": question}, future))
111
+ futures[key] = future
112
+ answers = {}
113
+ for key, future in futures.items():
114
+ try:
115
+ values = await asyncio.wrap_future(future)
116
+ except ValueError as error:
117
+ raise HTTPException(422, str(error))
118
+ except RuntimeError as error:
119
+ raise HTTPException(500, str(error))
120
+ if not all(math.isfinite(v) and v >= 0 for v in values) or sum(values) <= 0:
121
+ raise HTTPException(500, 'Model produced an invalid probability vector; refusing to substitute a prediction.')
122
+ answers[key] = answer(questions[key], values)
123
+ return {"model": NAME, "answers": answers}
124
+
125
+
126
+ if __name__ == "__main__":
127
+ uvicorn.run(app, host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", "8000")), log_level="warning")