SnapJudge
Non-autoregressive System-1 decision model for games. Give it a game state (tic-tac-toe board, snake grid, or temple-run obstacle as text or JSON) and typed questions; it returns typed answers with calibrated probabilities in a single forward pass on T4 (see Speed below). Inspired by convaiinnovations/laya: same architecture family (encoder + option-marker scorer + act head) and same training principle (RLCD — reinforcement learning against strictly proper scoring rules, so honest probabilities maximise reward). It never generates text, so there is nothing to parse and nothing to hallucinate.
Quickstart
pip install torch transformers safetensors huggingface_hub numpy
git clone https://huggingface.co/Brutalsky111/Snapjudge
export PYTHONPATH=$PYTHONPATH:$(pwd)/Snapjudge # repo root (provides `snapjudge/` package)
from huggingface_hub import snapshot_download
from snapjudge.agent import load
from snapjudge.router import GameRouter
from snapjudge.data_gen import questions_for
local = snapshot_download("Brutalsky111/Snapjudge") # or git-clone path ./Snapjudge
router = GameRouter(agent=load(local))
# 1. tic-tac-toe: X can win immediately with cell2
state = {"game": "tictactoe",
"board": ["X", "X", " ", "O", "O", " ", " ", " ", " "],
"player": "X", "board_str": "XX.OO...."}
res = router.predict(state, questions_for("tictactoe"))
print(res["answers"]["next_move"]["choice"]) # -> cell2
print(res["routing"]) # -> {'model': 'joint', 'reason': 'game=tictactoe ...'}
# 2. snake: head (5,5), food to the right
res = router.predict(
{"game": "snake", "head": [5, 5], "body": [[5, 5], [5, 6]], "food": [7, 5], "grid": [10, 10]},
questions_for("snake"))
print(res["answers"]["direction"]["choice"]) # -> right
# 3. temple-run: gap, near, fast -> jump, urgency 2.0, crash imminent
res = router.predict(
{"game": "templerun", "obstacle": "gap", "lane": "mid", "distance": "near", "speed": "fast"},
questions_for("templerun"))
print(res["answers"]["action"]["choice"]) # -> jump
Every result carries routing metadata (res["routing"]) explaining which game was detected and why.
Decision primitives
| Primitive | Output | Game use |
|---|---|---|
choice |
Top label + per-option probs + confidence | next_move (9 cells), direction (4), action (5) |
score |
Expected ordinal level + distribution | danger / risk / urgency (3 levels each) |
noul |
Calibrated P(true) | must_block, will_die, game_over_soon |
Architecture
- Backbone: ModernBERT-base (149M, bidirectional, fully fine-tuned) + decision head trained from scratch: 2 transformer layers, option-marker scorer, act head. ~165M total.
- Option markers: every option is scored at its own
[MASK]token, softmaxed per question. New schemas need no retraining. - Format:
[CLS] <type> instructions [SEP] [MASK] opt0 [MASK] opt1 … [SEP] state [SEP]. - Budget: 256 tokens (
head_max_len = 128); all questions in one call answered in one forward pass.
Training (RLCD, like Laya)
- Supervised CE pretrain (3 epochs) on 5,400 synthetic games (16,200 typed decisions) with exact-solver labels: minimax (tic-tac-toe), collision + Manhattan-to-food (snake), obstacle table (temple-run).
- RLCD fine-tune (2 epochs): zero-mean Gaussian noise on logits (exploration), reward = log score + 0.5·spherical + RPS for ordinal questions; direct proper-score ascent with CE anchor.
- Calibration: one temperature per (question type, option-count bucket) refit on held-out data.
Benchmarks (held-out 600 games / 1,800 decisions, T4)
| Game | Accuracy (strict) | Fresh re-check, tie-aware* |
|---|---|---|
| temple-run (action/urgency/over) | 1.000 | 1.000 |
| snake (direction/risk/trapped) | 0.939 | 0.998 |
| tic-tac-toe (move/danger/block) | 0.772 | 0.848 |
| overall | 0.905, ECE 0.049 | 0.949, ECE 0.096 |
* Strict = argmax must equal the single stored label. Tie-aware = any optimal move counts (9-way tic-tac-toe and tied snake positions share target mass across all minimax-optimal moves). Strict choice acc on fresh seeds is 0.79; tie-aware is 0.95. Chart above shows seed 999/123.
Training curve: 0.824 → 0.862 → 0.876 (CE) → 0.882 → 0.905 (RLCD); ECE 0.060 → 0.042 after first RLCD epoch.
Speed (Tesla T4, fp16)
| Call | Latency |
|---|---|
| 3 questions, one state (warm, T4 fp16) | ~36–38 ms (p50) |
| 10 states batched (30 questions, one forward pass) | |
| First call (cold, CUDA warmup) | ~700 ms, one-time |
Reproduce: python3 bench.py --model . --n 200 --seed 999 (see bench.py).
Batch, validation, server, play
# batch: argmax-identical to predict(), probs within fp16 rounding
outs = router._agents["joint"].predict_batch([s1, s2, s3], [q1, q2, q3])
outs = router.predict_batch([{"state": s1, "questions": q1}, {"state": s2, "questions": q2}])
Malformed questions raise ValueError naming the question (unknown type, empty
instructions, choice with <2 options, score criteria not a list, noul keys not
true/false). Serve: python3 serve.py --model . --port 8000 →
POST /v1/systemone {state, questions}. Play: python3 play_ttt.py --human O.
Honest limits
- Labels come from exact solvers on synthetic positions — human play distributions will differ; fine-tune on your own logs before trusting deployment.
- 9-way tic-tac-toe choice (0.77) is the weakest head; narrow-board endgames dominate errors.
- Single joint checkpoint for all three games; no per-game experts yet (
GameRoutersupports attaching them). - Probabilities are temperature-fitted on synthetic held-out data — refit on your domain before using confidence gating.
Files
README.md # this model card
snapjudge_perf.png # performance chart (see Benchmarks)
snapjudge_config.json # encoder, head, context budgets, temperatures
model.safetensors # 656 MB weights
tokenizer/ # ModernBERT tokenizer snapshot
metrics.json # held-out metrics (original split)
snapjudge/ # runtime: common.py, agent.py, router.py, data_gen.py, train.py
example.py # runnable quickstart
bench.py # fresh-data accuracy/ECE/latency check
serve.py # stdlib POST /v1/systemone server
play_ttt.py # human vs model tic-tac-toe
make_chart.py # regenerates snapjudge_perf.png
requirements.txt
License & credit
Apache 2.0. Architecture and RLCD methodology inspired by Laya (Convai Innovations, Apache 2.0). Backbone: ModernBERT-base.
Model tree for Seedyai/Snapjudge
Base model
answerdotai/ModernBERT-base