chorcat commited on
Commit
07823a9
·
verified ·
1 Parent(s): 516904b

Publish encoder-v4

Browse files
README.md CHANGED
@@ -49,7 +49,7 @@ Measured with `rukh eval encoder` on 2026-09-21, over
49
 
50
  ### Acceptance bars
51
 
52
- The project set these two bars for the encoder in `GOAL.md` before any of this was trained. This
53
  is where the release stands against them, and it is the same table whether the answer is
54
  flattering or not.
55
 
@@ -99,10 +99,10 @@ How to read these numbers:
99
 
100
  - the blunder F1 of the encoder and of the material baseline are measured on the same rows of the held-out 'val' split, which is drawn by game_id, never by position, and those rows are cut in two by game_id again: the 'tune' half chooses the threshold and the 'score' half is what gets reported
101
  - the checkpoint carries no label-count curve: run `rukh train heads --curve` and evaluate one of its checkpoints to fill that table
102
- - the encoder is +9.7 F1 points from the baseline; GOAL.md asks for at least +5
103
- - the value head is correlated against `tanh(cp / 400)`, the bounded score it is trained on, and not against raw `cp`, where a forced mate is worth ±9 99x and a few rows would decide Pearson for the whole set; the GOAL.md bar of 0.80 is read on Spearman
104
  - the baseline counts material (1/3/3/5/9) and mobility and looks one ply ahead at captures: it cannot see a positional sacrifice, and calls Fischer's 17...Be6 (Byrne-Fischer, 1956) a nine-point blunder
105
- - the two blunder detectors do not see the same thing: the baseline is given the predecessor position and the move that was played, while the encoder is given only the resulting position and has to infer that something was thrown away. That is the comparison GOAL.md asks for, but it is not a level playing field
106
  - the blunder threshold 0.09617 was chosen on the 'tune' half (3,793 rows, 2,455 games) by maximising F1 there, and the reported numbers are measured on the 'score' half (3,660 rows, 2,368 games), which no threshold ever saw; the same rows at the fixed threshold 0.5 give an F1 of 0.0000 against 0.1855 tuned
107
  - the material baseline is a hard yes/no rule: it has no threshold, so nothing was tuned on its side and it got no half to tune on. The margin therefore compares a model at its best operating point against a rule at its only one
108
  - a blunder is rare (3.7 % of the labelled rows), so **accuracy is meaningless** here: a model that always answers "no blunder" scores 96.3 % without knowing anything about chess. F1 at an arbitrary threshold is nearly as bad, because an uncalibrated sigmoid can rank the positions well and still put every probability below 0.5: that number measures the operating point, not the representation. This is why the threshold is chosen on a `tune` half and the F1 is reported on a `score` half, and why ROC AUC and average precision, which no threshold can flatter, are reported next to it
@@ -140,26 +140,31 @@ similar positions (`rukh encoder embed --positions <parquet> --out <npy>`).
140
  - `onnx/parity.json`
141
 
142
  The ONNX graph returns **two** outputs, `value` and `blunder`, because that is all the demo's
143
- evaluation bar and blunder alert need; `blunder` comes out as a probability, so the page
144
- compares it against 0.5. `model-fp16.onnx` is for WebGPU and `model-int8.onnx` for the WASM
145
- fallback. The metadata carries `rukh_kind=encoder` and `rukh_heads`.
 
 
 
 
146
 
147
  ### How faithful the ONNX files are
148
 
149
  Every exported file was run against the PyTorch checkpoint on 1000
150
- held-out labelled positions, comparing the blunder decision at p >= 0.5. `onnx/parity.json` in this
151
  repository is that measurement, as the exporter wrote it.
152
 
153
  | File | Same blunder decision as PyTorch | Worst `value` drift |
154
  |---|---|---|
155
  | `model.onnx` (fp32) | 100.0 % | 4.44e-06 |
156
  | `model-fp16.onnx` (fp16) | 100.0 % | 0.00164 |
157
- | `model-int8.onnx` (int8) | 100.0 % | 0.117 |
158
 
159
  The bar the project set itself is 99.9 %.
160
- Every precision, int8 included, makes the same call on every position checked: quantizing this
161
- model costs nothing that the demo's blunder alert can see. That is worth stating next to the
162
- decoder's number, where it is not true.
 
163
 
164
  ## Training recipe
165
 
@@ -276,7 +281,7 @@ The pretraining checkpoint the heads started from: `checkpoints/encoder-mmm-v4-2
276
 
277
  ## In the course
278
 
279
- - Built in [M3 · El encoder: los labs](https://lab.rukh.borjaglez.com/curso/m3/03-labs-del-encoder/), the lesson that runs every command behind this repository.
280
  - Measured in the [single results table](https://lab.rukh.borjaglez.com/proyecto/) as the stage `encoder-v4`: every stage of the course, the same suite, the same day.
281
  - Bring it to the paths the configs read: `uv run rukh pull encoder-v4`.
282
 
 
49
 
50
  ### Acceptance bars
51
 
52
+ The project set these two bars for the encoder in `docs/acceptance.md` before any of this was trained. This
53
  is where the release stands against them, and it is the same table whether the answer is
54
  flattering or not.
55
 
 
99
 
100
  - the blunder F1 of the encoder and of the material baseline are measured on the same rows of the held-out 'val' split, which is drawn by game_id, never by position, and those rows are cut in two by game_id again: the 'tune' half chooses the threshold and the 'score' half is what gets reported
101
  - the checkpoint carries no label-count curve: run `rukh train heads --curve` and evaluate one of its checkpoints to fill that table
102
+ - the encoder is +9.7 F1 points from the baseline; docs/acceptance.md asks for at least +5
103
+ - the value head is correlated against `tanh(cp / 400)`, the bounded score it is trained on, and not against raw `cp`, where a forced mate is worth ±9 99x and a few rows would decide Pearson for the whole set; the docs/acceptance.md bar of 0.80 is read on Spearman
104
  - the baseline counts material (1/3/3/5/9) and mobility and looks one ply ahead at captures: it cannot see a positional sacrifice, and calls Fischer's 17...Be6 (Byrne-Fischer, 1956) a nine-point blunder
105
+ - the two blunder detectors do not see the same thing: the baseline is given the predecessor position and the move that was played, while the encoder is given only the resulting position and has to infer that something was thrown away. That is the comparison docs/acceptance.md asks for, but it is not a level playing field
106
  - the blunder threshold 0.09617 was chosen on the 'tune' half (3,793 rows, 2,455 games) by maximising F1 there, and the reported numbers are measured on the 'score' half (3,660 rows, 2,368 games), which no threshold ever saw; the same rows at the fixed threshold 0.5 give an F1 of 0.0000 against 0.1855 tuned
107
  - the material baseline is a hard yes/no rule: it has no threshold, so nothing was tuned on its side and it got no half to tune on. The margin therefore compares a model at its best operating point against a rule at its only one
108
  - a blunder is rare (3.7 % of the labelled rows), so **accuracy is meaningless** here: a model that always answers "no blunder" scores 96.3 % without knowing anything about chess. F1 at an arbitrary threshold is nearly as bad, because an uncalibrated sigmoid can rank the positions well and still put every probability below 0.5: that number measures the operating point, not the representation. This is why the threshold is chosen on a `tune` half and the F1 is reported on a `score` half, and why ROC AUC and average precision, which no threshold can flatter, are reported next to it
 
140
  - `onnx/parity.json`
141
 
142
  The ONNX graph returns **two** outputs, `value` and `blunder`, because that is all the demo's
143
+ evaluation bar and blunder alert need; `blunder` comes out as a probability. That probability is
144
+ **not calibrated**: a blunder is a few per cent of the labelled rows, so the head answers low and
145
+ never reaches 0.5. Its operating point was tuned on a held-out half and travels in the metadata as
146
+ `rukh_blunder_threshold`, which is what a reader should compare against -- 0.5 is a threshold this
147
+ head cannot cross, and a page using it shows an alert that never fires. `model-fp16.onnx` is for
148
+ WebGPU and `model-int8.onnx` for the WASM fallback. The metadata also carries `rukh_kind=encoder`
149
+ and `rukh_heads`.
150
 
151
  ### How faithful the ONNX files are
152
 
153
  Every exported file was run against the PyTorch checkpoint on 1000
154
+ held-out labelled positions, comparing the blunder decision at p >= 0.0961714. `onnx/parity.json` in this
155
  repository is that measurement, as the exporter wrote it.
156
 
157
  | File | Same blunder decision as PyTorch | Worst `value` drift |
158
  |---|---|---|
159
  | `model.onnx` (fp32) | 100.0 % | 4.44e-06 |
160
  | `model-fp16.onnx` (fp16) | 100.0 % | 0.00164 |
161
+ | `model-int8.onnx` (int8) | 98.7 % | 0.117 |
162
 
163
  The bar the project set itself is 99.9 %.
164
+
165
+ `model-int8.onnx` does not reach it: it flips the blunder decision on 1.3 % of
166
+ positions, roughly one in 77. That is the file the WASM fallback loads, so a phone on the
167
+ int8 build gets a measurably different alert from the one in the results table above.
168
 
169
  ## Training recipe
170
 
 
281
 
282
  ## In the course
283
 
284
+ - Built in [M3 · El encoder: los labs](https://lab.rukh.borjaglez.com/curso/m3/11-labs-del-encoder/), the lesson that runs every command behind this repository.
285
  - Measured in the [single results table](https://lab.rukh.borjaglez.com/proyecto/) as the stage `encoder-v4`: every stage of the course, the same suite, the same day.
286
  - Bring it to the paths the configs read: `uv run rukh pull encoder-v4`.
287
 
onnx/model-fp16.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:c418d19953f58d9c47806723f137f86a914ba3e853e5c6819f3b0845212ce26e
3
- size 78745459
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a0297a40964208925840a32dda80ef4ba32210ce4ae08a16f2c58cebd0abcad2
3
+ size 78745496
onnx/model-int8.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d19e8df829d5c6ed94f81d02ba27e526444791004999a81210312820040d10bd
3
- size 43393427
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a620e0bee38c8f5ea269a46fb419e0b77f7abc4e25851a08c238bf54a17bce83
3
+ size 43393464
onnx/model.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:5b83eb089a88a2ccee2eb276d6e0b2b05d42a0bbebb12438ac3bf6b2c0635621
3
- size 156660338
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:65d9b4cd2b6061d6d3b8036810f371dbb96783a22134e0c22f2d742a0ef3a019
3
+ size 156660375
onnx/parity.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "version": 1,
3
  "kind": "encoder",
4
- "measures": "the blunder decision at p >= 0.5",
5
  "positions": 1000,
6
  "source": "validation-labels",
7
  "exporter": "dynamo",
@@ -22,7 +22,7 @@
22
  },
23
  "int8": {
24
  "file": "model-int8.onnx",
25
- "agreement": 1.0,
26
  "max_abs_value_delta": 0.11746431887149811,
27
  "max_abs_blunder_delta": 0.022291138768196106
28
  }
 
1
  {
2
  "version": 1,
3
  "kind": "encoder",
4
+ "measures": "the blunder decision at p >= 0.0961714",
5
  "positions": 1000,
6
  "source": "validation-labels",
7
  "exporter": "dynamo",
 
22
  },
23
  "int8": {
24
  "file": "model-int8.onnx",
25
+ "agreement": 0.987,
26
  "max_abs_value_delta": 0.11746431887149811,
27
  "max_abs_blunder_delta": 0.022291138768196106
28
  }