Spaces:
Running on Zero
Running on Zero
File size: 12,341 Bytes
331dbbf cc0be50 a168e21 cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 a168e21 cc0be50 db1dccd cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf b6e309c 331dbbf 8b25b36 cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 b3115fb cc0be50 b3115fb cc0be50 b3115fb cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 8b25b36 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 331dbbf cc0be50 | 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 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | import random
from typing import Dict, List, Set
import spaces
import torch
import torch.nn.functional as F
import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_ID = "WhirlwindAI/Arithmetic-SLM"
IM_START = "[IM_START]"
IM_END = "[IM_END]"
NO_THINK = "/no think"
CTX_LEN = 2048
STOP_STRINGS = [IM_END, IM_START]
# ---------------------------------------------------------------------------
# Load model + tokenizer once, at module scope, moved eagerly to CUDA so
# ZeroGPU can pack the weights and stream them into VRAM on the first call.
# The model uses the pure-torch attention backend (config:
# attention_backend="torch", torch_fallback=True) so no flash kernels are
# needed at runtime.
# ---------------------------------------------------------------------------
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
dtype=torch.bfloat16,
trust_remote_code=True,
).to("cuda")
model.eval()
# ---------------------------------------------------------------------------
# Sampling helpers — ported 1:1 from the model repo's inference.py so output
# matches the authors' reference path exactly.
# ---------------------------------------------------------------------------
def apply_repetition_penalty(logits, generated_ids, penalty):
if penalty is None or penalty == 1.0:
return logits
for tid in set(generated_ids):
if tid < 0 or tid >= logits.numel():
continue
if logits[tid] > 0:
logits[tid] = logits[tid] / penalty
else:
logits[tid] = logits[tid] * penalty
return logits
def apply_frequency_presence_penalty(logits, generated_ids, frequency_penalty, presence_penalty):
if not generated_ids:
return logits
if frequency_penalty == 0.0 and presence_penalty == 0.0:
return logits
counts: Dict[int, int] = {}
for tid in generated_ids:
counts[tid] = counts.get(tid, 0) + 1
for tid, count in counts.items():
if tid < 0 or tid >= logits.numel():
continue
if frequency_penalty:
logits[tid] -= frequency_penalty * count
if presence_penalty:
logits[tid] -= presence_penalty
return logits
def get_banned_ngram_tokens(generated_ids, no_repeat_ngram_size) -> Set[int]:
n = no_repeat_ngram_size
banned: Set[int] = set()
if n <= 0:
return banned
if len(generated_ids) + 1 < n:
return banned
prefix_len = n - 1
current_prefix = tuple(generated_ids[-prefix_len:])
ngram_map: Dict[tuple, Set[int]] = {}
for i in range(len(generated_ids) - n + 1):
prefix = tuple(generated_ids[i:i + prefix_len])
next_token = generated_ids[i + prefix_len]
ngram_map.setdefault(prefix, set()).add(next_token)
banned.update(ngram_map.get(current_prefix, set()))
return banned
def apply_no_repeat_ngram(logits, generated_ids, no_repeat_ngram_size):
if no_repeat_ngram_size <= 0:
return logits
banned = get_banned_ngram_tokens(generated_ids, no_repeat_ngram_size)
for tid in banned:
if 0 <= tid < logits.numel():
logits[tid] = -float("inf")
return logits
def apply_top_k(logits, top_k):
if top_k is None or top_k <= 0:
return logits
top_k = min(top_k, logits.size(-1))
values, _ = torch.topk(logits, top_k)
cutoff = values[-1]
logits[logits < cutoff] = -float("inf")
return logits
def apply_top_p(logits, top_p):
if top_p is None or top_p >= 1.0:
return logits
if top_p <= 0:
return logits
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
sorted_probs = F.softmax(sorted_logits, dim=-1)
cumulative = torch.cumsum(sorted_probs, dim=-1)
remove = cumulative > top_p
remove[1:] = remove[:-1].clone()
remove[0] = False
indices_to_remove = sorted_indices[remove]
logits[indices_to_remove] = -float("inf")
return logits
def sample_next_token(logits, generated_ids, temperature, top_k, top_p,
repetition_penalty, frequency_penalty, no_repeat_ngram_size):
logits = logits.float().clone()
logits = apply_repetition_penalty(logits, generated_ids, repetition_penalty)
logits = apply_frequency_presence_penalty(logits, generated_ids, frequency_penalty, 0.0)
logits = apply_no_repeat_ngram(logits, generated_ids, no_repeat_ngram_size)
if temperature <= 0:
return int(torch.argmax(logits).item())
logits = logits / temperature
logits = apply_top_k(logits, top_k)
logits = apply_top_p(logits, top_p)
probs = F.softmax(logits, dim=-1)
if torch.isnan(probs).any() or torch.isinf(probs).any() or probs.sum() <= 0:
return int(torch.argmax(logits).item())
return int(torch.multinomial(probs, num_samples=1).item())
def build_stop_sequences(stop_strings) -> List[List[int]]:
out = []
for s in stop_strings:
ids = tokenizer.encode(s, add_special_tokens=False)
if ids:
out.append(ids)
return out
def endswith_sequence(ids, suffix) -> bool:
if not suffix or len(ids) < len(suffix):
return False
return ids[-len(suffix):] == suffix
def strip_after_stop_text(text, stop_strings) -> str:
best = None
for s in stop_strings:
if not s:
continue
pos = text.find(s)
if pos != -1 and (best is None or pos < best):
best = pos
return text if best is None else text[:best]
def build_prompt(expression: str, use_think_format: bool) -> str:
if use_think_format:
return (
f"{IM_START}user\n"
f"{expression} {NO_THINK}"
f"{IM_END}\n"
f"{IM_START}assistant\n"
"<think>\n</think>\n"
)
return expression
@spaces.GPU(duration=30)
def solve(
expression: str,
use_think_format: bool = True,
temperature: float = 0.5,
top_k: int = 40,
top_p: float = 0.95,
max_new_tokens: int = 48,
seed: int = -1,
) -> str:
"""Solve an arithmetic expression with the Arithmetic-SLM model.
Args:
expression: An arithmetic expression ending in '=', e.g. '(10 + 28) * 3 ='.
use_think_format: Use the production [IM_START]/[IM_END] chat template with a '/no think' tag.
temperature: Sampling temperature (lower = more deterministic).
top_k: Top-k sampling cutoff.
top_p: Nucleus (top-p) sampling cutoff.
max_new_tokens: Maximum number of tokens to generate.
seed: RNG seed; -1 for random.
Returns:
The model's completion of the expression (typically the solved result).
"""
expression = (expression or "").strip()
if not expression:
return "Please enter an arithmetic expression, e.g. '(10 + 28) * 3 ='."
if seed is not None and int(seed) >= 0:
random.seed(int(seed))
torch.manual_seed(int(seed))
if torch.cuda.is_available():
torch.cuda.manual_seed_all(int(seed))
repetition_penalty = 1.05
frequency_penalty = 0.10
no_repeat_ngram_size = 4
min_new_tokens = 1
prompt = build_prompt(expression, use_think_format)
encoded = tokenizer(prompt, return_tensors="pt", add_special_tokens=False)
encoded.pop("token_type_ids", None)
idx = encoded["input_ids"].to("cuda")
stop_sequences = build_stop_sequences(STOP_STRINGS)
eos_id = tokenizer.eos_token_id
generated: List[int] = []
with torch.no_grad():
for step in range(int(max_new_tokens)):
idx_cond = idx[:, -CTX_LEN:]
out = model(input_ids=idx_cond)
logits = out.logits[:, -1, :][0]
if step < min_new_tokens:
if eos_id is not None and 0 <= eos_id < logits.numel():
logits[eos_id] = -float("inf")
for seq in stop_sequences:
if len(seq) == 1 and 0 <= seq[0] < logits.numel():
logits[seq[0]] = -float("inf")
next_id = sample_next_token(
logits, generated, float(temperature), int(top_k), float(top_p),
repetition_penalty, frequency_penalty, no_repeat_ngram_size,
)
idx = torch.cat(
[idx, torch.tensor([[next_id]], dtype=torch.long, device=idx.device)], dim=1
)
generated.append(next_id)
if step >= min_new_tokens:
if eos_id is not None and next_id == eos_id:
break
full_ids = idx[0].tolist()
if any(endswith_sequence(full_ids, seq) for seq in stop_sequences):
break
full_text = tokenizer.decode(idx[0].tolist(), skip_special_tokens=False)
if use_think_format:
# Show the completion after the prompt, cleaned of control markers.
if full_text.startswith(prompt):
completion = full_text[len(prompt):]
else:
pos = full_text.rfind(prompt)
completion = full_text[pos + len(prompt):] if pos != -1 else full_text
completion = strip_after_stop_text(completion, STOP_STRINGS)
return completion.strip()
# Raw mode: return the full continued expression, matching the reference
# inference script's behavior exactly (strip only at [IM_END]/[IM_START]).
completion = strip_after_stop_text(full_text, STOP_STRINGS)
return completion.strip()
CSS = """
#col-container { max-width: 820px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
with gr.Column(elem_id="col-container"):
gr.Markdown(
"""
# 🧮 Arithmetic-SLM
A tiny (31.7M parameter) specialized language model that **completes arithmetic
expressions** — it learned to do math token by token, not with a calculator.
Handles operator precedence, parentheses, and decimals.
Enter an expression ending in `=` and let the model finish it.
[Model card](https://huggingface.co/WhirlwindAI/Arithmetic-SLM)
"""
)
with gr.Row():
expression = gr.Textbox(
label="Arithmetic expression",
placeholder="(10 + 28) * 3 =",
value="(10 + 28) * 3 =",
scale=4,
)
run = gr.Button("Solve", variant="primary", scale=1)
output = gr.Textbox(label="Model output", lines=3)
with gr.Accordion("Advanced settings", open=False):
use_think_format = gr.Checkbox(
label="Use production /no think chat template (recommended)",
value=True,
info=(
"Wraps the input in the [IM_START]/[IM_END] template with a "
"<think> block — the format the model was trained for, and "
"the most reliable. Uncheck for raw free-continuation mode."
),
)
temperature = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="Temperature")
top_k = gr.Slider(0, 100, value=40, step=1, label="Top-k")
top_p = gr.Slider(0.1, 1.0, value=0.95, step=0.01, label="Top-p")
max_new_tokens = gr.Slider(8, 128, value=48, step=1, label="Max new tokens")
seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
gr.Examples(
examples=[
["59 + 45 ="],
["16 + 4 * 3 ="],
["(16 / 4) + 44 ="],
["3 * 9 + 12 / 1 ="],
["(132 / 12) + (46 - 15) ="],
["0.5 * 0.5 ="],
["8 * 5 + 4 / 4 ="],
["(85 - 45) + 56 ="],
],
inputs=[expression],
outputs=output,
fn=solve,
cache_examples=True,
cache_mode="lazy",
)
inputs = [expression, use_think_format, temperature, top_k, top_p, max_new_tokens, seed]
run.click(solve, inputs=inputs, outputs=output, api_name="solve")
expression.submit(solve, inputs=inputs, outputs=output, api_name=False)
if __name__ == "__main__":
demo.launch(mcp_server=True)
|