Results on test2000
2,000 held-out Lichess puzzles, split by game, stratified by rating (400–2799). Greedy decoding, one
answer per solver position, the legal moves listed in the prompt. A puzzle is solved when every
solver move is right. 95% bootstrap CIs over puzzles. Puzzle rating = rating at 50% solve.
Table with columns: Model, Solve rate, Move accuracy, Legal, Puzzle rating| Model | Solve rate | Move accuracy | Legal | Puzzle rating |
|---|
| Qwen3.5-2B base, zero-shot | 4.1% [3.2, 5.0] | 15.6% | 96.4% | below range |
| base → GRPO dense (no primer) | 25.8% [23.9, 27.8] | 43.2% | 100.0% | 973 |
| base → GRPO sparse (no primer) | 28.0% [26.1, 30.0] | 44.3% | 99.9% | 1038 |
| primer-M + T8 (SFT only) | 35.7% [33.6, 37.9] | 55.8% | 100.0% | 1237 |
| primer-M + T8 → GRPO sparse | 35.4% [33.4, 37.6] | 56.0% | 99.8% | 1237 |
| primer-M + T8 → GRPO dense (target 0.14) | 34.8% [32.6, 36.9] | 55.6% | 100.0% | 1214 |
| Stockfish 19, 1 node (reference) | 68.6% [66.6, 70.6] | 83.6% | 100.0% | 2109 |
What the project found (paired differences, docs/RESULTS.md):
- The primer raises the post-RL solve rate: primer → GRPO beats GRPO from the base model by
+7.4 points [+5.8, +9.2] (sparse) and +6.8 [+5.2, +8.4] (dense).
- That gain is the SFT's. GRPO on top of primer-M added nothing: −0.2 [−1.6, +1.1] (sparse) and
−0.9 [−2.3, +0.4] (dense, target 0.14) vs the primer alone.
- A rules-only primer (board reading, no move choice) did not help (+0.4 [−1.0, +1.8] after RL).
- Without the legal-move list in the prompt, these models collapse (primer → GRPO dense: 1.9%).
Browse every answer: the puzzle browser.
Training
GRPO (TRL, vLLM colocate) from the primer-M + T8 model above: LoRA r=32 (alpha 64) on the language model's linear layers, merged after training. 200 steps × 64 prompts × 8 samples from 66,008 Lichess puzzle positions (rl_train), lr 1e-5, β = 1e-3, DAPO loss, temperature 1.0, generation stopped at the first </answer>, adaptive entropy bonus with target 0.2 (config v3). Reward: 1 if the move is the puzzle's move (any mate on the last move), else 0, plus a format reward.
Hardware: one spot A100 80GB on Google Cloud. Configs, job scripts and the cost ledger are in the
repository. The whole project (every run, eval and failed attempt) cost under $200 of
cloud credits.
The model was trained and evaluated with exactly this prompt, rendered with the Qwen3.5 chat
template and enable_thinking=False (the template pre-fills an empty think block, and the model
answers directly in about ten tokens):
[system]You are a strong chess player solving a puzzle. [user]Position (FEN): r1b3k1/pp3ppp/8/2p1r3/3p4/1BP2Q2/PPK2PP1/RNB2q2 w - - 0 19Side to move: WhiteLegal moves: Ba4, Bc4, Bd2, Bd5, Be3, Be6, Bf4, Bg5, Bh6, Bxf7+, Kd2, Na3, Nd2, Qc6, Qd1, Qd3, Qd5, Qe2, Qe3, Qe4, Qf4, Qf5, Qf6, Qg3, Qg4, Qh3, Qh5, Qxb7, Qxf7+, a3, a4, c4, cxd4, g3, g4Find the best move. Think it through inside <think></think>, then giveexactly one move from the list, in SAN, inside <answer></answer>.
Expected answer: <answer>Qxf7+</answer>.
Usage
from transformers import AutoModelForImageTextToText, AutoTokenizer repo = "nikext/knight-school-qwen3.5-2b-primer-m-grpo-sparse"tok = AutoTokenizer.from_pretrained(repo)model = AutoModelForImageTextToText.from_pretrained(repo, dtype="bfloat16", device_map="auto")# messages = the system and user turns shown above, for your positiontext = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True, enable_thinking=False)ids = tok(text, return_tensors="pt").to(model.device)out = model.generate(**ids, max_new_tokens=24, do_sample=False)print(tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True))
The checkpoint keeps Qwen3.5's vision tower, unchanged. The evals served the language model only
(vLLM --language-model-only). With transformers 5.17, greedy answers matched the graded vLLM
answers on 39–40 of 40 sampled test2000 positions per checkpoint; the rest differed by a near tie.
Limitations
- A puzzle solve rate, not playing strength. Puzzles come with a forcing tactic, and the prompt
lists the legal moves. The model has not been tested in games.
- It depends on the legal-move list: the training and eval prompts always include it.
- Single seed per arm; the CIs cover puzzle sampling, not training variance.
- English prompt format only; other prompt wordings were not tested.
- Not for use against people in live games. The project never plays on Lichess or chess.com.
License
Apache-2.0, as the base model Qwen/Qwen3.5-2B. Training data:
Lichess puzzle and evaluation databases (CC0). Stockfish (GPL-3.0) was used as a tool and is not
distributed.