Skip to content

flybrain: make a brain usable -- carry it forward, ship it with a bui… - #98

Merged
WestWaters merged 3 commits into
mainfrom
fix/flybrain-usable-defaults
Sep 16, 2026
Merged

WestWaters merged 3 commits into
mainfrom
fix/flybrain-usable-defaults

Conversation

@WestWaters

Copy link
Copy Markdown
Owner

…ld, verify it

Five things a user hits, and the measurement tool that would have caught them.

pollard-brainverify. The one measurement, with the construction that produced the published numbers built in. Rebuilding it by hand cost an evening and three different wrong answers -- 45.8%, 33.3%, 56.2% -- on a brain that measures 100%. It reports the floor, the recall, and BOTH leakage controls, and exits non-zero if any control is above 2%, because a number without its controls is not a measurement.

The query default was wrong, and so was my first correction. A token is filed under the words immediately before it, so retrieval reproduces that context. The verified prompt is "Question: what is the secret word? Answer: The secret word is" -- question AND continuation. Shipping only the question returns confident noise from a perfect memory; shipping only the continuation is wrong the same way. Both halves now, and a test pins it against the trainer's own ASKS and the verifier's construction.

recall() crashed on most documents. A final window shorter than ek left a short carry, and the next address slice died on a shape mismatch -- on any document whose length does not divide the window, which is nearly all of them.

--continue-from. Training could only start over. On the same backbone this is cumulative curriculum -- run 900 steps, look at it, run 900 more. On a DIFFERENT backbone the memory-shaped parts (connectome, slots, codebook, decay) carry over and only the hidden-size-shaped layers are re-initialised, so a brain MOVES to a new model instead of starting from nothing; what is re-init reconverges in tens of steps because the memory it is learning to address is already organised. Changing bits/span between runs is refused outright -- that silently re-rolls the codebook and everything already written decodes to noise.

--codec bytes. The payload was this backbone's vocabulary indices, so only that tokenizer could read a brain back. Bytes store TEXT: any tokenizer can read it, and a six-letter word costs 6x8=48 bits against 4x18=72. The codec is recorded in the checkpoint and defaults to the original behaviour, because reading a brain with the wrong codec is silent garbage.

pollard --brain. A brain now ships WITH a build on every lane, with a BRAIN.md saying what it attaches to. GPTQ loads under transformers so it attaches at run time; GGUF, MLX and EXL3 run under engines with no hook for injecting memory tokens, so the file travels with the build and the note says so rather than leaving someone to discover it.

Also: training progress was invisible when redirected (block-buffered stdout, so --train > log wrote nothing and read as a hung job), and the SKILL state-size line was stale at 8.8 MB after the width default moved to 328.

48/48 pass.

…ld, verify it

Five things a user hits, and the measurement tool that would have caught them.

**pollard-brainverify.** The one measurement, with the construction that produced the published
numbers built in. Rebuilding it by hand cost an evening and three different wrong answers -- 45.8%,
33.3%, 56.2% -- on a brain that measures 100%. It reports the floor, the recall, and BOTH leakage
controls, and exits non-zero if any control is above 2%, because a number without its controls is
not a measurement.

**The query default was wrong, and so was my first correction.** A token is filed under the words
immediately before it, so retrieval reproduces that context. The verified prompt is
"Question: what is the secret word? Answer: The secret word is" -- question AND continuation.
Shipping only the question returns confident noise from a perfect memory; shipping only the
continuation is wrong the same way. Both halves now, and a test pins it against the trainer's own
ASKS and the verifier's construction.

**recall() crashed on most documents.** A final window shorter than `ek` left a short carry, and the
next address slice died on a shape mismatch -- on any document whose length does not divide the
window, which is nearly all of them.

**--continue-from.** Training could only start over. On the same backbone this is cumulative
curriculum -- run 900 steps, look at it, run 900 more. On a DIFFERENT backbone the memory-shaped
parts (connectome, slots, codebook, decay) carry over and only the hidden-size-shaped layers are
re-initialised, so a brain MOVES to a new model instead of starting from nothing; what is re-init
reconverges in tens of steps because the memory it is learning to address is already organised.
Changing bits/span between runs is refused outright -- that silently re-rolls the codebook and
everything already written decodes to noise.

**--codec bytes.** The payload was this backbone's vocabulary indices, so only that tokenizer could
read a brain back. Bytes store TEXT: any tokenizer can read it, and a six-letter word costs 6x8=48
bits against 4x18=72. The codec is recorded in the checkpoint and defaults to the original behaviour,
because reading a brain with the wrong codec is silent garbage.

**pollard --brain.** A brain now ships WITH a build on every lane, with a BRAIN.md saying what it
attaches to. GPTQ loads under transformers so it attaches at run time; GGUF, MLX and EXL3 run under
engines with no hook for injecting memory tokens, so the file travels with the build and the note
says so rather than leaving someone to discover it.

Also: training progress was invisible when redirected (block-buffered stdout, so `--train > log`
wrote nothing and read as a hung job), and the SKILL state-size line was stale at 8.8 MB after the
width default moved to 328.

48/48 pass.
…n a quantized model

Three changes, all opt-in: a brain written before them loads and behaves exactly as it did, which the
verified 100% brain confirms at 32 samples with every control at 0%.

**--canon: one brain, any hidden size.** A brain's address matrix was sized by the backbone, so it
was locked to one model. With --canon it trains in a fixed canonical width and bridges to whatever
it is attached to, using a probe basis built at attach time from the backbone's OWN responses to
fixed probe texts -- so direction i means the same thing on every model, measured at +0.53
correspondence across families against -0.02 for a random per-model projection. Nothing about the
bridge is trained. The return path is a pseudo-inverse, not a transpose: a transpose measured a
round-trip gain of 7.5 and the brain could not learn through it at all.

**A payload change no longer aborts a transfer.** --continue-from carries trained WEIGHTS; written
memory lives in a .flystate file, so a new codebook has nothing stored to corrupt. Changing bits,
span or codec resizes the value and decoder layers and re-initialises exactly those, while the
address path, gate and connectome carry across. People swap backbones and payloads constantly.
A .flystate from a differently shaped brain IS still refused, in load_state, where real memory lives.

**pollard_brain_backends: the runtime seam.** A brain needs four operations from a backbone --
embed(ids), forward(embeds) -> (logits, hidden), forward_ids(ids), out_weight() -- and nothing else.
Brains ran only under transformers because those calls were written inline against one library, not
because of the memory, the connectome or the payload. Transformers, MLX, exllamav3 and llama.cpp are
implementations of that protocol now.

MLX is verified working end to end on a 4-BIT QUANTIZED model:

  backend     mlx, hidden 896
  embed       (1, 6, 896)
  forward     logits (1, 6, 151936)   hidden (1, 6, 896)
  out_weight  (151936, 896)

That last line was a bug worth the whole exercise: a quantized MLX model reports its output
embedding PACKED -- (151936, 112) for 4-bit -- and the brain derives its token codes from it. Handed
packed bytes it builds a codebook out of bit-patterns and every stored token decodes to noise, with
nothing raising anywhere. The backend dequantizes, and a test pins it.

exllamav3 is written against its embedding-input path and not yet run. llama.cpp raises with the
reason: the C API has both halves (llama_batch.embd, llama_get_embeddings) but llama-cpp-python does
not surface them together, so it is a binding to write rather than a property of the format.

50/50 pass.
I said llama.cpp was the hard one and might be a wall. It was neither. The high-level
Llama.eval() takes tokens only, which is why the lane looked closed, but llama-cpp-python exposes
the whole C API underneath and that has both halves a brain needs:

  llama_batch_init(n_tokens, embd, n_seq)   a batch whose `embd` field carries EMBEDDINGS, not ids
  llama_get_embeddings_ith(ctx, i)          the final hidden state, per token

Verified against a stock Q4_K_M build of Qwen2.5-0.5B:

  backend      gguf, hidden 896
  embed        (1, 6, 896)
  forward      logits (1, 6, 151936)   hidden (1, 6, 896)
  hidden std   10.3633

That last number is the test that matters. llama.cpp's DEFAULT pooling returns one vector for the
whole sequence, and a brain addressing on a pooled mean has nothing per-token to key on -- every
write lands in the same place and the memory is uniformly useless without anything failing. The
context is opened with pooling NONE, and a test pins it.

What llama.cpp will not hand over is its output embedding matrix, and the brain's token codes come
from that. Rather than guess, out_weight() raises and says the two ways through: train the brain
against the transformers copy of the same model (token codes are a property of the vocabulary, not
of the quantization) or use --codec bytes, which does not need the matrix at all. The byte payload
was built two commits ago for tokenizer portability; it turns out to be what makes this lane
self-sufficient too.

exllamav3 is rewritten against its real module walk -- forward_ls() iterates fwd_modules, so
skipping the first lets embeddings in and stopping before the last lets the hidden state out, no
fork needed. Written, not yet run: there is no EXL3 model on the box to run it against.

51/51 pass.
@WestWaters
WestWaters merged commit a5174af into main Sep 16, 2026
1 check passed
@WestWaters
WestWaters deleted the fix/flybrain-usable-defaults branch September 16, 2026 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant