Downloads · 30 days
0
chayuto/gemma-4-e2b-it-solitaire-advisor-lora
gemma-4-e2b-it-solitaire-advisor-lora is a text generation model from chayuto. Use it when you need the model to write or continue text. It is set up for mlx. The card lists the license as gemma.
A LoRA adapter that distils a 31B Gemma Klondike Solitaire advisor into the ~2B-effective Gemma 4 E2B text model (4-bit, MLX), runnable locally on a 16 GB Apple Silicon Mac.
Downloads · 30 days
0
Access
Public
Updated Jun 30, 2026
Repo size
203 MB
Likes
0
Public
Click a slice to open those files.
.safetensors203 MB · 100%
From the Hugging Face model README
A LoRA adapter that distils a 31B Gemma Klondike Solitaire advisor into the ~2B-effective Gemma 4 E2B text model (4-bit, MLX), runnable locally on a 16 GB Apple Silicon Mac.
This is the Gemma 4 E2B successor to
chayuto/gemma-3n-e2b-it-solitaire-advisor-lora.
That earlier card promised a Gemma 4 E2B variant "once mlx-lm ships the
missing architecture support"; the base is now loadable via a small local
sanitize() patch (see Usage), so this is that promised release,
same project, same teacher, same data pipeline.
It is the project's strongest and lead student: it beats the untuned base under a trusted full-game evaluation both on held-out decks and on fresh, never-seen decks (it generalizes), not just on a single-turn bench. Headline numbers, all adjudicated by exact engine replay plus a sound solver:
The teacher is gemma-4-31b-it (Google's Gemma 4 31B, accessed through a
separate harvester app). The teacher itself wins roughly 31% of games, so this
adapter's ceiling is teacher-level imitation, not optimal play.
| Base model | mlx-community/Gemma4-E2B-IT-Text-int4 (Gemma 4 E2B IT, text-only, int4) |
| Adapter type | LoRA over a 4-bit-quantised base |
| LoRA rank | 16 (scale 2.0, dropout 0.05) |
| LoRA target modules | self_attn.{q,k,v,o}_proj, mlp.{gate,up,down}_proj |
| LoRA layers | top 16 layers |
| Training framework | mlx-lm |
| Hardware | Apple M5, 16 GB unified memory (Metal GPU) |
| Adapter size on disk | ~51 MB per checkpoint (bf16 LoRA weights over the 4-bit base) |
| Iterations trained | 1,000 (shipped weights = iter 1,000) |
| Quantisation | base remains int4; LoRA weights bfloat16 |
| Decoding used for eval | greedy |
The shipped adapters.safetensors is byte-identical to
checkpoints/0001000_adapters.safetensors (iter 1,000), selected over the
earlier checkpoints by the checkpoint-selection pass
(below).
In scope. Acting as a move-selection advisor inside a Klondike Solitaire client that already enforces game rules:
LEGAL MOVES block),
emit a single JSON object choosing one offered legal move by index, or
move_index: -1 to resign.mlx-lm.Out of scope.
legalMoves array supplied in the
prompt; it does not verify legality from first principles.Unlike the 3n predecessor (scored on a 20-state single-turn tier bench), this
adapter is evaluated on full games, which is the eval the project now
trusts. Each game is played turn-by-turn under the faithful production prompt
(hybrid-v1.6) on a fixed deck, greedy decoding, cap 200 turns, with a tiered
JSON parse-rescue (temp-0.3 retry) matching production. Every finished or
resigned game is then exact-adjudicated: the recorded decisions are replayed
through the engine with zero drift, and the final or resign position is handed
to a sound best-first solver (SOLVED / UNSOLVABLE / UNKNOWN at a 300k-node cap).
This distinguishes a real win from a cap-truncated one, and a correct resign on
a dead board from a quit on a winnable one. The two eval sets:
meanFC below is the mean number of cards on foundations (out of 52) at game
end across the deck set; 52 is a win.
| arm | corpus | meanFC | wins | resigns |
|---|---|---|---|---|
| base (untuned) | none | 14.2 | 1 | 0 |
| gate | 2,492 rows, 100% won | 27.5 | 4 | 0 |
| allsucc | 2,500 rows, 38% won | 25.5 | 3 | 1 |
| volume (this adapter) | 6,859 rows, 36% won | 27.7 | 5 | 1 |
Two facts this established. First, training beats the untuned base under trusted
eval (the first time in the project). Second, the won-only filter is not the
lever; data volume is: the won-only gate and the natural-mix allsucc
tie at matched size, and the full-volume arm here is the strongest student, so
"collect and train on more data" is the validated recipe. The 1 in-distribution
resign (#4221577640) is on a deal that was winnable from the deal; whether
volume's board at the moment it resigned was already dead is UNKNOWN at the
300k-node cap, so it is neither claimed correct nor false.
The decisive question was whether the student learned to play Klondike or memorized the harvester's deck distribution. On fresh decks with zero corpus overlap, paired against base:
| arm | wins/12 | meanFC | mean paired delta vs base | better than base |
|---|---|---|---|---|
| base | 1 | 15.6 | --- | --- |
| gate (won-only) | 3 | 21.2 | +5.6 | 7/12 |
| volume (this adapter) | 5 | 28.5 | +12.9 | 9/12 |
Verdict: GENERALIZES, decisively. Both trained arms show a positive paired delta and win multiple fresh, never-seen decks that base cannot. The student learned to play; it did not memorize the deck pool. Win counts are exact (no cap-truncated win above fc=40). Read the paired delta, not absolute fc: the fresh set is biased easy (only deals the solver cracked under a 200k-node cap were kept), which lifts both arms and compresses the gap.
Resigns on the fresh set are informative because every fresh deck is winnable.
Volume resigned 2, and both were adjudicated correct: #9000021 (fc18/fd3)
solved UNSOLVABLE, #9000024 (fc11/fd12) solved UNSOLVABLE. Both boards were
already structurally dead at the moment of resignation, so the resigns saved a
200-turn flail rather than throwing a winnable game. Where there is an error it
is earlier play, not the resign itself (judge resign-correctness on the board at
resign, not the deck at deal).
Update (2026-07-01): pooled fresh-deck rate pinned at ~33%, and two follow-on programs did not beat this adapter. The 5/12 above is one of three fresh samples. A larger 24-deck fresh set (2026-06-28) read 6/24 strict (8/24 after adjudication, meanFC 24.4), and pooling all three fresh samples gives 16 of 48, about 33% (roughly 1 in 3 fresh winnable decks), below the ~42% the two small samples implied but still about 4x the untuned base (~8%). Two subsequent programs to push past this adapter both failed to beat it: static-corpus reweighting and solver-grounding (closed 2026-06-23, every recipe collapses to 3-4 of 12 on fresh decks while volume holds 5) and on-policy SFT on the student's own self-wins (STaR / expert iteration, closed 2026-07-01, the best diverse-pool retrain reaches parity at 4 vs 5, not a lift). So this volume adapter stands as the project's lead student; the only untried lever to beat rather than match it is reward-weighted RFT/GRPO.
Quality across the four saved checkpoints is non-monotonic, so the final checkpoint is not automatically the best; it was selected on the 13 held-out decks. JSON discipline is measured as temp-0.3 parse-rescues (lower is cleaner):
| checkpoint | wins | meanFC | temp parse-rescues |
|---|---|---|---|
| iter 250 | 5 | 31.7 | 102 |
| iter 500 | 2 | 21.3 | 126 |
| iter 1,000 (shipped) | 5 | 27.7 | 34 |
Iter 1,000 keeps the win count of the best early checkpoint while being roughly
3x cleaner on JSON, and iter 500 is a deep trough. The remaining JSON-discipline
gap is best closed by constrained decoding at inference, not by an earlier
checkpoint (see Limitations). The 250/500/750 checkpoints are
included under checkpoints/ for reproducibility of this table.
The 3n adapter's headline numbers come from a 20-state single-turn tier bench; this adapter's come from full-game play with exact adjudication. They are not directly comparable. The shift to full-game adjudicated eval is deliberate: in this project, raw harness scores have repeatedly disagreed with what an exact replay plus a sound solver show, so single-turn or unadjudicated numbers are treated as unreliable. The full-game result here is the stronger and more honest claim.
Source. Per-decision play logs from a Klondike Solitaire client where the
31B gemma-4-31b-it teacher chose moves turn-by-turn, published as the
chayuto/klondike-llm-decisions
dataset (CC-BY-4.0). Logs span multiple app builds and prompt-template versions.
Selection (this adapter, the "volume" arm). The entire non-eval success pool: 6,859 decisions across 77 games (36% of which were won), with the 13 eval seeds held out. Split at the game level into 5,663 train / 531 validation / 665 test. At iters=1,000 with batch size 1 the model sees ~1,000 examples (~0.18 epoch), so this arm tests "more unique, diverse data at a fixed gradient budget", and it won: full volume beat a matched 2,500-row arm on wins (5 vs 3).
Known data-quality issues the adapter inherits:
gate arm does not have.model: mlx-community/Gemma4-E2B-IT-Text-int4
max_seq_length: 2048
batch_size: 1
num_layers: 16
grad_checkpoint: true
learning_rate: 2.0e-4
iters: 1000
save_every: 250
val_batches: 25
lora_parameters:
rank: 16
scale: 2.0
dropout: 0.05
keys:
- self_attn.q_proj
- self_attn.k_proj
- self_attn.v_proj
- self_attn.o_proj
- mlp.gate_proj
- mlp.up_proj
- mlp.down_proj
Hyperparameters are identical to the project's other Gemma 4 E2B arms (gate, allsucc, v2, v5), so arm-to-arm differences are attributable to the corpus, not the optimiser.
# Apple Silicon, Python 3.12 venv recommended
python3.12 -m venv venv && source venv/bin/activate
pip install mlx mlx-lm huggingface-hub
The Gemma4-E2B-IT-Text-int4 base needs a small sanitize() patch to load on
current mlx-lm (it strips the alternating-attention k_norm/k_proj/v_proj
keys that the loader does not yet implement). The 6-line patch used here is
gemma4_finetune/gemma4_text_patch.py in the source repo
(github.com/chayuto/solitaire-analytics);
apply it (or use an mlx-lm version that has since merged the support) before
loading. Peak memory to load the patched base is ~2.7 GB.
from huggingface_hub import snapshot_download
from mlx_lm import load, generate
# import gemma4_text_patch # apply the base-loading patch first (see above)
adapter_path = snapshot_download(
repo_id="chayuto/gemma-4-e2b-it-solitaire-advisor-lora",
allow_patterns=["adapters.safetensors", "adapter_config.json"],
)
model, tokenizer = load(
"mlx-community/Gemma4-E2B-IT-Text-int4",
adapter_path=adapter_path,
)
solitaire_prompt = open("your_solitaire_prompt.txt").read() # the v1.6 harvester prompt
wrapped = tokenizer.apply_chat_template(
[{"role": "user", "content": solitaire_prompt}],
tokenize=False, add_generation_prompt=True,
)
print(generate(model, tokenizer, prompt=wrapped, max_tokens=512))
The model emits a JSON object whose final_decision.move_index is a 0-based
index into the prompt's legalMoves array (or -1 to resign). Trust
move_index, not the prose. The exact prompt renderer, the play harness
(tournament_A.py, play_deck_with_student.py), and the adjudicator
(adjudicate_final_position.py) that produced every number in this card are in
the source repo.
mlx-lm without the sanitize() patch.transformers/PEFT is
not validated here.mlx-community/Gemma4-E2B-IT-Text-int4; applying it to a different quant or
the full-precision base is unvalidated.The adapter is released under the Gemma Terms of Use (inherited from the
base model). Use, redistribution, and modification require compliance with the
Gemma Prohibited Use Policy.
The training and evaluation code in the source repository is MIT. The
training data (chayuto/klondike-llm-decisions)
is CC-BY-4.0.
@misc{orapinpatipat2026solitaireadvisorgemma4,
title = {Distilling a 31B Klondike Solitaire advisor into Gemma 4 E2B via MLX LoRA},
author = {Orapinpatipat, Chayut},
year = {2026},
month = jun,
howpublished = {\url{https://huggingface.co/chayuto/gemma-4-e2b-it-solitaire-advisor-lora}},
note = {LoRA adapter; volume arm, iter-1000 checkpoint},
}
mlx-community/Gemma4-E2B-IT-Text-int4 from the
mlx-community team.mlx-lm from Apple Machine Learning Research.gemma-4-31b-it from Google DeepMind.This is the Gemma 4 E2B milestone of an ongoing project (solitaire-analytics):
the strongest student so far, beating the untuned base under trusted full-game
eval and generalizing to fresh, unseen decks (~33% of fresh winnable deals,
about 4x base). It supersedes the
gemma-3n
adapter as the project's lead student; the 3n repo remains as the v1 baseline.
As of 2026-07-01 this adapter is the confirmed, shipped lead: two programs to improve on it have been run to conclusion and neither beat it, static-corpus engineering (closed 2026-06-23) and on-policy SFT on the student's own self-wins (closed 2026-07-01). The single untried lever to beat rather than match it is reward-weighted RFT/GRPO, which is parked. Standing improvement directions otherwise: constrained decoding for JSON robustness and resign calibration.