rukh · lab

// The project

Rukh, on one page.

Rukh is a chess language model trained from scratch, aligned with reinforcement learning from verifiable rewards, deployed on WebGPU and turned into an agent with tools. This site is the course that documents every step.

01 / What it is// one artifact, the whole lifecycle

One model, every stage of the lifecycle.

A custom decoder (tens of millions of parameters) trained on Lichess games, an encoder that understands positions, instruction-style fine-tuning with target-Elo tokens, a reward model, DPO and GRPO with Stockfish as the judge, ONNX export to play in the browser and, in the second phase, RAG and agents that comment games.

  1. M1Data & tokenizationLichess cut, UCI vocabulary, BPE, dataloaders
  2. M2DecoderGPT-style MoveDecoder trained from scratch, ONNX, WebGPU demo
  3. M3EncoderMasked move modeling, value and blunder heads
  4. M4Fine-tuningMasters SFT, Elo-conditioning, LoRA, QLoRA vs Qwen3
  5. M5AlignmentReward model, DPO, GRPO with Stockfish as verifier
  6. M6EvaluationSingle results table, model cards, final demo
  7. A1-A6Agentic coachRAG over positions and books, LangGraph agent, MCP server
02 / Three repositories// architecture

Three repositories, no shared packages.

Everything the two web apps share (CSS tokens, the TypeScript tokenizer, precomputed JSON) is copied by a script and guarded by a hash test. All trained weights and datasets live on Hugging Face, never in git.

Architecture: rukh publishes to Hugging Face; rukh-lab and rukh-web read from itrukhtrain · eval · agentsrukh-labcourse · lab.rukhrukh-webplay · WebGPUHugging Faceartifacts/web/*.json → sync:datamodels · adapters · datasets
  • borja-glez/rukh

    Model, data, training, evaluation and agents. Python 3.12, PyTorch, MLflow.

  • borja-glez/rukh-lab

    This site: the course, in Spanish. Static Astro with Preact islands.

  • borja-glez/rukh-web

    The demo: play against the model in the browser with onnxruntime-web.

03 / Results// rukh eval nightly

Measured the same way, every time.

One table for every stage, always measured the same way. It fills in as the course progresses; each row links to its model card.

StageParamsLegal moves, no mask (%)Top-1 (%)Top-3 (%)Puzzles 1000-1500 (%)Puzzles 1500-2000 (%)Puzzles 2000+ (%)Elo vs Stockfish (95% CI)Mean loss (cp)Hugging Face
medium-greedy115.1 M99.452.980.937.624.010.21091 (990 … 1194)
small39.0 M99.451.179.4785 (680 … 896)
small-greedy39.0 M99.451.179.434.721.110.31007 (920 … 1101)
tiny5.3 M94.540.367.164 (-200 … 292)
Encoder: a blunder detector and a value head are not measured like a player. The threshold is chosen on a held-out “tune” half and the F1 is reported on a “score” half; the fixed-0.5 column is there because a 3.7 % positive class makes that default meaningless.
StageParamsBlunder F1, tuned threshold (%)Threshold (chosen on “tune”)Blunder F1, threshold 0.5 (%)Baseline F1 (%)Margin (F1 points)Blunder ROC AUCAverage precisionBlunder base rate (%)Value: SpearmanValue: PearsonResult (%)
encoder15.1 M17.90.1280.08.9+9.00.7380.1093.70.4070.44249.9
encoder-squares14.3 M14.60.04460.08.9+5.70.6960.0913.70.4220.68850.2

Source: rukh eval nightly · updated 19/09/2026

04 / Links// public artifacts