Repository navigation
Living Weights: facts learned and checked in conversation, not only asked cold - #891
Conversation
pocket.zig is at 593 lines and holding a conversation's rows adds to quiet(), so quiet() and its two thresholds move to pocket_quiet.zig as they are, beside pocket_joint.zig and pocket_values.zig. No change in what it does.
…ly asked cold A fact's lesson fits its neurons on single-turn prompts, and its checks ask single-turn questions. After a long unrelated conversation, or a few questions into a chain, the same question reads differently: a learned neuron's gate u x drops from about +3 to about 0, under the fit's firing level of 1, and the fact is not recalled. Nothing in a lesson saw that, so nothing caught it. A lesson now also asks its questions after conversations (slide_context.zig): three turns of small talk, a short and a long unrelated task (about 1.2k and 2.8k tokens on the models tried), and a chain (an opener, then the latest learned facts' questions with the answers their lessons wrote). The assistant turns are the model's own greedy replies, written once and kept in the graph file (slide_talks.zig), so every later lesson, and a restart, reads the same turns. Per fact and conversation: six of its questions fitted to fire, four near misses and two questions about the user no fact answers fitted to stay silent, and up to three of its answers learned there too. A prompt in a conversation carries Example.context, the rows it shares with every other question asked after the same conversation. The pocket reads it through the trainer's optional tapRows hook. Nemotron's on CUDA (cuda_train_tap.zig) runs the prompt through the engine's prompt chunks with the keys pass's MoE and taps, the states carried from chunk to chunk, so any length works; it runs each conversation once and keeps its Mamba states (the KV rows stay in place) for the next question. It reads the conversation's every eighth row as one to stay silent on. TF_SLIDE_TAP_CHECK compares the tapped rows of a prompt a pass can hold with the pass's own, and a reused conversation's rows with a fresh run. A learner without the hook (Metal) keeps the conversation prompts its passes hold and leaves out the rest (lesson_examples.readable), and logs how many. Each fact's two held-out questions are also asked after two conversations no lesson learns in (a Docker task, and a chain's opening that ends on a question the model cannot answer). A fact that only comes back cold does not pass.
…rows it fires on A fact learned after another is also asked, and answered, after a chain that holds the earlier fact's question and answer, and the earlier fact's neurons fire there, on their own question. quiet() refits an earlier neuron that fires on the new fact's questions so that it stays silent on them. It read the chain's rows as other rows to push below zero, could not, and undid the refit, so the new fact was learned with the earlier fact's neurons still firing on its questions (every refit in a first run on another model). A conversation's rows before a question where the earlier neuron fires are now held as they read, beside the rows its own lesson stored, so the refit changes it only on the new fact's rows. A test builds the case: before this change the refit is undone (no refit kept), after it the neuron is silent on the new fact's questions and reads its own question in the chain as before. The Nemotron CUDA module's host tests import pocket_quiet.zig.
|
Thank you, @ZackSample, for testing taught facts after conversations as well as cold questions, and for showing the retention tradeoff. This work moves with Living Weights to 1.1.1, the next patch release. The reviewed integration stores conversation names with answers, deduplicates shared tap rows and keeps thinking checks cold. Before release, the combined learner needs both four- and ten-fact runs, unrelated-reply comparisons, lesson take-out and save/reopen checks on CUDA and Metal. The current results do not justify treating conversation recall as improved on every backend. Posted by Ash's AI Assistant Synthena |
A prompt whose answer begins with its value has its reply row and its value row at the same place. The read planned both, and the trainer's taps refuse a row asked twice (error.BadTap), so such a conversation example failed its lesson (TF_SLIDE_POCKET_VALUES=on). Each row is now tapped once, its values handed to every label on it; the tap check runs only where the trainer has passes. Test: a value row at the reply row is tapped once and both labels read it.
…in the fact graph (#891) A fact's answer records (#872's graph format) carry the name of the kept conversation a check is asked after ("" when asked cold, written only when set). A later lesson's recheck of earlier facts and a delete's recheck ask those after the same kept turns, the delete's of the model as shipped too; thinking stays cold, as in the lesson's own checks. A repair keeps cold answers only. Test: answer records survive the graph file with their conversation.
…ollow-ups named as ours After review of the fourteen lines that overstated: #710's fresh-quantization gain is one measured case; #902's stop and group cleanup hold where the watch lines are open and through the shared helper; #887 says its chunks change the attention bits below 8,192, on M5 too, with no larger error against float64; #899 records the ring start and the wider refusal; #802 keeps the author's B70 receipts apart from our host-checked lifetime fixes; #836's equalities are the GB10's and its Windows build the author's; #888 says a failed restore's pinned memory is kept, never freed; #734's tree rounds are checked against the host's, not float64; the Living Weights lines (#876, #877, #878, #891) say which runs their numbers come from, the ten-fact costs, and the M3 Ultra's four-fact result; #946 and #910 read plainer.
… in conversation (#876, #877, #878, #891) This reverts the merge bafca587d (its first parent kept): the learner moves back from zig/src/living to the Nemotron family as 1.0.12 ships it, and the server, the build and the families lose the shared module. 1.1.1 takes the series again. Kept from the commits that came after it: the lean moves (shard_edit_link_test.zig, the kernel list in cuda_kernels.zig), the KV pages, which now read the lesson route from the Nemotron family, the required-tools lanes import in the server build, and Drama's and Qwen-VL's build wiring.
…Next on CUDA's lines say what was measured The six Living Weights rows (#876, #877, #878, #891, #905 and #992) leave Unreleased with the series. The EXL3 operators' line names GLM-5.3-Flash's two-rank engine as the family that serves them; #995's and #979's launches have their records on nvcc 13.0 and 13.2; and Flash Next on CUDA states what a GB10 served from the MLX 4-bit checkpoint, text and PNG images.
|
Thank you, @ZackSample, for testing taught facts after conversations as well as cold questions, and for showing the retention tradeoff. This work moves with Living Weights to the next patch release that passes its gates, 1.1.1 if they pass in time. The reviewed integration stores conversation names with answers, deduplicates shared tap rows and keeps thinking checks cold. Before release, the combined learner needs both four- and ten-fact runs, unrelated-reply comparisons, lesson take-out and save/reopen checks on CUDA and Metal. The current results do not justify treating conversation recall as improved on every backend. Nemotron on Apple M3 Ultra kept 3 of 4 facts and Apple M5 Ultra kept 2 of 4, both failing at the same rounding check, so Living Weights stays held for a repaired 4/4 rerun plus the NVIDIA GB10 27B and persistence/retention gates. Posted by Ash's AI Assistant Synthena |
… only asked cold @ZackSample's ashhart#891 (head a203094): a lesson also learns a fact's questions after scripted conversations (talk, task, long, and a chain of the latest learned facts' questions) and checks its two held-out questions after two conversations no lesson learns in. The model writes each conversation once; the fact graph keeps it. On CUDA a prompt in a conversation is read at any length through the trainer's taps; Metal leaves out what its passes cannot hold. Reconciled with what landed after its base: - the quiet refit holds both ashhart#876's rows (other prompts') and ashhart#891's (inside a conversation, where a chain carries an earlier fact's question and answer), above refit_above; - ashhart#877's rounds, recheck, repair and take-out kept; the conversation checks join each round's verdict; - ashhart#872's graph format kept, talks and chain beside it; checks asked in conversation stay out of its answer records here (the next commits give them their conversation); - after ashhart#878's move: the tap path in living/pocket_read.zig, the example checks in lesson_examples.readable, cuda_train_tap.zig's lesson_route from living; - askIn takes `shipped`, so a delete's checks can ask the model as shipped after a conversation; json as a module.
A prompt whose answer begins with its value has its reply row and its value row at the same place. The read planned both, and the trainer's taps refuse a row asked twice (error.BadTap), so such a conversation example failed its lesson (TF_SLIDE_POCKET_VALUES=on). Each row is now tapped once, its values handed to every label on it; the tap check runs only where the trainer has passes. Test: a value row at the reply row is tapped once and both labels read it.
…in the fact graph (ashhart#891) A fact's answer records (ashhart#872's graph format) carry the name of the kept conversation a check is asked after ("" when asked cold, written only when set). A later lesson's recheck of earlier facts and a delete's recheck ask those after the same kept turns, the delete's of the model as shipped too; thinking stays cold, as in the lesson's own checks. A repair keeps cold answers only. Test: answer records survive the graph file with their conversation.
…rt#877, ashhart#878, ashhart#891) One line each, with the GB10 measurements; the M3 Ultra numbers follow its gate.
…ollow-ups named as ours After review of the fourteen lines that overstated: ashhart#710's fresh-quantization gain is one measured case; ashhart#902's stop and group cleanup hold where the watch lines are open and through the shared helper; ashhart#887 says its chunks change the attention bits below 8,192, on M5 too, with no larger error against float64; ashhart#899 records the ring start and the wider refusal; ashhart#802 keeps the author's B70 receipts apart from our host-checked lifetime fixes; ashhart#836's equalities are the GB10's and its Windows build the author's; ashhart#888 says a failed restore's pinned memory is kept, never freed; ashhart#734's tree rounds are checked against the host's, not float64; the Living Weights lines (ashhart#876, ashhart#877, ashhart#878, ashhart#891) say which runs their numbers come from, the ten-fact costs, and the M3 Ultra's four-fact result; ashhart#946 and ashhart#910 read plainer.
… in conversation (ashhart#876, ashhart#877, ashhart#878, ashhart#891) This reverts the merge bafca587d (its first parent kept): the learner moves back from zig/src/living to the Nemotron family as 1.0.12 ships it, and the server, the build and the families lose the shared module. 1.1.1 takes the series again. Kept from the commits that came after it: the lean moves (shard_edit_link_test.zig, the kernel list in cuda_kernels.zig), the KV pages, which now read the lesson route from the Nemotron family, the required-tools lanes import in the server build, and Drama's and Qwen-VL's build wiring.
…Next on CUDA's lines say what was measured The six Living Weights rows (ashhart#876, ashhart#877, ashhart#878, ashhart#891, ashhart#905 and ashhart#992) leave Unreleased with the series. The EXL3 operators' line names GLM-5.3-Flash's two-rank engine as the family that serves them; ashhart#995's and ashhart#979's launches have their records on nvcc 13.0 and 13.2; and Flash Next on CUDA states what a GB10 served from the MLX 4-bit checkpoint, text and PNG images.
The problem
A fact's lesson fits its pocket neurons on single-turn prompts, and every check asks a single-turn question. In a real
chat the question comes after other turns, and then it reads differently.
I measured this on Qwen3.8 Flash Next (our CUDA port, the Living Weights code of this repo): 8 user facts taught, then
the same held-out questions asked in five contexts, all greedy. The assistant turns of the conversations are the model's
own live replies.
The untaught model scored 0 everywhere. I read every answer: the misses are "I don't have access to your personal
information" or an invented value.
The gate shows why. I read each learned neuron's
u xat the decide row of each probe prompt, with the fit's scale.2/27.
So the conversation moves the residual under the gate, and nothing in a lesson ever saw a conversation.
What this changes
1.
quiet()moves topocket_quiet.zig, unchanged (6921253a).pocket.zigis at 593 lines. This is the samepatch as the first commit of the refit PR (#876), with the same patch-id, so either can land first.
2. Facts are learned and checked in conversation (
cc7c89f0).The conversations (
server/slide_context.zig,server/slide_talks.zig). A lesson also asks its questions afterfour conversations:
latest two learned facts' questions with the answers their lessons wrote.
The assistant turns are the model's own greedy replies, written once and kept in the graph file (
talks, and afact's
chainpair), so every later lesson and a restarted server read the same text. The scripted lines aregeneric, and none of them is a probe below.
What a lesson learns there. Per fact and per conversation:
The checks. Each fact's two held-out questions are also asked after two conversations that no lesson learns in:
a Docker task, and a chain's opening that ends on a question the model cannot answer. A fact that only comes back
cold does not pass.
Reading long prompts (
families/nemotron/pocket_read.zig,cuda_train_tap.zig). An example now carriesExample.context: the rows of its conversation, which every question asked after it shares. The pocket reads such anexample through an optional trainer hook,
tapRows.cuda_forward.zig'schunkPre,chunkMixerandchunkPost, the states carried from chunk to chunk as serving's prefill carries them), with thekeys pass's MoE (
cuda_train_fwd.experts, the same pocket kernel). It copies the MoE input at the asked rows asthe keys pass does, so any length works.
place, for the next question after it.
TF_SLIDE_TAP_CHECK=1compares each tapped prompt with a fresh run (nothing kept, cut in 1,024-row pieces insteadof at the conversation's end), and with the trainer's own keys pass when one holds it.
Learners without the hook. A learner without
tapRowskeeps the conversation prompts its passes hold and leavesout the rest (
lesson_examples.readable), logging how many. That is Metal today: the chains fit in its 256 rows, thetask conversations do not. Answers in a conversation are always read by passes, so a learner leaves out the ones
longer than its passes, on CUDA too.
The pocket's log line (
layer N at the reply fires on ...) adds the fact's own questions in a conversation andthe most the neuron reads on other prompts there.
3. An earlier fact's refit holds the conversation rows it fires on (
4a63c4bb). A fact learned after another isalso asked after a chain that holds the earlier fact's own question and answer, and the earlier fact's neurons fire
there.
quiet()read those rows as rows to push below zero. It could not, and so it undid the refit.on its questions.
.none), beside the rows its ownlesson stored.
expected 1, found 0). After it, the neuron is silenton the new fact's questions and reads its own question in the chain as before.
cuda.zigandtensorfold.zigimportpocket_quiet.zigin their tests, as Living Weights: a quiet refit holds an earlier fact's neuron on its own questions as read now #876 does. So this test and the pocket'searlier tests run in the Linux
zig build test.Receipt (Nemotron)
lw-ctx-mainonmainc7137bb (v1.0.11 + 19), Zig 0.17.0; NVIDIA GB10 (DGX Spark, sm_121), driver580.178.04, CUDA 13.0.88, Ubuntu 24.04.5; checkpoint
TensorFold/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-MLX-4bitrevision d9d758fb. Each run used a fresh
cp -RLcopy:tensorfold-native serve <copy> --no-thinking --temperature 0 --slide --slide-graph <graph.json>.mainand this branch each taught the ten facts of 1.0.8's receipt ("I like blue." to "I am allergic topeanuts."), one learn request each.
and greedy by each build's model.
the same as plain keyword hits, except for one
mainreply that names sushi as an example after "I don't know".mainwas built at c7137bb. The branch binary was built at a586a277, which differs from the head 4a63c4b by onecomment's wording.
main, 10 facts taughtmain, the 6 facts both keptmain6/13, this branch 9/11.mainsays "Your son is called Hadi");main, "When is my birthday?" and "Where do I live?" after the chat answer "My name is Nemotron. I am createdby NVIDIA researchers."
mainkept 7 of 10, this branch 6.is my home?" asked cold with thinking on ("I remember you mentioned that you know which city is your home, but I
don't have that information stored"). No conversation check failed it.
branch against 13/20 on
main, and 13/20 is also the A2 receipt'smain.main. Every chained miss on both builds is a fact that was notkept.
Volvo?" → "Yes, you do.", which names no value). 3 were nurse ("your work is running a small Go web server in
Docker"). 1 was Ana in round 1, recalled in round 2.
main11/12 (a reworded haiku).--slide, on both builds, gave the 20 cold replies and 12 unrelated replies identical toafter lesson 10. The base checkpoint's sha256 is unchanged.
main(+8%).TF_SLIDE_TAP_CHECK=1read 102 prompts in aconversation (2,180 tapped rows, prompts up to 2,855 tokens).
own values: 0 values differ, most 0.0000.
bits.
met a conversation row its neuron fires on (
0 conversation rows held). Its test covers it, and Flash Next belowneeded it.
zig build teston Linux:main743/748 (5 skipped); commit 1 743/748; commit 2 746/751; commit 3 750/755. Commit 3's four more are its owntest and the pocket's three that now run on Linux.
zig build test-server-cpu: 209/209 (main207; +2: the conversations persist across a reopen of the graph, andthe learned and checked conversations differ).
zig fmt --check build.zig zigis clean.python3 tools/zig/lean_check.pygives527 problems against 527 on
main(0 new).zig build nativeandzig build test-nativewith-Dtarget=aarch64-macosand a stub
xcruncompile every Zig module and stop only at linking Apple's frameworks, as onmain. Metal'strainer has no
tapRows, so it learns the chains, leaves out the task conversations, and logs how many it left out.It runs the same checks, so it checks in conversation. A Mac lesson is still needed.
Supporting evidence: the same fix on two other models
The fix was developed on Qwen3.8 Flash Next and carried to Qwen3.8-27B in our CUDA ports. Neither is in this repo yet,
so these numbers support the design and are not this PR's receipt. Each port has its own
tapRows, through its ownengine's batched pass. On both,
TF_SLIDE_TAP_CHECKfound 0 values different from the trainer's pass: Flash Next on 48prompts, and the 27B on a 128-row conversation, run and found kept.
Flash Next, the same 7 facts before and after:
u x+2.44 → +3.68 (cold +3.77).against 116 and 199 in the study before this work.
Qwen3.8-27B, with the fix: cold 15/18, task chat ~4k 15/18, chained 14/16. Of the wordings recalled cold:
Checklist
untouched. The taps run inside a lesson, on the trainer's own sequence, as its passes do.)
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. (Nottouched. Both builds gave identical replies before teaching.)
rows equal the trainer's pass's bits.)
type-checked only.)
Not in this PR
mainhas no re-check of earlier facts after a lesson. When the Living Weights: a later lesson's quiet refit can silence an earlier fact on its own questions, and nothing asks it again #870fix adds one, it should ask each fact's conversation wordings too. In our build that was one line in the re-check
(
Asking.beforefrom the wording's conversation). The graph would also need to keep each wording's conversationname, which this PR leaves out (
questionskeeps the cold wordings only).expect your Living Weights: a later lesson's quiet refit can silence an earlier fact on its own questions, and nothing asks it again #870 fix to cover that ground.
tapRows. Metal reads the chains and leaves the long conversations out, as above.u xtable above is from Flash Next's diagnostic tool, which I didnot port. Nemotron's lessons log only the fit-time reading. On the branch, in all 60 of those lines (every lesson,
every reply neuron), the fact's neurons fire on all 27 of its questions in a conversation, and other prompts there
read at most -0.20.
How it relates to #878 and your #870 work
families/nemotrontozig/src/living. This PR changeslearner.zig,lesson_examples.zig,pocket.zig,pocket_read.zigandpocket_quiet.zigwhere they are onmain. Whichever lands second rebases by path.tapRowsis one more optional trainer hook beside Living Weights as a core module: the learner, pocket and fits in zig/src/living, a family described as data (Nemotron only) #878's list, andcuda_train_tap.zigstays Nemotron's.quiet(). If your Living Weights: a later lesson's quiet refit can silence an earlier fact on its own questions, and nothing asks it again #870 fix rewritesquiet()(Living Weights: a quiet refit holds an earlier fact's neuron on its own questions as read now #876 holds an earlier fact's own rows as read now), the conversationhold sits beside that in the same place: one more condition on which rows are held.
chain's rows only make earlier refits fail safe (undone), as described above.