Spaces:
Running
Running
| #!/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("&", "&").replace("<", "<").replace(">", ">") | |
| 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 <think> | |
| </div> | |
| <div style="color:#c8d6e5;"> | |
| {body} | |
| </div> | |
| <div style="color:#3d444d;font-size:11px;margin-top:8px;"></think></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 <think> 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) | |