加载中…
加载中…
A domain-specific small language model for step-by-step math problem solving, built by team03 (SLM Learners) for the Pramana SLM++ Bootcamp Round 2 submission.
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
model = AutoModelForCausalLM.from_pretrained(
"singhmandavi/math-slm-qwen2.5-0.5b-v4", torch_dtype=torch.float16)
tok = AutoTokenizer.from_pretrained("singhmandavi/math-slm-qwen2.5-0.5b-v4")
msgs = [{"role": "user", "content": "What is 17 * 23? Solve step by step."}]
text = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
enc = tok(text, return_tensors="pt")
out = model.generate(**enc, max_new_tokens=1024, temperature=0,
pad_token_id=tok.eos_token_id)
print(tok.decode(out[0][enc["input_ids"].shape[1]:], skip_special_tokens=True))
For an OpenAI-compatible endpoint, serve with serve_hf.py (stdlib + transformers only, no Ollama needed).
A Q8_0 GGUF quantization is included for CPU inference and llama.cpp-based runtimes (no GPU required). Tested on CPU with llama.cpp and imported into Ollama.
# Ollama (uses the bundled Modelfile.v4)
ollama create math-slm-v4 -f Modelfile.v4
ollama run math-slm-v4 "What is 17 * 23?"
# llama.cpp
./llama-cli -m math-slm-v4-q8_0.gguf -p "What is 17 * 23?" --temp 0 --top-k 1
Quantization is Q8_0 (8-bit) — effectively lossless for this 0.5B model relative to the fp16 safetensors. Max context 2,048 tokens, matching the training length.
| Item | Value |
|---|---|
| Corpus | train_all.jsonl — 29,853 rows (14,308 direct + 15,545 CoT) |
| Effective train / val | 23,532 / 471 (after --drop-overlong, 5,850 CoT rows ≥2048 tokens dropped) |
| Sources | ExamBench (169Pi/exambench, Apache-2.0, commit b5f3d8b) + MathNet IMO split (CC-BY-4.0) |
| Optimizer | AdamW (betas 0.9/0.999, eps 1e-8, wd 0.01) |
| LR | 1.5e-4, cosine decay, ~132 warmup steps (3% of total) |
| Effective batch | 16 (2 micro-batch × 8 grad-accum) |
| Epochs / steps | 3 / 4,410 logged (theoretical total 4,413 = 1,471/epoch × 3; last multiple of 5 logged) |
| Max seq len | 2,048 |
| LoRA | r=16, alpha=32, dropout 0.05, all proj targets (q/k/v/o/gate/up/down); 8.8M trainable params (1.75%) |
| Hardware | 1× NVIDIA H100 80GB, ~1.84 GPU-hours |
| Seed | 7 |
| Tokens seen | 53,356,887 (last logged, step 4,410) |
Precision note: training ran in bf16 compute (QLoRA 4-bit NF4 base), but the merged checkpoint uploaded here is float16 (the merge step reloads the base in fp16).
pull_data.py; licenses verified through the HF API on 2026-09-05 and recorded in data_manifest.md.build_heldout_eval.py from raw ExamBench rows never used in training, with a final overlap check that fails loudly on any contamination.Level 1 — training health (eval/results_level1_v4.json)
| Check | Result |
|---|---|
| Numerical stability | PASS |
| Undertraining | PASS (loss reduction 19.7%) |
| Overfitting | OK (final gap 0.14, stable) |
| Gradient explosion | OK |
| Convergence | FAIL (marginal — loss trend ticks up in the final 10% of steps; consistent with cosine-decay/batch noise rather than real instability, since all other checks are healthy) |
Level 2 — general capability / instruction following (eval/results_level2_v4.json)
Level 3 — domain knowledge (eval/results_level3_v4.json)
Run post-hoc against the merged checkpoint served over HTTP (serve_hf.py +
evaluate.py --level 3), against the 126-row held-out set (eval/heldout_eval.jsonl,
SHA-256 d2ac7ef3…).
| Metric | Result |
|---|---|
| Breadth | 0.500 (threshold 0.600) |
| Depth | 0.570 |
| Difficulty-weighted | 0.500 |
| L3 composite | 0.521 |
Official composite: L3 = 0.30×Breadth + 0.30×Depth + 0.40×DifficultyWeighted
= 0.30×0.500 + 0.30×0.570 + 0.40×0.500 = 0.521.
| Sub-domain | Depth-weighted | Adequate (≥0.6) |
|---|---|---|
| Algebra | 0.625 | Yes |
| Calculus | 0.675 | Yes |
| Combinatorics / General | 0.531 | No |
| Trigonometry / Geometry | 0.450 | No |
| Difficulty | Score |
|---|---|
| Easy | 0.219 |
| Medium | 0.500 |
| Hard | 0.781 |
Caveat: the 126-row held-out set is unevenly distributed across sub-domains and difficulty (combinatorics alone holds 113 of 126 rows; several algebra/calculus/trig buckets have n=1–2), so per-bucket scores are noisy. The easy (0.219) < hard (0.781) inversion is consistent with that noise plus the verbose-CoT bias — short-answer easy prompts get a rambling preamble — rather than a genuine capability ordering.
logs/training_log.jsonl (official L1 schema: step, train_loss, val_loss, lr, grad_norm, tokens_seen)logs/training_events.jsonleval/results_level1_v4.json, eval/results_level2_v4.json, eval/results_level3_v4.jsoneval/heldout_eval.jsonl (126 rows, SHA-256 d2ac7ef3…)reproducibility/dependency_versions.txt (211 packages, pip freeze from the GPU node)eb4fecba0468286afe3e4960c8680c45f87e46d414941d0614dfe2f5ad518be5The uploaded artifact is the merged final checkpoint (weights + tokenizer + configs). The best/ LoRA adapter referenced by the training run was not uploaded — unnecessary for a merged model, and it is not claimed to be present.
Base model Qwen2.5-0.5B-Instruct is Apache-2.0. Training data: ExamBench (Apache-2.0) and MathNet (CC-BY-4.0, attribution required).