Spaces:
Running on Zero
Running on Zero
Download app.py from hugging-apps/sansi-typed-decision: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/spaces/hugging-apps/sansi-typed-decision/resolve/main/app.py
- Command line
-
hf download hf://spaces/hugging-apps/sansi-typed-decision/app.py
-
curl -L -o app.py https://huggingface.co/spaces/hugging-apps/sansi-typed-decision/resolve/main/app.py
10.4 kB
| """SanSi typed-decision demo β one decision, read after every thinking loop. | |
| SanSi (minnesotanlp/SanSi-2.6B) turns a language model into a classifier over *declared* | |
| options: it never generates text, it returns a probability per option after each of the | |
| Ouro backbone's loops. One forward pass therefore answers at every compute budget 1-8. | |
| """ | |
| import html | |
| import time | |
| import gradio as gr | |
| import spaces # must precede any torch/CUDA-touching import | |
| import torch | |
| from sansi.hub import decide, load | |
| MODEL_ID = "minnesotanlp/SanSi-2.6B" | |
| model, tok = load(MODEL_ID) # Ouro-2.6B + LoRA + readout, moved to cuda by load() | |
| model.to("cuda") | |
| # --------------------------------------------------------------------------- UI | |
| # Validated categorical palette, slot 1, both modes (dataviz reference instance). | |
| SERIES_LIGHT = "#2a78d6" | |
| SERIES_DARK = "#3987e5" | |
| GREEN_LIGHT = "#006300" # success text token, light surface | |
| GREEN_DARK = "#0ca30c" | |
| RED_LIGHT = "#d03b3b" # status critical, light surface (icon+label paired in UI) | |
| RED_DARK = "#e66767" | |
| CSS = """ | |
| .dark .gradio-container { color: var(--body-text-color); } | |
| :root .prob-fill { background: SERIES_LIGHT_HEX; } | |
| :root:where([data-theme="dark"]) .prob-fill, | |
| :root[data-theme="dark"] .prob-fill { background: SERIES_DARK_HEX; } | |
| :root .strip-step { background: rgba(42,120,214,.14); } | |
| :root:where([data-theme="dark"]) .strip-step, | |
| :root[data-theme="dark"] .strip-step { background: rgba(57,135,229,.22); } | |
| .sansi-wrap { font-variant-numeric: tabular-nums; } | |
| .sansi-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; } | |
| .sansi-letter { flex: 0 0 26px; font-weight: 700; } | |
| .sansi-opttext { flex: 1 1 auto; min-width: 0; } | |
| .sansi-track { flex: 0 0 100px; display: inline-block; } | |
| .sansi-p { flex: 0 0 48px; text-align: right; } | |
| .prob-track { background: var(--panel-background-color, transparent); border: 1px solid var(--border-color-primary, rgba(11,11,11,.10)); border-radius: 6px; height: 26px; position: relative; overflow: hidden; display: block; } | |
| .prob-fill { height: 100%; border-radius: 5px 2px 2px 5px; } | |
| .verdict { font-size: 1.02em; margin: 10px 0 2px; } | |
| .strip { display: flex; gap: 3px; margin: 6px 0 14px; } | |
| .strip-step { flex: 1 1 0; text-align: center; border-radius: 4px; padding: 4px 2px; font-size: 0.9em; } | |
| """ | |
| # --------------------------------------------------------------------- examples | |
| # The authors' own showcase items (Sansi/docs/data/items.json, Apache-2.0) + the | |
| # model-card quick start. p values listed are from the paper (SanSi-1.4B analysis | |
| # model) β the demo serves SanSi-2.6B, so numbers differ slightly. | |
| EXAMPLES = [ | |
| # label, state, question, options, loops | |
| ["Shortest of three (model card)", | |
| "Mia is taller than Sam. Sam is taller than Lee.", | |
| "Who is the shortest?", | |
| "Mia\nSam\nLee", 8], | |
| ["Paraphrase: answer flips with thought (paper)", | |
| "The six people killed were four Burmese citizens and two Russians .", | |
| 'Does this sentence mean the same thing: "The six people killed were four Russian and two Burmese citizens ."', | |
| "no: Different meaning, even if most words match\nyes: Same meaning, possibly reworded", 8], | |
| ["Logic: entailment flips to unknown (paper)", | |
| "No road is dustless. Some streets are roads.", | |
| 'Using only the facts and rules above, is the statement "Some streets are dustless." true, false, or unknown?', | |
| "true\nfalse\nunknown", 8], | |
| ["Math: settles on 300 after 8 loops (paper)", | |
| '{\n "subject": "elementary mathematics",\n "question": "If 30,000 is divided by 10 and then divided by 10 again, what will be the resulting number?"\n}', | |
| "Which option correctly answers the question?", | |
| "a: 3\nb: 30\nc: 300\nd: 3,000", 8], | |
| ["MNLI: contradiction, not entailment (teaser)", | |
| "I guess I lay there unconscious awhile.", | |
| 'Given the text above, is this hypothesis entailed, neutral, or contradicted? Hypothesis: "I lay awake for a long time."', | |
| "entailment\nneutral\ncontradiction", 8], | |
| ["Unanswerable: stays near-uniform (SQuAD 2.0)", | |
| "Fresno is the largest U.S. city not directly linked to an Interstate highway. When the Interstate Highway System was created in the 1950s, the decision was made to build what is now Interstate 5 on the west side of the Central Valley, and thus bypass many of the population centers in the region, instead of upgrading what is now State Route 99. Due to rapidly raising population and traffic in cities along SR 99, as well as the desirability of Federal funding, much discussion has been made to upgrade it to interstate standards and eventually incorporate it into the interstate system, most likely as Interstate 9. Major improvements to signage, lane width, median separation, vertical clearance, and other concerns are currently underway.", | |
| "What do many people want to integrate into interstate 5? (Answer from the passage above.)", | |
| "1950s\nFresno\nwest\nSR 99", 8], | |
| ] | |
| def decide_all(state, question, options_text, loops): | |
| """Option probabilities after every loop. | |
| Args: | |
| state: context the decision is grounded in ("" allowed). | |
| question: the question to answer. | |
| options_text: one option per line, 2 to 26 of them. | |
| loops: how many thinking loops to run (1-16; >8 reuse the last trained readout). | |
| Returns: | |
| (report_html, last_probs_json, elapsed_seconds) | |
| """ | |
| t0 = time.perf_counter() | |
| options = [o.strip() for o in options_text.split("\n") if o.strip()] | |
| if not 2 <= len(options) <= 26: | |
| raise gr.Error(f"Give between 2 and 26 options (one per line) β got {len(options)}.") | |
| probs_per_loop = decide(model, tok, state.strip(), question.strip(), options, loops=int(loops)) | |
| elapsed = time.perf_counter() - t0 | |
| n = len(options) | |
| letters = [chr(ord("A") + i) for i in range(n)] | |
| final = probs_per_loop[-1] | |
| arg = max(range(n), key=final.__getitem__) | |
| p_max = final[arg] | |
| threshold = (1 + 1 / n) / 2 # "hard answer" cut from the paper | |
| confident = p_max >= threshold | |
| esc = [html.escape(o) for o in options] | |
| rows = [] | |
| for i in range(n): | |
| pct = final[i] * 100 | |
| fill = f'<div class="prob-fill" style="width:{pct:.2f}%"></div>' | |
| rows.append( | |
| f'<div class="sansi-row"><span class="sansi-letter">{letters[i]}</span>' | |
| f'<span class="sansi-opttext">{esc[i]}</span>' | |
| f'<span class="sansi-track"><span class="prob-track">{fill}</span></span>' | |
| f'<span class="sansi-p">{final[i]:.3f}</span></div>' | |
| ) | |
| # Per-loop strip: which option the argmax lands on after each loop (the flip story). | |
| steps = [] | |
| for t, pl in enumerate(probs_per_loop): | |
| a_t = max(range(n), key=pl.__getitem__) | |
| right = (a_t == arg) | |
| steps.append( | |
| f'<span class="strip-step">{t + 1}: {letters[a_t]}{" β" if right else " β¦"}</span>' | |
| ) | |
| verdict = ( | |
| f'Answer <b>{letters[arg]}</b> ({p_max:.2f}) β {html.escape(options[arg])}.' | |
| + ("" if confident else ' Confidence below the "hard answer" cut β the model may be signaling an unanswerable question.') | |
| ) | |
| report = ( | |
| f'<div class="sansi-wrap"><p class="verdict">{verdict}</p>' | |
| + "".join(rows) | |
| + f'<div class="strip" title="argmax after each loop">{"".join(steps)}</div></div>' | |
| ) | |
| return report, final, elapsed | |
| # -------------------------------------------------------------------------- UI | |
| with gr.Blocks( | |
| title="SanSi: A Looped Typed Decision Model", | |
| theme=gr.themes.Citrus(), | |
| css=CSS.replace("SERIES_LIGHT_HEX", SERIES_LIGHT).replace("SERIES_DARK_HEX", SERIES_DARK), | |
| ) as demo: | |
| gr.HTML( | |
| """<div style="display:flex;align-items:center;gap:12px;"> | |
| <div style="width:44px;height:44px;border-radius:10px;background:linear-gradient(135deg,#2a78d6,#4a3aa7);display:flex;align-items:center;justify-content:center;font-size:24px">π</div> | |
| <div><h1 style="margin:0;font-size:1.45em">SanSi: A Looped Typed Decision Model for System 1.5 Thinking</h1> | |
| <p style="margin:2px 0 0;opacity:.75">Declare the options, get a probability per option β read after <b>every</b> thinking loop. No text is generated.</p></div> | |
| </div>""" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| state_in = gr.Textbox(label="State (context)", lines=3, placeholder="Facts, a passage, or leave empty") | |
| question_in = gr.Textbox(label="Question", lines=2) | |
| options_in = gr.Textbox(label="Options β one per line (2β26)", lines=4, | |
| value="Mia\nSam\nLee") | |
| loops_in = gr.Slider(minimum=1, maximum=16, step=1, value=8, label="Thinking loops", | |
| info="1β8 trained; beyond 8 reuses the last trained readout") | |
| submit = gr.Button("Decide", variant="primary") | |
| with gr.Column(): | |
| report_out = gr.HTML(label="Decision (after the last loop)") | |
| probs_out = gr.JSON(label="Option probabilities (last loop)") | |
| elapsed_out = gr.Number(label="Elapsed (s)", precision=1) | |
| submit.click( | |
| decide_all, | |
| inputs=[state_in, question_in, options_in, loops_in], | |
| outputs=[report_out, probs_out, elapsed_out], | |
| api_name="decide", | |
| ) | |
| for comp in (question_in, options_in): | |
| comp.submit( | |
| decide_all, | |
| inputs=[state_in, question_in, options_in, loops_in], | |
| outputs=[report_out, probs_out, elapsed_out], | |
| api_name=False, | |
| ) | |
| gr.Examples( | |
| examples=[[e[1], e[2], e[3], e[4]] for e in EXAMPLES], | |
| inputs=[state_in, question_in, options_in, loops_in], | |
| fn=decide_all, | |
| outputs=[report_out, probs_out, elapsed_out], | |
| cache_examples=True, | |
| cache_mode="lazy", | |
| label="Examples β from the paper and the model card", | |
| ) | |
| gr.Markdown( | |
| "Model: [minnesotanlp/SanSi-2.6B](https://huggingface.co/minnesotanlp/SanSi-2.6B) Β· " | |
| "paper [SanSi: A Looped Typed Decision Model for System 1.5 Thinking](https://huggingface.co/papers/2610.07730) Β· " | |
| "code [minnesotanlp/Sansi](https://github.com/minnesotanlp/Sansi) (Apache-2.0). " | |
| "Inference runs the authors' own `sansi` package." | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(mcp_server=True) |