chayuto commited on
Commit
cb3ce1d
Β·
verified Β·
1 Parent(s): c22a89b

v1.1: promote iter-750 as canonical (best of 4 checkpoints by tier+foundation), add learning curve, fix draw-1 wording, beef up Usage with concrete example, plain-text typography pass

Browse files
README.md CHANGED
@@ -23,15 +23,20 @@ A LoRA adapter that distils a 31B Gemma Klondike Solitaire advisor into the
23
  ~2B-effective **Gemma 3n E2B** text-only model, runnable locally on a 16 GB
24
  Apple Silicon Mac via MLX.
25
 
26
- This is the **first distillation run** (1,000 LoRA iters on 1,279 training
27
- examples) and produced a **2Γ— reduction in tier-score gap to the teacher**
28
- (βˆ’1.32 β†’ βˆ’0.67 on a 20-state evaluation bench), eliminated illegal-move
29
- generation (1/20 β†’ 0/20), and recovered 2 of 5 teacher-foundation moves the
30
- untuned base model missed β€” including a triple-replicated failure state that
31
- had defeated three prior small-model experiments.
 
 
 
 
 
32
 
33
  > **Why Gemma 3n and not Gemma 4 E2B?**
34
- > The intended student was **Gemma 4 E2B** β€” the same series as the teacher
35
  > and the project's long-term target. As of `mlx-lm` 0.31.3 (the latest at
36
  > this writing), all mlx-community Gemma 4 E2B quants fail to load with a
37
  > 140-parameter architecture-mismatch error (layers 15-34's
@@ -39,8 +44,8 @@ had defeated three prior small-model experiments.
39
  > implemented in mlx-lm's `Gemma4Model` class). The student here is therefore
40
  > the previous-generation **`gemma-3n-E2B`**, which `mlx-lm` fully supports.
41
  > A **Gemma 4 E2B variant of this adapter will be published** once `mlx-lm`
42
- > ships the missing architecture support β€” same training script, same data,
43
- > same eval bench, same methodology β€” at a separate repo
44
  > (`chayuto/gemma-4-e2b-it-solitaire-advisor-lora`).
45
 
46
  The teacher is `gemma-4-31b-it` (Google's Gemma 4 31B, accessed through a
@@ -58,7 +63,7 @@ separate harvester app).
58
  | Training framework | [`mlx-lm`](https://github.com/ml-explore/mlx-lm) 0.31.3 |
59
  | Hardware | Apple M5 16 GB unified memory (Metal GPU) |
60
  | Adapter size on disk | 45 MB per checkpoint |
61
- | Iterations trained | 1,000 |
62
  | Wall-clock training time | ~95 minutes |
63
  | Quantisation | base remains 4-bit; LoRA weights bfloat16 |
64
 
@@ -66,71 +71,147 @@ separate harvester app).
66
 
67
  **In scope.** Acting as a move-selection advisor inside a Klondike Solitaire
68
  client that already enforces game rules:
69
- - Imperfect-information Klondike (draw-3 variant); the advisor is shown the
70
- full visible state plus the count of face-down cards
 
71
  - Single-turn decisions: given the prompt schema below, emit a single JSON
72
- object choosing one of the offered legal moves
73
  - Local inference on Apple Silicon (8 GB+ unified memory) via `mlx-lm`
74
 
75
  **Out of scope.**
76
  - Open-ended chat or general-purpose text generation. The model has been
77
- fine-tuned to a narrow JSON-emitting role and is expected to be measurably
78
- worse than the base model at unrelated tasks.
79
  - Game-rule enforcement. The advisor selects from a `legalMoves` array
80
- supplied in the prompt; it does not verify legality from first principles
81
- and should not be trusted to do so.
82
  - Optimal Solitaire play. The distillation target is a 31B model that itself
83
- is imperfect (it stalls on ~21 % of post-cutover games observed in
84
- production). This adapter inherits that ceiling.
85
- - Other Solitaire variants (Spider, FreeCell, etc.) β€” out-of-distribution.
86
 
87
  ## Usage
88
 
89
- ### Inference with `mlx-lm`
 
 
 
 
 
 
 
 
90
 
91
  ```python
 
92
  from mlx_lm import load, generate
93
 
 
 
 
 
 
 
 
 
94
  model, tokenizer = load(
95
  "mlx-community/gemma-3n-E2B-it-text-4bit-dwq",
96
- adapter_path="chayuto/gemma-3n-e2b-it-solitaire-advisor-lora",
97
  )
98
 
99
- prompt = open("your_solitaire_prompt.txt").read()
100
- messages = [{"role": "user", "content": prompt}]
 
101
  wrapped = tokenizer.apply_chat_template(
102
- messages, tokenize=False, add_generation_prompt=True
 
103
  )
104
  response = generate(model, tokenizer, prompt=wrapped, max_tokens=512)
105
  print(response)
106
  ```
107
 
108
- Expect ~13 s per call on M5 16 GB, ~6.3 GB peak Metal memory.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- ### Expected I/O shape
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  **Input**: a single user-role message containing the full Solitaire game prompt
113
- (rules preamble, current game state, legal-moves block, prior reasoning trail).
114
- A complete example lives at `eval/sample_prompt.txt` in this repo.
 
 
115
 
116
- **Output**: a JSON object with three required keys:
117
 
118
  ```json
119
  {
120
- "board_analysis": "string β€” terse description of the visible state",
121
- "strategic_plan": "string β€” why the chosen move is preferred",
122
- "final_decision": {
123
  "move_index": 0,
124
  "confidence": 0.9,
125
  "alternative_move_index": 1
126
- }
127
  }
128
  ```
129
 
130
- `move_index` is a 0-based index into the `legalMoves` array from the prompt.
131
- The client is expected to apply the chosen move; if `move_index` is illegal
132
- the client should fall back (the adapter eliminated illegal moves on the
133
- 20-state eval but generalisation is not guaranteed).
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  ## Training data
136
 
@@ -145,7 +226,7 @@ ingest pipeline filters (success outcome, valid `rawResponse` JSON with the
145
  three required keys, not from a stalled game). 25 distinct play sessions,
146
  heterogeneous prompt templates (~63 % pre-cutover legacy format, ~37 % the
147
  current production template `0462323c…`). Split at the session level
148
- 80 / 10 / 10 β†’ 1,279 train / 126 val / 131 test.
149
 
150
  **License.** Released as a derived training corpus under **CC-BY-4.0** in
151
  the project's published dataset (separately staged). No personally
@@ -154,14 +235,14 @@ responses, and timing.
154
 
155
  **Known data-quality issues** the adapter inherits:
156
  - 11 % of source rows dropped by the ingest filter for malformed
157
- `rawResponse` β€” root cause not yet localised in the harvester.
158
  - Teacher `confidence` field is saturated (median 0.90, never below 0.80)
159
- even in lost games β€” not a reliable training signal; it is included in
160
- completions but treated as suspect downstream.
161
  - Mixed prompt-template formats in training; eval is on the most-recent
162
- template only.
163
  - No deck seed in logs (open harvester P0), so we cannot verify the
164
- teacher's choices against solver-optimal play.
165
 
166
  ## Training procedure
167
 
@@ -181,10 +262,10 @@ save_every: 250
181
  val_batches: 25
182
 
183
  lora_parameters:
184
- rank: 16
185
- scale: 2.0
186
- dropout: 0.05
187
- keys:
188
  - self_attn.q_proj
189
  - self_attn.k_proj
190
  - self_attn.v_proj
@@ -202,28 +283,65 @@ breaks `altup.predict()`'s direct `.weight` access mid-training.
202
 
203
  | iter | train loss | val loss | peak MLX | wall (cumulative) |
204
  |---:|---:|---:|---:|---:|
205
- | 1 | β€” | 6.365 | β€” | 0 m |
206
- | 10 | 3.160 | β€” | 11.45 GB | ~1 m |
207
  | 100 | 0.388 | 0.426 | 11.45 GB | ~10 m |
208
  | 250 | (checkpoint) | (checkpoint) | 11.49 GB | ~25 m |
209
  | 500 | (checkpoint) | (checkpoint) | 11.49 GB | ~50 m |
210
  | 750 | (checkpoint) | (checkpoint) | 11.49 GB | ~75 m |
211
  | 1000 | **0.222** | **0.369** | 11.49 GB | ~95 m |
212
 
213
- Most of the learning happened in the first 100 iters (val 6.365 β†’ 0.426).
214
- Iters 100–1,000 contributed an additional 0.057 of val-loss improvement β€”
215
  diminishing but still positive at 1,000. Train/val gap at the end is 0.147
216
  (mild memorisation, val still trending down).
217
 
218
  ### Checkpoints
219
 
220
- All four intermediate checkpoints are published under `checkpoints/` for
221
- ablation studies and early-stopping analysis:
222
 
223
  - `0000250_adapters.safetensors`
224
  - `0000500_adapters.safetensors`
225
- - `0000750_adapters.safetensors`
226
- - `0001000_adapters.safetensors` (= the root `adapters.safetensors`)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
  ## Evaluation
229
 
@@ -233,7 +351,7 @@ ablation studies and early-stopping analysis:
233
  composed of 5 early-game, 8 midgame, and 7 oscillation states from two
234
  post-cutover production sessions (template `0462323c…`). The bench
235
  intentionally includes 7 states where the 31B teacher chose a
236
- `{tableau,discard}_to_foundation` move β€” the failure mode this distillation
237
  was most intended to fix.
238
 
239
  The 31B teacher's pick on each state is the production-recorded ground truth.
@@ -243,15 +361,16 @@ draw = 1, recycle = 1, illegal = 0) is the same scale used in the
243
  A single generation per state was used; future work should add multiple
244
  runs per state for variance estimation.
245
 
246
- ### Headline
247
 
248
- | metric | untuned base | this adapter | 31B teacher | Ξ” this vs base |
249
  |---|---:|---:|---:|---:|
250
- | JSON validity | 20 / 20 | **20 / 20** | β€” | 0 |
251
- | Illegal moves chosen | 1 / 20 | **0 / 20** | β€” | **βˆ’1** |
252
- | Teacher-pick agreement | 11 / 20 | 11 / 20 | β€” | 0 |
253
- | **Mean tier (all 20)** | 2.10 | **2.75** | 3.42 | **+0.65** |
254
- | **Gap to teacher** | **βˆ’1.32** | **βˆ’0.67** | β€” | **+0.65** (halved) |
 
255
 
256
  Teacher-pick agreement is unchanged in count but shifted in composition: the
257
  adapter recovered some agreements on foundation states and lost some on
@@ -261,44 +380,67 @@ captures it.
261
 
262
  ### Per category
263
 
264
- | category | n | untuned | adapter | teacher | Ξ” |
265
- |---|---:|---:|---:|---:|---:|
266
- | early | 5 | 2.60 | 3.20 | 4.20 | +0.60 |
267
- | midgame | 8 | 1.38 | 1.75 | 2.00 | +0.38 |
268
- | oscillation | 7 | 2.57 | **3.57** | 4.29 | **+1.00** |
269
 
270
- Oscillation gained the most β€” same category where the foundation-miss
271
  failure mode lived in the untuned base.
272
 
273
  ### Foundation-move recovery (the primary fine-tuning target)
274
 
275
- On 5 of 20 bench states the teacher chose a `tableau_to_foundation` move that
276
- the untuned adapter missed:
277
 
278
- | state | untuned pick | adapter pick | teacher pick | outcome |
279
  |---|---|---|---|---|
280
- | `early-3687a40eda7b` | shuffle | **foundation** | foundation (diff. idx) | recovered (tier 2 β†’ 6) |
281
- | `early-e6291973dd07` | shuffle | draw | foundation | regressed (2 β†’ 1) |
282
- | `midgame-4ab5735a4f20` | draw | draw | foundation | unchanged |
283
- | `oscillation-a774c0d22f24` | draw | shuffle | foundation | slight (+1) |
284
- | **`oscillation-bfb84ae55c3f`** | draw | **foundation** | foundation | **fully recovered** |
 
 
 
285
 
286
  `oscillation-bfb84a` is notable: previously a 3-experiment replicated failure
287
  mode (C0-Haiku missed 1/3 runs, A4-Haiku missed 1/3 runs, untuned 3n-E2B
288
- missed 3/3 runs). After this fine-tune, it is correctly solved.
 
 
 
 
 
 
 
289
 
290
- ### "Lost" agreements that are actually wins
291
 
292
- Two states where the adapter's disagreement with the teacher is a tier
293
- *improvement*:
294
 
295
- | state | untuned (= teacher) | adapter | tier change |
296
  |---|---|---|---:|
297
- | `midgame-031d9c9e3fe7` | shuffle (2) | waste_play (4) | +2 above teacher |
298
- | `oscillation-d0ff552ed744` | draw (1) | shuffle (2) | +1 above teacher |
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- The 31B teacher is not an oracle; it leaves some tier points on the table.
301
- The adapter has picked up a few of them.
 
302
 
303
  ### Reproducing the evaluation
304
 
@@ -319,28 +461,28 @@ Wall time ~5 min per arm on M5.
319
  ## Limitations
320
 
321
  - **N = 20 single-run eval.** The +0.65 tier delta is large enough to be
322
- directionally trustworthy, but per-state changes (especially 1- or
323
- 2-state foundation gains) should not be over-interpreted as guarantees on
324
- unseen positions.
325
  - **Heterogeneous training templates.** Training mixed pre-cutover legacy and
326
- current production prompt formats; eval is on post-cutover only. Effect
327
- on generalisation across template shift is unmeasured.
328
  - **No endgame states in bench.** Both source post-cutover sessions stalled
329
- ≀ 25 % progress (genuine sample), so the adapter's endgame behaviour is
330
- untested. Prior `gemma-4-31b-it` evidence suggests endgame is a different
331
- failure regime; treat extrapolation with caution.
332
  - **Trained on a teacher that itself loses ~ 55 % of games.** The adapter's
333
- ceiling is teacher-level play, not optimal play. The "lost agreements that
334
- are wins" rows hint there is room to outperform the teacher in places, but
335
- the dataset doesn't actively reward that β€” only teacher imitation.
336
  - **Memorisation risk.** Final train loss 0.222 vs val 0.369 shows mild
337
- divergence; pushing iters past ~1,500 without data augmentation is likely
338
- to widen this.
339
  - **Confidence field is suspect.** The teacher emits `confidence: 0.9 Β± 0.05`
340
- almost regardless of board state; the adapter learned this poorly-calibrated
341
- signal. Do not use `final_decision.confidence` for routing decisions.
342
  - **Apple-Silicon-only.** Distributed via `mlx`. CUDA/CPU inference would
343
- need conversion through `transformers` / PEFT, which is not validated here.
344
 
345
  ## Bias and ethical considerations
346
 
@@ -349,11 +491,11 @@ classical sense (race, gender, etc.) are not directly applicable. Worth
349
  noting:
350
 
351
  - The teacher (and therefore the adapter) inherits whatever value
352
- judgements are encoded in the harvester's prompt β€” including the rule
353
- "prefer revealing face-down cards before sending cards to foundations"
354
- which is a heuristic that loses to certain optimal lines.
355
  - Production use will lock in the teacher's playstyle. If the goal is a
356
- diverse advisor, training on a single teacher is the wrong objective.
357
 
358
  ## License
359
 
@@ -373,19 +515,19 @@ If you use this adapter, please cite:
373
 
374
  ```bibtex
375
  @misc{orapinpatipat2026solitaireadvisor,
376
- title = {Distilling a 31B Klondike Solitaire advisor into Gemma 3n E2B via MLX QLoRA},
377
- author = {Orapinpatipat, Chayut},
378
- year = {2026},
379
- month = may,
380
- howpublished = {\url{https://huggingface.co/chayuto/gemma-3n-e2b-it-solitaire-advisor-lora}},
381
- note = {LoRA adapter; v1 = 1,000-iter checkpoint},
382
  }
383
  ```
384
 
385
  ## Acknowledgements
386
 
387
  - Base model `mlx-community/gemma-3n-E2B-it-text-4bit-dwq` from the
388
- [`mlx-community`](https://huggingface.co/mlx-community) team.
389
  - Training framework `mlx-lm` from Apple Machine Learning Research.
390
  - Teacher model `gemma-4-31b-it` from Google DeepMind.
391
 
@@ -398,13 +540,14 @@ through T5) are documented in the
398
  [methodology notes](training/METHODOLOGY.md) in this repo.
399
 
400
  Planned next iterations:
401
- 1. Eval the 250/500/750-iter intermediate checkpoints to find the optimal
402
- early-stopping point.
403
- 2. Re-train at 2,000-3,000 iters once data growth justifies the extra
404
- training time (val loss was still falling at 1,000).
 
405
  3. Re-train on a post-cutover-only slice once β‰₯ 1,000 such rows are
406
- available (currently 351).
407
  4. **Re-publish on Gemma 4 E2B** as soon as `mlx-lm` ships the
408
- alternating-attention architecture support that 0.31.3 lacks. Will live
409
- at `chayuto/gemma-4-e2b-it-solitaire-advisor-lora`; this Gemma 3n repo
410
- will remain as the v1 fallback / reproducibility baseline.
 
23
  ~2B-effective **Gemma 3n E2B** text-only model, runnable locally on a 16 GB
24
  Apple Silicon Mac via MLX.
25
 
26
+ This is the **first distillation run**. The shipped weights
27
+ (`adapters.safetensors`) are the **iter-750 checkpoint**, the best of a
28
+ 1,000-iter training based on intermediate-checkpoint evaluation. It nearly
29
+ closes the tier-score gap to the teacher (-1.32 -> **-0.27**, ~80 %
30
+ recovery on the 20-state eval bench) and recovers **6 of 7 teacher-foundation
31
+ moves** the untuned base model missed, including a triple-replicated failure
32
+ state that had defeated three prior small-model experiments. The iter-1000
33
+ checkpoint (under `checkpoints/`) is also available but is mildly overfit:
34
+ mean tier 2.75 vs iter-750's 3.15, with 2 fewer foundation recoveries. See
35
+ the [learning-curve section](#learning-curve--why-iter-750-not-iter-1000) for
36
+ details.
37
 
38
  > **Why Gemma 3n and not Gemma 4 E2B?**
39
+ > The intended student was **Gemma 4 E2B**, the same series as the teacher
40
  > and the project's long-term target. As of `mlx-lm` 0.31.3 (the latest at
41
  > this writing), all mlx-community Gemma 4 E2B quants fail to load with a
42
  > 140-parameter architecture-mismatch error (layers 15-34's
 
44
  > implemented in mlx-lm's `Gemma4Model` class). The student here is therefore
45
  > the previous-generation **`gemma-3n-E2B`**, which `mlx-lm` fully supports.
46
  > A **Gemma 4 E2B variant of this adapter will be published** once `mlx-lm`
47
+ > ships the missing architecture support, same training script, same data,
48
+ > same eval bench, same methodology, at a separate repo
49
  > (`chayuto/gemma-4-e2b-it-solitaire-advisor-lora`).
50
 
51
  The teacher is `gemma-4-31b-it` (Google's Gemma 4 31B, accessed through a
 
63
  | Training framework | [`mlx-lm`](https://github.com/ml-explore/mlx-lm) 0.31.3 |
64
  | Hardware | Apple M5 16 GB unified memory (Metal GPU) |
65
  | Adapter size on disk | 45 MB per checkpoint |
66
+ | Iterations trained | 1,000 (shipped checkpoint = iter 750) |
67
  | Wall-clock training time | ~95 minutes |
68
  | Quantisation | base remains 4-bit; LoRA weights bfloat16 |
69
 
 
71
 
72
  **In scope.** Acting as a move-selection advisor inside a Klondike Solitaire
73
  client that already enforces game rules:
74
+ - Imperfect-information **draw-1** Klondike (one card flipped from stock per
75
+ draw); the advisor is shown the full visible state plus the count of
76
+ face-down cards
77
  - Single-turn decisions: given the prompt schema below, emit a single JSON
78
+ object choosing one of the offered legal moves
79
  - Local inference on Apple Silicon (8 GB+ unified memory) via `mlx-lm`
80
 
81
  **Out of scope.**
82
  - Open-ended chat or general-purpose text generation. The model has been
83
+ fine-tuned to a narrow JSON-emitting role and is expected to be measurably
84
+ worse than the base model at unrelated tasks.
85
  - Game-rule enforcement. The advisor selects from a `legalMoves` array
86
+ supplied in the prompt; it does not verify legality from first principles
87
+ and should not be trusted to do so.
88
  - Optimal Solitaire play. The distillation target is a 31B model that itself
89
+ is imperfect (it stalls on ~21 % of post-cutover games observed in
90
+ production). This adapter inherits that ceiling.
91
+ - Other Solitaire variants (Spider, FreeCell, etc.), out-of-distribution.
92
 
93
  ## Usage
94
 
95
+ ### Install
96
+
97
+ ```bash
98
+ # Apple Silicon, Python 3.12 venv recommended (mlx wheels are not on 3.14+)
99
+ python3.12 -m venv venv && source venv/bin/activate
100
+ pip install mlx mlx-lm huggingface-hub
101
+ ```
102
+
103
+ ### Quick start, Python
104
 
105
  ```python
106
+ from huggingface_hub import snapshot_download
107
  from mlx_lm import load, generate
108
 
109
+ # Pull the adapter once (~45 MB for the shipped iter-750 weights;
110
+ # checkpoints/ subdir adds another 180 MB if you want intermediate iters too).
111
+ adapter_path = snapshot_download(
112
+ repo_id="chayuto/gemma-3n-e2b-it-solitaire-advisor-lora",
113
+ allow_patterns=["adapters.safetensors", "adapter_config.json"],
114
+ )
115
+
116
+ # Load base + LoRA. First call also downloads the base model (~3 GB).
117
  model, tokenizer = load(
118
  "mlx-community/gemma-3n-E2B-it-text-4bit-dwq",
119
+ adapter_path=adapter_path,
120
  )
121
 
122
+ # Wrap your Solitaire prompt as a single user message and apply the chat
123
+ # template (this matches what the model was trained against).
124
+ solitaire_prompt = open("your_solitaire_prompt.txt").read()
125
  wrapped = tokenizer.apply_chat_template(
126
+ [{"role": "user", "content": solitaire_prompt}],
127
+ tokenize=False, add_generation_prompt=True,
128
  )
129
  response = generate(model, tokenizer, prompt=wrapped, max_tokens=512)
130
  print(response)
131
  ```
132
 
133
+ **Resource envelope** on Apple M5 16 GB:
134
+ - First load: 2.3 s (cached) + ~5 min one-time base-model download
135
+ - Per call: ~13 s wall, ~6.3 GB peak Metal memory
136
+ - The model is text-only and runs entirely on the local Metal GPU; no
137
+ network after initial download.
138
+
139
+ ### Concrete example
140
+
141
+ A real prompt from the eval bench is at
142
+ [`eval/sample_prompt.txt`](eval/sample_prompt.txt) (state `oscillation-bfb84a`,
143
+ 4471 chars, 4 cards on foundations, 18 face-down, Ace of Spades in waste).
144
+ Its tail looks like this:
145
+
146
+ ```
147
+ LEGAL MOVES (respond with the index of your chosen move):
148
+ [0] tableau_to_tableau Move KH plus 1 more from column 1 to column 2 (empty)
149
+ [1] discard_to_foundation Send AS from the waste to the spades foundation
150
+ [2] draw_card Draw the next card from the stock onto the waste
151
+
152
+ PROGRESS: foundation=4/52, face-down remaining=18, completion=8%
153
+ ...
154
+ Now choose the best move and reply with only the JSON object.
155
+ ```
156
+
157
+ The iter-750 adapter responds with:
158
 
159
+ ```json
160
+ {
161
+ "board_analysis": "The board is mostly face-down with 18 hidden cards remaining. Foundations are partially built (AH, AD, 2C, S). The waste pile contains the AS. The only legal tableau move is moving the KH and QC from column 1 to column 2, which does not reveal any hidden cards or create any new strategic opportunities.",
162
+ "strategic_plan": "The primary objective is to reveal the Ace of Spades and other low cards (2, 3, 5, 6, 8, 9) to the foundations and tableau columns. ...",
163
+ "final_decision": {
164
+ "move_index": 1,
165
+ "confidence": 0.95,
166
+ "alternative_move_index": -1
167
+ }
168
+ }
169
+ ```
170
+
171
+ `move_index: 1` correctly sends the Ace of Spades to the spades foundation
172
+ - the optimal play here, and a move the untuned base missed on 3/3 runs.
173
+ This is the headline `oscillation-bfb84a` recovery referenced in Β§Foundation
174
+ move recovery below.
175
+
176
+ ### Expected I/O contract
177
 
178
  **Input**: a single user-role message containing the full Solitaire game prompt
179
+ in the project's harvester format. The prompt must include `LEGAL MOVES`
180
+ block; the model is trained to index into it. See
181
+ [`eval/prompts_C0/*/prompt.txt`](eval/prompts_C0/) for 20 worked examples
182
+ across early/midgame/oscillation game states.
183
 
184
+ **Output**: a single JSON object with three required keys:
185
 
186
  ```json
187
  {
188
+ "board_analysis": "string, terse description of the visible state",
189
+ "strategic_plan": "string, why the chosen move is preferred",
190
+ "final_decision": {
191
  "move_index": 0,
192
  "confidence": 0.9,
193
  "alternative_move_index": 1
194
+ }
195
  }
196
  ```
197
 
198
+ - `move_index` is the **0-based index into the prompt's `legalMoves` array**.
199
+ This is the only field the client needs to consume.
200
+ - `confidence` is inherited from the teacher and is poorly calibrated
201
+ (saturates at 0.85-0.95). Do **not** route on it.
202
+ - `alternative_move_index` can be `-1` if no alternative is suggested.
203
+ - The strategic_plan prose sometimes references move indices inconsistently
204
+ (an artifact of the `PRIOR REASONING` section in the training prompts);
205
+ trust `final_decision.move_index`, not the narrative.
206
+
207
+ ### Robustness
208
+
209
+ Iter-750 produces valid JSON on 20/20 eval states. **Two of 20** choose
210
+ an illegal `move_index` (off-by-one on 2-move arrays). Clients should
211
+ defensively fall back to the highest-tier legal move when
212
+ `move_index >= len(legalMoves)`. The iter-1000 checkpoint under
213
+ `checkpoints/0001000_adapters.safetensors` eliminates both illegals at the
214
+ cost of 2 foundation moves, see the learning-curve section to choose.
215
 
216
  ## Training data
217
 
 
226
  three required keys, not from a stalled game). 25 distinct play sessions,
227
  heterogeneous prompt templates (~63 % pre-cutover legacy format, ~37 % the
228
  current production template `0462323c…`). Split at the session level
229
+ 80 / 10 / 10 -> 1,279 train / 126 val / 131 test.
230
 
231
  **License.** Released as a derived training corpus under **CC-BY-4.0** in
232
  the project's published dataset (separately staged). No personally
 
235
 
236
  **Known data-quality issues** the adapter inherits:
237
  - 11 % of source rows dropped by the ingest filter for malformed
238
+ `rawResponse`, root cause not yet localised in the harvester.
239
  - Teacher `confidence` field is saturated (median 0.90, never below 0.80)
240
+ even in lost games, not a reliable training signal; it is included in
241
+ completions but treated as suspect downstream.
242
  - Mixed prompt-template formats in training; eval is on the most-recent
243
+ template only.
244
  - No deck seed in logs (open harvester P0), so we cannot verify the
245
+ teacher's choices against solver-optimal play.
246
 
247
  ## Training procedure
248
 
 
262
  val_batches: 25
263
 
264
  lora_parameters:
265
+ rank: 16
266
+ scale: 2.0
267
+ dropout: 0.05
268
+ keys:
269
  - self_attn.q_proj
270
  - self_attn.k_proj
271
  - self_attn.v_proj
 
283
 
284
  | iter | train loss | val loss | peak MLX | wall (cumulative) |
285
  |---:|---:|---:|---:|---:|
286
+ | 1 | - | 6.365 | - | 0 m |
287
+ | 10 | 3.160 | - | 11.45 GB | ~1 m |
288
  | 100 | 0.388 | 0.426 | 11.45 GB | ~10 m |
289
  | 250 | (checkpoint) | (checkpoint) | 11.49 GB | ~25 m |
290
  | 500 | (checkpoint) | (checkpoint) | 11.49 GB | ~50 m |
291
  | 750 | (checkpoint) | (checkpoint) | 11.49 GB | ~75 m |
292
  | 1000 | **0.222** | **0.369** | 11.49 GB | ~95 m |
293
 
294
+ Most of the learning happened in the first 100 iters (val 6.365 -> 0.426).
295
+ Iters 100–1,000 contributed an additional 0.057 of val-loss improvement -
296
  diminishing but still positive at 1,000. Train/val gap at the end is 0.147
297
  (mild memorisation, val still trending down).
298
 
299
  ### Checkpoints
300
 
301
+ All four training checkpoints are published under `checkpoints/`:
 
302
 
303
  - `0000250_adapters.safetensors`
304
  - `0000500_adapters.safetensors`
305
+ - `0000750_adapters.safetensors` (**= the root `adapters.safetensors`**, best by tier score)
306
+ - `0001000_adapters.safetensors` (final iter; mildly overfit, see learning curve below)
307
+
308
+ ### Learning curve, why iter 750, not iter 1000
309
+
310
+ The 20-state eval bench was run against the untuned base and each of the
311
+ four saved checkpoints. The curve is **not monotonic**:
312
+
313
+ | checkpoint | mean tier | Ξ” vs teacher | Ξ” vs untuned | foundation recovery (of 7) | illegal | JSON valid |
314
+ |---:|---:|---:|---:|---:|---:|---:|
315
+ | untuned (iter 0) | 2.10 | -1.32 | 0.00 | 2 / 7 | 1 | 20 / 20 |
316
+ | iter 250 | 2.10 | -1.32 | 0.00 | 3 / 7 | 3 | 20 / 20 |
317
+ | iter 500 | 2.60 | -0.82 | +0.50 | 4 / 7 | 2 | 18 / 20 |
318
+ | **iter 750** | **3.15** | **-0.27** | **+1.05** | **6 / 7** | 2 | 20 / 20 |
319
+ | iter 1000 | 2.75 | -0.67 | +0.65 | 4 / 7 | 0 | 20 / 20 |
320
+
321
+ Key observations:
322
+
323
+ 1. **Iter 750 is the strategic peak.** Mean tier 3.15 is within 0.27 of the
324
+ 31B teacher's 3.42; 6 of 7 teacher-foundation states are correctly
325
+ recovered (vs only 2 of 7 untuned, 4 of 7 at iter 1000).
326
+ 2. **Iter 1000 has lost ground.** Two of the four foundation moves
327
+ recovered at iter 750 regressed to non-foundation choices by iter 1000.
328
+ Mean tier dropped from 3.15 to 2.75.
329
+ 3. **There is a real format / strategy tradeoff at iter 1000.** It is the
330
+ *only* checkpoint with zero illegal moves, but the strategic regression
331
+ outweighs the marginal format gain (iter 750's 2 illegal moves can be
332
+ handled client-side by falling back to a draw).
333
+ 4. **Iter 500 had a brief JSON-format instability** (2/20 generations
334
+ missed the JSON schema). This had recovered by iter 750. Worth flagging
335
+ as a known training-dynamics quirk on this dataset size.
336
+
337
+ If you want **strict format reliability** at the cost of strategic strength,
338
+ the iter-1000 weights under `checkpoints/0001000_adapters.safetensors` are
339
+ appropriate. For most use cases, the shipped iter-750 weights are the right
340
+ default.
341
+
342
+ Raw per-checkpoint scored eval results are published under `eval/`:
343
+ `posttune_at250.json`, `posttune_at500.json`, `posttune_at750.json`,
344
+ `posttune_at1000.json`, plus the aggregated `learning_curve.json`.
345
 
346
  ## Evaluation
347
 
 
351
  composed of 5 early-game, 8 midgame, and 7 oscillation states from two
352
  post-cutover production sessions (template `0462323c…`). The bench
353
  intentionally includes 7 states where the 31B teacher chose a
354
+ `{tableau,discard}_to_foundation` move, the failure mode this distillation
355
  was most intended to fix.
356
 
357
  The 31B teacher's pick on each state is the production-recorded ground truth.
 
361
  A single generation per state was used; future work should add multiple
362
  runs per state for variance estimation.
363
 
364
+ ### Headline (iter-750 shipped weights)
365
 
366
+ | metric | untuned base | this adapter (iter 750) | iter 1000 (for ref) | 31B teacher |
367
  |---|---:|---:|---:|---:|
368
+ | JSON validity | 20 / 20 | **20 / 20** | 20 / 20 | - |
369
+ | Illegal moves chosen | 1 / 20 | 2 / 20 | **0 / 20** | - |
370
+ | Teacher-pick agreement | 11 / 20 | 11 / 20 | 11 / 20 | - |
371
+ | **Mean tier (all 20)** | 2.10 | **3.15** | 2.75 | 3.42 |
372
+ | **Gap to teacher** | **-1.32** | **-0.27** | -0.67 | - |
373
+ | Foundation recovery (of 7 missed) | 2 / 7 | **6 / 7** | 4 / 7 | - |
374
 
375
  Teacher-pick agreement is unchanged in count but shifted in composition: the
376
  adapter recovered some agreements on foundation states and lost some on
 
380
 
381
  ### Per category
382
 
383
+ | category | n | untuned | adapter (iter 750) | iter 1000 | teacher | Ξ” adapter vs untuned |
384
+ |---|---:|---:|---:|---:|---:|---:|
385
+ | early | 5 | 2.60 | **4.20** | 3.20 | 4.20 | **+1.60** (matches teacher) |
386
+ | midgame | 8 | 1.38 | 2.12 | 1.75 | 2.00 | +0.74 (beats teacher mean) |
387
+ | oscillation | 7 | 2.57 | 3.57 | 3.57 | 4.29 | +1.00 |
388
 
389
+ Oscillation gained the most, same category where the foundation-miss
390
  failure mode lived in the untuned base.
391
 
392
  ### Foundation-move recovery (the primary fine-tuning target)
393
 
394
+ The bench includes 7 states where the teacher chose a foundation move.
395
+ At iter 750, **6 of 7 are correctly recovered**:
396
 
397
+ | state | untuned (iter 0) | iter 750 (shipped) | iter 1000 | teacher |
398
  |---|---|---|---|---|
399
+ | `early-3687a40eda7b` | shuffle | **foundation** | **foundation** | foundation |
400
+ | `early-e6291973dd07` | shuffle | **foundation** | draw | foundation |
401
+ | `midgame-4ab5735a4f20` | draw | **foundation** | draw | foundation |
402
+ | `oscillation-026f3139d6f2` | **foundation** | **foundation** | **foundation** | foundation |
403
+ | `oscillation-30700e2ca639` | **foundation** | **foundation** | **foundation** | foundation |
404
+ | `oscillation-a774c0d22f24` | draw | **foundation** | shuffle | foundation |
405
+ | `oscillation-bfb84ae55c3f` | draw | **foundation** | **foundation** | foundation |
406
+ | Recovered count | **2 / 7** | **6 / 7** | **4 / 7** | - |
407
 
408
  `oscillation-bfb84a` is notable: previously a 3-experiment replicated failure
409
  mode (C0-Haiku missed 1/3 runs, A4-Haiku missed 1/3 runs, untuned 3n-E2B
410
+ missed 3/3 runs). The adapter solves it from iter 250 onward and the
411
+ solution is stable through iter 1000.
412
+
413
+ The single state still missed at iter 750 (`midgame-4ab5735a4f20`) was also
414
+ the hardest at iter 1000 (still missed there too). It is a state where the
415
+ foundation move is at `move_index=1` of a 4-move array; the adapter
416
+ consistently prefers `move_index=0` (a draw). Probably needs targeted
417
+ training-data augmentation to fix.
418
 
419
+ ### Adapter strictly outperforms the teacher on three states
420
 
421
+ States where the iter-750 adapter's pick is a higher tier than the teacher's:
 
422
 
423
+ | state | teacher | iter-750 adapter | tier improvement |
424
  |---|---|---|---:|
425
+ | `midgame-0d463176c4be` | draw (1) | shuffle (2) | +1 |
426
+ | `midgame-a658537fe2ae` | draw (1) | shuffle (2) | +1 |
427
+ | `oscillation-21cc5243e1d8` | draw (1) | shuffle (2) | +1 |
428
+
429
+ These are all draw -> shuffle substitutions: when the teacher punted with a
430
+ draw, the adapter found a productive tableau move. The 31B teacher is not
431
+ an oracle; it leaves some tier points on the table that distillation has
432
+ picked up.
433
+
434
+ ### Two illegal moves remain at iter 750
435
+
436
+ | state | n legal | iter-750 chose | note |
437
+ |---|---:|---:|---|
438
+ | `midgame-81dc0fb02394` | 2 | 2 | off-by-one (same state that was illegal untuned) |
439
+ | `oscillation-d0ff552ed744` | 2 | 2 | off-by-one |
440
 
441
+ Both are choosing `move_index=2` on a 2-move array. Client code should
442
+ fall back to the highest-tier legal move. Iter-1000 fixes both but at the
443
+ cost of two foundation moves, net negative trade.
444
 
445
  ### Reproducing the evaluation
446
 
 
461
  ## Limitations
462
 
463
  - **N = 20 single-run eval.** The +0.65 tier delta is large enough to be
464
+ directionally trustworthy, but per-state changes (especially 1- or
465
+ 2-state foundation gains) should not be over-interpreted as guarantees on
466
+ unseen positions.
467
  - **Heterogeneous training templates.** Training mixed pre-cutover legacy and
468
+ current production prompt formats; eval is on post-cutover only. Effect
469
+ on generalisation across template shift is unmeasured.
470
  - **No endgame states in bench.** Both source post-cutover sessions stalled
471
+ ≀ 25 % progress (genuine sample), so the adapter's endgame behaviour is
472
+ untested. Prior `gemma-4-31b-it` evidence suggests endgame is a different
473
+ failure regime; treat extrapolation with caution.
474
  - **Trained on a teacher that itself loses ~ 55 % of games.** The adapter's
475
+ ceiling is teacher-level play, not optimal play. The "lost agreements that
476
+ are wins" rows hint there is room to outperform the teacher in places, but
477
+ the dataset doesn't actively reward that, only teacher imitation.
478
  - **Memorisation risk.** Final train loss 0.222 vs val 0.369 shows mild
479
+ divergence; pushing iters past ~1,500 without data augmentation is likely
480
+ to widen this.
481
  - **Confidence field is suspect.** The teacher emits `confidence: 0.9 Β± 0.05`
482
+ almost regardless of board state; the adapter learned this poorly-calibrated
483
+ signal. Do not use `final_decision.confidence` for routing decisions.
484
  - **Apple-Silicon-only.** Distributed via `mlx`. CUDA/CPU inference would
485
+ need conversion through `transformers` / PEFT, which is not validated here.
486
 
487
  ## Bias and ethical considerations
488
 
 
491
  noting:
492
 
493
  - The teacher (and therefore the adapter) inherits whatever value
494
+ judgements are encoded in the harvester's prompt, including the rule
495
+ "prefer revealing face-down cards before sending cards to foundations"
496
+ which is a heuristic that loses to certain optimal lines.
497
  - Production use will lock in the teacher's playstyle. If the goal is a
498
+ diverse advisor, training on a single teacher is the wrong objective.
499
 
500
  ## License
501
 
 
515
 
516
  ```bibtex
517
  @misc{orapinpatipat2026solitaireadvisor,
518
+ title = {Distilling a 31B Klondike Solitaire advisor into Gemma 3n E2B via MLX QLoRA},
519
+ author = {Orapinpatipat, Chayut},
520
+ year = {2026},
521
+ month = may,
522
+ howpublished = {\url{https://huggingface.co/chayuto/gemma-3n-e2b-it-solitaire-advisor-lora}},
523
+ note = {LoRA adapter; v1 = 1,000-iter checkpoint},
524
  }
525
  ```
526
 
527
  ## Acknowledgements
528
 
529
  - Base model `mlx-community/gemma-3n-E2B-it-text-4bit-dwq` from the
530
+ [`mlx-community`](https://huggingface.co/mlx-community) team.
531
  - Training framework `mlx-lm` from Apple Machine Learning Research.
532
  - Teacher model `gemma-4-31b-it` from Google DeepMind.
533
 
 
540
  [methodology notes](training/METHODOLOGY.md) in this repo.
541
 
542
  Planned next iterations:
543
+ 1. done ~~Eval the 250/500/750-iter intermediate checkpoints to find the
544
+ optimal early-stopping point.~~ (done, iter 750 selected as shipped weights)
545
+ 2. Targeted training-data augmentation on the one remaining
546
+ missed-foundation state (`midgame-4ab5735a4f20`) to push foundation
547
+ recovery from 6/7 to 7/7.
548
  3. Re-train on a post-cutover-only slice once β‰₯ 1,000 such rows are
549
+ available (currently 351). Should reduce template-shift confound.
550
  4. **Re-publish on Gemma 4 E2B** as soon as `mlx-lm` ships the
551
+ alternating-attention architecture support that 0.31.3 lacks. Will live
552
+ at `chayuto/gemma-4-e2b-it-solitaire-advisor-lora`; this Gemma 3n repo
553
+ will remain as the v1 fallback / reproducibility baseline.
adapters.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:60189aec804bad85ebb74c589fe3e08eff96d429521de1aef3028bf1bed214f5
3
  size 45116520
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7d9012f195919a48c2809f1d4b1dc7294f5485beef39bfadbd21d2eda23b2910
3
  size 45116520
eval/learning_curve.json ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "teacher_mean_tier": 3.421,
3
+ "checkpoints": [
4
+ {
5
+ "label": "untuned (iter 0)",
6
+ "n": 20,
7
+ "json_valid_count": 20,
8
+ "illegal_count": 1,
9
+ "agreement_count": 11,
10
+ "mean_tier_all": 2.1,
11
+ "delta_vs_teacher": -1.321,
12
+ "per_category": {
13
+ "early": 2.6,
14
+ "midgame": 1.375,
15
+ "oscillation": 2.571
16
+ },
17
+ "foundation_count": 2,
18
+ "foundation_mean_tier": 2.714
19
+ },
20
+ {
21
+ "label": "tuned @ iter 250",
22
+ "n": 20,
23
+ "json_valid_count": 20,
24
+ "illegal_count": 3,
25
+ "agreement_count": 7,
26
+ "mean_tier_all": 2.1,
27
+ "delta_vs_teacher": -1.321,
28
+ "per_category": {
29
+ "early": 2.2,
30
+ "midgame": 0.875,
31
+ "oscillation": 3.429
32
+ },
33
+ "foundation_count": 3,
34
+ "foundation_mean_tier": 3.571
35
+ },
36
+ {
37
+ "label": "tuned @ iter 500",
38
+ "n": 20,
39
+ "json_valid_count": 18,
40
+ "illegal_count": 2,
41
+ "agreement_count": 11,
42
+ "mean_tier_all": 2.6,
43
+ "delta_vs_teacher": -0.821,
44
+ "per_category": {
45
+ "early": 2.2,
46
+ "midgame": 2.0,
47
+ "oscillation": 3.571
48
+ },
49
+ "foundation_count": 4,
50
+ "foundation_mean_tier": 4.0
51
+ },
52
+ {
53
+ "label": "tuned @ iter 750",
54
+ "n": 20,
55
+ "json_valid_count": 20,
56
+ "illegal_count": 2,
57
+ "agreement_count": 11,
58
+ "mean_tier_all": 3.15,
59
+ "delta_vs_teacher": -0.271,
60
+ "per_category": {
61
+ "early": 4.2,
62
+ "midgame": 2.125,
63
+ "oscillation": 3.571
64
+ },
65
+ "foundation_count": 6,
66
+ "foundation_mean_tier": 5.286
67
+ },
68
+ {
69
+ "label": "tuned @ iter 1000",
70
+ "n": 20,
71
+ "json_valid_count": 20,
72
+ "illegal_count": 0,
73
+ "agreement_count": 11,
74
+ "mean_tier_all": 2.75,
75
+ "delta_vs_teacher": -0.671,
76
+ "per_category": {
77
+ "early": 3.2,
78
+ "midgame": 1.75,
79
+ "oscillation": 3.571
80
+ },
81
+ "foundation_count": 4,
82
+ "foundation_mean_tier": 4.0
83
+ }
84
+ ]
85
+ }
eval/posttune_at1000.json ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "mlx-community/gemma-3n-E2B-it-text-4bit-dwq",
3
+ "adapter_path": "adapters_t5",
4
+ "n": 20,
5
+ "overall_peak_gb": 6.37,
6
+ "mean_call_seconds": 13.28,
7
+ "json_valid_count": 20,
8
+ "agreement_count": 11,
9
+ "results": [
10
+ {
11
+ "state_id": "early-1dbcd96c5df6",
12
+ "category": "early",
13
+ "call_seconds": 12.33,
14
+ "call_peak_gb": 6.29,
15
+ "response_chars": 804,
16
+ "json_valid": true,
17
+ "e2b_move_index": 1,
18
+ "teacher_move_index": 1,
19
+ "agreement": true,
20
+ "e2b_tier": "draw",
21
+ "e2b_tier_score": 1,
22
+ "teacher_tier": "draw",
23
+ "teacher_tier_score": 1,
24
+ "n_legal": 2
25
+ },
26
+ {
27
+ "state_id": "early-3687a40eda7b",
28
+ "category": "early",
29
+ "call_seconds": 8.05,
30
+ "call_peak_gb": 6.27,
31
+ "response_chars": 662,
32
+ "json_valid": true,
33
+ "e2b_move_index": 2,
34
+ "teacher_move_index": 3,
35
+ "agreement": false,
36
+ "e2b_tier": "foundation",
37
+ "e2b_tier_score": 6,
38
+ "teacher_tier": "foundation",
39
+ "teacher_tier_score": 6,
40
+ "n_legal": 5
41
+ },
42
+ {
43
+ "state_id": "early-81deee72436d",
44
+ "category": "early",
45
+ "call_seconds": 16.81,
46
+ "call_peak_gb": 6.35,
47
+ "response_chars": 1292,
48
+ "json_valid": true,
49
+ "e2b_move_index": 0,
50
+ "teacher_move_index": 0,
51
+ "agreement": true,
52
+ "e2b_tier": "waste_play",
53
+ "e2b_tier_score": 4,
54
+ "teacher_tier": "waste_play",
55
+ "teacher_tier_score": 4,
56
+ "n_legal": 3
57
+ },
58
+ {
59
+ "state_id": "early-acd9af4ae639",
60
+ "category": "early",
61
+ "call_seconds": 11.54,
62
+ "call_peak_gb": 6.36,
63
+ "response_chars": 897,
64
+ "json_valid": true,
65
+ "e2b_move_index": 0,
66
+ "teacher_move_index": 0,
67
+ "agreement": true,
68
+ "e2b_tier": "waste_play",
69
+ "e2b_tier_score": 4,
70
+ "teacher_tier": "waste_play",
71
+ "teacher_tier_score": 4,
72
+ "n_legal": 2
73
+ },
74
+ {
75
+ "state_id": "early-e6291973dd07",
76
+ "category": "early",
77
+ "call_seconds": 14.1,
78
+ "call_peak_gb": 6.35,
79
+ "response_chars": 1084,
80
+ "json_valid": true,
81
+ "e2b_move_index": 0,
82
+ "teacher_move_index": 2,
83
+ "agreement": false,
84
+ "e2b_tier": "draw",
85
+ "e2b_tier_score": 1,
86
+ "teacher_tier": "foundation",
87
+ "teacher_tier_score": 6,
88
+ "n_legal": 3
89
+ },
90
+ {
91
+ "state_id": "midgame-031d9c9e3fe7",
92
+ "category": "midgame",
93
+ "call_seconds": 16.42,
94
+ "call_peak_gb": 6.34,
95
+ "response_chars": 1243,
96
+ "json_valid": true,
97
+ "e2b_move_index": 1,
98
+ "teacher_move_index": 0,
99
+ "agreement": false,
100
+ "e2b_tier": "waste_play",
101
+ "e2b_tier_score": 4,
102
+ "teacher_tier": "shuffle",
103
+ "teacher_tier_score": 2,
104
+ "n_legal": 6
105
+ },
106
+ {
107
+ "state_id": "midgame-0cef3a609da7",
108
+ "category": "midgame",
109
+ "call_seconds": 12.52,
110
+ "call_peak_gb": 6.31,
111
+ "response_chars": 899,
112
+ "json_valid": true,
113
+ "e2b_move_index": 0,
114
+ "teacher_move_index": 0,
115
+ "agreement": true,
116
+ "e2b_tier": "shuffle",
117
+ "e2b_tier_score": 2,
118
+ "teacher_tier": "shuffle",
119
+ "teacher_tier_score": 2,
120
+ "n_legal": 3
121
+ },
122
+ {
123
+ "state_id": "midgame-0d463176c4be",
124
+ "category": "midgame",
125
+ "call_seconds": 11.58,
126
+ "call_peak_gb": 6.36,
127
+ "response_chars": 816,
128
+ "json_valid": true,
129
+ "e2b_move_index": 0,
130
+ "teacher_move_index": 0,
131
+ "agreement": true,
132
+ "e2b_tier": "draw",
133
+ "e2b_tier_score": 1,
134
+ "teacher_tier": "draw",
135
+ "teacher_tier_score": 1,
136
+ "n_legal": 2
137
+ },
138
+ {
139
+ "state_id": "midgame-230df7b7160e",
140
+ "category": "midgame",
141
+ "call_seconds": 12.17,
142
+ "call_peak_gb": 6.34,
143
+ "response_chars": 936,
144
+ "json_valid": true,
145
+ "e2b_move_index": 1,
146
+ "teacher_move_index": 1,
147
+ "agreement": true,
148
+ "e2b_tier": "draw",
149
+ "e2b_tier_score": 1,
150
+ "teacher_tier": "draw",
151
+ "teacher_tier_score": 1,
152
+ "n_legal": 2
153
+ },
154
+ {
155
+ "state_id": "midgame-4ab5735a4f20",
156
+ "category": "midgame",
157
+ "call_seconds": 13.1,
158
+ "call_peak_gb": 6.34,
159
+ "response_chars": 993,
160
+ "json_valid": true,
161
+ "e2b_move_index": 0,
162
+ "teacher_move_index": 1,
163
+ "agreement": false,
164
+ "e2b_tier": "draw",
165
+ "e2b_tier_score": 1,
166
+ "teacher_tier": "foundation",
167
+ "teacher_tier_score": 6,
168
+ "n_legal": 4
169
+ },
170
+ {
171
+ "state_id": "midgame-81dc0fb02394",
172
+ "category": "midgame",
173
+ "call_seconds": 16.42,
174
+ "call_peak_gb": 6.37,
175
+ "response_chars": 1263,
176
+ "json_valid": true,
177
+ "e2b_move_index": 0,
178
+ "teacher_move_index": 1,
179
+ "agreement": false,
180
+ "e2b_tier": "shuffle",
181
+ "e2b_tier_score": 2,
182
+ "teacher_tier": "draw",
183
+ "teacher_tier_score": 1,
184
+ "n_legal": 2
185
+ },
186
+ {
187
+ "state_id": "midgame-823116ccc048",
188
+ "category": "midgame",
189
+ "call_seconds": 17.09,
190
+ "call_peak_gb": 6.34,
191
+ "response_chars": 1271,
192
+ "json_valid": true,
193
+ "e2b_move_index": 0,
194
+ "teacher_move_index": null,
195
+ "agreement": false,
196
+ "e2b_tier": "shuffle",
197
+ "e2b_tier_score": 2,
198
+ "teacher_tier": null,
199
+ "teacher_tier_score": null,
200
+ "n_legal": 5
201
+ },
202
+ {
203
+ "state_id": "midgame-a658537fe2ae",
204
+ "category": "midgame",
205
+ "call_seconds": 12.4,
206
+ "call_peak_gb": 6.33,
207
+ "response_chars": 927,
208
+ "json_valid": true,
209
+ "e2b_move_index": 1,
210
+ "teacher_move_index": 1,
211
+ "agreement": true,
212
+ "e2b_tier": "draw",
213
+ "e2b_tier_score": 1,
214
+ "teacher_tier": "draw",
215
+ "teacher_tier_score": 1,
216
+ "n_legal": 2
217
+ },
218
+ {
219
+ "state_id": "oscillation-026f3139d6f2",
220
+ "category": "oscillation",
221
+ "call_seconds": 14.49,
222
+ "call_peak_gb": 6.34,
223
+ "response_chars": 1077,
224
+ "json_valid": true,
225
+ "e2b_move_index": 0,
226
+ "teacher_move_index": 0,
227
+ "agreement": true,
228
+ "e2b_tier": "foundation",
229
+ "e2b_tier_score": 6,
230
+ "teacher_tier": "foundation",
231
+ "teacher_tier_score": 6,
232
+ "n_legal": 4
233
+ },
234
+ {
235
+ "state_id": "oscillation-21cc5243e1d8",
236
+ "category": "oscillation",
237
+ "call_seconds": 14.74,
238
+ "call_peak_gb": 6.33,
239
+ "response_chars": 1132,
240
+ "json_valid": true,
241
+ "e2b_move_index": 0,
242
+ "teacher_move_index": 0,
243
+ "agreement": true,
244
+ "e2b_tier": "draw",
245
+ "e2b_tier_score": 1,
246
+ "teacher_tier": "draw",
247
+ "teacher_tier_score": 1,
248
+ "n_legal": 2
249
+ },
250
+ {
251
+ "state_id": "oscillation-30700e2ca639",
252
+ "category": "oscillation",
253
+ "call_seconds": 10.2,
254
+ "call_peak_gb": 6.36,
255
+ "response_chars": 796,
256
+ "json_valid": true,
257
+ "e2b_move_index": 0,
258
+ "teacher_move_index": 0,
259
+ "agreement": true,
260
+ "e2b_tier": "foundation",
261
+ "e2b_tier_score": 6,
262
+ "teacher_tier": "foundation",
263
+ "teacher_tier_score": 6,
264
+ "n_legal": 3
265
+ },
266
+ {
267
+ "state_id": "oscillation-a774c0d22f24",
268
+ "category": "oscillation",
269
+ "call_seconds": 12.21,
270
+ "call_peak_gb": 6.34,
271
+ "response_chars": 795,
272
+ "json_valid": true,
273
+ "e2b_move_index": 0,
274
+ "teacher_move_index": 2,
275
+ "agreement": false,
276
+ "e2b_tier": "shuffle",
277
+ "e2b_tier_score": 2,
278
+ "teacher_tier": "foundation",
279
+ "teacher_tier_score": 6,
280
+ "n_legal": 5
281
+ },
282
+ {
283
+ "state_id": "oscillation-bfb84ae55c3f",
284
+ "category": "oscillation",
285
+ "call_seconds": 13.66,
286
+ "call_peak_gb": 6.34,
287
+ "response_chars": 1100,
288
+ "json_valid": true,
289
+ "e2b_move_index": 1,
290
+ "teacher_move_index": 1,
291
+ "agreement": true,
292
+ "e2b_tier": "foundation",
293
+ "e2b_tier_score": 6,
294
+ "teacher_tier": "foundation",
295
+ "teacher_tier_score": 6,
296
+ "n_legal": 3
297
+ },
298
+ {
299
+ "state_id": "oscillation-d0ff552ed744",
300
+ "category": "oscillation",
301
+ "call_seconds": 11.11,
302
+ "call_peak_gb": 6.31,
303
+ "response_chars": 821,
304
+ "json_valid": true,
305
+ "e2b_move_index": 1,
306
+ "teacher_move_index": 0,
307
+ "agreement": false,
308
+ "e2b_tier": "shuffle",
309
+ "e2b_tier_score": 2,
310
+ "teacher_tier": "draw",
311
+ "teacher_tier_score": 1,
312
+ "n_legal": 2
313
+ },
314
+ {
315
+ "state_id": "oscillation-d729a3bd2b7a",
316
+ "category": "oscillation",
317
+ "call_seconds": 14.66,
318
+ "call_peak_gb": 6.34,
319
+ "response_chars": 1118,
320
+ "json_valid": true,
321
+ "e2b_move_index": 0,
322
+ "teacher_move_index": 2,
323
+ "agreement": false,
324
+ "e2b_tier": "shuffle",
325
+ "e2b_tier_score": 2,
326
+ "teacher_tier": "waste_play",
327
+ "teacher_tier_score": 4,
328
+ "n_legal": 4
329
+ }
330
+ ]
331
+ }
eval/posttune_at250.json ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "mlx-community/gemma-3n-E2B-it-text-4bit-dwq",
3
+ "adapter_path": "adapters_t5_at250",
4
+ "n": 20,
5
+ "overall_peak_gb": 6.37,
6
+ "mean_call_seconds": 15.46,
7
+ "json_valid_count": 20,
8
+ "agreement_count": 7,
9
+ "results": [
10
+ {
11
+ "state_id": "early-1dbcd96c5df6",
12
+ "category": "early",
13
+ "call_seconds": 18.62,
14
+ "call_peak_gb": 6.29,
15
+ "response_chars": 1278,
16
+ "json_valid": true,
17
+ "e2b_move_index": 0,
18
+ "teacher_move_index": 1,
19
+ "agreement": false
20
+ },
21
+ {
22
+ "state_id": "early-3687a40eda7b",
23
+ "category": "early",
24
+ "call_seconds": 17.27,
25
+ "call_peak_gb": 6.27,
26
+ "response_chars": 1297,
27
+ "json_valid": true,
28
+ "e2b_move_index": 0,
29
+ "teacher_move_index": 3,
30
+ "agreement": false
31
+ },
32
+ {
33
+ "state_id": "early-81deee72436d",
34
+ "category": "early",
35
+ "call_seconds": 15.66,
36
+ "call_peak_gb": 6.35,
37
+ "response_chars": 1059,
38
+ "json_valid": true,
39
+ "e2b_move_index": 1,
40
+ "teacher_move_index": 0,
41
+ "agreement": false
42
+ },
43
+ {
44
+ "state_id": "early-acd9af4ae639",
45
+ "category": "early",
46
+ "call_seconds": 16.52,
47
+ "call_peak_gb": 6.36,
48
+ "response_chars": 1242,
49
+ "json_valid": true,
50
+ "e2b_move_index": 0,
51
+ "teacher_move_index": 0,
52
+ "agreement": true
53
+ },
54
+ {
55
+ "state_id": "early-e6291973dd07",
56
+ "category": "early",
57
+ "call_seconds": 12.47,
58
+ "call_peak_gb": 6.35,
59
+ "response_chars": 872,
60
+ "json_valid": true,
61
+ "e2b_move_index": 0,
62
+ "teacher_move_index": 2,
63
+ "agreement": false
64
+ },
65
+ {
66
+ "state_id": "midgame-031d9c9e3fe7",
67
+ "category": "midgame",
68
+ "call_seconds": 14.01,
69
+ "call_peak_gb": 6.34,
70
+ "response_chars": 1044,
71
+ "json_valid": true,
72
+ "e2b_move_index": 2,
73
+ "teacher_move_index": 0,
74
+ "agreement": false
75
+ },
76
+ {
77
+ "state_id": "midgame-0cef3a609da7",
78
+ "category": "midgame",
79
+ "call_seconds": 14.7,
80
+ "call_peak_gb": 6.31,
81
+ "response_chars": 1125,
82
+ "json_valid": true,
83
+ "e2b_move_index": 2,
84
+ "teacher_move_index": 0,
85
+ "agreement": false
86
+ },
87
+ {
88
+ "state_id": "midgame-0d463176c4be",
89
+ "category": "midgame",
90
+ "call_seconds": 17.76,
91
+ "call_peak_gb": 6.36,
92
+ "response_chars": 1540,
93
+ "json_valid": true,
94
+ "e2b_move_index": 0,
95
+ "teacher_move_index": 0,
96
+ "agreement": true
97
+ },
98
+ {
99
+ "state_id": "midgame-230df7b7160e",
100
+ "category": "midgame",
101
+ "call_seconds": 14.29,
102
+ "call_peak_gb": 6.34,
103
+ "response_chars": 1062,
104
+ "json_valid": true,
105
+ "e2b_move_index": 2,
106
+ "teacher_move_index": 1,
107
+ "agreement": false
108
+ },
109
+ {
110
+ "state_id": "midgame-4ab5735a4f20",
111
+ "category": "midgame",
112
+ "call_seconds": 16.07,
113
+ "call_peak_gb": 6.33,
114
+ "response_chars": 1284,
115
+ "json_valid": true,
116
+ "e2b_move_index": 2,
117
+ "teacher_move_index": 1,
118
+ "agreement": false
119
+ },
120
+ {
121
+ "state_id": "midgame-81dc0fb02394",
122
+ "category": "midgame",
123
+ "call_seconds": 13.61,
124
+ "call_peak_gb": 6.37,
125
+ "response_chars": 1039,
126
+ "json_valid": true,
127
+ "e2b_move_index": 2,
128
+ "teacher_move_index": 1,
129
+ "agreement": false
130
+ },
131
+ {
132
+ "state_id": "midgame-823116ccc048",
133
+ "category": "midgame",
134
+ "call_seconds": 14.83,
135
+ "call_peak_gb": 6.34,
136
+ "response_chars": 1038,
137
+ "json_valid": true,
138
+ "e2b_move_index": 0,
139
+ "teacher_move_index": null,
140
+ "agreement": false
141
+ },
142
+ {
143
+ "state_id": "midgame-a658537fe2ae",
144
+ "category": "midgame",
145
+ "call_seconds": 17.23,
146
+ "call_peak_gb": 6.33,
147
+ "response_chars": 1364,
148
+ "json_valid": true,
149
+ "e2b_move_index": 2,
150
+ "teacher_move_index": 1,
151
+ "agreement": false
152
+ },
153
+ {
154
+ "state_id": "oscillation-026f3139d6f2",
155
+ "category": "oscillation",
156
+ "call_seconds": 15.04,
157
+ "call_peak_gb": 6.34,
158
+ "response_chars": 1012,
159
+ "json_valid": true,
160
+ "e2b_move_index": 2,
161
+ "teacher_move_index": 0,
162
+ "agreement": false
163
+ },
164
+ {
165
+ "state_id": "oscillation-21cc5243e1d8",
166
+ "category": "oscillation",
167
+ "call_seconds": 15.57,
168
+ "call_peak_gb": 6.33,
169
+ "response_chars": 1125,
170
+ "json_valid": true,
171
+ "e2b_move_index": 0,
172
+ "teacher_move_index": 0,
173
+ "agreement": true
174
+ },
175
+ {
176
+ "state_id": "oscillation-30700e2ca639",
177
+ "category": "oscillation",
178
+ "call_seconds": 12.05,
179
+ "call_peak_gb": 6.36,
180
+ "response_chars": 940,
181
+ "json_valid": true,
182
+ "e2b_move_index": 0,
183
+ "teacher_move_index": 0,
184
+ "agreement": true
185
+ },
186
+ {
187
+ "state_id": "oscillation-a774c0d22f24",
188
+ "category": "oscillation",
189
+ "call_seconds": 13.98,
190
+ "call_peak_gb": 6.34,
191
+ "response_chars": 962,
192
+ "json_valid": true,
193
+ "e2b_move_index": 2,
194
+ "teacher_move_index": 2,
195
+ "agreement": true
196
+ },
197
+ {
198
+ "state_id": "oscillation-bfb84ae55c3f",
199
+ "category": "oscillation",
200
+ "call_seconds": 18.77,
201
+ "call_peak_gb": 6.34,
202
+ "response_chars": 1464,
203
+ "json_valid": true,
204
+ "e2b_move_index": 1,
205
+ "teacher_move_index": 1,
206
+ "agreement": true
207
+ },
208
+ {
209
+ "state_id": "oscillation-d0ff552ed744",
210
+ "category": "oscillation",
211
+ "call_seconds": 13.37,
212
+ "call_peak_gb": 6.31,
213
+ "response_chars": 966,
214
+ "json_valid": true,
215
+ "e2b_move_index": 0,
216
+ "teacher_move_index": 0,
217
+ "agreement": true
218
+ },
219
+ {
220
+ "state_id": "oscillation-d729a3bd2b7a",
221
+ "category": "oscillation",
222
+ "call_seconds": 17.42,
223
+ "call_peak_gb": 6.34,
224
+ "response_chars": 1320,
225
+ "json_valid": true,
226
+ "e2b_move_index": 1,
227
+ "teacher_move_index": 2,
228
+ "agreement": false
229
+ }
230
+ ]
231
+ }
eval/posttune_at500.json ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "mlx-community/gemma-3n-E2B-it-text-4bit-dwq",
3
+ "adapter_path": "adapters_t5_at500",
4
+ "n": 20,
5
+ "overall_peak_gb": 6.37,
6
+ "mean_call_seconds": 13.78,
7
+ "json_valid_count": 18,
8
+ "agreement_count": 11,
9
+ "results": [
10
+ {
11
+ "state_id": "early-1dbcd96c5df6",
12
+ "category": "early",
13
+ "call_seconds": 13.59,
14
+ "call_peak_gb": 6.29,
15
+ "response_chars": 944,
16
+ "json_valid": false,
17
+ "e2b_move_index": null,
18
+ "teacher_move_index": 1,
19
+ "agreement": false
20
+ },
21
+ {
22
+ "state_id": "early-3687a40eda7b",
23
+ "category": "early",
24
+ "call_seconds": 15.64,
25
+ "call_peak_gb": 6.27,
26
+ "response_chars": 1159,
27
+ "json_valid": true,
28
+ "e2b_move_index": 0,
29
+ "teacher_move_index": 3,
30
+ "agreement": false
31
+ },
32
+ {
33
+ "state_id": "early-81deee72436d",
34
+ "category": "early",
35
+ "call_seconds": 10.93,
36
+ "call_peak_gb": 6.35,
37
+ "response_chars": 818,
38
+ "json_valid": true,
39
+ "e2b_move_index": 0,
40
+ "teacher_move_index": 0,
41
+ "agreement": true
42
+ },
43
+ {
44
+ "state_id": "early-acd9af4ae639",
45
+ "category": "early",
46
+ "call_seconds": 10.03,
47
+ "call_peak_gb": 6.36,
48
+ "response_chars": 818,
49
+ "json_valid": true,
50
+ "e2b_move_index": 0,
51
+ "teacher_move_index": 0,
52
+ "agreement": true
53
+ },
54
+ {
55
+ "state_id": "early-e6291973dd07",
56
+ "category": "early",
57
+ "call_seconds": 17.64,
58
+ "call_peak_gb": 6.35,
59
+ "response_chars": 1312,
60
+ "json_valid": true,
61
+ "e2b_move_index": 0,
62
+ "teacher_move_index": 2,
63
+ "agreement": false
64
+ },
65
+ {
66
+ "state_id": "midgame-031d9c9e3fe7",
67
+ "category": "midgame",
68
+ "call_seconds": 11.51,
69
+ "call_peak_gb": 6.34,
70
+ "response_chars": 920,
71
+ "json_valid": true,
72
+ "e2b_move_index": 2,
73
+ "teacher_move_index": 0,
74
+ "agreement": false
75
+ },
76
+ {
77
+ "state_id": "midgame-0cef3a609da7",
78
+ "category": "midgame",
79
+ "call_seconds": 16.38,
80
+ "call_peak_gb": 6.31,
81
+ "response_chars": 1182,
82
+ "json_valid": true,
83
+ "e2b_move_index": 0,
84
+ "teacher_move_index": 0,
85
+ "agreement": true
86
+ },
87
+ {
88
+ "state_id": "midgame-0d463176c4be",
89
+ "category": "midgame",
90
+ "call_seconds": 14.66,
91
+ "call_peak_gb": 6.36,
92
+ "response_chars": 1248,
93
+ "json_valid": true,
94
+ "e2b_move_index": 0,
95
+ "teacher_move_index": 0,
96
+ "agreement": true
97
+ },
98
+ {
99
+ "state_id": "midgame-230df7b7160e",
100
+ "category": "midgame",
101
+ "call_seconds": 11.39,
102
+ "call_peak_gb": 6.34,
103
+ "response_chars": 887,
104
+ "json_valid": true,
105
+ "e2b_move_index": 1,
106
+ "teacher_move_index": 1,
107
+ "agreement": true
108
+ },
109
+ {
110
+ "state_id": "midgame-4ab5735a4f20",
111
+ "category": "midgame",
112
+ "call_seconds": 11.08,
113
+ "call_peak_gb": 6.33,
114
+ "response_chars": 866,
115
+ "json_valid": true,
116
+ "e2b_move_index": 1,
117
+ "teacher_move_index": 1,
118
+ "agreement": true
119
+ },
120
+ {
121
+ "state_id": "midgame-81dc0fb02394",
122
+ "category": "midgame",
123
+ "call_seconds": 19.54,
124
+ "call_peak_gb": 6.37,
125
+ "response_chars": 1465,
126
+ "json_valid": true,
127
+ "e2b_move_index": 1,
128
+ "teacher_move_index": 1,
129
+ "agreement": true
130
+ },
131
+ {
132
+ "state_id": "midgame-823116ccc048",
133
+ "category": "midgame",
134
+ "call_seconds": 15.73,
135
+ "call_peak_gb": 6.34,
136
+ "response_chars": 1239,
137
+ "json_valid": true,
138
+ "e2b_move_index": 0,
139
+ "teacher_move_index": null,
140
+ "agreement": false
141
+ },
142
+ {
143
+ "state_id": "midgame-a658537fe2ae",
144
+ "category": "midgame",
145
+ "call_seconds": 15.14,
146
+ "call_peak_gb": 6.33,
147
+ "response_chars": 1126,
148
+ "json_valid": true,
149
+ "e2b_move_index": 0,
150
+ "teacher_move_index": 1,
151
+ "agreement": false
152
+ },
153
+ {
154
+ "state_id": "oscillation-026f3139d6f2",
155
+ "category": "oscillation",
156
+ "call_seconds": 15.71,
157
+ "call_peak_gb": 6.34,
158
+ "response_chars": 1093,
159
+ "json_valid": true,
160
+ "e2b_move_index": 0,
161
+ "teacher_move_index": 0,
162
+ "agreement": true
163
+ },
164
+ {
165
+ "state_id": "oscillation-21cc5243e1d8",
166
+ "category": "oscillation",
167
+ "call_seconds": 13.32,
168
+ "call_peak_gb": 6.33,
169
+ "response_chars": 1040,
170
+ "json_valid": false,
171
+ "e2b_move_index": null,
172
+ "teacher_move_index": 0,
173
+ "agreement": false
174
+ },
175
+ {
176
+ "state_id": "oscillation-30700e2ca639",
177
+ "category": "oscillation",
178
+ "call_seconds": 13.18,
179
+ "call_peak_gb": 6.36,
180
+ "response_chars": 1056,
181
+ "json_valid": true,
182
+ "e2b_move_index": 0,
183
+ "teacher_move_index": 0,
184
+ "agreement": true
185
+ },
186
+ {
187
+ "state_id": "oscillation-a774c0d22f24",
188
+ "category": "oscillation",
189
+ "call_seconds": 11.23,
190
+ "call_peak_gb": 6.34,
191
+ "response_chars": 818,
192
+ "json_valid": true,
193
+ "e2b_move_index": 3,
194
+ "teacher_move_index": 2,
195
+ "agreement": false
196
+ },
197
+ {
198
+ "state_id": "oscillation-bfb84ae55c3f",
199
+ "category": "oscillation",
200
+ "call_seconds": 11.96,
201
+ "call_peak_gb": 6.34,
202
+ "response_chars": 907,
203
+ "json_valid": true,
204
+ "e2b_move_index": 1,
205
+ "teacher_move_index": 1,
206
+ "agreement": true
207
+ },
208
+ {
209
+ "state_id": "oscillation-d0ff552ed744",
210
+ "category": "oscillation",
211
+ "call_seconds": 11.67,
212
+ "call_peak_gb": 6.33,
213
+ "response_chars": 878,
214
+ "json_valid": true,
215
+ "e2b_move_index": 1,
216
+ "teacher_move_index": 0,
217
+ "agreement": false
218
+ },
219
+ {
220
+ "state_id": "oscillation-d729a3bd2b7a",
221
+ "category": "oscillation",
222
+ "call_seconds": 15.23,
223
+ "call_peak_gb": 6.34,
224
+ "response_chars": 1167,
225
+ "json_valid": true,
226
+ "e2b_move_index": 2,
227
+ "teacher_move_index": 2,
228
+ "agreement": true
229
+ }
230
+ ]
231
+ }
eval/posttune_at750.json ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "mlx-community/gemma-3n-E2B-it-text-4bit-dwq",
3
+ "adapter_path": "adapters_t5_at750",
4
+ "n": 20,
5
+ "overall_peak_gb": 6.37,
6
+ "mean_call_seconds": 14.75,
7
+ "json_valid_count": 20,
8
+ "agreement_count": 11,
9
+ "results": [
10
+ {
11
+ "state_id": "early-1dbcd96c5df6",
12
+ "category": "early",
13
+ "call_seconds": 16.85,
14
+ "call_peak_gb": 6.29,
15
+ "response_chars": 1316,
16
+ "json_valid": true,
17
+ "e2b_move_index": 1,
18
+ "teacher_move_index": 1,
19
+ "agreement": true
20
+ },
21
+ {
22
+ "state_id": "early-3687a40eda7b",
23
+ "category": "early",
24
+ "call_seconds": 8.67,
25
+ "call_peak_gb": 6.27,
26
+ "response_chars": 695,
27
+ "json_valid": true,
28
+ "e2b_move_index": 2,
29
+ "teacher_move_index": 3,
30
+ "agreement": false
31
+ },
32
+ {
33
+ "state_id": "early-81deee72436d",
34
+ "category": "early",
35
+ "call_seconds": 10.9,
36
+ "call_peak_gb": 6.35,
37
+ "response_chars": 795,
38
+ "json_valid": true,
39
+ "e2b_move_index": 0,
40
+ "teacher_move_index": 0,
41
+ "agreement": true
42
+ },
43
+ {
44
+ "state_id": "early-acd9af4ae639",
45
+ "category": "early",
46
+ "call_seconds": 10.64,
47
+ "call_peak_gb": 6.36,
48
+ "response_chars": 798,
49
+ "json_valid": true,
50
+ "e2b_move_index": 0,
51
+ "teacher_move_index": 0,
52
+ "agreement": true
53
+ },
54
+ {
55
+ "state_id": "early-e6291973dd07",
56
+ "category": "early",
57
+ "call_seconds": 11.33,
58
+ "call_peak_gb": 6.35,
59
+ "response_chars": 946,
60
+ "json_valid": true,
61
+ "e2b_move_index": 2,
62
+ "teacher_move_index": 2,
63
+ "agreement": true
64
+ },
65
+ {
66
+ "state_id": "midgame-031d9c9e3fe7",
67
+ "category": "midgame",
68
+ "call_seconds": 16.98,
69
+ "call_peak_gb": 6.34,
70
+ "response_chars": 1257,
71
+ "json_valid": true,
72
+ "e2b_move_index": 0,
73
+ "teacher_move_index": 0,
74
+ "agreement": true
75
+ },
76
+ {
77
+ "state_id": "midgame-0cef3a609da7",
78
+ "category": "midgame",
79
+ "call_seconds": 16.36,
80
+ "call_peak_gb": 6.31,
81
+ "response_chars": 1073,
82
+ "json_valid": true,
83
+ "e2b_move_index": 1,
84
+ "teacher_move_index": 0,
85
+ "agreement": false
86
+ },
87
+ {
88
+ "state_id": "midgame-0d463176c4be",
89
+ "category": "midgame",
90
+ "call_seconds": 16.43,
91
+ "call_peak_gb": 6.36,
92
+ "response_chars": 1365,
93
+ "json_valid": true,
94
+ "e2b_move_index": 1,
95
+ "teacher_move_index": 0,
96
+ "agreement": false
97
+ },
98
+ {
99
+ "state_id": "midgame-230df7b7160e",
100
+ "category": "midgame",
101
+ "call_seconds": 16.91,
102
+ "call_peak_gb": 6.34,
103
+ "response_chars": 1346,
104
+ "json_valid": true,
105
+ "e2b_move_index": 1,
106
+ "teacher_move_index": 1,
107
+ "agreement": true
108
+ },
109
+ {
110
+ "state_id": "midgame-4ab5735a4f20",
111
+ "category": "midgame",
112
+ "call_seconds": 17.47,
113
+ "call_peak_gb": 6.33,
114
+ "response_chars": 1363,
115
+ "json_valid": true,
116
+ "e2b_move_index": 1,
117
+ "teacher_move_index": 1,
118
+ "agreement": true
119
+ },
120
+ {
121
+ "state_id": "midgame-81dc0fb02394",
122
+ "category": "midgame",
123
+ "call_seconds": 18.22,
124
+ "call_peak_gb": 6.37,
125
+ "response_chars": 1427,
126
+ "json_valid": true,
127
+ "e2b_move_index": 2,
128
+ "teacher_move_index": 1,
129
+ "agreement": false
130
+ },
131
+ {
132
+ "state_id": "midgame-823116ccc048",
133
+ "category": "midgame",
134
+ "call_seconds": 17.98,
135
+ "call_peak_gb": 6.34,
136
+ "response_chars": 1284,
137
+ "json_valid": true,
138
+ "e2b_move_index": 0,
139
+ "teacher_move_index": null,
140
+ "agreement": false
141
+ },
142
+ {
143
+ "state_id": "midgame-a658537fe2ae",
144
+ "category": "midgame",
145
+ "call_seconds": 15.3,
146
+ "call_peak_gb": 6.33,
147
+ "response_chars": 1151,
148
+ "json_valid": true,
149
+ "e2b_move_index": 0,
150
+ "teacher_move_index": 1,
151
+ "agreement": false
152
+ },
153
+ {
154
+ "state_id": "oscillation-026f3139d6f2",
155
+ "category": "oscillation",
156
+ "call_seconds": 12.69,
157
+ "call_peak_gb": 6.34,
158
+ "response_chars": 959,
159
+ "json_valid": true,
160
+ "e2b_move_index": 0,
161
+ "teacher_move_index": 0,
162
+ "agreement": true
163
+ },
164
+ {
165
+ "state_id": "oscillation-21cc5243e1d8",
166
+ "category": "oscillation",
167
+ "call_seconds": 17.12,
168
+ "call_peak_gb": 6.33,
169
+ "response_chars": 1345,
170
+ "json_valid": true,
171
+ "e2b_move_index": 1,
172
+ "teacher_move_index": 0,
173
+ "agreement": false
174
+ },
175
+ {
176
+ "state_id": "oscillation-30700e2ca639",
177
+ "category": "oscillation",
178
+ "call_seconds": 13.23,
179
+ "call_peak_gb": 6.36,
180
+ "response_chars": 1020,
181
+ "json_valid": true,
182
+ "e2b_move_index": 2,
183
+ "teacher_move_index": 0,
184
+ "agreement": false
185
+ },
186
+ {
187
+ "state_id": "oscillation-a774c0d22f24",
188
+ "category": "oscillation",
189
+ "call_seconds": 16.5,
190
+ "call_peak_gb": 6.34,
191
+ "response_chars": 1193,
192
+ "json_valid": true,
193
+ "e2b_move_index": 2,
194
+ "teacher_move_index": 2,
195
+ "agreement": true
196
+ },
197
+ {
198
+ "state_id": "oscillation-bfb84ae55c3f",
199
+ "category": "oscillation",
200
+ "call_seconds": 11.69,
201
+ "call_peak_gb": 6.34,
202
+ "response_chars": 934,
203
+ "json_valid": true,
204
+ "e2b_move_index": 1,
205
+ "teacher_move_index": 1,
206
+ "agreement": true
207
+ },
208
+ {
209
+ "state_id": "oscillation-d0ff552ed744",
210
+ "category": "oscillation",
211
+ "call_seconds": 13.85,
212
+ "call_peak_gb": 6.31,
213
+ "response_chars": 1099,
214
+ "json_valid": true,
215
+ "e2b_move_index": 2,
216
+ "teacher_move_index": 0,
217
+ "agreement": false
218
+ },
219
+ {
220
+ "state_id": "oscillation-d729a3bd2b7a",
221
+ "category": "oscillation",
222
+ "call_seconds": 15.94,
223
+ "call_peak_gb": 6.34,
224
+ "response_chars": 1312,
225
+ "json_valid": true,
226
+ "e2b_move_index": 2,
227
+ "teacher_move_index": 2,
228
+ "agreement": true
229
+ }
230
+ ]
231
+ }
training/METHODOLOGY.md CHANGED
@@ -1,4 +1,4 @@
1
- # Methodology β€” training & evaluation
2
 
3
  This document captures the methodology behind the v1 adapter
4
  (`adapters.safetensors`, 1,000-iter LoRA checkpoint). It is intentionally
@@ -7,7 +7,7 @@ reproduce the work.
7
 
8
  ## 1. Motivation
9
 
10
- The project goal is a *local* Klondike Solitaire advisor β€” a small LLM that
11
  runs on consumer Apple Silicon hardware (16 GB unified memory, Metal GPU)
12
  and matches the move-selection quality of a hosted 31B teacher
13
  (`gemma-4-31b-it`). The teacher is too large to run locally; distillation
@@ -15,15 +15,15 @@ into a small student is the path to a usable local product.
15
 
16
  Klondike was chosen as the domain because:
17
 
18
- 1. Move-by-move strategy is *easy to score* β€” every legal move falls into
19
- one of a small set of types (foundation / reveal / waste_play / shuffle /
20
- draw / recycle) with an obvious ordinal value structure.
21
  2. The 31B teacher already has a deployed harvester, so production-quality
22
- training labels accumulate naturally.
23
  3. Failure modes are concrete and replicable. The previous prompt-format
24
- study (Phase 1.5) had already identified the "foundation-miss" anti-pattern
25
- as the single largest source of suboptimal play in small models β€” giving
26
- the distillation a sharp target to aim at.
27
 
28
  ## 2. Architectural decisions
29
 
@@ -32,7 +32,7 @@ Klondike was chosen as the domain because:
32
  The original target was **Gemma 4 E2B** (~2B effective parameters, text-only).
33
  At the time of this run, `mlx-lm` 0.31.3 was the latest published version
34
  and could not load any `Gemma4ForConditionalGeneration` variant (all four
35
- mlx-community quants tested failed identically β€” see the project's
36
  [T2 progress notes](https://github.com/chayuto/solitaire-analytics/blob/main/gemma4_finetune/tier2_progress.txt)).
37
  The structural issue is that mlx-lm's `Gemma4Model` class only implements
38
  the first 15 of 35 attention layers; the alternating-attention pattern with
@@ -64,7 +64,7 @@ quantisation step.
64
 
65
  Production prompts are 1,000-2,600 tokens. The training config uses
66
  `max_seq_length=2048`. At iter 1 we saw a warning that the longest single
67
- example was 2,298 tokens β€” i.e., ~5-10 % of training tails are silently
68
  truncated at the chosen budget. Trade-off: raising `max_seq_length` to 2,624
69
  would eliminate the truncation but push activation memory past the 16 GB
70
  envelope. We accepted the truncation.
@@ -87,10 +87,10 @@ The training-eligible filter (`prepare_dataset.py`) keeps rows where:
87
  1. `outcome == "success"` (the teacher actually returned a response)
88
  2. `rawResponse` parses as JSON
89
  3. The parsed JSON contains all three keys: `board_analysis`,
90
- `strategic_plan`, `final_decision`
91
  4. (Upstream filter, applied during ingest) the row is not from a stalled
92
- game β€” defined as foundation count + face-down count unchanged for β‰₯ 25
93
- consecutive turns
94
 
95
  Of 1,730 candidate rows, 1,536 (88.8 %) survived this filter for training.
96
  The 11.2 % drop rate from a single field-presence check is a known
@@ -137,11 +137,11 @@ contributes gradient.
137
 
138
  | iter | train loss | val loss | wall (cumulative) |
139
  |---:|---:|---:|---:|
140
- | 1 | β€” | 6.365 | 0 m |
141
- | 10 | 3.160 | β€” | ~1 m |
142
- | 20 | 0.943 | β€” | ~2 m |
143
- | 30 | 0.580 | β€” | ~3 m |
144
- | 50 | 0.508 | β€” | ~5 m |
145
  | 100 | 0.388 | 0.426 | ~10 m |
146
  | 250 | (checkpoint) | (checkpoint) | ~25 m |
147
  | 500 | (checkpoint) | (checkpoint) | ~50 m |
@@ -149,7 +149,7 @@ contributes gradient.
149
  | 1000 | 0.222 | 0.369 | ~95 m |
150
 
151
  Most learning happens in the first 100 iters. Iters 100-1,000 contribute
152
- an additional 0.057 of val-loss improvement β€” diminishing but still positive.
153
  Train/val gap at the end is 0.147; the val curve is still trending down but
154
  slowly. Pushing past 2,000 iters without data augmentation is likely to
155
  widen the gap.
@@ -172,16 +172,16 @@ bench, all rendered against the current production prompt template
172
 
173
  - **5 early-game** (foundation count < 4)
174
  - **8 midgame** (foundation count 4-25)
175
- - **0 endgame** β€” both source post-cutover sessions stalled before reaching
176
- endgame, so this category was unavailable. This is a real limitation of
177
- the bench, acknowledged.
178
- - **7 oscillation** β€” states where the recent moves indicate the teacher
179
- was looping between draws and tableau shuffles without progressing
180
- foundations or revealing face-downs
181
 
182
  Of these, **7 states have a `{tableau,discard}_to_foundation` move
183
  available** in the legalMoves array. These are the foundation-move test
184
- ground β€” the failure mode this fine-tune was most intended to fix.
185
 
186
  ### 6.2 Scoring
187
 
@@ -189,11 +189,11 @@ The same tier-score scale used in the Phase 1.5 prompt-format study:
189
 
190
  | move type | tier | rationale |
191
  |---|---:|---|
192
- | `tableau_to_foundation` | 6 | maximally productive β€” advances win-progress |
193
  | `discard_to_foundation` | 6 | same |
194
  | `*_reveal` (move that flips a face-down) | 5 | unlocks information |
195
  | `discard_to_tableau` (waste play that lands productively) | 4 | activates a stale waste card |
196
- | `tableau_to_tableau` (no reveal) | 2 | "shuffle" β€” preserves options, no info gain |
197
  | `draw_card` | 1 | always available but rarely strategically optimal |
198
  | `recycle_stock` | 1 | costs nothing but exposes no new state |
199
  | illegal (chosen `move_index` not in `legalMoves`) | 0 | failure |
@@ -201,9 +201,9 @@ The same tier-score scale used in the Phase 1.5 prompt-format study:
201
  ### 6.3 Comparison points
202
 
203
  - **Untuned base** (`mlx-community/gemma-3n-E2B-it-text-4bit-dwq` with no
204
- adapter): the floor we are improving from.
205
  - **31B teacher** (`gemma-4-31b-it`): the production-recorded picks on the
206
- same turns; the ceiling we are distilling toward.
207
  - **This adapter** (1,000-iter LoRA): the result being reported.
208
 
209
  A single inference per state per arm was used. Future work should add
@@ -211,13 +211,54 @@ multi-run variance estimation (the Phase 1.5 study used 3 runs per state).
211
 
212
  ### 6.4 Headline results
213
 
214
- | metric | untuned base | this adapter | 31B teacher | Ξ” adapter vs base |
 
 
 
 
215
  |---|---:|---:|---:|---:|
216
- | JSON validity | 20 / 20 | 20 / 20 | β€” | 0 |
217
- | Illegal moves | 1 / 20 | 0 / 20 | β€” | **βˆ’1** |
218
- | Teacher agreement | 11 / 20 | 11 / 20 | β€” | 0 |
219
- | **Mean tier (all 20)** | 2.10 | 2.75 | 3.42 | **+0.65** |
220
- | **Gap to teacher** | βˆ’1.32 | βˆ’0.67 | β€” | **halved** |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
  Detailed per-state, per-category, and foundation-recovery breakdowns are in
223
  the [model card](../README.md).
@@ -225,27 +266,27 @@ the [model card](../README.md).
225
  ## 7. Threats to validity
226
 
227
  - **Single eval bench (N = 20)**. The +0.65 delta is large enough that
228
- noise alone is unlikely to produce it, but per-state changes (especially
229
- on small subsets like the 7 foundation states) have wide effective
230
- confidence intervals. A larger or multiply-resampled bench would tighten
231
- the estimate.
232
  - **Template confound**. Training was on heterogeneous templates (~63 %
233
- legacy, ~37 % current); eval is on current-only. We cannot disentangle
234
- whether the +0.65 is from learning Solitaire reasoning or from learning
235
- the current template's surface form. The fact that the gain is consistent
236
- across categories suggests the former, but we cannot prove it.
237
  - **Teacher is not ground truth**. The 31B teacher is itself a flawed
238
- player; matching it more closely is the *training* objective but the
239
- *real* objective is "play better Solitaire". Two bench states show the
240
- adapter strictly outperforming the teacher on tier score; we have no way
241
- to know if those are flukes or evidence of generalised improvement.
242
  - **No game-level eval**. We evaluated single-turn decisions on a frozen
243
- set of states. The real product question β€” "does this adapter win more
244
- games end-to-end than the untuned base?" β€” was not measured. This is the
245
- most important deferred experiment.
246
  - **Memorisation as iters grow**. The train/val gap (0.222 vs 0.369) is
247
- modest at iter 1,000 but trending widen. Future iters at this dataset
248
- size should be paired with data augmentation or stronger regularisation.
249
 
250
  ## 8. Reproduction
251
 
@@ -259,11 +300,11 @@ python3.12 -m venv venv
259
  source venv/bin/activate
260
  pip install mlx mlx-lm
261
 
262
- # 3. (Re)train β€” needs the training dataset (separately staged for HF datasets)
263
  # Hyperparameters identical to v1 are in training/lora_config.yaml
264
  mlx_lm.lora --config training/lora_config.yaml \
265
- --data <your-prepared-dataset-dir> \
266
- --adapter-path my_adapters
267
 
268
  # 4. Re-evaluate
269
  python eval/baseline_n20_runner.py
@@ -280,38 +321,38 @@ production prompts). The bench files and the teacher's recorded picks
280
 
281
  ```
282
  chayuto/gemma-3n-e2b-it-solitaire-advisor-lora/
283
- β”œβ”€β”€ README.md # model card (entry point)
284
- β”œβ”€β”€ adapter_config.json # mlx-lm LoRA config (live)
285
- β”œβ”€β”€ adapters.safetensors # final (= iter 1000) adapter weights
286
  β”œβ”€β”€ checkpoints/
287
- β”‚ β”œβ”€β”€ 0000250_adapters.safetensors
288
- β”‚ β”œβ”€β”€ 0000500_adapters.safetensors
289
- β”‚ β”œβ”€β”€ 0000750_adapters.safetensors
290
- β”‚ └── 0001000_adapters.safetensors
291
  β”œβ”€β”€ training/
292
- β”‚ β”œβ”€β”€ METHODOLOGY.md # this file
293
- β”‚ β”œβ”€β”€ lora_config.yaml # production training config
294
- β”‚ └── prepare_dataset.py # data prep script
295
  └── eval/
296
- β”œβ”€β”€ baseline_n20.json # untuned-base eval (scored)
297
- β”œβ”€β”€ posttune.json # this-adapter eval (scored)
298
- β”œβ”€β”€ teacher_picks_n20.json # ground-truth teacher picks
299
- β”œβ”€β”€ prompts_C0/ # 20 rendered production prompts
300
- β”œβ”€β”€ baseline_n20_runner.py
301
- └── posttune_n20_runner.py
302
  ```
303
 
304
  ## 10. Open questions
305
 
306
  Things this run did *not* answer, and should:
307
 
308
- 1. What's the optimal stopping iter? β€” eval each of the 250/500/750
309
- checkpoints against the same bench; pick the val-loss / tier-score elbow.
310
- 2. What's the marginal value of additional training? β€” eval at 2,000 iters
311
- to see whether the val curve has actually flattened.
312
- 3. Does this adapter actually win more games? β€” game-level (multi-turn,
313
- stateful) eval on a held-out set of seeds.
314
  4. How much of the +0.65 is from the new template's surface form vs from
315
- real Solitaire reasoning? β€” re-train on a post-cutover-only slice once
316
- one is large enough (currently 351 rows; need ~1,000+) and compare.
317
  5. How does this compare to the Gemma 4 E2B target whenever mlx-lm catches up?
 
1
+ # Methodology, training & evaluation
2
 
3
  This document captures the methodology behind the v1 adapter
4
  (`adapters.safetensors`, 1,000-iter LoRA checkpoint). It is intentionally
 
7
 
8
  ## 1. Motivation
9
 
10
+ The project goal is a *local* Klondike Solitaire advisor, a small LLM that
11
  runs on consumer Apple Silicon hardware (16 GB unified memory, Metal GPU)
12
  and matches the move-selection quality of a hosted 31B teacher
13
  (`gemma-4-31b-it`). The teacher is too large to run locally; distillation
 
15
 
16
  Klondike was chosen as the domain because:
17
 
18
+ 1. Move-by-move strategy is *easy to score*, every legal move falls into
19
+ one of a small set of types (foundation / reveal / waste_play / shuffle /
20
+ draw / recycle) with an obvious ordinal value structure.
21
  2. The 31B teacher already has a deployed harvester, so production-quality
22
+ training labels accumulate naturally.
23
  3. Failure modes are concrete and replicable. The previous prompt-format
24
+ study (Phase 1.5) had already identified the "foundation-miss" anti-pattern
25
+ as the single largest source of suboptimal play in small models, giving
26
+ the distillation a sharp target to aim at.
27
 
28
  ## 2. Architectural decisions
29
 
 
32
  The original target was **Gemma 4 E2B** (~2B effective parameters, text-only).
33
  At the time of this run, `mlx-lm` 0.31.3 was the latest published version
34
  and could not load any `Gemma4ForConditionalGeneration` variant (all four
35
+ mlx-community quants tested failed identically, see the project's
36
  [T2 progress notes](https://github.com/chayuto/solitaire-analytics/blob/main/gemma4_finetune/tier2_progress.txt)).
37
  The structural issue is that mlx-lm's `Gemma4Model` class only implements
38
  the first 15 of 35 attention layers; the alternating-attention pattern with
 
64
 
65
  Production prompts are 1,000-2,600 tokens. The training config uses
66
  `max_seq_length=2048`. At iter 1 we saw a warning that the longest single
67
+ example was 2,298 tokens, i.e., ~5-10 % of training tails are silently
68
  truncated at the chosen budget. Trade-off: raising `max_seq_length` to 2,624
69
  would eliminate the truncation but push activation memory past the 16 GB
70
  envelope. We accepted the truncation.
 
87
  1. `outcome == "success"` (the teacher actually returned a response)
88
  2. `rawResponse` parses as JSON
89
  3. The parsed JSON contains all three keys: `board_analysis`,
90
+ `strategic_plan`, `final_decision`
91
  4. (Upstream filter, applied during ingest) the row is not from a stalled
92
+ game, defined as foundation count + face-down count unchanged for β‰₯ 25
93
+ consecutive turns
94
 
95
  Of 1,730 candidate rows, 1,536 (88.8 %) survived this filter for training.
96
  The 11.2 % drop rate from a single field-presence check is a known
 
137
 
138
  | iter | train loss | val loss | wall (cumulative) |
139
  |---:|---:|---:|---:|
140
+ | 1 | - | 6.365 | 0 m |
141
+ | 10 | 3.160 | - | ~1 m |
142
+ | 20 | 0.943 | - | ~2 m |
143
+ | 30 | 0.580 | - | ~3 m |
144
+ | 50 | 0.508 | - | ~5 m |
145
  | 100 | 0.388 | 0.426 | ~10 m |
146
  | 250 | (checkpoint) | (checkpoint) | ~25 m |
147
  | 500 | (checkpoint) | (checkpoint) | ~50 m |
 
149
  | 1000 | 0.222 | 0.369 | ~95 m |
150
 
151
  Most learning happens in the first 100 iters. Iters 100-1,000 contribute
152
+ an additional 0.057 of val-loss improvement, diminishing but still positive.
153
  Train/val gap at the end is 0.147; the val curve is still trending down but
154
  slowly. Pushing past 2,000 iters without data augmentation is likely to
155
  widen the gap.
 
172
 
173
  - **5 early-game** (foundation count < 4)
174
  - **8 midgame** (foundation count 4-25)
175
+ - **0 endgame**, both source post-cutover sessions stalled before reaching
176
+ endgame, so this category was unavailable. This is a real limitation of
177
+ the bench, acknowledged.
178
+ - **7 oscillation**, states where the recent moves indicate the teacher
179
+ was looping between draws and tableau shuffles without progressing
180
+ foundations or revealing face-downs
181
 
182
  Of these, **7 states have a `{tableau,discard}_to_foundation` move
183
  available** in the legalMoves array. These are the foundation-move test
184
+ ground, the failure mode this fine-tune was most intended to fix.
185
 
186
  ### 6.2 Scoring
187
 
 
189
 
190
  | move type | tier | rationale |
191
  |---|---:|---|
192
+ | `tableau_to_foundation` | 6 | maximally productive, advances win-progress |
193
  | `discard_to_foundation` | 6 | same |
194
  | `*_reveal` (move that flips a face-down) | 5 | unlocks information |
195
  | `discard_to_tableau` (waste play that lands productively) | 4 | activates a stale waste card |
196
+ | `tableau_to_tableau` (no reveal) | 2 | "shuffle", preserves options, no info gain |
197
  | `draw_card` | 1 | always available but rarely strategically optimal |
198
  | `recycle_stock` | 1 | costs nothing but exposes no new state |
199
  | illegal (chosen `move_index` not in `legalMoves`) | 0 | failure |
 
201
  ### 6.3 Comparison points
202
 
203
  - **Untuned base** (`mlx-community/gemma-3n-E2B-it-text-4bit-dwq` with no
204
+ adapter): the floor we are improving from.
205
  - **31B teacher** (`gemma-4-31b-it`): the production-recorded picks on the
206
+ same turns; the ceiling we are distilling toward.
207
  - **This adapter** (1,000-iter LoRA): the result being reported.
208
 
209
  A single inference per state per arm was used. Future work should add
 
211
 
212
  ### 6.4 Headline results
213
 
214
+ The shipped weights are the **iter-750 checkpoint**, selected after running
215
+ the bench against all four saved checkpoints. Iter 1000 was demonstrably
216
+ worse on this bench (overfitting), see Β§6.5 for the full curve.
217
+
218
+ | metric | untuned base | iter-750 (shipped) | iter-1000 (regressed) | 31B teacher |
219
  |---|---:|---:|---:|---:|
220
+ | JSON validity | 20 / 20 | 20 / 20 | 20 / 20 | - |
221
+ | Illegal moves | 1 / 20 | 2 / 20 | 0 / 20 | - |
222
+ | Teacher agreement | 11 / 20 | 11 / 20 | 11 / 20 | - |
223
+ | **Mean tier (all 20)** | 2.10 | **3.15** | 2.75 | 3.42 |
224
+ | **Gap to teacher** | **-1.32** | **-0.27** | -0.67 | - |
225
+ | Foundation recovery (of 7) | 2 / 7 | **6 / 7** | 4 / 7 | - |
226
+
227
+ ### 6.5 Learning curve & early-stopping decision
228
+
229
+ Each saved checkpoint was evaluated against the same 20-state bench:
230
+
231
+ | iter | mean tier | Ξ” teacher | Ξ” untuned | foundation 6/7? | illegal | JSON valid |
232
+ |---:|---:|---:|---:|---:|---:|---:|
233
+ | 0 | 2.10 | -1.32 | 0.00 | 2 / 7 | 1 | 20 / 20 |
234
+ | 250 | 2.10 | -1.32 | 0.00 | 3 / 7 | 3 | 20 / 20 |
235
+ | 500 | 2.60 | -0.82 | +0.50 | 4 / 7 | 2 | **18 / 20** |
236
+ | **750** | **3.15** | **-0.27** | **+1.05** | **6 / 7** | 2 | 20 / 20 |
237
+ | 1000 | 2.75 | -0.67 | +0.65 | 4 / 7 | 0 | 20 / 20 |
238
+
239
+ Observations:
240
+
241
+ 1. The curve is *non-monotonic*. Mean tier peaks at iter 750 and regresses
242
+ by iter 1000.
243
+ 2. **Two foundation-state regressions between iter 750 and iter 1000**:
244
+ `early-e6291973dd07` (foundation -> draw) and
245
+ `oscillation-a774c0d22f24` (foundation -> shuffle).
246
+ 3. **Iter 500 had a transient JSON-format instability** (2 / 20
247
+ generations failed schema) that recovered by iter 750. This kind of
248
+ short-window format instability midway through training is consistent
249
+ with the LoRA adapter still finding a stable representation.
250
+ 4. **Iter 1000 trades strategy for format reliability.** It is the only
251
+ checkpoint with zero illegal moves but loses tier score for the
252
+ privilege. On net, iter 750 wins.
253
+
254
+ Decision: ship iter 750 as `adapters.safetensors`. Iter 1000 remains
255
+ available under `checkpoints/0001000_adapters.safetensors` for users who
256
+ prioritise format strictness over strategy. Iters 250/500 are also kept
257
+ in `checkpoints/` for full provenance.
258
+
259
+ This is the rare case where running the cheap eval against intermediate
260
+ checkpoints *changed* the shipping decision. Future runs should default to
261
+ evaluating every saved checkpoint before publishing.
262
 
263
  Detailed per-state, per-category, and foundation-recovery breakdowns are in
264
  the [model card](../README.md).
 
266
  ## 7. Threats to validity
267
 
268
  - **Single eval bench (N = 20)**. The +0.65 delta is large enough that
269
+ noise alone is unlikely to produce it, but per-state changes (especially
270
+ on small subsets like the 7 foundation states) have wide effective
271
+ confidence intervals. A larger or multiply-resampled bench would tighten
272
+ the estimate.
273
  - **Template confound**. Training was on heterogeneous templates (~63 %
274
+ legacy, ~37 % current); eval is on current-only. We cannot disentangle
275
+ whether the +0.65 is from learning Solitaire reasoning or from learning
276
+ the current template's surface form. The fact that the gain is consistent
277
+ across categories suggests the former, but we cannot prove it.
278
  - **Teacher is not ground truth**. The 31B teacher is itself a flawed
279
+ player; matching it more closely is the *training* objective but the
280
+ *real* objective is "play better Solitaire". Two bench states show the
281
+ adapter strictly outperforming the teacher on tier score; we have no way
282
+ to know if those are flukes or evidence of generalised improvement.
283
  - **No game-level eval**. We evaluated single-turn decisions on a frozen
284
+ set of states. The real product question, "does this adapter win more
285
+ games end-to-end than the untuned base?", was not measured. This is the
286
+ most important deferred experiment.
287
  - **Memorisation as iters grow**. The train/val gap (0.222 vs 0.369) is
288
+ modest at iter 1,000 but trending widen. Future iters at this dataset
289
+ size should be paired with data augmentation or stronger regularisation.
290
 
291
  ## 8. Reproduction
292
 
 
300
  source venv/bin/activate
301
  pip install mlx mlx-lm
302
 
303
+ # 3. (Re)train, needs the training dataset (separately staged for HF datasets)
304
  # Hyperparameters identical to v1 are in training/lora_config.yaml
305
  mlx_lm.lora --config training/lora_config.yaml \
306
+ --data <your-prepared-dataset-dir> \
307
+ --adapter-path my_adapters
308
 
309
  # 4. Re-evaluate
310
  python eval/baseline_n20_runner.py
 
321
 
322
  ```
323
  chayuto/gemma-3n-e2b-it-solitaire-advisor-lora/
324
+ β”œβ”€β”€ README.md # model card (entry point)
325
+ β”œβ”€β”€ adapter_config.json # mlx-lm LoRA config (live)
326
+ β”œβ”€β”€ adapters.safetensors # final (= iter 1000) adapter weights
327
  β”œβ”€β”€ checkpoints/
328
+ β”‚ β”œβ”€β”€ 0000250_adapters.safetensors
329
+ β”‚ β”œβ”€β”€ 0000500_adapters.safetensors
330
+ β”‚ β”œβ”€β”€ 0000750_adapters.safetensors
331
+ β”‚ └── 0001000_adapters.safetensors
332
  β”œβ”€β”€ training/
333
+ β”‚ β”œβ”€β”€ METHODOLOGY.md # this file
334
+ β”‚ β”œβ”€β”€ lora_config.yaml # production training config
335
+ β”‚ └── prepare_dataset.py # data prep script
336
  └── eval/
337
+ β”œβ”€β”€ baseline_n20.json # untuned-base eval (scored)
338
+ β”œβ”€β”€ posttune.json # this-adapter eval (scored)
339
+ β”œβ”€β”€ teacher_picks_n20.json # ground-truth teacher picks
340
+ β”œβ”€β”€ prompts_C0/ # 20 rendered production prompts
341
+ β”œβ”€β”€ baseline_n20_runner.py
342
+ └── posttune_n20_runner.py
343
  ```
344
 
345
  ## 10. Open questions
346
 
347
  Things this run did *not* answer, and should:
348
 
349
+ 1. What's the optimal stopping iter?, eval each of the 250/500/750
350
+ checkpoints against the same bench; pick the val-loss / tier-score elbow.
351
+ 2. What's the marginal value of additional training?, eval at 2,000 iters
352
+ to see whether the val curve has actually flattened.
353
+ 3. Does this adapter actually win more games?, game-level (multi-turn,
354
+ stateful) eval on a held-out set of seeds.
355
  4. How much of the +0.65 is from the new template's surface form vs from
356
+ real Solitaire reasoning?, re-train on a post-cutover-only slice once
357
+ one is large enough (currently 351 rows; need ~1,000+) and compare.
358
  5. How does this compare to the Gemma 4 E2B target whenever mlx-lm catches up?