fluidlayer 2.0.0__tar.gz → 2.2.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.
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/PKG-INFO +30 -31
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/README.md +28 -29
- fluidlayer-2.2.0/deployment/__init__.py +1 -0
- fluidlayer-2.2.0/deployment/session_registry.py +178 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/BLOG.md +3 -4
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/CHANGELOG.md +20 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/COMPARISON.md +4 -4
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/DESIGN_pinned_memory.md +11 -12
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/FRAMEWORK.md +6 -2
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/GETTING_STARTED.md +17 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/GRAFT.md +12 -47
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/MILESTONES.md +9 -16
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/NEGATIVE-RESULTS.md +34 -87
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/POSITIONING.md +9 -23
- fluidlayer-2.2.0/docs/PROCESS.md +347 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/README.md +4 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/REPRODUCING.md +40 -6
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/TEN-MILLION.md +8 -2
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/index.html +3 -3
- fluidlayer-2.2.0/examples/appkit/attach_layer.py +235 -0
- fluidlayer-2.2.0/examples/appkit/load_model.py +63 -0
- fluidlayer-2.2.0/experiments/e15_scale_model.py +317 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e9_ten_million.py +96 -78
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/pretrain_prior.py +5 -3
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/__init__.py +1 -1
- fluidlayer-2.2.0/fluidlayer/qwen2_model.py +156 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/PKG-INFO +30 -31
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/SOURCES.txt +9 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/top_level.txt +1 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/pyproject.toml +3 -3
- fluidlayer-2.2.0/tests/test_qwen2_model.py +26 -0
- fluidlayer-2.2.0/tests/test_session_registry.py +126 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/CITATION.cff +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/LICENSE +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/MANIFEST.in +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/Makefile +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/API.md +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/docs/LIMITATION.md +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/examples/gate_demo.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/examples/graft_demo.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/examples/quickstart.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e10_gate.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e11_exact.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e12_router.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e13_knn_baseline.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e14_real_gpt2.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e1_prior_perplexity.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e2_scaling.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e4_gbalance.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e5_graft.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e6_graft.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e6_real_corpus.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/e7_lossy_fix.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/gpu_graft.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/experiments/run_all.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/autodiff.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/blocks.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/bpe.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/data.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/diffusion.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/gpt2_model.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/gpt2_tokenizer.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/graft.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/losses.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/memory.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/model.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/optim.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/py.typed +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/sheaf.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/topology.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer/torch_graft.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/dependency_links.txt +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer.egg-info/requires.txt +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/fluidlayer_single.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/requirements.txt +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/run_without_pytest.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/selftest.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/setup.cfg +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/conftest.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_bpe.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_graft.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_memory.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_selftest.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_topology.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tests/test_torch_parity.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/_bundle_template.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/bundle_single_file.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/download_gpt2.py +0 -0
- {fluidlayer-2.0.0 → fluidlayer-2.2.0}/tools/fetch_pypi.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fluidlayer
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: A research memory layer for language models
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: A research memory layer for language models with optional GPT-2 and Qwen2 adapters
|
|
5
5
|
Author: Kapil Dhinwa
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/kapildhinwa2010-dev/fluidtech
|
|
@@ -216,14 +216,17 @@ Details: [`docs/TEN-MILLION.md`](docs/TEN-MILLION.md)
|
|
|
216
216
|
## The negative results
|
|
217
217
|
|
|
218
218
|
This is the part that makes the rest believable.
|
|
219
|
-
Eleven
|
|
219
|
+
Eleven negative or unexpected findings are documented. Most have checked-in
|
|
220
|
+
result files; the archived transformer and real-GPT-2 outputs are now
|
|
221
|
+
checked in. Their original model/script environments are not fully included,
|
|
222
|
+
so those historical runs cannot be independently repeated as-is.
|
|
220
223
|
|
|
221
224
|
| What we expected | What happened |
|
|
222
225
|
|---|---|
|
|
223
226
|
| The **topological** selection criterion picks the right facts | It does **not** — `0.000` precision vs `0.970` for plain geometric affinity. |
|
|
224
227
|
| `‖L·1‖∞` detects the frozen state | It does **not** — the frustration index does; the old claim is now a test case. |
|
|
225
228
|
| `λ₂` predicts long-context quality | Barely — Pearson `0.20`. |
|
|
226
|
-
| A transformer loses at some scale |
|
|
229
|
+
| A transformer loses at some scale | The archived run says it **wins**; see `results/train_results.json`. |
|
|
227
230
|
| A domain prior beats a frequency table | It helps (`−0.006 nats`) but a **unigram table wins 6× more**. |
|
|
228
231
|
| The diffusion tier is neutral on a frozen model | It **degrades** the model by **`+0.81 nats`** (fixed → `+0.0000`, but no upside either). |
|
|
229
232
|
| Training the layer helps | It **hurts** — the training-free identity map is better (`0.800 vs 0.650`). |
|
|
@@ -235,16 +238,18 @@ Read it: [`docs/NEGATIVE-RESULTS.md`](docs/NEGATIVE-RESULTS.md)
|
|
|
235
238
|
## The repository
|
|
236
239
|
|
|
237
240
|
```text
|
|
238
|
-
fluidlayer/ the package (
|
|
241
|
+
fluidlayer/ the package (NumPy core; optional PyTorch adapters)
|
|
239
242
|
graft.py the attachable memory layer
|
|
240
243
|
memory.py the frozen register (20 B per fact, bit-exact)
|
|
241
244
|
torch_graft.py the same layer in PyTorch, for a real LLM
|
|
242
245
|
gpt2_model.py a GPT-2 forward pass in PyTorch (no transformers needed)
|
|
243
246
|
gpt2_tokenizer.py the GPT-2 byte-level BPE, in pure Python
|
|
247
|
+
qwen2_model.py an optional PyTorch Qwen2 forward pass
|
|
244
248
|
sheaf.py topology.py diffusion.py blocks.py autodiff.py ...
|
|
245
249
|
|
|
246
250
|
experiments/ every measurement; each writes a JSON into results/
|
|
247
251
|
e14_real_gpt2.py the graft on a REAL pretrained GPT-2 (124M)
|
|
252
|
+
e15_scale_model.py the graft on Qwen2.5-0.5B
|
|
248
253
|
e13_knn_baseline.py the layer vs. the closest prior art (kNN-LM)
|
|
249
254
|
|
|
250
255
|
tools/ the artifact checker, bundler, checkpoint downloader,
|
|
@@ -253,7 +258,7 @@ fluidlayer/bpe.py the byte-level BPE tokenizer
|
|
|
253
258
|
|
|
254
259
|
tests/ the unit tests (pytest, or run them without it)
|
|
255
260
|
|
|
256
|
-
examples/ quickstart.py, graft_demo.py, gate_demo.py
|
|
261
|
+
examples/ quickstart.py, graft_demo.py, gate_demo.py, appkit/
|
|
257
262
|
|
|
258
263
|
docs/ the design docs, the API, the limits, the theory report
|
|
259
264
|
|
|
@@ -273,28 +278,20 @@ and [limitations](docs/LIMITATION.md).
|
|
|
273
278
|
|
|
274
279
|
---
|
|
275
280
|
|
|
276
|
-
##
|
|
281
|
+
## Real-model experiment status
|
|
277
282
|
|
|
278
|
-
GPT-2
|
|
283
|
+
The E14 GPT-2 experiment script, forward implementation, tokenizer, and
|
|
284
|
+
archived `results/e14_real_gpt2.json` are included. The checkpoint is not;
|
|
285
|
+
rerun E14 to independently reproduce the recorded measurements. See
|
|
286
|
+
[the reproduction guide](docs/REPRODUCING.md) for download and run commands.
|
|
279
287
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
On the real model, the classic kNN-LM interpolation beats the gated override by **8×** on perplexity — the opposite of the 131k-parameter model, where the gate won by **7.4×**.
|
|
289
|
-
The gate wins where the base model is weak; interpolation wins where it is strong.
|
|
290
|
-
The capability works on both.
|
|
291
|
-
|
|
292
|
-
The checkpoint, the forward pass and the tokenizer are all in this repository:
|
|
293
|
-
|
|
294
|
-
- [`fluidlayer/gpt2_model.py`](fluidlayer/gpt2_model.py)
|
|
295
|
-
- [`fluidlayer/gpt2_tokenizer.py`](fluidlayer/gpt2_tokenizer.py)
|
|
296
|
-
|
|
297
|
-
No `transformers` dependency, and the generation is verified coherent.
|
|
288
|
+
E15 adds a from-scratch Qwen2.5-0.5B forward implementation and app-kit
|
|
289
|
+
integration. Its archived run records bit-identical attachment, exact recall
|
|
290
|
+
of a planted fact 1,411 tokens back, and a best gated loss improvement of
|
|
291
|
+
`0.041` nats; the kNN-LM baseline improves loss by `0.274` nats, so this run
|
|
292
|
+
does not show the gate outperforming that baseline. The checkpoint is not
|
|
293
|
+
bundled. Reproduction requires the optional GPU dependencies and local model
|
|
294
|
+
weights; see [E15 instructions](docs/REPRODUCING.md#qwen2-scale-experiment-e15).
|
|
298
295
|
|
|
299
296
|
The Torch twin is now tested against the NumPy reference in [`tests/test_torch_parity.py`](tests/test_torch_parity.py): the two implementations must agree on the router, the store and the gated override.
|
|
300
297
|
|
|
@@ -314,7 +311,9 @@ claim or validate production-scale operation.
|
|
|
314
311
|
|
|
315
312
|
## A research prototype, honestly labelled
|
|
316
313
|
|
|
317
|
-
|
|
314
|
+
The core research experiments use small CPU models and character-level or
|
|
315
|
+
512-token subword corpora. E14 and E15 are separate pretrained-model
|
|
316
|
+
experiments; their checkpoints are not included.
|
|
318
317
|
|
|
319
318
|
The subword repeat is the closest thing to a realistic tokenizer in this repository.
|
|
320
319
|
The GPU run, [`experiments/gpu_graft.py`](experiments/gpu_graft.py), is written and has not been executed here.
|
|
@@ -370,7 +369,7 @@ A research memory layer with an operator-level stability bound, a bounded in-mem
|
|
|
370
369
|
## Origin and acknowledgments
|
|
371
370
|
|
|
372
371
|
The project began with the author's idea of flowing information through a
|
|
373
|
-
topological structure. The
|
|
374
|
-
|
|
375
|
-
decisions are documented alongside
|
|
376
|
-
reproduced.
|
|
372
|
+
topological structure. The author describes it as a school project and
|
|
373
|
+
credits AI assistance for implementation and repository organization. The
|
|
374
|
+
research decisions, measurements, and limitations are documented alongside
|
|
375
|
+
the code so the work can be evaluated and reproduced.
|
|
@@ -179,14 +179,17 @@ Details: [`docs/TEN-MILLION.md`](docs/TEN-MILLION.md)
|
|
|
179
179
|
## The negative results
|
|
180
180
|
|
|
181
181
|
This is the part that makes the rest believable.
|
|
182
|
-
Eleven
|
|
182
|
+
Eleven negative or unexpected findings are documented. Most have checked-in
|
|
183
|
+
result files; the archived transformer and real-GPT-2 outputs are now
|
|
184
|
+
checked in. Their original model/script environments are not fully included,
|
|
185
|
+
so those historical runs cannot be independently repeated as-is.
|
|
183
186
|
|
|
184
187
|
| What we expected | What happened |
|
|
185
188
|
|---|---|
|
|
186
189
|
| The **topological** selection criterion picks the right facts | It does **not** — `0.000` precision vs `0.970` for plain geometric affinity. |
|
|
187
190
|
| `‖L·1‖∞` detects the frozen state | It does **not** — the frustration index does; the old claim is now a test case. |
|
|
188
191
|
| `λ₂` predicts long-context quality | Barely — Pearson `0.20`. |
|
|
189
|
-
| A transformer loses at some scale |
|
|
192
|
+
| A transformer loses at some scale | The archived run says it **wins**; see `results/train_results.json`. |
|
|
190
193
|
| A domain prior beats a frequency table | It helps (`−0.006 nats`) but a **unigram table wins 6× more**. |
|
|
191
194
|
| The diffusion tier is neutral on a frozen model | It **degrades** the model by **`+0.81 nats`** (fixed → `+0.0000`, but no upside either). |
|
|
192
195
|
| Training the layer helps | It **hurts** — the training-free identity map is better (`0.800 vs 0.650`). |
|
|
@@ -198,16 +201,18 @@ Read it: [`docs/NEGATIVE-RESULTS.md`](docs/NEGATIVE-RESULTS.md)
|
|
|
198
201
|
## The repository
|
|
199
202
|
|
|
200
203
|
```text
|
|
201
|
-
fluidlayer/ the package (
|
|
204
|
+
fluidlayer/ the package (NumPy core; optional PyTorch adapters)
|
|
202
205
|
graft.py the attachable memory layer
|
|
203
206
|
memory.py the frozen register (20 B per fact, bit-exact)
|
|
204
207
|
torch_graft.py the same layer in PyTorch, for a real LLM
|
|
205
208
|
gpt2_model.py a GPT-2 forward pass in PyTorch (no transformers needed)
|
|
206
209
|
gpt2_tokenizer.py the GPT-2 byte-level BPE, in pure Python
|
|
210
|
+
qwen2_model.py an optional PyTorch Qwen2 forward pass
|
|
207
211
|
sheaf.py topology.py diffusion.py blocks.py autodiff.py ...
|
|
208
212
|
|
|
209
213
|
experiments/ every measurement; each writes a JSON into results/
|
|
210
214
|
e14_real_gpt2.py the graft on a REAL pretrained GPT-2 (124M)
|
|
215
|
+
e15_scale_model.py the graft on Qwen2.5-0.5B
|
|
211
216
|
e13_knn_baseline.py the layer vs. the closest prior art (kNN-LM)
|
|
212
217
|
|
|
213
218
|
tools/ the artifact checker, bundler, checkpoint downloader,
|
|
@@ -216,7 +221,7 @@ fluidlayer/bpe.py the byte-level BPE tokenizer
|
|
|
216
221
|
|
|
217
222
|
tests/ the unit tests (pytest, or run them without it)
|
|
218
223
|
|
|
219
|
-
examples/ quickstart.py, graft_demo.py, gate_demo.py
|
|
224
|
+
examples/ quickstart.py, graft_demo.py, gate_demo.py, appkit/
|
|
220
225
|
|
|
221
226
|
docs/ the design docs, the API, the limits, the theory report
|
|
222
227
|
|
|
@@ -236,28 +241,20 @@ and [limitations](docs/LIMITATION.md).
|
|
|
236
241
|
|
|
237
242
|
---
|
|
238
243
|
|
|
239
|
-
##
|
|
244
|
+
## Real-model experiment status
|
|
240
245
|
|
|
241
|
-
GPT-2
|
|
246
|
+
The E14 GPT-2 experiment script, forward implementation, tokenizer, and
|
|
247
|
+
archived `results/e14_real_gpt2.json` are included. The checkpoint is not;
|
|
248
|
+
rerun E14 to independently reproduce the recorded measurements. See
|
|
249
|
+
[the reproduction guide](docs/REPRODUCING.md) for download and run commands.
|
|
242
250
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
On the real model, the classic kNN-LM interpolation beats the gated override by **8×** on perplexity — the opposite of the 131k-parameter model, where the gate won by **7.4×**.
|
|
252
|
-
The gate wins where the base model is weak; interpolation wins where it is strong.
|
|
253
|
-
The capability works on both.
|
|
254
|
-
|
|
255
|
-
The checkpoint, the forward pass and the tokenizer are all in this repository:
|
|
256
|
-
|
|
257
|
-
- [`fluidlayer/gpt2_model.py`](fluidlayer/gpt2_model.py)
|
|
258
|
-
- [`fluidlayer/gpt2_tokenizer.py`](fluidlayer/gpt2_tokenizer.py)
|
|
259
|
-
|
|
260
|
-
No `transformers` dependency, and the generation is verified coherent.
|
|
251
|
+
E15 adds a from-scratch Qwen2.5-0.5B forward implementation and app-kit
|
|
252
|
+
integration. Its archived run records bit-identical attachment, exact recall
|
|
253
|
+
of a planted fact 1,411 tokens back, and a best gated loss improvement of
|
|
254
|
+
`0.041` nats; the kNN-LM baseline improves loss by `0.274` nats, so this run
|
|
255
|
+
does not show the gate outperforming that baseline. The checkpoint is not
|
|
256
|
+
bundled. Reproduction requires the optional GPU dependencies and local model
|
|
257
|
+
weights; see [E15 instructions](docs/REPRODUCING.md#qwen2-scale-experiment-e15).
|
|
261
258
|
|
|
262
259
|
The Torch twin is now tested against the NumPy reference in [`tests/test_torch_parity.py`](tests/test_torch_parity.py): the two implementations must agree on the router, the store and the gated override.
|
|
263
260
|
|
|
@@ -277,7 +274,9 @@ claim or validate production-scale operation.
|
|
|
277
274
|
|
|
278
275
|
## A research prototype, honestly labelled
|
|
279
276
|
|
|
280
|
-
|
|
277
|
+
The core research experiments use small CPU models and character-level or
|
|
278
|
+
512-token subword corpora. E14 and E15 are separate pretrained-model
|
|
279
|
+
experiments; their checkpoints are not included.
|
|
281
280
|
|
|
282
281
|
The subword repeat is the closest thing to a realistic tokenizer in this repository.
|
|
283
282
|
The GPU run, [`experiments/gpu_graft.py`](experiments/gpu_graft.py), is written and has not been executed here.
|
|
@@ -333,7 +332,7 @@ A research memory layer with an operator-level stability bound, a bounded in-mem
|
|
|
333
332
|
## Origin and acknowledgments
|
|
334
333
|
|
|
335
334
|
The project began with the author's idea of flowing information through a
|
|
336
|
-
topological structure. The
|
|
337
|
-
|
|
338
|
-
decisions are documented alongside
|
|
339
|
-
reproduced.
|
|
335
|
+
topological structure. The author describes it as a school project and
|
|
336
|
+
credits AI assistance for implementation and repository organization. The
|
|
337
|
+
research decisions, measurements, and limitations are documented alongside
|
|
338
|
+
the code so the work can be evaluated and reproduced.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Optional deployment helpers for applications embedding FluidLayer."""
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Bounded, process-local isolation for stateful per-session memory branches.
|
|
2
|
+
|
|
3
|
+
This helper is an application integration aid, not an authentication system
|
|
4
|
+
or a cross-process session store. Applications must derive identifiers from
|
|
5
|
+
their authenticated identity layer and must not expose branch objects beyond
|
|
6
|
+
the lease that owns them.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
from collections.abc import Iterator
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Callable, Generic, Protocol, TypeVar
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Resettable(Protocol):
|
|
21
|
+
"""The minimum interface required from a stateful branch."""
|
|
22
|
+
|
|
23
|
+
def reset(self) -> None:
|
|
24
|
+
"""Clear all state associated with the branch."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
BranchT = TypeVar("BranchT", bound=Resettable)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SessionRegistryError(RuntimeError):
|
|
31
|
+
"""Base exception for session-registry lifecycle errors."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SessionCapacityError(SessionRegistryError):
|
|
35
|
+
"""Raised when the registry is full of unexpired sessions."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SessionBusyError(SessionRegistryError):
|
|
39
|
+
"""Raised when a caller tries to reset or close an active session."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class _Session(Generic[BranchT]):
|
|
44
|
+
branch: BranchT
|
|
45
|
+
last_used: float
|
|
46
|
+
active_leases: int = 0
|
|
47
|
+
lock: threading.RLock = field(default_factory=threading.RLock)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SessionRegistry(Generic[BranchT]):
|
|
51
|
+
"""Manage separate stateful branches for `(tenant_id, session_id)` keys.
|
|
52
|
+
|
|
53
|
+
Each lease holds a per-session lock for its whole duration. This prevents
|
|
54
|
+
concurrent requests from interleaving writes to one branch while allowing
|
|
55
|
+
independent sessions to proceed concurrently. Expired idle sessions are
|
|
56
|
+
removed when the registry is next accessed. Capacity is a hard bound:
|
|
57
|
+
active or unexpired sessions are never silently evicted.
|
|
58
|
+
|
|
59
|
+
The registry is process-local and volatile. For multiple workers, use
|
|
60
|
+
worker affinity or an application-owned session service; do not serialize
|
|
61
|
+
live branch objects and assume they are safe to restore.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
branch_factory: Callable[[], BranchT],
|
|
67
|
+
*,
|
|
68
|
+
max_sessions: int = 1024,
|
|
69
|
+
ttl_seconds: float = 1800.0,
|
|
70
|
+
clock: Callable[[], float] = time.monotonic,
|
|
71
|
+
) -> None:
|
|
72
|
+
if not callable(branch_factory):
|
|
73
|
+
raise TypeError("branch_factory must be callable")
|
|
74
|
+
if isinstance(max_sessions, bool) or not isinstance(max_sessions, int):
|
|
75
|
+
raise TypeError("max_sessions must be an integer")
|
|
76
|
+
if max_sessions <= 0:
|
|
77
|
+
raise ValueError("max_sessions must be greater than zero")
|
|
78
|
+
if isinstance(ttl_seconds, bool) or not isinstance(ttl_seconds, (int, float)):
|
|
79
|
+
raise TypeError("ttl_seconds must be a finite positive number")
|
|
80
|
+
if not math.isfinite(ttl_seconds) or ttl_seconds <= 0:
|
|
81
|
+
raise ValueError("ttl_seconds must be a finite positive number")
|
|
82
|
+
if not callable(clock):
|
|
83
|
+
raise TypeError("clock must be callable")
|
|
84
|
+
|
|
85
|
+
self._branch_factory = branch_factory
|
|
86
|
+
self._max_sessions = max_sessions
|
|
87
|
+
self._ttl_seconds = float(ttl_seconds)
|
|
88
|
+
self._clock = clock
|
|
89
|
+
self._sessions: dict[tuple[str, str], _Session[BranchT]] = {}
|
|
90
|
+
self._lock = threading.RLock()
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _validate_id(value: str, name: str) -> str:
|
|
94
|
+
if not isinstance(value, str):
|
|
95
|
+
raise TypeError(f"{name} must be a string")
|
|
96
|
+
if not value.strip():
|
|
97
|
+
raise ValueError(f"{name} must not be empty")
|
|
98
|
+
return value
|
|
99
|
+
|
|
100
|
+
def _expire_idle_sessions(self, now: float) -> None:
|
|
101
|
+
expired = [
|
|
102
|
+
key
|
|
103
|
+
for key, session in self._sessions.items()
|
|
104
|
+
if session.active_leases == 0
|
|
105
|
+
and now - session.last_used >= self._ttl_seconds
|
|
106
|
+
]
|
|
107
|
+
for key in expired:
|
|
108
|
+
del self._sessions[key]
|
|
109
|
+
|
|
110
|
+
@contextmanager
|
|
111
|
+
def lease(self, tenant_id: str, session_id: str) -> Iterator[BranchT]:
|
|
112
|
+
"""Yield the branch for one identity pair, serialized per session."""
|
|
113
|
+
tenant = self._validate_id(tenant_id, "tenant_id")
|
|
114
|
+
session_name = self._validate_id(session_id, "session_id")
|
|
115
|
+
key = (tenant, session_name)
|
|
116
|
+
now = self._clock()
|
|
117
|
+
|
|
118
|
+
with self._lock:
|
|
119
|
+
self._expire_idle_sessions(now)
|
|
120
|
+
session = self._sessions.get(key)
|
|
121
|
+
if session is None:
|
|
122
|
+
if len(self._sessions) >= self._max_sessions:
|
|
123
|
+
raise SessionCapacityError(
|
|
124
|
+
f"session registry is at capacity "
|
|
125
|
+
f"({self._max_sessions}); retry after sessions expire "
|
|
126
|
+
"or close an idle session"
|
|
127
|
+
)
|
|
128
|
+
session = _Session(self._branch_factory(), last_used=now)
|
|
129
|
+
self._sessions[key] = session
|
|
130
|
+
session.active_leases += 1
|
|
131
|
+
session.last_used = now
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
with session.lock:
|
|
135
|
+
yield session.branch
|
|
136
|
+
finally:
|
|
137
|
+
with self._lock:
|
|
138
|
+
session.active_leases -= 1
|
|
139
|
+
session.last_used = self._clock()
|
|
140
|
+
|
|
141
|
+
def reset_session(self, tenant_id: str, session_id: str) -> bool:
|
|
142
|
+
"""Reset an idle session; return False if it does not exist."""
|
|
143
|
+
key = (
|
|
144
|
+
self._validate_id(tenant_id, "tenant_id"),
|
|
145
|
+
self._validate_id(session_id, "session_id"),
|
|
146
|
+
)
|
|
147
|
+
with self._lock:
|
|
148
|
+
self._expire_idle_sessions(self._clock())
|
|
149
|
+
session = self._sessions.get(key)
|
|
150
|
+
if session is None:
|
|
151
|
+
return False
|
|
152
|
+
if session.active_leases:
|
|
153
|
+
raise SessionBusyError("cannot reset a session with an active lease")
|
|
154
|
+
session.branch.reset()
|
|
155
|
+
session.last_used = self._clock()
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
def close_session(self, tenant_id: str, session_id: str) -> bool:
|
|
159
|
+
"""Remove an idle session; return False if it does not exist."""
|
|
160
|
+
key = (
|
|
161
|
+
self._validate_id(tenant_id, "tenant_id"),
|
|
162
|
+
self._validate_id(session_id, "session_id"),
|
|
163
|
+
)
|
|
164
|
+
with self._lock:
|
|
165
|
+
session = self._sessions.get(key)
|
|
166
|
+
if session is None:
|
|
167
|
+
return False
|
|
168
|
+
if session.active_leases:
|
|
169
|
+
raise SessionBusyError("cannot close a session with an active lease")
|
|
170
|
+
del self._sessions[key]
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def session_count(self) -> int:
|
|
175
|
+
"""Return the number of registered sessions after expiring idle entries."""
|
|
176
|
+
with self._lock:
|
|
177
|
+
self._expire_idle_sessions(self._clock())
|
|
178
|
+
return len(self._sessions)
|
|
@@ -85,10 +85,9 @@ suite.
|
|
|
85
85
|
λ₂ does not predict quality (pearson 0.20). The certificate is true
|
|
86
86
|
about the operator; it says nothing about the task. The claim was
|
|
87
87
|
weakened, not the data massaged.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
which is the honest summary of the entire architecture.
|
|
88
|
+
Archived results record a transformer beating the fluid (2.6331 vs 2.7397
|
|
89
|
+
nats) and a 97.3 % mechanism result. The historical source scripts are not
|
|
90
|
+
included, so see N4 in `docs/NEGATIVE-RESULTS.md` for the reproduction caveat.
|
|
92
91
|
Grafted onto a frozen model, my own diffusion tier made the model
|
|
93
92
|
worse — +0.81 nats (ppl 13.3 -> 29.9) on real English. The cause is
|
|
94
93
|
structural: the persistent state sits outside the autodiff graph, so the
|
|
@@ -6,6 +6,26 @@ uses [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.2.0] - 2026-11-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Include the optional deployment session-isolation companion in the
|
|
14
|
+
published wheel and source distribution.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Add clean-install CI verification for the deployment package.
|
|
19
|
+
|
|
20
|
+
## [2.1.0] - 2026-11-03
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Added the optional PyTorch Qwen2 adapter, E15 scale-model experiment,
|
|
25
|
+
archived measurements, and a runnable app-kit example.
|
|
26
|
+
- Categorized the new project-process, app-kit, experiment, and result files
|
|
27
|
+
into `docs/`, `examples/`, `experiments/`, and `results/`.
|
|
28
|
+
|
|
9
29
|
### Changed
|
|
10
30
|
|
|
11
31
|
- Improved the Markdown documentation structure, added a documentation
|
|
@@ -205,7 +205,7 @@ what makes per-user persistent memory economically possible at all.
|
|
|
205
205
|
| ---|---|---|---|---|---|---|---|---|---|
|
|
206
206
|
| exact needle at a position|✅|✅ recent|✅|❌|❌|❌|❌|✅|✅ pinned / L3|
|
|
207
207
|
| exact specific fact|✅|✅ recent|✅|❌|❌|❌|❌|✅|✅ pinned (20 B)|
|
|
208
|
-
| global gist / aggregate|✅|❌|✅|✅|✅|✅|❌|❌|✅ 97.3 %
|
|
208
|
+
| global gist / aggregate|✅|❌|✅|✅|✅|✅|❌|❌|✅ archived 97.3 % result (`results/mechanism.json`)|
|
|
209
209
|
| positional recall ( "3rd paragraph ")|✅|✅|✅|✅|❌|❌|❌|❌|❌|
|
|
210
210
|
| infinite stream / never re-encode|❌|✅|❌|❌|✅|✅|n/a|✅|✅|
|
|
211
211
|
| no "lost in the middle "|❌|❌|❌|❌|~ (no positions)|~|n/a|~|✅ (no positional decay)|
|
|
@@ -266,12 +266,12 @@ the measured semantic/local-geometric policy performed better.
|
|
|
266
266
|
KV bytes/token, KV @ctx, sessions that fit in VRAM, prefill FLOPs, crossover point
|
|
267
267
|
fluid read cost 16 KB = 0.009 µs; fluid compute = 384 flops/token/layer
|
|
268
268
|
our `selftest.py` → 50/50
|
|
269
|
-
mechanism test:
|
|
270
|
-
LM A/B:
|
|
269
|
+
mechanism test: archived result records 97.3 % sign accuracy; original source unavailable
|
|
270
|
+
LM A/B: archived result records transformer 2.6331 vs FluidLayer 2.7397; original training source unavailable
|
|
271
271
|
param-matched FLOPs: 1.10× cheaper @8K → 2.54× @128K
|
|
272
272
|
⚠️ PROJECTED — needs E1/E5 to become fact
|
|
273
273
|
that the 16 KB fluid carries long-range information at usable quality
|
|
274
|
-
that a real LLM actually routes to the fluid (the 97.3 %
|
|
274
|
+
that a real LLM actually routes to the fluid (the archived 97.3 % synthetic-task result does not establish real-model routing)
|
|
275
275
|
that topology picks the right facts to freeze
|
|
276
276
|
that pinned facts survive a real model's distribution shift
|
|
277
277
|
Every table above is a CONDITIONAL: "IF the fluid is usable, THEN this".
|
|
@@ -45,10 +45,9 @@ This is the entire design in one move:
|
|
|
45
45
|
FluidLayer stops being a memory. It becomes an index.
|
|
46
46
|
The memory moves to a write-once register; the fluid answers where, and
|
|
47
47
|
the register answers what.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
We just gave it an exact value to point at.
|
|
48
|
+
The archived `results/mechanism.json` records a 97.3 % global-aggregation
|
|
49
|
+
result. Its original experiment source is not present, so the historical run
|
|
50
|
+
cannot be independently regenerated (see N4 in `docs/NEGATIVE-RESULTS.md`).
|
|
52
51
|
|
|
53
52
|
## 1. The impossibility, stated precisely
|
|
54
53
|
|
|
@@ -89,7 +88,7 @@ keep.
|
|
|
89
88
|
└──────────────────────────────────────────────────┘
|
|
90
89
|
Slot layout (20 B, as implemented):`key uint64 | val int32 | sig uint32 | anchor uint16 | meta uint16`.
|
|
91
90
|
The read path is three clean steps, each with a guarantee:
|
|
92
|
-
query h ──▶ L2 routes h → anchor a (
|
|
91
|
+
query h ──▶ L2 routes h → anchor a (approximate; archived score in results/mechanism.json)
|
|
93
92
|
anchor a → slot id s (exact: 1 byte)
|
|
94
93
|
L1[s] = (key, token_id) (exact: 4 bytes)
|
|
95
94
|
verify key matches h's context hash (fail-loud)
|
|
@@ -174,12 +173,12 @@ This is a testable prediction, and it is falsifiable.
|
|
|
174
173
|
Which tokens to freeze? The longest bars = the structural landmarks =
|
|
175
174
|
the tokens that carry the document's skeleton. Freeze those and you get
|
|
176
175
|
exact extraction of the load-bearing facts, not random trivia.
|
|
177
|
-
|
|
176
|
+
The theory suggests why diffusion can carry global aggregation: the
|
|
178
177
|
harmonic branch of `L` (its kernel) is the globally dominant H₀ mode —
|
|
179
|
-
and the global mean is the longest bar. The
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
and the global mean is the longest bar. The archived mechanism output records
|
|
179
|
+
a 97.3 % score, beating local aggregators (65.6 % / 71.0 %); its original
|
|
180
|
+
source is unavailable, so the run cannot be independently regenerated. The
|
|
181
|
+
v0.1 theory report and the mechanism experiment now have one
|
|
183
182
|
unified explanation. That is a real, publishable narrative.
|
|
184
183
|
|
|
185
184
|
## 5. Budget: the 3-D trade-off
|
|
@@ -366,8 +365,8 @@ P4 needs one GPU for a few hours.
|
|
|
366
365
|
|
|
367
366
|
Attention remembers by position. We remember by geometry. But geometry alone
|
|
368
367
|
smears: diffusion destroys exactness by construction. So we split the
|
|
369
|
-
memory. A 16 KB sheaf routes
|
|
370
|
-
|
|
368
|
+
memory. A 16 KB sheaf routes to an approximate landmark (the archived
|
|
369
|
+
mechanism result records 97.3 % sign accuracy) — and an append-only frozen register stores the exact token id
|
|
371
370
|
at 14 bytes per fact, 196,234× cheaper than a KV cache. Topology decides
|
|
372
371
|
which landmarks get exact storage, using the document's own persistence
|
|
373
372
|
diagram, at zero per-token cost. The model can promote a fact or recall one
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
> **Historical design note:** this is not a current implementation contract.
|
|
4
4
|
> For current behavior and limitations, see [the API reference](API.md) and
|
|
5
5
|
> [limitations](LIMITATION.md).
|
|
6
|
+
> Archived LM-comparison and mechanism-test JSON outputs are included under
|
|
7
|
+
> `results/`, but their original experiment source files are unavailable.
|
|
8
|
+
> Treat the numbers as historical snapshots, not independently rerunnable
|
|
9
|
+
> measurements.
|
|
6
10
|
|
|
7
11
|
A topological fluid architecture for sequence models: replace the
|
|
8
12
|
transformer's quadratic attention with a cellular-sheaf diffusion whose
|
|
@@ -345,8 +349,8 @@ fluidlayer/
|
|
|
345
349
|
losses.py CE (+ tensor variant), sheaf energy, band decorrelation
|
|
346
350
|
optim.py Adam (decoupled WD, clipping, cosine schedule)
|
|
347
351
|
data.py Gutenberg corpus + char tokenizer + synthetic fallback
|
|
348
|
-
selftest.py
|
|
349
|
-
train.py LM A/B + global-aggregation demo
|
|
352
|
+
selftest.py historical numerical checks
|
|
353
|
+
train.py historical LM A/B + global-aggregation demo (source unavailable; see archived results/)
|
|
350
354
|
|
|
351
355
|
### Run it
|
|
352
356
|
|
|
@@ -106,6 +106,23 @@ integration must handle the model's positional embeddings, attention masks,
|
|
|
106
106
|
device placement, session boundaries, and task-specific gain calibration.
|
|
107
107
|
Call `branch.reset()` when starting a new independent session.
|
|
108
108
|
|
|
109
|
+
## Run the GPT-2/Qwen2 app kit
|
|
110
|
+
|
|
111
|
+
The repository includes a standalone app kit that loads a local GPT-2 or
|
|
112
|
+
Qwen2 checkpoint and demonstrates attachment and recall:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python -m pip install "fluidlayer[gpu]"
|
|
116
|
+
hf download Qwen/Qwen2.5-0.5B --local-dir models/qwen05b
|
|
117
|
+
python examples/appkit/attach_layer.py --model models/qwen05b
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The Qwen2 forward implementation is in the published `fluidlayer` package;
|
|
121
|
+
the Qwen tokenizer is provided by the optional Transformers dependency.
|
|
122
|
+
Model weights are not bundled. This app kit is a research example, not a
|
|
123
|
+
production serving wrapper. See [the E15 result](../results/e15_scale_model.json)
|
|
124
|
+
and [reproduction guide](REPRODUCING.md#qwen2-scale-experiment-e15).
|
|
125
|
+
|
|
109
126
|
## Memory behavior and safety
|
|
110
127
|
|
|
111
128
|
The attachable store is bounded. Its reported 20-byte slot cost is a logical
|