Text Generation
Transformers
Safetensors
English
slm
arithmetic
math
causal-lm
custom_code
Eval Results (legacy)
Instructions to use WhirlwindAI/Arithmetic-SLM with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use WhirlwindAI/Arithmetic-SLM with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="WhirlwindAI/Arithmetic-SLM", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("WhirlwindAI/Arithmetic-SLM", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use WhirlwindAI/Arithmetic-SLM with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "WhirlwindAI/Arithmetic-SLM" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "WhirlwindAI/Arithmetic-SLM", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/WhirlwindAI/Arithmetic-SLM
- SGLang
How to use WhirlwindAI/Arithmetic-SLM with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "WhirlwindAI/Arithmetic-SLM" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "WhirlwindAI/Arithmetic-SLM", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "WhirlwindAI/Arithmetic-SLM" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "WhirlwindAI/Arithmetic-SLM", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use WhirlwindAI/Arithmetic-SLM with Docker Model Runner:
docker model run hf.co/WhirlwindAI/Arithmetic-SLM
| #!/usr/bin/env python3 | |
| import argparse | |
| import random | |
| from typing import Dict, List, Optional, Set | |
| import torch | |
| import torch.nn.functional as F | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| IM_START = "[IM_START]" | |
| IM_END = "[IM_END]" | |
| NO_THINK = "/no think" | |
| # ============================================================ | |
| # UTILS | |
| # ============================================================ | |
| def get_dtype(name: str): | |
| name = str(name).lower() | |
| if name in {"bf16", "bfloat16"}: | |
| return torch.bfloat16 | |
| if name in {"fp16", "float16", "half"}: | |
| return torch.float16 | |
| if name in {"fp32", "float32", "float"}: | |
| return torch.float32 | |
| raise ValueError(f"Unknown dtype: {name}") | |
| def set_seed(seed: int): | |
| if seed is None: | |
| return | |
| if seed < 0: | |
| seed = random.randint(0, 2**31 - 1) | |
| print(f"[INFO] random seed: {seed}") | |
| random.seed(seed) | |
| torch.manual_seed(seed) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(seed) | |
| def build_prompt(args) -> str: | |
| if args.no_think: | |
| return ( | |
| f"{IM_START}user\n" | |
| f"{args.prompt} {NO_THINK}" | |
| f"{IM_END}\n" | |
| f"{IM_START}assistant\n" | |
| "<think>\n</think>\n" | |
| ) | |
| return args.prompt | |
| def decode(tokenizer, ids: List[int]) -> str: | |
| return tokenizer.decode(ids, skip_special_tokens=False) | |
| def extract_completion(full_text: str, prompt: str) -> str: | |
| if full_text.startswith(prompt): | |
| return full_text[len(prompt):] | |
| pos = full_text.rfind(prompt) | |
| if pos != -1: | |
| return full_text[pos + len(prompt):] | |
| return full_text | |
| def strip_after_stop_text(text: str, stop_strings: List[str]) -> str: | |
| best = None | |
| for s in stop_strings: | |
| if not s: | |
| continue | |
| pos = text.find(s) | |
| if pos != -1: | |
| if best is None or pos < best: | |
| best = pos | |
| if best is None: | |
| return text | |
| return text[:best] | |
| def build_stop_sequences(tokenizer, stop_strings: List[str]) -> 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: List[int], suffix: List[int]) -> bool: | |
| if not suffix: | |
| return False | |
| if len(ids) < len(suffix): | |
| return False | |
| return ids[-len(suffix):] == suffix | |
| # ============================================================ | |
| # SAMPLING | |
| # ============================================================ | |
| def apply_repetition_penalty( | |
| logits: torch.Tensor, | |
| generated_ids: List[int], | |
| penalty: float, | |
| ) -> torch.Tensor: | |
| if penalty is None or penalty == 1.0: | |
| return logits | |
| if penalty <= 0: | |
| raise ValueError("--repetition-penalty must be > 0") | |
| 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: torch.Tensor, | |
| generated_ids: List[int], | |
| frequency_penalty: float, | |
| presence_penalty: float, | |
| ) -> torch.Tensor: | |
| 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: List[int], | |
| no_repeat_ngram_size: int, | |
| ) -> Set[int]: | |
| n = no_repeat_ngram_size | |
| banned = 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 = {} | |
| for i in range(len(generated_ids) - n + 1): | |
| prefix = tuple(generated_ids[i:i + prefix_len]) | |
| next_token = generated_ids[i + prefix_len] | |
| if prefix not in ngram_map: | |
| ngram_map[prefix] = set() | |
| ngram_map[prefix].add(next_token) | |
| banned.update(ngram_map.get(current_prefix, set())) | |
| return banned | |
| def apply_no_repeat_ngram( | |
| logits: torch.Tensor, | |
| generated_ids: List[int], | |
| no_repeat_ngram_size: int, | |
| ) -> torch.Tensor: | |
| if no_repeat_ngram_size <= 0: | |
| return logits | |
| banned = get_banned_ngram_tokens( | |
| generated_ids=generated_ids, | |
| no_repeat_ngram_size=no_repeat_ngram_size, | |
| ) | |
| for tid in banned: | |
| if 0 <= tid < logits.numel(): | |
| logits[tid] = -float("inf") | |
| return logits | |
| def apply_top_k(logits: torch.Tensor, top_k: int) -> torch.Tensor: | |
| 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: torch.Tensor, top_p: float) -> torch.Tensor: | |
| if top_p is None or top_p >= 1.0: | |
| return logits | |
| if top_p <= 0: | |
| raise ValueError("--top-p must be > 0") | |
| 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 apply_min_p(logits: torch.Tensor, min_p: float) -> torch.Tensor: | |
| if min_p is None or min_p <= 0: | |
| return logits | |
| probs = F.softmax(logits, dim=-1) | |
| max_prob = torch.max(probs) | |
| keep = probs >= (min_p * max_prob) | |
| logits[~keep] = -float("inf") | |
| return logits | |
| def apply_typical_p(logits: torch.Tensor, typical_p: float) -> torch.Tensor: | |
| if typical_p is None or typical_p >= 1.0: | |
| return logits | |
| if typical_p <= 0: | |
| raise ValueError("--typical-p must be > 0") | |
| probs = F.softmax(logits, dim=-1) | |
| log_probs = F.log_softmax(logits, dim=-1) | |
| entropy = -(probs * log_probs).sum() | |
| shifted_scores = torch.abs((-log_probs) - entropy) | |
| sorted_scores, sorted_indices = torch.sort(shifted_scores, descending=False) | |
| sorted_probs = probs[sorted_indices] | |
| cumulative_probs = torch.cumsum(sorted_probs, dim=-1) | |
| remove = cumulative_probs > typical_p | |
| remove[1:] = remove[:-1].clone() | |
| remove[0] = False | |
| indices_to_remove = sorted_indices[remove] | |
| logits[indices_to_remove] = -float("inf") | |
| return logits | |
| def apply_bad_words( | |
| logits: torch.Tensor, | |
| tokenizer, | |
| bad_words: List[str], | |
| ): | |
| for word in bad_words: | |
| ids = tokenizer.encode(word, add_special_tokens=False) | |
| if len(ids) == 1: | |
| tid = ids[0] | |
| if 0 <= tid < logits.numel(): | |
| logits[tid] = -float("inf") | |
| return logits | |
| def sample_next_token( | |
| logits: torch.Tensor, | |
| generated_ids: List[int], | |
| tokenizer, | |
| args, | |
| ) -> int: | |
| logits = logits.float().clone() | |
| logits = apply_bad_words( | |
| logits=logits, | |
| tokenizer=tokenizer, | |
| bad_words=args.bad_words, | |
| ) | |
| logits = apply_repetition_penalty( | |
| logits=logits, | |
| generated_ids=generated_ids, | |
| penalty=args.repetition_penalty, | |
| ) | |
| logits = apply_frequency_presence_penalty( | |
| logits=logits, | |
| generated_ids=generated_ids, | |
| frequency_penalty=args.frequency_penalty, | |
| presence_penalty=args.presence_penalty, | |
| ) | |
| logits = apply_no_repeat_ngram( | |
| logits=logits, | |
| generated_ids=generated_ids, | |
| no_repeat_ngram_size=args.no_repeat_ngram_size, | |
| ) | |
| if args.temperature <= 0: | |
| return int(torch.argmax(logits).item()) | |
| logits = logits / args.temperature | |
| logits = apply_top_k(logits, args.top_k) | |
| logits = apply_top_p(logits, args.top_p) | |
| logits = apply_min_p(logits, args.min_p) | |
| logits = apply_typical_p(logits, args.typical_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()) | |
| # ============================================================ | |
| # MODEL | |
| # ============================================================ | |
| def model_forward_logits(model, input_ids: torch.Tensor): | |
| out = model(input_ids=input_ids) | |
| if hasattr(out, "logits"): | |
| return out.logits | |
| if isinstance(out, tuple): | |
| return out[0] | |
| raise RuntimeError("Impossible de récupérer logits depuis la sortie du modèle.") | |
| def generate_manual( | |
| model, | |
| tokenizer, | |
| input_ids: torch.Tensor, | |
| args, | |
| ) -> torch.Tensor: | |
| idx = input_ids | |
| generated_after_prompt: List[int] = [] | |
| stop_sequences = build_stop_sequences( | |
| tokenizer, | |
| stop_strings=args.stop_strings, | |
| ) | |
| eos_id = tokenizer.eos_token_id | |
| for step in range(args.max_new_tokens): | |
| idx_cond = idx[:, -args.ctx_len:] if args.ctx_len > 0 else idx | |
| logits = model_forward_logits(model, idx_cond) | |
| logits = logits[:, -1, :][0] | |
| if step < args.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: | |
| tid = seq[0] | |
| if 0 <= tid < logits.numel(): | |
| logits[tid] = -float("inf") | |
| next_id = sample_next_token( | |
| logits=logits, | |
| generated_ids=generated_after_prompt, | |
| tokenizer=tokenizer, | |
| args=args, | |
| ) | |
| next_tensor = torch.tensor( | |
| [[next_id]], | |
| dtype=torch.long, | |
| device=idx.device, | |
| ) | |
| idx = torch.cat([idx, next_tensor], dim=1) | |
| generated_after_prompt.append(next_id) | |
| full_ids = idx[0].tolist() | |
| if step >= args.min_new_tokens: | |
| if eos_id is not None and next_id == eos_id: | |
| break | |
| should_stop = False | |
| for seq in stop_sequences: | |
| if endswith_sequence(full_ids, seq): | |
| should_stop = True | |
| break | |
| if should_stop: | |
| break | |
| return idx | |
| # ============================================================ | |
| # CLI | |
| # ============================================================ | |
| def parse_args(): | |
| p = argparse.ArgumentParser( | |
| description="Inference script for Arithmetic-SLM using [IM_START]/[IM_END]." | |
| ) | |
| p.add_argument( | |
| "--model", | |
| default="PhysiQuanty/Arithmetic-SLM", | |
| help="HF model id or local path.", | |
| ) | |
| p.add_argument( | |
| "--prompt", | |
| default="59 + 45 =", | |
| help="Raw arithmetic prompt. With --no-think, inserted in chat template.", | |
| ) | |
| p.add_argument( | |
| "--no-think", | |
| action="store_true", | |
| help="Use production no-think template with [IM_START]/[IM_END].", | |
| ) | |
| p.add_argument( | |
| "--device", | |
| default="cuda" if torch.cuda.is_available() else "cpu", | |
| ) | |
| p.add_argument( | |
| "--dtype", | |
| default="bfloat16", | |
| choices=["bfloat16", "bf16", "float16", "fp16", "float32", "fp32"], | |
| ) | |
| p.add_argument("--ctx-len", type=int, default=2048) | |
| p.add_argument("--max-new-tokens", type=int, default=64) | |
| p.add_argument("--min-new-tokens", type=int, default=1) | |
| p.add_argument("--temperature", type=float, default=0.7) | |
| p.add_argument("--top-k", type=int, default=40) | |
| p.add_argument("--top-p", type=float, default=0.90) | |
| p.add_argument("--min-p", type=float, default=0.0) | |
| p.add_argument("--typical-p", type=float, default=1.0) | |
| p.add_argument("--repetition-penalty", type=float, default=1.05) | |
| p.add_argument("--frequency-penalty", type=float, default=0.10) | |
| p.add_argument("--presence-penalty", type=float, default=0.0) | |
| p.add_argument("--no-repeat-ngram-size", type=int, default=4) | |
| p.add_argument("--seed", type=int, default=-1) | |
| p.add_argument( | |
| "--stop-string", | |
| action="append", | |
| default=None, | |
| help="Additional stop string. Can be passed multiple times.", | |
| ) | |
| p.add_argument( | |
| "--bad-word", | |
| action="append", | |
| default=None, | |
| help="Single-token word/token to ban. Can be passed multiple times.", | |
| ) | |
| p.add_argument( | |
| "--print-full", | |
| action="store_true", | |
| help="Print full prompt + completion.", | |
| ) | |
| p.add_argument( | |
| "--trust-remote-code", | |
| action="store_true", | |
| default=True, | |
| ) | |
| args = p.parse_args() | |
| if args.max_new_tokens <= 0: | |
| raise ValueError("--max-new-tokens must be > 0") | |
| if args.min_new_tokens < 0: | |
| raise ValueError("--min-new-tokens must be >= 0") | |
| if args.temperature < 0: | |
| raise ValueError("--temperature must be >= 0") | |
| if args.top_k < 0: | |
| raise ValueError("--top-k must be >= 0") | |
| if not (0 < args.top_p <= 1.0): | |
| raise ValueError("--top-p must be in (0, 1]") | |
| if args.min_p < 0: | |
| raise ValueError("--min-p must be >= 0") | |
| if not (0 < args.typical_p <= 1.0): | |
| raise ValueError("--typical-p must be in (0, 1]") | |
| if args.repetition_penalty <= 0: | |
| raise ValueError("--repetition-penalty must be > 0") | |
| if args.no_repeat_ngram_size < 0: | |
| raise ValueError("--no-repeat-ngram-size must be >= 0") | |
| stop_strings = [ | |
| IM_END, | |
| IM_START, | |
| ] | |
| if args.stop_string: | |
| stop_strings.extend(args.stop_string) | |
| args.stop_strings = stop_strings | |
| bad_words = [] | |
| if args.bad_word: | |
| bad_words.extend(args.bad_word) | |
| args.bad_words = bad_words | |
| return args | |
| def main(): | |
| args = parse_args() | |
| set_seed(args.seed) | |
| dtype = get_dtype(args.dtype) | |
| print(f"[INFO] model: {args.model}") | |
| print(f"[INFO] device: {args.device}") | |
| print(f"[INFO] dtype: {args.dtype}") | |
| print(f"[INFO] template: {'no_think' if args.no_think else 'raw'}") | |
| print(f"[INFO] IM_START: {IM_START}") | |
| print(f"[INFO] IM_END: {IM_END}") | |
| print(f"[INFO] NO_THINK: {NO_THINK}") | |
| print(f"[INFO] temperature: {args.temperature}") | |
| print(f"[INFO] top_k: {args.top_k}") | |
| print(f"[INFO] top_p: {args.top_p}") | |
| print(f"[INFO] min_p: {args.min_p}") | |
| print(f"[INFO] typical_p: {args.typical_p}") | |
| print() | |
| tokenizer = AutoTokenizer.from_pretrained( | |
| args.model, | |
| trust_remote_code=args.trust_remote_code, | |
| ) | |
| model = AutoModelForCausalLM.from_pretrained( | |
| args.model, | |
| dtype=dtype, | |
| trust_remote_code=args.trust_remote_code, | |
| ).to(args.device) | |
| model.eval() | |
| prompt = build_prompt(args) | |
| encoded = tokenizer( | |
| prompt, | |
| return_tensors="pt", | |
| add_special_tokens=False, | |
| ) | |
| encoded.pop("token_type_ids", None) | |
| input_ids = encoded["input_ids"].to(args.device) | |
| output_ids = generate_manual( | |
| model=model, | |
| tokenizer=tokenizer, | |
| input_ids=input_ids, | |
| args=args, | |
| ) | |
| full_text = decode(tokenizer, output_ids[0].tolist()) | |
| if args.print_full: | |
| print(full_text) | |
| return | |
| completion = extract_completion(full_text, prompt) | |
| completion = strip_after_stop_text(completion, args.stop_strings) | |
| print(completion) | |
| if __name__ == "__main__": | |
| main() | |