DuoNeural's picture
Upload app.py with huggingface_hub
6b7cf15 verified
Raw
History Blame Contribute Delete
8.91 kB
#!/usr/bin/env python3
"""
app.py β€” SmolLM2-360M-Think Demo β€” HuggingFace Spaces
DuoNeural (Archon + Jesse + Aura) β€” 2026
Demonstrates Think Instillation: a 360M model reasoning through multiple-choice
questions using learned <think> traces, trained via SFT + GRPO with dead-prompt filtering.
"""
import re
import torch
import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer
model = None
tokenizer = None
DEVICE = "cpu"
MODEL_ID = "DuoNeural/SmolLM2-360M-Think-R18"
EXAMPLES = [
["What is the main function of the mitochondria in a cell?",
"(A) Producing proteins\n(B) Generating energy (ATP)\n(C) Storing genetic material\n(D) Breaking down waste"],
["Which planet in our solar system has the most moons?",
"(A) Jupiter\n(B) Saturn\n(C) Uranus\n(D) Neptune"],
["What gas do plants take in during photosynthesis?",
"(A) Oxygen\n(B) Nitrogen\n(C) Carbon dioxide\n(D) Hydrogen"],
["If a car travels 60 miles per hour, how far will it go in 2.5 hours?",
"(A) 100 miles\n(B) 120 miles\n(C) 150 miles\n(D) 180 miles"],
["Which of the following is an example of a conductor of electricity?",
"(A) Rubber\n(B) Wood\n(C) Copper\n(D) Plastic"],
["What is the chemical formula for water?",
"(A) CO2\n(B) NaCl\n(C) H2O\n(D) O2"],
]
PROMPT_TEMPLATE = """Answer the following multiple choice question. Think step by step before answering.
Question: {question}
Choices:
{choices}
Reasoning: <think>"""
def load_model():
global model, tokenizer
if model is not None:
return
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.float32,
low_cpu_mem_usage=True,
).to(DEVICE)
model.eval()
def parse_output(full_text: str):
"""Extract <think> content and final answer from model output."""
think_match = re.search(r"<think>(.*?)</think>", full_text, re.DOTALL)
think_content = think_match.group(1).strip() if think_match else ""
# Look for answer pattern (A)/(B)/(C)/(D) after </think>
after_think = full_text[think_match.end():] if think_match else full_text
answer_match = re.search(r"\(([ABCD])\)", after_think)
answer = f"({answer_match.group(1)})" if answer_match else "No clear answer found"
return think_content, answer, after_think.strip()
def generate(question: str, choices: str, max_new_tokens: int, temperature: float, top_p: float):
if not question.strip():
return "", "", "", "Please enter a question."
load_model()
prompt = PROMPT_TEMPLATE.format(
question=question.strip(),
choices=choices.strip(),
)
inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
input_len = inputs["input_ids"].shape[1]
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=int(max_new_tokens),
temperature=float(temperature),
top_p=float(top_p),
do_sample=(temperature > 0.01),
pad_token_id=tokenizer.eos_token_id,
eos_token_id=tokenizer.eos_token_id,
)
# Decode only the generated portion
generated_ids = outputs[0][input_len:]
raw_output = tokenizer.decode(generated_ids, skip_special_tokens=True)
# The model started mid-<think> tag, so reconstruct
full_with_think = "<think>" + raw_output
think_content, answer, after_think = parse_output(full_with_think)
# Build colored HTML for the reasoning trace
think_html = _render_think_html(think_content) if think_content else _no_think_html()
answer_html = _render_answer_html(answer)
return think_html, answer_html, raw_output
def _render_think_html(think_content: str) -> str:
# Escape HTML and preserve newlines
escaped = think_content.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
lines = escaped.split("\n")
rendered_lines = []
for line in lines:
line = line.strip()
if not line:
rendered_lines.append("")
continue
rendered_lines.append(f'<span style="color:#c8d6e5;">{line}</span>')
body = "<br>".join(rendered_lines)
return f"""
<div style="background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:16px;font-family:monospace;font-size:13px;line-height:1.6;">
<div style="color:#58a6ff;font-size:11px;font-weight:bold;margin-bottom:8px;letter-spacing:0.1em;">
β—† REASONING TRACE &lt;think&gt;
</div>
<div style="color:#c8d6e5;">
{body}
</div>
<div style="color:#3d444d;font-size:11px;margin-top:8px;">&lt;/think&gt;</div>
</div>"""
def _no_think_html() -> str:
return """
<div style="background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:16px;color:#666;font-size:13px;font-family:monospace;">
No &lt;think&gt; trace found in output. The model may have skipped reasoning.
</div>"""
def _render_answer_html(answer: str) -> str:
is_valid = re.match(r"\([ABCD]\)", answer)
color = "#2ea043" if is_valid else "#f85149"
border = "#238636" if is_valid else "#da3633"
icon = "βœ“" if is_valid else "?"
return f"""
<div style="background:#0d1117;border:2px solid {border};border-radius:8px;padding:16px;text-align:center;">
<div style="color:{color};font-size:32px;font-weight:bold;font-family:monospace;">
{icon} {answer}
</div>
<div style="color:#666;font-size:11px;margin-top:4px;">Model's final answer</div>
</div>"""
def fill_example(question, choices):
return question, choices
def build_demo():
with gr.Blocks(
title="SmolLM2-360M-Think β€” DuoNeural Think Instillation",
theme=gr.themes.Base(primary_hue="blue", secondary_hue="purple", neutral_hue="slate"),
css="""
.gradio-container { background: #010409; color: #e6edf3; }
.gr-button-primary { background: #1f6feb !important; border: 1px solid #388bfd !important; }
footer { display: none !important; }
"""
) as demo:
gr.Markdown("""
# 🧠 SmolLM2-360M-Think
**Think Instillation** β€” DuoNeural, 2026
A 360M model trained to reason through problems using `<think>` traces before answering.
No giant teacher model. No distillation. Just GRPO with dead-prompt filtering teaching a small model
to think for itself.
**28/100 correct on ARC-Easy** (GRPO helped: post_SFT=0.250 β†’ final=0.280 with +0.030 delta)
Type a multiple-choice question below and watch the model reason through it.
""")
with gr.Row():
with gr.Column(scale=2):
question_in = gr.Textbox(
label="Question",
placeholder="What is the main function of the mitochondria in a cell?",
lines=3,
)
choices_in = gr.Textbox(
label="Answer Choices",
placeholder="(A) Producing proteins\n(B) Generating energy\n(C) Storing DNA\n(D) Breaking down waste",
lines=4,
value="(A) Producing proteins\n(B) Generating energy (ATP)\n(C) Storing genetic material\n(D) Breaking down waste",
)
with gr.Column(scale=1):
max_tokens = gr.Slider(64, 512, value=256, step=32, label="Max reasoning tokens")
temperature = gr.Slider(0.0, 1.2, value=0.7, step=0.1, label="Temperature")
top_p = gr.Slider(0.5, 1.0, value=0.9, step=0.05, label="Top-p")
think_btn = gr.Button("Think + Answer", variant="primary", size="lg")
gr.Markdown("**Examples** β€” click any row to load it:")
examples_table = gr.Examples(
examples=EXAMPLES,
inputs=[question_in, choices_in],
label=None,
)
gr.Markdown("### πŸ’­ Reasoning Trace")
think_out = gr.HTML()
gr.Markdown("### 🎯 Final Answer")
answer_out = gr.HTML()
with gr.Accordion("Raw model output", open=False):
raw_out = gr.Textbox(label="Raw generated text", lines=8, interactive=False)
think_btn.click(
fn=generate,
inputs=[question_in, choices_in, max_tokens, temperature, top_p],
outputs=[think_out, answer_out, raw_out],
)
question_in.change(fn=None, inputs=None, outputs=None)
gr.Markdown("""
---
**DuoNeural** β€” open research lab Β· one human, two AIs, shared curiosity
Think Instillation technique by Archon (DuoNeural). GRPO with dead-prompt filtering.
Model: [DuoNeural/SmolLM2-360M-Think-R18](https://huggingface.co/DuoNeural/SmolLM2-360M-Think-R18) Β·
[huggingface.co/DuoNeural](https://huggingface.co/DuoNeural)
""")
return demo
if __name__ == "__main__":
demo = build_demo()
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)