File size: 6,195 Bytes
ca4c9b1
 
 
 
dec9a86
 
 
 
 
 
 
 
 
 
 
 
 
 
ca4c9b1
 
 
 
dec9a86
 
 
ca4c9b1
 
 
 
 
 
 
 
 
 
dec9a86
ca4c9b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dec9a86
ca4c9b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6323a4
ca4c9b1
 
 
 
b6323a4
ca4c9b1
 
 
 
b6323a4
ca4c9b1
 
b6323a4
ca4c9b1
b6323a4
ca4c9b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import torch
import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer

try:
    import spaces  # HF ZeroGPU: required so the Space detects a GPU-capable function
    HAS_SPACES = True
except ImportError:
    # Allows running locally (outside HF Spaces) without the `spaces` package.
    HAS_SPACES = False

    class _NoOpSpaces:
        @staticmethod
        def GPU(func):
            return func

    spaces = _NoOpSpaces()

# --------------------------------------------------------------------------
# Config
# --------------------------------------------------------------------------
MODEL_ID = "SupraLabs/Supra2-100M-Instruct"
ZERO_GPU = HAS_SPACES  # HF ZeroGPU: GPU only exists inside @spaces.GPU-decorated calls
DEVICE = "cpu" if ZERO_GPU else ("cuda" if torch.cuda.is_available() else "cpu")
DTYPE = torch.float32  # 100M params — CPU inference is fast enough, no need for bf16/GPU
MAX_CONTEXT_TOKENS = 1024  # model was trained at 1024; 2048 config but untested beyond 1024

# --------------------------------------------------------------------------
# Load model + tokenizer once at startup
# --------------------------------------------------------------------------
print(f"[*] Loading {MODEL_ID} on {DEVICE}...")

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    dtype=DTYPE,
    trust_remote_code=True,
)
model.to(DEVICE)
model.eval()

print("[*] Model loaded.")

# --------------------------------------------------------------------------
# Generation logic
# --------------------------------------------------------------------------
def build_messages(history, user_message):
    """Convert Gradio chat history (list of dicts) + new message into
    the messages format expected by the model's chat template."""
    messages = []
    for turn in history:
        messages.append({"role": turn["role"], "content": turn["content"]})
    messages.append({"role": "user", "content": user_message})
    return messages


def truncate_messages_to_fit(messages, max_tokens):
    """Drop oldest turns (keeping the latest user message) until the
    tokenized prompt fits within max_tokens. Small model = tiny context,
    so this matters in multi-turn chats."""
    while len(messages) > 1:
        prompt = tokenizer.apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True
        )
        n_tokens = len(tokenizer(prompt)["input_ids"])
        if n_tokens <= max_tokens:
            return messages
        messages.pop(0)  # drop oldest turn
    return messages


@spaces.GPU(duration=30)  # ZeroGPU: allocates a GPU for the duration of this call only
def respond(user_message, history, max_new_tokens, temperature, top_p, top_k):
    if not user_message or not user_message.strip():
        return history, ""

    history = history or []
    messages = build_messages(history, user_message)
    messages = truncate_messages_to_fit(messages, MAX_CONTEXT_TOKENS - max_new_tokens)

    prompt_text = tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )
    inputs = tokenizer(prompt_text, return_tensors="pt").to(DEVICE)

    with torch.no_grad():
        output_ids = model.generate(
            **inputs,
            max_new_tokens=int(max_new_tokens),
            do_sample=True,
            temperature=float(temperature),
            top_p=float(top_p),
            top_k=int(top_k),
            no_repeat_ngram_size=3,
            pad_token_id=tokenizer.pad_token_id,
            eos_token_id=tokenizer.eos_token_id,
        )

    generated_tokens = output_ids[0][inputs["input_ids"].shape[-1]:]
    response = tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()

    history = history + [
        {"role": "user", "content": user_message},
        {"role": "assistant", "content": response},
    ]
    return history, ""


def clear_chat():
    return [], ""


# --------------------------------------------------------------------------
# UI
# --------------------------------------------------------------------------
DESCRIPTION = """
# 🧠 Supra2-100M-Instruct — Chat Demo

A **100M-parameter** decoder-only model trained from scratch by **SupraLabs**
on ~30B tokens of English web text (Qwen3 architecture, custom 32K tokenizer).

[Model card](https://huggingface.co/SupraLabs/Supra2-100M-Instruct) ·
[Base model](https://huggingface.co/SupraLabs/Supra2-100M-Base) ·
[SupraLabs on HF](https://huggingface.co/SupraLabs)
"""

with gr.Blocks(title="Supra2-100M-Instruct Chat", theme=gr.themes.Soft()) as demo:
    gr.Markdown(DESCRIPTION)

    chatbot = gr.Chatbot(
        label="Supra2-100M-Instruct",
        type="messages",
        height=500,
        avatar_images=(None, "https://cdn-avatars.huggingface.co/v1/production/uploads/697f2832c2c5e4daa93cece7/IQMtz5gg-vLFP7Gn75POT.png"),
    )

    with gr.Row():
        msg = gr.Textbox(
            placeholder="What is AI?",
            show_label=False,
            scale=8,
            container=False,
        )
        submit_btn = gr.Button("Enter", variant="primary", scale=1)

    with gr.Row():
        clear_btn = gr.Button("🗑️ Delete chat")

    with gr.Accordion("⚙️ Hyperparams", open=False):
        max_new_tokens = gr.Slider(16, 512, value=200, step=8, label="Max new tokens")
        temperature = gr.Slider(0.1, 1.5, value=0.7, step=0.05, label="Temperature")
        top_p = gr.Slider(0.1, 1.0, value=0.85, step=0.05, label="Top-p")
        top_k = gr.Slider(1, 100, value=25, step=1, label="Top-k")

    gr.Examples(
        examples=[
            "What is AI?",
            "Write a short poem about the sea.",
            "Give me pros and cons for eating fast food.",
            "Who was Albert Einstein?",
        ],
        inputs=msg,
    )

    gen_inputs = [msg, chatbot, max_new_tokens, temperature, top_p, top_k]
    gen_outputs = [chatbot, msg]

    msg.submit(respond, gen_inputs, gen_outputs)
    submit_btn.click(respond, gen_inputs, gen_outputs)
    clear_btn.click(clear_chat, None, [chatbot, msg])

if __name__ == "__main__":
    demo.queue().launch()