Text Generation
Transformers
Safetensors
Arabic
llama
arabic
reasoning
chain-of-thought
math
gsm8k
small-language-model
slm
sft
conversational
text-generation-inference
Instructions to use oddadmix/Nawah-Math-Reasoning with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use oddadmix/Nawah-Math-Reasoning with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="oddadmix/Nawah-Math-Reasoning") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("oddadmix/Nawah-Math-Reasoning") model = AutoModelForCausalLM.from_pretrained("oddadmix/Nawah-Math-Reasoning", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use oddadmix/Nawah-Math-Reasoning with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "oddadmix/Nawah-Math-Reasoning" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "oddadmix/Nawah-Math-Reasoning", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/oddadmix/Nawah-Math-Reasoning
- SGLang
How to use oddadmix/Nawah-Math-Reasoning 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 "oddadmix/Nawah-Math-Reasoning" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "oddadmix/Nawah-Math-Reasoning", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'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 "oddadmix/Nawah-Math-Reasoning" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "oddadmix/Nawah-Math-Reasoning", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use oddadmix/Nawah-Math-Reasoning with Docker Model Runner:
docker model run hf.co/oddadmix/Nawah-Math-Reasoning
File size: 6,386 Bytes
867d0f3 | 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 | """
Build the v6 SFT corpus: the v5 three-way mix, with the synthetic corpus swapped for the larger
one that carries the relational pool.
This is `prepare_v5_sft.py` with `SYNTH_DIR` repointed and a fourth eval column added. Separate
file, same reason v5 was separate from v3: v5 is the shipped model and must stay reproducible.
The synth split comes from `split_synth_v6.py`, not `build_synth_dataset.py` — the v6 corpus is
20,139 rows larger, so a re-drawn shuffle would put 1,955 of v5's held-out rows into v6's train.
The eval rows are pinned to v5's instead, which keeps the synth cell comparable and, because
`eval.jsonl` is byte-identical to v5's, makes the mix-eval synth column the *same 400 rows* v5 was
scored on.
`eval_rel.jsonl` (400 relational rows, held out of train) is the column that actually measures
what v6 was built for — none of the legacy eval rows test a relational comparison.
data/{train,eval}.jsonl <- prepare_data.py (Arabic_Reasoning_Dataset)
data_gsm_sft/{train,eval}.jsonl <- prepare_gsm_sft.py (oddadmix/gsm8k-reasoning-ar)
data_synth_v6_sft/{train,eval,eval_rel}.jsonl <- split_synth_v6.py (120,462-row corpus)
Why all three: v3 scores 77.3% on GSM8K-ar but 2.0% on the synth held-out set, and v4 (synth only)
inverts that — 35.6% synth, 19.5% GSM. Neither corpus alone covers the other's distribution, so v5
trains on all three at once.
Answer styles are NOT normalised — GSM8K ends in a bare numeral, Arabic_Reasoning and the synth
corpus in an "إذن، …" sentence. A `source` tag rides on every row so eval_reasoning.py scores each
on its own terms. The eval set keeps v1's 400 AR rows and v2's first 600 GSM rows *unchanged*, so
the AR and GSM cells stay directly comparable to v1/v2/v3.
Writes data_v6_sft/{train,eval}.jsonl.
"""
import json
import os
import random
from collections import Counter
from pathlib import Path
AR_DIR = Path("data") # v1 splits
GSM_DIR = Path("data_gsm_sft") # v2 splits
SYNTH_DIR = Path("data_synth_v6_sft") # the 120,462-row corpus, relational pool included
OUT = Path("data_v6_sft")
# Arabic_Reasoning is ~25x smaller than GSM8K, so it is repeated (same value v3 used).
REPEAT = int(os.environ.get("REPEAT", 3))
SYNTH_REPEAT = int(os.environ.get("SYNTH_REPEAT", 1))
# Eval: v1's 400 AR rows are all kept and v2's first 600 GSM rows, exactly as v3 built them, plus
# synth rows so all three distributions are scored in one pass.
EVAL_GSM = int(os.environ.get("EVAL_GSM", 600))
EVAL_SYNTH = int(os.environ.get("EVAL_SYNTH", 400))
EVAL_REL = int(os.environ.get("EVAL_REL", 400))
SEED = 42
def load(path):
with open(path, encoding="utf-8") as fh:
return [json.loads(line) for line in fh]
def tag(rows, source):
return [{**r, "source": source} for r in rows]
def token_stats(rows, tok):
per_source, lengths = Counter(), []
for r in rows:
text = (f"<|im_start|>user\n{r['instruction']}<|im_end|>\n<|im_start|>assistant\n"
f"<think>\n{r['reasoning']}\n</think>\n{r['answer']}<|im_end|>")
n = len(tok.encode(text, add_special_tokens=False))
per_source[r["source"]] += n
lengths.append(n)
lengths.sort()
pct = lambda p: lengths[min(int(len(lengths) * p / 100), len(lengths) - 1)]
return per_source, {"p50": pct(50), "p95": pct(95), "p99": pct(99), "max": lengths[-1]}
def interleave(groups):
"""Round-robin proportional to each group's size, so a truncated eval run still covers all
three sources instead of whichever landed first."""
groups = [g for g in groups if g]
if not groups:
return []
total = sum(len(g) for g in groups)
out, idx = [], [0] * len(groups)
for _ in range(total):
# pick the group that is furthest behind its target share
pick = min(range(len(groups)), key=lambda i: (idx[i] / len(groups[i])) if idx[i] < len(groups[i]) else 2.0)
if idx[pick] >= len(groups[pick]):
break
out.append(groups[pick][idx[pick]])
idx[pick] += 1
for g, i in zip(groups, idx): # anything the loop could not place
out.extend(g[i:])
return out
def main():
for d in (AR_DIR, GSM_DIR, SYNTH_DIR):
for split in ("train", "eval"):
if not (d / f"{split}.jsonl").exists():
raise SystemExit(f"missing {d/f'{split}.jsonl'}")
ar_train = tag(load(AR_DIR / "train.jsonl"), "arabic_reasoning")
ar_eval = tag(load(AR_DIR / "eval.jsonl"), "arabic_reasoning")
gsm_train = tag(load(GSM_DIR / "train.jsonl"), "gsm8k_ar")
gsm_eval = tag(load(GSM_DIR / "eval.jsonl"), "gsm8k_ar")
synth_train = tag(load(SYNTH_DIR / "train.jsonl"), "synth_math_ar")
synth_eval = tag(load(SYNTH_DIR / "eval.jsonl"), "synth_math_ar")
rel_eval = tag(load(SYNTH_DIR / "eval_rel.jsonl"), "synth_relational_ar")
train = gsm_train + ar_train * REPEAT + synth_train * SYNTH_REPEAT
random.Random(SEED).shuffle(train)
eval_rows = interleave([ar_eval, gsm_eval[:EVAL_GSM], synth_eval[:EVAL_SYNTH],
rel_eval[:EVAL_REL]])
OUT.mkdir(exist_ok=True)
for name, split in (("train", train), ("eval", eval_rows)):
with open(OUT / f"{name}.jsonl", "w", encoding="utf-8") as fh:
for r in split:
fh.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"[+] {name}: {len(split):,} -> {OUT / f'{name}.jsonl'} "
f"{dict(Counter(r['source'] for r in split))}")
try:
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained(os.environ.get("BASE_MODEL", "/notebooks/50M/50M-2048-Emhotob"))
tok.add_special_tokens({"additional_special_tokens": ["<|im_start|>", "<|im_end|>", "<think>", "</think>"]})
per_source, pct = token_stats(train, tok)
total = sum(per_source.values())
print(f"[*] train tokens: {total/1e6:.1f}M/epoch (REPEAT={REPEAT}, SYNTH_REPEAT={SYNTH_REPEAT})")
for src, n in per_source.most_common():
print(f" {src:<18} {n/1e6:6.2f}M {100*n/total:5.1f}%")
print(f"[*] sample length: p50 {pct['p50']} p95 {pct['p95']} p99 {pct['p99']} max {pct['max']}")
except Exception as exc:
print(f"[!] token stats skipped: {exc}")
if __name__ == "__main__":
main()
|