SF-CoT + DAgger (round 5) β€” chess move-commit baseline (LoRA adapter)

A LoRA adapter on Qwen/Qwen3-1.7B, supervised-fine-tuned on verbalized-minimax Stockfish chain-of-thought traces and then refined with 5 rounds of DAgger (on-policy roll-out β†’ Stockfish relabel β†’ retrain). This checkpoint (dag5) is the best-by-committed-move-quality point of that baseline, produced as a control experiment in the Latent Grafting research project.

⚠️ Research / negative-result artifact β€” not a strong chess engine. It is the least-bad member of a family of models that all commit chess moves worse than a trivial no-search policy baseline on hard positions. It is published for reproducibility of the finding described below, not for play. Do not deploy it as a chess player.


Table of contents

  1. TL;DR
  2. Model details
  3. Intended use & limitations
  4. Prompt & output format
  5. How to use
  6. Training data
  7. Training procedure
  8. Evaluation
  9. Results
  10. The research finding
  11. Reproducibility & artifacts
  12. Citation

TL;DR

  • What: Qwen3-1.7B + LoRA that writes a Stockfish-style verbalized-minimax analysis for a chess position and then commits best move: <uci>. No engine graft, no soft-tokens β€” a pure-text control.
  • Why it exists: to test whether plain text chain-of-thought (plus on-policy DAgger correction) can turn chess reasoning into a good committed move at the 1.7B scale.
  • Headline result: it can't. DAgger improved the text baseline from βˆ’116% to a βˆ’66% peak (this checkpoint) recovery of the search headroom, but that peak still commits worse than a no-search policy prior, and neither more DAgger rounds nor fresher, more-diverse on-policy data pushed past it.

Model details

Base model Qwen/Qwen3-1.7B
Adapter type LoRA (PEFT), r = 32, Ξ± = 64, dropout 0
Target modules q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj
Trainable params ~35 M (adapter only; ~133 MB safetensors)
Precision bf16
Task causal-LM (chess position β†’ verbalized analysis β†’ committed move)
Graft / soft-tokens None β€” this is the text-only control (no engine trunk spliced in)
License Apache-2.0 (inherits base model license)

Intended use & limitations

Intended use. Research reproducibility for the Latent Grafting "read β†’ commit wall" study; a baseline to compare against latent-value approaches; an example of a verbalized-minimax CoT format.

Out of scope. Actual chess play or move recommendation. On adversarially-selected hard positions this model's committed move is, on average, worse than simply taking a policy prior's top move without any search. It also has not been evaluated for full-game Elo.

Known limitations.

  • Commits worse than the no-search prior on the search-required suite (see Results).
  • Trained/evaluated only on chess; single game.
  • The evaluation suite is selected around a particular prior's errors β€” absolute recovery % is calibrated to that frame (the direction of the result is robust; the magnitude is frame-relative).
  • Occasionally the verbalized value reads saturate; commit is by parsing best move: from the trace.

Prompt & output format

Input:

FEN: <fen> moves: <space-separated recent uci moves>
Analyze:

Output (the model continues from Analyze:): a verbalized-minimax trace β€” top candidate moves with evaluations and short principal-variation lines β€” terminating in a legal committed move:

 <uci>: <eval> (line: <pv> ) ; <uci>: <eval> (line: <pv> ) ; ... ; best move: <uci>

How to use

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
import torch

REPO = "GoodStartLabs/sf-cot-dagger-chess-qwen3-1.7b"
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-1.7B")
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-1.7B", torch_dtype=torch.bfloat16)
model = PeftModel.from_pretrained(base, REPO).eval()

fen = "r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 4 4"
prompt = f"FEN: {fen} moves: \nAnalyze:"
ids = tok(prompt, return_tensors="pt")
out = model.generate(**ids, max_new_tokens=200, do_sample=False)
print(tok.decode(out[0][ids.input_ids.shape[1]:], skip_special_tokens=True))
# ... parse the trailing "best move: <uci>" to get the committed move.

Training data

  • Base SFT corpus: ~40,000 verbalized-minimax Stockfish traces (Stockfish 17.1). Each root position is analysed with top-6 multipv plus principal-variation lines, at adaptive depth (12, deepened to 20 when the top-2 evaluations are close), then serialized into the text format above. ~38.9k unique positions (highly diverse).
  • DAgger corpora (rounds 1–5): on-policy positions the model reached or mis-committed during roll-out, each relabeled with a fresh correct Stockfish trace (wrong-commits duplicated 2Γ—). Accumulated cumulatively across rounds. Root positions mined from a self-play/tactical shard pool.

Training procedure

  1. SFT the base corpus (completion-only cross-entropy on the trace tokens; the prompt is masked).
  2. DAgger loop, 5 rounds: roll the current model out over ~2,000 root positions β†’ flag the positions where its committed move disagrees with Stockfish + the position it reaches after its move β†’ generate correct Stockfish traces for those β†’ append to the corpus β†’ retrain from the cumulative corpus (3,000 steps). Round r's model seeds round r+1's roll-out.
  3. Checkpoint selection: dag5 = the round with the best committed-move recovery (and, on the original loop, the minimum SFT eval-loss simultaneously).

Hyperparameters: LoRA r32/Ξ±64; bf16; completion-only CE; per-round 3,000 steps.


Evaluation

Metric. Committed-move centipawn loss (cp-loss): the model commits one move per position; the loss is how many centipawns worse that move is than Stockfish's best (per Stockfish evaluation).

Suite. A search-required suite of 150 positions, selected so a no-search policy prior is materially wrong (prior cp-loss 173.0; Stockfish-best "oracle" 92.6). This isolates positions where reasoning/search should help.

Recovery % = (prior βˆ’ model) / (prior βˆ’ oracle) β€” the fraction of the 173β†’92.6 headroom the model closes. 0% = matches the prior; +100% = matches Stockfish; negative = worse than the prior.

Definitions of the reference points:

  • base LLM β€” Qwen3-1.7B reading the raw FEN, no adapter (built-in ability, ~619 Elo elsewhere).
  • prior β€” a no-search policy baseline's top move (already a strong reference: it beats this model). Not the bare base model.
  • oracle β€” Stockfish's actual best move on the suite.

Results

Committed-move cp-loss on the search-required suite (prior 173.0, oracle 92.6):

model cp-loss recovery
Stockfish best (oracle) 92.6 +100%
no-search policy prior 173.0 0%
this model β€” SF-CoT + DAgger round 5 226.4 βˆ’66%
SF-CoT baseline (pre-DAgger) 266.4 βˆ’116%

DAgger trajectory (this model is round 5):

round cp-loss recovery SFT eval-loss
pre-DAgger 266.4 βˆ’116% β€”
1 263.6 βˆ’113% β€”
2 247.6 βˆ’93% 0.543
3 250.4 βˆ’96% 0.519
4 255.5 βˆ’103% 0.497
5 (this model) 226.4 βˆ’66% 0.477
6 233.0 βˆ’75% 0.491
7 236.3 βˆ’79% 0.451
8 237.0 βˆ’80% 0.446

Past round 5, SFT eval-loss kept falling to new minima while committed-move recovery worsened β€” imitation loss and move quality decouple.

Diversity control (fresh disjoint on-policy positions each round, restarted from round 5):

round recovery eval-loss
fresh 1 βˆ’96% 0.489
fresh 2 βˆ’84% 0.495
fresh 3 βˆ’101% 0.507
fresh 4 βˆ’77% 0.497

Fresh, more-diverse positions (eval-loss rises, confirming they are not merely memorized) still land in the βˆ’77β€¦βˆ’101% band β€” they do not recover the round-5 peak. So the plateau is not an artifact of DAgger re-mining a stale position set; it is a genuine commit ceiling.


The research finding (why this model exists)

This model is the text-only control in a study of where a value signal is lost between a chess engine and a language model's committed move. The broader project finds:

  • The engine's position value is abundant and linearly readable upstream (a ridge probe recovers position value at RΒ² 0.92 from the engine trunk), and a dedicated latent reader can commit at +95% recovery.
  • But every path that turns that value into a generated committed move at 1.7B loses it: latent graft generative commit β‰ˆ βˆ’42%; this text-CoT baseline β‰ˆ βˆ’116%, improved by DAgger to a βˆ’66% peak β€” all still worse than the no-search prior.

Conclusion: at this scale the bottleneck is move selection / commit, not the availability or readability of the value signal. Plain text chain-of-thought + DAgger does not overcome it (and this model is its best point). See the project write-up for the full ladder (base β†’ prior β†’ oracle) and the latent-value approaches.


Reproducibility & artifacts

  • Base model: Qwen/Qwen3-1.7B.
  • This adapter: runs/graft/sf_baseline_dag5/lora_best (round-5 DAgger checkpoint).
  • Format: PEFT LoRA (adapter_config.json + adapter_model.safetensors).
  • Eval: committed-move cp-loss on a 150-position search-required suite (prior 173.0, oracle 92.6).

Citation

If you reference this artifact:

@misc{goodstartlabs_sfcot_dagger_2026,
  title  = {SF-CoT + DAgger: a text chain-of-thought chess move-commit baseline (Qwen3-1.7B LoRA)},
  author = {GoodStart Labs},
  year   = {2026},
  note   = {Latent Grafting project; research negative-result artifact.},
  howpublished = {\url{https://huggingface.co/GoodStartLabs/sf-cot-dagger-chess-qwen3-1.7b}}
}
Downloads last month
9
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for GoodStartLabs/sf-cot-dagger-chess-qwen3-1.7b

Finetuned
Qwen/Qwen3-1.7B
Adapter
(620)
this model