Model family and roles
The one trained behavior
Given a chess position, the student's rating tier (beginner ~1000-1200, intermediate ~1300-1600, advanced ~1700-2000), the move the student played, and engine grounding (a Stockfish sound-move pool with short lines plus Maia human-move likelihoods at that tier), the model's single trained job is to choose the tier-appropriate instructive move and render it as move - short principle. That choice is graded deterministically, with no model judge, on three clauses a stranger with an engine and a human-move model can check:
- sound — not a blunder (engine cp-loss below 250);
- tier-appropriate — matches the canonical tier move (the most human-findable sound move for a beginner, the engine's sharpest line for an advanced player);
- distinct across levels — a beginner and an advanced player are not handed the same move on a differentiating position.
The English explanation ("prose") is not the trained behavior. It is an optional display layer that can be rendered by engine templates or a frontier model and is verified separately by a non-LLM checker. See Limitations.
Results (base vs. tuned)
Held-out validation slice: 120 positions x 3 tiers. Grounding is held byte-identical between rows, so the only thing that changes between base and tuned is the weights. Move selection is scored deterministically against the canonical tier move, using the STRICT any-legal move extractor (accept any clearly-named legal move; no in-pool backfill) — the single canonical parser shared with the grand eval.
Table with columns: Contender (same grounding), tier-fit ↑, coherence violation ↓, distinct moves / level ↑| Contender (same grounding) | tier-fit ↑ | coherence violation ↓ | distinct moves / level ↑ |
|---|
| Qwen3-32B base (untuned) | 0.347 | 0.500 | 0.290 |
| v4 (this adapter) | 0.77 | 0.140 | 0.73 |
| best frontier on tier-fit (Gemini 3.1 Pro) | 0.553 | 0.292 | 0.210 |
distinct-moves-per-level = 73/100 canonical beginner!=advanced opportunities (a no-answer counts as a miss); the grand-eval table's 0.785 uses the 73/93 denominator = only the positions where v4 named both tier moves.
Note: "tier-fit" = "tier-policy exact match" (exact agreement with the preregistered select_tier_move rule, a project rule, not validated pedagogy).
Moat (head-to-head vs. the best frontier). Of the 120 positions, v4 gives a distinct, sound, correctly-graded per-tier move on 67, and of those it diverges from the best frontier's move on 62. On those 62 diverging positions v4 wins 51, loses 5, ties 6 on the moat (tier-fit then soundness) against the best frontier at each position. That 51-5-6 is a selection-conditioned subset (it conditions on v4 first giving a distinct, sound, correctly-graded move), so it overstates a raw win rate; the honest unbiased, reproducible head-to-head over all 92 positions where v4 diverges from the best frontier is 56-24-12 (56-24-40 over all 120; recomputed from the committed raw/greedy gens by scripts/reproduce_v4.py), and the primary unbiased comparison is the all-scenario tier-fit 0.767 vs 0.553. The frontier loses this axis because it hands the engine's single best move to every level, repeating one move across the three tiers about 77% of the time.
The result is off-consensus: a well-engineered prompt on the same base does not close the tier-fit gap (measured at 1.7B, 4B, and 32B), because tier-appropriate move selection is added by data, not by prompting.
How to use
The adapter files (LoRA weights, tokenizer, chat template) are at the repo root. Load the exact 4-bit base and attach the adapter with PEFT:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
BASE = "unsloth/Qwen3-32B-unsloth-bnb-4bit"
ADAPTER = "khoilamalphaai/chess-coach-32b-v4-qlora"
tokenizer = AutoTokenizer.from_pretrained(ADAPTER)
base = AutoModelForCausalLM.from_pretrained(BASE, device_map="auto", torch_dtype=torch.bfloat16)
model = PeftModel.from_pretrained(base, ADAPTER).eval()
messages = [
{"role": "system", "content": "You are a chess coach doing move review. Recommend one sound, tier-appropriate move and tag it with a short principle."},
{"role": "user", "content": "Student tier: beginner.\n[Engine-grounded prompt: FEN, played move, Stockfish sound-move pool + short lines, Maia likelihoods.]"},
]
prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=512, do_sample=False)
print(tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
Notes:
- The base is a pre-quantized 4-bit checkpoint, so
bitsandbytes is required and no separate BitsAndBytesConfig is needed. You can also load it in one step with unsloth's FastLanguageModel.from_pretrained pointed at this adapter repo.
- The model is trained for the grounded move-review format above. Run it with engine grounding in the prompt; do not expect reliable chess reasoning ungrounded.
Limitations
- Prose is weaker than smaller checkpoints, by design. v4 is the strongest checkpoint on the move and simultaneously the weakest recent one on prose: its blinded instructiveness grade is 4.67 (below the 4B tune at 5.32 and the prior v3 at 6.35), and about 40% of its raw drafts trip the prose faithfulness check before the shipped gate. This is a non-issue for the graded claim, which is the move: render prose with engine templates or a frontier model on top of the chosen move and gate it with the non-LLM verifier.
- Not the local form factor. This 32B adapter is the reference that shows scale buys a still-stronger move (and beats the frontier on the moat). The on-spec production target is a small ~4B local coach; this repo is for the strongest move, not for laptop inference.
- Grounding-dependent. Move truth comes from the engine + tier rule; the adapter is the last-mile chooser. Truthfulness of any rendered prose relies on the inference-time verifier, not the weights.
Links
Training details
- Trained on the
v4 split of khoilamalphaai/chess-coach-move-review: distilled, engine-grounded, tier-contrastive move reviews with a hard faithfulness filter (0% false move labels).
- QLoRA on
unsloth/Qwen3-32B-unsloth-bnb-4bit, r=32, alpha=32, dropout 0.0, LoRA on q,k,v,o,gate,up,down, task type CAUSAL_LM (Unsloth + TRL, PEFT 0.19.1).
- License: Apache-2.0, inherited from Qwen3-32B.