File size: 10,154 Bytes
0a16466 | 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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | """decider-4b on ZeroGPU, in TypeSafe's /v1/systemone shape.
Endpoint signature deliberately mirrors dkappe/IDU's `systemone` (state, questions,
three temperature overrides) and returns the same (table, raw-json-string) tuple, so the
existing jev-bench runner drives this Space without a special case.
Scoring path is the authors' own reference implementation, vendored in `decider/` from
https://huggingface.co/Mapika/decider-4b (Apache-2.0): prompt.py, model.py, systemone.py,
infer.py. Three deviations from the reference, all noted here:
* the checkpoint is loaded once at module scope and moved to CUDA eagerly, which is the
ZeroGPU pattern the official decider demos use;
* temperature is an explicit per-type argument rather than instance state, so
concurrent requests on one worker cannot race each other. decider_config.json carries
temperature_by_type {choice: 1.11, noul: 1.56, score: 1.287}, fitted per answer type,
and serving every answer at the single 1.099 value would flatten the yes/no and Score
probabilities;
* questions are scored independently (one row each). The packed single-row path is not
implemented because per-type temperature is applied per slot.
A caller passes 0 for a temperature to keep the checkpoint's fitted value, which is the
same sentinel IDU uses.
"""
import os
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import spaces # noqa: E402 — must precede torch / transformers
import json # noqa: E402
import time # noqa: E402
import gradio as gr # noqa: E402
import torch # noqa: E402
from huggingface_hub import hf_hub_download # noqa: E402
from decider.infer import Example, Q, neutralize_options # noqa: E402
from decider.model import DecisionModel, collate # noqa: E402
from decider.prompt import MAX_OPTIONS, build # noqa: E402
from decider.systemone import ( # noqa: E402
assemble,
plan_rows,
render_question,
render_state,
row_types,
)
MODEL_ID = "Mapika/decider-4b"
SPACE_REVISION = "0.1.0"
CFG = json.load(open(hf_hub_download(MODEL_ID, "decider_config.json")))
VERSION = CFG.get("version", "dev")
DEFAULT_T = float(CFG.get("temperature", 1.0))
T_BY_TYPE = {k: float(v) for k, v in (CFG.get("temperature_by_type") or {}).items()}
NEUTRALIZE_NONE = bool(CFG.get("neutralize_none", True))
DEFAULT_ISOLATED = bool(CFG.get("isolated_levels", False))
MAX_STATE_TOKENS = int(CFG.get("max_state_tokens", 32768))
MAX_FWD_TOKENS = 65536
# Loaded once at module scope, eagerly moved to the (ZeroGPU-intercepted) CUDA device.
MODEL = DecisionModel(MODEL_ID, dtype=torch.bfloat16, grad_ckpt=False).to("cuda").eval()
TOK = MODEL.tok
class _Keep:
"""The reference's rng stand-in: keep the caller's option order, never shuffle."""
def shuffle(self, x):
pass
def sample(self, xs, k):
return xs[:k]
def _temps(tc, ts, tn):
"""Per-answer-type temperatures. 0 means 'keep the checkpoint's fitted value'."""
d = dict(T_BY_TYPE)
for k in ("choice", "noul", "score"):
d.setdefault(k, DEFAULT_T)
if tc and float(tc) > 0:
d["choice"] = float(tc)
if ts and float(ts) > 0:
d["noul"] = float(ts)
if tn and float(tn) > 0:
d["score"] = float(tn)
return d
def _system_one(state, questions, temps, isolated=None):
"""Reference `Decider.system_one`, eager path, with per-slot temperature."""
if isolated is None:
isolated = DEFAULT_ISOLATED
ctx = render_state(state)
rqs = {k: render_question(v) for k, v in questions.items()}
opts = ((lambda r: neutralize_options(r["options"])[0]) if NEUTRALIZE_NONE
else (lambda r: list(r["options"])))
flat, index = plan_rows(rqs, isolated)
items = [
build(Example(ctx, [Q(r["question"], opts(r), 0)]), TOK, _Keep(),
max_options=MAX_OPTIONS, max_ctx_tokens=MAX_STATE_TOKENS,
layout="state_first")
for r in flat
]
rtypes = row_types(rqs, index)
probs = []
with torch.no_grad():
per = max(1, MAX_FWD_TOKENS // max(len(it["ids"]) for it in items))
for i in range(0, len(items), per):
chunk = items[i:i + per]
bt = collate(chunk, TOK.pad_token_id)
lg = MODEL.slot_logits(
*[bt[k].to("cuda") for k in
("input_ids", "attention_mask", "slot_idx", "slot_batch", "nopts")]
)
# one slot per item, so slot order follows item order
tvec = torch.tensor([temps.get(rtypes[i + j], DEFAULT_T)
for j in range(len(chunk))],
device=lg.device, dtype=lg.dtype)
pr = torch.softmax(lg / tvec.unsqueeze(-1), -1).float().cpu()
c = 0
for it in chunk:
probs.append(pr[c:c + len(it["slots"])])
c += len(it["slots"])
flatp = [p.tolist() for ps in probs for p in ps]
answers = assemble(rqs, index, flatp)
n_tokens = sum(len(it["ids"]) for it in items)
return {
"model": f"decider-4b-{VERSION}",
"answers": answers,
"usage": {"input_tokens": n_tokens, "output_tokens": 0, "rows": len(items)},
}
def _table(raw):
rows = []
for k, a in raw["answers"].items():
if a.get("type") == "choice":
rows.append([k, a.get("choice"), a.get("confidence")])
elif a.get("type") == "score":
rows.append([k, a.get("score"), a.get("confidence")])
else:
rows.append([k, a.get("noul"), None])
return {"headers": ["question", "answer", "confidence"], "data": rows,
"metadata": None}
@spaces.GPU(duration=60)
def answer_systemone(state_text: str, q_text: str, tc: float = 0, ts: float = 0,
tn: float = 0) -> tuple:
"""Score typed questions against a state and return the Jev shape.
Args:
state_text: the state, free text or JSON.
q_text: the questions object, as JSON.
tc, ts, tn: temperature overrides for choice / noul / score. 0 keeps the
checkpoint's fitted value.
"""
t0 = time.time()
if isinstance(q_text, str):
questions = json.loads(q_text)
else:
questions = q_text
if not isinstance(questions, dict) or not questions:
return ({"headers": ["question", "answer", "confidence"], "data": [], "metadata": None},
json.dumps({"error": "questions must be a non-empty JSON object"}))
if isinstance(state_text, str) and state_text.strip().startswith(("{", "[")):
try:
state = json.loads(state_text)
except Exception:
state = state_text
else:
state = state_text
raw = _system_one(state, questions, _temps(tc, ts, tn))
raw["space"] = f"dkappe/decider4b {SPACE_REVISION}"
raw["device"] = str(MODEL.model.device) if hasattr(MODEL, "model") else "cuda"
raw["elapsed_s"] = round(time.time() - t0, 2)
return _table(raw), json.dumps(raw, ensure_ascii=False, default=str)
@spaces.GPU(duration=5)
def health() -> str:
return (f"decider-4b {VERSION} on ZeroGPU\n"
f"temperatures: base {DEFAULT_T}, by type {T_BY_TYPE}\n"
f"isolated_levels={DEFAULT_ISOLATED} neutralize_none={NEUTRALIZE_NONE}\n"
f"max_state_tokens={MAX_STATE_TOKENS} max_options={MAX_OPTIONS}")
DEMO_Q = {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "Money: charges, invoices, refunds, duplicate payments.",
"technical": "The software misbehaving: bugs, errors, crashes, outages.",
"account": "Logging in, passwords, 2FA, locked accounts, permissions.",
"returns": "Physical goods: exchanges, damaged items, shipping, delivery.",
"other": "None of the above: legal, press, partnerships, hiring.",
},
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the writer?",
"criteria": [
"Making a request or asking a question, no problem reported.",
"Reporting a problem and staying civil.",
"Angry and escalating: demands immediate action, threatens to cancel.",
],
},
"refund_requested": {
"type": "noul",
"instructions": "Is the writer asking for money back?",
},
}
DEMO_STATE = ("I was charged twice for order C-26. Please refund the duplicate charge.\n"
"Order history: order C-26 charged $310 twice on 2026-09-19.\n"
"Refund policy: duplicate charges are refunded when the same order is "
"charged twice.")
with gr.Blocks(title="decider-4b systemone") as demo:
gr.Markdown(
"# decider-4b, Jev-shaped decisions\n"
"`choice` / `score` / `noul` questions scored in one forward pass, with calibrated "
"probabilities. 4B dense, Qwen3.5-4B-Base, Apache 2.0. "
"The endpoint mirrors `dkappe/IDU`'s `systemone` so one benchmark harness drives "
"both. A temperature of 0 keeps the checkpoint's fitted per-type value."
)
with gr.Tab("systemone"):
st = gr.Code(label="state", language="json", value=DEMO_STATE, lines=6)
qt = gr.Code(label="questions", language="json",
value=json.dumps(DEMO_Q, indent=2), lines=18)
with gr.Row():
tc = gr.Number(label="choice temp (0 = fitted)", value=0)
ts = gr.Number(label="noul temp (0 = fitted)", value=0)
tn = gr.Number(label="score temp (0 = fitted)", value=0)
run = gr.Button("decide", variant="primary")
out_rows = gr.Dataframe(label="answers")
out_raw = gr.Code(label="raw response", language="json", lines=16)
run.click(answer_systemone, [st, qt, tc, ts, tn], [out_rows, out_raw],
api_name="systemone")
with gr.Tab("health"):
hb = gr.Button("check")
ho = gr.Textbox(label="status", lines=6)
hb.click(health, None, ho, api_name="health")
if __name__ == "__main__":
demo.launch()
|