atom-ai / docs /DECODE_FIX_AND_BPTT.md
thefinalboss's picture
ATOM (atom-ai): code snapshot (100fae0 + batched-train), README with measured numbers, curated checkpoints
9f1076a verified
|
Raw History Blame Contribute Delete
23.3 kB
# Decode-label fix + truncated BPTT through the field
Follow-up to `docs/ROOT_CAUSE.md` (commit f8a8918). Code: commit fbea452 (on top of f8a8918). Scripts, raw JSON and
logs: `docs/artifacts/decfix_bptt/`. All checkpoints are in **new** directories; nothing was overwritten or deleted.
Metric: next-byte CE in nats/byte on the fixed 2048-transition val (`eval_windows.py`: 4 × 512, reset per window);
"field-alone" = same with the `last_atom` bigram readout zeroed.
## TL;DR
1. **Decode fix (step 1) done.** In byte-tick mode a generated byte is now committed through the *training*
encode path, so its label and features are bit-identical to training. Teacher-forced and generation logits now
match exactly (0.0). Doubled punctuation in 24 probe chats went from 7 to **0**. GRU-judge NLL of the replies
went from 9.69 to **1.37** (4996k), and to **1.27** after a 16k-step fine-tune with efference off (`_decfix`).
Val CE barely moved (1.247 → 1.287): the decode fix changes decoding, not what the model knows.
2. **BPTT (step 2) passes the decisive test.** With `--field-bptt 64/128` plus a learnable per-mode leak, a fresh
ATOM memorises the 2 KB excerpt: **938 bytes** regenerated verbatim past the 32-byte prefix (K=128) and
**648** (K=64) in 60 min. The target was > 500. Before: ATOM ~10–13, the K=0 control 10, GRU 1,453. TF argmax
accuracy on the text is 99.7 % (5 errors in 1,939 bytes); the control reaches 89.9 %. No NaN, the field cap is
never hit (0 %), and gradient now reaches lags 1–64. Before it was exactly 0 at every lag ≥ 1.
3. **Full corpus (step 3) is running and improving.** Val 2048 went from 1.247 to **0.48–0.53** (fresh init + BPTT
K=64, 400–512k steps, ~3 h on 1 core; best row 0.478 at 496k) and to **0.65–0.73** (from `_decfix` + BPTT, 400k steps,
plateauing; stopped). The old recipe sat at 1.25 for millions of steps. The fresh run is now at the byte 4-gram
level (0.48), well under a trigram (0.96), and still far from the GRU (0.10). Field-alone went 2.10 → **0.56–0.64**:
the field now carries most of the prediction. GRU-judge NLL of the replies went 1.37 → **0.88**.
4. **Honest verdict: it does not speak fluent French yet.** Replies contain more whole corpus lines (`Avec plaisir,
continuons.`, `Oui, tout à fait.`, `Bonne question, je vais réfléchir.`), but still with word salad in between
and still largely independent of the prompt. Measured context grew only moderately: the perturbation KL is
non-negligible out to ~12–16 bytes, versus 3–4 before. The learnable leak drifts *up* (0.05 → mean 0.13, e-fold
~7 bytes), so the optimiser prefers short memory on this data.
## 1. What changed (all opt-in / escape-flagged; defaults proven bit-identical)
| file | change | flag / default |
|---|---|---|
| `src/io/atomizer.py` | `packet_from_payload(payload, "generated")`: when `max_span_bytes == 1` and the payload is 1 byte, commit via `encode_bytes(payload, reset=False, flush=False)`, i.e. the exact training code. The result is `max_span` (LF/CR `newline`) in linguistic mode, and in eager mode whatever eager training gives. New attribute `Atomizer.legacy_gen_labels` (runtime only, not saved). | **automatic in byte-tick**; `--legacy-gen-labels` (trainer, `chat_atom_native.py`, `chat_seeds_probe.py`) restores the old `_infer_commit_boundary` labels exactly. Non-byte-tick is unchanged. |
| `src/atom_native.py` | `field_bptt` (`set_field_bptt`, `detach_field_graph`, `_bptt_prev_alpha`). In train mode with grad on, `_advance` builds α_t = leak(α_{t-1}) + projection(atom_t) **out of place with autograd** (same ops and order as the legacy in-place path) and keeps α_t's graph for the next tick. Eval and `no_grad` ticks never keep a graph. A stale graph (after reset, load or eval ticks) falls back to a detached copy. | `--field-bptt K` (0 = off = legacy per-tick detach + `no_grad` injection) |
| `src/atom_native.py` | Learnable per-mode leak `field_leak_logit` [n_modes, 1], λ_m = sigmoid, initialised at `--field-leak`. It only gets gradient under BPTT and is exclusive with `--field-leak-spectrum`. | `--field-leak-learnable` (off = no parameter) |
| `src/atom_native.py` | Checkpoint: keys `field_bptt` / `field_leak_logit` are written **only when on** (off = layout unchanged). Load restores them; absent = off. | |
| `tools/run_atom_native.py` | With K > 0: accumulate K tick losses (mean), one backward, clip 1.0, one AdamW step, `stabilize_dynamics_parameters`, cut the graph. A window also closes at the end of the run and at each `--episode-reset` boundary. CLI wins after `--resume` in both directions. Config/metrics keys are added only when on. | |
| `scripts/train_decfix.sh`, `scripts/train_bptt.sh` | Step-1 fine-tune driver; resumable step-3 driver (8k chunks, progress TSV every 2 chunks). | |
Efference with BPTT: `efference_loss` predicts under `no_grad`, which cuts the graph at that tick (documented;
efference is off in every BPTT run here). Batching several sequences through a vectorised field was **not**
implemented: the model is single-sequence throughout (atom list, atomizer, last_atom). One process runs about
120 ticks/s on one core; parallel arms use the other cores instead.
### Tests (`test/test_decode_fix_bptt.py`, 15 tests, all pass)
* Byte-tick generated commits are identical to `encode_bytes` of the same stream (payload, start, label, level,
features), in both pack modes. The linguistic labels ⊂ {max_span, newline}.
* `legacy_gen_labels=True` reproduces the f8a8918 atomizer exactly (labels + features), and `generate_packets`
output is byte-identical to the f8a8918 model for 6 sampled (prompt, seed) pairs. Non-byte-tick is unchanged
with either flag.
* TF path vs generation path: max |Δlogit| **== 0.0**. With legacy labels it is > 0 (sanity check).
* `--field-bptt 0` is **bit-identical to f8a8918** on a 40-step train + 20-step eval trace (no leak, leak 0.3, leak
spectrum). The **trainer** checkpoint (all tensors, config, loss trajectory) is bit-identical with defaults +
efference off, and with efference on + `--legacy-gen-labels`.
* BPTT on: forward values (loss, logits, α) are bit-identical to off at equal weights. d loss_T / d atom_{T−L} > 0 at
L = 1, 2, 3, 5, 8 when on, and **== 0** when off. A window cut stops the gradient exactly.
* Learnable leak: initialisation equals the scalar leak; it receives gradient under BPTT.
* Checkpoint roundtrip. An old or off checkpoint loads as off. Trainer CLI: default off, on, CLI wins after resume.
Full suite: 152 passed, 1 skipped, 3 failed (log: artifacts/decfix_bptt/pytest_full.txt). The 3 failures (`test_chat_merge_off::payload_copy_sees_distinct_atoms`,
`test_speech_lock::dialogue_wrap`, `test_speech_lock::speech_ok_rejects_attractor_soup`) **also fail on f8a8918**
(checked in a clean worktree). Two existing tests were updated on purpose:
`test_last_atom_readout::test_commit_boundary_never_generated` now asserts the fixed labels plus the legacy flag.
`test_slow_mode_readout::test_trainer_off_bit_identical_to_legacy` uses `--efference-every 2`, so it now passes
`--legacy-gen-labels` to the new trainer. The fix changes efference commits by design; with the flag the output is
bit-identical to the old code.
## 2. Step 1: decode fix + `_decfix` fine-tune
Fine-tune: `scripts/train_decfix.sh`, from `checkpoints/byte_tick_fieldsat/…4996000…fieldsat.pt`, 2 × 8k steps. The
recipe is exactly the fieldsat one (LR 1.5e-5 / SLR 3.6e-5, leak 0.3, last-atom dropout 0.5, field loss 0.05, episode 8000,
stream skip = START−496000) except **`--efference-every 0`**. Output: `checkpoints/byte_tick_decfix/atom_native_step_{5004000,5012000}_d32_ms1M_lrd_b4828_fieldsat_decfix.pt`.
Measurements (`measure_decfix.py`; chats use the probe settings: no role prime, 96 packets / 100 bytes, T 0.8, top-k 12;
"24 samples" = 3 prompts × seeds 0–7; judge = GRU 2×512 at val 0.10, first reply line, NLL/byte):
| ckpt | decode | val 2048 | field-alone | doubled punct (24 chats) | space runs ≥3 | judge NLL | same reply for all 3 prompts |
|---|---|---|---|---|---|---|---|
| fieldsat 4996k | legacy (as shipped) | 1.247 | 2.103 | 7 / 20 punct | 29 | 9.69 | 3/8 seeds |
| fieldsat 4996k | **fixed** | 1.247 | 2.103 | **0 / 83** | 0 | **1.37** | 3/8 |
| decfix 5012k | legacy | 1.287 | 2.061 | 10 / 31 | 53 | 9.16 | 0/8 |
| decfix 5012k | **fixed** | 1.287 | 2.061 | **0 / 152** | 0 | **1.27** | 0/8 |
* Removing efference for 16k steps does **not** remove the learned copy rule for OOD labels (legacy decode on 5012k
is still junk). The fix is in the decode path, as intended.
* Val got slightly worse (1.247 → 1.287), within the chunk-to-chunk noise of this branch; field-alone improved
slightly. The fine-tune is not a quality gain; its purpose is to stop training the copy shortcut.
Exact chats, decfix 5012k, fixed decode (unseeded draw, then seeds 1/2/3; `probe_chats` in `measure_decfix_5012k.json`):
* Bonjour, comment ça va ? → `Peut-être ce prends d'accord ?\nAssistant: Bon, je ais d'accord avec toi.`
* Qui es-tu ? → `Peut-être ce tu proposes ?\nAssistant: Je suis d'accord ?\nAssistant: Oui, je suis d'accord ?`
* Il était une fois → `Je suis ci.`
* seed 1: `Peut-être ce d'accord ?…` / `Oui, on verra.` / `Je suis d'accord ?\nAssistant: Je suis d'accord avec toi.`
* seed 2: `Peux qu'on proposes ?…` / (same) / `Je vais laisir, coute.`
* seed 3: `Peut-être ce nuis d'accord ?…` / `Oui, on verra.` / `Je suis d'accord ?\nAssistant: Peut-être un parle marci.`
Legacy decode on the same checkpoint: `Je` + ~30 spaces + `,,` + spaces `iissis` …, `Peut--..\nUtilisateur::` + spaces.
## 3. Step 2: truncated BPTT, the decisive 2 KB memorisation test
`memorize_bptt.py` uses the same per-tick logic as the trainer (AdamW wd .01, clip 1, stabilize after each step,
atom flush every 64, field loss .05, last-atom dropout .5, reset per pass, **efference off**) plus the trainer's window
rule. Text: `root_cause/memorize_2k.txt` (1,940 B). 60 min, 1 thread each, run in parallel. Regeneration: greedy from the
first 32 bytes through the (fixed) chat commit path. The count is the number of bytes identical to the text past the prefix.
| run | init | K | leak | lr | passes | updates | TF CE | **regen past prefix** | TF argmax acc | cap | NaN | learned λ mean (min–max) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| f_k0_l05_lr3e4 (**control, no BPTT**) | fresh | 0 | .05 | 3e-4 | 256 | 496k | 0.601 | **10** | 89.9 % | 0 | no | fixed |
| f_k64_l05_lr1e3 | fresh | 64 | .05 | 1e-3 | 305 | 9.5k | 0.027 | 13 | 99.0 % | 0 | no | fixed |
| f_k64_l05_lr3e3 | fresh | 64 | .05 | 3e-3 | 301 | 9.3k | 0.030 | 249 | 99.0 % | 0 | no | fixed |
| f_k64_l30_lr2e3 | fresh | 64 | .30 | 2e-3 | 305 | 9.5k | 0.018 | 137 | 99.2 % | 0 | no | fixed |
| f_k128_l05_lr2e3 | fresh | 128 | .05 | 2e-3 | 303 | 4.8k | 0.025 | 33 | 99.2 % | 0 | no | fixed |
| **f_k64_learn05_lr2e3** | fresh | 64 | learn (init .05) | 2e-3 | 305 | 9.5k | 0.012 | **648** | 99.7 % | 0 | no | .124 (.051–.326) |
| **f_k128_learn05_lr2e3** | fresh | 128 | learn (init .05) | 2e-3 | 304 | 4.9k | **0.006** | **938** | 99.7 % | 0 | no | .123 (.070–.221) |
| c_decfix_k64_learn05_lr2e3 | decfix 5012k | 64 | learn (init .05) | 2e-3 | 295 | 9.1k | 0.059 | 10 | 98.6 % | 0 | no | .133 (.054–.243) |
| *(root cause) ATOM, old recipe* | fresh / 4996k | 0 | .3 | 3e-4 | 117 / 106 | | 0.31 / 0.255 | 13 / 10 | | | | |
| *(root cause) GRU 124k* | | | | | 5,500 | | 0.005 | 1,453 | | | | |
* **Pass: > 500 bytes (938 and 648).** Without BPTT, the same budget and leak stay at TF CE 0.60 and 10 bytes.
* Regeneration from 20 later prefixes (`diag_mem_*.json`): mean verbatim run is 320 bytes (K128-learn), 263
(K64-learn), 96–99 (fixed-leak BPTT runs), 10 (control), 3.9 (decfix 5012k). The best models fail at only 5–6
positions of the text. Those are genuinely ambiguous template continuations: the same reply prefix appears after
different questions, more than ~30 bytes back.
* A learnable per-mode leak clearly beats a fixed one. Starting from the trained decfix checkpoint memorises
*worse* than fresh (TF 0.059, 10 bytes). The trained readout/last-atom solution seems to resist being repurposed
in 60 min.
* Numerical stability: no NaN or Inf in any run. The field RMS cap (3.0) was never hit (0 % of ticks). energy_decay,
coupling and phase_sync stay inside their clamps. Gradient norms are finite, with clipping at 1.0.
* Gradient through time (`grad_by_lag`, loss at tick 200 of val window 1): ‖∂L/∂atom_{T−L}‖ for L = 0/1/2/4/8/16/32/64.
decfix 5012k with K=64 gives 1.53 / 0.73 / 0.59 / 0.25 / 0.049 / 0.0032 / 8e-6 / 1e-10. The legacy path gives
1.44 / **0 / 0 / 0 …**. For the corpus-trained BPTT model (5252k) with K=64 it is 2.44 / 3.17 / 1.17 / 1.54 / 0.83 /
0.32 / 0.029 / 0.0013. Gradient reaches 16–32 ticks back and dies by 64. That is the combined effect of the leak
and the field dynamics; no explosion.
### Perturbation sensitivity (KL of the next-byte distribution L bytes after flipping one letter; val windows, 24 points)
Corpus-trained checkpoints (the meaningful comparison; memorisation models are off-distribution on these windows):
| ckpt | L=1 | 2 | 3 | 4 | 6 | 8 | 12 | 16 | 24 | 32 | 64 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| fieldsat 4996k (ROOT_CAUSE) normal | 7.56 | 4.33 | 0.52 | 0.29 | 0.14 | 0.081 | 0.057 | 0.008 | 0.0004 | 0.0001 | 0 |
| decfix 5012k normal | 8.43 | 4.62 | 0.52 | 0.43 | 0.21 | 0.054 | 0.066 | 0.005 | 0.0009 | 0.0001 | 0 |
| BPTT from decfix 5252k normal | 4.92 | 2.72 | 0.37 | 0.19 | 0.11 | 0.106 | 0.112 | 0.023 | 0.0023 | 0.0002 | 0 |
| BPTT fresh 256k normal | 7.07 | 3.49 | 0.72 | 0.36 | 0.34 | 0.062 | 0.032 | 0.007 | 0.0016 | 0.0001 | 0 |
| fieldsat 4996k field-alone | 0.83 | 0.31 | 0.27 | 0.21 | 0.056 | 0.040 | 0.010 | 0.0015 | 0.0004 | 0 | 0 |
| BPTT from decfix 5252k field-alone | 2.92 | 0.99 | 0.82 | 1.56 | 0.26 | 0.25 | 0.12 | 0.033 | 0.0056 | 0.0007 | 0 |
| BPTT fresh 256k field-alone | 7.41 | 2.31 | 0.37 | 1.42 | 0.29 | 0.12 | 0.029 | 0.008 | 0.0019 | 0.0001 | 0 |
The field now carries the last ~4–8 bytes strongly (field-alone KL at lag 4 is 1.4–1.6 vs 0.21). The tail beyond 16
bytes is still tiny, as a mean learned leak of 0.12–0.13 implies. BPTT makes the field **usable** as a memory. It does
not by itself give the long-range (prompt-length) memory: on this templated corpus the optimiser does not ask for it.
On the 2 KB text, where it is necessary, it does learn it (938 bytes verbatim needs > 30-byte dependencies).
## 4. Step 3: long full-corpus BPTT runs (running)
Driver: `scripts/train_bptt.sh` (chunks of 8k steps, one new checkpoint per chunk, progress row every 2 chunks:
val 2048 normal / field-alone, train stats, 3 probe chats unseeded + seed 1). Recipe = fieldsat recipe + `--efference-every 0
--field-bptt K --field-leak 0.05 --field-leak-learnable`, LR = SLR. Stream position: `(CUR−496000) mod 1,000,452`
transitions. This is the same text position as the legacy skip without replaying 4.5M packets (≈5 min per chunk).
The 256-period phase feature differs by a constant offset, which does not matter for new branches. About 120 ticks/s,
about 130 s per 8k chunk.
Arms (all started at the same time and run in parallel, 1 core each; box times PDT, i.e. ET−3):
| arm | init | K | LR=SLR | dir | status at the 3 h mark (05:42 PDT) |
|---|---|---|---|---|---|
| **fresh (best)** | fresh init (`checkpoints/byte_tick_bptt_fresh/atom_native_step_0_fresh_init.pt`, trainer constructor, seed 20260913) | 64 | 2e-3 | `checkpoints/byte_tick_bptt_fresh/` suffix `_d32_bptt_fresh` | **running**, 512k steps |
| fresh K128 | same fresh init | 128 | 2e-3 | `checkpoints/byte_tick_bptt_fresh_k128/` | **running**, 336k steps |
| main (from decfix) | pilotA 5044k = decfix 5012k + 32k BPTT steps (`checkpoints/byte_tick_bptt_pilotA/`) | 64 | 1e-3 | `checkpoints/byte_tick_bptt/` suffix `_d32_ms1M_fieldsat_decfix_bptt` | stopped at 5444k (plateau ~0.66–0.73; dominated by fresh) |
| from decfix, LR 2e-3 | same pilotA 5044k | 64 | 2e-3 | `checkpoints/byte_tick_bptt_lr2e3/` | stopped at 5444k (worse than main) |
Val 2048 normal / field-alone (from `bptt*_progress.tsv`; one row every 16k steps; rows fluctuate ±0.05):
| steps of BPTT | fresh K64 | fresh K128 | from decfix, LR 1e-3 | from decfix, LR 2e-3 |
|---|---|---|---|---|
| 0 | (random) | (random) | 1.287 / 2.061 (decfix 5012k) | same |
| 16k | 1.496 / 2.435 | 1.606 / 2.762 | 1.017 / 2.584 (5060k, incl. pilot) | 1.128 / 2.456 |
| 96k | 0.941 / 1.253 | 1.021 / 1.454 | 0.782 / 1.674 (5188k) | 0.812 / 1.540 |
| 192k | 0.754 / 0.915 | 0.766 / 1.053 | 0.742 / 1.446 (5252k) | 0.781 / 1.295 |
| 320k | 0.562 / 0.676 | 0.624 / 0.831 | 0.658 / 1.186 (5364k) | 0.675 / 1.054 |
| 400–512k | **0.478–0.531 / 0.56–0.64** (496k: 0.478 / 0.562) | – | 0.66–0.73 / 1.14–1.25 (5444k) | 0.76–0.80 / 1.09–1.14 |
Learned leak mean (all modes): fresh 0.05 → 0.155, main 0.05 → 0.098. Cap fraction 0.000, no NaN/Inf, all chunks finite.
Wall time per 8k chunk ≈ 130 s (≈ 120 ticks/s).
Final measurements (`measure_bptt_final.json`, `diag_bptt_final.json`):
| ckpt | val 2048 | field-alone | judge NLL (fixed decode) | doubled punct (24 chats) | KL lag 8 / 12 / 16 / 24 | grad lag 8 / 16 / 32 |
|---|---|---|---|---|---|---|
| fieldsat 4996k (reference) | 1.247 | 2.103 | 1.37 | 0 | 0.081 / 0.057 / 0.008 / 0.0004 | 0 / 0 / 0 |
| main 5412k (from decfix) | 0.654 | 1.185 | 1.24 | 0 | 0.115 / 0.145 / 0.028 / 0.003 | 0.042 / 0.016 / 0.001 |
| **fresh 448k** | **0.570** | **0.683** | **0.88** | 0 | 0.049 / 0.035 / 0.010 / 0.0007 | 1.73 / 0.94 / 0.053 |
| GRU 124k (ROOT_CAUSE) | 0.099 | | 0.41 (samples) | | | |
The fresh run never saw efference, so it never learned the copy rule: even the *legacy* decode gives no junk on it
(judge 0.80, 0 doubled punctuation).
Exact probe chats, fresh 448k (fixed decode; unseeded draw, then seeds 1/2/3; first line shown in full, rest cut at 100 bytes):
* Bonjour, comment ça va ? → `Avec plaisir, continuons.`
* Qui es-tu ? → `On mairdi da de tu fait.\nUtilisateur: Tu ais demps a.je te t'éccor ?\nAssistant: Oui, je t'écco`
* Il était une fois → `Bonne question, je vais réfléchir.\nUtilisateur: Tu veux qu'on recommence ?\nAssistant: Je suis `
* seed 1: `Oui, tout à fait.\nUtilisateur: Tu vis as lusisav ex unf ?…` / `Oui, tout à fait.\nUtilisateur: Tu vis as cconter ça ?…` / `Bonne question, je vais réfléchir.\nUtilisateur: Tu vis d'accord ?…`
* seed 2: `Oui, tout à fait.\nUtilisateur: Tu es d'accord ?\nAssistant: Je suis d'accord avec toi.` (identical for all 3 prompts)
* seed 3: `On paraisin de mat ça ?` / `On m'appelle his quelle une sist ron ?\nAssistant: Je suis d'accord avec toi.…` / `D'accord, je t'éccoute.\nUtilisateur: Qu'est-ce que tu proposes ?\nAssistant: Bonne question, je …`
Main 5412k (from decfix): `Peut-être ce tu fait.` / `Bonne quest-tu de j'approposes ?\nAssistant: Je suis d'accord avec toi.…` /
`Mit à fait.\nUtilisateur: Tu es d'accord ?\nAssistant: Monti, tout à fait.…`; seed 3 gives the same
`J'ai précoute.\nUtilisateur: Et toi, qu'en penses-tu ?…` for all 3 prompts.
Reading: many first lines are now exact corpus replies, and the model continues the dialogue format (it writes the next
`Utilisateur:` turn itself). Replies do not depend on the prompt (same seed → often the same reply for all 3 prompts),
and there is still word salad inside turns. It is not fluent.
Monitoring / restart (runs are resumable: re-running resumes from the newest checkpoint in `OUT`):
```
# fresh K64 (driver pid 3132056; log logs/bptt_fresh/master.log, chunk logs logs/bptt_fresh/train_*_bptt.log)
nohup env INIT=checkpoints/byte_tick_bptt_fresh/atom_native_step_0_fresh_init.pt START=0 BASE=0 K=64 LEAK=0.05 LEARN=1 LR=2e-3 \
OUT=checkpoints/byte_tick_bptt_fresh SUF=d32_bptt_fresh TSV=docs/artifacts/decfix_bptt/bptt_fresh_progress.tsv \
LOGD=logs/bptt_fresh bash scripts/train_bptt.sh >> logs/bptt_fresh/master.log 2>&1 < /dev/null &
# fresh K128 (driver pid 3149305; logs/bptt_fresh_k128/): same with K=128 OUT=checkpoints/byte_tick_bptt_fresh_k128
# SUF=d32_bptt_fresh_k128 TSV=docs/artifacts/decfix_bptt/bptt_fresh_k128_progress.tsv LOGD=logs/bptt_fresh_k128
# main from decfix (stopped; resume): INIT=checkpoints/byte_tick_bptt_pilotA/atom_native_step_5044000_d32_ms1M_fieldsat_decfix_bptt_pilotA.pt
# START=5044000 K=64 LEAK=0.05 LEARN=1 LR=1e-3 bash scripts/train_bptt.sh >> logs/bptt/master.log
# stop: kill <driver pid> $(pgrep -P <driver pid>)
```
END_TARGET defaults to START + 2,000,000 steps (≈ 9 h per arm), so the running arms will keep going unless stopped.
## 5. Verdict
* **The decode bug is fixed properly** (automatic in byte-tick, exact escape flag, bit-identical tests). ATOM's
output went from punctuation and space floods to French-looking words and corpus phrases. That is a real, free improvement.
* **The field is now trained through time and it works as a memory.** Decisive test passed (938 / 648 bytes vs 10),
gradient reaches past atoms, no instability. On the full corpus, val CE fell from the 1.25 plateau to ~0.5
(fresh) in 3 h and was still falling slowly. Field-alone went 2.10 → ~0.6. Fresh init beats continuing from
the decfix checkpoint.
* **Not fluent, not prompt-aware.** CE ~0.5 is at byte 4-gram level, versus 0.10 for a 124k GRU. Chats
are corpus fragments stitched with word salad. The same seed often gives the same reply for different prompts.
The measured context is ~8–16 bytes, not the ≥ 30 bytes needed to condition on the question.
* Next levers (not done): (1) let the learnable leak reach smaller values, e.g. a per-mode init ladder from
0.005 to 0.3, or a penalty/cap; the optimiser drifts λ up; (2) longer K for the corpus run (K=128 arm is running);
(3) a batched field (B sequences) for 10–30× throughput; (4) more diverse data (26 KB of unique text caps what any model can say).
## Artifact index (`docs/artifacts/decfix_bptt/`)
| file | content |
|---|---|
| `dfb_common.py` | shared helpers: val 2048, perturbation KL, gradient-by-lag, chat samples, doubled-punct rate, GRU judge (reuses `root_cause/`) |
| `measure_decfix.py`, `measure_4996k.json`, `measure_decfix_5012k.json`, `measure_bptt_interim.json`, `measure_bptt_final.json` | step-1 / step-3 measurements, fixed vs legacy decode, all chat samples |
| `memorize_bptt.py`, `mem_*.json` | 2 KB memorisation runs (log, regen head, args) |
| `diag_mem.py`, `diag_mem_{a,b,c,d}.json`, `diag_decfix_5012k.json`, `diag_bptt_*.json` | per-position TF errors, regeneration from 20 prefixes, perturbation KL, gradient-by-lag |
| `tsv_bptt.py`, `bptt*_progress.tsv`, `pilotA_progress.tsv` | step-3 progress rows |
| `logs/` | copies of the run logs |
### Addendum: latest progress rows at commit time (05:52 PDT / 08:52 ET)
- fresh K64: 512k 0.494/0.578, 528k 0.504/0.585 (best is still 0.478/0.562 at 496k, so it is plateauing). Still running; driver PID 3132056.
- fresh K128: 352k 0.640/0.816, 368k 0.580/0.780 (still improving). Still running; driver PID 3149305.
- Chats: still prompt-independent formulaic replies with word salad. **Not fluent.**