sillage 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sillage-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abderrahmane Sghairi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sillage-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: sillage
3
+ Version: 1.0.0
4
+ Summary: Gradient-free test-time memory and learning for frozen language models
5
+ Author-email: Abderrahmane Sghairi <sghairipro63@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/riscoss63/sillage
8
+ Project-URL: Papers, https://doi.org/10.5281/zenodo.22079016
9
+ Keywords: language-models,associative-memory,hebbian,test-time-learning,fast-weights,gradient-free,continual-learning,vsa,hyperdimensional
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: numpy>=1.24
20
+ Requires-Dist: torch>=2.0
21
+ Requires-Dist: transformers>=4.51
22
+ Provides-Extra: research
23
+ Requires-Dist: scipy>=1.10; extra == "research"
24
+ Requires-Dist: matplotlib>=3.7; extra == "research"
25
+ Dynamic: license-file
26
+
27
+ # Sillage
28
+
29
+ **Your language model forgets everything. Sillage gives it a memory — and a
30
+ way to keep learning — in a fixed handful of megabytes, with no gradients and
31
+ no index that grows.**
32
+
33
+ > *sillage* (n., French) — the trace left behind by something that has passed:
34
+ > a ship's wake, a scent in a room. What a model keeps of what it read.
35
+
36
+ A frozen language model reads your documents, remembers them, and predicts
37
+ better next time. No gradients, no fine-tuning, no vector database. One
38
+ Hebbian matrix written as the model reads, a semantic tier routed by
39
+ confidence, a cold store that consolidates by surprise, and a rank-16 adapter
40
+ on the readout — four mechanisms, four preprints, one command-line tool.
41
+ Everything runs on a laptop CPU.
42
+
43
+ ![demo](https://raw.githubusercontent.com/riscoss63/sillage/main/figs/demo.gif)
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install sillage
49
+ ```
50
+
51
+ ## Use
52
+
53
+ ```bash
54
+ sillage index notes.md # instant: no model, already queryable
55
+ sillage ask "what did the report say?"
56
+
57
+ sillage read notes.md # memorize it (CPU: ~8 min per 10k tokens)
58
+ sillage complete "The report said" # generate WITH the memory
59
+ sillage status # what it knows, tier by tier
60
+ sillage forget --all
61
+ ```
62
+
63
+ ```console
64
+ $ sillage read preprint_v1.txt # Monday, memory empty
65
+ read preprint_v1.txt: 5859 tokens in 4.9 min | PPL 12.14 -> 11.83 (adapter) -> 11.71 (+memory)
66
+
67
+ $ sillage read preprint_v2.md # Tuesday, a new process
68
+ read preprint_v2.md: 11041 tokens in 9.5 min | PPL 10.68 -> 9.82 (adapter) -> 5.39 (+memory)
69
+ ```
70
+
71
+ Three numbers per file: what the frozen model alone predicts, what the
72
+ rank-16 adapter adds, and what the memory of everything read so far adds on
73
+ top. The state lives in `./.sillage`, survives restarts, and never grows:
74
+ 7.4 MB with GPT-2, 25 MB with Qwen3, the same after one document or ten
75
+ thousand. It never learns from its own generations — only from what you give
76
+ it to read.
77
+
78
+ From Python:
79
+
80
+ ```python
81
+ from sillage import Sillage
82
+
83
+ s = Sillage(model="gpt2") # any causal LM; omit it and the state
84
+ # says which model it belongs to
85
+ s.read("notes.md") # read, memorize, index -- then save
86
+ s.ask("what did the report say?") # exact passages, nothing generated
87
+ print(s.complete("The report said"))
88
+ ```
89
+
90
+ ## How much better, exactly?
91
+
92
+ On 36k tokens of technical text the model had never seen (frozen GPT-2 124M,
93
+ every system tuned identically on a held-out prefix, 95 % bootstrap CIs):
94
+
95
+ | system | perplexity | change | memory used |
96
+ |---|---|---|---|
97
+ | frozen GPT-2 | 31.2 | — | 0 |
98
+ | \+ RAG-style retrieve & rescore | 29.9 | −4 % | corpus + index |
99
+ | \+ kNN-LM, **unbounded** store | 23.6 | −24 % | 55 MB, grows forever |
100
+ | \+ **this memory** (fixed) | **19.2** | **−38 %** | **4.2 MB, constant** |
101
+ | \+ memory **and** fast weights | **16.6** | **−47 %** | 7.4 MB, constant |
102
+
103
+ The fixed 4.2 MB memory beats the unbounded datastore it was designed to
104
+ approximate — paired bootstrap **P = 1.000**, replicated over 5 random seeds
105
+ and on a second model (Qwen3-0.6B).
106
+
107
+ **Where it does not win:** on long, low-repetition narrative text, kNN-LM
108
+ still beats it. This memory captures verbatim recurrence; that is its regime,
109
+ and it is measured and published rather than hidden.
110
+
111
+ ## Any causal language model
112
+
113
+ ```bash
114
+ sillage read notes.md --model qwen # shortcut
115
+ sillage read notes.md --model HuggingFaceTB/SmolLM2-135M # any hub id
116
+ sillage read notes.md --model ./my-finetuned-llama # any local folder
117
+ ```
118
+
119
+ Verified across three architectures (GPT-2, Qwen3, GPT-NeoX). For a model
120
+ nobody has tuned, the readout calibrates itself on a rolling window of what
121
+ you read, following the papers' protocol; for the two the papers did tune,
122
+ their published settings are kept. A memory is written in one model's token
123
+ space, so give each model its own `--state` directory.
124
+
125
+ ## The four preprints
126
+
127
+ | # | title | DOI |
128
+ |---|---|---|
129
+ | 1 | Sillage: Surprise-Gated Amplitude Memory for Frozen Language Models | [10.5281/zenodo.22079016](https://doi.org/10.5281/zenodo.22079016) |
130
+ | 2 | Route the Scores, Not the Keys | [10.5281/zenodo.22079444](https://doi.org/10.5281/zenodo.22079444) |
131
+ | 3 | One Signal, Three Tiers | [10.5281/zenodo.22079471](https://doi.org/10.5281/zenodo.22079471) |
132
+ | 4 | Memory Remembers, Fast Weights Adapt | [10.5281/zenodo.22079481](https://doi.org/10.5281/zenodo.22079481) |
133
+
134
+ Sources, figures, every number as committed JSON, the reproduction pipeline
135
+ and the three negative results are on GitHub:
136
+ **<https://github.com/riscoss63/sillage>**
137
+
138
+ ## Citation
139
+
140
+ ```bibtex
141
+ @article{sghairi2026sillage,
142
+ title = {Sillage: Surprise-Gated Amplitude Memory
143
+ for Frozen Language Models},
144
+ author = {Sghairi, Abderrahmane},
145
+ year = {2026},
146
+ doi = {10.5281/zenodo.22079016},
147
+ url = {https://doi.org/10.5281/zenodo.22079016}
148
+ }
149
+ ```
150
+
151
+ MIT licensed.
sillage-1.0.0/PYPI.md ADDED
@@ -0,0 +1,125 @@
1
+ # Sillage
2
+
3
+ **Your language model forgets everything. Sillage gives it a memory — and a
4
+ way to keep learning — in a fixed handful of megabytes, with no gradients and
5
+ no index that grows.**
6
+
7
+ > *sillage* (n., French) — the trace left behind by something that has passed:
8
+ > a ship's wake, a scent in a room. What a model keeps of what it read.
9
+
10
+ A frozen language model reads your documents, remembers them, and predicts
11
+ better next time. No gradients, no fine-tuning, no vector database. One
12
+ Hebbian matrix written as the model reads, a semantic tier routed by
13
+ confidence, a cold store that consolidates by surprise, and a rank-16 adapter
14
+ on the readout — four mechanisms, four preprints, one command-line tool.
15
+ Everything runs on a laptop CPU.
16
+
17
+ ![demo](https://raw.githubusercontent.com/riscoss63/sillage/main/figs/demo.gif)
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install sillage
23
+ ```
24
+
25
+ ## Use
26
+
27
+ ```bash
28
+ sillage index notes.md # instant: no model, already queryable
29
+ sillage ask "what did the report say?"
30
+
31
+ sillage read notes.md # memorize it (CPU: ~8 min per 10k tokens)
32
+ sillage complete "The report said" # generate WITH the memory
33
+ sillage status # what it knows, tier by tier
34
+ sillage forget --all
35
+ ```
36
+
37
+ ```console
38
+ $ sillage read preprint_v1.txt # Monday, memory empty
39
+ read preprint_v1.txt: 5859 tokens in 4.9 min | PPL 12.14 -> 11.83 (adapter) -> 11.71 (+memory)
40
+
41
+ $ sillage read preprint_v2.md # Tuesday, a new process
42
+ read preprint_v2.md: 11041 tokens in 9.5 min | PPL 10.68 -> 9.82 (adapter) -> 5.39 (+memory)
43
+ ```
44
+
45
+ Three numbers per file: what the frozen model alone predicts, what the
46
+ rank-16 adapter adds, and what the memory of everything read so far adds on
47
+ top. The state lives in `./.sillage`, survives restarts, and never grows:
48
+ 7.4 MB with GPT-2, 25 MB with Qwen3, the same after one document or ten
49
+ thousand. It never learns from its own generations — only from what you give
50
+ it to read.
51
+
52
+ From Python:
53
+
54
+ ```python
55
+ from sillage import Sillage
56
+
57
+ s = Sillage(model="gpt2") # any causal LM; omit it and the state
58
+ # says which model it belongs to
59
+ s.read("notes.md") # read, memorize, index -- then save
60
+ s.ask("what did the report say?") # exact passages, nothing generated
61
+ print(s.complete("The report said"))
62
+ ```
63
+
64
+ ## How much better, exactly?
65
+
66
+ On 36k tokens of technical text the model had never seen (frozen GPT-2 124M,
67
+ every system tuned identically on a held-out prefix, 95 % bootstrap CIs):
68
+
69
+ | system | perplexity | change | memory used |
70
+ |---|---|---|---|
71
+ | frozen GPT-2 | 31.2 | — | 0 |
72
+ | \+ RAG-style retrieve & rescore | 29.9 | −4 % | corpus + index |
73
+ | \+ kNN-LM, **unbounded** store | 23.6 | −24 % | 55 MB, grows forever |
74
+ | \+ **this memory** (fixed) | **19.2** | **−38 %** | **4.2 MB, constant** |
75
+ | \+ memory **and** fast weights | **16.6** | **−47 %** | 7.4 MB, constant |
76
+
77
+ The fixed 4.2 MB memory beats the unbounded datastore it was designed to
78
+ approximate — paired bootstrap **P = 1.000**, replicated over 5 random seeds
79
+ and on a second model (Qwen3-0.6B).
80
+
81
+ **Where it does not win:** on long, low-repetition narrative text, kNN-LM
82
+ still beats it. This memory captures verbatim recurrence; that is its regime,
83
+ and it is measured and published rather than hidden.
84
+
85
+ ## Any causal language model
86
+
87
+ ```bash
88
+ sillage read notes.md --model qwen # shortcut
89
+ sillage read notes.md --model HuggingFaceTB/SmolLM2-135M # any hub id
90
+ sillage read notes.md --model ./my-finetuned-llama # any local folder
91
+ ```
92
+
93
+ Verified across three architectures (GPT-2, Qwen3, GPT-NeoX). For a model
94
+ nobody has tuned, the readout calibrates itself on a rolling window of what
95
+ you read, following the papers' protocol; for the two the papers did tune,
96
+ their published settings are kept. A memory is written in one model's token
97
+ space, so give each model its own `--state` directory.
98
+
99
+ ## The four preprints
100
+
101
+ | # | title | DOI |
102
+ |---|---|---|
103
+ | 1 | Sillage: Surprise-Gated Amplitude Memory for Frozen Language Models | [10.5281/zenodo.22079016](https://doi.org/10.5281/zenodo.22079016) |
104
+ | 2 | Route the Scores, Not the Keys | [10.5281/zenodo.22079444](https://doi.org/10.5281/zenodo.22079444) |
105
+ | 3 | One Signal, Three Tiers | [10.5281/zenodo.22079471](https://doi.org/10.5281/zenodo.22079471) |
106
+ | 4 | Memory Remembers, Fast Weights Adapt | [10.5281/zenodo.22079481](https://doi.org/10.5281/zenodo.22079481) |
107
+
108
+ Sources, figures, every number as committed JSON, the reproduction pipeline
109
+ and the three negative results are on GitHub:
110
+ **<https://github.com/riscoss63/sillage>**
111
+
112
+ ## Citation
113
+
114
+ ```bibtex
115
+ @article{sghairi2026sillage,
116
+ title = {Sillage: Surprise-Gated Amplitude Memory
117
+ for Frozen Language Models},
118
+ author = {Sghairi, Abderrahmane},
119
+ year = {2026},
120
+ doi = {10.5281/zenodo.22079016},
121
+ url = {https://doi.org/10.5281/zenodo.22079016}
122
+ }
123
+ ```
124
+
125
+ MIT licensed.
@@ -0,0 +1,374 @@
1
+ # Sillage
2
+
3
+ **Your language model forgets everything. Sillage gives it a memory — and a
4
+ way to keep learning — in a fixed handful of megabytes, with no gradients and
5
+ no index that grows.**
6
+
7
+ > *sillage* (n., French) — the trace left behind by something that has passed:
8
+ > a ship's wake, a scent in a room. What a model keeps of what it read.
9
+
10
+ [![tests](https://github.com/riscoss63/sillage/actions/workflows/tests.yml/badge.svg)](https://github.com/riscoss63/sillage/actions/workflows/tests.yml)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
12
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](pyproject.toml)
13
+ [![CPU only](https://img.shields.io/badge/hardware-CPU%20only-green.svg)](requirements.txt)
14
+ [![Papers: 4](https://img.shields.io/badge/preprints-4-orange.svg)](papers/)
15
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22079016.svg)](https://doi.org/10.5281/zenodo.22079016)
16
+
17
+ A frozen LM reads your documents, remembers them, and predicts better next
18
+ time — **no gradients, no fine-tuning, no growing index**. One Hebbian matrix
19
+ written as the model reads, a semantic tier routed by confidence, a cold store
20
+ that consolidates by surprise, and a rank-16 adapter on the readout. Four
21
+ mechanisms, four papers, **one command-line tool**. Everything runs on a
22
+ laptop CPU.
23
+
24
+ <p align="center"><img src="figs/demo.gif" width="94%" alt="Sillage demo: a frozen LM reads a document on Monday and recalls it on Tuesday"></p>
25
+
26
+ Two sessions, two days apart, nothing kept in context: on Tuesday the second
27
+ draft costs **half** the perplexity it would have cost on Monday (10.68 -> 5.39),
28
+ and the model completes a sentence it can only know from what it read.
29
+
30
+ ---
31
+
32
+ ## Quickstart (60 seconds, then a coffee)
33
+
34
+ ```bash
35
+ git clone https://github.com/riscoss63/sillage && cd sillage
36
+ pip install -e . # numpy + torch + transformers
37
+ ```
38
+
39
+ ```bash
40
+ sillage index notes.md # instant: no model, already queryable
41
+ sillage ask "what did the report say?"
42
+
43
+ sillage read notes.md # memorize it (CPU: ~8 min per 10k tokens)
44
+ sillage complete "The report said" # generate WITH the memory
45
+ sillage status # what it knows, tier by tier
46
+ ```
47
+
48
+ ```console
49
+ $ sillage read preprint_v1.txt # Monday, memory empty
50
+ read preprint_v1.txt: 5859 tokens in 4.9 min | PPL 12.14 -> 11.83 (adapter) -> 11.71 (+memory)
51
+
52
+ $ sillage read preprint_v2.md # Tuesday, a new process
53
+ read preprint_v2.md: 11041 tokens in 9.5 min | PPL 10.68 -> 9.82 (adapter) -> 5.39 (+memory)
54
+ memory consolidated and saved (16900 tokens lifetime, 9883 cold grams, 194 passages indexed).
55
+ ```
56
+
57
+ Three numbers per file, because there are two mechanisms: what the frozen
58
+ model alone predicts, what the rank-16 adapter adds, and what the memory of
59
+ everything read so far adds on top.
60
+
61
+ The memory lives in `./.sillage` and survives restarts — it survived a power
62
+ cut during development. Its size is fixed the day you start: 7.4 MB with
63
+ GPT-2, 25 MB with Qwen3 (whose vocabulary is 3x larger), the same after one
64
+ document or ten thousand. It **never learns from its own generations**,
65
+ only from what you give it to read.
66
+
67
+ Reading is the slow part (one frozen forward pass per token, on CPU): about
68
+ 8 minutes per 10k tokens with the default Qwen3-0.6B, about 2 with
69
+ `--model gpt2` — which is 4x faster but English-only. `index` and `ask` need
70
+ no model at all and are instant.
71
+
72
+ ### Which model can it wrap? Any of them
73
+
74
+ ```bash
75
+ sillage read notes.md --model qwen # shortcut
76
+ sillage read notes.md --model HuggingFaceTB/SmolLM2-135M # any hub id
77
+ sillage read notes.md --model ./my-finetuned-llama # any local folder
78
+ ```
79
+
80
+ Nothing here is model-specific: the memory only ever sees next-token logits,
81
+ one hidden state and the observed token, so **any causal language model
82
+ works** — GPT-2, Qwen, Llama, Mistral, Pythia, SmolLM, your own fine-tune.
83
+ Verified across three architectures (GPT-2, Qwen3, GPT-NeoX). Four things to
84
+ know before pointing it at a new one:
85
+
86
+ - **The readout tunes itself on an unfamiliar model.** How strongly the
87
+ memory speaks (`beta`, `lambda`) and when it stays quiet (the abstention
88
+ threshold) were tuned per model in the papers. For any model they did not
89
+ tune, the tool does that tuning itself: one position in three joins a
90
+ rolling window, the published grids are searched on it at the end of each
91
+ read, and the winner governs the next one — so nothing is ever scored with
92
+ settings fitted on itself. For `qwen` and `gpt2` the published settings are
93
+ kept instead, and that is a measured decision, not deference: refitting
94
+ them on your own cold memory *loses* (see below). `--calibrate` forces
95
+ fitting anyway, `--no-calibrate` forbids it, `read --recalibrate` starts
96
+ over.
97
+ - The **semantic tier stays off** for an unknown model (`--semantic` to force
98
+ it). Paper 2 is explicit: raw hidden states need whitening except where
99
+ their geometry is already well conditioned, and that has only been verified
100
+ on Qwen3.
101
+ - A memory is written in **one model's token space**, so give each model its
102
+ own `--state` directory. After that you can drop `--model`: the state
103
+ remembers which model it belongs to, and refuses to be opened by another.
104
+ - **Cost follows the model**: reading time follows its size, and the adapter
105
+ is `vocab x 16` floats (3.2 MB at GPT-2's vocabulary, 9.7 MB at Qwen3's).
106
+ A 7B model on a CPU is possible but slow; this was designed for the 0.1–1B
107
+ class.
108
+
109
+ What calibration actually does, measured on a model the papers never touched
110
+ (Pythia 70M, two documents, second one scored out of sample):
111
+
112
+ ```console
113
+ $ sillage read doc_a.txt --model EleutherAI/pythia-70m
114
+ read doc_a.txt: 5025 tokens in 1.0 min | PPL 2.02 -> 1.92 (adapter) -> 1.79 (+memory)
115
+ fitted the readout on 1675 observations from what was just read (the papers' grids):
116
+ n-gram tier : beta 40, lambda 0.85, abstain below q75
117
+ +0.2461 nats on that window. It governs the NEXT read, never this one,
118
+ so no perplexity printed here was tuned on itself.
119
+ ```
120
+
121
+ On the next document that fitted readout gives **1.38** where the GPT-2
122
+ defaults give 1.42 — worth having when nobody has tuned your model.
123
+
124
+ It also tracks the memory as it fills, because each fit sees a warmer memory
125
+ than the last. Reading one paper three times (GPT-2, `--calibrate`), where
126
+ the readout fitted at the end of a pass is the one that governs the next:
127
+
128
+ | pass | perplexity of this pass | what its window then fitted |
129
+ |---|---|---|
130
+ | 1 | 56.0 | `beta 20, lambda 0.1, abstain below q75` |
131
+ | 2 | 7.5 | `beta 40, lambda 0.85, abstain below q50` |
132
+ | 3 | 1.5 | `beta 40, lambda 0.85, abstain below q25` |
133
+
134
+ (Frozen GPT-2 alone stays at 68.8 throughout.)
135
+
136
+ Prefer no install? `pip install -r requirements.txt` and then
137
+ `python -m sillage read notes.md` does exactly the same thing — that also
138
+ works if the `sillage` command lands outside your PATH.
139
+
140
+ <details>
141
+ <summary><b>Use it from Python (four lines)</b></summary>
142
+
143
+ ```python
144
+ from sillage import Sillage
145
+
146
+ s = Sillage(model="gpt2") # any causal LM; omit it and the state
147
+ # says which model it belongs to
148
+ s.read("notes.md") # read, memorize, index -- then save
149
+ s.ask("what did the report say?") # exact passages, nothing generated
150
+ print(s.complete("The report said"))
151
+ ```
152
+
153
+ `Sillage.status()` returns the same numbers as the CLI as a dict, and
154
+ `sillage.SillageMemory` is the mechanism alone (numpy only, no transformers)
155
+ if you want to wire it into your own generation loop.
156
+ </details>
157
+
158
+ <details>
159
+ <summary><b>Every option</b></summary>
160
+
161
+ | command | what it does |
162
+ |---|---|
163
+ | `sillage read FILE...` | read + memorize + index (all four mechanisms) |
164
+ | `sillage index FILE...` | index only — instant, no model needed |
165
+ | `sillage ask "..."` | grounded excerpts with their source and section |
166
+ | `sillage complete "..."` | generate with the memory and the adapter |
167
+ | `sillage chat` | both, interactively (`/say`, `/read`, `/status`) |
168
+ | `sillage papers` | index the four preprints shipped here, then ask them |
169
+ | `sillage demo FILE` | two sessions on one document, start to finish |
170
+ | `sillage status` / `forget --all` | inspect / wipe |
171
+
172
+ Flags: `--model NAME` (see below), `--state DIR` (or `$SILLAGE_STATE`),
173
+ `--no-fastweights`, `--no-semantic`, `--half-life N` (forgetting, in tokens),
174
+ `--no-calibrate` / `read --recalibrate`, `-n`, `--temp`, `-k`. Globs are
175
+ expanded by the tool itself, so `sillage read docs/*.md` works on Windows
176
+ too.
177
+ </details>
178
+
179
+ ---
180
+
181
+ ## How much better, exactly?
182
+
183
+ On 36k tokens of technical text the model had never seen (frozen GPT-2 124M,
184
+ every system tuned identically on a held-out prefix, 95 % bootstrap CIs):
185
+
186
+ | system | perplexity | change | memory used |
187
+ |---|---|---|---|
188
+ | frozen GPT-2 | 31.2 | — | 0 |
189
+ | \+ RAG-style retrieve & rescore | 29.9 | −4 % | corpus + index |
190
+ | \+ kNN-LM, **unbounded** store | 23.6 | −24 % | 55 MB, grows forever |
191
+ | \+ **this memory** (fixed) | **19.2** | **−38 %** | **4.2 MB, constant** |
192
+ | \+ memory **and** fast weights | **16.6** | **−47 %** | 7.4 MB, constant |
193
+
194
+ The fixed 4.2 MB memory beats the unbounded datastore it was designed to
195
+ approximate — paired bootstrap **P = 1.000**, replicated over 5 random seeds
196
+ and on a second model (Qwen3-0.6B). And the gains show up in behaviour, not
197
+ just likelihood: recall of recurring technical terms after one reading pass
198
+ goes from **11.3 % to 23.7 %** (McNemar 261:14).
199
+
200
+ <p align="center"><img src="figs/fig1_main.png" width="88%" alt="Main results"></p>
201
+
202
+ ## Should you use this?
203
+
204
+ | your situation | better option |
205
+ |---|---|
206
+ | One machine, private documents, must run offline | **this** |
207
+ | The model must keep learning after deployment, forever, at bounded cost | **this** |
208
+ | You can afford a datastore that grows with everything you read | kNN-LM / RAG |
209
+ | You need the model to *reason* better, not to *remember* better | fine-tuning |
210
+ | The document fits in the context window and you only read it once | just paste it |
211
+
212
+ It is a **memory**, not an intelligence upgrade: it makes a model better at
213
+ the text in front of it, on a fixed byte budget, forever.
214
+
215
+ ---
216
+
217
+ ## How it works, in four ideas
218
+
219
+ <p align="center"><img src="figs/fig0_architecture.png" width="82%" alt="architecture"></p>
220
+
221
+ 1. **Keys by binding.** Each position is addressed by a sliding *n*-gram
222
+ product of random token hypervectors — a key that fires on exact
223
+ repetition and stays near-orthogonal otherwise.
224
+ 2. **Values as amplitudes.** The matrix stores *square roots* of accumulated
225
+ mass, not counts. That one change roughly doubles the memory's benefit:
226
+ square-root compression stops frequent continuations from drowning rare
227
+ ones in superposition.
228
+ 3. **Surprise decides.** Every write is scaled by the model's own token
229
+ surprise `−ln p_LM` — free at inference. The same scalar also arbitrates
230
+ which tier answers, and which patterns get consolidated to cold storage.
231
+ 4. **Fast weights adapt what memory cannot.** A rank-16 delta-rule adapter on
232
+ the readout (3.2 MB, no error transported through any layer) wins exactly
233
+ where memory is weakest, and their gains add up (89–98 % additive).
234
+
235
+ ### The four papers are the four mechanisms — all of them are in the tool
236
+
237
+ | paper | mechanism in `sillage/` | on by default | switch |
238
+ |---|---|---|---|
239
+ | 1 · Sillage | `M_G`, 4.2 MB Hebbian *n*-gram tier, amplitude writes, surprise gate | yes | — |
240
+ | 2 · Router | `M_S`, 12.6 MB semantic tier, **score-level** mixing with abstention | `--model qwen` only (other models need whitening) | `--semantic` / `--no-semantic` |
241
+ | 3 · Hierarchy | cold store of exact 4-grams, consolidated by surprise **mass** at save time | yes | — |
242
+ | 4 · Fast weights | `A`, rank-16 delta-rule readout adapter, uniform step | yes | `--no-fastweights` |
243
+ | 1 & 3 · long horizons | leaky forgetting (half-life in tokens) | no — needed past ~0.5 writes/parameter | `--half-life 100000` |
244
+ | all four · protocol | readout **calibration**: the papers' grids fitted on a rolling window of what you just read, governing the next read | only for a model the papers did not tune | `--calibrate` / `--no-calibrate`, `read --recalibrate` |
245
+
246
+ `sillage status` shows both of the last two lines: which readout is in force
247
+ right now, and how close the matrix is to saturation — the capacity law being
248
+ the honest limit of the whole approach.
249
+
250
+ ## The four preprints
251
+
252
+ All four are archived on Zenodo with permanent DOIs; the LaTeX sources and figures are in [`papers/`](papers/). `sillage papers` indexes them so you can query them offline.
253
+
254
+ | # | title | the finding |
255
+ |---|---|---|
256
+ | 1 | **[Sillage](https://doi.org/10.5281/zenodo.22079016)** · [source](papers/sillage/sillage.tex) | a fixed 4.2 MB Hebbian cache beats an unbounded kNN-LM on novel repetitive text |
257
+ | 2 | **[Route the Scores, Not the Keys](https://doi.org/10.5281/zenodo.22079444)** · [source](papers/router/router.tex) | gradient-free semantic keys work — but only if you mix at the score level, never in the key |
258
+ | 3 | **[One Signal, Three Tiers](https://doi.org/10.5281/zenodo.22079471)** · [source](papers/hierarchy/hierarchy.tex) | consolidating by *surprise mass* keeps 94 % of a cold store's value with 10 % of its entries |
259
+ | 4 | **[Memory Remembers, Fast Weights Adapt](https://doi.org/10.5281/zenodo.22079481)** · [source](papers/fastweights/fastweights.tex) | two gradient-free mechanisms, opposite regimes, near-additive gains |
260
+
261
+ ## What did *not* work (and how we know)
262
+
263
+ This is the part most repositories leave out.
264
+
265
+ - **Hidden states make terrible Hebbian keys.** Their similarity geometry is
266
+ too entangled (95th-percentile cosine 0.93 between random pairs); the best
267
+ semantic-only configuration reached 8 % of the *n*-gram key's gain and was
268
+ harmful off-domain.
269
+ - **Surprise gating helps memory and *hurts* fast weights** (−18 % on one
270
+ stream, −100 % on another). The delta rule already carries its own error
271
+ term; gating double-counts it. Gate the mechanisms that cannot see their own
272
+ error; leave alone the ones that can.
273
+ - **Two evaluation bugs shipped before any result did.** Raw Gutenberg `\r\n`
274
+ endings inflated every method by **+1.35 phantom nats**; a one-position
275
+ misalignment made the RAG baseline score at chance. Both were caught by
276
+ controls, not by luck.
277
+ - **Self-calibration loses to a proper tuning, where one exists.** Fitting the
278
+ readout on your own stream sounds strictly better than using someone else's
279
+ constants. It is not: the window is read by a memory that is *colder* than
280
+ the one those settings will govern, so the fit comes out systematically too
281
+ timid. Measured on GPT-2, fitting on one technical paper and scoring on the
282
+ next (gain over the frozen model, in nats):
283
+
284
+ | readout fitted on | gain on the next paper |
285
+ |---|---|
286
+ | the whole window | +0.098 |
287
+ | its recent half | +0.103 |
288
+ | its recent quarter | +0.109 |
289
+ | **the papers' published settings** | **+0.120** |
290
+ | an oracle fitted on that paper itself | +0.125 |
291
+
292
+ Recency helps, and never enough. Hence the rule the tool follows: calibrate
293
+ a model nobody has tuned, and keep the published constants for the two that
294
+ were tuned properly — on 36k–500k-token streams, not on a few thousand cold
295
+ ones.
296
+
297
+ Hence the three controls we now consider mandatory for streaming-memory work —
298
+ `python eval/diagnostic.py` runs them: a **shuffled-retrieval null** (replace
299
+ retrieved values with random tokens; any surviving gain is an artifact), a
300
+ **unigram-cache null**, and a **base-model sanity perplexity**.
301
+
302
+ ---
303
+
304
+ ## Reproducing everything
305
+
306
+ Every number in every paper regenerates from these scripts with fixed seeds,
307
+ on CPU. See **[REPRODUCE.md](REPRODUCE.md)** for the full pipeline; results
308
+ are committed as JSON in [`results/`](results/) (including per-seed values).
309
+ `python test_unit.py` checks the mechanisms themselves in five seconds
310
+ (numpy only, no model: retrieval, the square-root rule, forgetting, the delta
311
+ rule, consolidation, the state round-trip, the readout tuner, the multi-model
312
+ paths); `python test_sillage.py` runs 13 end-to-end tests of the tool (each
313
+ command in its own process, invented facts the base model cannot know).
314
+
315
+ <details>
316
+ <summary><b>Repository layout</b></summary>
317
+
318
+ ```
319
+ sillage/ the tool: core.py (the four mechanisms), runtime.py,
320
+ index.py (grounded retrieval), cli.py
321
+ pyproject.toml packaging: pip install -e . gives you the `sillage` command
322
+ test_unit.py the mechanisms, in five seconds, numpy only
323
+ test_sillage.py the tool, end to end, in its own processes
324
+ .github/ CI: the unit tests and a LaTeX check on every push
325
+ papers/ the four preprints (LaTeX + figures)
326
+ results/ every number in every paper (JSON)
327
+ pipeline/ corpora and frozen-LM passes \
328
+ memory/ the memory systems (papers 1-3) | paper
329
+ fastweights/ the readout adapter (paper 4) | reproduction
330
+ eval/ evaluations, controls, diagnostics |
331
+ figures/ figure generation /
332
+ data/ dumps/ regenerable artifacts (gitignored, ~2 GB)
333
+ ```
334
+
335
+ Version 1.0 merged the two former scripts into one tool: `assistant.py` →
336
+ `sillage read` / `complete`, `papers_assistant.py` → `sillage papers` / `ask`,
337
+ `demo.py` → `sillage demo`. Old `memory_state/` directories are still read.
338
+ The research scripts keep their own bootstrap header, so any of them runs from
339
+ anywhere and resolves `data/`, `dumps/`, `results/` identically.
340
+ </details>
341
+
342
+ <details>
343
+ <summary><b>Caveats worth knowing before you try it</b></summary>
344
+
345
+ - The papers' numbers were measured on two frozen models (GPT-2 124M,
346
+ Qwen3-0.6B) at CPU scale. The tool accepts any causal LM and calibrates
347
+ its readout for it, but nothing here has been measured above 1B parameters,
348
+ and the semantic tier has only been validated on Qwen3.
349
+ - The memory captures **surface repetition**; paraphrase recall is what the
350
+ semantic tier partially addresses, and it remains the open frontier.
351
+ - A fixed matrix saturates at long horizons (~0.5 writes per parameter);
352
+ forgetting (×2.3, `--half-life`) and capacity (×3.4) are the measured
353
+ remedies.
354
+ - `sillage forget <file>` removes a document from the index, not from the
355
+ matrices: Hebbian traces are superposed, so only `--all` or forgetting
356
+ removes those. The tool says so rather than pretending otherwise.
357
+ - The papers' *Manuscripts* stream (unpublished drafts) is not redistributed —
358
+ drop your own documents in `manuscripts/` to run that protocol.
359
+ </details>
360
+
361
+ ## Citation
362
+
363
+ ```bibtex
364
+ @article{sghairi2026sillage,
365
+ title = {Sillage: Surprise-Gated Amplitude Memory
366
+ for Frozen Language Models},
367
+ author = {Sghairi, Abderrahmane},
368
+ year = {2026},
369
+ doi = {10.5281/zenodo.22079016},
370
+ url = {https://doi.org/10.5281/zenodo.22079016}
371
+ }
372
+ ```
373
+
374
+ MIT licensed. Issues and questions welcome.