Bestemshe ResMLP — Neural Value & Policy Distillation
Authors: Ansar Zeinulla & Murat Manassov
Affiliation: Nazarbayev University, Kazakhstan
Paper: Strongly Solving Bestemshe and Benchmarking Neural Value Approximations against a 10GB Endgame Oracle
10GB Endgame Oracle Dataset: ansarzeinulla/bestemshe-tablebase
Code Repository: github.com/ansarzeinulla/Bestemshe
This repository hosts the weights and evaluation benchmarks for the dual-headed Residual Multilayer Perceptron (ResMLP) trained via continuous supervised distillation on over 2 billion board configurations directly generated from the 10GB Bestemshe Endgame Oracle.
The architecture predicts:
- Value Head ($V$): Probability distribution over
{LOSS, DRAW, WIN}for the player to move. - Policy Head ($\pi$): Action logits across the 5 playable pits (
0..4).
Model Architecture Specifications
- Input Layer: 10 board pits ($0..50$ stones) + 2 Kazans ($0..24$ captured stones) mapped via categorical embedding layers ($E_{\text{pits}} \in \mathbb{R}^{51 \times 32}$, $E_{\text{kazan}} \in \mathbb{R}^{13 \times 32}$).
- Trunk: 8 Residual MLP blocks with LayerNorm and GELU activations ($\text{Hidden Dimension} = 1024$).
- Heads: Linear Value Head ($\mathbb{R}^{1024} \to \mathbb{R}^3$) and Policy Head ($\mathbb{R}^{1024} \to \mathbb{R}^5$).
- Parameters: ~18.5 Million parameters.
- Training Horizon: 394,852 steps at batch size 32,768 ($>2 \times 10^9$ total training positions seen).
Benchmarking against the 10GB Endgame Oracle
1. Final Adversarial Match Benchmark (Step 394,852)
Evaluated against the ground-truth Tablebase on 2,000 full matches from 1,000 adversarial, forced-symmetric starting configurations (equal Kazans, equal stone counts per side):
| Metric | Score | Context |
|---|---|---|
| Move-Level Optimal Rate | 99.69% | Matches Oracle choice on 226,232 / 226,933 moves executed |
| Move-0 Start WDL Accuracy | 83.50% | Raw value prediction on hard symmetric OOD starts |
| Game-Level Degradation | 27.80% | Games lost/drawn due to compounding micro-errors over 113-ply horizons |
2. Complete Training Progression Across Checkpoints
Below is the aggregated evaluation statistics across 38 milestone evaluation steps during training:
| Step | Positions Evaluated | Games | WDL Accuracy | Optimal Move Rate | Incidents |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 0.0000 | 1.0000 | 0 |
| 6 | 10 | 5 | 0.2000 | 0.6000 | 2 |
| 26 | 100 | 20 | 0.5300 | 0.5900 | 10 |
| 76 | 1,000 | 100 | 0.6760 | 0.7120 | 50 |
| 176 | 1,000 | 100 | 0.7550 | 0.7460 | 50 |
| 476 | 50,000 | 1,000 | 0.8171 | 0.8254 | 500 |
| 976 | 100,000 | 2,000 | 0.8455 | 0.8887 | 1,000 |
| 1,976 | 200,000 | 3,000 | 0.8535 | 0.9190 | 1,500 |
| 3,976 | 300,000 | 5,000 | 0.8787 | 0.9385 | 5,000 |
| 8,976 | 500,000 | 8,000 | 0.9118 | 0.9547 | 4,000 |
| 18,976 | 10,000 | 100 | 0.9192 | 0.9662 | 100 |
| 28,976 | 10,000 | 100 | 0.9224 | 0.9679 | 100 |
| 38,976 | 100,000 | 10,000 | 0.9263 | 0.9672 | 10,000 |
| 48,976 | 1,000 | 100 | 0.9310 | 0.9700 | 100 |
| 98,976 | 10,000 | 1,000 | 0.9223 | 0.9683 | 1,000 |
| 143,976 | 10,000 | 1,000 | 0.9216 | 0.9681 | 1,000 |
| 358,976 | 100,000 | 100 | 0.9060 | 0.9602 | 100 |
| 394,852 (Final) | 100,000 | 100 | 0.9294 | 0.9701 | 100 |
Standalone Python Inference
You can run single-position evaluation directly by downloading latest.pt and executing this PyTorch script:
pip install torch huggingface_hub
"""
Standalone single-position inference for Bestemshe ResMLP.
Requires: pip install torch huggingface_hub
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
from huggingface_hub import hf_hub_download
class ResBlock(nn.Module):
def __init__(self, w):
super().__init__()
self.norm = nn.LayerNorm(w)
self.fc1 = nn.Linear(w, w)
self.fc2 = nn.Linear(w, w)
def forward(self, x):
return x + self.fc2(F.gelu(self.fc1(self.norm(x))))
class BestemsheNet(nn.Module):
"""Embeddings -> Residual MLP -> Value head (loss/draw/win) + Policy head (5 moves)."""
def __init__(self, width=1024, blocks=8, emb=32):
super().__init__()
self.pit_emb = nn.Embedding(51, emb) # stones per pit: 0..50
self.kaz_emb = nn.Embedding(13, emb) # kazan // 2: 0..12
self.inp = nn.Linear(12 * emb, width)
self.body = nn.Sequential(*[ResBlock(width) for _ in range(blocks)])
self.value_head = nn.Linear(width, 3)
self.policy_head = nn.Linear(width, 5)
def forward(self, pits, kaz):
x = torch.cat([self.pit_emb(pits).flatten(1),
self.kaz_emb(kaz).flatten(1)], dim=1)
h = self.body(F.gelu(self.inp(x)))
return self.value_head(h), self.policy_head(h)
def load_model(repo_id="ansarzeinulla/bestemshe-resmlp", filename="latest.pt", device="cpu"):
path = hf_hub_download(repo_id=repo_id, filename=filename)
ckpt = torch.load(path, map_location="cpu")
args = ckpt["args"]
model = BestemsheNet(args["width"], args["blocks"]).to(device).eval()
# Strip torch.compile() prefix if present
state_dict = {k.replace("_orig_mod.", ""): v for k, v in ckpt["model"].items()}
model.load_state_dict(state_dict)
return model, ckpt.get("step", 0)
@torch.no_grad()
def evaluate_position(model, pits, kazan_self, kazan_opp, device="cpu"):
"""
pits: list of 10 ints — stones in each pit (0..4 = own side, 5..9 = opponent's)
kazan_self, kazan_opp: int — each player's kazan (captured stones), 0..24 (even)
Returns WDL probabilities for the side to move + raw 5-way move-policy logits
(index 0..4, own pits only).
"""
pits_t = torch.tensor([pits], dtype=torch.long, device=device)
kaz_t = torch.tensor([[kazan_self // 2, kazan_opp // 2]], dtype=torch.long, device=device)
value_logits, policy_logits = model(pits_t, kaz_t)
probs = torch.softmax(value_logits[0], dim=0).cpu().numpy()
return {
"p_loss": float(probs[0]),
"p_draw": float(probs[1]),
"p_win": float(probs[2]),
"predicted_class": int(value_logits[0].argmax()), # 0=loss, 1=draw, 2=win
"policy_logits": policy_logits[0].cpu().numpy().tolist(),
}
if __name__ == "__main__":
model, step = load_model()
print(f"Loaded checkpoint at training step: {step}")
# Standard starting board: 5 stones in every pit, kazans empty
result = evaluate_position(model, pits=[5] * 10, kazan_self=0, kazan_opp=0)
print("Standard Starting Position Evaluation:")
print(f" P(Loss): {result['p_loss']:.4f}")
print(f" P(Draw): {result['p_draw']:.4f}")
print(f" P(Win) : {result['p_win']:.4f}")
print(f" Policy Logits (Pits 0..4): {result['policy_logits']}")
Citation
@misc{zeinulla2026bestemshe_model,
title={Bestemshe ResMLP: Neural Value and Policy Distillation},
author={Zeinulla, Ansar and Manassov, Murat},
year={2026},
publisher={Hugging Face Models},
howpublished={\url{https://huggingface.co/ansarzeinulla/bestemshe-resmlp}}
}
Dataset used to train ansarzeinulla/bestemshe-resmlp
Space using ansarzeinulla/bestemshe-resmlp 1
Evaluation results
- Optimal Move Rateself-reported99.690